39

I need to watch for an attribute change on any of the children of a specific DOM element. So far, I have been using mutation events.

The problem was - they were buggy: e.g. under Chromium, DOMAttrModified was not fired but DOMSubtreeModified was. The problem was easy to solve: because according to the specification, DOMSubtreeModified is fired if any of the other events is fired, so I just listened to DOMSubtreeModified.

Anyway, Chromium, in the recent versions, stopped firing anything if an attribute has been modified.

The new Mutation Observer API, however, works flawlessly.

Until now, I only need to fire a callback upon ANY change of the subtree of a specific element - simply because nothing else is supposed to change - so I solved my problem by just using mutation events & mutation observer (when available) in the same piece of code.

However, now I need to do more powerful filtering of the events (e.g. on new node, on removed node) - so is there a library, possibly a jQuery plug-in, that would allow me to elegantly use both of these APIs - MutationObserver if available and mutation events as a fallback, with the ability to filter for specific event types (e.g. element added, attribute changed).

E.g.

$("#test").watch({onNewElement: 1}, function(newElement){})
$("#test").watch({onNewAttribute: 1}, function(modifiedElement) {})

Or without jQuery

watchChanges("#test", {onNewElement: 1}, function(newElement){})
watchChanges("#test", {onNewAttribute: 1}, function(modifiedElement){})
Praveen Kumar Purushothaman
  • 164,888
  • 24
  • 203
  • 252
Ivo
  • 1,673
  • 3
  • 19
  • 28
  • Some searching found this, but it only supports the new API: http://code.google.com/p/mutation-summary/. I also found this, which appears to be a simple version of what you're asking for: http://stackoverflow.com/questions/10868104/can-you-have-a-javascript-hook-trigger-after-a-dom-elements-style-object-change – thirtydot Jul 15 '12 at 11:56
  • I also found mutation-summary, but it does not support mutation events as a fallback. The first answer of the thread you posted is basically what I do in my code to observe attribute changes. In the same principle, a library can be built that allows watching for specific DOM events using both mutation observers and (as a fallback) events. That is what I need. – Ivo Jul 15 '12 at 12:00
  • 2
    +1: I really dig 'best practice' questions. – Jezen Thomas Jul 21 '12 at 23:50

2 Answers2

2

Would this work?

http://darcyclarke.me/development/detect-attribute-changes-with-jquery/

$.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);
        }
    }
}
Coffee Bite
  • 4,956
  • 5
  • 33
  • 38
  • I saw it, but it's only for attributes. Plus, it falls back to setInterval even if the browser might support DOMAttrModified. – Ivo Jul 21 '12 at 23:53
  • @Ivo right. Here is a thought though - are you in control of all these changes happening to the dom? Have you considered overriding the jquery manipulation methods to trigger a change/append/remove event (but that's assuming all the changes happen through jquery). I know that's a lot of work, and not exactly what you are looking for, but gets the work done. – Coffee Bite Jul 22 '12 at 00:54
  • 1
    I already got the work done a long time ago - I use both mutation events and observers to monitor attributes, and I use only the events to watch for newly-added stuff on the DOM - and I am not quite in control since I am watching for changes made via contenteditable/WYSIHTML5 editor. But it all works for the browsers I have planned to support. This is more of a "best approach" question. And monkey-patching jQuery seems like a bad idea. Monkey-patching the DOM API might be more appropriate but I'm still not sure. – Ivo Jul 22 '12 at 10:43
2

For efficient DOM monitoring with jQuery, you might take a look at the jQuery Behavior Plugin (Disclaimer: I'm the author) which utilizes a optimized version of Live Query. I'm using it in several products for 3 year now, without any problems.

Edit: Listening for attribute changes would work like this:

$.behavior({
    'div:first': {
        'changeAttr': function (event, data) {
            console.log('Attribute "' +  data.attribute + '" changed from "' + data.from + '" to "' + data.to + '"');
        }
    }
});

$('div:first').attr('foo', 'bar');
FloHimself
  • 291
  • 3
  • 12