Client-side full-text search on array of objects - javascript

I have the following example JavaScript array of objects and need to enable users to search on it using words/phrases, returning the objects:
var items = [];
var obj = {
index: 1,
content: "This is a sample text to search."
};
items.push(obj);
obj = {
index: 2,
content: "Here's another sample text to search."
};
items.push(obj);
It's probably efficient to use jQuery's $.grep to perform the search, such as this for a single word:
var keyword = "Here";
var results = $.grep(items, function (e) {
return e.content.indexOf(keyword) != -1;
});
However, how do you search for a phrase in the content field of the objects? For example, searching for the phrase another text won't work using indexOf, because the two words aren't next to each other. What's an efficient way to perform this search in jQuery?

You can use vanilla JS if you're stuck. It does use filter and every which won't work in older browsers, but there are polyfills available.
var items = [];
var obj = {
index: 1,
content: "This is a sample text to search."
};
items.push(obj);
obj = {
index: 2,
content: "Here's another sample text to search."
};
items.push(obj);
function find(items, text) {
text = text.split(' ');
return items.filter(item => {
return text.every(el => {
return item.content.includes(el);
});
});
}
console.log(find(items, 'text')) // both objects
console.log(find(items, 'another')) // object 2
console.log(find(items, 'another text')) // object 2
console.log(find(items, 'is text')) // object 1
(Edit: updated to use includes, and a slightly shorter arrow function syntax).

if you use query-js you can do this like so
var words = phrase.split(' ');
items.where(function(e){
return words.aggregate(function(state, w){
return state && e.content.indexOf(w) >= 0;
});
},true);
if it should just match at least one change the && to || and true to false

Related

Dynamically create array of objects, from array of objects, sorted by an object property

I'm trying to match and group objects, based on a property on each object, and put them in their own array that I can use to sort later for some selection criteria. The sort method isn't an option for me, because I need to sort for 4 different values of the property.
How can I dynamically create separate arrays for the objects who have a matching property?
For example, I can do this if I know that the form.RatingNumber will be 1, 2, 3, or 4:
var ratingNumOne = [],
ratingNumTwo,
ratingNumThree,
ratingNumFour;
forms.forEach(function(form) {
if (form.RatingNumber === 1){
ratingNumOne.push(form);
} else if (form.RatingNumber === 2){
ratingNumTwo.push(form)
} //and so on...
});
The problem is that the form.RatingNumber property could be any number, so hard-coding 1,2,3,4 will not work.
How can I group the forms dynamically, by each RatingNumber?
try to use reduce function, something like this:
forms.reduce((result, form) => {
result[form.RatingNumber] = result[form.RatingNumber] || []
result[form.RatingNumber].push(form)
}
,{})
the result would be object, with each of the keys is the rating number and the values is the forms with this rating number.
that would be dynamic for any count of rating number
You could use an object and take form.RatingNumber as key.
If you have zero based values without gaps, you could use an array instead of an object.
var ratingNumOne = [],
ratingNumTwo = [],
ratingNumThree = [],
ratingNumFour = [],
ratings = { 1: ratingNumOne, 2: ratingNumTwo, 3: ratingNumThree, 4: ratingNumFour };
// usage
ratings[form.RatingNumber].push(form);
try this its a work arround:
forms.forEach(form => {
if (!window['ratingNumber' + form.RatingNumber]) window['ratingNumber' + form.RatingNumber] = [];
window['ratingNumber' + form.RatingNumber].push(form);
});
this will create the variables automaticly. In the end it will look like this:
ratingNumber1 = [form, form, form];
ratingNumber2 = [form, form];
ratingNumber100 = [form];
but to notice ratingNumber3 (for example) could also be undefined.
Just to have it said, your solution makes no sense but this version works at least.
It does not matter what numbers you are getting with RatingNumber, just use it as index. The result will be an object with the RatingNumber as indexes and an array of object that have that RatingNumber as value.
//example input
var forms = [{RatingNumber:5 }, {RatingNumber:6}, {RatingNumber:78}, {RatingNumber:6}];
var results = {};
$.each(forms, function(i, form){
if(!results[form.RatingNumber])
results[form.RatingNumber]=[];
results[form.RatingNumber].push(form);
});
console.log(results);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
HIH
// Example input data
let forms = [{RatingNumber: 1}, {RatingNumber: 4}, {RatingNumber: 2}, {RatingNumber: 1}],
result = [];
forms.forEach(form => {
result[form.RatingNumber]
? result[form.RatingNumber].push(form)
: result[form.RatingNumber] = [form];
});
// Now `result` have all information. Next can do something else..
let getResult = index => {
let res = result[index] || [];
// Write your code here. For example VVVVV
console.log(`Rating ${index}: ${res.length} count`)
console.log(res)
}
getResult(1)
getResult(2)
getResult(3)
getResult(4)
Try to create an object with the "RatingNumber" as property:
rating = {};
forms.forEach(function(form) {
if( !rating[form.RatingNumber] ){
rating[form.RatingNumber] = []
}
rating[form.RatingNumber].push( form )
})

Checking Each Value in For Loop

I've searched for an answer for this but haven't found one that cover this well with a good example.
I have a for loop:
for (var i=0;i<userProfileProperties.length;i++) {
if (userProfileProperties[i].indexOf("ValueImSearchingFor") {
console.log("GOTIT");
}
}
I'm trying to test each value in the loop to see if it contains a certain set of letters. If it doesn't, that value can be dropped. I can't get this to work. I've searched and have found examples but none seem do what I'm trying to do. or at least I've found no "working" example. I'm new to javascript.
So if my values in the loop returned normally would be: Jack User1, Jill User1, and Jerry User2; the values I want returned are all "User1"
I can't get this to work for:
while(userEnumerator.moveNext()){
var oUser = userEnumerator.get_current();
if(oUser.val.indexOf('ValueImsearchingFor') > -1)
{ ... do this} }
Use Array.prototype.filter() method available for arrays as below:
ES5
var res = userProfileProperties.filter(function (val) {
return val.indexOf("ValueImSearchingFor") > -1
});
ES6
let res = userProfileProperties.filter((val) => {
return val.indexOf("ValueImSearchingFor") > -1
});
let userProfileProperties = [
'ValueImSearchingFor 1',
'ValueImSearchingFor 2',
'test',
'ValueImSearchingFor 3',
'test 1'
];
let res = userProfileProperties.filter((val) => {
return val.indexOf("ValueImSearchingFor") > -1
});
console.log(res);

Lodash help on filtering using a search term and multiple property names

I wish to search on multiple columns however all the code I could find on the internet was restricted on a single search term that would search multiple columns. I wish to filter on multiple columns by multiple search terms
data:
var propertynames = ['firstName','lastName'];
var data = [
{
"city":"Irwin town",
"address":"1399 Cecil Drive",
"lastName":"Auer",
"firstName":"Wanda"
},
{
"city":"Howell haven"
"address":"168 Arnoldo Light"
"lastName":"Balistreri",
"firstName":"Renee"
}
];
var searchTerm = 'Wanda Auer';
Should result in an array that filtered out the 2nd object.
Thanks!
I've created two solutions for your question. The first one is do exactly what you need: filters collection by two fields. The second one is more flexible, because it allows filter by any multiple fields.
First solution:
function filterByTwoFields(coll, searchFilter) {
return _.filter(coll, function(item) {
return (item.firstName + ' ' + item.lastName) === searchTerm;
});
}
var data = [
{
"city":"Irwin town",
"address":"1399 Cecil Drive",
"lastName":"Auer",
"firstName":"Wanda"
},
{
"city":"Howell haven",
"address":"168 Arnoldo Light",
"lastName":"Balistreri",
"firstName":"Renee"
}
];
var searchTerm = 'Wanda Auer';
var result = filterByTwoFields(data, searchTerm);
alert(JSON.stringify(result));
<script src="https://raw.githubusercontent.com/lodash/lodash/master/dist/lodash.min.js"></script>
Second solution:
function filterByMultipleFields(coll, filter) {
var filterKeys = _.keys(filter);
return _.filter(coll, function(item) {
return _.every(filterKeys, function(key) {
return item[key] === filter[key];
});
});
}
var data = [
{
"city":"Irwin town",
"address":"1399 Cecil Drive",
"lastName":"Auer",
"firstName":"Wanda"
},
{
"city":"Howell haven",
"address":"168 Arnoldo Light",
"lastName":"Balistreri",
"firstName":"Renee"
}
];
var filter = {
firstName: 'Wanda',
lastName: 'Auer'
}
var result = filterByMultipleFields(data, filter);
alert(JSON.stringify(result));
<script src="https://raw.githubusercontent.com/lodash/lodash/master/dist/lodash.min.js"></script>
Not the most efficent but it does the job. You might want to make an non case sensitive comparison on the property values:
var searchTerm = 'Wanda Auer',
splitted = searchTerm.split(' ');
var result = data.filter(function(item){
return window.Object.keys(item).some(function(prop){
if(propertynames.indexOf(prop) === -1)
return;
return splitted.some(function(term){
return item[prop] === term;
});
});
});
https://jsfiddle.net/3g626fb8/1/
Edit: Just noticed the Lodash tag. If you want to use it, the framework is using the same function names as the array prototype, i.e. _.filter and _.some
Here is a pretty straightforward lodash version:
var matchingRecords = _.filter(data, function (object) {
return _(object)
.pick(propertynames)
.values()
.intersection(searchTerm.split(' '))
.size() > 0;
})
It filters the objects based on if any of the chosen property name values intersect with the search term tokens.

Reconstruct JSON after duplicates have been removed

I have the following JSON -
{
"node1":[
{
"one":"foo",
"two":"foo",
"three":"foo",
"four":"foo"
},
{
"one":"bar",
"two":"bar",
"three":"bar",
"four":"bar"
},
{
"one":"foo",
"two":"foo",
"three":"foo",
"four":"foo"
}
],
"node2":[
{
"link":"baz",
"link2":"baz"
},
{
"link":"baz",
"link2":"baz"
},
{
"link":"qux",
"link2":"qux"
},
]
};
I have the following javascript that will remove duplicates from the node1 section -
function groupBy(items, propertyName) {
var result = [];
$.each(items, function (index, item) {
if ($.inArray(item[propertyName], result) == -1) {
result.push(item[propertyName]);
}
});
return result;
}
groupBy(catalog.node1, 'one');
However this does not account for dupicates in node2.
The resulting JSON I require is to look like -
{
"node1":[
{
"one":"foo",
"two":"foo",
"three":"foo",
"four":"foo"
},
{
"one":"bar",
"two":"bar",
"three":"bar",
"four":"bar"
}
],
"node2":[
{
"link":"baz",
"link2":"baz"
},
{
"link":"qux",
"link2":"qux"
},
]
};
However I cannot get this to work and groupBy only returns a string with the duplicates removed not a restructured JSON?
You should probably look for some good implementation of a JavaScript set and use that to represent your node objects. The set data structure would ensure that you only keep unique items.
On the other hand, you may try to write your own dedup algorithm. This is one example
function dedup(data, equals){
if(data.length > 1){
return data.reduce(function(set, item){
var alreadyExist = set.some(function(unique){
return equals(unique, item);
});
if(!alreadyExist){
set.push(item)
}
return set;
},[]);
}
return [].concat(data);
}
Unfortunately, the performance of this algorithm is not too good, I think somewhat like O(n^2/2) since I check the set of unique items every time to verify if a given item exists. This won't be a big deal if your structure is really that small. But at any rate, this is where a hash-based or a tree-based algorithm would probably be better.
You can also see that I have abstracted away the definition of what is "equal". So you can provide that in a secondary function. Most likely the use of JSON.stringify is a bad idea because it takes time to serialize an object. If you can write your own customized algorithm to compare key by key that'd be probably better.
So, a naive (not recommended) implementation of equals could be somewhat like the proposed in the other answer:
var equals = function(left, right){
return JSON.stringify(left) === JSON.stringify(right);
};
And then you could simply do:
var res = Object.keys(source).reduce(function(res, key){
res[key] = dedup(source[key], equals);
return res;
},{});
Here is my version:
var obj = {} // JSON object provided in the post.
var result = Object.keys(obj);
var test = result.map(function(o){
obj[o] = obj[o].reduce(function(a,c){
if (!a.some(function(item){
return JSON.stringify(item) === JSON.stringify(c); })){
a.push(c);
}
return a;
},[]); return obj[o]; });
console.log(obj);//outputs the expected result
Using Array.prototype.reduce along with Array.prototype.some I searched for all the items being added into the new array generated into Array.prototype.reduce in the var named a by doing:
a.some(function(item){ return JSON.stringify(item) === JSON.stringify(c); })
Array.prototype.some will loop trough this new array and compare the existing items against the new item c using JSON.stringify.
Try this:
var duplicatedDataArray = [];
var DuplicatedArray = [];
//Avoiding Duplicate in Array Datas
var givenData = {givenDataForDuplication : givenArray};
$.each(givenData.givenDataForDuplication, function (index, value) {
if ($.inArray(value.ItemName, duplicatedDataArray) == -1) {
duplicatedDataArray.push(value.ItemName);
DuplicatedArray.push(value);
}
});

underscore find method issue on array of objects

I have following array of objects
var ppl = [
{
name: "John",
content: "<p>description</p>"
},
{
name: "Mike",
content: "<p>Desc</p>"
},
{
name: "Steve",
content: "html"
},
{
name: "Michael",
content: "<p>description</p>"
}
];
What I am doing is to display above array. Then when user clicks on name return his content. Like following
$('a.ppl').on('click', function (e) {
e.preventDefault();
var text = $(this).text();
var content = _.find(ppl, function (desc) { if (desc.name === text) return desc.content; });
console.log(content);
});
What above code does is it finds the content of the person clicked however it returns the entire object of that person e.g. when John is clicked the his entire object {
name: "John",
content: "<p>description</p>"
} is returned by the _.find() function. I just need the content. How can I return content only?
If I were you I would simply do a loop:
var length = ppl.length;
var findcat = function(){
for (var a = 0; a < length; a++) { if(ppl[a].name==text){return ppl[a].content} };
}
var content = findcat();
rather than using underscore.js .
Or if you really want to use underscore.js, change it to this:
var content = _.find(ppl, function (desc) { if (desc.name === text) return desc; });
content = content.content;
and it will work.
Updates (regarding HTML strings in json):
It is okay to store them in json as these HTML strings will simply be considered as normal strings data (just don't forget to escape characters like quotation and forward slash). When real HTML elements are being created from these strings (using jquery functions like .html(string), append(string) ), the browser will need to render these new contents and it may cause a slow performance comparing to leaving all the page-rendering at the start for the browser, but the difference will be pretty subtle. So in terms of performance, it is always okay to have them in json. But in terms of security, you should be careful when there were HTML markup in your data because you are making XSS easier to be accomplished. (Here is a wikipedia article that provides more details on XSS, also known as Cross-site scripting.)
I don't think you need an array here. A simpler and more efficient way would be to use names as properties.
var ppl = {"John": "<p>description</p>", "Mike": "<p>Desc</p>" };
$('a.ppl').on('click', function (e) {
e.preventDefault();
var text = $(this).text();
console.log(ppl[text]);
});
This is the expected Behavior of find operator which returns whole found item ! , why dont use content.content
the _.find looks through each value in the list, returning the first one that passes a truth test, when you return desc.content, it is evalued to true, so the desc object is return. so you can't return inside the find. but you can just access the content as desc.content. here is jsfiddle code:
$('a.ppl').on('click', function (e) {
e.preventDefault();
var text = $(this).text();
var desc = _.find(ppl, function (desc) {
return desc.name === text;
});
console.log(desc.content);
});

Categories