I have two functions that call fetch on a Backbone model. The first one creates a new instance of a model using an id and calls fetch(), the second one retrieves an existing model instance from a collection using id and calls fetch(). In the first one, the model's parse function is triggered, but not on the second one...and I don't know why.
First one (triggers parse)
App.fetchItemById = function (id) {
App.myItem = new App.Item({id: id});
App.myItem.fetch({
traditional: true,
data: {
elements: 'all',
format:'json',
},
success: function(){
App.myItemView = new App.ItemView({
el: $('#active-item'),
model: App.myItem,
});
App.myItemView.render();
}
});
};
Second one (doesn't trigger parse)
App.fetchItemFromCollectionById = function (id) {
App.myItem = App.myItemCollection.get(id);
App.myItem.fetch({
traditional: true,
data: {
elements: 'all',
format:'json',
},
success: function(){
App.myItemView = new App.ItemView({
el: $('#active-item'),
model: App.myItem,
});
App.myItemView.render();
}
});
};
All of the documentation I've read says that the model's parse function is ALWAYS called on fetch.
Anyone know why parse isn't triggered on the second one?
Here's the model definition:
App.Item = Backbone.Model.extend({
urlRoot: '/api/v1/item/',
defaults: {
},
initialize: function(){
},
parse : function(response){
console.log('parsing');
if (response.stat) {
if (response.content.subitems) {
this.set(‘subitems’, new App.SubitemList(response.content.subitems, {parse:true}));
delete response.content.subitems;
this
}
return response.content;
} else {
return response;
}
},
});
FIXED, THANKS TO EMILE & COREY - SOLUTION BELOW
Turns out, when I first load the App.MyItemCollection, the models in the collection were just generic models, not correctly cast as instances of App.Item. Adding "model: App.Item" to the collection definition solved the problem. See below:
ORIGINAL
App.ItemList = Backbone.Collection.extend({
url: '/api/v1/item/',
parse : function(response){
if (response.stat) {
return _.map(response.content, function(model, id) {
model.id = id;
return model;
});
}
}
});
UPDATED, SOLVED PROBLEM
App.ItemList = Backbone.Collection.extend({
url: '/api/v1/item/',
model: App.Item,
parse : function(response){
if (response.stat) {
return _.map(response.content, function(model, id) {
model.id = id;
return model;
});
}
}
});
parse
is only called on successful fetch
In the Backbone source , we can see that the parse
function is only called when the fetch
async request succeeds.
fetch: function(options) {
options = _.extend({parse: true}, options);
var model = this;
var success = options.success;
// The success callback is replaced with this one.
options.success = function(resp) {
// parse is called only if the fetch succeeded
var serverAttrs = options.parse ? model.parse(resp, options) : resp;
// ...snip...
// then the provided callback, if any, is called
if (success) success.call(options.context, model, resp, options);
};
wrapError(this, options);
return this.sync('read', this, options);
},
The documentation on parse
says:
parse is called whenever a model's data is returned by the server, in
fetch
, andsave
.
Supposing that fetch
fails, no model data is returned by the server, so parse won't be called.
For fetch
to succeed with an existing model, the default behavior requires that the model has an id
in its attributes
hash.
The cid
is only used to distinguish models locally and won't work on its own for a fetch.
A special property of models, the cid or client id is a unique identifier automatically assigned to all models when they're first created. Client ids are handy when the model has not yet been saved to the server, and does not yet have its eventual true id, but already needs to be visible in the UI.
As mentioned by Cory Danielson , if the model
property is not set on the myItemCollection
collection, the default model parse
will be called, not your App.Item
model's parse
.
Models shouldn't be instantiated in the parse
function as it's a responsibility of the collection.
App.ItemList = Backbone.Collection.extend({
model: App.Item, // make sure to set this property
url: '/api/v1/item/',
parse : function(response){
if (response.stat) {
return _.map(response.content, function(model, id) {
model.id = id;
return model;
});
}
}
});
If we don't set the model
property on the collection, anytime we'll use add
or set
on the collection, we'll be facing the same problem where our new models will be Backbone.Model
instances instead of App.Item
instances.
parse
Your parse
function has some code smell you should know about:
parse
shouldn't have any side-effects on the model. It should receive data and return transformed data, that's it.undefined
otherwise.delete
is inefficient and it's better to just return a new object. Anyway, that's another side-effect that you should avoid.That being said, I see that you're nesting a collection within the attributes
hash of your model, within the parsing. I would advise against that pattern for a couple reasons:
parse: true
, and never set subitems
outside the model.default
is now more complex since you have to keep in mind that there's a collection within the attributes.There are multiple solutions to these which I already answered here:
It's possible that your collection isn't using the same model class for it's model property. That's why parse on that particular model would not be called.
If the model
property isn't set to the model that you expect, it's not going to call parse on that model. By default the collection will use a Backbone.Model.
MyItemCollection = Backbone.Collection.extend({
model: App.Item
});
What you might have is something like this
MyItemCollection = Backbone.Collection.extend({
model: Backbone.Model.extend({
urlRoot: '/api/v1/item/'
})
});
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.