Search code examples
javascriptmutation-observersselectors-api

how to use querySelectorAll on the added nodes in a MutationObserver


I'm trying to create a querySelectorAll function in a MutationObserver, so it's like calling querySelectorAll to the newly added elements. The reason for this is so it works with other existing code.

This is proving to be hard without hard-coding selectors and using if statements, I've thought of the following ways which all failed:

  • Try to use the added node's parent's querySelectorAll, but then it includes elements that are not just added.
  • Use the added node querySelectorAll function and merge all the results, but it doesn't work as doesn't include the added node itself.
  • Create a new element and move all the added nodes to it, and call querySelectorAll on that element, but then the nodes disappear after the MutationObserver runs and don't get added.

Is there a way to do this, or some modification to one of the ways I've come up with so it works?


Solution

  • As I'm sure you know, your callback receives an array of MutationRecords, each of which has a NodeList of added nodes called addedNodes.

    Turning those into a list of elements matching a selector is probably more complicated than it ideally would be, but here's one approach (see comments):

    function applySelector(selector, records) {
        // We can't create a NodeList; let's use a Set
        const result = new Set();
        // Loop through the records...
        for (const {addedNodes} of records) {
            for (const node of addedNodes) {
                // If it's an element...
                if (node.nodeType === 1) {
                    // Add it if it's a match
                    if (node.matches(selector)) {
                        result.add(node);
                    }
                    // Add any children
                    addAll(result, node.querySelectorAll(selector));
                }
            }
        }
        return [...result]; // Result is an array, or just return the set
    }
    

    Live Example:

    const ob = new MutationObserver(records => {
        const result = applySelector("span", records);
        console.log(`Got ${result.length} matches:`);
        for (const span of result) {
            console.log(span.id);
        }
    });
    const target = document.getElementById("target");
    ob.observe(target, {childList: true});
    target.insertAdjacentHTML(
        "beforeend",
        `<div>
          blah
          <span id="span1">span</span>
          blah
          <div>
            <span id="span2">lorem <span id="span3">ipsum</span></span>
          </div>
        </div>`
    );
    
    function addAll(set, list) {
        for (const entry of list) {
            set.add(entry);
        }
    }
    function applySelector(selector, records) {
        // We can't create a NodeList; let's use a Set
        const result = new Set();
        // Loop through the records...
        for (const {addedNodes} of records) {
            for (const node of addedNodes) {
                // If it's an element...
                if (node.nodeType === 1) {
                    // Add it if it's a match
                    if (node.matches(selector)) {
                        result.add(node);
                    }
                    // Add any children
                    addAll(result, node.querySelectorAll(selector));
                }
            }
        }
        return [...result]; // Result is an array, or just return the set
    }
    <div id="target"></div>