Skip to content

Instantly share code, notes, and snippets.

@adamhotep
Forked from BrockA/waitForKeyElements.js
Last active August 29, 2025 15:59
Show Gist options
  • Save adamhotep/7c9068f2196326ab79145ae308b68f9e to your computer and use it in GitHub Desktop.
Save adamhotep/7c9068f2196326ab79145ae308b68f9e to your computer and use it in GitHub Desktop.
A utility function, for userscripts including Greasemonkey, that detects and handles AJAXed content.
/*--- waitForKeyElements(): A utility function, for Greasemonkey scripts,
that detects and handles AJAXed content.
Original: https://gist.github.com/BrockA/2625891
Non-jQuery version by: Adam Katz,
https://gist.github.com/adamhotep/7c9068f2196326ab79145ae308b68f9e
License: CC BY-NC-SA 4.0 (*not* GPL-compatible)
changes made by Adam Katz (tracked by adamhotep's github gist) are
also licensed GPL v2+ (but note the CC BY-NC-SA prevents commercial use)
License via https://gist.github.com/BrockA/2625891#gistcomment-1617026
Usage example:
// ==UserScript==
// …
// @require https://git.io/waitForKeyElements.js
// ==/UserScript==
waitForKeyElements (
"div.comments",
commentCallbackFunction
);
//--- Page-specific function to do what we want when the node is found.
function commentCallbackFunction (elem) {
elem.innerHTML = "This comment changed by waitForKeyElements().";
}
*/
function waitForKeyElements (
selectorTxt, /* Required: The querySelector string that
specifies the desired element(s).
*/
actionFunction, /* Required: The code to run when elements are
found. It is passed the matched element.
*/
bWaitOnce, /* Optional: If false, will continue to scan for
new elements even after the first match is
found.
*/
iframeSelector /* Optional: If set, identifies the iframe to
search.
*/
) {
var targetNodes, btargetsFound;
//--- Additionally avoid what we've found
var selectorClean = selectorTxt.replace(/(,)|$/g, ":not([wfke_found])$1");
if (typeof iframeSelector == "undefined")
targetNodes = document.querySelectorAll(selectorClean);
else {
targetNodes = [];
var iframe = document.querySelectorAll(iframeSelector);
for (var i = 0, il = iframe.length; i < il; i++) {
var nodes = iframe[i].contentDocument.querySelectorAll(selectorClean);
if (nodes) targetNodes.concat(Array.from(nodes));
}
}
if (targetNodes && targetNodes.length > 0) {
btargetsFound = true;
//--- Found target node(s). Go through each and act if they are new.
for (var t = 0, tl = targetNodes.length; t < tl; t++) {
if (!targetNodes[t].getAttribute("wfke_found")) {
//--- Call the payload function.
var cancelFound = false;
try {
cancelFound = actionFunction (targetNodes[t]);
}
//--- Log errors to console rather than stopping altogether
catch (error) {
var name = actionFunction.name;
if (name)
name = 'in function "' + name + '":\n';
console.log ("waitForKeyElements: actionFunction error\n"
+ name + error);
}
if (cancelFound)
btargetsFound = false;
else
targetNodes[t].setAttribute("wfke_found", true);
}
}
}
else {
btargetsFound = false;
}
//--- Get the timer-control variable for this selector.
var controlObj = waitForKeyElements.controlObj || {};
var controlKey = selectorTxt.replace (/[^\w]/g, "_");
var timeControl = controlObj [controlKey];
//--- Now set or clear the timer as appropriate.
if (btargetsFound && bWaitOnce && timeControl) {
//--- The only condition where we need to clear the timer.
clearInterval (timeControl);
delete controlObj [controlKey]
}
else {
//--- Set a timer, if needed.
if ( ! timeControl) {
timeControl = setInterval ( function () {
waitForKeyElements ( selectorTxt,
actionFunction,
bWaitOnce,
iframeSelector
);
},
300
);
controlObj [controlKey] = timeControl;
}
}
waitForKeyElements.controlObj = controlObj;
}
@adamhotep
Copy link
Author

adamhotep commented Sep 26, 2017

In my first revision, I credited the original author, noted the license, and incorporated changes from EnterTheNameHere's first (and currently only) revision (#5), which uses try/catch exception handling in case there is a problem with the function provided to waitForKeyElements() to act on.

I've also created https://git.io/waitForKeyElements.js for easier loading in userscripts.

@z-aki
Copy link

z-aki commented Aug 12, 2025

shouldn't the iframe code be like this?

document.querySelectorAll("frame[name=\"mainframe\"]")[0].contentWindow.document.querySelector("form")

https://stackoverflow.com/questions/26630519/queryselector-for-web-elements-inside-iframe

@adamhotep
Copy link
Author

adamhotep commented Aug 13, 2025

General note: I've since migrated all of my scripts to use nf.wait$() from my Nofus.js project at https://github.com/adamhotep/nofus.js. That function doesn't have special provisions for iframes because they haven't appeared necessary.

@z-aki, I'm not sure what you're referring to. Where is name="mainframe" used? I don't know my connection to your linked Stack Overflow post, which doesn't appear to refer to me or this code, nor have I participated in that question.

Is this a reference to this answer of mine (which also does not mention a name attribute)? My answer there should work just fine with nf.wait$() in place of waitForKeyElements(). If the frame(s) in question are not created dynamically, you don't need this sort of function at all; just use document.querySelectorAll("iframe, frame").forEach(…) to find the frame and apply a function to it.

@z-aki
Copy link

z-aki commented Aug 14, 2025

line 58

var nodes = iframe[i].querySelectorAll(selectorClean);

should be

var nodes = iframe[i].contentWindow.document.querySelectorAll(selectorClean);

The existing gist doesn't work for a call like this where frame is selected and then a div inside it is selected.

waitForKeyElements("div[id='country2']", fix, false, "frame[name='mainframe']");

website: https://www.indianbankcreditcard.in/indbcreditcustomer/html/LoginFrame.html

// ==UserScript==
// @name         Indian bank Credit Card expand terms and conditions
// @version      0.1
// @description  Removes the height style from the parent div of country1/2/3/4.
// @author       https://github.com/z-aki
// @namespace    https://github.com/z-aki
// @match        https://www.indianbankcreditcard.in/indbcreditcustomer/html/UAMLogin.jsp
// @icon         https://www.indianbankcreditcard.in/indbcreditcustomer/html/favicons/android-icon-192x192.png
// @grant        none
// @require      https://gist.github.com/adamhotep/7c9068f2196326ab79145ae308b68f9e/raw/373f5e8405b98781001aea9a9e74585367344960/waitForKeyElements.js
// @downloadURL  none
// ==/UserScript==

function fix(elem) {
  console.log("Tampermonkey: Fixing height");
  // remove the height style from the parent div of country1/2/3/4
  elem.parentElement.style.height = "";
}

waitForKeyElements("div[id='country2']", fix, false, "frame[name='mainframe']");

@adamhotep
Copy link
Author

Ah, thanks for clarifying that the name attribute was from your code and not mine or any of the linked SO posts. I've adjusted line 58 as per your suggestion (though I used contentDocument rather than contentWindow.document, which should be identical).

@z-aki
Copy link

z-aki commented Aug 27, 2025

thanks. But the script mentioned above doesn't work still, on the website linked above.

however this works https://z-aki.github.io/tampermonkey/2025/07/15/indian-bank-credit-card-expand-terms-and-conditions/

@adamhotep
Copy link
Author

@z-aki: that code uses @CoeJoder's waitForKeyElements() rather than this function. I'm glad you found something that works.

To others reading this, I highly suggest using nf.wait$() from my Nofus.js project. That function doesn't have special provisions for iframes because they haven't appeared necessary. Also notably, it utilizes a MutationObserver rather than a loop on a timer (as all waitForKeyElements() functions do). This means it runs every time the document changes rather than having to poll for changes periodically. This is both faster and more efficient.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment