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);
}
}
}
References:
Rick Strahl’s inital work in 2008 on a watch plugin over at:
http://www.west-wind.com/weblog/posts/478985.aspx
tags: advanced / attributes / change / DOMAttrModified / jquery
13 comments
-
Posted: Jan 28, 2011
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
-
Posted: May 31, 2011
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.
-
Posted: Jul 6, 2011
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.
-
Posted: Jul 11, 2011
What is the license to use your code?
-
Posted: Jul 12, 2011
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.
-
Posted: Jul 21, 2011
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);
}
}
} -
Posted: Aug 5, 2011
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
-
Posted: Aug 5, 2011
BTW. What is the license on this plugin?
-
Posted: Aug 8, 2011
Also – DOMAttrModified hs to be checked before onpropertychange, as IE9 supports both and onpropertychange is glitchy.
-
Posted: Aug 8, 2011
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.
-
Posted: Jan 2, 2012
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.
-
Posted: Feb 20, 2012
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) -
Posted: Apr 20, 2012
Thank you for posting this very useable plugin. I updated my copy to monitor the jquery.attr() not the jquery.css().
thanks


