Monday, 20 April 2015

How Backbone.Model.isNew works

I'm using Backbone in one of my current projects for a rich and responsive UI. When creating a new data item client side, I sometimes need to do a server call to get the data for the new model because setting the defaults in the new model requires logic that is only found on the server.  But (importantly) the model is still "new" at this point; it hasn't been persisted to the server.  So two server round trips are required to create the object:

It would be possible to create the model on the first call to the server and then update it on the second call, but that sets up a slightly different workflow in several ways:

  1. If the user wants to abandon the creation in step (3) we need to delete the model, or inform the user that they need to do this.
  2. If operations are auditing this would be audited as a create and an update rather than simply a create.
  3. This wouldn't work at all if some values can only be set at creation time.
So I have gone with the two stage "get defaults, commit" process shown above.

This leads to a problem; when I parse the model returned from the server at step 2 as a Backbone Model I find that Model.isNew() returns false. This means that when I commit the model through a Model.save() an "update" operation occurs instead of a "create"  Why is this?  Let's look at the definition of isNew from backbone.js:


    // A model is new if it has never been saved to the server, and lacks an id.
    isNew: function() {
      return !this.has(this.idAttribute);
    },

My model has the default value for idAttribute, "id". The id of my model on the server is a .NET
Guid, so when the server model is serialised to be returned in step 2 it will be serialised as:


{id: "00000000-0000-0000-0000-000000000000", title: null, anotherAtt: "Clever server logic set this"}

As this model does have a value for "id", Model.has("id") will return true and Model.isNew() will return false.
Help is at hand though: when I define my model I can simply override the isNew() function:


var MyModel = Backbone.Model.extend({
 isNew: function() {
  return !this.has(this.idAttribute) || this.id === '00000000-0000-0000-0000-000000000000';
 }
});

Or alternatively if I'm going to use this behaviour throughout my application I can modify this behaviour on the Model prototype:


    Backbone.Model.prototype.isNew = function () {
        return typeof this.attributes.id !== 'undefined' && this.attributes.id === '00000000-0000-0000-0000-000000000000';
    };

Either way, Backbone will now correctly distinguish between models that have been persisted and those that have not.

No comments:

Post a Comment