1

My model data is in the format -

{
  feature1: {
    enabled: true,
    default: false
  },
  feature2: {
    enabled: true,
    default: false
  }
}

And somewhere in my code I am setting it as this.model.get("feature1").enabled = false;

Now how do I check if the model has changed attributes when the data format is like the above.

T J
  • 42,762
  • 13
  • 83
  • 138
Ajey
  • 7,924
  • 12
  • 62
  • 86
  • 2
    Would it work to listen to the model 'change' event and use this.model.set('feature1',this.model.get('feature1').enabled = false);? I am not sure the way you currently change the attribute will take advantage of any of backbone's built in features. (If it does, someone will soon have an answer posted :)) [Backbone Model-set](http://backbonejs.org/#Model-set) And this SO question: [Backbone Nested Attributes](http://stackoverflow.com/questions/6351271/backbone-js-get-and-set-nested-object-attribute) – jacob.mccrumb Mar 07 '16 at 21:25
  • Possible duplicate of [Backbone.js get and set nested object attribute](http://stackoverflow.com/questions/6351271/backbone-js-get-and-set-nested-object-attribute) – jacob.mccrumb Mar 07 '16 at 21:28

2 Answers2

1

You can only bind change events in Backbone models to top level attributes.

This can be done here in two ways, either by making this a collection of models where that can be done, or by using a deep model plugin such as https://github.com/kahwee/backbone-deep-model.

So it would become:

var model = new Backbone.DeepModel({
   feature1: {
      enabled: true,
      default: false
   },
   feature2: {
      enabled: true,
      default: false
   }
});

model.on('change:feature1.* change:feature2.*', function(model, val) {
    console.log(val);
});

model.set({
  'feature1.enabled': false
});

jsbin example - https://jsbin.com/gatuyadoji/edit?js,console,output

OllieH
  • 262
  • 1
  • 4
1

Before showing a possible solution let me tell you to consider if individual nested attribute changes are really neccessary. Updating the view upon feature changes might not cost that much time as you've to worry about.

If only one view requires to detect attribute changes in nested model attributes, maybe listening to the change event is enough since you're able to detect changes of individual attributes upon the change event.

The following solutions require you to update the whole attribute like this:

model.set("feature1", _.extend({ }, model.get("feature1"), { enabled: false }));

Please also refer to this good answer to a similar question.

When a Model's change occures, the event gets three aguments: the model, the changed values and the options to set() and the Model stores the previous values - in your case an object of objects - in _previousAttributes.

model.on("change:feature1", function(model, value) {
    var prev = model._previousAttributes;

    if (model.get("feature1").enabled !== prev.feature1.enabled) {
        // update upon change of "enabled"
    }

    if (model.get("feature1").default !== prev.feature1.default) {
        // update upon change of "default"
    }
});

Since this code is a bit bulky for every "feature" you can write a separate helper:

// returns an object which maps a key difference to true to easily check
// using "key" in obj
function objDiff(a, b) {
    var keys = _.union(_.keys(a), _.keys(b)), isDef = {};
    _.each(keys, function(key) {
        if (a[key] !== b[key]) {
            isDef[key] = true;
        }
    });
    return isDef;
}

And check for the attribute change this way:

model.on("change:feature1", function(model, value) {
    var changed = objDiff(model.get("feature1"), model._previousAttributes.feature1);

    if ("enabled" in changed) {
        // update upon change of "enabled"
    }

    if ("default" in changed) {
        // update upon change of "default"
    }
});

Both implementations will also work if you're listening to change only.


Side note: since your question asks for change handlers on certain attributes I assume that you're listening to individual attributes and update parts of the view individually. Consider splitting your model into a map, list or Backbone Collection of Models which only have enabled and default and featurename attributes. Your view can then render and update upon "featureModelX" changes.

var FeatureModel = Backbone.Model({
    defaults: {
        enabled: false,
        default: false
    }
});

new FeatureModel({feature: "feature1"});
Community
  • 1
  • 1
try-catch-finally
  • 7,436
  • 6
  • 46
  • 67