Iterate over an array or object with underscore - backbone.js

Why title return null !! , is there any confusion with anything !
Here is my code
_.each(collection.models, function(element, index, list){
console.log(JSON.stringify(element)); //{"title":"Dipped Bunny Blossom","id":49,"created_at":"2015-03-24T10:16:17Z","updated_at":"2015-03-24T13:56:12Z","type":"simple","status":"publish","downloadable":false,"virtual":false,"permalink":"http://beta-it.com/ticarttest/shop/dipped-bunny-blossom/","sku":"","price":"50.00","regular_price":"50.00","sale_price":null,"price_html":"<span class=\"amount\">$50.00</span>","taxable":false,"tax_status":"taxable","tax_class":"","managing_stock":false,"stock_quantity":0,"in_stock":true,"backorders_allowed":false,"backordered":false,"sold_individually":false,"purchaseable":true,"featured":true,"visible":true,"catalog_visibility":"visible","on_sale":false,"weight":"1.00","dimensions":{"length":"50","width":"50","height":"50","unit":"cm"},"shipping_required":true,"shipping_taxable":true,"shipping_class":"","shipping_class_id":null,"description":"","short_description":"","reviews_allowed":true,"average_rating":"4.50","rating_count":2,"related_ids":[34,43,45,47,120],"upsell_ids":[],"cross_sell_ids":[],"parent_id":0,"categories":["BIRTHDAY","BUSINESS GIFTS","DIPPED FRUIT"],"tags":[],"images":[{"id":50,"created_at":"2015-03-03T10:16:10Z","updated_at":"2015-03-03T10:16:10Z","src":"http://beta-it.com/ticarttest/wp-content/uploads/2015/03/Dipped-Bunny-Blossom.jpg","title":"Dipped-Bunny-Blossom","alt":"","position":0}],"featured_src":"http://beta-it.com/ticarttest/wp-content/uploads/2015/03/Dipped-Bunny-Blossom.jpg","attributes":[],"downloads":[],"download_limit":0,"download_expiry":0,"download_type":"","purchase_note":"","total_sales":1,"variations":[],"parent":[]}
console.log(element.id); // return 49 ok
console.log(element.title); //return null !!!!
// We are looping through the returned models from the remote REST API
// Implement your custom logic here
});

For a backbone collection, your code should look more like this:
collection.each(function(model) {
console.log(model.attributes);
console.log(model.id);
console.log(model.get('title'));
});
Most attributes in backbone models must be accessed using the get method. id is a special case that can be accessed via get or as a direct id property. According to the docs:
If you set the id in the attributes hash, it will be copied onto the model as a direct property.

Related

Backbone collection.reset function returns array

I have the following code in backbone view:
getAccounts: function() {
var filteredCollection = this.view.collection.reset(this.view.collection.where({ AccountStatus: 'Open' }));
return filteredCollection;
}
And I assume that this code returns me collection according to doc link http://backbonejs.org/#Collection-reset
But it returns an array. What is wrong here?
The documentation says
Returns the newly-set models
This means you get an array containing newly set models. It doesn't say it returns the collection itself. There is no reason to return the collection itself because you just performed this action on the collection and you already have access to it.
You can just do return this.view.collection instead.

Fetch a backbone collection with only the models with specified value

I have a dictionary of type {name: value}
A = {
name: x,
name: y,
name: z
}
I want to fetch a collection (consisting of models having one of their attribute as 'name'), but to be optimal I want to fetch such that the value of the attribute 'name' exists in my dictionary.
Is there a way to do specific filtering like that?
If you're doing the filtering client-side, overriding the filter method is really NOT the way to go.
Now you no longer have it available, should you need it later. Also, modifying the collection itself from within the filter method is an undesirable sideeffect.
Instead you should be using the parse method, which will automatically be called when fetching the collection.
Now as I understand it, you want to limit the fetched set to models with names matching the keys in your dictionary.
If so, I would do the following:
parse: function(response, options) {
// Do we want to filter the response?
if (options.filterNames) {
// Filter
response = _.filter(response, function(obj) {
// Check if this model name is one of the allowed names
return _.contains(options.filterNames, obj.name);
});
}
// Backbone will use the return value to create the collection
return response;
}
And then call fetch using
someCollection.fetch({filterNames: _.keys(someDictionary)});
If you're certain, you will always be filtering the collection on fetch, you can omit passing the option and just use the dictionary within parse.
Alternatively you could create a fetchFiltered() method on the collection, which would then invoke the line above.
After investigations and trials, here are the two ways this can be resolved:
1. Client side filtering after fetching the collection from the server. This is a less optimal method, especially when the collection is huge. In situations when you really want 5 models out of a 1000 model collection, it can be an overkill. But if the server side has no logic of accepting and using the filtering client side filtering should look something like:
Overload the collection filter code something like:
var filter = {
filter: function() {
var results = _.filter(this.models, function(model) {
// Perform the check on this model, like compare it to your local dict
if (checkPassed) {
return true;
}
return false;
});
results = _.map(results, function(model) {
return model.toJSON();
});
// Reset the existing collection to filtered models
this.reset(results) ;
};
var ExtendedCollection = OriginalCollection.extend(filter);
Pass a filter option in the fetch ajax call to the server, and the server should understand the filter and return the collection based off that.

Backbone Collection get property

Got a server returning a JSON object like so:
{
'key1':'value'
'key2':{
'key2_0':'value'
}
}
And a collection:
var Collection = Backbone.Collection.extend({
url:api.url//which returns the object above
});
var collection = new Collection();
collection.fetch({
success:function(data){
//do something
}
});
Now i need to use certain properties of the collection throughout my application, but say i need key1, i always have to do collection.at(0).get('key1');//returns 'value', because the data returned is stored within the collection, in a new Array at key 0.
Question:
How to directly... collection.get('key1')//now returns undefined... because it is.
I know i could expose an object to the global scope in the collection success function some_other_var = data.toJSON()[0] and access the some_other_var properties directly, but that's not what i'm looking for;
In order to use the get() function from a Backbone.Collection you need to know the model id or cid wanted.
For instance, lets say your data coming from the server is like follow:
[{
id: '123',
name: 'Alex'
}, {
id: '456',
name: 'Jhon'
}]
In that case you can do this:
this.collection.get('123').get('name') // Return "Alex"
Keep in mind that collection is just a set of model, so behind the scenes by doing collection.get() you are getting a model
Tip: If you don't have any kind of id in your server data, there is always the option of using underscore methods:
find
filter
some
contains
etc
It seems like you're trying to ascribe attributes to a collection, but a collection is merely a set of models. Having additional data that is constant throughout the collection suggests that it should be wrapped inside another Model, which is demonstrated here: Persisting & loading metadata in a backbone.js collection

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.

Save Backbone.js Model and update entire Collection

I would like to be able to call save() on a backbone model and have the backend return the entire collection of this model instead of only the changed attributes of the model. I would then like backbone to update the entire returned collection. The use case for this is the following:
A user has multiple addresses and can choose a shipping address from this collection. If she chooses a different shipping address from the collection the previous shipping address should be updated to the state of 'just another plain address'. For this the entire collection has to be updated instead of only the changed model.
Is this somehow possible in backbone.js?
Thanks a lot in advance!
models that are bound to collections, contain their collection parent as a property. Also, since your returning a list of models, we can assume that it is always in a list.
mymodel = Backbone.Model.extend({
parse: function (data) {
if(this.collection && typeof(data) === 'array') {
this.collection.reset(data);
}
return data;
}
});
I do not think that overriding sync or breaking the expectations of what save returns is necessary here.
It would be simpler I guess to override save on the model, something on the lines of:
save: function (key, value, options) {
var p = Model.prototype.save.call(this, key, value, options),
self=this;
if (this.collection) {
p.done(function () { self.collection.fetch(); });
}
return p;
}
which will save using the normal save obtaining its promise, and then if saving was successful and the model is part of a collection, it will fetch the collection from the server.
Another way would be to bind to the model's change event, check if it belongs to a collection and fetch, but that would also happen on set.
Yep, it's possible. You'll need to override the sync function on the model
MyModel = Backbone.Model.extend({
sync: function(method, model) {
if (method === 'create') {
//perform save logic and update the model's collection
} else if (method === 'update') {
//perform save logic and update the model's collection
} else if (method === 'read') {
...
} else if (method === 'destroy') {
...
}
}
});
Take a look at the Backbone.sync function for more information.
What your use case actually calls for is the updating of two models, not the updating of an entire collection. (Other than fetching, collections don't interact with the server in Backbone.) Assuming you have addresses A, B, and C, with A as the current shipping address and C as the new shipping address, your code can simply:
Update C to be the new shipping address.
Update A to be 'just another address.'
If your problem is that you don't know which address is the current address (i.e., address A), then you can just search inside your collection for it (i.e., "give me the current shipping address"), update C, and then update the returned address (address A).
If you absolutely need to update an entire collection (you don't), cycle through collection.models and update each one individually. There simply is no concept in Backbone of a collection being acted upon RESTfully.
Edit: I may have misread your question. If you meant update the collection on the client (i.e., you didn't intend to update the collection on the server), then the code is still similar, you just update both the old model and the new one.

Resources