backbone.js: Is there a change since last server-s

2019-03-11 01:37发布

问题:

I have a backbone-Model. With model.set() I can set a local value, with model.save() I can save the whole model to the server.

How do I know, whether there was a change since the last server-save meaning the local version is dirty.

model.isNew(); works only if the model has never been saved to the server.

回答1:


EDIT: This answer was written prior to the 1.0 version of Backbone. As of the current Backbone version (1.2.2) hasChanged no longer reflects "since last save" changes. Instead, it reflects changes "since last set".


Listen for change events or check hasChanged.

If the model has changed, you can save on change. You can even wire your save method to fire when the change event happens.

If you don't want to save on change, then set a property for the model being dirty and clear it when you explicitly save.

Something like:

change: function(){
    this.dirty = true;
}

save: function(){
    // do your save
    if(success){
        this.dirty = false;
    }
}

isDirty: function(){
    return this.dirty
}


回答2:

I'm working with CouchDB and Couch has a _rev attribute that changes after every save success. I solved the problem of "since-last-server-save" by placing the following code in the models initialize function:

     initialize : function() {
        this.on('change',function(){
            if(this.hasChanged('_rev')) {
                this.isSaved = true;
            }else{
                this.isSaved = false;
            }
        },this);
      }


回答3:

Alternate option is to set all your updates to 'silent' updates, and then collect the changes when you want to sync:

// Updates
myModel.set({foo: 'bar'}, {silent: true}); // Does not fire a 'changed' event
myModel.set({foobar: 'barfoo'}, {silent: true});

// Sync
if (myModel.hasChanged()) {
  console.log(myModel.changedAttributes()); // {'foo':'bar', 'foobar':'barfoo'}
  _.each(myModel.changedAttributes(), function(value, key) {
    console.log(key+' used to be '+myModel.previous(key)+', but now is '+value);
  }
  myModel.save();
}


回答4:

All answers suggesting listening for changes (using events) are correct unless you pass { silent: true } option. In that case you need to overwrite default set method in order to save attributes that has changed, and reset that list after calling save method.

MidnightLightning's answer is not correct. If you call set method twice then changedAttributes will return only the attributes that has change since last set call - it's in Backbone documentation:

changedAttributesmodel.changedAttributes([attributes])

Retrieve a hash of only the model's attributes that have changed since the last set, or false if there are none.

In my case I solved the problem with this code:

(function(_, Backbone) {
  'use strict';

  var _set = Backbone.Model.prototype.set,
    _save = Backbone.Model.prototype.save;

  _.extend(Backbone.Model.prototype, {
    set: function(key, val, options) {
      var options = this._getOptions(key, val, options), 
        toReturn = _set.call(this, key, val, options);
      if(!_.isUndefined(options) && options.silent && !!this.changedAttributes()) {
        this.silentChanges = _.extend([], this.silentChanges);
        [].push.apply(this.silentChanges, _.keys(this.changedAttributes()));
      }
      return toReturn;
    },
    save: function(key, val, options) {
      var options = this._getOptions(key, val, options),
        toReturn = _save.call(this, key, val, options);
      if(!_.isUndefined(options) && options.triggerSilents) {
        this.triggerSilentChanges();
      }
      return toReturn;
    },
    unset: function(key, options) {
      if(!_.isUndefined(options) && options.silent) {
        this.silentChanges = _.extend([], this.silentChanges, _.keys(this.changedAttributes()));
      }
    },
    triggerSilentChanges: function() {
      if(!_.isEmpty(this.silentChanges)) {
        var that = this;
        _.each(this.silentChanges, function(key) {
          that.trigger('change:' + key);
          that.silentChanges = _.without(that.silentChanges, key);
        });
        Backbone.Model.prototype.trigger.call(this, 'change');
      }
    },
    _getOptions: function(key, val, options) {
      if(key == null || _.isObject(key)) {
        return val;
      }
      return options;
    }
  });
})(_, Backbone);

If I want to get all changed attributes I use silentChages property inside the model. If I want to trigger event for all set/unset attributes when I save I add 'triggerSilents: true' option. I can also manually trigger all changes event by calling triggerSilentChanges method.



回答5:

Just for reference, I made a little Code Snippet which replaces the default Backbone.sync method.

The replaced Backbone.sync figures out which Attributes has changed since the last save() and it works with both - models and collections.

https://github.com/ChiefORZ/backbone.dirty-sync