简体   繁体   中英

Backbone model fetch() not calling parse

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 , and save .

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.


Make sure a collection uses our own model

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.


Avoid side-effects in 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.
  • It should always return data . In your case, it only returns data under a condition, and returns undefined otherwise.
  • Using delete is inefficient and it's better to just return a new object. Anyway, that's another side-effect that you should avoid.

Be careful with nested models and collections

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:

  • The model becomes really fragile , where you need to be really careful to always call parse: true , and never set subitems outside the model.
  • It creates all the models for the subitems as soon as a model is created, which is inefficient with a lot of models. Lazy initializing the sub-items would help.
  • Using the default is now more complex since you have to keep in mind that there's a collection within the attributes.
  • Saving the model is more complex for the same reason.
  • Changes within the subitems won't trigger events in the parent model.

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM