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