Sunday, July 14, 2013

Listen/Watch for object or array changes in Javascript with Watch.JS

I was assigned to project in which it was desired to migrate from a Backbone project to plain jQuery/Javascript project. As you may know, Backbone is a Javascript framework which embodies your application in a MVC platform. The nice thing about a backbone model is that it is an object literal with an array of attributes which you get either with model.attributes or model.get("attribute's name"):
var AppModel = Backbone.Model.extend({

    // Model instance members go here ...

    defaults: {
        //Default model attributes
    },

    initialize: function() {
        // This line will watch for changes in model object and will call the callback
        // i.e. The killer famous line!
        this.listenTo(this, "change", callback);
    }
});

Whenever the model in your backbone application changes you can handle the "change" event in a pretty neat way like this:

object.listenTo(other, event, callback)

As you see the model.listenTo has three arguments: First is the model object, second is the type of event to be captured and finally the callback function which will be fired in case that event will happen. 

So the idea here is to implement the same model.listenTo "change" method on the object literal or the model. I started looking at different stackoverflow discussions and finally made my decision. The solution (well, at least one of best ones to be fair) exists in a well-written library called Watch.JS. The weird thing about this case is that browser have different approaches regarding this. For example there is a sweat property on the Object prototype called Watch. I guess the idea behind Watch.JS is actually coming from there. 

The steps needs to be taken are quite simple. First, add the source of Watch.js, Second create an object literal and third, enable watch for the object/array and attach a callback function to it, like the following chunk of code:
 
   var defaultModelAttributes = {
      personName: "Amir Rahnama"
   };

   //defining a 'watcher' for an attribute of your object literal / array
   watch(defaultModelAttributes, "personName", function() {
     alert("personName value changed!");
   });

   //when changing the attribute its watcher will be invoked
   defaultModelAttributes.personName = "A stupid guy";

You can also look for all changes in the same object literal by changing line 11 with the followings:
watch(defaultModelAttributes, function() { ... });

You can also set how deep you would like your changes to be watched, just pass in a depth token to the watch function after you declare the callback function, as in the following:

var defaultModelAttributes = {
    person: {
        personId: "870718-5768",
        personName: {
            firstName: "Amir",
            lastName: "Rahnama"
        }           
    }
    };

    watch(defaultModelAttributes, function(){
        alert("defaultModelAttributes is changed");
    }, 2);

    defaultModelAttributes.person.personName.lastName = "Akhavan";

UPDATE: I tried to use the library with RequireJS, but I believe that there should be a compatibility issues, there. I do not know the reason yet, but in case you are interested here is the link to issue on WatchJS Github page: Setting an object literal attribute with RequireJS does not trigger the passed callback function.

2 comments: