Andrei
Andrei

Reputation: 171

The onload event on a script element is fired only once, even if I change the src. Can I do something about this?

I think the title explains all.

Here is the code:

<script>
var script = document.createElement('script');
script.onload = function(){
    console.log('Loaded');
};
document.querySelector('head').appendChild(script);
</script>

<button onclick="script.src = 'https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js?call_number=' + Date.now(); console.log(script.src)">Load the script no matter how many times we click the button</button>

Push the button twice and check the console. The onload event is called only once.

Is that an intended behavior for browsers ?

I'm asking because the script might contain data based on the call_number parameter. Because I can not call the onload more than once, I am forced to insert the script element in the document again and again. That's frustrating.

Please, don't tell me to create a function to insert the script. That's not my problem. My problem is that I'm forced to remove/add the script from the dom.

So, I'm asking again: Is that an normal behavior for browsers or I'm doing something wrong?

PS: As a side note. On an iframe element, the onload event is called each time the button is clicked.

Here is the code for an iframe:

<body>
<script>
var iframe = document.createElement('iframe');
iframe.onload = function(){
    console.log('Loaded');
};
document.querySelector('body').appendChild(iframe);
</script>
</body>

<button onclick="iframe.src = 'https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js?call_number=' + Date.now(); console.log(iframe.src)">Load the iframe no matter how many times we click the button</button>

Very annoying...

Upvotes: 0

Views: 669

Answers (1)

JPC
JPC

Reputation: 943

What's Happening

It's not that the onLoad event only fires once, it's that the script itself only fires once.

To avoid unintentional side effects, browsers only fire script tags once as soon as they are encountered/initialized. In your case it happens when a src is added, but it can also happen if the script's innerHTML is set instead. If either of these are set before appending them to the DOM, the script will only fire once it's been appended.

TL;DR: Script tags can't be reused. Changing any part of a script tag after it's been fired will only result in the DOM being updated. The script itself will not be fired again.

To emphasize this point, take the following script:

<script>
var script = document.createElement('script');
script.innerHTML = `console.log('Loaded');`;
document.querySelector('head').appendChild(script);
script.innerHTML = ``;
</script>

<button onclick="script.innerHTML = `console.log('Loaded');`;">Load the script no matter how many times we click the button</button>

The word "Loaded" will appear in your console as soon as the innerHTML is read by the page, which happens when document.querySelector('head').appendChild(script); is encountered. You can also append the script tag before adding the innerHTML in which case "Loaded" will appear when the innerHTML is set. Either way, clicking the button will result in no output since that particular script tag has already fired. Adding a src later or with a button will also not cause it to fire. Note too that in this case, onLoad will never be fired since the inline js has been run rather than loading a src file.

Simply removing and appending the same element variable again will also not work as the DOM will remember it. Your only way to refire or fire a new script is to generate a new script tag and initialize it either with a src (preferred) or innerHTML.

Suggestions

You could encapsulate your scripts in a function and add them to an Array you can track like so:

<script>
var scripts = [];
function loadScript(callNumber) {
  var script = document.createElement('script');
  script.src = 'https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js?call_number=' + callNumber;
  script.onload = function() {
    console.log('Loaded', scripts.length, script.src);
  };
  scripts.push(script);
  document.querySelector('head').appendChild(script);
}
</script>

<button onclick="loadScript(Date.now());">Load the script no matter how many times we click the button</button>

If you don't care about anything but the last script instance, you can just reassign a variable each time. If I understand correctly that you never want more than one of this script tag on the DOM for whatever reason (not encouraged or normally necessary), you can remove old ones as part of the call function. The version below should function exactly as you were hoping your example would:

<script>
var script;
function loadScript(callNumber) {
  if (script) script.remove();
  script = document.createElement('script');
  script.src = 'https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js?call_number=' + callNumber;
  script.onload = function() {
    console.log('Loaded', scripts.length, script.src);
  };
  document.querySelector('head').appendChild(script);
}
</script>

<button onclick="loadScript(Date.now());">Load the script no matter how many times we click the button</button>

I would also encourage you to reevaluate why you need to reload the script and investigate whether loading the script without a call number, then accessing functions from it directly would work better. Having run the script, any functions and variables in it will still be available to the page after it's loaded and you can simply access them directly. For example if your loaded .js file has a function foo(date) {}, call that from the onclick rather than rerunning the entire .js file.

Even better, if it's your code and not an external library, add a click listener to the document from within the .js file and perform all your logic there.

Why iFrames Are Different

iFrames load entire pages independently of the one you're on. Updating it's src will cause it to load the new document with its own DOM, whatever it happens to be, just like if you updated the url in your browser and hit Enter.

Upvotes: 0

Related Questions