I recently came across a jQuery plugin that claims to emulate the functionality of Object.prototype.watch
, a method inherent in all objects that enables you to watch for a property change and run a function upon that change. Of course, this isn’t available in all implementations. IE, for example, doesn’t support this but has a DOM equivalent, onPropertyChange
. Both methods are useful but their slightly different functionality makes it impossible to use either of them to create a unified solution. Again we’re left to resort to the lowest common denominator, setInterval
.
The jQuery plugin I mentioned attempts to use watch()
if it’s available, otherwise it uses setInterval
, repeatadly iterating through a stack of objects checking for changes. Even this attempt at a unified solution is futile because the functionality behind watch()
is fundamentally unachievable with cross-browser JavaScript. According to this MDC documentation, the watch
method not only acts as a type of event registrar for property-changes but also has the capability to define the property’s value (following the attempted change). In other words, the psuedo-event from watch()
will fire before the property is actually changed, not after – the return value from the handler becomes the actual value of that property. Therefore, any attempts at emulation (for example, with setInterval
) will not succeed since we’re only able to fire our psuedo-event after the change.
The following example shows how you would use watch()
– at no time does the property actually change to ‘456’, it’s constantly ‘123’:
var obj = { prop: 123 }; obj.watch('prop', function(propertyName, oldValue, newValue){ return oldValue; }); obj.prop = 456; alert(obj.prop); // 123 |
Additionally, watch()
does not recognise internal property changes (within the DOM). If you setup a watch on the value
property of an input
element, even when you type into that field, your watch-handler will not execute. I’m not sure if this is intentional but it makes this technique entirely useless for monitoring DOM properties.
So, it appears that the only way to monitor DOM properties consistently across all browsers is to only use setInterval
. The following plugins, watch
and unwatch
work quite well:
jQuery.fn.watch = function( id, fn ) { return this.each(function(){ var self = this; var oldVal = self[id]; $(self).data( 'watch_timer', setInterval(function(){ if (self[id] !== oldVal) { fn.call(self, id, oldVal, self[id]); oldVal = self[id]; } }, 100) ); }); return self; }; jQuery.fn.unwatch = function( id ) { return this.each(function(){ clearInterval( $(this).data('watch_timer') ); }); }; // Plus, I finally found use for jQuery.data()! ;) |
These can be used on any type of object, an example:
$('input').watch('value', function(propName, oldVal, newVal){ log('Value has been changed to ' + newVal); }); // or... var obj = { prop: 123 }; $(obj).watch('prop', function(propName, oldVal, newVal){ log('Prop has been changed to ' + newVal); }); |
I went ahead and created a new special event for jQuery using the plugin:
jQuery.fn.valuechange = function(fn) { return this.bind('valuechange', fn); }; jQuery.event.special.valuechange = { setup: function() { jQuery(this).watch('value', function(){ jQuery.event.handle.call(this, {type:'valuechange'}); }); }, teardown: function() { jQuery(this).unwatch('value'); } }; // Usage: $('input').bind('valuechange', function(){ log('Value changed... New value: ' + this.value); }); |
This event is useful when you want to be notified every single time the value of an input element changes. No native DOM event will do this.
If there’s one thiing you should take away from all this then it’s this: if you are going to try making a browser-specific feature available across all browsers you should make sure that you match the functionality precisely, otherwise it’s worthless. For example, an Array.prototype.forEach
method that passes the index as the first parameter instead of the second is useless. If you can’t match the functionality then simply create your own abstraction.
Thanks for reading! Please share your thoughts with me on Twitter. Have a great day!
Using timers is an interesting solution.
I have implemented
object.(un)?watch
using accessors that works in every current browser; http://code.eligrey.com/object.watch/latest/object-watch.jsI’ve discovered a wonderful site to help me with my scripting questions and I think I found a nice solution for monitoring DOM properties that I wanted to share.
Basically it uses jQuery bind function as follows:
Opps the wonderful site I was referring to is http://stackoverflow.com/
@Motty, unfortunately, the “DOMSubtreeModified” event is not supported in Opera, IE or FF2.