Backbone.js model's destroy method does not trigger success or error event

MaxiWheat picture MaxiWheat · May 14, 2012 · Viewed 8.5k times · Source

I am beginning to learn Backbone.js and I started with this boilerplate and made an example by loading JSON data from a static file on disk and display it in an html table.

Then I tried to bind an event on a button which is supposed to delete an element from the collection, then from the DOM. The thing works fine, the click triggers the destroy method, the remove event is triggered on the collection, but nothing comes out of the success or error callbacks of destroy

Anybody have a clue ?

The model :

define([
  'underscore',
  'backbone'
], function(_, Backbone) {
  var memberModel = Backbone.Model.extend({
    url: "/members.json",
    defaults: {
      email: "",
      firstname: "",
      lastname: "",
      gender: "",
      language: "",
      consent: false,
      guid: "",
      creationDate: ""
    },
    initialize: function(){
    }

  });

  return memberModel;

});

The view :

define([
  'jquery',
  'underscore',
  'backbone',
  'mustache',
  'collections/members',
  'text!templates/members/page.html'
], function($, _, Backbone, Mustache, membersCollection, membersPageTemplate){
  var membersPage = Backbone.View.extend({
    el: '.page',
    initialize: function(){
        this.members = new membersCollection();

        this.members.on('remove', function(){
                // works fine
            $('.members-body tr').first().remove();
            console.log('removed from collection');
        });
    },

    render: function () {
        var that = this;

        this.members.fetch({success: function(){

            var wrappedMembers = {"members" : that.members.toJSON()};

            that.$el.html(Mustache.render(membersPageTemplate, wrappedMembers));

            $('#delete-member').click(function(){
                that.members.at(0).destroy({ 
                        // prints nothing!!!
                    success: function(){ console.log('sucess'); }, 
                    error: function(){ console.log('error'); }
                });

            });

        }});

    }
  });
  return membersPage;
});

Answer

EBarr picture EBarr · May 15, 2012

I agree that this is odd. I'm not entirely sure what's happening yet, but here is what I suspect...

Your Destroy() calls aren't returning valid JSON.

  • Watching firebug,fiddler, or whatever, what do your destroy() responses look like?
  • I'm also curious what that is when your delete-click function is triggered.
  • Does destroy return false or a jqXHR object?

There is a bit a disconnect in backbone (at least i was for me at first). When calling destroy() or fetch/save for that matter, the call is async. Immediately after the call the delete event fires and your collection can respond. But, digging a bit deeper in the documentation, another event is fired up confirmation of the delete:

and a "sync" event, after the server has successfully acknowledged the model's deletion

So your collection is acting on the presumption that the delete has succeeded. If you want your collection to respond to a confirmed delete, look for the sync event.

This leaves one nagging point -- why isn't your error handler firing? Well, the callbacks are designed to respond to the confirmation of a delete. But I bet the JS call stack doesn't know how to interpret the response it's getting back, as a success or error. You likely stumbled across the realities of AJAX. I've found in backbone, jquery, and bunch of other frameworks that you can confuse the ajax error handling. Google "jquery ajax error not called" and you'll find a host of different scenarios where the error event isn't triggered.


UPDATE

After the comments back and forth...two things are happening. First, your model is perceived as 'new' which means calls to Destroy() don't make server requests. As a result your success/error don't fire. See this commit.

With that said, I don't think you consider your model new (not persisted to the server). You need to do one of two things. Either include a property named id OR in your model map your models ID (guid i assume) to the ID of the model. Mapping is easy by appling the following line to your model: idAttribute: "guid". You can see more on the idAttribute here.