-1

This question is not duplicate of

Conditionally load JavaScript file

and nor this

How to include an external javascript file conditionally?

I have gone through them, they are kind of similar to what I want to do but not exactly same. Here is how, in the above question the user just wants to load the script file once based on a condition. But in my case I want to load different js files based on click events.

So here in my case I have an HTML document:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Experiment</title>
    <link href="s.css" rel="stylesheet" type="text/css">
    
</head>
<body>
    <div class="navigation">
        <nav>
            <ul>
                <li id="home_btn"> Home</li>
                <li id="about_btn"> About </li>
            </ul>
        </nav>
    </div>

    <canvas id="myCanvas">

    </canvas>

    <div class="notePane">
        <p> This is just a bunch of text not explanation</p>
    </div>
   
</body>
<script src="./exp.js" type="text/javascript"></script>
</html>

and this h.html file is linked to an exp.js file. Now in the exp.js file :

var h_btn = document.getElementById("home_btn");
var a_btn = document.getElementById("about_btn");
var head = document.getElementsByTagName("head")[0];
var js = document.createElement("script");
js.type="module";


h_btn.addEventListener("click", showHome );
a_btn.addEventListener("click", showAbout);

function showHome() {
   
    js.src="./j1.js";
    head.appendChild(js);
}

function showAbout() {
    js.src="./j2.js";
    head.appendChild(js);
}

So things work fine when I click the h_btn on the web page. It loads j1.js. But then when I click on the a_btn on the web page I expect to see j2.js linked but I don't see it. I have to refresh the page and then click on a_btn to see j2.js linked. How do I link j1.js and j2.js such that I don't have to refresh the page again and again to load the correct script.

Ulysse BN
  • 10,116
  • 7
  • 54
  • 82
Yuseff
  • 169
  • 4
  • 14
  • If you clone the script element instead of using it, does it make a difference? Reusing the same element is not a good idea. When you reuse it, it is not creating another element, it is moving it from one place to the other in the document. – epascarello Jan 11 '21 at 14:06

2 Answers2

1

Update: OP has updated the question requirements such that he wants to "unload" a JS file when another is clicked. There is no way to undo all the runtime logic once a JS file is loaded: the only way is to reload the page. Removing the <script> tag or changing the src attribute will not magically unbind event listeners or "undeclare" variables.

Therefore, if OP wants to "start anew", the only way is to check if a custom script has been loaded before: if it has, we force reload the page. There are of course many ways to "inform" the next page which source to load, if available: in the example below, we use query strings:

var h_btn = document.getElementById("home_btn");
var a_btn = document.getElementById("about_btn");
var head = document.getElementsByTagName("head")[0];
var appendedScriptKey;
var scripts = {
    'home': './j1.js',
    'about': './j2.js'
}

h_btn.addEventListener("click", showHome);
a_btn.addEventListener("click", showAbout);

// Check query string if a specific script is set
var params = (new URL(document.location)).searchParams;
var scriptKey = params.get('scriptKey');
if (scriptKey && scriptKey in scripts) {
    appendScript(scriptKey);
}

function appendScript(key) {
    if (hasAppendedScript) {
        location.href = location.href + (location.search ? '?' : '&') + 'script=' + key;
        location.reload();
    }

    var js = document.createElement("script");
    js.type="module";

    js.src = scripts[key];
    head.appendChild(js);

    appendedScript = key;
}

function showHome() {
    appendedScriptKey('home');
}

function showAbout() {
    appendScript('about');
}

This is because of how Node.appendChild() works. The first click works because you're creating a new element and inserting it into your document. However, the second click will not work as you've expected because the node already exists:

The Node.appendChild() method adds a node to the end of the list of children of a specified parent node. If the given child is a reference to an existing node in the document, appendChild() moves it from its current position to the new position

This means that the second click will only mutate the src attribute of the already-injected <script> element instead of creating a new one, and that also means that the second script src will not be loaded.

A solution will be to use a function that will create a script tag every single time:

var h_btn = document.getElementById("home_btn");
var a_btn = document.getElementById("about_btn");
var head = document.getElementsByTagName("head")[0];
h_btn.addEventListener("click", showHome);
a_btn.addEventListener("click", showAbout);

function insertScript(src) {
  var js = document.createElement("script");
  js.type = "module";
  js.src = src;
  head.appendChild(js);
}

function showHome() {
  insertScript('./j1.js');
}

function showAbout() {
  insertScript('./j2.js');
}

But this will also mean that multiple clicks on the same button will cause the script to be injected multiple times. This does not affect browser performance much since the browser has the loaded script cached somewhere, but to guard against this, it might be a good idea to implement some kind of unique identifier per script, and check against that before injection. There are many ways to do this, and this is just one way:

var h_btn = document.getElementById("home_btn");
var a_btn = document.getElementById("about_btn");
var head = document.getElementsByTagName("head")[0];
h_btn.addEventListener("click", showHome);
a_btn.addEventListener("click", showAbout);

// Store scripts that you've injected
var scripts = [];

function insertScript(src) {
  // If we have previously attempted injection, then do nothing
  if (scripts.indexOf(src) !== -1) {
    return;
  }
  
    var js = document.createElement("script");
    js.type = "module";
    js.src = src;
    head.appendChild(js);
  
  // Push script to array
  scripts.push(src);
}

function showHome() {
    insertScript('./j1.js');
}

function showAbout() {
    insertScript('./j2.js');
}

Alternative unique script injection strategies and ideas:

  • Use ES6 Map() to track unique script sources being injected
  • Perhaps only store src to array/dict/map when the script has successfully loaded
Terry
  • 63,248
  • 15
  • 96
  • 118
  • The solution you provided works but not completely. Cause when I click the home button and then the about button it works fine but when I click on the home button again it does not work. Another problem I am facing is that the j1.js and j2.js mix up. when I switch between them. So I thought that in the insert function inside the conditional after checking if the src previously exists then I would remove the script[0] element from the dom and then append . But this isn't working . Can you pls help me with that – Yuseff Jan 12 '21 at 04:34
  • Actually what I want is to be able to switch between these j1 and j2 files. And once I click on the j1 and don't want previous js files to be there . – Yuseff Jan 12 '21 at 04:40
  • You can switch, but you do know you cannot “undo” whatever the first loaded script has done, right? It will not magically remove all the event listeners and etc just because you remove the script tag. – Terry Jan 12 '21 at 07:10
  • So do you know any method to remove all that was there before , so that the newly load js file can begin with clean web page – Yuseff Jan 12 '21 at 08:08
  • The only way is to reload the page. – Terry Jan 12 '21 at 15:58
0

You have to create the element twice, as there can only be one element with 1 src.

var h_btn = document.getElementById("home_btn");
var a_btn = document.getElementById("about_btn");
var js1 = document.createElement("script");
var js2 = document.createElement("script");


h_btn.addEventListener("click", showHome);
a_btn.addEventListener("click", showAbout);

function showHome() {

  js1.src = "j1.js";
  document.body.appendChild(js1);
}

function showAbout() {
  js2.src = "j2.js";
  document.body.appendChild(js2);
}
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Experiment</title>
  <link href="s.css" rel="stylesheet" type="text/css">

</head>

<body>
  <div class="navigation">
    <nav>
      <ul>
        <li id="home_btn"> Home</li>
        <li id="about_btn"> About </li>
      </ul>
    </nav>
  </div>

  <canvas id="myCanvas">

    </canvas>

  <div class="notePane">
    <p> This is just a bunch of text not explanation</p>
  </div>

</body>
<script src="exp.js" type="text/javascript"></script>

</html>
Endothermic_Dragon
  • 1,147
  • 3
  • 13
  • The code doesn't work because the files don't exist on stack overflow, but it should work in your code. – Endothermic_Dragon Jan 11 '21 at 14:07
  • your code works for when I click on home button and then on the about button . But it doesn't work when I want to switch back to the home . – Yuseff Jan 12 '21 at 04:38
  • I edited the code a little bit. Try it out now. If it still doesn't work, there's probably something wrong in the js files. – Endothermic_Dragon Jan 12 '21 at 12:13
  • Also make sure that this script is placed at the END of the body, or add a dom content loaded to the script to make sure it fires appropriately when the whole document has loaded. – Endothermic_Dragon Jan 12 '21 at 12:16