Update: Since first publishing this article in 2010, there was a Mutation Observer specification that came about and is now incorporated into my $.watch() plugin. As well, I’ve added a library agnostic version of the code to the repo. The latest code can be found here

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 setInterval(). 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 setInterval() 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/

21 Comments

Alicante Airport Car Hire Posted: Jan 28, 2011 @ 11:53am

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

Otis Posted: May 31, 2011 @ 4:04pm

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.

Michael Posted: Jul 06, 2011 @ 6:31pm

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.

Alfonso Posted: Jul 11, 2011 @ 8:12pm

What is the license to use your code?

darcyclarke Posted: Jul 12, 2011 @ 5:25am

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.

Frank Posted: Jul 21, 2011 @ 3:03pm

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);
}
}
}

Andre Posted: Aug 05, 2011 @ 6:51pm

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

Andre Posted: Aug 05, 2011 @ 11:31pm

BTW. What is the license on this plugin?

Andre Posted: Aug 08, 2011 @ 12:05am

Also – DOMAttrModified hs to be checked before onpropertychange, as IE9 supports both and onpropertychange is glitchy.

Andre Posted: Aug 08, 2011 @ 12:14am

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.

Alice Wonder Posted: Jan 02, 2012 @ 4:10am

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.

Emilie Posted: Feb 20, 2012 @ 2:52pm

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)

Michael Ocana Posted: Apr 20, 2012 @ 9:56am

Thank you for posting this very useable plugin. I updated my copy to monitor the jquery.attr() not the jquery.css().

thanks

Andy Pillip Posted: Aug 06, 2012 @ 9:54am

@Michael Ocana

Where can I get your modified version?

Justin Posted: Aug 16, 2012 @ 3:19pm

@DarcyClarke — great plugin
@Frank — love your fallback for $.attr()

Omid Rad Posted: Dec 12, 2012 @ 2:00pm

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

Darcy Clarke Posted: Dec 12, 2012 @ 10:12pm

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

Filipe Torres Posted: Feb 27, 2013 @ 9:20pm

Hi Darcy,
I tested your plugin and works great, but how to unwatch an element?
Regards.

Filipe

C# MVC Web Development Posted: May 07, 2013 @ 10:51pm

@Filipe a regular jQuery $(selector).unbind(); should do the trick Thanks author for this excellent plug-in

Rafael Posted: Jul 24, 2013 @ 11:44am

hello dear programmer. thank you for wharing your knowledge and skills so others can have some kind of success in life!

i keep geting this error:
Uncaught TypeError: Object [object Object] has no method ‘left’

when i tried to do this:

$(“#cinta”).watch(‘left’, function(){
if(parseFloat($(this).left()) == 400){
$(“#cinta”).animate({left:”-=400px”},400);
}

});

i downloaded jquery.watch andloaded in my site, indeed it WATCHES the changes of the atribute “left”

any idea on how could i proceed ?

Tim Posted: Nov 05, 2013 @ 4:41pm

Doesn’t work with IE10.

Leave a Reply: