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: / / / /

13 comments

  • Alicante Airport Car Hire

    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

  • Otis

    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.

  • Michael

    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.

  • Alfonso

    Posted: Jul 11, 2011

    What is the license to use your code?

  • darcyclarke

    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.

  • Frank

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

  • Andre

    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

  • Andre

    Posted: Aug 5, 2011

    BTW. What is the license on this plugin?

  • Andre

    Posted: Aug 8, 2011

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

  • Andre

    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.

  • Alice Wonder

    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.

  • Emilie

    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)

  • Michael Ocana

    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

"w3fools.com - because friends don't let friends use w3schools" ~ dan heberden
themify
Learning JavaScript