Firing event on DOM attribute change

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?

Answers:

Answer

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.

Answer

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.

Answer

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 () {....});
Answer

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

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://stackoverflow.com/questions/4561845/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);
...
Answer

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.

Tags

Recent Questions

Top Questions

Home Tags Terms of Service Privacy Policy DMCA Contact Us

©2020 All rights reserved.