2

I have a model attribute which is an object:

name : "testing",
orderCondition: {
  minOrderAmount: 20, 
  deliveryCostRequire: "MIN_ORDER_AMOUNT", 
  deliveryCostAmount: 5.55
}

When I use listenTo like this, the render function is not called

this.listenTo(this.model, "change:orderCondition", this.render); // NO FIRING

But when I use listenTo on other attributes, it works.

this.listenTo(this.model, "change:name", this.render); // FIRING

Why listenTo doesn't see changes in nested objects but see them in simple attributes?

Emile Bergeron
  • 17,074
  • 5
  • 83
  • 129
Przemek eS
  • 1,224
  • 1
  • 8
  • 21

2 Answers2

2

A simple way to make the nested object attribute trigger a change event is to replace the whole object with the new one. The most straightforward way with a simple set:

model.set('orderCondition', _.extend({}, model.get('orderCondition'), {
    deliveryCostRequire: "TOTAL_ORDER_AMOUNT"
}));

Making a function to set nested attributes on the model is a good way to encapsulate that complexity.

var Model = Backbone.Model.extend({

    setDeliveryCostRequire: function(value, options) {
        // build a new object for 'orderCondition'
        var newOrderCondition = _.extend({}, this.get('orderCondition'), {
            deliveryCostRequire: value
        });
        // replace 'orderCondition' with the new object.
        this.set({ orderCondition: newOrderCondition }, options);
        // optionally trigger a custom event for it.
        this.trigger('change:deliveryCostRequire', this, value, options);
        return this;
    },
});

This is the basic concept.

Backbone.epoxy is a two-way binding library which also offers computed fields for models which achieves the same as above, but with the additional benefit of being completely transparent from outside the model.

var Model = Backbone.Model.extend({
    computeds: {
        deliveryCostRequire: {
            deps: ['orderCondition'],
            get: function(orderCondition) {
                return orderCondition && orderCondition.deliveryCostRequire;
            },
            set: function(value) {
                return {
                    orderCondition: _.extend({}, this.get('orderCondition'), {
                        deliveryCostRequire: value
                    })
                };
            },
        },
        deliveryCostAmount: { /* ...other computed... */ },
    }
});

With this model, you could do the following:

model.set('deliveryCostRequire', 'TOTAL_ORDER_AMOUNT');
model.get('deliveryCostRequire');
this.listenTo(model, 'change:deliveryCostRequire', this.render);

I also made a simple way to bubble up events of nested models and collections.

Emile Bergeron
  • 17,074
  • 5
  • 83
  • 129
0

Simply because Backbone doesn't work with nested objects. For example you can not set an attribute of an object property via model.set().

Your this.listenTo is only listening to the change of entire object, not it's properties.

You can try using libraries like backbone-deep-model for nested object support.

T J
  • 42,762
  • 13
  • 83
  • 138
  • I can set attribute using model.get like this `this.model.get("orderCondition").minOrderAmount = Some value;` – Przemek eS Jan 17 '17 at 11:10
  • 2
    @ThePrzemyslaw94 `.` is not backbone setter. Backbone only lets you access direct attribute of properties, here `this.model.get("orderCondition")` events and anything else doesn't work below that level by default. – T J Jan 17 '17 at 15:03