loading javascript dependencies on demand - javascript

I'm sure there are different approaches to this problem, and I can think of some. But I'd like to hear other people's opinion on this. To be more specific I've built a widget that allows users to choose their location from a google maps map. This widget is displayed on demand and will probably be used every 1 out of 10 uses of the page where it's placed. The simplest way to load the dependency for this widget (google maps js api) is to place a script tag in the page. But this would make the browser request that script on every page load. I'm looking for a way to make the browser request that script only when the user requires for the widget to be displayed.

function loadJSInclude(scriptPath, callback)
{
var scriptNode = document.createElement('SCRIPT');
scriptNode.type = 'text/javascript';
scriptNode.src = scriptPath;
var headNode = document.getElementsByTagName('HEAD');
if (headNode[0] != null)
headNode[0].appendChild(scriptNode);
if (callback != null)
{
scriptNode.onreadystagechange = callback;
scriptNode.onload = callback;
}
}
And to use (I used swfObject as an example):
var callbackMethod = function ()
{
// Code to do after loading swfObject
}
// Include SWFObject if its needed
if (typeof(SWFObject) == 'undefined')
loadJSInclude('/js/swfObject.js', callbackMethod);
else
callbackMethod();

You might want to take a look at jsloader: http://www.jsloader.com/

Gaia Ajax does this (I know since I implemented it - I'm the original founder) and they're GPL. So unless you're afraid they'll sue you (they're FUDding me with lawsuits now) you might want to check out how they do it. Basic technology is to inject a script tag using DOM when script is needed. Though you must take care NOT to reference stuff in this file before it is finished loading (which happens asynchronously)
The solution to that problem (one solution) is to add up a variable at the bottom of the file and use recursive setTimeout calls to check if the variable is defined and defer execution of the code depending on the file being finished loading until that "bottom of JS file" variable is defined...
We actually also tracked which files where included by appending the hashed value of the filenames into a hidden field on the page. This means we never ended up including the same file more then once...
Pretty nifty in fact...

You might want to take a look at a real DEMO on real estate site.
On the demo page, just click on the link [Xem bản đồ] to see the map loaded on demand.
The map loaded only when the link be clicked not at the time of page load, so it can reduce page download time.

The Google AJAX APIs provide dynamic loading for Google's JavaScript APIs. There is an example of loading the Maps JS on-demand in the documentation:
function mapsLoaded() {
var map = new google.maps.Map2(document.getElementById("map"));
map.setCenter(new google.maps.LatLng(37.4419, -122.1419), 13);
}
function loadMaps() {
google.load("maps", "2", {"callback" : mapsLoaded});
}

you can load script dynamically by adding <script src="..."> tag to DOM tree.

Related

Angular2 with Typescript, how to use Plupload CDN script file in lazy-loaded module?

I would like to use Plupload in an Angular2 component and access the Plupload JavaScript file from a CDN. I want it specific to a component so that it is not downloaded if it is not required - I want it to be in a lazy loaded module. How can I do this?
Now fully answered on this page!
The result of this quest, which included offering and awarding bounties to two people who worked hard with me on it, is as follows:
Example of using Plupload with Angular 2 and TypeScript
How to Lazy load a script from a CDN in Angular 2
Example of how to use Plupload in a lazy loaded module
How to use a lazy loaded script in Angular 2
(See edit history for the ugly details that used to make up this question.)
Here's the overview of what you need to do to create a lazy-loaded Plupload feature while loading Plupload from a CDN:
When the feature is needed (e.g. user clicks a button or visits a page), dynamically add a <script> tag to the page to load the Plupload library from a CDN.
Wait until the library is loaded to proceed (or you could get a "plupload is undefined" error).
Display the UI to interact with Plupload in one of your Angular templates. In its simplest form, this UI consists of two buttons: "Select files" and "Upload files".
Initialize Plupload and wire it up to the UI.
Complete, working code: https://plnkr.co/edit/4t39Rod4YNAOrHmZdxqc?p=preview
Please take note of the following points in my implementation:
Regarding #2. A better way to check whether Plupload has finished loading would be to poll the global namespace for the existence of the plupload variable. As long as window.plupload does not exist, it means the library hasn't been loaded yet and that we should NOT proceed. For simplicity my code just waits for one second and proceeds.
Number 4 can prove a bit tricky. Plupload makes a heavy use of direct DOM access to wire its API to the HTML (e.g. document.getElementById('filelist')). This is something Angular discourages and that you should try avoiding whenever possible. More specifically direct DOM access is used in the following places:
To tell Plupload which DOM element should trigger the "Select files" dialog (what they call the browse_button config option). For this I could not avoid the direct DOM reference and I used the #ViewChild decorator to get a hold of the "Select Files" button.
To display selected files in the template. For this I converted the Plupload syntax into the regular Angular syntax. I push selected files to a class property called fileList which I display in the template using a standard *ngFor.
The "Upload Files" button triggers some code that does the actual uploading and refreshes the UI to show upload progress. Once more, I converted this to regular Angular syntax using event binding and data binding.
Let me know if you have any questions.
In this approach no need for any extra loader modules.
See example (check console for Woohoo): http://plnkr.co/edit/gfUs4Uhe8kMGzPxwpBay?p=preview
updated plunker: https://plnkr.co/edit/leG062tg7uX8sLrA0i2i?p=preview
You can lazyload some js by adding the script url to you document:
Create a my-lazy-load.function.ts:
export function lazyload(url) {
// based on https://friendlybit.com/js/lazy-loading-asyncronous-javascript/
let scripts = document.getElementsByTagName('script');
for (let i = scripts.length; i--;) {
if (scripts[i].src.match(url)) return true;
}
let s = document.createElement('script');
s.type = 'text/javascript';
s.async = true;
s.src = url;
let x = document.getElementsByTagName('script')[0];
x.parentNode.insertBefore(s, x);
return true;
}
In your component that you want to add plupload:
import {lazyload} from "./my-lazy-load.function.ts";
export class MyComponent implements OnInit {
pluploadInterval:number = null;
hasPlupload: boolean = false;
ngOnInit() {
lazyload("https://cdnjs.cloudflare.com/ajax/libs/plupload/2.3.1/plupload.full.min.js");
this.pluploadInterval = window.setInterval(()=>{
if(window.plupload) { // you can check existence of plupload object any way you want
// Woohoo, I am ready to be used
this.hasPlupload = true; // everything is run outside ngZone, wrap it if angular2 is not reacting to changes, or change window.setInterval to setInterval
window.clearInterval(this.pluploadInterval); // don't forget to clean interval
}
}, 100); // timeinterval can vary
....
The browser will load this automatically.
Notice if(plupload) it assumes that there is global object plupload that the script adds (I do not know if it truely added, check your working example in pure javascript). As it is jquery extension you can check it's prototype like this: jQuery test for whether an object has a method?
OLD HISTORICAL:
#Reid here is plunker: https://plnkr.co/edit/zDWWQbTQUSHBqCsrUMUi?p=preview the plupload is actually loaded, but added to require with define("plupload", ['./moxie'], extract); I am not sure at the moment how to extract from there and which package require is belong to... the code for finding correct module loader belongs to plupload itself, here it is (from plupload.dev.js):
if (typeof define === "function" && define.amd) {
define("plupload", ['./moxie'], extract);
} else if (typeof module === "object" && module.exports) {
module.exports = extract(require('./moxie'));
} else {
global.plupload = extract(global.moxie);
}
I think that your best bet is to use the Require.js Library so that you can dynamically load your scripts from within your components.
The small trade off is that you will have to add this 18KB library to your index.html page (CDN), however this could save you huge amounts of loading if your 3rd party libraries are massive.
I have no experience with using plupload, so instead I put together the following plunkr which uses an external animation library, drawn from a CDN. The plunkr animates a number from 0 - 100.
https://plnkr.co/edit/fJCtezsERYHOYplLh7Jo?p=preview
index.html
<script src="https://cdnjs.cloudflare.com/ajax/libs/require.js/2.3.3/require.min.js"></script>
component.ts
ngOnInit(){
// Dynamically loads the framework from the CDN.
require(["https://cdnjs.cloudflare.com/ajax/libs/gsap/1.19.1/TweenLite.min.js"],
// This function is then called ONCE LOAD IS COMPLETE
function (common) {
// Do greensock things
var demo = {score:0},
scoreDisplay = document.getElementById("scoreDisplay");
//create a tween that changes the value of the score property of the demo object from 0 to 100 over the course of 20 seconds.
var tween = TweenLite.to(demo, 20, {score:100, onUpdate:showScore})
//each time the tween updates this function will be called.
function showScore() {
scoreDisplay.innerHTML = demo.score.toFixed(2);
}
});
}
What I like about this approach, is that in the onLoad callback from require, the syntax is unchanged from a normal implementation of the library, so you can just copy paste your currently working code into the callback.

How to tell if Google Analytics is loaded

I make a lot of javascript widgets. As part of these widgets, we often use Google Analytics to track actions within the widget. Simplified, what I do is:
var setupGA = function(){ window._gaq.push(['_setAccount', 'UA-###']); };
if(window._gaq){
setupGA();
} else {
this.loadScript(GOOGLE_ANALYTICS_PATH, function(){
var waitga = setInterval(function(){
if(window._gaq){
clearInterval(waitga);
setupGA();
}
}, 500);
});
}
Where GOOGLE_ANALYTICS_PATH is my local path to the file and loadScript is a custom method to load the script and execute a callback.
With Google updating their analytics library (now analytics.js), old methods of ensuring that the analytics library has been loaded no longer work. The example code for google analytics helpfully provides a global ga object, but this object can have a custom name. With the old queue to check, I'm wondering how best I can check for the existance of either analytic.js or ga.js versions of google analytics (I Can skip the very old Urchin Tracking types).
It must be something like:
var setupGA = function(){ window._gaq.push(['_setAccount', 'UA-###']); };
if(window._gaq && !window.**ga**){
setupGA();
} else {
this.loadScript(GOOGLE_ANALYTICS_PATH, function(){
var waitga = setInterval(function(){
//_gaq will always be loaded for now
if(window._gaq){
clearInterval(waitga);
setupGA();
}
}, 500);
});
}
Where the ga is whatever global element I can check. I suppose thats what I'm looking for
Answered my own question with a little more exploration.
With the new Universal Analytics platform, it seems that a window level variable is created. That variable is called GoogleAnalyticsObject. This object will give you the variable name created to house the Universal Analytics Object
EX: Using (window,document,'script','//www.google-analytics.com/analytics.js','ga'); will return "ga"
EX: Using (window,document,'script','//www.google-analytics.com/analytics.js','notGA'); will return "notGA"
You can then use this variable to pull the Analytics object:
window[window.GoogleAnalyticsObject]
Another approach is to open the browser console and look for the tracking request in the network tab. If the tracking code isn't loading successfully, you won't see a tracking request being sent to Google.
With Universal Analytics (analytics.js), look for a request to www.google-analytics.com/collect
With Legacy code (ga.js or urchin.js), look for a request for __utm.gif
If your browser uses a script blocking plugin like NoScript, the tracking code won't run unless you allow scripts from www.google-analytics.com

Can't connect API to Chrome extension

I am developing chrome extension. I want to connect some API to current tab after click on button in popup.html. I use this code in popup.js:
$('button').click(function() {
chrome.tabs.executeScript({
file: 'js/ymaps.js'
}, function() {});
});
In ymaps.js I use following code to connect API to current tab:
var script = document.createElement('script');
script.src = "http://api-maps.yandex.ru/2.0-stable/?load=package.standard&lang=ru-RU";
document.getElementsByTagName('head')[0].appendChild(script);
This API is needed to use Yandex Maps. So, after that code I create <div> where map should be placed:
$('body').append('<div id="ymapsbox"></div>');
And this simple code only loads map to created <div>:
ymaps.ready(init);//Waits DOM loaded and run function
var myMap;
function init() {
myMap = new ymaps.Map("ymapsbox", {
center: [55.76, 37.64],
zoom: 7
});
}
I think, everything is clear, and if you are still reading, I'll explain what is the problem.
When I click on button in my popup.html I get in Chrome's console Uncaught ReferenceError: ymaps is not defined. Seems like api library isn't connected. BUT! When I manually type in console ymaps - I get list of available methods, so library is connected. So why when I call ymaps-object from executed .js-file I get such an error?
UPD: I also tried to wrap ymaps.ready(init) in $(document).ready() function:
$(document).ready(function() {
ymaps.ready(init);
})
But error is still appearing.
Man below said that api library maybe isn't loaded yet. But this code produces error too.
setTimeout(function() {
ymaps.ready(init);
}, 1500);
I even tried to do such a way...
chrome.tabs.onUpdated.addListener(function(tabId, changeInfo) {
if (changeInfo.status == "complete") {
chrome.tabs.executeScript({
file: 'js/gmm/yandexmaps.js'
});
}
});
ymaps is not defined because you're trying to use it in the content script, while the library is loaded in the context of the page (via the <script> tag).
Usually, you can solve the problem by loading the library as a content script, e.g.
chrome.tabs.executeScript({
file: 'library.js'
}, function() {
chrome.tabs.executeScript({
file: 'yourscript.js'
});
});
However, this will not solve your problem, because your library loads more external scripts in <script> tags. Consequently, part of the library is only visible to scripts within the web page (and not to the content script, because of the separate script execution environments).
Solution 1: Intercept <script> tags and run them as a content script.
Get scriptTagContext.js from https://github.com/Rob--W/chrome-api/tree/master/scriptTagContext, and load it before your other content scripts. This module solves your problem by changing the execution environment of <script> (created within the content script) to the content script.
chrome.tabs.executeScript({
file: 'scriptTagContext.js'
}, function() {
chrome.tabs.executeScript({
file: 'js/ymaps.js'
});
});
See Rob--W/chrome-api/scriptTagContext/README.md for documentation.
See the first revision of this answer for the explanation of the concept behind the solution.
Solution 2: Run in the page's context
If you -somehow- do not want to use the previous solution, then there's another option to get the code to run. I strongly recommend against this method, because it might (and will) cause conflicts in other pages. Nevertheless, for completeness:
Run all code in the context of the page, by inserting the content scripts via <script> tags in the page (or at least, the parts of the extension that use the external library). This will only work if you do not use any of the Chrome extension APIs, because your scripts will effectively run with the limited privileges of the web page.
For example, the code from your question would be restructed as follows:
var script = document.createElement('script');
script.src = "http://api-maps.yandex.ru/2.0-stable/?load=package.standard&lang=ru-RU";
script.onload = function() {
var script = document.createElement('script');
script.textContent = '(' + function() {
// Runs in the context of your page
ymaps.ready(init);//Waits DOM loaded and run function
var myMap;
function init() {
myMap = new ymaps.Map("ymapsbox", {
center: [55.76, 37.64],
zoom: 7
});
}
} + ')()';
document.head.appendChild(script);
};
document.head.appendChild(script);
This is just one of the many ways to switch the execution context of your script to the page. Read Building a Chrome Extension - Inject code in a page using a Content script to learn more about the other possible options.
This is not a timing issue, rather an "execution environment"-related issue.
You inject the script into the web-page's JS context (inserting the script tag into head), but try to call ymaps from the content script's JS context. Yet, content-scripts "live" in an isolated world and have no access to the JS context of the web-page (take a look at the docs).
EDIT (thx to Rob's comment)
Usually, you are able to bundle a copy of the library and inject it as a content script as well. In your perticular case, this won't help either, since the library itself inserts script tags into to load dependencies.
Possible solutions:
Depending on your exact requirements, you could:
Instead of inserting the map into the web-page, you could display (and let the user interact with) it in a popup window or new tab. You will provide an HTML file to be loaded in this new window/tab containing the library (either referencing a bundled copy of the file or using a CDN after relaxing the default Content Security Policy - the former is the recommended way).
Modify the external library (i.e. to eliminate insertion of script tags). I would advise against it, since this method introduces additional maintainance "costs" (e.g. you need to repeat the process every time the library is updated).
Inject all code into the web-page's context.
Possible pitfall: Mess up the web-pages JS, e.g. overwriting already defined variables/functions.
Also, this method will become increasingly complex if you need to interact with chrome.* APIs (which will not be available to the web-page's JS context, so you'll need to device a proprietary message passing mechanism, e.g. using custom events).
Yet, if you only need to execute some simple initialization code, this is a viable alternative:
E.g.:
ymaps.js:
function initMap() {
ymaps.ready(init);//Waits DOM loaded and run function
var myMap;
function init() {
myMap = new ymaps.Map("ymapsbox", {
center: [55.76, 37.64],
zoom: 7
});
}
}
$('body').append('<div id="ymapsbox"></div>');
var script1 = document.createElement('script');
script1.src = 'http://api-maps.yandex.ru/2.0-stable/?load=package.standard&lang=ru-RU';
script1.addEventListener('load', function() {
var script2 = document.createElement('script');
var script2.textContent = '(' + initMap + ')()';
document.head.appendChild(script2);
});
document.head.appendChild(script1);
Rob already pointed to this great resource on the subject:
Building a Chrome Extension - Inject code in a page using a Content script
There is a much easier solutioin from Yandex itself.
// div-container of the map
<div id="YMapsID" style="width: 450px; height: 350px;"></div>
<script type="text/javascript">
var myMap;
function init (ymaps) {
myMap = new ymaps.Map("YMapsID", {
center: [55.87, 37.66],
zoom: 10
});
...
}
</script>
// Just after API is loaded the function init will be invoked
// On that moment the container will be ready for usage
<script src="https://...?load=package.full&lang=ru_RU&onload=init">
Update
To work this properly you must be sure that init has been ready to the moment of Yandex-scirpt is loaded. This is possible in the following ways.
You place init on the html page.
You initiate loading Yandex-script from the same script where init is placed.
You create a dispatcher on the html page which catches the ready events from both components.
And you also need to check that your container is created to the moment of Yandex-script is loaded.
Update 2
Sometimes it happens that init script is loaded later than Yandex-lib. In this case it is worth checking:
if(typeof ymaps !== 'undefined' && typeof ymaps.Map !== 'undefined') {
initMap();
}
Also I came across a problem with positioning of the map canvas, when it is shifted in respect to the container. This may happen, for example, when the container is in a fading modal window. In this case the best is to invoke a window resize event:
$('#modal').on('shown.bs.modal', function (e) {
window.dispatchEvent(new Event('resize'));
});

Load jQuery script after JavaScript script has finished

Similar to the way jQuery loads after the document has loaded I need a certain bit of jQuery after a JavaScript script has FINISHED.
I am loading contact data from Google Data API and the div where the data shows up needs to be completely loaded (by the JavaScript) to be accessible by the jQuery if I'm right.
Is there any way to do this?
Let's say you're waiting for Google Analytics to load. Now, you know that when Google Analytics loads, _gaq will be registered on the DOM. So, barring all other (better) asynchronous options, you can create a function that waits for the existence of _gaq:
waitForGaq = function(fn, attemptsLeft) {
var tick = attemptsLeft || 30;
if (typeof(_gaq) != 'object' || !_gaq._getAsyncTracker) {
//_gaq isn't registered yet
if (tick > 1) {
//recurse
setTimeout(function() {
waitForGaq(fn, tick - 1);
}, 100)
}
else {
//no ticks left, log error
log('failed to load window.gaq');
}
}
else {
//gaq is loaded, fire fn
fn();
}
}
So, any code needs to run AFTER _gaq is registered, you can add to the page thusly:
waitForGaq(function() {
//here's all my code...
});
To reiterate: recursing like this is only necessary if the script that you're adding doesn't offer an asynchronous way of interacting with the library. Google Analytics, for one, is kind of a moot example as it offers a cleaner approach:
http://code.google.com/apis/analytics/docs/tracking/asyncTracking.html
Try head.js
It allows to define exactly when (after specific script or after all of them have been loaded) you want to execute particular piece of code.
Not to mention it is by itself very neat.
inject a tag directly after the original with the jQuery code you need to run
<script src="foo" id="bar">
$("#bar").after(
$("<script>").text("some code")
);
if i understand correctly, you want to do something like:
<script>
// your javascript code
</script>
<script>
//your jquery code
</script>

JavaScript: How to download JS asynchronously?

On my web site, I'm trying to accomplishes the fastest page load as possible.
I've noticed that it appears my JavaScript are not loading asynchronously. Picture linked below.
alt text http://img249.imageshack.us/img249/2452/jsasynch2.png
How my web site works is that it needs to load two external JavaScript files:
Google Maps v3 JavaScript, and
JQuery JavaScript
Then, I have inline JavaScript within the HTML that cannot be executed until those two files above are downloaded.
Once it loads these external javascript files, it then, and only then, can dynamically render the page. The reason why my page can't load until both Google Maps and JQuery are loaded is that - my page, based on the geolocation (using Gmaps) of the user will then display the page based on where they are located (e.g. New York, San Francisco, etc). Meaning, two people in different cities viewing my site will see different frontpages.
Question: How can I get my JavaScript files to download asynchronously so that my overall page load time is quickest?
UPDATE:
If I were to download, somehow, Google-maps and JQuery asynchronously, how would I create an event that would be fired once both Google-maps and JQuery have downloaded since my page has a hard dependency on those files to execute.
UPDATE 2
Even though there are 3 answers below, none still actually answer the problem I have. Any help would be greatly appreciated.
HTTP downloads are generally limited by browsers to two simultaneous downloads per domain. This is why some sites have the dynamic content on www.domain.tla and the images and javascript on static.domain.tla.
But browsers act slightly differently with scripts, while a script is downloading, however, the browser won't start any other downloads, even on different hostnames.
The standard solution is to move scripts to the bottom of the page, but there is a workaround that might or might not work for you: Insert the script DOM element using Javascript.
You could use something like this, which works pretty well in most browsers. It has some issues in IE6 at least, but I don't really have the time to investigate them.
var require = function (scripts, loadCallback) {
var length = scripts.length;
var first = document.getElementsByTagName("script")[0];
var parentNode = first.parentNode;
var loadedScripts = 0;
var script;
for (var i=0; i<length; i++) {
script = document.createElement("script");
script.async = true;
script.type = "text/javascript";
script.src = scripts[i];
script.onload = function () {
loadedScripts++;
if (loadedScripts === length) {
loadCallback();
}
};
script.onreadystatechange = function () {
if (script.readyState === "complete") {
loadedScripts++;
if (loadedScripts === length) {
loadCallback();
}
}
};
parentNode.insertBefore(script, first);
}
};
require([
"http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js",
"http://ajax.googleapis.com/ajax/libs/prototype/1.6.1.0/prototype.js",
"http://ajax.googleapis.com/ajax/libs/yui/2.7.0/build/yuiloader/yuiloader-min.js"
], function () {
console.log(jQuery);
console.log($);
console.log(YAHOO);
});
Someone asked me to comment on this thread, but that was before #lonut posted a response. #lonut's code is a very good solution, but I have some comments (critical and not so critical):
First, #lonut's code assumes that the scripts do NOT have load dependencies on the other scripts. This is a little hard to explain, so let's work with the simple example of jquery.min.js and prototype.js. Suppose we have a simple page that just loads these two scripts like this:
<script src="jquery.min.js"></script>
<script src="prototype.js"></script>
Remember - there's nothing else in the page - no other JavaScript code. If you load that page the two scripts get downloaded and everything's fine. Now, what happens if you remove the jquery.min.js script? If you get errors from prototype.js because it's trying to reference symbols defined in jquery.min.js, then prototype.js has a load dependency on jquery.min.js - you cannot load prototype.js unless jquery.min.js has already been loaded. If, however, you don't get any errors, then the two scripts can be loaded in any order you wish. Assuming you have no load dependencies between your external scripts, #lonut's code is great. If you do have load dependencies - it gets very hard and you should read Chapter 4 in Even Faster Web Sites.
Second, one problem with #lonut's code is some versions of Opera will call loadCallback twice (once from the onload handler and a second time from the onreadystatechange handler). Just add a flag to make sure loadCallback is only called once.
Third, most browsers today open more than 2 connections per hostname. See Roundup on Parallel Connections.
The LABjs dynamic script loader is designed specifically for this type of case. For instance, you might do:
$LAB
.script("googlemaps.js")
.script("jquery.js")
.wait(function(){
// yay, both googlemaps and jquery have been loaded, so do something!
});
If the situation was a little more complex, and you had some scripts that had dependencies on each other, as Steve Souders has mentioned, then you might do:
$LAB
.script("jquery.js")
.wait() // make sure jquery is executed first
.script("plugin.jquery.js")
.script("googlemaps.js")
.wait(function(){
// all scripts are ready to go!
});
In either case, LABjs will download all of the scripts ("jquery.js", "googlemaps.js", and "plugin.jquery.js") in parallel, as least up to the point the browser will allow. But by judicious use of the .wait() in the chain, LABjs will make sure they execute in the proper order. That is, if there's no .wait() in between the two scripts in the chain, they will each execute ASAP (meaning indeterminate order between tehm). If there's a .wait() in between two scripts in the chain, then the first script will execute before the second script, even though they loaded in parallel.
Here is how I've managed to load gmaps asynchronously on a jquery mobile:
First, you can load jquery (i.e. with the require function posted above by Ionuț G. Stan)
Then you can make use of the callback param in gmaps to do the following:
<!DOCTYPE html>
<html>
<body>
<script type="text/javascript">
var require = function (scripts, loadCallback) {
var length = scripts.length;
var first = document.getElementsByTagName("script")[0];
var parentNode = first.parentNode;
var loadedScripts = 0;
var script;
for (var i=0; i<length; i++) {
script = document.createElement("script");
script.async = true;
script.type = "text/javascript";
script.src = scripts[i];
script.onload = function () {
loadedScripts++;
if (loadedScripts === length) {
loadCallback();
}
};
script.onreadystatechange = function () {
if (script.readyState === "complete") {
loadedScripts++;
if (loadedScripts === length) {
loadCallback();
}
}
};
parentNode.insertBefore(script, first);
}
};
require([
"http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js",], function () {
$.ajax({
type: "GET",
url: 'http://maps.googleapis.com/maps/api/js?v=3&sensor=false&callback=setMyMap',
dataType: "script"
});
});
function setMyMap() {
console.log('your actions here');
var coords = new google.maps.LatLng(40.5439532,-3.6441775);
var mOptions = {
zoom: 8,
center: coords,
mapTypeId: google.maps.MapTypeId.ROADMAP
}
var map = new google.maps.Map(document.getElementById("gmap"), mOptions);
}
</script>
<div id="gmap" style="width:299px; height:299px"></div>
</body>
The point is load jquery async (whathever method you choose) and on that callback place a new async call to gmaps with your starting method in the callback param of the gmaps script string.
Hope it helps
Regardless what order they download in, the scripts should be parsed/executed in the order in which they occur on the page (unless you use DEFER).
So, you can put both Google Maps first in the head, THEN JQuery. Then, in the body of your page somewhere:
<script language="Javascript">
function InitPage() {
// Do stuff that relies on JQuery and Google, since this script should
// not execute until both have already loaded.
}
$(InitPage); // this won't execute until JQuery is ready
</script>
But this does have the disadvantage of blocking your other connections while loading the beginning of the page, which isn't so awesome for page performance.
Instead, you can keep JQuery in the HEAD, but load the Google scripts from the InitPage() function, using JQuery's Javascript-loading functionality rather than the Google JSAPI. Then start your rendering when that call-back function executes. Same as the above, but with this InitPage() function instead:
function InitPage() {
$.getScript('Google Maps Javascript URL', function() {
// Safe to start rendering now
});
Move your javascript includes (<script src="...) from the HEAD element to the end of your BODY element. Generally whatever is placed in the HEAD is loaded synchronously, whatever is placed in the BODY is loaded asynchronously. This is more or less true for script includes, however most browsers these days block everything below the script until it is loaded - hence why having scripts included at the bottom of the body is best practice.
Here is the YUI guildline for this which explains it in further detail:
http://developer.yahoo.net/blog/archives/2007/07/high_performanc_5.html
This is also the reason why stylesheets should be in the head, and javascript should be in the body. As we do not want to see our page turn from spaghetti to niceness while the styles load asynchronously, and we don't want to wait on our javascript while our page loads.
The objective you have in mind would be served by using requireJS. RequireJS downloads the js resources asynchronously. Its a very simple and useful library to implement. Please read more here. http://requirejs.org/

Categories