Backbone: Add a "reset" method to Backbone.Model

Created on 31 Jul 2014  ·  22Comments  ·  Source: jashkenas/backbone

I needed to update some of a Model's data that came in from the server. Currently, there are two options: call Model.set, or set Model.attributes directly. I didn't want changes to be recorded, but I also couldn't use silent because I needed the respective views to update. So, I wrote a monkey patch:

Backbone.Model.prototype.reset = (attributes, options) ->
    attrs = attributes || {};
    if options.parse
      attrs = this.parse(attrs, options) || {}

    @set(attrs, options);
    @changed = {};

Wondered why Backbone.Model doesn't have a reset method like Backbone.Collection?

question

Most helpful comment

@thesmart, Model#set does not unset missing attributes which is why I think Model#reset is necessary.

It should be a requirement to give a reason when closing an issue. I'd like to know why @akre54 closed this. Backbone models are deliberately primitive and unopinionated, however, the lack of Model#reset expresses an opinion about how models are used. From http://backbonejs.org/#Getting-started

Philosophically, Backbone is an attempt to discover the minimal set of data-structuring (models and collections) and user interface (views and URLs) primitives that are generally useful when building web applications with JavaScript.

All 22 comments

Reset is an escape hatch, that allows you to easily do efficient rendering in bulk when you know that you need it.

For your case, just use set.

Can you set without making attribute changes dirty?

On Aug 1, 2014, at 7:28 AM, Jeremy Ashkenas [email protected] wrote:

Reset is an escape hatch, that allows you to easily do efficient rendering in bulk when you know that you need it.

For your case, just use set.


Reply to this email directly or view it on GitHub.

Calling Model.clear() before Model.set({}) will work in your case? This way you won't extend the current attributes, instead they will be replaced. Model.clear() also supports silent option if you don't want to trigger two "change" events on the model.

I agree that this method is missing. I also thought about using model.clear() and model.set() in conjunction. Then I ran across the problem, that I trigger the change event twice now.
Using the silent option when calling model.clear() is not an option, because I also want to have a change event fired, when a property gets unset.

A model.reset() method would take a new attributes hash and fill this hash with undefined values for old attributes keys not being present in the new attribute hash.

Model.prototype.reset = function(attrs, options) {
    for (var key in this.attributes) {
        if (key in attrs) continue;
        attrs[key] = void 0;
    }

    return this.set(attrs, options);
};

@lennerd

What about:

Model.prototype.reset = function(attrs, options) {
    for (var key in this.attributes) {
        this.unset(key, {silent:true});
    }
    return this.set(attrs, options);
};

No, this does not help. Imagine you a have a key foo in the current attributes hash of the model, which is not present in the new attrs hash you pass to model.reset(). When I listen to the change:foo event, it won't be triggered with the new value undefined, because we used model.unset() in silent mode.

Backbone.Model.prototype.reset = function(attrs, options) {
    for (var key in this.attributes) {
        this.unset(key, {silent:true});
    }
    return this.set(attrs, options);
};

var bar = new Backbone.Model();

bar.on('change', function(model) {
    console.log('The model bar has been changed.');
});

bar.on('change:foo', function(model, foo) {
    console.log('Foo has been changed to: ' + foo);
});

bar.set(foo, 'test');
// => The model bar has been changed.
// => Foo has been changed to: test

bar.reset({ foo2: 'test2' });
// => The model bar has been changed.
// Foo was resetted but no change event has been triggered.

http://jsfiddle.net/lennerd/3s1Ltwgu/

Cool, I get what you mean. I'd probably opt for using this.unset(key, options) over overriding this.attributes explicitly, but that's just a matter of swapping out attrs[key] = void 0; :panda_face:

Pardon any ignorance I show, as I'm still relatively new to backbone.js, but the behavior being discussed sounds like Model.fetch. The description says:

Resets the model's state from the server

It looks as though change events are still triggered but do not make the model "dirty". Is there a reason this approach cannot be taken when you are looking to reset attributes based on a server response? I would imagine the only such situation this might occur is when a model changes as a side-effect of another operation, but generally such side-effects are considered poor programming and should be avoided if possible. If the side-effect cannot be avoided, perhaps it makes more sense to send down an "update model XYZ" response flag instead of the new model attributes and trigger a fetch whenever you see such a response.

Again, pardon any ignorance I am exhibiting in this comment.

@kolorahl ,

What the OP wants to achieve is to clear the current model attributes and pass a new JSON which becomes the new attributes of the model. In this manner, we don't really want to hit the backend if already have the JSON.

I somehow agree with @lupugabriel1 with his clear + set method. But I think this is one functionality that needs to be considered. Something like Backbone.Collection#reset

The reason I needed this is because the world is changing. Backbone assumes Model#fetch() XHR is the primary method for loading data from the server, but we're doing a lot more stuff using websockets. When data gets pushed to the client, it's redundant to call .fetch and we need a decent way to side-load the data and still get an event hook to trigger.

Why aren't you using #set?

@jridgewell because #set will make the attributes dirty. Let's try using set and see what happens:

function callback(data_from_server) {
  console.info(data_from_server);
  m = new Backbone.Model(data_from_server);
  m.set('foo', 'what?', {silent: true});
  console.info(m.changedAttributes())
}

Actual outcome:

{foo: 'bar'}
{foo: "what?"}

Desired outcome:

{foo: 'bar'}
false

Set is fine for when the state has changed out-of-sync with the server, but Model#reset is needed because there is no way to mark state as synchronize w/ the server.

I ended up writing a different monkey patch for this feature:

/**
 * Allow for side-loading data from the server, calling the sync event afterwards.
 * @param attributes
 * @param options
 */
Backbone.Model.prototype.sync_set = function(attributes, options) {
  var attrs = attributes || {};
  if (options.parse) {
    attrs = this.parse(attrs, options) || {}
  }

  this.set(attrs, options);
  this.changed = {};
  this.trigger('sync', this, attributes, options);
  return this;
}
function callback(data_from_server) {
  console.info(data_from_server);
  m = new Backbone.Model(data_from_server);
  m.set('foo', 'what?', {silent: true});
  console.info(m.changedAttributes())
}
{foo: 'bar'}
false

This probably needs to unset missing items also. Not sure if Model#set(attributes) does that.

As @lennerd pointed out, calling clear() followed by set() is not a good option, because it

1) Fires two change events and
2) If you use silent:true on the clear call, you do not get change events on the attributes which were unset.

collection.reset() is very intuitive and I think that Model could really benefit from an equivalent method. I find myself trying to use model.reset(attrs) all the time, always disappointed when it's not there. :(

I went ahead and created a little extension to add a working reset method to Backbone.Model: Backbone-Model-Reset

Agree - would be useful to have natively. I needed to just reset attributes without mangling the 'id' attribute, as clear() was doing. Here's the gist of it.

@thesmart, Model#set does not unset missing attributes which is why I think Model#reset is necessary.

It should be a requirement to give a reason when closing an issue. I'd like to know why @akre54 closed this. Backbone models are deliberately primitive and unopinionated, however, the lack of Model#reset expresses an opinion about how models are used. From http://backbonejs.org/#Getting-started

Philosophically, Backbone is an attempt to discover the minimal set of data-structuring (models and collections) and user interface (views and URLs) primitives that are generally useful when building web applications with JavaScript.

As far as I can tell, Backbone's data model is incompatible with REST
because there is no way to safely model sever state on the client after a
model has been constructed. Constructing a new model is the only way to get
a fresh state without reset.

On Tuesday, July 5, 2016, pgifford [email protected] wrote:

@thesmart https://github.com/thesmart, Model#set does not unset missing
attributes which is why I think Model#reset is necessary.

It should be a requirement to give a reason when closing an issue. I'd
like to know why @akre54 https://github.com/akre54 closed this.
Backbone models are deliberately primitive and unopinionated, however, the
lack of Model#reset expresses an opinion about how models are used. From
http://backbonejs.org/#Getting-started

Philosophically, Backbone is an attempt to discover the minimal set of
data-structuring (models and collections) and user interface (views and
URLs) primitives that are generally useful when building web applications
with JavaScript.


You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
https://github.com/jashkenas/backbone/issues/3253#issuecomment-230586424,
or mute the thread
https://github.com/notifications/unsubscribe/AARJLQ8-BGeV0X_owHVpyPQAdeiMweNMks5qSriDgaJpZM4CTBHH
.

I needed a model reset and went ahead and made it in the _spirit_ of Backbone. I use this in my personal extension of Backbone.

It's better than a simple .clear followed by a .set because it merges the defaults back into the model, letting any passed attributes to override them like on initialization.

/**
 * Clears the model's attributes and sets the default attributes.
 * @param {Object} attributes to overwrite defaults
 * @param {Object} options  to pass with the "set" call.
 * @return {Backbone.Model}  this object, to chain function calls.
 */
reset: function(attributes, options) {
    options = _.extend({ reset: true }, options);

    // ensure default params
    var defaults = _.result(this, 'defaults'),
        attrs = _.defaults(_.extend({}, defaults, attributes || {}), defaults);

    // apply
    this._reset(attrs, options);

    // triggers a custom event, namespaced to model in order
    // to avoid collision with collection's native reset event
    // when listening to a collection.
    if (!options.silent) this.trigger('model:reset', this, options);

    return this;
},

/**
 * Private method to help wrap reset with a custom behavior in child
 * classes.
 * @param  {Object} attributes to overwrite defaults
 * @param  {Object} options  to pass with the "set" call.
 */
_reset: function(attrs, options) {
    this.clear({ silent: true }).set(attrs, options);
},

It triggers change events (change and change:attribute) in addition to the custom model:reset event if not silent: true. It could easily be customized to trigger only the model:reset event but I think that the change events should always trigger when resetting a model.

i've tweaked the solution of lennerd https://github.com/jashkenas/backbone/issues/3253#issuecomment-58789524 a little bit.

Backbone.Model.prototype.reset = function(attrs, options) {
    var missing = {};
    for (var key in this.attributes) {
        if (key in attrs) continue;
        attrs[key] = undefined;
        missing[key] = true;
    }
    // trigger all change events at the same time
    this.set(attrs, options);
    // remove missing attributes completely
    for (var key in missing) {
        // no silent option here in case  attributes changed again meanwhile (edge case)
        this.unset(key)
    }
    return this;
};
Was this page helpful?
0 / 5 - 0 ratings

Related issues

ghost picture ghost  ·  8Comments

etler picture etler  ·  13Comments

tribalvibes picture tribalvibes  ·  11Comments

jashkenas picture jashkenas  ·  7Comments

inf3rno picture inf3rno  ·  17Comments