0

I have created a small script loader function that will iterate over a javascript object populated with names of files in a directory. This is used to dynamically load Mustache templated files to create a page.

I created the javascript object to list the templates in the order I want them output to the page but sometimes when I load the page the elements are not in the right order. I know that in theory that $.each() 'does' iterate in order from top to bottom, but I'm wondering if there is some way I can explicitly force it to do in order to avoid the occasional improperly laid out page.

var loadModules = function(){
var templates = {
    1 : 'header',
    2 : 'about' 
}
$.each(templates, function(key, value) {
    $.getScript("js/modules/"+value+".js", function(){
        Init();
    });
});
}

This is a snippet of the code and from time to time when I load the page you will see the 'about' section appear above the 'header'. Is there any way I can stop this from happening or is it just a 'browser hiccup'?

I created the 'templates' object in order to specify the order of the page elements but it only seems to work most of the time.

nate63
  • 75
  • 1
  • 1
  • 7
  • 1
    objects are unordered collection of elements. That's said, you could use a for loop and target specific object property key – A. Wolff Feb 05 '14 at 14:44
  • 1
    Your Template could be an array which will be in order as $.each uses an indexed for to walk arrays. – Alex K. Feb 05 '14 at 14:45
  • Excellent, I guess sometimes the simplest methods are the best. Taking from both of your answers I changed the object to an array and replaced the $.each with a plain ol' for loop and it works great. – nate63 Feb 05 '14 at 15:03
  • I take that back. the order is still changing. I guess the fact that the $.getScript function is an async ajax call is still causing problems... – nate63 Feb 05 '14 at 15:24

3 Answers3

1

Update if you don't want to change the structure of your templates so they can load better asynchronously then you may have to fall back a recursive loading approach. This will be relatively akin to synchronous loading of resources as you will be ensuring only one request is running at a time.

var templates = [
    'header',
    'about'
    /*....*/
];

//load up your templates 1 by 1 in a recursive loop
(function loadNextTemplate(index) {
    if(index >= templates.length) return; //base case
    $.getScript("js/modules/" + templates[index] + ".js")
     .then(function() {
        Init();
        loadNextTemplate(index + 1);
     });
})(0);
megawac
  • 10,953
  • 5
  • 40
  • 61
  • I like the look of this but my problem is that each script has it's own Init() function and each Init() has to run. If I run Init just once it will only load one of the modules. How would I modify this to run each loaded script's Init() function as it is loaded? – nate63 Feb 05 '14 at 16:15
  • So they each expose a different global Init function? – megawac Feb 05 '14 at 16:16
  • essentially, yes. each one of those loaded scripts has it's own Init() function to initialize the module. – nate63 Feb 05 '14 at 16:43
  • Thanks. This works great. The method I was using, setting async to false still seemed to throw up and load the modules in random order from time to time. This method has been tested extensively and has not yet yielded undesired results. – nate63 Feb 08 '14 at 12:49
0

Then you call $.getScript it send XMLHttpRequest, which use as async by default in $.ajax. $.getScript is the short calling of $.ajax for javascript files. So then the each is running it sends 2 async AJAX requests without guarantee in what order will requests ends.

If you what to call Init after 2 ajax requests ends, use jQuery Deffered/Promises pattern:

$.when($.getScript("js/modules/header.js"),  $.getScript("js/modules/about.js")).done(function(){
    Init();//Init will call after to AJAX requests ends, but without its order
});

If you need to load header.js always first and about.js must be the second, use this pattern like this:

$.when($.getScript("js/modules/header.js").then($.getScript("js/modules/about.js")).done(function(){
    Init();//header loads, after this about loads, after this Init will call
});

If you need to load 10-15 js files try to use AMD by requirejs. It need some special code wrappers functions, but requirejs is more useful. As for me using Deffered/Promises for 15 callbacks is not the hell.

Alex
  • 11,115
  • 12
  • 51
  • 64
  • I see what you are saying about the async nature of $.getScript, but the array will continue to grow as more elements are added to the page and I would hate to chain together 10-15 '.then' statements to ensure proper order. That would take away from the semi-dynamic nature of the function. – nate63 Feb 05 '14 at 15:17
  • Maybe RequireJs is my best option here... I will check it out. Thanks – nate63 Feb 05 '14 at 16:16
0

Solved. Instead of using the always asynchronous $.getScript I opted to go with a simple ajax call setting the async value of the call to false. This always loads the scripts in the order they are listed in the array thanks to the for loop and async:false value of the ajax call. Eg:

var loadModules = function(){
//List modules in templates array in order you want them to appear in the DOM
var templates = [
    'header',
    'about' 
];


var i;
for (i=0; i<templates.length; ++i){
    $.ajax({
        url: "js/modules/"+templates[i]+".js",
        dataType: 'script',
        success: function(){
            Init();
        },
        async: false
    });
}

Thaks for all your help on this. I took a little bit from every answer/suggestion and applied them all so I don't know who's answer to mark as the right one..

nate63
  • 75
  • 1
  • 1
  • 7
  • Note: setting **async:false** will block the thread until the request completes. Consider the case when someone with a poor internet connection tries to load content - his browser will be frozen. See http://stackoverflow.com/questions/6517403/what-are-the-drawbacks-of-using-synchronous-ajax-call – megawac Feb 05 '14 at 18:25
  • Here's the post I was looking for on the topic [How to return the response of an ajax call](http://stackoverflow.com/questions/14220321/how-to-return-the-response-from-an-ajax-call/14220323#14220323) – megawac Feb 05 '14 at 18:38
  • interesting reading... My issue is as long as the calls are asynchronous then the first returned piece of data will be at the top of the page (my page is being completely built from loading these modules). If this happens asynchronously, which would be a good idea, then all modules would be loading into the browser at the same time and the order of them on the page would be determined by the fastest response down to the slowest. That is why I experimented with the async: false param. It may not be ideal but at least I can specify the order of the page elements. – nate63 Feb 05 '14 at 20:14
  • A recursive approach can obtain similar results without the locking disadvantage of sync requests – megawac Feb 05 '14 at 20:35