Steve.NayLinAung
Steve.NayLinAung

Reputation: 5155

Backbone.js - Update specific attribute on Model does not want to fire "change" event

In the following code,

this.listenTo(this.model, 'change:voteCount', this.changeVoteCount);
this.listenTo(this.model, 'change', this.renderTemplate);

If "voteCount" attribute updated, I want to trigger "this.changeVoteCount" method but not trigger "this.renderTemplate".

I already googling about 2 hours and couldn't find solution. How can I do for this requirement?

Update

(just the explanation for why I choose @try-catch-finally answer)

For people who also meet this problem, I want to clear out the confusion which answer should be chosen as their solution.

I will describe some details to explain why I choose this answer. The following is my model structure:

{
   'name': 'some string',
   'location': 'some string',
   'link': 'some string',
   'voteCount': 0    }

Otherwise 'voteCount' attribute updated, I want to trigger "change" event binded method (in my case, this.renderTemplate).

So I tried the first approach,

this.listenTo(this.model, 'change:name change:location change:link', this.renderTemplate);

In this approach, if I update 'location' and 'name' at once in one place, then the this.renderTemplate method will trigger two times. I want this.renderTemplate method trigger just one time whenever 'location', 'name' or 'link' attributes updated separately or together at the same time.

So finally, I tried the following approach and succeed.

this.listenTo(this.model, 'change', function(model) {
            if (_.isEmpty(_.intersection(_.keys(model.changed), ["voteCount"]))) {
                this.renderTemplate.apply(this, arguments);
            }
        });

Upvotes: 1

Views: 161

Answers (2)

antejan
antejan

Reputation: 2624

If you don't want render on every parameter change, you can specify which of them should trigger render.

this.listenTo(this.model, 'change:param1 change:param2', this.renderTemplate);

Edit: if you don't want to render multiple times on multiple fields change, you can use underscore's debounce:

this.listenTo(this.model, 'change:param1 change:param2', _.debounce(this.renderTemplate));

It will call render only once if it will be called several times in a short time.

Upvotes: 2

try-catch-finally
try-catch-finally

Reputation: 7634

Beside listing all attributes you want to listen for their change mentioned by @antejan, you could do this programatically too.

This way you'd examine the model's changed attribute which contains all attributes that have been changed.

this.listenTo(this.model, 'change', function(model) {
     if (!_.contains(_.keys(model.changed), "voteCount") {
          this.renderTemplate.apply(this, arguments);
     }
});

When blacklisting multiple keys you could also do:

if (_.isEmpty(_.intersection(_.keys(model.changed), ["attr1", "attr2", ...])) { ...

As long as your model does only have a few attributes I'd prefer @antejans way of listing all attributes explicitly since its more clear whats going on:

this.listenTo(this.model, "change:attr1 change:attr2 ...", this.renderTemplate);

Note that there might be a performance penalty when listing multiple change:attr events since Backbone will call the listeners in a for-loop. See the annotated Backbone source of set():

  ...

  if (!silent) {
    if (changes.length) this._pending = options;
    for (var i = 0, l = changes.length; i < l; i++) {
      this.trigger('change:' + changes[i], this, current[changes[i]], options);
    }
  }

  // You might be wondering why there's a `while` loop here. Changes can
  // be recursively nested within `"change"` events.
  if (changing) return this;
  if (!silent) {
    while (this._pending) {
      options = this._pending;
      this._pending = false;
      this.trigger('change', this, options);
    }
  }

Upvotes: 2

Related Questions