Observing DOM changes

We have been working on a project recently that has required the ability to alter the DOM on a website that we have no control over.

Whilst this is typically easy to do with static web-pages, when the website is a SPA (single page application) then it becomes a lot trickier.

You have to find a way to monitor changes that occur on the page without becoming too intrusive.

Luckily there are a few JavaScript functions now available to help:
- document.querySelector lets you see if a DOM element exists
- MutationObserver provides a way to watch for changes in the DOM. Typically you're meant to provide a node to watch but sometimes you're interested in new DOMelements appearing.

In reality, there was not really a useful guide available to help so we've gone through a lot of trial and error to find an approach that works.

We now have 5 functions available to us that we thought would be useful to post online so other people could use them as inspiration too!

waitForElement - wait for an element to exist and returns a promise, so use like waitForElement('#id').then();

waitForElement: function (selector) {
    return new Promise((resolve) => {
        if (document.querySelector(selector)) {
            return resolve(document.querySelector(selector));
        }
        const observer = new MutationObserver(() => {
            if (document.querySelector(selector)) {
                resolve(document.querySelector(selector));
                observer.disconnect();
            }
        });
        observer.observe(document.body, {
            childList: true,
            subtree: true
        });
    });
}

waitForElementCount - wait for an element to exist at least a certain number of times and returns a promise, so use like waitForElement('.class').then();

waitForElementCount: function (selector, count) {
    return new Promise((resolve) => {
        if (document.querySelector(selector) && document.querySelectorAll(selector).length >= count) {
            return resolve(document.querySelector(selector));
        }
        const observer = new MutationObserver(() => {
            if (document.querySelector(selector) && document.querySelectorAll(selector).length >= count) {
                resolve(document.querySelector(selector));
                observer.disconnect();
            }
        });
        observer.observe(document.body, {
            childList: true,
            subtree: true
        });
    });
}

waitForNotElement - wait for an element to not exist and returns a promise, so use like waitForNotElement('#id').then();

waitForNotElement: function (selector) {
    return new Promise((resolve) => {
        if (!document.querySelector(selector)) {
            return resolve(document.querySelector(selector));
        }
        const observer = new MutationObserver(() => {
            if (!document.querySelector(selector)) {
                resolve(document.querySelector(selector));
                observer.disconnect();
            }
        });
        observer.observe(document.body, {
            childList: true,
            subtree: true
        });
    });
}

waitForElementAll - wait for an element to exist and executes the callback. This continues to check for the element to re-appear and will execute the same callback.

waitForElementAll: function (selector, callback) {
    let found = false;
    const observer = new MutationObserver(() => {
        if (document.querySelector(selector) && !found) {
            found = true;
            callback();
        } else {
            found = false;
        }
    });
    observer.observe(document.body, {
        childList: true,
        subtree: true
    });
}

waitForElementAllCount - wait for an element to exist at least a certain number of times and executes the callback. This continues to check for the element to re-appear and will execute the same callback.

waitForElementAllCount: function (selector, count, callback) {
    let found = false;
    const observer = new MutationObserver(() => {
        if (document.querySelector(selector) && document.querySelectorAll(selector).length >= count && !found) {
            found = true;
            callback();
        } else {
            found = false;
        }
    });
    observer.observe(document.body, {
        childList: true,
        subtree: true
    });
}



Want to get in touch?