Search code examples
javascripthtmlonloadhead

Javascript function call fails in head, but not in body


Solution: There appears to have been an issue in the way I was calling my queuing mechanism into window.onload.

Preamble - I'm glad to see I'm being flagged as a duplicate, where the solution is to use window.onload, which is something I'm already using here. A couple comments have indicated possible issues with the queue that I got from another stackoverflow solution, but leave me in the dark due to lack of elaboration.

Question: If I make a function call in the head, it fails. If I make a function call in the body, it succeeds. Why?

Extension: Why do the calls to the same function in global.js, which is above the failing call, succeed?

I have some javascript that creates an onload "queue" of sorts (function addLoadEvent in global.js). A call to this function adds to the queue for when onload is called.

Except, I've found that a particular script function call fails if it's located in the head vs in the body. I can't figure out why, because everything that's needed (other js functions) are loaded above the function call itself, and the actual call to the function isn't triggered until onload, ensuring that the necessary html elements exist.

Order of loading:

  1. html file up to head
  2. global.js - including addLoadEvent(func)
    • addLoadEvent x2 (succeeds)
  3. inline script in head - includes initialFighter()
    • addLoadEvent (location one - fails)
  4. html file after head
    • addLoadEvent (location two - succeeds)
  5. onload triggers queued calls

For the purpose of this question, the function call that fails or succeeds based on it's location is the following.

addLoadEvent(initialFighter());

Here's the stripped down HTML, note the 2 locations flagged. If I copy paste the above function call to Location one, it fails. If I copy paste the above function call to Location two, it succeeds.

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8" />
  <script src="global.js"></script>  
  <script type="text/javascript">
  function showFighter(id) {
    var pic = document.getElementById("picture");
    var picPath = 'url(images/' + id + '.png)';
    pic.style.backgroundImage = (picPath);
  }
  function initialFighter() {
    var fighter = getURLParameter('fighter');
    if (typeof(fighter) != "undefined" && fighter != null) {
      showFighter(fighter);
    } else {
      showFighter('Foobar');
    }
  }
  ***** LOCATION ONE *****
  </script>
</head>
<body>
<header></header>
<nav id="nav"></nav>
<section id="fighters">
  <div id="picture"></div>
  <div id="text"></div>
  <script type="text/javascript">
  ***** LOCATION TWO *****
  </script>
</section>
<footer id="footer"></footer>
</body>
</html>

Below is global.js, which is the very first script file loaded:

Note that there are 2 addLoadEvent(func) calls here, and they succeed (don't run until after html elements exist) despite being above practically everything else.

function addLoadEvent(func) {
  var prevOnLoad = window.onload;
  if (typeof window.onload != 'function') {
    window.onload = func;
  } else {
    window.onload = function() {
      if (prevOnLoad) {
        prevOnLoad();
      }
      func();
    }
  }
}

function loadFile(id, filename) {
  var xmlhttp;
  if (window.XMLHttpRequest) {
    xmlhttp=new XMLHttpRequest();
  }
  xmlhttp.onreadystatechange=function() {
    if (xmlhttp.readyState==4 && xmlhttp.status==200) {
      document.getElementById(id).innerHTML=xmlhttp.responseText;
    }
  }
  xmlhttp.open('GET', filename, true);
  xmlhttp.send();
}

addLoadEvent(loadFile('nav', 'nav.txt'));
addLoadEvent(loadFile('footer', 'footer.txt'));

function getURLParameter(name) {
  return decodeURIComponent((new RegExp('[?|&]' + name + '=' + '([^&;]+?)(&|#|;|$)').exec(location.search) || [null, ''])[1].replace(/\+/g, '%20')) || null;
}

Solution

  • The argument to addLoadEvent is supposed to be a function, which will be called during the onload callback. You're calling the function immediately, and passing its return value, so the function isn't waiting for the onload event, and you get errors because the elements aren't in the DOM yet. It should be:

    addLoadEvent(function() { loadFile('nav', 'nav.txt'); });
    addLoadEvent(function() { loadFile('footer', 'footer.txt'); });
    addLoadEvent(initialFighter);
    

    You don't need an anonymous wrapper function for initialFighter, since it doesn't take any arguments. You just need to leave out the (), so you pass a reference to the function, instead of calling the function immediately.

    Also, instead of chaining onload event handlers by saving the old value and calling it inside the new function, you can simply use addEventListener, as these automatically add to the list of listeners instead of replacing it:

    function addLoadEvent(func) {
      window.addEventListener("load", func);
    }