Is script defer reliable? - javascript

Currently I serve all javascripts combined in one large file via Amazon Cloudfront. But since jQuery is so large, I'm thinking about using the version provided by Google. Of course I would include both script tags in the bottom of the page and would add the defer attribute, if I had not read this article: http://hacks.mozilla.org/2009/06/defer/
If I understand it right, the defer attribute works only in the Firefox correctly, while every other browser (at that time) would execute the scripts in a random order. Is that true? Of course my scripts depend on jQuery, so it must be executed before my scripts.

In 2017, it seems reliable enough to stop avoiding these 5 letters in favor of 17-liner workarounds. Browser support for defer is quite good these days

I'm not sure I'd bother with defer if you aren't certain of its support. Just do this instead:
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.6.4/jquery.js"></script>
<script>window.jQuery || document.write("<script src='localJSFolder/jquery-1.6.4.min.js'></script>")</script>
All modern browsers ought to run the scripts sequentially. This is the easiest way to use Google's CDN with a local fallback.

I would suggest something like this (using window.onload to truly behave like defer):
$(window).load(function () {
var script = document.createElement("script");
script.type = "text/javascript";
if (script.readyState) { // IE
script.onreadystatechange = function () {
if (script.readyState === "loaded" || script.readyState === "complete") {
script.onreadystatechange = null;
// do something
}
};
}
else { // Others
script.onload = function() {
// do something (the same thing as above)
};
}
script.src = file;
document.getElementsByTagName("head")[0].appendChild(script);
});
If you need more than one file, put in a loop and set file to something like file[x]

Related

"Bundling" solution for HTML/JS only?

I'm sure someone is going to tell me why this is an awful idea, but it may be better than what we have going on already. So all our plugins have been copied/pasted into a Master.js script which is a nightmare to update or sort through. Most of the plugins are minified (which is good) but I'd really like to take the idea of bundling from .net/c#/aspx and implement something of the likes to rid this Master.js file. Copying/pasting new scripts will often freeze the project and nearly everything in it is outdated - so here's my mad science:
In Master.js, create an array of all scripts used:
var scripts = [
'jquery-1.10.2.min.js', // jQuery v1.10.2 | 2005 (https://code.jquery.com/)
'jquery-migrate-1.2.1.min.js', // jQuery Migrate v1.2.1 | 2005 (https://code.jquery.com/)
'jquery-ui.min.js', // jQuery UI v1.10.3 | 2014-09-14 (https://code.jquery.com/)
'bootstrap.min.js', // Bootstrap v3.1.0 | 2014 (http://getbootstrap.com)
];
and create a loop to add scripts to the page:
for (var i = 0; i < scripts.length; i++) {
var link = 'MainJS/' + scripts[i];
LoadJsCssFilePortal(link, 'js');
};
function LoadJsCssFilePortal(filename, filetype, callback) {
// if filename is a external JavaScript file
if (filetype == "js") {
var fileref = document.createElement('script');
fileref.setAttribute("type", "text/javascript");
fileref.setAttribute("src", filename);
};
if (typeof fileref != "undefined") {
// add this script to the document
document.getElementsByTagName("head")[0].appendChild(fileref);
};
};
I also have the function
function LoadAndRunPortalJS(url, callback) {
var script = document.createElement("script")
script.type = "text/javascript";
if (script.readyState) { //IE
script.onreadystatechange = function () {
if (script.readyState === "loaded" || script.readyState === "complete") {
script.onreadystatechange = null;
callback();
}
};
} else { //Others
script.onload = function () {
callback();
};
}
script.src = url;
document.getElementsByTagName("head")[0].appendChild(script);
};
which has a build in callback and is called as such
LoadAndRunPortalJS(link, function () {
// when done load next
});
The issue is that the scripts are not finished loading before
the next increment in the loop
the other scripts on the page that are dependent on Master.js
Typically, one could use the jquery deferred object to enforce the order/completion of scripts so this could possibly be used after the first script (jquery) is loaded i > 0 - but this does not solve the issue of the scripts running outside of master.
This is the domain of script loaders/bundlers. You might consider using RequireJS, Webpack for out of the box solutions. If you still want to roll your own, read on.
I'd replace the array of file names you have, with a more resilient structure of the sort
var manifest = [
{key: 'jquery', file: 'jquery-1.10.2.min.js'}
...
}
This should handle any future versioning issues that may popup (it will). The array structure also preserves your loading order (would not recommend such a method).
On page load, only the manifest described above would be loaded. Any on load functions that you would want to call, have to be deferred until scripts are loaded. I would recommend using a method similar to how ad managers (example, google ads) handle it.
var onBootloaderInit = [/* array of functions */];
....
onBootloaderInit.push(
function() {
/* Code inside can now use jQuery or the likes */
}
);
Once loading has finished, you would now simply iterate through the onBootloaderInit array, and execute each function.
To ensure that scripts are loaded in the order you require, and waits for scripts that have been queued, use a script loader (example, LABjs).
$LAB.script('<path to jQuery>').wait(function() {
/* Code that depends on jQuery */
});

How to include jQuery in remote Javascript file only if site doesn't have jQuery?

I have a script (embed.js) that is being included from other websites/domains. Ex:
<script type="text/javascript" src="//mydomain.com/embed.js"></script>
The code in this script relies on jQuery, but obviously not every website uses jQuery. So, I put this code in the embed.js script at the top:
if (typeof jQuery == 'undefined') {
var jq = document.createElement('script');
jq.type = 'text/javascript';
jq.src = 'http://ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js';
document.getElementsByTagName('head')[0].appendChild(jq);
jQuery.noConflict();
}
But, I get an error about "jQuery" not being defined for the noConflict() line. It is indeed adding the jQuery tag/code to the page, but for some reason after that appendChild() line, the following noConflict() line doesn't recognize jQuery.
How do I only include jQuery on a page if it's not already on it, through a remote Javascript file, and then in that same Javascript file use that jQuery I just included? (This all needs to be through one file.)
You are getting the error because the method you're using for loading jQuery loads it asynchronously, thus jQuery is not yet loaded when you try to execute the .noConflict() line.
You would have similar problems if your code is also trying to use jQuery as it initializes (if your code isn't also loading asynchronously).
If you need to load jQuery synchronously (simpler solution, but probably not preferred), then you can use document.write() to actually write the script tag that would load it and the browser will process that synchronously.
You can also load jQuery in a way that will notify you when it is actually loaded at which time you can run the .noConflict() line and then call your own initialization code that uses jQuery. This is probably the most self-contained mechanism and avoids document.write() which can slow down the loading process some in modern browsers (use of document.write() in some circumstances prevents some loading optimizations).
For example, you could load jQuery with this function and then it would call your callback when it was loaded successfully:
function loadScript(sScriptSrc, oCallback) {
var oHead = document.getElementsByTagName('head')[0];
var oScript = document.createElement('script');
oScript.type = 'text/javascript';
oScript.src = sScriptSrc;
// most browsers
oScript.onload = oCallback;
// IE 6 & 7
oScript.onreadystatechange = function() {
if (this.readyState == 'complete') {
oCallback();
}
}
oHead.appendChild(oScript);
}
loadScript("jquery.js", function() {
jQuery.noConflict();
// call your own initialization function that uses jQuery here
});

How can I dynamically add external scripts using jQuery?

I thought that I could just use jQuery's .append() and add them to the head, but that doesn't seem to be working for my external scripts (Knockout.js).
Here's my code that runs when the page loads. It seems to be working for the stylesheet, but not for the external scripts.
if (window.jQuery === undefined || window.jQuery.fn.jquery !== '1.8.0') {
var script_tag = document.createElement('script');
script_tag.setAttribute("type","text/javascript");
script_tag.setAttribute("src",
"http://code.jquery.com/jquery-1.8.0.min.js");
if (script_tag.readyState) {
script_tag.onreadystatechange = function () { // For old versions of IE
if (this.readyState == 'complete' || this.readyState == 'loaded') {
scriptLoadHandler();
}
};
} else {
script_tag.onload = scriptLoadHandler;
}
// Try to find the head, otherwise default to the documentElement
(document.getElementsByTagName("head")[0] || document.documentElement).appendChild(script_tag);
} else {
// The jQuery version on the window is the one we want to use
jQuery = window.jQuery;
main();
}
function main() {
jQuery(document).ready(function($) {
$("head").append("<script type='text/javascript' src='http://knockoutjs.com/js/jquery.tmpl.js'></script>");
$("head").append("<script type='text/javascript' src='http://cloud.github.com/downloads/SteveSanderson/knockout/knockout-1.2.1.js'></script>");
$("head").append("<link href='style.css' rel='stylesheet' type='text/css' />");
// Then it appends the necessary HTML code [...]
});
}
Here's my test environment where you can see my current code in action with Firebug.
Here's what I'm seeing in Firebug after the page loads:
EDIT: It looks like it's having issues with the Knockout.js scripts in my code, so I'll look into those. Thank you for the comments and answer regarding dynamic scripts. I learned something :)
Have you tried jQuery.getScript() ? It basically loads a script from the server and then executes it.
$.getScript("yourScript.js", function(){});
Try to add scripts this way, I have seen that issue before in some browsers.
var script = document.createElement( 'script' );
script.type = 'text/javascript';
script.src = script_url;
$("head").append( script );
According to this jQuery API pages comment here, this behavior is perfectly normal, as jQuery cleans up the DOM after you
Your code is executed (if URL is correct, and XSS is not blocked) whether you fetch it by $.append()ing it or use $.getScript().
However, loading your site gives me at least three two solid errors. You might want to work on those.
The errors:
ReferenceError: $ is not defined
search.js
Line 54
and
TypeError: jQuery is undefined
http://knockoutjs.com/js/jquery.tmpl.js?_=1353351345859
Line 7
You should use something like AngularJS if your application is this complex. Otherwise you are reinventing the wheel.

document.write fallback causing jQuery to load out of order

I'm building a new site using HTML5 Boilerplate 4.0, and am running into trouble with its jQuery local fallback code. The code in question is here:
<!-- <script src="//ajax.googleapis.com/ajax/libs/jquery/1.8.1/jquery.min.js"></script> -->
<script>window.jQuery || document.write('<script src="js/vendor/jquery-1.8.1.min.js"><\/script>')</script>
<script src="js/plugins.js"></script>
<script src="js/main.js"></script>
I'm developing locally, for now, so I've commented out the CDN line. My problem is that jQuery does load, but it loads after plugins.js and main.js, leading to undefined errors.
The closest to maybe an explanation I've found is the #4 point of this previous answer, which suggests this would be expected, but... the above is easily the most used local fallback code for jQuery, and it's H5BP, which is heavily vetted. I must be missing something, yes?
I answered a similar question some time ago.
You can do something like this:
function loadScript(pathToScript, callback) {
if (/jquery/.test(pathToScript) && window.jQuery) {
//jQuery has already been loaded so simply call the callback
callback.apply();
} else {
var head = document.getElementsByTagName("head")[0];
var script = document.createElement("script");
script.type = "text/javascript";
script.src = pathToScript + "?t=" + new Date().getTime(); //prevent caching
if (callback) {
script.onload = callback;
}
head.appendChild(script);
}
}
var scripts = ['js/vendor/jquery-1.8.1.min.js', 'js/plugins.js', 'js/main.js'];
(function (i) {
if (i < scripts.length) {
var self = arguments.callee;
loadResource(scripts[i], function () {
self(++i);
});
}
})(0);
Neither did I find a precise answer on StackOverflow about this issue, however, it seems that this page covered the subject :
To sum up - creating a truly robust failover solution is not simple. We need to consider browser incompatabilities, document states and events, dependencies, deferred loading and time-outs! Thankfully tools like LABjs exist to help us ease the pain by giving us complete control over the loading of our JavaScript files.
http://happyworm.com/blog/2010/01/28/a-simple-and-robust-cdn-failover-for-jquery-14-in-one-line/
Note : solution relies on the use of LABjs
You may consider using yepnope to load your scripts in parallel with the fallback.
From their website:
yepnope.js has the capability to do resource fallbacks and still
download dependent scripts in parallel with the first.
And the code:
yepnope([{
load: 'http:/­/ajax.googleapis.com/ajax/libs/jquery/1.5.1/jquery.min.js',
complete: function () {
if (!window.jQuery) {
yepnope('local/jquery.min.js');
}
}
}, {
load: 'jquery.plugin.js',
complete: function () {
jQuery(function () {
jQuery('div').plugin();
});
}
}]);
I hope this help!

Including a .js file within a .js file [duplicate]

This question already has answers here:
How do I include a JavaScript file in another JavaScript file?
(70 answers)
Closed 9 years ago.
I'd like to know if it is possible to include a .js file within another .js file?
The reason for me wanting to do this is to keep client includes to a minimum. I have several .js files already written with functions that are needed by the client. The client would have an html file which he/she manages with a .js file include (my .js file).
I could re-write a new .js file with all the functions in it or, to avoid doing double work, figure out a way to write a .js file that includes other .js files.
I basically do like this, create new element and attach that to <head>
var x = document.createElement('script');
x.src = 'http://example.com/test.js';
document.getElementsByTagName("head")[0].appendChild(x);
You may also use onload event to each script you attach, but please test it out, I am not so sure it works cross-browser or not.
x.onload=callback_function;
The best solution for your browser load time would be to use a server side script to join them all together into one big .js file. Make sure to gzip/minify the final version. Single request - nice and compact.
Alternatively, you can use DOM to create a <script> tag and set the src property on it then append it to the <head>. If you need to wait for that functionality to load, you can make the rest of your javascript file be called from the load event on that script tag.
This function is based on the functionality of jQuery $.getScript()
function loadScript(src, f) {
var head = document.getElementsByTagName("head")[0];
var script = document.createElement("script");
script.src = src;
var done = false;
script.onload = script.onreadystatechange = function() {
// attach to both events for cross browser finish detection:
if ( !done && (!this.readyState ||
this.readyState == "loaded" || this.readyState == "complete") ) {
done = true;
if (typeof f == 'function') f();
// cleans up a little memory:
script.onload = script.onreadystatechange = null;
head.removeChild(script);
}
};
head.appendChild(script);
}
// example:
loadScript('/some-other-script.js', function() {
alert('finished loading');
finishSetup();
});
There is no straight forward way of doing this.
What you can do is load the script on demand. (again uses something similar to what Ignacio mentioned,but much cleaner).
Check this link out for multiple ways of doing this:
http://ajaxpatterns.org/On-Demand_Javascript
My favorite is(not applicable always):
<script src="dojo.js" type="text/javascript">
dojo.require("dojo.aDojoPackage");
Google's closure also provides similar functionality.
A popular method to tackle the problem of reducing JavaScript references from HTML files is by using a concatenation tool like Sprockets, which preprocesses and concatenates JavaScript source files together.
Apart from reducing the number of references from the HTML files, this will also reduce the number of hits to the server.
You may then want to run the resulting concatenation through a minification tool like jsmin to have it minified.
I use #gnarf's method, though I fall back on document.writelning a <script> tag for IE<7 as I couldn't get DOM creation to work reliably in IE6 (and TBH didn't care enough to put much effort into it). The core of my code is:
if (horus.script.broken) {
document.writeln('<script type="text/javascript" src="'+script+'"></script>');
horus.script.loaded(script);
} else {
var s=document.createElement('script');
s.type='text/javascript';
s.src=script;
s.async=true;
if (horus.brokenDOM){
s.onreadystatechange=
function () {
if (this.readyState=='loaded' || this.readyState=='complete'){
horus.script.loaded(script);
}
}
}else{
s.onload=function () { horus.script.loaded(script) };
}
document.head.appendChild(s);
}
where horus.script.loaded() notes that the javascript file is loaded, and calls any pending uncalled routines (saved by autoloader code).

Categories

Resources