2

I am trying to write a HTML page that asks users a series of questions. The answers to these questions are evaluated by my JavaScript code and used to determine which additional JavaScript file the user needs to access. My code then adds the additional JavaScript file to the head tag of my HTML page. I don't want to merge the code into a single JavaScript file because these additional files are large enough to be a nightmare if they're together, and I don't want to add them all to the head when the page first loads because I will have too many variable conflicts. I'm reluctant to redirect to a new webpage for each dictionary because this will make a lot of redundant coding. I'm not using any libraries.

I begin with the following HTML code:

<head>
<link rel="stylesheet" type="text/css" href="main.css">
<script src="firstSheet.js" type="text/JavaScript"></script>
</head>
//Lots of HTML.
<div id="mainUserMenu">
</div>

And I have the following JavaScript function:

function thirdLevelQuestions(secondLevelAnswer) {
//Code here to calculate the variables. This part works.
activeDictionary = firstKey + secondKey + '.js';
//Changing the HTML header to load the correct dictionary.
document.head.innerHTML = '<link rel="stylesheet" type="text/css" href="main.css"><script src="' + activeDictionary + '" type="text/JavaScript"></script><script src="firstSheet.js" type="text/JavaScript"></script>';
//for loop to generate the next level of buttons.
for (var i = 0; i < availableOptions.length; i++) {
    document.getElementById('mainUserMenu').innerHTML += '<button onclick="fourthLevelQuestions(' + i + ')">' + availableOptions[i] + '</button>';
}

}

This creates the buttons that I want, and when I inspect the head element I can see both JavaScript files there. When I click on any of the buttons at this level they should call a function in the second file. Instead Chrome tells me "Uncaught ReferenceError: fourthLevelQuestions is not defined" (html:1). If I paste the code back into firstSheet.js the function works, so I assume the problem is that my HTML document is not actually accessing the activeDictionary file. Is there a way to do this?

  • hmm.. I have never done this, but could it work if you reloaded the page (location.reload()) after button click. When the page reloads, in the header element you'd check from a cookie or session variable or local storage what kind of activeDictionary users wants. Of course following the original button click, you need to save the activeDictionary value into a cookie, or session variable or local storage before reloading the page. – jyrkim Dec 02 '14 at 09:07

3 Answers3

1

What Can be done

You are trying to load Javascript on Demand. This has been a well thought out problem lately and most of the native solutions didn't work well across bowser implementations. Check a study here with different solutions and background of the problem explained well.

For the case of large web applications the solution was to use some javascript library that helped with modularising code and loading them on demand using some script loaders. The focus is on modularizing code and not in just script loading. Check some libraries here. There are heavier ones which includes architectures like MVC with them.

If you use AJAX implementation of jQuery with the correct dataType jQuery will help you evaluate the scripts, they are famous for handling browser differences. You can as well take a look at the exclusive getScript() which is indeed a shorthand for AJAX with dataType script. Keep in mind that loading script with native AJAX does not guarantee evaluation of the javascript included, jQuery is doing the evaluation internally during the processing stage.

What is wrong

What you have done above might work in most modern browsers (not sure), but there is an essential flaw in your code. You are adding the script tags to your head using innerHTML which inserts those lines to your HTML. Even if your browser loads the script it takes a network delay time and we call it asynchronous loading, you cannot use the script right away. Then what do you do? Use the script when its ready or loaded. Surprisingly you have an event for that, just use it. Can be something like:

   var head= document.getElementsByTagName('head')[0];
   var script= document.createElement('script');
   script.type= 'text/javascript';
   script.onreadystatechange= function () {
      if (this.readyState == 'complete') helper();
   }
   script.onload= helper;
   script.src= 'helper.js';
   head.appendChild(script);

Check this article for help with implementation without using external libraries

From the variable name activeDictionary If I guess that you are loading some data sets as opposed to javascript programs, you should try looking into JSON and loading and using them dynamically.

If this Question/Answer satisfies your needs, you should delete your question to avoid duplicate entries in SO.

Community
  • 1
  • 1
sabithpocker
  • 15,274
  • 1
  • 42
  • 75
  • 1
    That was an incredibly helpful answer. In this instance though I am trying to create a series of JavaScript files that are totally independent of each other in terms of code, so I don't think it is a duplication of including a JavaScript file in another JavaScript file. – Catherine Gracey Dec 03 '14 at 05:39
  • @CatherineGracey Also one thing that I missed to mention is the case of variable conflicts you mentioned. Dividing code into separate files **doesn't** make a difference as all javascript in a browser window gets executed in the same global `window` scope, you can take a look at articles on using `closures` to avoid globals and variable conflicts. – sabithpocker Dec 04 '14 at 05:23
  • The potential for variable conflicts is because I am committing a little programming sin by blowing away the values in my global variables each time I swap out these files. That's why I can't load all of my options at the beginning - I don't know ahead of time which values should survive and which would be the incorrect version. – Catherine Gracey Dec 04 '14 at 23:08
  • If the software works from the user perspective that's not a sin anymore. Think of changing the ideals once demand comes in :) – sabithpocker Dec 05 '14 at 06:33
0

The best way to achieve this would be with jQuery:

$(document).ready(function() {
    $('#button').click(function() {
        var html = "<script src='newfile.js' type='text/javascript'></script>";
        var oldhtml = "<script src='firstSheet.js' type='text/javascript'></script>";

        if ($(this).attr('src') == 'firstSheet.js') {
            $('script[src="firstSheet.js"]').replace(html);
            return;
        }

        $('script[src="newfile.js"]').replace(oldhtml);
    });
});
zanderwar
  • 3,440
  • 3
  • 28
  • 46
0

I would suggest you create the elements how they should be and then append them. Also, if you are dynamically adding the firstSheet.js you shouldn't include it in your .html file.

function thirdLevelQuestions(secondLevelAnswer) {
    var mainUserMenu = document.getElementById('mainUserMenu');
    activeDictionary = firstKey + secondKey + '.js';
    var css = document.createElement('link');
    css.rel = 'stylesheet';
    css.type = 'text/css';
    css.href = 'main.css';
    var script1 = document.createElement('script');
    script1.type = 'text/javascript';
    script1.src = 'firstSheet.js';
    var script2 = document.createElement('script');
    script2.type = 'text/javascript';
    script2.src = activeDictionary;
    document.head.appendChild(css);
    document.head.appendChild(script1);
    document.head.appendChild(script2);
    for (var i = 0; i < availableOptions.length; i++) {
        var btn = document.createElement('button');
        btn.onclick = 'fourthLevelQuestions(' + i + ')';
        var val = document.createTextNode(availableOptions[i]);
        btn.appendChild(val);
        mainUserMenu.appendChild(btn);
    }
}
Weafs.py
  • 22,731
  • 9
  • 56
  • 78