Search code examples
javascriptecmascript-6mutation-observers

Best way to check if mutation observer passes certain conditions


I have implemented a mutation observer that watches for mutations to the page load. I am specifically looking for the point at which a particular element is loaded/exists. I can specify it by its classname which for sake of example is foo.

When foo is found in the list of mutated objects, I wish to call a function.

Currently, I have the following:

  mutations.forEach(mutation => {
    if (!mutation.addedNodes || mutation.addedNodes[0].className === undefined || mutation.addedNodes[0].className !== 'foo') {
      return;
    } else {
      console.log(mutation.addedNodes[0].className + ' loaded!');
    }
  });

This technically works: when it passes the three conditions I do indeed see the console log.

However, in cases where mutation.addedNodes[0] does not have any className data it provides the error

Cannot read property 'className' of undefined

I do understand why this is happening; sometimes mutations has no addedNodes[0] so is indeed undefined. What I am not sure about is the best way to fire the console log (later will be my function) only when those above tests are passed.

I am still learning ES6, and I expect there is something that can help me here but I'm just struggling to find the best way.


Solution

    1. During page load each reported node may be a container with dozens or hundreds of nested nodes so you need to enumerate all addedNodes and check each one's tree.
    2. Some of the reported mutation records may be about removed nodes (for example, by the page script) so addedNodes will be an empty array.
    3. Some of the added nodes will be comments or text nodes so you need to skip those.

    const matching = [];
    for (const {addedNodes} of mutations) {
      for (const node of addedNodes) {
        if (node.nodeType !== Node.ELEMENT_NODE) {
          continue;
        }
        if (node.classList.contains(className)) {
          matching.push(node);
        }
        if (node.children[0]) {
          matching.push(...node.getElementsByClassName(className));
        }
      }
    }
    if (matching.length) console.log(matching);