Skip to content

Instantly share code, notes, and snippets.

@simonrob
Forked from double-beep/waitForKeyElements.js
Last active March 15, 2024 14:57
Show Gist options
  • Save simonrob/86cbf1fa9f24f7d821632e9c1ca96571 to your computer and use it in GitHub Desktop.
Save simonrob/86cbf1fa9f24f7d821632e9c1ca96571 to your computer and use it in GitHub Desktop.
A utility function, for Greasemonkey scripts, that detects and handles AJAXed content.
/* waitForKeyElements(): The improved utility function for userscripts - now with MutationObservers and no jQuery!
Usage example:
waitForKeyElements(
'div.comments',
element => console.log('Look! A new element appeared!', element)
);
// -----------------------------------------------------------------
waitForKeyElements(
'ul#list',
callbackFunction,
{ waitOnce: false }
);
function callbackFunction(node) {
node.innerText = 'Text changed with waitForKeyElements()';
}
*/
/**
* waitForKeyElements
* @param {string} selector Required: The selector string that specifies the desired element(s).
* @param {Function} callback Required: The code to run when elements are found. It is called repeatedly with each matching element.
* @param {Object} options Optional: A configuration/options object - see defaults (below) for options.
*/
function waitForKeyElements(selector, callback, options) {
const defaults = {
waitOnce: true, // If true (default), wait for a single matching element. If false, continue to scan for
// new elements after the first match is found.
allElements: true, // If true (default), the callback will be triggered repeatedly for every matching element.
// If false, the callback will be triggered only for the first matching element.
logTree: false // If true, will log the full subtree to the console (useful for finding the actual element
// added to the page, which may be a parent of the desired element). Default: false.
};
options = {...defaults, ...(options || {})};
const elements = options.allElements ? document.querySelectorAll(selector) : [document.querySelector(selector)].filter(e => e != null);
if (elements.length > 0) {
// if the elements already exist, then call the function passed
elements.forEach(element => callback(element));
if (options.waitOnce) {
return;
}
}
let called = false;
const observer = new MutationObserver(mutations => {
mutations
.filter(({addedNodes}) => addedNodes.length) // keep only newly added elements
.forEach(({addedNodes}) => {
[...addedNodes]
.filter(element => {
if (options.logTree && element?.querySelector?.(selector)) {
console.log(element);
}
return true;
})
.filter(element => element?.matches?.(selector))
.forEach(element => {
if (options.allElements || !called) {
if (options.waitOnce) {
observer.disconnect(); // here in case the callback modifies the elements
}
called = true; // cancel the callback only when actually matched
callback(element, observer);
}
});
});
});
observer.observe(document.body, {
childList: true,
subtree: true
});
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment