Delay function call when dynamically loading javascript files - javascript

I am using enquire to dynamically load javascript files but hitting what I can only assume to be a loading priority problem since it works some of the time. Is there a way to hold off running a function until all files are loaded?
The relevent bit of enquire is
enquire.register("screen and (min-width: 564px)", {
match : function() {
loadJS('script/jquery.js');
loadJS('script/jquery.slides.min.js');
loadJS('script/TMSScript.js');
}
and the load function is
function loadJS(url)
{
var script = document.createElement('script');
script.type = 'text/javascript';
script.src = url;
document.head.appendChild(script);
}
The function I need to run is located at the end of TMSScript.js and it calls the jquery plugin so all 3 files need to be loaded in order for it to work. If I load all files in the header then the function will execute fine with a simple onload call in the body.
The idea is that a different method will be used on mobiles for my gallery (probably jquery mobile) and I don't want to load any unnecessary files.

Someone may correct me, but I believe outside of either loading these three in separate script tags placed in the correct order, or loading a single js file with these plugins concatenated in the correct order, you can't. Loading a src on a programatically created element now runs in an async fashion (in current browsers anyways I beleive), meaning you wouldn't be sure exactly when it's going to return back, and in what order.
It sounds like you want to use something like browserify or require.js which can help handle what you're trying to accomplish. I suggest checking out those projects.

Related

Knockout JS plays nicely with Ratchet and push.js until I add a data-transition

I am using the Ratchet.js/push.js library to create the UI for a mobile web app. In this library, links are handled by "pushing" the to-be-loaded file into the ".content" DOM element rather than loading the entire page. However, push.js does not load any scripts it finds when loading a page - which disables my Knockout.js code.
I found a solution here on StackOverflow that works pretty well - just add an event listener for the push event. I modified it so that it can load any script across multiple pages and so it works with external script files:
window.addEventListener('push', function () {
var scriptsList = document.querySelectorAll('script.js-custom'); // Add a "js-custom" class to your script tag
for (var i = 0; i < scriptsList.length; ++i) {
// Handle scripts in separate files by assigning the script file name to its id.
// We save it in a variable because the ".done" callback is asynchronous.
scriptName = scriptsList[i].id; // IMPORTANT: Only one loadable script per page!
$.getScript("/path info here/" + scriptName)
.done(function (script, textStatus) {
eval(script);
})
... error handling ...
}
});
In the target HTML page, scripts are given class and id tags so they work with the above:
<script src="Challenge.js" class="js-custom" id="challenge.js"></script>
Note, too, that Knockout bindings have to occur to a specific named DOM element so that knockout doesn't get confused:
ko.cleanNode($("#ChallengePage")[0]);
ko.applyBindings(challengeFn, $("#ChallengePage")[0]);
We use cleanNode to avoid the "already bound" errors.
OK! So all this works great and I hope that someone who is struggling with this finds it useful.
HOWEVER, when the link is given a transition:
<a href="challenge.html" data-transition="slide-in">....
Then it breaks with a "Cannot read property 'nodeType' of undefined. I had thought that maybe it was just a problem of waiting for the transition to finish, but even if I replace the eval of the script with:
scriptContents = script;
setTimeout(function () { eval(scriptContents); }, 1000);
it doesn't help.
Any advice or assistance would be greatly appreciated! I don't really need to "push" pages if I don't get to use the transitions so I am hoping that someone will have the last key to making this all work!
UPDATE: The error was occurring because the "document.querySelectorAll" call when using a transition uses the current document rather than the document being pushed. Also, using "webkitTransitionEnd" as my event works as well but this doesn't fix the document issue. Thus, I can make this work, but only for a single transition - now I don't have a way of getting the document being loaded. Ideally, a solution that works whether a links uses a transition or not is what I am looking for.
The combination of Ratchet and Knockout will likely be popular in the coming months so I hope that others find this solution.
To combine the Ratchet.js and Knockout.js libraries requires only that you handle the fact that Ratchet.js (via Push.js) will attempt to manage your page transitions. During a transition, the JavaScript on your target page - including Knockout - will not be run unless you specifically make this happen. That is what this solution does: it makes it possible to load and run your Knockout JavaScript code even though Ratchet is managing page transitions.
In my solution, we always place JavaScript in a separate file and implement Content Security Policy that forbids any JS code from running on the page. It is simply good security hygiene and helps reduce the attack surface for XSS attacks. So the solution below 1) assumes that the JS is in a separate file and 2) assumes that the HTML and JS files have the exact same name and path - except for the extensions (sort of like treating the .js file like an ASP.NET code-behind for the HTML file).
On your "root" page - the one that starts all of your interactions with other pages on your mobile web app, place the following function. It will load the appropriate .js file whenever the corresponding .html file is loaded by Ratchet:
window.addEventListener('push', function (params) {
var targetPage = params.target.document.baseURI.replace(".html", ".js");
$.getScript(targetPage)
.done(function (script, textStatus) {
eval(script);
})
.fail(function (jqxhr, settings, exception) {
alert("Error loading script: " + exception);
});
});
Note that you will have to apply your Knockout bindings to a named and unique div in your HTML page (generally a div that lives directly underneath the Ratchet .content div). This is just because each page load has to apply its Knockout bindings to just the HTML being loaded.
ko.cleanNode($("#DivPageName")[0]);
ko.applyBindings(KnockoutFn, $("#DivPageName")[0]);
UPDATE: I have found that this solution gets "confused" at times as pages are pushed and popped from the history stack. I have decided not to use it although it seems like it is about 97% there. If anyone has any improvements that would make this completely reliable, I am all ears!

How do I use Firebase in an external Javascript file

This is probably a very simple issue, but I've been trying to use Firebase in an external javascript file that is being used with an HTML file and can't get it to work properly. I am planning to use this file for many other similar pages, so I'd rather keep it in an external document. Specifically, my code is:
$(function() {
var head= document.getElementsByTagName('head')[0];
var script= document.createElement('script');
script.src= 'https://cdn.firebase.com/v0/firebase.js';
head.appendChild(script);
var Database = new Firebase('https://myfirebase.firebaseIO.com/');
...
but when I try to run it, it says that the Firebase object/keyword is undefined. I know that the script is being correctly appended to the HTML page because I've checked the HTML on the page after running the code.
I have also read somewhere that you might need to have a personal server to run Firebase, but frankly I don't really know what that means - in any case, I use Mac OSX and run all of my HTML and Javascript in Chrome.
Thank you very much!
The problem is that using document.createElement does not force the script to be loaded and rendered before your inclusive script is invoked (it's being invoked now). There are no guarantees by this method on when the script you include will get invoked.
Additionally, you are loading the script onDomReady by putting it inside $(function() {...}); you would want to insert it into the header immediately, not wait for the entire document to load.
The simplest answer is to just put Firebase into the head of the html page; you haven't really explained your limitations here, but I assume this isn't an option for you. If it is, KISS.
Another simple answer is to utilize jQuery, since you obviously have it available.
$.getScript('https://cdn.firebase.com/v0/firebase.js', function() {
// now I can use Firebase
});
You can also accomplish this with other methods (wait until Firebase is defined using a setInterval; utilize other script retrieval methods besides document.createElement--try googling "load scripts dynamically via javascript load order"), but I think this covers your needs sufficiently.

Where to add asynchronous scripts in javascript

I want to load a non-blocking javascript on my page. Before async is supported, the best practice seems to dynamically load it via a simple script.
Here is an example that works fine in which it is inserted before the first <script>:
var myscript = document.createElement('script');
myscript.async = true; // cannot hurt, right?
myscript.type = 'text/javascript';
myscript.src = 'myscript.js';
var node = document.getElementsByTagName('script')[0];
node.parentNode.insertBefore(myscript, node);
I found several versions inserting the script in different places like the end of <head> or the <body>:
document.getElementsByTagName("head")[0].appendChild(myscript);
document.getElementsByTagName("body")[0].appendChild(myscript);
The order seems to matter in some browsers though it is asynchronous.
Are there any difference in terms of browser support? performance? blocking risk?
I don't have any constraint in terms of order (they don't impact each other) but I want to make sure that if my script takes too long to load the page content will still load just fine. I would think the last solution works best but I am not sure of the differences.
You'll want to use something like $script.js: http://www.dustindiaz.com/scriptjs
Appending the scripts at the end of the body is the best solution here. It still allows for loading the DOM without blocking for the script tags. Also if you put your scripts at the end of the document you no longer need to wrap your functions in a DOM ready event because at the moment your scripts start executing the DOM will already be loaded by the browser and you could directly start manipulating it or subscribing to some events.
You could try having a script that waits for the page to be complete and then loads the script that you want to add. Have done this recently and the page loads fine and then a new block appears.
var Widget = {}
Widget.myDocReadyInterval = setInterval(function(){
if (document.readyState === "complete")
{
clearInterval(Widget.myDocReadyInterval);
Widget.startLoading();
}
}, 20);
Widget.startLoading(){
// do what you need here...
}
Your question focuses on the load part, but actually performance can be impacted by different phases:
load
parsing
execution
For this reason adding the script at the end of the body is usually considered the less obtrusive.
To push it even further, you could wait for DOM ready to run your load script. In this case, it won't matter whether you attach the script to the head or the body.
[Edit] Side comment: the head and body tags are not mandatory in html pages. document.getElementsByTagName('script')[0] is a good approach in such edge cases as it guarantees that you'll get an element (there's at least your load script in the page).

Adding external scripts in DOM from javascript code

I have included 3 external js files at the end of body.
Suppose my document already contains a js named as insertlibs.js and here is the code
var script1 = document.createElement('script');
script1.src='http://code.jquery.com/jquery-latest.js';
script1.type='text/javascript';
document.getElementsByTagName('Body').item(0).appendChild(script1);
// Similar way to include underscore
var script2 = document.createElement('script');
script2.src='hhttp://documentcloud.github.com/backbone/backbone-min.js';
script2.type='text/javascript';
document.getElementsByTagName('Body').item(0).appendChild(script2);
But what is happening sometimes, it is throwing an error that $ is not defined and I tried to debug in Firefox and there is a parallel download occurring for jquery and backbone and sometimes backbone library getting download earlier than jQuery which is causing this error.
As far as i know that if a script tag is included, it will block further request So as soon as I add jquery in dom. I am confused about the workflow here happening.
So i have found the solution, I merged both the js and making a single call which is working perfectly but that does not explain me the flow happening in above case. Please help.
This is because you are attempting to include backbone without ensuring that jquery has been completely loaded. To correct this, you can use the script's onload attribute to attach a callback which will be fired when jquery is loaded.
For ex:
var script1 = document.createElement('script');
script1.src='http://code.jquery.com/jquery-latest.js';
script1.type='text/javascript';
// add an onload handler
script1.onload = function() {
// load the rest of the scripts here
var script2 = document.createElement('script');
script2.src='hhttp://documentcloud.github.com/backbone/backbone-min.js';
script2.type='text/javascript';
document.getElementsByTagName('Body').item(0).appendChild(script2);
}
document.getElementsByTagName('Body').item(0).appendChild(script1);
As far as i know that if a script tag is included, it will block further request
No, the blocking / synchronous download is only when the tags are right in the parsed HTML (or are inserted via document.write during the parse); dynamically DOM-appended scripts load asynchronously and in parallel.
To do that but ensure that scripts are executed when their dependencies are met, you need to use AMD loaders.

Load js files after ajax inclusion

This is the deal, i'm importing some php files, one of the files has a slider, this slider requires .js files.
But when i make the ajax call, the file is imported but the js files aren't. Is this supposed to be like this?
I tried this:
var script = document.createElement('script');
script.src = 'http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js';
script.type = 'text/javascript';
document.getElementsByTagName('head')[0].appendChild(script);
window.onload=function(){
$(".cs_article").append('<p>Tdkasdlasdlkamsdklasmdasdaest</p>');
$.getScript('js/jquery.ennui.contentslider.js');
$.getScript('js/jquery.easing.1.3.js');
};
Kinda works, well, the append works on the DOM even with it being already loaded, but i can't get the scripts to run...
I've done my homework, searched on google, and found several ways of importing the scripts, but all end up with the same result...
this works
https://github.com/chriso/load.js
Bind your window.onload function to yourjQueryScriptTag.onload instead; before setting the src and appending it to the DOM, since onload may fire before jQuery is loaded.
http://jsfiddle.net/5SbXc/
Also, you may want to consider upgrading to a newer version of jQuery if possible.

Categories