The Problem
One of the biggest issues I’ve come across, in my recent run in’s with event driven Javascript programming, is tracking attribute changes in the DOM. A good example of what I’m talking about would be: When my <p> tag has it’s background color changed to red. Usually, the only way you could track this change would be to trigger an event when you go to change that background color. This isn’t very scalable as you would have to bind every animation you create with some sort of trigger. Instead, what I’m looking for, is a DOM event to be thrown when this change happens.
A Solution
This is, in large part, a very easy problem to solve, without a DOM event, using a recursive check of my element utilizing setTimeout(). The problem with this solution is it’s sheer bloat and constant DOM traversing to check my selected element’s attributes every second. So, in light of this solutions pitfalls, we can use a little known DOM event called DOMAttrModified (you can read more about this and all other standard DOM events here: http://www.w3.org/TR/DOM-Level-2-Events/events.html)
The Best Solution
This event is not commonly used most likely because it is not yet supported by all browsers. That said, IE does support a similar event called “propertychange”, and, at the very least, we can always fall back on our initial setTimeout() solution if we’re dealing with all other browsers that don’t support anything (I’m looking at you Chrome/Safari). You can see a list of browsers and event compatibility here: http://www.quirksmode.org/dom/events/index.html
Since jQuery is a defacto go-to Javascript Library for developers these days, I created a plugin for it which mimics the DOMAttrModified event across all browsers.
Description:
This plugin lets you specify which attributes to “watch” for changes. It will fire your callback function when one of the attributes has changed.
Demo:
http://darcyclarke.me/dev/watch/
Code:
$.fn.watch = function(props, callback, timeout){
if(!timeout)
timeout = 10;
return this.each(function(){
var el = $(this),
func = function(){ __check.call(this, el) },
data = { props: props.split(","),
func: callback,
vals: [] };
$.each(data.props, function(i) { data.vals[i] = el.css(data.props[i]); });
el.data(data);
if (typeof (this.onpropertychange) == "object"){
el.bind("propertychange", callback);
} else if ($.browser.mozilla){
el.bind("DOMAttrModified", callback);
} else {
setInterval(func, timeout);
}
});
function __check(el) {
var data = el.data(),
changed = false,
temp = "";
for(var i=0;i < data.props.length; i++) {
temp = el.css(data.props[i]);
if(data.vals[i] != temp){
data.vals[i] = temp;
changed = true;
break;
}
}
if(changed && data.func) {
data.func.call(el, data);
}
}
}

19 Comments
Hey, I am checking this blog using the phone and this appears to be kind of odd. Thought you’d wish to know. This is a great write-up nevertheless, did not mess that up.
- David
One problem I’ve come across, though, is when I set a watch on an element which later is removed from the DOM. Errors occur. Maybe an ‘unwatch’ function can be added.
Hey, just wanted to say thanks for putting this together.
I had written some code using scroll() to modify elements, and then had to change over to using a jQuery slider for aesthetic purposes. Naturally, this broke my code. Fortunately, your watch() method was a perfect substitute. 6 lines changed in my code, and everything’s working great again.
What is the license to use your code?
Hey @Alfonso! There’s no proper license associated with any of my plugins or example code at this moment. That said, I think it can be assumed that you can use the code freely as you see fit, I only ask that you leave some form of credit back to me and this post/blog wherever you use it.
With a few modifications, this plugin couls also work for Attributes other than CSS too. Though it hasn’t been tested a lot, I got this code working with custom attributes on Firefox, Chrome and IE9+.
jQuery.fn.watch = function(props, callback, timeout){
var $ = jQuery;
if(!timeout) timeout = 10;
return this.each(function() {
var el = $(this),
func = function() { __check.call(this, el) },
data = {props:props.split(“,”),func:callback,vals:[]};
$.each(data.props, function(i) { data.vals[i] = (!el.css(data.props[i])?el.attr(data.props[i]):el.css(data.props[i])); });
el.data(data);
if((typeof(this.onpropertychange) == “object”)||($.browser.mozilla)) el.bind(“DOMAttrModified”, callback);
else setInterval(func, timeout);
});
function __check(el) {
var data = el.data(),
changed = false,
temp = “”;
for(var i=0;i < data.props.length; i++) {
temp = (!el.css(data.props[i])?el.attr(data.props[i]):el.css(data.props[i]));
if(data.vals[i] != temp){
data.vals[i] = temp;
changed = true;
break;
}
}
if(changed && data.func) {
data.func.call(el, data);
}
}
}
DOMAttrModified also supported in IE9 and Opera, thus one shouldn’t do $.browser.mozilla check but rather something like this: http://stackoverflow.com/questions/4562354/javascript-detect-if-event-lister-is-supported
BTW. What is the license on this plugin?
Also – DOMAttrModified hs to be checked before onpropertychange, as IE9 supports both and onpropertychange is glitchy.
Polling is not optimized. There should really be only 1 loop that goes over all added elements and monitored attributes. You have separate loop for each attribute.
Just ran into this scenario myself, needing to trigger a script when an html5 details node opens or closes.
Since details is presently only supported in chrome, DOMAttrModified was not available.
However, DOMSubtreeModified is available and worked. When the open attribute is added or removed from a details node, it does trigger a DOMSubtreeModified event.
Whether that event is triggered by a change in attribute or in other brothers, I have not yet investigated.
It work in chrome though and sure beats the hell out of polling.
It doesn’t work in IE8. I get an error on line 117:
el.on(‘propertychange’, callback);
(Object doesn’t support this property or method)
Thank you for posting this very useable plugin. I updated my copy to monitor the jquery.attr() not the jquery.css().
thanks
@Michael Ocana
Where can I get your modified version?
@DarcyClarke — great plugin
@Frank — love your fallback for $.attr()
The latest solution for this problem as of today is Mutation Observers. You might want to change the implementation of the watch plugin to use that. See:
http://code.google.com/p/mutation-summary/wiki/DOMMutationObservers
Hey Omid,
You’re right that Mutation Observers are the best way to go these days. I’ll look into updating this plugin soon but have a few projects on the go that require some attention first. I promise this will happen in the early New Year and I’ll blog about the update.
Cheers,
Darcy
Hi Darcy,
I tested your plugin and works great, but how to unwatch an element?
Regards.
Filipe
@Filipe a regular jQuery $(selector).unbind(); should do the trick Thanks author for this excellent plug-in
Leave a Reply: