Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Ember.js: Observing all object properties

Tags:

ember.js

I would like to observe all changes on an object properties.
In the following example, i would like to be notified by the personChanged observer if firstname or lastname is changed.
BUT I would like to have something generic applied on all object properties (using Ember.keys() ??) How to replace 'firstname', 'lastname' with something more generic ?

In my example:

personChanged is called when firstname or lastname is changed:

 App.MyObject: Ember.Object.create({
   firstname: 'first',
   lastname: 'last',
   personChanged: Ember.observer(function() {
            console.log("person changed");
   }, 'firstname', 'lastname') 
 })
like image 771
fvisticot Avatar asked Dec 29 '12 00:12

fvisticot


1 Answers

This is possible using an ObjectProxy in two flavours, depending on your requirements. Both approaches differ only in when and how many times is the observer called and both of them rely on Ember.keys.

The HTML for both the solutions is the same.

HTML

<script type="text/x-handlebars" data-template-name="app">
    Name: {{App.MyObject.firstname}} {{App.MyObject.lastname}}

    <ul>
    {{#each App.List}}
        <li>{{this}}</li>
    {{/each}}
    </ul>
</script>

Solution 1

JsFiddle: http://jsfiddle.net/2zxSq/

Javascript

App = Em.Application.create();

App.List = [];

App.MyObject = Em.ObjectProxy.create({
    // Your Original object, must be defined before 'init' is called, however.
    content: Em.Object.create({
        firstname: 'first',
        lastname:  'last'
    }),


    // These following two functions can be abstracted out to a Mixin
    init: function () {
        var self = this;
        Em.keys(this.get('content')).forEach(function (k) {
            Em.addObserver(self.get('content'), k, self, 'personChanged')
        });
    },

    // Manually removing the observers is necessary.
    willDestroy: function () {
        var self = this;
        Em.keys(this.get('content')).forEach(function (k) {
            Em.removeObserver(self.get('content'), k, self, 'personChanged');
        });
    },

    // The counter is for illustrative purpose only
    counter: 0,
    // This is the function which is called.
    personChanged: function () {
        // This function MUST be idempotent.
        this.incrementProperty('counter');
        App.List.pushObject(this.get('counter'));
        console.log('person changed');
    }
});

App.ApplicationView = Em.View.extend({
    templateName: 'app'
});

// Test driving the implementation.
App.MyObject.set('firstname', 'second');
App.MyObject.set('lastname',  'last-but-one');

App.MyObject.setProperties({
    'firstname': 'third',
    'lastname' : 'last-but-two'
});

While initialising MyObject, all properties which already exist on the content object are observed, and the function personChanged is called each time any of the property changes. However, since the observers are fired eagerly [1], the function personChanged should be idempotent, which the function in example is not. The next solution fixes this by making the observer lazy.

Solution 2

JsFiddle: http://jsfiddle.net/2zxSq/1/

Javascript

App.MyObject = Em.ObjectProxy.create({
    content: Em.Object.create({
        firstname: 'first',
        lastname:  'last'
    }),


    init: function () {
        var self = this;
        Em.keys(this.get('content')).forEach(function (k) {
            Em.addObserver(self, k, self, 'personChanged')
        });
    },

    willDestroy: function () {
        var self = this;
        Em.keys(this.get('content')).forEach(function (k) {
            Em.removeObserver(self, k, self, 'personChanged');
        });
    },

    // Changes from here 
    counter: 0,
    _personChanged: function () {
        this.incrementProperty('counter');
        App.List.pushObject(this.get('counter'));
        console.log('person changed');
    },

    // The Actual function is called via Em.run.once
    personChanged: function () {
        Em.run.once(this, '_personChanged');
    }
});

The only change here is that the actual observer function is now called only at the end of the Ember Run loop, which might be the behaviour you are looking for.

Other notes

These solutions use ObjectProxy instead of defining the observers on the object itself to avoid setting spurious observers (on properties such as init, willDestroy, etc.) or an explicit list of properties to observe.

This solution can be extended to start observing dynamic properties by overriding the setUnknownProperty on the proxy to add an observer every time a key is added to content. The willDestroy will remain the same.

Reference

[1] This might get changed soon thanks to Asyn Observers

like image 196
musically_ut Avatar answered Jan 31 '23 03:01

musically_ut