I'm creating a web application that has multiple pages of content that I'm loading dynamically with AJAX and the HTML5 History API. When a user attempts to change the page, the new content is loaded with a $.get and injected into the body, like so:
$.get("somepage.html", function (data)
{
$("body").html(data);
});
Most of these pages require additional scripts to be loaded. This wouldn't be an issue except for the fact that $(document).ready fires before these scripts are loaded. Somepage.html looks something like this.
<script src='http://getjquerysomewhere/'></script>
<script src='my_script_that_depends_on_jQuery'></script>
This issue is complicated by the fact that these pages must have the ability to be loaded on their own. I'm therefore unsure how I can eliminate the $(document).ready functions without affecting this behavior as well.
How should I approach this problem?
What you are trying to do is certainly possible, but it's not going to be very maintainable in the long-run.
One of the biggest issues you'll run into is properly injecting the code from the ajax loaded html into the current page. You can't just ignore it and let it all run because then you'll be including libraries multiple times (resulting in plugins getting overwritten/removed), and the code for the page you are loading may happen too soon due to the dom already being ready.
This pretty much leaves you with two options: dependency injection or front-loading.
Dependency injection will probably be the easiest of the two for you to implement because it requires the least amount of changes to your current code-base. All you would have to do is ensure that all pages requested with ajax only include the content of the <body> (which can be done with server-side code), and ensure that all page-specific code is included before the closing </body> of each page. Then you would just have to use the dependency-injection methods to run your code with the proper dependencies.
You could also have it only include <div id="#content">...</div> for your partials, which ever makes more sense for your use-case.
Front-loading would be a little more difficult because you'll have this one giant file that has all of your code for all of the pages, unless you use a build process (if you've never used a build-process before, you really should try it, even if you don't think you need it.) With front-loading, you'll either have to use event delegation, or have init methods for each page that you selectively execute as you load each page. This could become a maintainability nightmare without good build processes.
You can call functions from the original scripts on the page which you have loaded. For Instance you could do this in your main:
<script>
function ExternalScriptLoaded(){}
</script>
Then on your external page:
<script>
try{ ExternalScriptLoaded(); }catch(err){alert('This page was not loaded with ajax because i can't find the function');}
</script>
The alert will trigger if the script can't find the function on your main page.
i.e. call the function after you know the script has finished runnng.
Hope this helped.
Related
I am building a multi-page front end for a web application. There is minimal javascript for the UI, but I know there will a great deal of javascript to handle the back-end, so I am doing my best to keep it as sparse and limited as possible.
I want to conditionally execute javascript synchronously while using only one minified js file. I have some global functions, but most of the code is page specific. Each page has the id of the page in the body element. For example, about.html has the following code <html>...<body id="about">...</body></html>.
I am aware of various async methods to load javascript, such as require.js or even $.getScript, but for various reasons, I am trying to avoid that route. Because I am preprocessing the HTML, I also don't want to break the scripts up per page (though I could), because it is a small file, and I don't want to have to create a bunch of different <script> tags. My current solution is to conditionally execute the required javascript per page, and I am doing it like the example below:
$(document).ready(function(){
//global.js code
if(document.body.id==='about'){
// about.js code
}
if(document.body.id==='home'){
//home.js code
}
// etc.
});
During the build process, all js is stored in seperate files, so it's not as visually asinine as it looks here, because there is only a single include filename.js inside the body of the conditional.
The conditionals prevent unnecessary code from executing on each page. However, it dawned on me while I was doing this that there might not actually be any benefit to doing it this way. I read that $(window).load or $('body').load events might cause conflicts with $(document).ready, and they are both definitely slower - as is window.onload (I tested). That would've been my preferred way of going about this I suppose. Is what I read true? And / or, is what I'm doing in anyway useful?
For page specific events, why not just add the body ID in the element reference?
$( '#about .do-something' ).on()...
And if you're actually executing non event based code on a per page basis
if( $( '#about' ).length ) { // trigger your code here }
It's not too dissimilar to your solution except it doesn't require you to keep a manifest of sorts about what code to execute where. Either way, both solutions get cached.
Google Pagespeed said I should load my JS files asynchronously, but this has introduced a problem for many of my pages with code using libraries and plugins.
For example, I have the following code on one page:
$(document).ready(function () {
var hound = new Bloodhound({ .......
});
So when the page loads, I am creating a Twitter Bloodhound (goes with Typeahead) object. The problem is, if Bloodhound and Typeahead are loaded asynchronously, then an error is thrown:
Uncaught ReferenceError: Bloodhound is not defined
This is because those scripts haven't been loaded yet.
I came up with this solution:
$(document).ready(function () {
createBloodhound();
});
function createBloodhound() {
if (typeof Bloodhound != "undefined") { // if bloodhound JS has loaded
var hound = new Bloodhound({ .......
}
else {
setTimeout(function(){
createBloodhound();
}, 10);
}
}
Is this a good practice, or is there a better way?
NOTE: I realize there are libraries like RequireJS out there to handle dependencies when loading files, but I don't think this type of solution will not work in my case because I load the libraries asynchronously in a wrapper file (since they're required for every page). The example code here would not be on every page, but only on a specific page on my website.
The best approach is to use a callback mechanism, which you can react to, rather than using a polling mechanism. I used script.js, which is simple and yet functional, and offers the callback mechanism.
Without that, you could implement something yourself. Performance-wise though, utilizing callback are better.
Depending on the complexity of your site, different options might be best. If...
All of your javascript is in JS files
Your above-the-fold content looks identical before and after the JS is loaded (or close enough to identical that the flash of change when your JS does load wouldn't distract your users)
The total file size is small (or most of your JS is needed on pages everyone will visit every time they visit your site)
... then combine them into one file and just server that instead of all the individual ones. Then you don't have to worry about dependencies at all. Include that script file at the bottom of your body tag (no need for async or defer attributes, but you can use them if you want).
If some of your javascript is necessary to make your above-the-fold content look correct, do the same thing, except split your JS into two files. One file contains only what is necessary to make the above-the-fold content look correct, and the other file contains everything else. Include the first one in your head tag (possibly inlining it), and include the second one at the bottom of your body tag. If the second one depends on the first tag, do not use the async attribute, because it might get executed first.
If you have some large JS files that are only used on some pages, and those files depend on other JS files, stick your scripts at the bottom of your body tag and use the defer attribute.
If you have javascript mixed in with your HTML, you can use a callback mechanism (like script.js), or you can build up execution queues like Google Analytics does, which the external script knows to look for when it first loads.
I have many (almost 15) javascript files which I am using in my webpage.
What is the best way to include all of these files?
Simply at bottom or is there some other technique that should be used which can load javascript faster?
You could combine and minify them. YUI Compressor could do that.
You may consider using a parallel/non-blocking javascript loader.
Following are some great libraries that could do this for you..
http://headjs.com/
http://requirejs.org/
http://labjs.com/
Google Closure Compiler has a convient web api for merging and minifying all your js.
https://developers.google.com/closure/compiler/docs/api-ref
You can put a script tag with a direct link to the api.
I haven't quite mastered implementing all this yet I have found that a fair amount of minification & concatenation can be automated by servers, I have learned a fair amount from reading the (well-commented) .htaccess file in the HTML5 Boilerplate and associated docs.
Modernizr also features an asyncronous loader function and it was well enough documented that I was able to get it to work when I just couldn't quite figure out require.js
You could use a script loader library or you can do it yourself.
Where to include scripts and how the browser loads resources
Always at the bottom just before the closing <body> tag, to enhance the user experience. It ensures that the page content gets rendered first, which gives the impression that your pages load quicker.
The browser runs a single thread and will process the page content from top to bottom, if it happens on a resource, it first has to retrieve the resource, process it and then only continue with the next tag on the page. Since we generally want to wait for the DOM to be finished loading, before running any scripts in any case, this strategy is always a win win.
Size matters
For production you want to ensure that all your scripts are minified regardless of the strategy you are going to employ to get them loaded (smaller size == less to download == quicker)
The correct order
Most important is always the dependency order, if we call a function or access a variable and the browser doesn't know about it first, the script will fail. You want to ensure that a script is only included after any scripts they depend on. So the first thing to do is to work out the order by which you need to include and introduce functionality to the browser.
How to include scripts into a page
There is only one way to include a script in a page (not recommending same origin ajax/eval) and that is by means of the <script> tag.
There are only 2 ways to create a <script> tag:
Statically located in html or
dynamically added with JavaScript.
Statically added scripts
Since we know the order we need our scripts included we can add them one by one after each other like this
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.9.0/jquery.min.js"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/jquery.tablesorter/2.5.2/jquery.tablesorter.min.js"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/jquery.tablesorter/2.5.2/jquery.tablesorter.widgets.min.js"></script>
Dynamically added scripts
There are three ways to effectively create script tags in the dom, dynamically, which we will explore. These are certainly not the only ways...
document.write
The write method will append to the existing dom.
document.write('<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.9.0/jquery.min.js"></script>');
But now we have a problem as there is no way of telling that the script is done loading, because we have to wait until it is complete before loading the next one or calling something that is defined there.
document.createElement
The createElement method creates a dom element and we can insert it anywhere in the dom we choose.
The following example checks if jQuery exists or creates a <script> element to go fetch it. In any event, the function ready_go is called and we should have jQuery.
(function() {
if (!window.jQuery) {
var jq = document.createElement('script');
jq.type = 'text/javascript';
jq.async = true;
jq.src = 'http://ajax.googleapis.com/ajax/libs/jquery/1.9.0/jquery.min.js'
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(jq, s);
jq.onreadystatechange = jq.onload = ready_go;
} else ready_go();
})();
jQuery.getScript
Since we have jQuery now we don't have to do things the hard way, getScript takes as first argument the script url and will call the function as 2nd argument once the script is complete.
function ready_go() {
$.getScript('http://cdnjs.cloudflare.com/ajax/libs/jquery.tablesorter/2.5.2/jquery.tablesorter.min.js', function () {
// script is done loading we can include the next
$.getScript('http://cdnjs.cloudflare.com/ajax/libs/jquery.tablesorter/2.5.2/jquery.tablesorter.widgets.min.js', function () {
// script is done loading we can include the next or start using our stuff
});
});
}
Where to get the scripts from?
You either want to get the scripts from public CDN or host them yourself. An important concept to bear in mind is that scripts, as with any other resources get cached by the browser and you will not be required to repeatedly download the same scripts.
Scripts from a Content Delivery Network
The advantage of a CDN is twofold;
you get closer to the user, because a CDN consists of POPs distributed across the world and even though you are calling one url the server responding may be different
many sites may use the same CDN if your user already got jQuery from google's CDN when visiting my site his browser will simply use the cache copy already in its possession when requested by you.
Scripts served from origin (your server)
I agree with the strategy that #Mario and #Xharze suggests, beats including 18 scripts on every page.
Use CDNs where scripts are available but for the rest.
Concatenate all your scripts together, keeping in mind that the same dependency order applies, and minify them.
Having one script will simplify your includes and because the browser will cache the script no additional downloads will be required again, especially beneficial in application used for extended periods and accessing many pages.
The only advantage separate scripts hold is for users who exit from the landing page and why do we care to optimise for them they're not going to stay because of it.
nJoy!
A lot of my pages have amll bits of jquery.
Im thinking of putting them into one external file with one $(document).ready(function() {
and everything in there.
is this a good/bad idea?
will each page be slower overall if there is more code to execute even if its not relevant to the page? i imagine each line of code in the external script gets executed when the dom is ready..? or is my understanding wrong?
will each page be slower overall if there is more code to execute even if its not relvamnt to the page?
The external script file may have some overheads for loading, but if you use the script on any number of pages more than one, external is a good idea; it'll be cached and be instant.
i imagine each line of code in the external script gets executed when the dom is ready..? or is my understanding wrong?
Yes. If you wrap your code in a function as an argument to $(document).ready() it gets executed on DOMContentLoaded.
If you put all your bits of JS into one page - without calling them as functions- then, yes, they will get executed everytime you include them. It would be better to put the common functions into an external scripts and keep it there. This will increase your code reuse as well as speed up page load because your JS will be cached.
I've been trying to get this sorted all day, but really cant figure it out. I've got a page with <div id="ajax"></div> that is populated off a tab based menu pulling in a snippet of HTML code stored in an external file that contains some javascript (mainly form validation).
I've seen in places that I need to eval() the code, but then on the same side of things people say its the last thing to do.
Can someone point me in the right direction, and provide an example if possible as I am very new to jQuery / JavaScript.
Many thanks :)
pulling in a snippet of HTML code stored in an external file that contains some javascript (mainly form validation).
Avoid doing this. Writing <script> to innerHTML doesn't cause the script to get executed... though moving the element afterwards can cause it to get executed, at different times in different browsers.
So it's inconsistent in practice, and it doesn't really make any sense to include script anyway:
when you load the same snippet twice, you'd be running the same script twice, which might redefine some of the functions or variables bound to the page, which can leave you in extremely strange and hard-to-debug situations
non-async/defer scripts are expecting to run at parse time, and may include techniques which can't work when inserted into an existing document (in the case of document.write this typically destroys the whole page, a common symptom when trying to load third-party ad/tracking scripts).
Yes, jQuery tries to make some of this more consistent between browsers by extracting script elements and executing them. But no, it doesn't manage to fix all cases (and in principle can't). So don't ask it to. Keep your scripts static, and run any binding script code that needs to happen in the load callback.
If you fetch html via Ajax, and that html has a <script> tag in it, and you write that html into your document via something like $('#foo').append(html), then the JS should run immediately without any hassle whatsoever.
jquery automatically processes scripts received in an ajax request when adding the content to your page. If you are having a particular problem then post some code.
See this link
dataType: "html" - Returns HTML as
plain text; included script tags are
evaluated when inserted in the DOM.