PeriodicalExecuter loading scripts dynamically - in order

45 views
Skip to first unread message

gingerbbm

unread,
Jan 13, 2010, 10:15:29 AM1/13/10
to Prototype & script.aculo.us
Dear all

I am loading a number of JavaScript files dynamically by appending to
the <head/> element. To ensure they're added in the correct order I
came up with the following code which uses a PeriodicalExecuter to
step through an array of script names as and when an anticipated
object in each script becomes available.

function loadScripts() {
var scripts = [
{ src: 'js/s1.js' , obj: 'Stu.s1.func' },
{ src: 'js/s2.js' , obj: 'Stu.s2.func' },
{ src: 'js/s3.js' , obj: 'Stu.s3.func' }
];
var stoptime = (new Date()).getTime() + 5000;
var i = 0;
var scriptadded = false;

new PeriodicalExecuter(function(pe) {
if (!scriptadded) {
addScript(scripts[i].src);
scriptadded = true;
}

if ((window.Stu) && (eval(scripts[i].obj))) {
// Found an object
if (i < scripts.length - 1) {
// Prepare to load next script
i++;
scriptadded = false;
} else {
// That was the last script
pe.stop();
callback_success();
}
} else if ((new Date()).getTime() > stoptime) {
pe.stop();
callback_outoftime();
}
}, 0.2);
}

File "s1.js" is defined as:

var Stu = window.Stu || {};
Stu.s1 = { func: function () { alert('s1'); } };

File "s2.js" is defined as:

var Stu = window.Stu || {};
Stu.s2 = { func: function () { alert('s2'); } };

File "s3.js" is defined as:

var Stu = window.Stu || {};
Stu.s3 = { func: function () { alert('s3'); } };

The function addScript() is not defined here but was inspired by [1].
The function callback_success() will do something interesting with my
dynamically-loaded functionality ;)

The problem is that it works in IE8, Safari 4 and Chrome 3 but not
Firefox 3.5.7. It breaks when eval'ing the second object, i.e.
"Stu.s2.func". I guess this is something to do with scope, but why
should it work the first time around?

I appreciate this might not be a Prototype issue per se but any advice
would be welcome. I'm currently considering making one long script to
avoid any of these shenanigans.

Thanks
Stuart

[1] http://proto-scripty.wikidot.com/prototype:how-to-load-scripts-dynamically.

T.J. Crowder

unread,
Jan 13, 2010, 12:03:51 PM1/13/10
to Prototype & script.aculo.us
Hi,

> I guess this is something to do with scope, but why
> should it work the first time around?

On the first loop, `scriptadded` will be false and so you do call
`addScript` and it all works. On the second loop, `scriptadded` will
be true, and so you never call `addScript` and the second script isn't
loaded.

OT: Why the `eval`? You can check whether the symbol exists directly,
you don't need `eval` to do it. (Avoid `eval` whenver possible.) It
requires a very small bit of rejigging your scripts array, but it's
easily done...

HTH,
--
T.J. Crowder
Independent Software Consultant
tj / crowder software / com
www.crowdersoftware.com

> [1]http://proto-scripty.wikidot.com/prototype:how-to-load-scripts-dynami....

gingerbbm

unread,
Jan 13, 2010, 1:16:51 PM1/13/10
to Prototype & script.aculo.us
Hi T.J.

'scriptadded' does get reset to false just after the loop counter 'i'
is incremented, and in fact through Firebug I can see the <script/>
tags appearing in the HTML - so I know this aspect of the code is
working.

> OT: Why the `eval`?

I know its use is to be avoided but I couldn't see a way around it.
If, for example, my array looked like this...

var scripts = [
{ src: 'js/s1.js' , obj: Stu.s1.func },
{ src: 'js/s2.js' , obj: Stu.s2.func },
{ src: 'js/s3.js' , obj: Stu.s3.func }

];

...then 'scripts[i].obj' will be null because at declaration time none
of the Stu literals exist. If there is an easy workaround I'd
appreciate a pointer ;)

Thanks
Stuart

T.J. Crowder

unread,
Jan 13, 2010, 2:24:43 PM1/13/10
to Prototype & script.aculo.us
Hi,

> 'scriptadded' does get reset to false just after the loop counter 'i'
> is incremented

Doh! Sorry I missed that.

>, and in fact through Firebug I can see the <script/>
> tags appearing in the HTML - so I know this aspect of the code is
> working.

I think your issue is with these lines in the scripts:

var Stu = window.Stu || {};

You're declaring that via `var`, but looking for it as a property of
`window`. There is some weirdness about global `var` statements and
window properties which I'd try to avoid by being explicit:

if (!window.Stu) {
window.Stu = {};
}

...or if you prefer the functional style:

window.Stu = window.Stu || {};

> > OT: Why the `eval`?
>
> I know its use is to be avoided but I couldn't see a way around it.

You can check the individual parts of the path. If you change
`scripts` to

var scripts = [
{ src: 'js/s1.js' , obj: ['Stu', 's1', 'func'] },
{ src: 'js/s2.js' , obj: ['Stu', 's2', 'func'] },
{ src: 'js/s3.js' , obj: ['Stu', 's3', 'func'] }
];

...you could use

function checkObjExists(context, path) {
var index;

for (index = 0; index < path.length; ++index) {
context = context[path[index]];
if (!context) {
// This part wasn't found
return false;
}
}

// Found
return true;
}

...like so:

if ((window.Stu) && (checkObjExists(window, scripts[i].obj))) {

If you prefer to keep your scripts string the way it is, just use
`scripts[i].obj.split('.')` to create the array on the fly.

gingerbbm

unread,
Jan 13, 2010, 4:55:38 PM1/13/10
to Prototype & script.aculo.us
Fantastic! Thank you T.J. :)

Both modifications were required and the checkObjExists() function is
dead handy.

I really am very grateful because this is the best solution to my
dynamic-loading requirement. I wanted to avoid having to rely on
concatenating several scripts into one (although some kind of
optimisation will need to be done to prevent too many separate HTTP
requests), and I also wanted to avoid more dumb, less elegant
solutions. Without your help I wouldn't have got there.

Cheers
Stuart

T.J. Crowder

unread,
Jan 13, 2010, 5:44:30 PM1/13/10
to Prototype & script.aculo.us
Cool! Glad to have helped.

-- T.J. :-)

Reply all
Reply to author
Forward
0 new messages