Firing event on DOM attribute change
Asked Answered
A

6

65

Is there any way to trigger event (may be custom) on attribute change?

Let's say, when IMG src is changed or DIV's innerHtml?

Aggie answered 30/12, 2010 at 10:29 Comment(1)
An query plugin would do that. #16782278Bookcase
A
53

Note: As of 2012, Mutation Events have been removed from the standard and are now deprecated. See other answers or documentation for how to use their replacement, MutationObserver.

You are referring to DOM Mutation Events. There is poor (but improving) browser support for these events. Mutation Events plugin for jQuery might get you some of the way.

Avaria answered 30/12, 2010 at 10:51 Comment(4)
DOM mutation events are actually reasonably well supported among browsers. It's just that IE doesn't support them at all (although IE 9 will).Brandebrandea
@TimDown if it comes to IE, it's not a strange thing.Barbaresi
These are now deprecated in favor of MutationObserver, developer.mozilla.org/en-US/docs/Web/API/MutationObserverKrp
For me this answer is too succinct. I couldn't find out how to use the plugin for jquery. But the answer of Mats is simple and there's no need for extra plugin.Rowlock
D
43

How to setup a MutationObserver, mostly copied from MDN but I've added my own comments for clarity.

window.MutationObserver = window.MutationObserver
    || window.WebKitMutationObserver
    || window.MozMutationObserver;
// Find the element that you want to "watch"
var target = document.querySelector('img'),
// create an observer instance
observer = new MutationObserver(function(mutation) {
     /** this is the callback where you
         do what you need to do.
         The argument is an array of MutationRecords where the affected attribute is
         named "attributeName". There is a few other properties in a record
         but I'll let you work it out yourself.
      **/
}),
// configuration of the observer:
config = {
    attributes: true // this is to watch for attribute changes.
};
// pass in the element you wanna watch as well as the options
observer.observe(target, config);
// later, you can stop observing
// observer.disconnect();

Hope this helps.

Duel answered 29/3, 2014 at 20:59 Comment(1)
Works like a charm in Chrome. Really useful!Jurgen
T
6

If you only need something specific then a simple setInterval() will work, by checking the target attribute(s) every few milliseconds:

var imgSrc = null;
setInterval(function () {
   var newImgSrc = $("#myImg").attr("src");
   if (newImgSrc !== imgSrc) {
      imgSrc = newImgSrc;
      $("#myImg").trigger("srcChange");
   }
}, 50);

Then bind to the custom "srcChange" event:

$("#myImg").bind("srcChange", function () {....});
Triaxial answered 30/12, 2010 at 10:44 Comment(3)
usage of intervals for this is really poor in my opinion (Yes it is 'a' way).Equalize
The usage of intervals is appropriate in limited quantities especially where performance won't become an issue. I haven't ran any bench tests but I know from experience I can manipulate all kinds of properties in the DOM in a fast interval without noticing performance degradation.Valetudinarian
The MutationObserver may be a more elegant solution, but what if the element in question hasn't been generated yet? And for debugging purposes, I think it's ok to let things temporarily be a little messy. My use-case at the moment is trying to chase down what's responsible for erroneously changing a value in a Legacy system, so this solution actually suits me better than the cleaner one.Betteanne
I
4

There is no native dom changed event you can hook into.

Good article here which tries to provide a solution in the form of a jquery plugin.

Code from article

$.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);
        }
    } }
Indisposed answered 30/12, 2010 at 10:41 Comment(2)
There are pretty huge performance implications with this approach.Nard
"Good article" link broken :(Spaniel
U
0

In addition to Mats' answer inspired by MDN's MutationObserver Example usage:

If your options contain <property>: true and you plan to change this property of target inside MutationObserver's callback function, use the following to prevent recursive calls – until script timeout, stack overflow or the like:

...
// Used to prevent recursive calls of observer's callback function
// From https://mcmap.net/q/299295/-firing-event-on-dom-attribute-change
let insideInitialObserverCallback = false

let callback = function(mutationsList) {
    insideInitialObserverCallback = ! insideInitialObserverCallback
    if ( insideInitialObserverCallback ) {

        // ... change target's given property ...       

    }
})

let observer = new MutationObserver(callback);
...
Uzzia answered 3/2, 2018 at 15:53 Comment(0)
U
0

I had same issue, where I had to find track attribute change of some particular DOM element. and I used MutationObserver.

But there was one more complication what I was facing while using MutationObserver. MutationObserver is need some target element while observing change.

while working with SPA (where AJAX, Angular , react or any other javascript framework have been used), you might have realized all the elements are dynamic. that's it would be difficult to set target.

Here I came with some solution where I have applied MutationObserver on DOM and then emitted customEvent when some attribute of any element gets change.

then in next step filter custom event as per our requirement.

 // code to change image src in each 1000ms.
        count = 0;
        setInterval(function() {
            dimension = `${600+count}x${400+count}`;
            document.querySelector('div.image-box img').src = `https://dummyimage.com/${dimension}/000/fff`;
            document.querySelector('div.image-box img').alt = dimension;
            count++;
        }, 1000);

        function startMutationObserver(tNode, c) {
            // Select the node that will be observed for mutations
            const targetNode = tNode ? tNode : document;

            // Options for the observer (which mutations to observe)
            const config = c ? c : {
                attributes: true,
                childList: true,
                subtree: true
            };

            // Callback function to execute when mutations are observed
            const callback = function(mutationsList, observer) {
                for (let mutation of mutationsList) {
                    if (mutation.type === 'childList') {
                        targetNode.dispatchEvent(new CustomEvent('newChild', {
                            detail: mutation
                        }));
                    } else if (mutation.type === 'attributes') {
                        targetNode.dispatchEvent(new CustomEvent('attributeChange', {
                            detail: mutation
                        }));
                    }
                }
            };

            // Create an observer instance linked to the callback function
            const observer = new MutationObserver(callback);

            // Start observing the target node for configured mutations
            observer.observe(targetNode, config);

            // Later, you can stop observing
            // observer.disconnect();
        }
        // call this function to start observing DOM element change
        startMutationObserver(document);

        // code to listen custom event and filter custom event as per requirement
        document.addEventListener('attributeChange', function(e) {
            // console.log(e);
            const ele = e.detail;

            if (ele.target.matches('div.image-box img') && ele.attributeName == 'src') {

                var src = e.detail.target.getAttribute('src');
                var alt = e.detail.target.getAttribute('alt');
                console.log(src, alt);

            }
        })
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>

<body>
    <div class="image-box">
        <img src="https://dummyimage.com/600x400/000/fff" alt="600x400">
    </div>

</body>

</html>

I hope this will help you to tracking any attribute change, insertion of new element also.. Lets try, and let me know if you are facing any issues.

Uncover answered 22/10, 2019 at 10:6 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.