I need to execute a function after a certain script was loaded so I load the script using an ajax call and on the success I invoke the function that uses an object that was suppose to be loaded already from that script and I get an error that this object is undefined.
Here is my code:
function LoadInlineManualScript() {
return $.ajax({
url: "../Scripts/inline-manual-player.js",
dataType: "script",
beforeSend: function () {
loader.hide();
loader.unbind();
},
success: SetInlineManualCallbacks
});
}
function SetInlineManualCallbacks() {
debugger;
//Here I get the error!
window.inline_manual_player.setCallbacks({
onTopicDone: function (player, topic_id, step_id) {
console.log('Step was displayed.');
}
});
}
And I get Uncaught TypeError: Cannot read property 'setCallbacks' of undefined.
I tried to change the call to use async:false but it didn't help.
Two things:
Function SetInlineManualCallbacks doesn't have a return statement. So despite issue 2, SetInlineManualCallbacks(inlineUid) should return undefined. However, success needs a function:
success: SetInlineManualCallbacks.bind(null, inlineUid)
Or old-school without bind:
success: function() {
SetInlineManualCallbacks(inlineUid)
}
But, looks like SetInlineManualCallbacks doesn't need any arguments, so this would be enough in your case:
success: SetInlineManualCallbacks
It says window.inline_manual_player is undefined. Judging from the variable name, I think it's because you just loaded the JS file content, as plain text, but you didn't execute the content as JavaScript.
You could use eval to execute it in success callback, but that's really a bad idea. I would probably insert a <script> tag into DOM, instead of using AJAX.
function LoadInlineManualScript() {
var s = document.createElement('script')
s.src = "../Scripts/inline-manual-player.js"
// your callback will fire when the script is loaded and executed
s.onload = SetInlineManualCallbacks
// it will start to load right after appended, and execute of course
document.body.appendChild(s)
}
Why you are loading by ajax? Let the browser do it.
The function loadScript insert the script on DOM and attach a callback to the event onload of script element.
function loadScript(href_script, callback) {
var script = document.createElement('script');
script.src = href_script;
script.onload = callback;
// The same code used by Google Analytics
var x = document.getElementsByTagName('script')[0];
x.parentNode.insertBefore(script, x);
};
function SetInlineManualCallbacks() {
debugger;
//Here I get the error!
console.log("Code Running");
}
Here an example:
loadScript("//www.google-analytics.com/analytics.js", SetInlineManualCallbacks);
When you do this
success: SetInlineManualCallbacks(inlineUid)
You are executing the method and setting whatever it returns to the success callback. You are not saying call this when success is triggered.
It should just be
success: SetInlineManualCallbacks
But I think there is an issue with the Ajax call itself, you are loading a script and expecting it to do something, I do not think the Ajax call is what you want. If the script is just returning inlineUid why are you using a script and not a JSON or text call. If inlineUid is global, than just read it in the callback method. Without knowing what is happening in the script file, it is hard to give a full answer.
As someone suggested, Inline Manual is initialising itself in that script. You have your Site in Test mode, therefore there is an additional ajax request within the script itself, to check if the user can access it. So the script might load, but the inline_manual_player object won't be available yet when it is loaded because there are other things happening before it creates the object.
If you want to set the callbacks before the object is available, you can use queuing actions before calling the script to load.
var inline_manual_player = inline_manual_player || [];
inline_manual_player.push(['setCallbacks', my_callbacks]);
http://help.inlinemanual.com/docs/queuing-actions
This answer is a combination of some answers I got here:
The function I use to load the script instead of using Jquery's $.getScript is:
//Must load the script after inlineManualTracking object is ready.
function LoadInlineManualScript() {
loader.hide();
loader.unbind();
var script = document.createElement('script');
script.src = "../Scripts/inline-manual-player.js";
// your callback will fire when the script is loaded and executed
script.onload = WaitForInlineManualScriptToLoad;
// it will start to load right after appended, and execute of course
document.body.appendChild(script);
}
The script I load has use strict and it cannot be loaded with jquery because of the following reason: "use strict"; + jQuery.getScript() = script can't export to global namespace like epascarello mentioned earlier.
Even after using Leo's answer I still had a problem with that object and that's why I use the following function after the onload event of the script.
function WaitForInlineManualScriptToLoad() {
if (typeof window.inline_manual_player == 'undefined') {
setTimeout(WaitForInlineManualScriptToLoad, 100);
return;
}
InitInlineManualPlayerCallBacks();
}
function InitInlineManualPlayerCallBacks() {
window.inline_manual_player.setCallbacks({
onTopicDone: function (player, topic_id, step_id) {
console.log('Step was displayed.');
}
});
}
This works! it's not the prettiest, but it works. The developer of inline-manual tool had responded here(Marek) and mentioned the fact that my site in under "Test" mode. There was no way we could know this can affect.
According to your question you want to execute a skript, after another script was loaded correctly.
In your own answer using the jQuery "getScript"-function won´t work for you, so why not trying the old-fashioned way without using jQuery after all?
Create a new script-element, add it to your document head and set a callback-function on readystate.
function loadScript(url, callback) {
var newScript = document.createElement( "script" )
newScript.type = "text/javascript";
if(newScript.readyState) { //IE
newScript.onreadystatechange = function() {
if (newScript.readyState === "loaded" || newScript.readyState === "complete") {
newScript.onreadystatechange = null;
callback();
}
};
} else { //Others
newScript.onload = function() {
callback();
};
}
newScript.src = url;
document.getElementsByTagName("head")[0].appendChild( newScript );
}
Now you can call the defined method and offer a callback-function, in your case this would be:
loadScript("../Scripts/inline-manual-player.js", function() {
debugger;
// should work now
window.inline_manual_player.setCallbacks({
onTopicDone: function (player, topic_id, step_id) {
console.log('Step was displayed.');
}
});
});
It seems the script wasn't executed at that time. Please note the callback is fired once the script has been loaded but not necessarily executed, based on JQuery documentation.
If you want to make sure the script was executed, you can load the script synchronically on the HTML before you use it. If you do this, you won't need ajax, and you will keep your code simple. KISS principle
If you need to do it the way you have it here, then you can apply the observer pattern creating a custom event.
Here is an example.
At the end of the file "inline-manual-player.js". You can do something like this.
$.event.trigger({
type: "fileExecuted");
Then instead of the success callback, you would subscribe to the "fileExecuted" event.
$(document).on("fileExecuted", SetInlineManualCallbacks);
Hope it helps.
success takes a function and not a value. Unless SetInlineManualCallbacks returns another function, use bind and partially apply arguments.
function LoadInlineManualScript() {
return $.ajax({
url: "../Scripts/inline-manual-player.js",
dataType: "script",
beforeSend: function () {
loader.hide();
loader.unbind();
},
success: SetInlineManualCallbacks.bind(null, inlineUid)
});
}
It would help if you explained where inlineUid comes from.
It would appear that inline-manual-player.js loads window.inline_manual_player asynchronously. This means the thread is allowed to move on after that file's onload event fires but before window.inline_manual_player is loaded. Thus, you're finding that window.inline_manual_player is undefined for a period. This is probably due to inline-manual-player.js making ajax calls of its own (or setTimeouts, etc. - JavaScript is an asynchronous beast).
Rather than setting an ugly (no offense) setTimeout, there are two potentially better possibilities.
1
If you're willing and able to update inline-manual-player.js, you could put something like this at the top of that file:
window.inline-manual-player-callback-queue = [];
... and later in that file, after window.inline_manual_player is asynchronously defined, add:
// I've made this an array, in case you'd like to add multiple functions to be called upon window.inline-manual-player being ready.
var queue = window.inline-manual-player-callback-queue;
var length = queue.length;
var i = 0;
for(i; i<length; i++) {
queue[i]();
}
Finally, in your initial file, input the following:
function addCallback() {
window.inline_manual_player.setCallbacks({
onTopicDone: function (player, topic_id, step_id) {
console.log('Step was displayed.');
}
});
}
$.ajax({
url: "../Scripts/inline-manual-player.js",
dataType: "script",
beforeSend: function () {
loader.hide();
loader.unbind();
},
success: function() {
// Because you've defined window.inline-manual-player-callback-queue at the start of inline-manual-player.js, the following will be available upon onload (aka success) of that file:
window.inline-manual-player-callback-queue.push(addCallback);
}
});
2
If editing inline-manual-player.js isn't an option, the initial author of that file may have already included something akin to window.inline-manual-player-callback-queue. Check the documentation of the file, and see if there's not a global method or array you can add your callback function to, to have inline-manual-player.js call it automatically, once all the async magic has taken place.
Best of luck to you!
Related
I have the following JavaScript snippet, which I run in Developer tools console after opening a new tab in Google Chrome with http://example.com
var jq = document.createElement('script');
jq.src = 'https://code.jquery.com/jquery-latest.min.js';
document.head.appendChild(jq);
if (window.jQuery) {
window['jqy'] = jQuery.noConflict();
alert('jQuery is loaded');
}
else {
alert('jQuery not loaded');
}
It alerts jQuery not loaded for the first time, even when I see that the <script> is added to the DOM. Why?
Because the script will load in an async manner and will take a while to load completely after the script tag creation.
The alert method is executed just after the addition of script tag.
To achieve something like this you need a callback which will execute after the script is loaded completely.
Maybe something like this?
function loadScript(url, callback) {
var e = document.createElement("script");
e.src = url;
e.type = "text/javascript";
e.addEventListener('load', callback);
document.getElementsByTagName("head")[0].appendChild(e);
}
loadScript("https://code.jquery.com/jquery-latest.min.js", function() {
// This callback will fire once the script is loaded
if (window.jQuery) {
window['jqy'] = jQuery.noConflict();
alert('jQuery is loaded');
} else {
alert('jQuery not loaded');
}
});
Each Method in the javascript can take some time to complete the desired task allocated to it. Even appendChild function also will take some time to execute the method to complete.
But system won't wait for the task to complete and execute the next line. Because of that for the first time alert is coming with the jquery not loaded.
But if you execute the same function in the console again without loading it will alert you as a jquery is loaded because appendchild method completed his task successfully.
To overcome this issue need to add the task as a callback function. But append child is not having any callback function. So we need to use the setTimeOut function to solve this issue.
I want to asynchronously include a specific version of jQuery in a page I don't control (e-commerce platform) and only use it in my script. The page may load other scripts, which may also want to do the same thing, and I don't know the order in which the page includes my script vs others.
Using jQuery.noConflict([removeAll]), can I ensure that:
my script gets the right version of jQuery
I don't overwrite jQuery version for anyone else?
I think this question is different from most other multiple jQuery version questions because people make assumptions about script inclusion order and don't use asynchronous jQuery loading with callbacks.
Thanks!
Here's my attempt at this (could someone confirm this is ok?):
myscript.js
(function() {
var myJQuery;
function loadjQuery(url, callback) {
var runCallbackOnSuccess = function() {
if (typeof jQuery != "undefined") {
myJQuery = jQuery.noConflict(true); // NOTICE THIS LINE
callback();
}
};
var scriptTag = document.createElement("script");
scriptTag.setAttribute("src", url);
scriptTag.onload = runCallbackOnSuccess;
document.getElementsByTagName("head")[0].appendChild(scriptTag);
}
function doSomethingInMyScript() {
myJQuery(document).ready(....);
}
loadjQuery("https://code.jquery.com/jquery-A.B.C.min.js", doSomethingInMyScript);
})();
otherscript.js (outside my control, but assuming it's like this)
(function() {
function loadjQuery(url, callback) {
<same as above, but without the noConflict() call>
}
function doSomethingInOtherScript() {
jQuery(document).ready(....);
}
loadjQuery("https://code.jquery.com/jquery-X.Y.Z.min.js", doSomethingInOtherScript);
})();
Will this code work regardless of:
whether the page includes myscript.js or otherscript.js first
whether the callback functions for myscript.js or otherscript.js is executed first?
I suspect it should be fine as long as the callback functions are executed immediately after each respective jQuery is loaded, with no possibility of code from the other script interleaving between jQuery loaded and callback executed.
I insert the jquery tag
var newScript = document.createElement("script");
newScript.id = "grazit_jq";
newScript.type = "text/javascript"
newScript.src = "https://ajax.googleapis.com/ajax/libs/jquery/1.8.3/jquery.min.js";
script.parentNode.appendChild(newScript);
And after that I want to check if jquery exists.
I do so in a few ways.One way is like this:
var checkJquery=function(){
if(typeof(jQuery) === 'undefined')
{
console.log('jQuery doesnt exist');
window.setTimeout(arguments.callee,1000);
return;
}
else{
sprk_lib=new sprk();
sprk_lib.load(jQuery);
}
};
sprk.prototype.load=function($){
console.log($);
};
UPDATE
Now i updated the code. It will call the function as soon as the jQuery object is availeable.
In that function I will execute my jquery code. Do you think that there will be problems? is it okay to do it that way?
original
I wait there. problem is that in the console log , it returns null and the prints jQuery exists. How can I stop the script till the jquery is loaded,, and then continue executing the script?
Consider using an established JavaScript script loader such as LABjs or LazyLoad which offer reliable callbacks that don't fire until the script has fully loaded.
Check out this summary of 5 JavaScript script loaders.
For example:
LazyLoad.js('https://ajax.googleapis.com/ajax/libs/jquery/1.8.3/jquery.min.js', function () {
console.log('jQuery exists!');
console.log('Document title: ' + $("title").text());
});
One point of note - if you want to hold off all further script execution until after jQuery has loaded, you could put the rest of your script inside this callback function. However, that would break a whole load of best practices (separation of concerns, loose coupling etc) so your best bet is to pass an existing function as your callback, which can in turn call out to other functions etc. For example:
function doSomething() {
console.log('I am doing something!')
doSomethingElse();
}
function doSomethingElse() {
console.log('I am doing something else!');
}
LazyLoad.js('https://ajax.googleapis.com/ajax/libs/jquery/1.8.3/jquery.min.js', doSomething);
You could further improve your application architecture (e.g. by introducing PubSub) but that's getting outside the scope of this question so I'll leave it there.
I have an application that is using ajax calls for loading the content. Those ajax calls are retrieving just the HTML. JavaScript is in another file.
After doing the AJAX-call I will call to a function that should start executing the JavaScript as soon as possible.
Here I leave a small function that will retrieve some code from an ajax call:
function loadPage(page) {
$(".container").html("");
if(page == "page1") {
$.ajax({
url: "/page1.php",
success: function(html){
$(".container").html(html);
loadPage1Script();
}
});
}else if(page == "page2"){
$.ajax({
url: "/page2.php",
success: function(html){
$(".container").html(html);
loadPage2Script();
}
});
}
}
After that, they will execute loadPage1Script() or loadPage2Script().
function loadPage1Script(){
//Start the carousel plugin, for example
}
A new code has been added to the HTML structure. Should I call to $(document).ready(); in the loadPage1Script() before executing the code to to attach all event handlers? Is there any differences if I do not do that? Will the script start faster if I add the $(document).ready(); ?
function loadPage1Script(){
$(document).ready(function(){
//Start the carousel plugin, for example
});
}
Taken from jQuery site(http://api.jquery.com/ready/):
"In most cases, the script can be run as soon as the DOM hierarchy has been fully constructed. The handler passed to .ready() is guaranteed to be executed after the DOM is ready, so this is usually the best place to attach all other event handlers and run other jQuery code."
If you are calling the loadpage function after you have aleady called
$( document ).ready( function () {
...
});
Then it shouldn't matter. Also, since the $.fn.html function isn't asynchronous, you will have no problem in running your loadPageScript functions straight away
There's no need to use $(document).ready() inside of those functions, because by the point they're run the DOM elements they need will already be present (you just inserted them into the page prior to calling the function).
You should, however, call the original loadPage function inside of $(document).ready() to ensure that the .container element(s) exist.
I need to load a variable number of javascript source files before running javascript code that depends on them. Sometimes 1 script needs to be loaded, other times 2. The getScript() method allows one script to be loaded - how could I use it to load x number of scripts before running its inner code?
$.getScript("test.js", function(){
// code to run after script is loaded
});
What I need:
$.getScript(new Array("1.js","2.js"), function(){
// code to run after all scripts are loaded
});
Thanks
If you are using jquery 1.5 you can use the new deferred syntax.
$.when(
$.getScript("1.js"),
$.getScript("2.js"),
$.getScript("3.js")
).then(function(){
alert("all loaded");
});
Just pass in the scripts you wish to load.
I suggest you look into LABjs
That is exactly what its purpose is.
I've use RequireJS quite extensively and it's very good. However, this might work for you:
$.getScript("1.js", function(){
$.getScript("2.js", function () {
// code to run after all scripts are loaded
});
});
That's a pretty nasty and little block of code there, IMO, but if it is actually only two scripts like that, it's probably worth it. The logic of the above could also be extracted to a generic function, but once you go too far down that path, it's probably smarter to use RequireJS, or LABjs as JAAulde suggested.
One way is to list all your scripts in an array, track how many scripts have loaded vs. the amount you want to load. Something like this:
var toLoad = ["1.js", "2.js", "3.js"], loaded = 0;
var onLoaded = function() {
loaded++;
if (loaded == toLoad.length) {
console.log('All scripts loaded!');
} else {
console.log('Not all scripts are loaded, waiting...');
}
}
for (var i = 0, len = toLoad.length; i < len; i++) {
$.getScript(toLoad[i], onLoaded);
}
Wrote this earlier tonight, but I promise I'm not a plant. ;-) In addition to LabJS and RequireJS, there's also Head.js which is dead simple to use and does exactly what you want.
Loading multiple scripts by $.getScript() one after the other and do stuff after all the scripts are loaded
Working Fiddle. Check the Console window for the output
We can create a function to which we pass array of js file paths, this function will do a $.getScript() for the first js file and on success method it will call the same function by passing the second js file index, and this on success will call the same function by passing 3rd file index and so on until it loads the last file. So its basically a recursion function which will give a callback when all the files in the array has been loaded.The end code would be as simple as
LoadAllScripts("yourArray",function(){
alert("all scripts loaded!!");
});
So the complete code would go like this.
var LoadAllScripts = function (scriptArray, callback) {
SyncLoadScript({ scriptArray: scriptArray, index: 0}, function () {
callback();
});
};
And SyncLoadScript (core of the logic) looks like
var SyncLoadScript = function (scriptConfig, callback) {
var $this = this;
var script = scriptConfig.scriptArray[scriptConfig.index];
if (scriptConfig.scriptArray.length > scriptConfig.index) {
if (script.trim().length > 0) {
$.getScript(script, function () {
console.log(script);
SyncLoadScript({ scriptArray: scriptConfig.scriptArray, index: ++scriptConfig.index, element: scriptConfig.element }, callback);
}).fail(function (jqXHR, textStatus, errorThrown) {
console.log(script + " failed while loading");
debugger;
console.log("Error: "+errorThrown);
SyncLoadScript({ scriptArray: scriptConfig.scriptArray, index: ++scriptConfig.index, element: scriptConfig.element }, callback);
});
}
else {
console.log("invalid script src!!");
}
}
else {
callback();
}
}
Then you can make a simple call to LoadAllScripts by passing array of js file path. like below.
LoadAllScripts(["1.js","2.js","3.js","4.js"], function () {
console.log("All the scripts have been loaded.");
//do your stuff after all the scripts are loaded.
});
Note: I have given empty callbacks for you guys to make tweaks and pass around any data of choice. probably to hold all the failed scripts that you can passback to the main function and try to reload them again.