I couldn't find any instances where this was being done so I'm asking you brainy people out there if there's a nice easy way to do what I'm wanting.
Trying to map two arrays together. One array has a list of the IDs (Foos), and the other array has all the properties (bars) for those IDs. Want to keep everything in my angular controller.
Here's a snippet. I'm trying to match on ID and map into the Name property.
$scope.Foos = $.map($scope.Foos, function (foo) {
return {
ID: foo.ID,
Name: $.grep($scope.bars, function(b){
return b.ID === foo.ID;
}).Name,
Property: $.grep($scope.bars, function(b){
return b.ID === foo.ID;
}).Property
};
});
What I understand is that $.grep will return the object based on the criteria, can I then call properties of that returned object?
Update:
Foos is ID (guid)
Bars is ID(guid), Name & Property
$.grep returns an array, not an object, so to do what you are wanting you would need to do something like:
Name: $.grep($scope.bars, function(b){
return b.ID === foo.ID;
})[0].Name
The problem here however is if there is no match , $.grep will return an empty array and you will end up throwing an error trying to get the first element from that empty array.
You really should look the properties up first, not while trying to build a more complex object
Something like:
$scope.Foos = $.map($scope.Foos, function (foo) {
// first filter the array
var bars = $.grep($scope.bars, function (b) {
return b.ID === foo.ID;
});
// now test we have result, if not make it ... empty string???
var name = bars.length ? bars[0].Name : '';
// do similar for `Property`
return {
ID: foo.ID,
Name: name,
......
};
});
This could also be modified to have some utility functions like getNameFromBars() and put the $.grep in there and return value found or the default.
If you wanted to get rid of jQuery you could use Array.prototype.map() and Array.prototype.filter() to replace $.map and $.grep.
Also $filter could be used.
Related
I have an array of players, each player is an object that has a number of properties, one is "goals".
var players = [
{
"id":"4634",
"name":"A. Turan",
"number":"0",
"age":"28",
"position":"M",
"goals":"1"
},
{
"id":"155410",
"name":"H. Çalhano?lu",
"number":"0",
"age":"21",
"position":"A",
"goals":"0"
},
{
"id":"4788",
"name":"B. Y?lmaz",
"number":"0",
"age":"30",
"position":"A",
"goals":"2",
}
]
I've written a function to cycle through the array and push every element that has more than '0' goals to an array, topScorers. Like so:
$scope.topScorerSearch = function() {
var topScorers = [];
$scope.teamDetails.squad.forEach(function(o) {
if (o.goals > 0) {
topScorers.push(o)
}
});
return topScorers;
}
With the function called as {{topScorerSearch()}}.
This returns only players who have scored. Perfect.
However, I want to run this on other properties, which will result in a lot of repetitious code. How can I make this a general purpose function that can be executed on different properties?
I tried including the 'prop' parameter, but it didn't work:
$scope.topScorerSearch = function(prop) {
var topScorers = [];
$scope.teamDetails.squad.forEach(function(o) {
if (o.prop > 0) {
topScorers.push(o)
}
});
return topScorers;
}
...and called the function like this:
{{topScorerSearch(goals)}}
Why doesn't this work? Where am I going wrong?
I believe the issue is that prop will not resolve to goals because goals is being treated as a variable with a null or undefined value, making prop null or undefined.
If you use the alternative way of accessing object properties object["property"] and use the function {{topScorers("goals")}} it should work out.
I have a small app that users can use to search for a movie, and then add it to their watchlist. Currently it is possible to add 1 movie multple times for the same user. Which ofcourse isn't expected behaviour.
My solution would be to find the unique id of the movie that's being added and crosscheck that with my movies_users data. If the movie_id value exists, do this, else do this.
At the moment I do have the unique movie id of the movie that's being added,
$scope.movieListID = response;
console.log ($scope.movieListID.id)
Which gets ouputted like a string, like so,
314365
And I got the movie records from the current user,
$scope.moviesCheck = response.data;
console.log ($scope.moviesCheck)
Which looks like this,
[{"id":2,"title":"Black Panther", "movie_id":"284054"}]
So what would be a good way to check if the result from $scope.movieListID.id already exists in the $scope.moviesCheck data?
* update *
Trying a suggestion below does not give the expected result.
var exists = function (id) {
$scope.moviesCheck.forEach(function (movie) {
console.log (movie.movie_id)
console.log (id)
if (movie.movie_id === id)
console.log ('duplicate')
else
console.log ('not duplicate')
});
}
exists($scope.movieListID.id);
The console.log output from this is,
312221
312221
not duplicate
Which clearly are duplicate results.
You can add a function in your controller to check if the movie exists in the list
var exists = function (id) {
$scope.moviesCheck.forEach(function (movie) {
if (movie.id === id)
return true;
});
return false;
}
// and call it
exists($scope.movieListID.id); // true or false
I'm not 100% if this is a good way to do this, but for me it works and I think it's pretty low on performance,
movieService.loadMovies().then(function(response) {
$scope.moviesCheck = response.data;
var arr = $scope.moviesCheck
function myIndexOf(o) {
for (var i = 0; i < arr.length; i++) {
if (arr[i].movie_id == o.exisitingMovie_id) {
return i;
}
}
return -1;
}
var checkDuplicate = (myIndexOf({exisitingMovie_id:movie.id}));
if (checkDuplicate == -1) {
From your question I've understood that, based on the object exists using id in the array of object, you have to do different action.
You can use $filter for this. Inject the filter for your controller and assign it to the scope. So this will be available whenever you want in this controller.
$scope.cFilter('filter')($scope.movies, {movie_id:$scope.existingMovie.movie_id}, true);
$sope.movies - is the list of movies passed to the filter. You can
send any list based on your need.
{movie_id:$scope.existingMovie.movie_id} - This one is the object
which one we need to find. This can be based on your need. Since we
are searching movie_id, we need to send the object with property
and value. {movie_id:$scope.existingMovie.movie_id}, Here movie_id is
the property and followed by the value with the colon.
true: This indicates that, to search exact matched values. By default
this is false. If this is set to false, then if we want to search 54
in the movie id, this will returns the objects whichever contains 54
as part of the value.
app.controller('controller', ['$filter',function($filter){
$scope.cFilter= $filter;
$scope.Exists=function(){
$scope.movies=[{"id":2,"title":"Black Panther", "movie_id":"284054"},{"id":3,"title":"Black Panther", "movie_id":"32343"},{"id":4,"title":"Black Panther", "movie_id":"98863"}]
$scope.existingMovie={"id":3,"title":"Black Panther", "movie_id":"32343"};
var _obj=$scope.cFilter('filter')($scope.movies, {movie_id:$scope.existingMovie.movie_id}, true);
if(_obj && _obj[0])
{
Console.log('object exists')
}
else
{
Console.log('Object is not found')
}
}
}])
Many Thanks Jeeva Jsb. This got me on the right track, however I thought I would clarify with a practical example that seems to work as expected.
So I have a function called getData which get the AJAX array but we need to check if the record exist before added to scope (else we get duplicates)
if (d.data.length) {
for (var i = 0; i < d.data.length; i++) {
var doesExist = $filter('filter')($scope.notifications, {NotifId:d.data[i].NotifId}, true);
if (doesExist.length == 0){
$scope.notifications.push(d.data[i]);
}
}
}
This should look familier...
when we are iterating through the returned AJAX object we need to check the ID of the (in my case notificiation)
var doesExist = $filter('filter')($scope.notifications, {NotifId:d.data[i].NotifId}, true);
This line creates a new array by filtering the existing array in scope ($scope.notifications) and passing in the same value from you interation.
If the value exists the object will be copied to the new array called doesExist.
A simple check of length will determine if the record needs to be written.
I hope this helps someone.
I have a 'flat' array with 3 items:
[{"title":"welcome","file":"default.aspx","category":"finance"},
{"title":"test2","file":"test2.aspx","category":"finance"},
{"title":"test1","file":"test1.aspx","category":"housing"}]
The objective is to transform this into a nested observableArray with 2 items:
[{"category":"finance","content":[
{"title":"welcome","file":"default.aspx","category":"finance"},
{"title":"test2","file":"test2.aspx","category":"finance"}]},
{"category":"housing","content":[
{"title":"test1","file":"test1.aspx","category":"housing"}]}]
http://www.knockmeout.net/2011/04/utility-functions-in-knockoutjs.html helped me to extract unique categories in two steps:
self.getcategories = ko.computed(function () {
var categories = ko.utils.arrayMap(self.pages(), function (item) {
return item.category();
});
return categories.sort();
});
self.uniqueCategories = ko.dependentObservable(function() {
return ko.utils.arrayGetDistinctValues(self.self.getcategories()).sort();
});
//uniqueCategories: ["finance","housing"]
However I can't figure out how to create the nested array. I got as far as this:
self.createCategories = ko.computed(function () {
ko.utils.arrayForEach(self.uniqueCategories(), function (item) {
var content = getCategoryContent(item);
var c = new category(item, content);
self.Categories.push(c);
});
return true;
});
function getCategoryContent(whichcategory) {
return ko.utils.arrayFilter(self.pages(), function (page) {
return page.category() === whichcategory;
});
}
It results however in 5 category items (finance 4x, housing 1x) where I expect just 2.
Your computed function createCategories is probably being invoked more than once. e.g. if you are adding items to your pages() array one at a time, this will trigger the createCategories function each time you add an item.
Normally you would make the categories array and return it from a computed function. Currently you are adding to the Categories observable array without clearing it each time.
An easy fix would be to clear out the Categories array at the top of the createCategories function. This would leave you in the odd situation of having to call createCategories at least once to set up the dependencies, but after that it would work automatically when the data changed.
Another option would be create an array and return it from the createCategories functions, then you could just rename the function to Categories and not have an observable array. This would be the way I would normally use computed functions.
On more option would be to just do the work as normal JavaScript functions (rather than computed) and just call createCategories manually when you change the original array (e.g. when you get a result back from your server).
I have an ng-repeat like:
<div ng-repeat="(k, v) in query_p[$index].tags">
I would like it so that if the key (k) is a certain string, say "foo", that string always appears first in the list. It seems the getter option or orderBy only works with arrays. Anyone have an example of how they might achieve this?
Basically you have an unordered object, and you want it to have some kind of order.
To do that you need to create a function that returns some ordered object.
myApp.filter('promote_foo', function() {
return function(object, comp) {
console.log(object);
console.log(comp);
var ordered = [];
for (var key in object) {
var obj = {
key: key,
value: object[key]
};
if (key === comp)
ordered.splice(0,0,obj);
else
ordered.push(obj);
}
console.log(ordered);
return ordered;
};
});
the function takes a parameter which will promote and object if the key matches it. Now I only call it in the controller directly, but you could use it just like any angular filter.
$scope.order = $filter('promote_foo')($scope.data, 'foo');
Also, you can play with the fiddle here.
Hope this helped!
I have a collection self.models. I also have an array of an object which contains the fields and filters I wish to apply to my collection called filterArr. An example of this would be:
[{field: "Account", filter: "123"}, {field: "Owner", filter: "Bob"}]
The question is, I'm not sure quite how I'd iterate through each of my models to return only those models to which this filterArr applies too, I know it has to be something like this, but this is hard-coded:
self.models = _.filter(self.models, function (model) {
model = model.toJSON();
return model.Account === "123" && model.Owner === "Bob";
});
First of all, underscore's filter returns an Array, so what you're doing effectively here is substituting your collection with a filtered array. Something like this would be more appropriate:
this.filtered = _.filter(this.models, ...);
Backbone Collection implements most of underscore's useful functions. So the solution above is far from optimal (in fact it doesn't work at all the way you want it to), instead do something like this:
this.filtered = this.models.filter(function() {...});
The best way to get and set model fields by name are by far the get and set functions of Backbone Model, so why not use them? Model.toJSON() works, but you're just copying the attributes-hash unnecessarily around.
this.filterObj = { // Why not make it an object instead of array of objects
"Account": "123",
"Owner": "Bob"
};
this.filtered = this.models.filter(function(model) {
// use the for in construct to loop the object
for (filter in filterObj) {
// if the model doesn't pass a filter check, then return false
if (model.get(filter) !== filterObj[filter]) return false;
}
// the model passed all checks, return true
return true;
});
Hope this helps!
Basically you need to iterate over model's attributes and compare their keys and values to filter's attributes.
self.models = _.filter(self.models, function (model) {
var fits = true; // does this model "fit" the filter?
model = model.toJSON();
_.each(model, function(modelVal, modelKey) {
_.each(filterArr, function(filter) {
if (modelKey === filter.field && modelVal !== filter.filter) {
fits = false
}
}
})
return fits
})
However, with a bit of underscore magic there's a trickier way. I'm not sure if it's better in terms of performance, but it surely looks better to my eye.
// change a bit the way filter is described:
var filter = {Account: '123', Owner: 'Bob'},
// save an array of filter keys (Account, Owner)
filterKeys = _.keys(filter),
// and an array of its values (123, Bob)
filterVals = _.values(filter)
self.models = _.filter(self.models, function (model) {
// pick a subset of model which has the same keys as filter
var filteredSubset = _.pick(model.attributes, filterKeys),
// pick values of this subset
subsetValues = _.values(filteredSubset)
// this values have to be equal to filter's values
// (use .join() to turn array to string before comparison due to references)
return filteredVals.join() === subsetValues.join()
})
Notice that in the latter case all models have to have all the keys declared in filter.
If I were you and I were looking for a most robust way, I would rewrite the first example, but would have changed _.each to standard for loops and return false as soon as first 'non-fit' value is met.