I'm having a bit of difficulty getting a JavaScript function to execute once and only when a DOM element is fully loaded. I've tried countless combindations of setInterval
s, seTimeout
s, FOR
loops, IF
statements, WHILE
loops, etc and gotten nowhere.
I did manage to get it to work once, but it's hit-and-miss as I could only get it to work by delaying the function by 2 seconds (which isn't good, as there's no telling exactly how long it takes to load) and rapidly re-firing the same function over and over, which also isn't good.
I just need something to constantly scan the page to tell whether an element exists and has content (innerHTML != undefined
, or something), execute a block of code as soon as it is loaded (and only once) and then stop scanning the page.
Has anyone found a way to do this? Also, I need JavaScript, not jQuery.
Thanks.
Original Code
function injectLink_Bridge(){
setTimeout(function(){
injectLink();
}, 2000);
}
function injectLink(){
var headerElement = document.getElementsByClassName("flex-module-header");
if (headerElement.innerHTML == undefined) {
console.log("Element doesn't exist. Restarting function");
setTimeout(function(){
injectLink_Bridge(); //I can't remember if the bridge is necessary or not
}, 2000);
} else {
console.log("Element exists. Injecting");
setTimeout(function(){
headerElement[1].innerHTML += "code" //Inject code into the second instance of the class-array
}, 2000);
}
}
Finished code
function injectLink(){
var headerElement = document.getElementsByClassName("flex-module-header")[1]; //Get the second instance
if(headerElement && headerElement.innerHTML != ""){
console.log("Element exists and has content. Injecting code...");
headerElement.innerHTML += "code"; //Currently revising, due to T.J. Crowder's side-note
} else {
console.log("Element doesn't exist or has no content. Refiring function...");
setTimeout(injectLink, 250);
}
}
Updated answer after you posted code:
getElementsByClassName
returns a NodeList
, not an element; NodeList
doesn't have an innerHTML
property. If you want the second matching element, use [1]
to get it, and then test whether you got something back (or if you like you can use .length > 1
to check first, but NodeList
is documented as returning null
for indexes that don't exist). Also, you don't need a function wrapper around your existing function when passing it to setTimeout
. So:
function injectLink(){
var headerElement = document.getElementsByClassName("flex-module-header")[1];
if (!headerElement) {
console.log("Element doesn't exist. Restarting function");
setTimeout(injectLink, 2000);
} else {
console.log("Element exists. Injecting");
// Do you really want a further two second delay?
setTimeout(function(){
headerElement.innerHTML += "code"; //Inject code into the second instance of the class-array
}, 2000);
}
}
Side note: Using .innerHTML += "markup";
is generally not a great way to append content to an element. Here's what the browser does when you do that:
So a bit of work, and also the possibility of wiping out event handlers. If you're adding a new child element, look at using createElement
and appendChild
. If you're adding further text content, look at createTextNode
and appendChild
.
Original answer:
If you give your element an id
(although the same concept works regardless of how you're finding the element), you can do this:
(function() {
setTimeout(check, 0);
function check() {
var elm = document.getElementById('theId');
if (!elm) {
setTimeout(check, 250); // Check again in a quarter second or so
return;
}
// Use the element
}
})();
There, we launch our first check almost immediately, and if we don't find the element, we schedule the check again 250ms later. Because we only schedule the next check if the previous one didn't find the element, we only loop as many times as necessary to find the element.
If the element doesn't have an id
, presumably you're finding it somehow (querySelector
, querySelectorAll
, etc.), and so will know whether you found it and will be able to schedule the next check.
Probably worth putting a limit on it, e.g.:
(function() {
var start = new Date();
setTimeout(check, 0);
function check() {
var elm = document.getElementById('theId');
if (!elm) {
if (new Date() - start > 5000) { // More than five seconds
throw "Couldn't find the element";
}
setTimeout(check, 250); // Check again in a quarter second or so
return;
}
// Use the element
}
})();
...so if you change your HTML and the element doesn't exist anymore, you see an error when testing and are reminded to update your code.