-
-
Save simonrob/86cbf1fa9f24f7d821632e9c1ca96571 to your computer and use it in GitHub Desktop.
A utility function, for Greasemonkey scripts, that detects and handles AJAXed content.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* 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