Invoking handler when all scripts finished loading via $.getScript - javascript

So let's say we load a bunch of scripts via $.getScript:
$.getScript( 'js/script1.js' );
$.getScript( 'js/script2.js' );
$.getScript( 'js/script3.js' );
Now, I'd like to invoke a handler when all those scripts finished loading. I tried binding a handler for the global ajaxStop event. According to the docs, the ajaxStop global event is triggered if there are no more Ajax requests being processed.
$( document ).ajaxStop( handler );
but it doesn't work (the handler is not invoked).
Live demo: http://jsfiddle.net/etGPc/2/
How can I achieve this?

I digged a little more into this issue and it's indeed the cross-domain script request that's the caveat. As I posted in the comments, that scenario has been implemented such that it sets the global option to false. This makes jQuery not to fire global ajax events. (No idea why that has been implemented though.)
This can be confirmed with this fiddle (pass means ajaxStop is fired):
cross-domain, no script: pass
cross domain, script: fail
no cross-domain, no script: pass
no cross-domain, script: pass
The most straight-forward thing to do is simply adding another prefilter which forces the global option to true:
jQuery.ajaxPrefilter( "script", function() {
s.global = true;
});
This also makes this failing scenario pass in the fiddle.

you're doing it wrong anyways :)
here is one of the things that can happen:
imagine the scripts are cached, then they might be loaded in no time.
so, straight after the first call $.getScript( 'js/script1.js' ); the script will be available and $.ajaxStop (might!!!) get called, in the worst case that would happen three times.
to answer your question indirectly i would propose a different solution which avoids this race condition alltogether.
you can try it here: http://jsfiddle.net/etGPc/8/
var urls, log, loaded;
// urls to load
urls = [
'https://raw.github.com/h5bp/html5-boilerplate/master/js/script.js',
'https://raw.github.com/h5bp/html5-boilerplate/master/js/plugins.js',
'https://raw.github.com/h5bp/html5-boilerplate/master/js/libs/modernizr-2.0.6.min.js'
];
// urls loaded
loaded = [];
log = $( '#log' );
$.map( urls, function( url ){
$.getScript( url, function(){
// append to loaded urls
loaded.push( url );
log.append( "loaded " + url + "<br>" );
// all loaded now?
if( loaded.length == urls.length ){
log.append( "<b>all done!</b>" );
}
} );
} );
if you haven't seen jQuery.map before: it's not really different from a for-loop :)
another advantage here is that this method doesn't get confused if you have other ajax requests going on at the same time.
p.s. to avoid naming-clashes you can wrap the entire thing in a self-executing function, i.e.
function(){
var urls, log, loaded;
... all code here ...
} ();
Update: Refactored the code a bit...
var urls, loadedUrls, log;
urls = [
'https://raw.github.com/h5bp/html5-boilerplate/master/js/script.js',
'https://raw.github.com/h5bp/html5-boilerplate/master/js/plugins.js',
'https://raw.github.com/h5bp/html5-boilerplate/master/js/libs/modernizr-2.0.6.min.js'
];
loadedUrls = [];
log = $( '#log' )[0];
urls.forEach(function ( url ) {
$.getScript( url, function () {
loadedUrls.push( url );
$( log ).append( 'loaded ' + url + '<br>' );
if( loadedUrls.length === urls.length ){
$( log ).append( '<b>all done!</b>' );
}
});
});
Live demo: http://jsfiddle.net/etGPc/10/

While dealing with my own problem I found a much better and elegant solution to this problem, and it is using the jQuery deffered object. I think you already know about the function, maybe for another usecase, but it works great for loading files, and fire functions when eveything is done. Code is as follows:
function getLatestNews() {
return $.get('/echo/js/?delay=2&js=', function(data) {
console.log('news data received');
$('.news').css({'color':'blue'});
});
}
function getLatestReactions() {
return $.get('/echo/js/?delay=5&js=', function(data) {
console.log('reactions data received');
$('.reactions').css({'color':'green'});
});
}
function prepareInterface() {
return $.Deferred(function(dfd) {
var latest = $('.news, .reactions');
latest.slideDown(500, dfd.resolve);
latest.addClass('active');
}).promise();
}
$.when(
getLatestNews(),
getLatestReactions(),
prepareInterface()
).then(function() {
console.log('fire after requests succeed');
$('.finished').html('I am done!');
}).fail(function() {
console.log('something went wrong!');
});
I made a small fiddle where you can check out the code.
http://jsfiddle.net/saifbechan/BKTwT/
You can check out a running copy there, I took the snippet from this tutorial, it's worth reading the whole tutorial, lot's of good information on asynchronous ajax.
http://msdn.microsoft.com/en-us/scriptjunkie/gg723713

Related

How to identify the caller function name in an AJAX or XMLHttpRequest call?

I am trying to find out how to identify the function name which initiated the Ajax call programmatically using JavaScript or jQuery for an existing code base.
I am working on the instrumentation of an existing large code base and hence trying to identify the function name which correlates with the AJAX requests. So, there is already a lot of code and modifying each method doesn't seem to be the best approach. So, I am trying to think about a generic approach which can work for all the methods. The code already has a generic wrapper around the AJAX call, so I can hook into the entry and exit points of the method.
For e.g. in below code, in the always function, I need to know the initiator function name or the call stack.
function ajaxWrapper(){
var jqxhr = $.ajax( "https://jsonplaceholder.typicode.com/posts/1" )
.done(function() {
console.log( "success" );
})
.fail(function() {
console.log( "error" );
})
.always(function() {
console.log( "complete" );
// TODO: Who Initiated the call ?
});
}
function initiator1(){
initiator2();
}
function initiator2(){
ajaxWrapper();
}
initiator1();
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
What have I tried?
I know one approach is using setting the custom headers in the beforeSend method and the always callback receives the jqXHR object as the parameter. But this doesn't seem to be suitable approach as I can't modify all the functions to send the method name as a parameter.
arguments.callee.caller approach, but it doesn't seem to work in case of the Asynchronous methods. Also, this feature seems to be deprecated because of performance and security reasons.
Note: I am not looking this information for debugging purpose. I am already aware of the Async call stack, Initiator and console.trace solutions available in developers tool.
One option is to generate the call stack inside your wrapper, because up to that point you are in a sync function call chain.
var err = new Error();
var stack = err.stack;
And use it later in the callback, because it's available as part of a higher scope. (Scopes don't care about async.)
You'll need to do some parsing (browser specific unfortunately) on that call stack. I don't know what you intend to do with the initiator, but you may want to keep all the chain, not just the first one.
Also note that this technique is unreliable and you shouldn't use it for anything critical.
function ajaxWrapper(){
var err = new Error();
var stack = err.stack;
var jqxhr = $.ajax( "https://jsonplaceholder.typicode.com/posts/1" )
.done(function() {
console.log( "success" );
})
.fail(function() {
console.log( "error" );
})
.always(function() {
console.log( "complete" );
// TODO: Who Initiated the call ?
console.log(stack);
});
}
function initiator1(){
initiator2();
}
function initiator2(){
ajaxWrapper();
}
initiator1();
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

How to find if the chained asynchronous scripts has been loaded?

Here's the scenario. I am doing a $.getScript()function call to get a script in my javascript file. The script that I'm downloading from the $.getScript() tries to download some other scripts that it's dependent on. In my script I'm using done() to check if the script loaded completely or not. And if it did, then I try calling the function that's not on the script that I just loaded form $.getScript but the script that was loaded in it.
It's getting confusing so let me demonstrate with some code:-
//my script.js
$.getScript("http://myexternaljs.com/first.js").done(function(){
doSecond(); //<- this resides in second.js which is being called in first.js below
}
//first.js
(function(){
$.getScript("http://firstexternal.com/second.js");
}());
//second.js
function doSecond(){
return console.log("hello world");
}
The problem here is second.js takes a little time to download so I keep getting doSecond() is undefined error on my call to doSecond() on done().
I could use a timeout and check if second.js loaded or not but is there a better way to do this?
I'm open to any AMD loaders or Promises answers as well.
You can also use $.ajaxSuccess:
$(document).ajaxComplete(function(ev, jqXhr, options) {
// You could be as specific as you need to here.
if ( options.url.match('second.js') ) {
doSecond();
}
});
Alternatively, you could do this inside ajaxComplete:
$(document).ajaxComplete(function(ev, jqXhr, options) {
// You could simplify to
// doSecond && doSecond()
// if you can trust that it will always be a function
if ( doSecond && $.isFunction(doSecond) ) {
doSecond();
}
});
The facts:
You have first.js and within this script is an include for second.js
You need to make a call to doSecond() which is defined in second.js
You need to ensure doSecond() is available before you call it
You can't directly change first.js or second.js but you can have someone else change it
Possible solutions, ordered by best to worst
1) Request that second.js be removed from first.js. Call them separately so that you can nest them:
$.getScript("first.js").done(function(){
$.getScript("second.js").done(function(){
doSecond();
});
});
This is the best solution. There are alternatives to this that basically do he same thing in principle (e.g. other people's answers here). If first.js was including second.js synchronously or otherwise forcing load before continuing (e.g. option #3 below), you wouldn't be running up against this problem to begin with. Therefore first.js already must be structured to deal with second.js be *a*sync loaded, so there shouldn't be an issue with them removing it from the file and you calling it yourself.
But you mentioned that the location of second.js is defined in first.js so this somehow isn't feasible to you (why not? can they put the path/to/script in a variable for you to access?)
2) Request that second.js be wrapped in a .done or equivalent loaded callback that pops a callback function that you can define.
// inside first.js
$.getScript("second.js").done(function(){
if (typeof 'secondLoaded'=='function')
secondLoaded();
});
// on-page or elsewhere, you define the callback
function secondLoaded() {
doSecond();
}
This is just a generic and easy "callback" example. There are a million ways to implement this principle, depending on what all is actually in these scripts and how much effort people are willing to make to restructure things.
3) Request that second.js script include be changed to be included via document.write
document.write(unescape("%3Cscript src='second.js' type='text/javascript'%3E%3C/script%3E"));
This will force js to resolve document.write before js can move on, so second.js should be loaded by the time you want to use doSecond(). But this is considered bad practice because until document.write is resolved, nothing else can happen. So if second.js is taking forever to load or eventually times out.. that makes for bad UX. So you should avoid this option unless you have no other choice because of "red tape" reasons.
4) use setTimeout to try and wait for it to load.
function secondLoaded() {
if (!secondLoaded.attempts) secondLoaded.attempts = 0;
if (secondLoaded.attempts < 5) {
if (typeof 'doSecond'=='function') {
doSecond();
} else {
secondLoaded.attempts++;
window.setTimeout('secondLoaded()',100);
}
}
}
secondLoaded();
I list this worse than #3 but really it's kind of a tossup.. In this situation you basically either have to pick between deciding a cutoff time to just not execute doSecond() (in this example, I try 5 times at 100ms intervals), or code it to just keep checking forever and ever (remove the .attempts logic or else swap it up w/ setInterval and removeInterval logic).
You could modify how $.getScript works.
$.fn.getScript = (function() {
var originalLoad = $.fn.getScript;
return function() {
originalLoad.apply($, arguments);
$(document).trigger('load_script')
};
})();
This will fire an event every time a script is loaded.
So you can wait for these events to fire and check if your method exists.
$(document).one('second_loaded', function() {
doSecond();
}).on('load_script', function() {
doSecond && document.trigger('second_loaded');
});
Note that one rather than on. It makes the event fire once.
Have you considered using jQuery.when:
$.when($.getScript("http://myexternaljs.com/first.js"),$.getScript("http://firstexternal.com/second.js"))
.done(function(){
doSecond();
}
If I were you, I'd facade $.getScript and perform some combination of the above tricks. After reading through the comments it seems that there is a possibility of loading the same script twice.
If you use requirejs this problem is solved for you because it only loads each script once. The answer here is to hang on to the requests made.
Loader:
var requiredScripts = {};
function importScript(url) {
if (!requiredScripts[url]) {
requiredScripts[url] = $.getScript(url);
}
return requiredScripts[url];
}
Usage:
// should cause 2 requests
$.when(
importScript('first.js'),
importScript('second.js')
).done(function() {
// should cause no requests
$.when(importScript('first.js')).done(function() {});
$.when(importScript('second.js')).done(function() {});
});
Real world example here using MomentJS and UnderscoreJS: http://jsfiddle.net/n3Mt5/
Of course requirejs would handle this for you and with better syntax.
Requirejs
define(function(require) {
var first = require('first'),
second = require('second');
});
$(window).one("second", function(e, t) {
if ( $(this).get(0).hasOwnProperty(e.type) && (typeof second === "function") ) {
second(); console.log(e.type, e.timeStamp - t);
$(this).off("second")
};
return !second
});
$.getScript("first.js")
.done(function( data, textStatus, jqxhr ) {
if ( textStatus === "success" ) {
first();
$.getScript("second.js")
.done(function( script, textStatus, jqxhr, callbacks ) {
var callbacks = $.Callbacks("once");
callbacks.add($(window).trigger("second", [ $.now() ]));
return ( textStatus === "success" && !!second
? callbacks.fire()
: $(":root").animate({top:"0"}, 1000, function() { callbacks.fire() })
)
});
};
})
// `first.js` : `function first() { console.log("first complete") }`
// `second.js` : `function second() { console.log("second complete") }`

Why does 'jQuery' have to be passed into this function (see last line of code)?

Source: http://blog.tomasjansson.com/creating-custom-unobtrusive-file-extension-validation-in-asp-net-mvc-3-and-jquery
$(function () {
jQuery.validator.unobtrusive.adapters.add('fileextensions', ['fileextensions'], function (options) {
var params = {
fileextensions: options.params.fileextensions.split(',')
};
options.rules['fileextensions'] = params;
if (options.message) {
options.messages['fileextensions'] = options.message;
}
});
jQuery.validator.addMethod("fileextensions", function (value, element, param) {
var extension = getFileExtension(value);
var validExtension = $.inArray(extension, param.fileextensions) !== -1;
return validExtension;
});
function getFileExtension(fileName) {
var extension = (/[.]/.exec(fileName)) ? /[^.]+$/.exec(fileName) : undefined;
if (extension != undefined) {
return extension[0];
}
return extension;
};
} (jQuery));
Wouldn't jQuery already be available inside this function, why would it be passed in at the end there? I don't get this and I've seen it a few times before, never had to use it so, was curious what's going on here.
Passing it in isn't doing anything. That syntax isn't right since the function, as it's used there, is a callback and not IIFE.
Only reason I could think to do that would be if no conflict mode is used. Even then the syntax is still not correct.
Read More: jQuery.noConflict
We pass jQuery or other jQuery Control variable ($, jQuery, jq, jQ, jQ1101) to the module or plugin because in the DOM, We can have multiple version of jQuery loaded or we can have other libraries which uses $ as control variable. Such as PrototypeJS or Zepto
By passing jQuery Control variable we ensure that we have right control variable for our module and internally we just use $ as jQuery variable.
Please see this example.
<html>
<head>
<title>StackOverflow 19257741</title>
<script type="text/javascript" src="http://zeptojs.com/zepto.min.js"></script>
<script type="text/javascript" src="http://code.jquery.com/jquery-1.10.1.min.js"></script>
</head>
<body>
<div id="content">
<!-- Other HTML Tags -->
</div>
</body>
<script type="text/javascript">
//Change jQuery Control because you have other library loadded or you have multiple jQuery loaded.
var jQ1101 = jQuery.noConflict(true);
//Now, you can not access jQuery by $ or jQuery
//This module have to have jQuery to do DOM Manipulation
var module = (function ($, zepto) {
var i = 0, //private ivar for module use only.
_init = function () {
var me = this;
//TODO: Module can init or do something here...
return me;
},
_fill = function (selector) {
//We can use $ here as jQuery Control
$(selector).css({ "backgroundColor": "#000000", "width": "100%", height: "100%" });
//Wait for 2 seconds
window.setTimeout(function() {
//Not select dom with zepto
zepto(selector).css({ "backgroundColor": "#777777", "width": "100%", height: "100%" });
}, 2000);
};
return {
init: _init,
fill: _fill
};
})(jQ1101, $); //We have to pass the current Control for jQuery so, module can use library for internal function.
//Call module then call fill method by passing variable
module.init().fill("#content");
//Two different Library
console.log(jQ1101.fn.jquery); //jQuery 1.10.1
console.log($); //Zepto
</script>
<html>
The code in that blog post is valid JavaScript syntax and will execute, but it doesn't do what the author probably expected. The $(function...) call looks like an attempt to run that function on DOM ready in the usual manner, but that's not what happens.
Let's deconstruct the code. First, strip out all the code in the function and add some logging:
console.log( 'before' );
$( function () {
console.log( 'DOM ready' );
} (jQuery) );
console.log( 'after' );
This is (perhaps surprisingly) valid JavaScript and will log:
before
DOM ready
after
But here's the problem: If you put that script in a web page, you really want it to log:
before
after
DOM ready
After all, you're running the script before the DOM is ready, expecting the function to be run later, after the DOM becomes ready. That's the whole point of using the $() call.
What went wrong? To make it more clear, let's break out the inner function as a separate named function:
console.log( 'before' );
function ready() {
console.log( 'DOM ready' );
}
$( ready(jQuery) );
console.log( 'after' );
And one more change to make it completely step-by-step:
console.log( 'before' );
function ready() {
console.log( 'DOM ready' );
}
var result = ready( jQuery );
$( result );
console.log( 'after' );
Each of these versions has exactly the same semantics and runs in the same order.
Now it should be clear what happened. We're calling the ready function immediately and passing its return value into the $() call. The ready function doesn't return any value, so that value is undefined.
The last line, then, is the equivalent of:
$( undefined );
And that call simply returns an empty jQuery object ([]).
The culprit, of course, is that (jQuery) at the end. Adding that is what caused the function to be called immediately, instead of passing a reference to the function into the $() call. And the presence of jQuery inside the parentheses is meaningless: this ready function doesn't expect any arguments, so that is ignored. It would be the same thing if () appeared there.
It's very much like a common error you see with setTimeout() and similar calls:
// Oops - calls doStuff immediately, not after one second
setTimeout( doStuff(), 1000 );
This raises the question: why didn't this code run into a problem since it doesn't doesn't work as expected? Well, the function does get called either way - the only difference is when it's run.
So two possible reasons why it didn't cause a problem.
This block of code may have been placed at the end of the <body> as is popular practice these days. In that case the DOM would probably be ready-enough when the code is run. DOM elements are created in order as the <body> is loaded, and if you put a <script> tag after a particular DOM element, that DOM element will indeed be available when that script is run.
The code may not require the DOM to be ready. Looking at the validator code in the blog post, it does look like setup code that doesn't do anything with the DOM when it's first run. If so, then as long as the jQuery.validator plugin is already loaded, then it wouldn't make any difference if this code runs immediately or later when the full DOM is ready.

jQuery - set Ajax handler priority

actually the question is as simple as the topic says. Is there any way to give different ajax handlers a higher/lower priority (which means here, that they will fire earlier) ?
What do I mean? Well, I have to deal with a fairly huge web-app. Tons of Ajax requests are fired in different modules. Now, my goal is to implement a simple session-timeout mechanism. Each request sends the current session-id as parameter, if the session-id is not valid anymore, my backend script returns the request with a custom response-header set (value is a uri).
So I'm basically going like this
window.jQuery && jQuery( document ).ajaxComplete(function( event, xhr, settings ) {
var redirectto = xhr.getResponseHeader( 'new_ajax_location' );
if( redirectto ) {
location.href = redirectto;
}
});
This does work like a charm of course, but my problem is that this global ajax event actually needs to get fired first 100% of the time, which is not the case. Some of those original ajax-requests handlers will throw an error because of missing or unexpected data, in that case, the global handler never gets executed.
Now, I'd be kinda happy if I wouldn't need to go through every single request handler and make it failsafe for invalid session data responses. I'd much more prefer to do the job at one particular place. But how can I make sure my global handler will get executed first ?
If I understand you correctly, you want to add your own callback to json requests everywhere. You could use the ajaxPrefilter hook.
$.ajaxPrefilter(function( options, originalOptions, jqXHR ) {
options.complete = function (jqXHR2, settings) {
var redirectto = jqXHR2.getResponseHeader( 'new_ajax_location' );
if( redirectto ) {
location.href = redirectto;
}
}
});
You can also change the options there to add the session-id to the request params.
jQuery allows you to "patch" it's methods, such as .post/.ajax.
You might be able to patch the appropriate method(s) so your special AJAX event is always triggered before the one your code wants to call.
This is some "pseudo-jQuery-code" which should help you get started. I doubt it works as is, but it demonstrates the basic concept and it should get you started.
(function( $ ){
var originalPost = $.post;
var calledSpecialOne = false;
$.post = function(url, data, success, dataType) {
if (!calledSpecialOne) {
originalPost( ... your special AJAX query ... ).then( function() {
originalPost(url, data, success, dataType);
}
calledSpecialOne = true;
} else {
originalPost(url, data, success, dataType);
}
}
})( jQuery );
The above is based on some other, unrelated, code I have tested, which makes $.each() work for undefined/null arrays):
(function($){
var originalEach = $.each;
$.each = function(collection, callback) {
return collection? originalEach(collection, callback) : [];
}
})(jQuery);
Note that many exposed functions are called internally by jQuery too, so be VERY carefull using this trick; you might break a different part of jQuery or cause other trouble.
In case of the AJAX functions, you should probably patch the innermost function only, to prevent infinite recursion.
(FWIW, I haven't found any side-effects for the $.each() patch so far).
I'm not sure if this is even what you meant, but I figured I might need it as some point anyway.
It patches jQuery's ajax function to accept an object with the property priority. If there isn't a priority set, its priority becomes 1 (highest priority 0). If there is a priority, it does one of two things. If the priority is something like 5, it checks to see if the ajax call with the previous priority (in this case 4) was called. If not, it adds it to an array of ajax calls with that priority (in this case 5). If the previous priority has been called, it calls the request normally. When a request is called with a priority, it calls any outgoing requests with the next highest priority.
In other words, if there's no priority, it waits for priority 0, if there is a priority, it waits for the priority below it to get called. If you want calls with no priority to be sent immediately, comment out the line with the comment I put saying default action?
(function( $ ){
var oldAjax=$.ajax;
var outgoing=[];
var sent=[];
$.ajax=function(url, settings) {
var pr = (typeof url==='object'?url:settings).priority;
pr = pr===undefined ? 1 : pr; // default action?
if(pr!==undefined){
if(pr==0 || sent[pr-1]!==undefined){
oldAjax(url,settings);
sent[pr]=true;
if(outgoing[pr]){
var rq=outgoing[pr].splice(0,1)[0];
$.ajax(rq[0],rq[1]);
}
if(outgoing[pr+1]){
var rq=outgoing[pr+1].splice(0,1)[0];
$.ajax(rq[0],rq[1]);
}
}
else{
if(outgoing[pr]!==undefined){
outgoing[pr].push([url,settings]);
}
else{
outgoing[pr]=[[url,settings]];
}
}
}
else{
oldAjax(url, settings);
}
};
})( jQuery );
Patches for get and post. You will have to include those extra arguments, or change this yourself to get the priority working with post and get requests. This is straight from jQuery 1.7, except for the extra argument, and the two lines I added comments to.
jQuery.each( [ "get", "post" ], function( i, method ) {
jQuery[ method ] = function( url, data, callback, type, priority ) {
// shift arguments if data argument was omitted
if ( jQuery.isFunction( data ) ) {
priority = type; // my change
type = type || callback;
callback = data;
data = undefined;
}
return jQuery.ajax({
type: method,
url: url,
data: data,
success: callback,
dataType: type,
priority: priority // my change
});
};
});
I setup an example here: http://jsfiddle.net/tk39z/
I actually created some ajax requests targeting google, so you can check the network panel in Chrome to see the requests.
I recently implemented something very similar to the functionality that I believe you're looking for.
I set up a filter on the server side to test for a valid session and, if not, return a 401 error with custom status text for a more specific error detail.
The ajax calls I made to these pages were written in the normal convention using the 'success' attribute.
For situations where there was an error, I added a global callback to the error function that would test for the specific error and redirect accordingly. such as:
$( document ).ajaxError(function(e, xhr) {
var redirectto = xhr.getResponseHeader( 'new_ajax_location' );
if( redirectto ) {
location.href = redirectto;
}
});

how to invoke a dynamically loaded javascript function

I was implementing a on-demand script controller based on jquery's getscript, it looks like this:
function controller = function(){
var script = function(){
var scripts = {};
return {
load: function(jsurl){
$.getScript(jsurl, null);
},
run: function(js){
window[js].apply(this,null);
}
}
};
return {
script: script()
};
}
var ctlr = controller();
then here is a remote script with a function to be loaded - remote.js
function remotefunc(){
alert( 'remotefunc invoked' );
}
and here is how the whole thing supposed to work, in the main script:
ctlr.script.load( 'remote.js' ); // remote script successfully loaded
ctlr.script.run( 'remotefunc' ); // got an error, window['remotefunc'] undefined
but as you can see, 'remotefunc' is defined in the global 'window' scope, so the window object is supposed to be able to 'see' it.
I thought the problem was probably the closure stuff in the 'controller' definition, so I did a direct $.getScirpt without using the 'controller':
$.getScript( 'http://path/to/remote.js', function(){
window['remotefunc'].apply( this, null ); // this worked
} );
strange. So it is about the 'controller' implementation(I kind need it)! Anybody can help me out with this? How to fix the 'controller' implementation so the
window[js].apply(this,null);
can actually work?
Thanx.
The reason it's telling you window['remotefunc'] is undefined is because you are not giving it time to actually download and execute the remote script before attempting to call a function defined in it.
The remote script is loaded asynchronously, which means the script execution isn't paused while waiting for a response.
You will need to either re-implement the getScript method to be synchronous or somehow work your class around the fact that the function will not be available in any determinate amount of time.
EDIT: Just found another possible solution, try calling this before your request
$.ajaxSetup({async: false});
This will make the getScript method synchronous
When using something like getSript, it's important to remember that it is fetching asynchronously. Meaning, the browser fires off the request and while that's happening, code after that line executes without pause.
jQuery provides a callback function parameter to get script that allows you to do something after the asynchronous fetch is finished.
Try this:
var script = function(){
var scripts = {};
return {
load: function(jsurl, callback){
$.getScript(jsurl, callback);
},
run: function(js){
window[js].apply(this,null);
}
}
};
Then, when using it:
ctlr.load( 'remote.js', function(){
// remote script successfully loaded
ctlr.run( 'remotefunc' );
});
Could this be a timing issue?
In your working example you call the function in a callback which jQuery will not invoke until the script is loaded. In your non-working example, you call the function immediately after getScript which is asynchronously loading the script.

Categories