Mutation Observer for creating new elements
Asked Answered
B

3

40

I am trying to make a function go off when a particular div is created. In the simplest of terms, I have something like this:

<a href="" id="foo">Click me!</a>
<script>
$("#foo").live("click",function(e) {
    e.preventDefault();
    $(this).append($("<div />").html("new div").attr("id","bar"));
});
</script>

Before, I had mutation events listen for the creation of div#bar - something like this:

$("#bar").live("DOMNodeInserted", function(event) {
    console.log("a new div has been appended to the page");
});

Is there an equivalent using Mutation Observers? I tried attrchange.js featured on Can you have a javascript hook trigger after a DOM element's style object changes? but that plugin only detects when an element has been modified, not when it's created.

Bethea answered 7/11, 2012 at 19:57 Comment(4)
Just use custom events: $( this ).append( ... ).trigger( 'newchild' );, and then $( '#bar' ).on( 'newchild', function () { ... });.Bezant
Demo: jsfiddle.net/u3dDk/1Bezant
Sorry Sime Vidas, custom events are not going to work for me. In my example, I gave a really simplified version of what I'm actually trying to do. I figured it would be easier than to paste in 500 lines of javaScript. Essentially, I need the JS that listens for the creation of div#bar to be completely separate from the click function for a#barBethea
Mutation observers can do that, but they're not implemented in IE.Bezant
L
36

This is code that listens for mutations on the childlist of #foo and checks to see if a child with the id of bar is added.

MutationObserver = window.MutationObserver || window.WebKitMutationObserver;

$("#foo").live("click",function(e) {
    e.preventDefault();
    $(this).append($("<div />").html("new div").attr("id","bar"));
});

// define a new observer
var obs = new MutationObserver(function(mutations, observer) {
    // look through all mutations that just occured
    for(var i=0; i<mutations.length; ++i) {
        // look through all added nodes of this mutation
        for(var j=0; j<mutations[i].addedNodes.length; ++j) {
            // was a child added with ID of 'bar'?
            if(mutations[i].addedNodes[j].id == "bar") {
                console.log("bar was added!");
            }
        }
    }
});

// have the observer observe foo for changes in children
obs.observe($("#foo").get(0), {
  childList: true
});

However, this only observes #foo. If you want to look for the addition of #bar as a new child of other nodes, you need to observe those potential parents with additional calls to obs.observe(). To observe a node with the id of baz, you might do:

obs.observe($('#baz').get(0), {
  childList: true,
  subtree: true
});

The addition of the subtree option means that the observer will look for the addition of #bar as either a child or a deeper descendant (e.g. grandchild).

Lodovico answered 7/11, 2012 at 21:17 Comment(6)
A solution that only works in Chrome/Safari is hardly a legitimate solution.Bezant
@ŠimeVidas Sorry, yes, my mistake; I had the vendor prefix in because I was testing in Chrome. I've now normalized the vendor prefixes and it otherwise should work fine as-is. It's MutationObserver in Firefox and WebKitMutationObserver in Webkit, but they work the same. Fiddle demo.Lodovico
@JimmyHa This will work in Firefox and Webkit browsers. Opera and IEdon't yet support Mutation Observers, so you'll need to use a fallback option like a DOM3 mutation event (or wait a long time for DOM4 methods to reach implementation maturity).Lodovico
@Lodovico - Thanks! I modified your js to fit what I needed. Instead of looking for id, I was looking for a particular class: if ($(mutations[i].addedNodes[j]).hasClass("new_div_class")) { I'll just say in my ticket that if user wants the same feature-rich thing I'm trying to do in IE, they'll have to add scope to the project and pay me more. Are we sure mutation observers are better than mutation events? It seems like so many more lines of scripting to do the same thing.Bethea
I believe the primary advantage of observers is the ability to narrow down exactly what kind of mutation event you want and what part of DOM you're interested in. With mutation events, the browser has to traverse potentially huge DOM trees every single time you make a change. With observers, you can narrow your sphere of interest by electing not to see subtrees, etc. See this post if you're interested in learning about the specific advantages.Lodovico
Is it supported by window phone? Safari mobile, FF and chrome support it (new generation only).Wendell
C
9

When using jQuery, the usage of MutationObservers can be simplified as shown below.

$("#btnAddDirectly").click(function () {
    $("#canvas").append($('<span class="stuff">new child direct</span>'));
});
$("#btnAddAsChildOfATree").click(function () {
    $("#canvas").append($('<div><div><span class="stuff">new child tree</span></div></div>'));
});

var obs = new MutationObserver(function(mutations, observer) {
  // using jQuery to optimize code
  $.each(mutations, function (i, mutation) {
    var addedNodes = $(mutation.addedNodes);
    var selector = "span.stuff"
    var filteredEls = addedNodes.find(selector).addBack(selector); // finds either added alone or as tree
    filteredEls.each(function () { // can use jQuery select to filter addedNodes
      alert('Insertion detected: ' + $(this).text());
    });
  });
});

var canvasElement = $("#canvas")[0];
obs.observe(canvasElement, {childList: true, subtree: true});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

<div id="canvas">
Canvas
</div>

<button id="btnAddDirectly">Add span directly to canvas</button>
<button id="btnAddAsChildOfATree">Add span as child of a tree</button>

Don't forget, the second argument to .observe(), MutationObserverInit, is important:

In the options, use childList: true if the span will only be added as a direct child. subtree: true if it can be at any level down below #canvas.

From the docs:

  • childList: Set to true if additions and removals of the target node's child elements (including text nodes) are to be observed.
  • subtree: Set to true if mutations to target and target's descendants are to be observed.
Clariceclarie answered 31/3, 2018 at 2:4 Comment(0)
F
0

Mutation Observer

// Select the node that will be observed for mutations
const targetNode = document.getElementById('some-id');

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

// Callback function to execute when mutations are observed
const callback = function(mutationsList, observer) {
    // Use traditional 'for loops' for IE 11
    for(const mutation of mutationsList) {
        if (mutation.type === 'childList') {
            console.log('A child node has been added or removed.');
        }
        else if (mutation.type === 'attributes') {
            console.log('The ' + mutation.attributeName + ' attribute was modified.');
        }
    }
};

// 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();

https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver

Fibril answered 10/1, 2021 at 13:2 Comment(1)
How does this answer the question?Brandenbrandenburg

© 2022 - 2024 — McMap. All rights reserved.