easiest way to search a javascript object list with jQuery? - javascript

What is the easiest way to search a javascript object list with jQuery?
For example, I have the following js config block defined:
var ProgramExclusiveSections =
{
"Rows":
[
{ 'ProgramId': '3', 'RowId': 'trSpecialHeader'},
{ 'ProgramId': '3', 'RowId': 'trSpecialRow1' },
{ 'ProgramId': '3', 'RowId': 'trSpecialRow2' },
{ 'ProgramId': '1', 'RowId': 'trOtherInfo' }
]
}
The user has selected Program ID = 3 so I want to get only the "rows" that I have configured in this js config object for Program ID = 3. This will get me the javascript object list:
var rows = ProgramExclusiveSections.Rows
but then I need to filter this down to only where RowId = 3. What's the easiest way for me to do this with jquery?

$.grep()
var matches = $.grep(rows, function (elt)
{
return elt.ProgramId === '3';
});

$.map() would do it (although $.grep() is more elegant).
var objs= $.map(ProgramExclusiveSections.Rows, function(obj, index) {
return obj.RowId === "3"? obj : null;
});
This will return an array of the objects with RowId "3" (notice you have a string and not a number).

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 )
})

Create an array of Objects from JSON

I have a JSON file like below:
[
{"fields":{category_class":"CAT2",category_name":"A"},"pk":1 },
{"fields":{category_class":"CAT1",category_name":"B"},"pk":2 },
{"fields":{category_class":"CAT1",category_name":"C"},"pk":3 },
{"fields":{category_class":"CAT2",category_name":"D"},"pk":4 },
{"fields":{category_class":"CAT3",category_name":"E"},"pk":5 },
{"fields":{category_class":"CAT1",category_name":"E"},"pk":6 },
]
I want to create an array of objects from the above JSON which will have two properties. i) CategoryClass ii) CategoryNameList. For example:
this.CategoryClass = "CAT1"
this.CategoryNameList = ['B','C','E']
Basically i want to select all categories name whose category class is CAT1 and so forth for other categories class. I tried this:
var category = function(categoryClass, categoryNameList){
this.categoryClass = categoryClass;
this.categoryList = categoryNameList;
}
var categories = [];
categories.push(new category('CAT1',['B','C','E'])
Need help.
You can use a simple filter on the array. You have a few double quotes that will cause an error in you code. But to filter only with CAT1 you can use the filter method
var cat1 = arr.filter( value => value.fields.category_class === "CAT1");
I would suggest this ES6 function, which creates an object keyed by category classes, providing the object with category names for each:
function groupByClass(data) {
return data.reduce( (acc, { fields } ) => {
(acc[fields.category_class] = acc[fields.category_class] || {
categoryClass: fields.category_class,
categoryNameList: []
}).categoryNameList.push(fields.category_name);
return acc;
}, {} );
}
// Sample data
var data = [
{"fields":{"category_class":"CAT2","category_name":"A"},"pk":1 },
{"fields":{"category_class":"CAT1","category_name":"B"},"pk":2 },
{"fields":{"category_class":"CAT1","category_name":"C"},"pk":3 },
{"fields":{"category_class":"CAT2","category_name":"D"},"pk":4 },
{"fields":{"category_class":"CAT3","category_name":"E"},"pk":5 },
{"fields":{"category_class":"CAT1","category_name":"E"},"pk":6 },
];
// Convert
var result = groupByClass(data);
// Outut
console.log(result);
// Example look-up:
console.log(result['CAT1']);
Question : Basically i want to select all categories name whose category class is CAT1 and so forth for other categories class
Solution :
function Select_CatName(catclass,array){
var CatNameList=[]
$(array).each(function(){
if(this.fields.category_class==catclass)
CatNameList.push(this.fields.category_name)
})
return CatNameList;
}
This function return the Desired Category Name List, you need to pass desired catclass and array of the data , as in this case it's your JSON.
Input :
Above function calling :
Output :
Hope It helps.

Getting sum() of an attribute in loopback without looping through all instances of the object

I have a model DummyModel that has the attributes attOne, attTwo and attThree.
To get all the instances of attOne, one can use-
DummyModel.find({where: {attTwo: 'someValue'}, fields: {attOne: true} });
The above query corresponds more or less to the MySQL query -
select attOne from DummyModel where attTwo = 'someValue'
However, I need to find the sum of all the attOne values returned from the above query. That is, the MySQL equivalent of -
select sum(attOne) from DummyModel where attTwo = 'someValue'
I read that loopback doesn't support aggregrate functions (i.e. groupby). But is there any way to getsum(attOne)?
I know one way is to get the object and then loop over all the instances and add it.
What I want to know is if there's any pre-existing loopback method to do the same.
Supposing that this code
f = DummyModel.find({where: {attTwo: 'someValue'}, fields: {attOne: true} });
returns an array like this
[
{attTwo: 'someValue' ,attOne: 1}
{attTwo: 'otherValue',attOne: 1}
]
you can use the reduce function to apply a function to all elements
var sum = f.reduce(function(last, d) {return d.attOne + last},0);
And here is the working code
DummyModel = {
find: function(a) {
return [{
attTwo: 'someValue',
attOne: 1
}, {
attTwo: 'otherValue',
attOne: 2
}];
}
}
f = DummyModel.find({
where: {
attTwo: 'someValue'
},
fields: {
attOne: true
}
});
sum = f.reduce(function(last, d) {
return d.attOne + last;
}, 0);
alert(sum);

AngularJS: Merge object by ID, i.e. replace old entry when IDs are identical

I am using Ionic with AngularJS and I am using a localForage database and AJAX via $http. My app has a news stream that contains data like this:
{
"feed":[
{
"id":"3",
"title":"Ein Hund",
"comments:"1"
},
{
"id":"2",
"title":"Eine Katze",
"comments":"2"
}
],
"ts":"20150907171943"
}
ts stands for Timestamp. My app saves the feed locally via localForage.
When the app starts it first loads the locally saved items:
$localForage.getItem("feed").then(function(val) { vm.feed = val; })
Then, it loads the new or updated items (ts < current timestamp) and merges both the old and new data:
angular.extend(vm.feed, response.data.feed);
Updated items look like this:
{
"feed":[
{
"id":"2",
"title":"Eine Katze",
"comments":"4"
}
],
"ts":"20150907171944"
}
That is, the comments count on feed item 2 has changed from 2 to 4. When I merge the old and new data, vm.feed has two items with id = 2.
Does angularjs has a built-in "merge by id" function, i. e. copy from source to destination (if it is a new element), or otherwise replace the old element? In case angularjs does not have such a function, what's the best way to implement this?
Thanks in advance!
angular.merge(vm.feed, response.data.feed);
// EDIT
Probably, it will not merge correctly, so you have to update all properties manually. Update ts property and then find your object with id and replace it.
There is no builtin, I usually write my own merge function:
(function(){
function itemsToArray(items) {
var result = [];
if (items) {
// items can be a Map, so don't use angular.forEach here
items.forEach(function(item) {
result.push(item);
});
}
return result;
}
function idOf(obj) {
return obj.id;
}
function defaultMerge(newItem, oldItem) {
return angular.merge(oldItem, newItem);
}
function mergeById(oldItems, newItems, idSelector, mergeItem) {
if (mergeItem === undefined) mergeItem = defaultMerge;
if (idSelector === undefined) idSelector = idOf;
// Map retains insertion order
var mapping = new Map();
angular.forEach(oldItems, function(oldItem) {
var key = idSelector(oldItem);
mapping.set(key, oldItem);
});
angular.forEach(newItems, function(newItem) {
var key = idSelector(newItem);
if (mapping.has(key)) {
var oldItem = mapping.get(key);
mapping.set(key, mergeItem(newItem, oldItem));
} else {
// new items are simply added, will be at
// the end of the result list, in order
mapping.set(key, newItem);
}
});
return itemsToArray(mapping);
}
var olds = [
{ id: 1, name: 'old1' },
{ id: 2, name: 'old2' }
];
var news = [
{ id: 3, name: 'new3' },
{ id: 2, name: 'new2' }
];
var merged = mergeById(olds, news);
console.log(merged);
/* Prints
[
{ id: 1, name: 'old1' },
{ id: 2, name: 'new2' },
{ id: 3, name: 'new3' }
];
*/
})();
This builds a Map from the old items by id, merges in the new items, and converts the map back to list. Fortunately the Map object will iterate on the entries in insertion order, according to the specification. You can provide your idSelector and mergeItem functions.
Thanks hege_hegedus. Based on your code, I've written my own and tried to use less loops to speed things up a bit:
function updateCollection(localCollection, fetchedCollection) {
angular.forEach(fetchedCollection, function(item) {
var append = true;
for (var i = 0; i < localCollection.length; i++) {
if (localCollection[i].id == item.id) {
// Replace item
localCollection[i] = item;
append = false;
break;
} else if (localCollection[i].id > item.id) {
// Add new element at the right position, if IDs are descending check for "< item.id" instead
localCollection.splice(i, 0, item);
append = false;
break;
}
}
if (append) {
// Add new element with a higher ID at the end
localCollection.push(item);
// When IDs are descending use .unshift(item) instead
}
});
}
There is still room for improvements, i. e. the iteration through all the objects should use binary search since all items are sorted by id.

Client-side full-text search on array of objects

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

Categories