Monday, September 14, 2009

LiveMonitor - Asynchronous Property Monitor

Today I would like to introduce you a quite uncommon JavaScript trick, a trapped Live Object or, generally speaking, a lightweight monitor able to understand when a generic property has been changed.

About Live Objects

A live object could be described as a particular object able to change without our interaction. The most common live object example is this:

// this is the most common live object
// the HTMLCollection
var divs = document.getElementsByTagName("div");
divs.length; // let's say 4

// let's add another div inside a generic node
document.body.appendChild(
document.createElement("div")
);

divs.length; // 5!

In few words DOM searches are dynamic, which is the reason almost every selector library needs to transform the current result into a static Array.

About LiveMonitor

Specially suited for live objects, LiveMonitor is a function which aim is to notify us when the specified property change:

// LiveMonitor example
var lm = new LiveMonitor(
// the entire list of elements trapped
document.getElementsByTagName("*"),
// the property to monitor
"length"
);

// add one or more notifier
lm.onchange(function(collection){
// this, will be the lm object
this.value; // will be collection.length
collection.length; // is the new length

alert([
"All nodes collection contained ",
this.value,
" nodes but now there are ",
collection.length
].join("\n"));
});

The example is quite silly, but the concept is that as soon as the monitor will check the "length" property, in this case in the one of the trapped collection, it will fire the onchange event, notifying us that somebody did something somewhere, and this something changed our monitored property.

LiveMonitor Code


var LiveMonitor = (function(){

/** LiveMonitor :: Asynchronous Property Monitor
* @author Andrea Giammarchi
* @license Mit Style
* @blog http://WebReflection.blogspot.com/
*/

function LiveMonitor(object, property){
this.value = object[property];
this._property = property;
this._object = object;
this._event = [];
this._i = 0;
};

LiveMonitor.prototype.onchange = function onchange(callback){
this._event.push(callback);
if(this._i === 0){
var _property = this._property,
_object = this._object,
_event = this._event,
self = this
;
this._i = setTimeout(function _i(){
if(self.value !== _object[_property]){
for(var i = 0, length = _event.length; i < length; ++i){
if(_event[i].call(self, _object) === false)
break
;
};
self.value = _object[_property];
};
if(0 < self._i)
self._i = setTimeout(_i, 15)
;
}, 15);
};
};

LiveMonitor.prototype.offchange = function offchange(callback){
for(var _event = this._event, i = 0, length = _event.length; i < length; ++i){
if(_event[i] === callback)
_event.splice(i, 1)
;
};
if(_event.length === 0){
clearTimeout(this._i);
this._i = 0;
};
};

LiveMonitor.prototype.clear = function clear(){
this._event = [];
this.offchange(null);
this.value = this._object[this._property];
};

return LiveMonitor;

})();


Not Only DOM Searches

LiveMonitor could be actually used as asynchronous notifier for any kind of variable.
Let's say we have an environment able to load runtime scripts but we cannot change loaded scripts (external source inclusion).
At some point we press the "load jQuery" button and we would like to be able to be notiied when it is available ...

if(!window.jQuery){

// create a LiveMonitor instance over
// jQuery property
var jqlm = new LiveMonitor(window, "jQuery");

// add onchange event
jqlm.onchange(function(window){

// remove this event
this.offchange(arguments.callee);
// remove jqlm as well, it is not useful
// anymore for this example

// use jQuery, it's here for sure!
$("body").html("Hello LiveMonitor!");
});

// load external script
loadScript("http://external/jQuery.js");
};


Conclusion

With a cross-browser, portable, and lightweight function, we can use notifications without effort over objects, HTMLCollections, Arrays, or everything else we would like to monitor. Let's say this is a sort of asynchronous cross browser Object.prototype.watch, but this time without alchemy.

No comments:

Post a Comment