Access a Backbone model from a collection.where - backbone.js

I'm using the method where on my Backbone collection like so:
var quote = app.Collections.quotes.where({Id: parseInt(id, 10)});
However, to access the only result/Model (as it's by ID, there's only going to be one) - how can I get the actual Model without resorting to using this:
var onlyModel = quote[0] ?
Is there a better way?

A better way is to use get on the collection. http://backbonejs.org/#Collection-get
var quote = app.Collection.quotes.get(parseInt(id, 10));

Backbone proxies Underscore functions on collections and notably findWhere that will return the first match found.
findWhere _.findWhere(list, properties)
Looks through the list and returns the first value that matches all of the key-value pairs listed in properties.
Your query can be written as
var quote = app.Collections.quotes.findWhere({Id: parseInt(id, 10)});
But in your case, if you are indeed looking for the model with a given id, you can directly use the get method
get collection.get(id)
Get a model from a collection, specified by an id, a cid, or by passing in a model.
var quote = app.Collection.quotes.get(id);

Related

How to create complex query parameters in Restangular

I need to create a fairly complex query string in Restangular.
http://vdmta.rd.mycompany.net//people?anr=Smith&attrs=givenName,displayName,name,cn
How do I do this?
So far I am OK getting as far as ?anr=Smith using this:
return Restangular.all('/people').getList({anr:searchTerm});
The last part attrs=x,y,x lets me control which attributes I want back in the search and could change per request I make.
Any advice appreciated.
Regards
i
You should be able to simply add another query parameter where the value is your comma separated list of attributes.
var attributes = ['givenName' , 'displayName']; // attributes you require for the request
var attributesAsString = attributes.join();
return Restangular.all('/people').getList({
anr : searchTerm,
attrs: attributesAsString
});

BackboneJS - get specific value from Model using .max

So I have this:
var competitionModel = new Competition.CompetitionModel();
competitionModel.contest_id = this.contest_id;
this.insertView('.comp', new Competition.View({model: competitionModel}));
competitionModel.fetch();
So far so good, the Model and its (selected) values are getting display in the <div class="comp">.
Now I want to get a specific value from the same Model, in this case profile_image and it has to be the MAX value from the model. I read something about .max()-method but I dont know how to use it
I have this structure:
<div class="image"></div>
<div class="comp"></div>
1) is it possible? 2) can I use the same methods? like this.insertView('.image', blablab)
So, could anyone help me out?
Ok, judging by your comment the property is an array of things.
You cannot use the backbone max (which only applies to collections) but you can use the underscore max (they are the same thing, in the end, the former is a wrapper for the latter but let's not go into the details). You can see the collection .max() in action here.
You should be able to do something like this:
var max = _.max(competitionModel.get("property"));
Eventually you can pass a function to transform values:
var max = _.max(competitionModel.get("property"), function (element) {
// element is a single item in the list, return a number here.
});
Alternatively you can also use the underscore wrapper like this:
var max = _(competitionModel.get("property")).max(function (e) { ... });
More on max() can be found in the Underscore Docs.

My model ids collide with cids ( eg: "c7" )

I am new to backbone.
After much confusion about not being able to add some of my models to a collection and sometimes getting the wrong model using collection.get(id) I found out that my model ids are colliding with backbones cids.
My model ids are something like "c7" or "c5e6". While the later is no problem "c7" is backbones own cid for the seventh element of the collection.
So if I ask for collection.get('c7') and expect null I instead get the element that was given the cid "c7" by backbone. And if I add an element with id "c7" I will never get it back with get("c7").
I wonder if I am the first one with this problem, I did not find anything about a syntax restriction of backbone ids, is there a way to solve this? As a workaround I will save my own ids in a custom attribute, and have to use collection.where instead of collection.get.
Any better ideas?
If you look at Backbone source code, you will see that the cid in a model is determined in the constructor by
this.cid = _.uniqueId('c');
c is an arbitrary prefix which means you could disambiguate your ids by overriding _.uniqueId, something like
_._uniqueId = _.uniqueId;
_.uniqueId = function(prefix) {
if (prefix === 'c') {
prefix = 'cc';
}
return _._uniqueId(prefix);
};
Without the override : http://jsfiddle.net/nikoshr/KmNSr/ and with it : http://jsfiddle.net/nikoshr/KmNSr/1/
Unfortunately, this does look like an edge case problem with no real solution. Looking at the Backbone source, we can see in the Backbone.Collection.set method that Backbone mixes both your IDs and their internal CIDs in the same object:
set: function(models, options) {
// ...
this._byId[model.cid] = model;
if (model.id != null) this._byId[model.id] = model;
// ...
return this;
},
The _byId object holds all IDs which causes your issue. Here is the Backbone.Collection.get method:
get: function(obj) {
if (obj == null) return void 0;
return this._byId[obj.id != null ? obj.id : obj.cid || obj];
},
When you call it using a non-existent ID (of your own) like "c7", the return ... line becomes return this._byId["c7"];. Since _byId has references to yours and Backbone's IDs, you're getting their entry returned when you expected null.
nikoshr has a great solution in the answer below.

Overwrite properties in angular forEach

I imagine this is an easy thing to do, but I wasnt able to find the information I was looking for through google. I have popupProperties which is just default stuff. I then call to the service which returns specific overrides depending on the popup. How can I iterate through all of the service's overrides and apply them to the popupProperties?
var popupProperties = getDefaultPopupProperties();
var popupOverrides= popupService.getPopupOverrides(currPopupId);
angular.forEach(popupOverrides, function(popupProperty, propertyName){
//replace defaults with popupData's properties
});
You should have a look at the solution of Josh David Miller which uses the extend method of angular (documentation).
var defaults = {name:'John',age:17,weight:55};
var overrides = {name:'Jack',age:28,color:'brown'};
var props = angular.extend(defaults, overrides);
// result
props: {
name:'Jack',
age:28,
weight:55,
color:'brown'
}
The values are copied in the defaults variable. There is no need of using the return value (var props =).
I presume you mean both functions are returning objects with a number of properties (as opposed to an array).
If so, the following should work - just JavaScript, nothing AngularJS specific:
for (var attrname in obj2) { obj1[attrname] = obj2[attrname]; }
See this question for more details How can I merge properties of two JavaScript objects dynamically?

Why does the backbone.js where function return an array of models?

When I use the Backbone.Collection.where function to filter the collection I get an array of models as return value but not an other filtered collection object. So I can't use other collection functions with that.
What is the purpose of such behavior?
where isn't the only method that returns an Array. where returns a new Array because you definitely don't want it mutating your existing Collection automatically. Also, many times you may want the result in Array form.
For whatever reason, the BB devs decided that it was better to return a new Array rather than a new Collection. One thought could be that, perhaps the returned data would be used in a different type of Collection. Another reason could be so that you always know what is returned from one of these methods. 2+ types of collections will ALWAYS return Arrays from these types of methods rather than having to try and inspect via instanceof or something else that isn't very reliable.
Edit
In addition, you COULD make your collections behave in a manner where you return new Collections. Create a base Collection to do something like this:
// Override the following methods
var override = ["where","find",...];
var collectionProto = Backbone.Collection.prototype;
BaseCollection = Backbone.Collection.extend({});
for (var key in collectionProto) {
if (collectionProto.hasOwnProperty(key) && override.indexOf(key) > -1) {
BaseCollection.prototype[key] = function () {
return new this.constructor(collectionProto[key].apply(this, arguments);
};
}
}
Instead over extending off Backbone.Collection, extend off BaseCollection.
Note that you can still use most of the underscore utilities on arrays. Here's how to use each() after a filter()
_.each( MyCollection.filter( filter_fn() {} ), each_fn() {} )

Resources