Quality Reads

Saturday, December 23, 2006

watch() :: One of Many Missing JS Functions in IE

I've been playing around with Adobe's Flex for a couple months now and one feature that keeps impressing me is the ease with which you can bind Actionscript variables to UI Elements. I started wondering if there was any way to attach an onchange event to a JavaScript variable so I could replicate the effect with HTML controls. After a little searching on Mozilla.org, I found the watch() function. You can check out the documentation here. Essentially, it will run a function that you define every time a JS variable is changed. Something very useful if you use data access objects to pass information back and forth in your AJAX applications. The problem is that IE doesn't support the method (surprise, surprise). So I set out to create a work around. Here's the resulting function:
 /* Cross Browser Watch Function */
Object.prototype.xwatch = function (prop,func){
if(!Object.watch){
new IEwatch(this,prop,func);
}
else{
this.watch(prop,func);
}
}
var IEwatch = Class.create();
IEwatch.prototype = {
initialize: function(obj,prop,func){
this.obj = obj;
this.prop = prop;
this.func = func;
this.oldvalue = this.obj[this.prop];
setInterval(this.check.bind(this),50);
},
check : function(){
if(this.obj[this.prop] != null && this.obj[this.prop] != this.oldvalue){
this.oldvalue=this.func(this.prop,this.oldvalue,this.obj[this.prop]);
}
}
}


First, I start off by extending the Object Class by adding a new method called xwatch (cross-browser watch). If the browser supports watch() then the native function is used otherwise my IEwatch method is used. IEwatch polls the variable specified looking for a change in the variable's value. Kinda crude but it works. I haven't run into any problems with race situations yet.

I also created the function below to bind a JS variable to another Object. You could use it to bind a variable to a text input or the innerHTML property of a div etc etc.

/* Data Bind Functions */
var dataBinding = Class.create();
dataBinding.prototype = {
initialize: function(sourceObj, sourceProp, destinationObj, destinationProp){
this.srcObj=sourceObj;
this.srcProp=sourceProp;
this.destObj=destinationObj;
this.destProp=destinationProp;
var temp = this.update(this.sourceProp, this.srcObj[this.srcProp],this.srcObj[this.srcProp]);
this.create();
},
create: function(){
this.srcObj.xwatch(this.srcProp,this.update.bind(this));
},
update: function(id, oldvalue, newvalue){
this.destObj[this.destProp] = newvalue;
return newvalue;
}
}

Note, these Classes required prototype.js to function.

2 comments:

LJHarb said...

Here's my version of your code, with watching and unwatching, and the only Prototype method used is bind, which can be replaced with an anonymous function. It uses closures instead of Classes. Also, function isset is below.

function isset(ref) { return ref.toString ? ref.toString().toLowerCase() !== "undefined" : false; }

Object.prototype.xwatch = function (prop, func) {
if (!Object.watch) { this.watchpoint = new IEwatch(this, prop, func); }
else { this.watch(prop, func); }
};
Object.prototype.xunwatch = function (prop) {
if (!Object.unwatch) {
if (this.watchpoint) {
this.watchpoint.unwatch();
delete(this.watchpoint);
}
} else { this.unwatch(prop); }
}
var IEwatch = function (obj, prop, func) {
prop = String(prop);
var _oldvalue, _interval,
self = {
setup: function () {
_oldvalue = obj[prop];
_interval = window.setInterval(self.check.bind(self), 50);
},
check: function () {
if (isset(typeof(obj[prop])) && obj[prop] !== null && obj[prop] !== _oldvalue) {
_oldvalue = func(prop, _oldvalue, obj[prop]);
}
},
unwatch: function () { window.clearInterval(_interval); }
};
if (typeof(func)==="function") { self.setup(); }
return self;
};

Brad Neuberg said...

IE supports the onpropertychange custom event for Microsoft Behavior/HTC elements. That is one way to emulate the watch() method there.