-3

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;
}
Doug
  • 1
  • 2
  • 1
    `addLoadEvent(loadFile('nav', 'nav.txt'));` does not do what you think it does. – zzzzBov Feb 13 '17 at 23:17
  • The argument to `addLoadEvent` has to be a function, not a call of a function. – Barmar Feb 13 '17 at 23:17
  • Can you please clarify these comments? If it's not doing what I think it's doing, how is it still working in most places? Barnar, you indicate that I'm sending incorrect parameters to addLoadEvent, yet somehow it still works in most cases? If the parameters were of the wrong type, wouldn't it fail 100%? – Doug Feb 13 '17 at 23:30
  • 1
    Possible duplicate of [javascript code not work in HEAD tag](http://stackoverflow.com/questions/15675745/javascript-code-not-work-in-head-tag) – Bulent Vural Feb 13 '17 at 23:47
  • Bulent, I am already implementing a solution to avoid that, involving the usage of window.onload. – Doug Feb 14 '17 at 00:02

1 Answers1

1

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);
}
Barmar
  • 741,623
  • 53
  • 500
  • 612
  • interesting. The explanation makes sense and I will try implementing it, but I still can't quite figure out how those 2 loadFile calls were working if they weren't being queued. If not being queued, those calls were clearly happening before the existence of the elements they were supposed to modify, which should have caused them to fail as well? Oops edit, for answer part 2, I'll have to look into addEventListener, I'm not familiar with how that ties into window.onload. – Doug Feb 14 '17 at 01:05
  • If you put it at location two, `loadFile('nav', 'nav.txt')` should work, but `loadFile('footer', 'footer.txt')` should fail, because location two is after the `nav` element is loaded. The call to `loadFile` isn't queued, it happens immediately. – Barmar Feb 14 '17 at 01:14
  • `initialFighter()` works in the body because it's after the `picture` element is loaded. – Barmar Feb 14 '17 at 01:16
  • I implemented both halves of your solution. The first half seems to have solved the overall issue, while the 2nd half seems a bit cleaner than what I had for managing a "queue", although it's not exactly a queue anymore. – Doug Feb 14 '17 at 01:17
  • It's the same kind of queue because the listener functions are called in the order that they're added. – Barmar Feb 14 '17 at 01:18
  • Just to comment on the location one/location two. Those to "loadFile" calls were actually happening inside global.js, which is well before even the body of the html is loaded. That's what I still don't understand. If those 2 calls weren't being queued in their current location, how the heck did they ever succeed long before the elements they modified existed? – Doug Feb 14 '17 at 01:27
  • Oh, it's because `loadFile()` uses AJAX. The AJAX callback function runs asynchronously, and it doesn't complete until after the DOM finishes loading. – Barmar Feb 14 '17 at 01:30