I'm using window.onload to call my JavaScript code that has to be executed after the page is fully loaded. From what I read this is the recommended method to call such scripts.
However, it does not work for some Ajax sites, www.bing.com for instance - window.onload is called before the pages is fully rendered.
Any suggestions?
The short answer is that there's no general way to solve this problem (right now).
The definition of a "page" is pretty fungible when AJAX comes into play - it's pretty hard to tell the difference between AJAX that is intended to be part of the initial page load, and that which might not be. So, browsers are left on their own to determine when window.onload() should be fired, and it doesn't always end up where you want.
Luckily, most people don't need a general solution, but rather a specific one. If you care about bing.com, then you can probably look at how bing.com works and design your code to trigger when the site reaches a state that you find acceptable.
I've wrestled with this a couple of times, and my usual reason for needing some sort of onload event triggering is to be able to interact with the HTML DOM and have getElementById, getElementsByTagName, or various other DOM selectors, return something other than nothing.
Once again, I'm not sure of the exact problem you are trying to solve, but if you must use some sort of onload, and it's because of DOM traversal of some kind, you can cheat a bit with the following sort of code:
window.onload = pageChecker;
function pageChecker() {
// Onload has fired, but we don't trust it.
// Check to see if our deepest nested element can be found
var testElement = document.getElementById("importantElement");
if ( !testElement ) {
// Try again in a bit, adjust timeout to what your users
// can tolerate
setTimeout(pageChecker, 50);
}
else {
//
// ... the element exists, run important code below ...
//
}
}
You might be able to listen for changes and requests by globally listening for DOMSubtreeModified and readystatechange events, and use those events instead of the load event for whatever you are trying to do.
I'd recomend using jQuerys $(...).ready() method.
In addition to the answer from Rasmus Kaj and if you are using jQuery, I would direct you to take a look at Global Ajax Event Handlers.
jQuery.fn.ajaxComplete (http://api.jquery.com/ajaxcomplete/)
jQuery.fn.ajaxStop (http://api.jquery.com/ajaxstop/)
Note, that this has nothing to do with the native onload event.
Related
I have been struggling with choosing unobtrusive javascript over defining it within the html syntax. I want to convince my self to go the unobtrusive route, but I am having trouble getting past the issues listed below. Can you please help convince me :)
1) When you bind events unobtrusively, there is extra overhead on the client's machine to find that html element, where as when you do stuff, you don't have to iterate the DOM.
2) There is a lag between when events are bound using document.ready() (jquery) and when the page loads. This is more apparent on very large sites.
3) If you bind events (onclick etc) unobtrusively, there is no way of looking at the html code and knowing that there is an event bound to a particular class or id. This can become problematic when updating the markup and not realizing that you may be effecting javascript code. Is there a naming convention when defining css elements which are used to bind javascript events (i have seen ppl use js_className)
4) For a site, there are different pieces of javascript for different pages. For example Header.html contains a nav which triggers javascript events on all pages, where as homepage.html and searchPage.html contains elements that trigger javascript on their respective pages
sudo code example:
header.html
<script src="../myJS.js"></script>
<div>Header</div>
<ul>
<li>nav1</li><li>nav2</li>
</ul>
homepage.html
<#include header.html>
<div class="homepageDiv">some stuff</div>
searchpage.html
<#include header.html>
<div class="searchpageDiv">some other stuff</div>
myJS.js
$(document).ready(function(){
$("ul.li").bind("click",doSomething());
$(".homePageDiv").bind("click",doSomethingElse());
$(".searchPageDiv").bind("click",doSomethingSearchy());
});
In this case when you are on the searchPage it will still try to look for the "homepageDiv" which does not exist and fail. This will not effect the functionality but thats an additional unnecessary traversal. I could break this up into seperate javascript files, but then the browser has to download multiple files, and I can't just serve one file and have it cached for all pages.
What is the best way to use unobtrusive javascript so that I could easily maintain a ( pretty script heavy) website, so another developer is aware of scripts being bound to html elements when they are modifying my code. And serve the code so that the client's browser is not looking for elements which do not exist on a particular page (but may exist on others).
Thanks!
You are confused. Unobtrusive JavaScript is not just about defining event handlers in a program. It's a set of rules for writing JavaScript such that the script doesn't affect the functionality of other JavaScript on the same page. JavaScript is a dynamic language. Anyone can make changes to anything. Thus if two separate scripts on the same page both define a global variable add as follows, the last one to define it will win and affect the functionality of the first script.
// script 1
var add = function (a, b) {
return a + b;
};
// script 2
add = 5;
//script 1 again
add(2, 3); // error - add is a number, not a function
Now, to answer your question directly:
The extra overhead to find an element in JavaScript and attach an event listener to it is not a lot. You can use the new DOM method document.querySelector to find an element quickly and attach an event listener to it (it takes less than 1 ms to find the element).
If you want to attach your event listeners quickly, don't do it when your document content loads. Attach your event listeners at the end of the body section or directly after the part of your HTML code to which you wish to attach the event listener.
I don't see how altering the markup could affect the JavaScript in any manner. If you try to attach an event listener to an element that doesn't exist in JavaScript, it will silently fail or throw an exception. Either way, it really won't affect the functionality of the rest of the page. In addition, a HTML designer really doesn't need to know about the events attached any element. HTML is only supposed to be used for semantic markup; CSS is used for styling; and JavaScript is used for behavior. Don't mix up the three.
God has given us free will. Use it. JavaScript supports conditional execution. There are if statements. See if homePageDiv exists and only then attach an event listener to it.
Try:
$(document).ready(function () {
$("ul.li").bind("click",doSomething());
if (document.querySelector(".homePageDiv")) {
$(".homePageDiv").bind("click",doSomethingElse());
} else {
$(".searchPageDiv").bind("click",doSomethingSearchy());
}
});
Your question had very little to do with unobtrusive JavaScript. It showed a lack of research and understanding. Thus, I'm down voting it. Sorry.
Just because jQuery.ready() executes does not mean that the page is visible to the end user. This is a behaviour defined by browsers and these days there are really 2 events to take into consideration here as mootools puts it DomReady vs Load. When jQuery executes the ready method it's talking about the dom loading loaded however this doesn't mean the page is ready to be viewed by the user, external elements which as pictures and even style sheets etc may still be loading.
Any binding you do, even extremely inefficient ones will bind a lot faster than all the external resources being loaded by the browser so IMHO user should experience no difference between the page being displayed and functionality being made available.
As for finding binding on elements in your DOM. You are really just fearing that things will get lost. This has not really been my actual experience, more often than not in your JS you can check what page you are on and only add javascript for that page (as Aadit mentioned above). After that a quick find operation in your editor should help you find anything if stuff gets lost.
Keep in mind that under true MVC the functionality has to be separate from the presentation layer. This is exactly what OO javascript or unobtrusive javascript is about. You should be able to change your DOM without breaking the functionality of the page. Yes, if you change the css class and or element id on which you bind your JS will break, however the user will have no idea of this and the page will at least appear to work. However if this is a big concern you can use OO-Javascript and put div's or span's as placeholders in your dom and use these as markers to insert functionality or tell you that it exists, you can even use html comments. However, in my experience you know the behavior of your site and hence will always know that there is some JS there.
While I understand most of your concerns about useless traversals, I do think you are nickle and dime'ing it at this point if you are worried about 1 additional traversal. Previous to IE8 it used to be the case that traversing with the tag name and id was a lot faster than my selector but this is no longer true infact browsers have evolved to be much faster when using just the selectors:
$("a#myLink") - slowest.
$("a.myLink") - faster.
$("#Link") - fastest.
$(".myLink") - fastest.
In the link below you can see that as many as 34 thousand operations per second are being performed so I doubt speed is an issue.
You can use firebug to test the speed of each in the case of a very large dom.
In Summary:
a) Don't worry about losing js code there is always ctrl+f
b) There is no lag because dom ready does not mean the page is visible to start with.
Update
Fixed order of speed in operations based on the tests results from here
However keep in mind that performances of IE < 8 are really had if you don't specify the container (this used to be the rule, now it seems to be the exception to the rule).
I want to call some JS after the page-load, this may involve a delay and as such I want the page loaded first so content is shown... but it seems that the code in onLoad handler is called before the rendering is complete. Is there a better event I can use, which is triggered when the page is 'finished'?
To clarify, I want to run some JS after the page is rendered on-screen, so a 'post-everything event' really.
There are several points of interest along the time sequence. This generic sequence is a good overview, even though different browsers and versions implement the details a little differently. (This assumes you're using raw Javascript and need to minimize cross-browser issues; with it's comprehensive internal handling of cross-browser issues JQuery is a little different):
T0] page-begun-- The browser has started working on the page, but otherwise the environment is in flux. Your JS operations may occur in the wrong context, and simply be flushed away when the right context stabilizes. You probably don't want to try to execute any JS at all.
T1] "onLoad" event-- [however you get events: addEventListener("Load"..., window.onload=..., etc.] All parts of the page have been identified and downloaded from the server and are in the local system's memory. In order for all parts to be identified, some parsing has already occurred. (Note that "load" is a cognate of "download", not "parse" nor "render".)
You now have the right environment and can begin to execute JS code without fear of losing anything. HOWEVER, operations that try to read or manipulate the HTML [getElementById(..., appendChild(..., etc.] may fail in strange ways, or may appear to work but then disappear, or may do something different than you expected.
T2] DOM-almost-ready-- This hack is very simple and fully cross browser. Just put your JS <script>...</script> at the very end of your HTML, just before the </body> tag. Most things will work right, although attempts to append to or modify the DOM at the very end of the <body> may produce surprising results. This isn't fully correct, but it works 99% of the time. Given its simplicity and the very high probability of correct operation, this may be the way to go (at least if you don't use JQuery).
T3] DOM-ready-- [however you get events: addEventListener("DOMContentLoaded"..., window.ondomcontentloaded=..., etc.] At this point the HTML has been completely parsed and JS is 100% available, including all functions that read or manipulate the HTML [getElementById(..., appendChild(..., etc.].
T4] Render-done-- The browser is finished displaying the content on the screen. There is NOT any such event or any reasonable cross-browser version-agnostic way to detect this situation. That's just as well, as you probably don't really want this anyway. If the browser has already displayed the page on the screen and then you manipulate the DOM, you'll get a "flash", where both the before and the after are visible on the screen at least briefly. What you probably really want is the point where you can execute arbitrary JS code; that's the previous (T3] DOM-ready) point in time.
Either attach a callback to window.onload
window.onload = function(){
// your code here
};
this will fire when all resources are loaded (which might be not what you want).
Or put all of your code at the bottom the page (before the closing body tag). The code will be run when the HTML is parsed.
FWIW, here is the jQuery code. You see, the use custom event handlers for IE and the other browsers, but use window.onload as fallback:
// Mozilla, Opera and webkit nightlies currently support this event
if ( document.addEventListener ) {
// Use the handy event callback
document.addEventListener( "DOMContentLoaded", DOMContentLoaded, false );
// A fallback to window.onload, that will always work
window.addEventListener( "load", jQuery.ready, false );
// If IE event model is used
} else if ( document.attachEvent ) {
// ensure firing before onload,
// maybe late but safe also for iframes
document.attachEvent("onreadystatechange", DOMContentLoaded);
// A fallback to window.onload, that will always work
window.attachEvent( "onload", jQuery.ready );
// If IE and not a frame
// continually check to see if the document is ready
var toplevel = false;
try {
toplevel = window.frameElement == null;
} catch(e) {}
if ( document.documentElement.doScroll && toplevel ) {
doScrollCheck();
}
}
As with a lot of JavaScript this will depend on which browser you are using.
As #Avitus' answer, have you looked at the execution point of JQuery's document ready event? This has been generalised across all browsers.
If you plan on using a javascript library (like jQuery) I would rather go with the $(document).ready() statement which is called once the DOM is ready to be manipulated.
The other option I see would be to include your function call at the end of your HTML page so that all the HTML content would be loaded so you can afterward execute your code safely
"The onload event waits for all binary content to download before firing. No kitty-tickilng until then."
As this post says, it is called after all binary content is downloaded, you need to listen for a ready event either using jQuery's ready, or your own function. This project looks interesting.
There are many cross browser implementations so use either jQuery or that project I linked to.
I have written my own function for my library, it uses internal methods so will not work on its own but might give you a feel for what you have to do. You can find that function here.
I have a page where I import most of my js in the <head> section.
The main page, however, has some inline JavaScript.
In one of my scripts imported in the <head>, I have $(document).ready... which uses some JS variables defined inline on the page.
As far as I can see, this works as I would expect -- $(document).ready doesn't run into any errors using the JS vars defined inline.
However, I have gotten some reports from users that they see " is not defined" error in code inside my $(document).ready where I reference the variable defined inline on the page.
I suspect this is browser specific behavior, but I'm not sure. I didn't find this explicitly in documentation, so can someone confirm that it's OK to use variables defined inline on a page in $(document).ready in JS pulled in from the <head>?
Citing your source would make your answer more credible. :)
Under the hood: $(document).ready()
As you would expect from John Resig, jQuery’s method for determining when the DOM is ready uses an assortment of optimizations.
For example, if a browser supports the DOMContentLoaded event (as many non-IE browsers do), then it will fire on that event. However, IE can’t safely fire until the document’s readyState reaches “complete”, which is typically later.
If none of those optimizations are available, window.onload will trigger the event
Source: here
Why not place a "script" tag at the end of the "body" tag that starts your app.
By doing this you are sure everything is in place to start (cross browser and cross library).
<html>
...
<script>startApp('param');</script>
</body>
</html>
"startApp" being a function defined anywhere before, either inline or in the "head" tag.
$(document).ready() uses an assortment of different methods for different browsers. Not too many browsers agree on how to handle this event, so there are a number of ways of testing it. I'm pretty sure the jQuery implementation, at least the IE hack, depends on inserting a fragment and checking it for the doScroll("left") property that should only exist once the DOM is ready. It's an undocumented behavior that could change with newer versions of IE.
The purpose of $(document).ready() is to allow you to run your DOM-dependent JavaScript before the page is finished loading, since window.onload doesn't fire until the entire page has been loaded.
The varying implementations also have varying levels of reliability. It sounds like one of the browsers is firing the script before it finishes loading.
Keep in mind that the order in which inline JavaScript is fired is not necessarily before ready. It sounds to me like you should consolidate your inline scripts into your $(document).ready() callback. jQuery does its best, but it's not perfect.
Your inline JS may not be processed before the event fires, as different browsers will work differently. You can assume that your ready function will start as soon as it can, but it is not known exactly when it will start.
You shouldn't assume that the ready function will start quickly, so you may need to write your inline logic to wait until this function has ran.
In javascript, coding to assumptions of when things will happen is risky, just as assuming that the property or function you want to use exists is risky, so you need to code very defensively.
You may want to put the inline javascript into functions and have it called from the ready function, or at least flip some flag to let the inline code know that it is now safe to run.
This will delay you by a tiny bit, most likely, but it will lead to a better user experience, I expect.
I see 2 main ways to set events in JavaScript:
Add an event directly inside the tag like this:
do foo
Set them by JavaScript like this:
<a id="bar" href="">do bar</a>
and add an event in a <script> section inside the <head> section or in an external JavaScript file, like that if you're using prototypeJS:
Event.observe(window, 'load', function() {
$('bar').observe('click', doBar);
}
I think the first method is easier to read and maintain (because the JavaScript action is directly bound to the link) but it's not so clean (because users can click on the link even if the page is not fully loaded, which may cause JavaScript errors in some cases).
The second method is cleaner (actions are added when the page is fully loaded) but it's more difficult to know that an action is linked to the tag.
Which method is the best?
A killer answer will be fully appreciated!
I think the first method is easier to read and maintain
I've found the opposite to be true. Bear in mind that sometimes more than one event handler will be bound to a given control.
Declaring all events in one central place helps to organize the actions taking place on the site. If you need to change something you don't have to search for all places making a call to a function, you simply have to change it in one place. When adding more elements that should have the same functionality you don't have to remember to add the handlers to them; instead, it's often enough to let them declare a class, or even not change them at all because they logically belong to a container element of which all child elements get wired to an action. From an actual code:
$$('#itemlist table th > a').invoke('observe', 'click', performSort);
This wired an event handler to all column headers in a table to make the table sortable. Imagine the effort to make all column headers sortable separately.
In my experience, there are two major points to this:
1) The most important thing is to be consistent. I don't think either of the two methods is necessarily easier to read, as long as you stick to it. I only get confused when both methods are used in a project (or even worse on the same page) because then I have to start searching for the calls and don't immediately know where to look.
2) The second kind, i.e. Event.observe() has advantages when the same or a very similar action is taken on multiple events because this becomes obvious when all those calls are in the same place. Also, as Konrad pointed out, in some cases this can be handled with a single call.
I believe the second method is generally preferred because it keeps information about action (i.e. the JavaScript) separate from the markup in the same way CSS separates presentation from markup.
I agree that this makes it a little more difficult to see what's happening in your page, but good tools like firebug will help you with this a lot. You'll also find much better IDE support available if you keep the mixing of HTML and Javascript to a minimum.
This approach really comes into its own as your project grows, and you find you want to attach the same javascript event to a bunch of different element types on many different pages. In that case, it becomes much easier to have a single pace which attaches events, rather than having to search many different HTML files to find where a particular function is called.
You can also use addEventListener (not in IE) / attachEvent (in IE).
Check out: http://www.quirksmode.org/js/events_advanced.html
These allow you to attach a function (or multiple functions) to an event on an existing DOM object. They also have the advantage of allowing un-attachment later.
In general, if you're using a serious amount of javascript, it can be useful to make your javascript readable, as opposed to your html. So you could say that onclick=X in the html is very clear, but this is both a lack of separation of the code -- another syntactic dependency between pieces -- and a case in which you have to read both the html and the javascript to understand the dynamic behavior of the page.
My personal preference is to use jQuery in external js files so the js is completely separate from the html. Javascript should be unobtrusive so inline (ie, the first example) is not really the best choice in my opinion. When looking at the html, the only sign that you are using js should be the script includes in the head.
An example of attaching (and handling) events might be something like this
var myObject = {
allLinkElements: null,
init: function()
{
// Set all the elements we need
myObject.setElements();
// Set event handlers for elements
myObject.setEventHandlers();
},
clickedLink: function()
{
// Handle the click event
alert('you clicked a link');
},
setElements: function()
{
// Find all <a> tags on the page
myObject.allLinkElements = $('a');
// Find other elements...
},
setEventHandlers: function()
{
// Loop through each link
myObject.allLinkElements.each(function(id)
{
// Assign the handler for the click event
$(this).click(myObject.clickedLink);
});
// Assign handlers for other elements...
}
}
// Wait for the DOM to be ready before initialising
$(document).ready(myObject.init);
I think this approach is useful if you want to keep all of your js organised, as you can use specific objects for tasks and everything is nicely contained.
Of course, the huge benefit of letting jQuery (or another well known library) do the hard work is that cross-browser support is (largely) taken care of which makes life much easier
Libraries like YUI and jQuery provide methods to add events only once the DOM is ready, which can be before window.onload. They also ensure that you can add multiple event handlers so that you can use scripts from different sources without the different event handlers overwriting each other.
So your practical choices are;
One. If your script is simple and the only one that will ever run on the page, create an init function like so:
window.onload = function () {
init();
}
function init() {
// actual function calls go here
doFoo();
}
Two. If you have many scripts or plan to mashup scripts from different sources, use a library and its onDOMReady method to safely add your event handlers
I have a javascript function that manipulates the DOM when it is called (adds CSS classes, etc). This is invoked when the user changes some values in a form. When the document is first loading, I want to invoke this function to prepare the initial state (which is simpler in this case than setting up the DOM from the server side to the correct initial state).
Is it better to use window.onload to do this functionality or have a script block after the DOM elements I need to modify? For either case, why is it better?
For example:
function updateDOM(id) {
// updates the id element based on form state
}
should I invoke it via:
window.onload = function() { updateDOM("myElement"); };
or:
<div id="myElement">...</div>
<script language="javascript">
updateDOM("myElement");
</script>
The former seems to be the standard way to do it, but the latter seems to be just as good, perhaps better since it will update the element as soon as the script is hit, and as long as it is placed after the element, I don't see a problem with it.
Any thoughts? Is one version really better than the other?
The onload event is considered the proper way to do it, but if you don't mind using a javascript library, jQuery's $(document).ready() is even better.
$(document).ready(function(){
// manipulate the DOM all you want here
});
The advantages are:
Call $(document).ready() as many times as you want to register additional code to run - you can only set window.onload once.
$(document).ready() actions happen as soon as the DOM is complete - window.onload has to wait for images and such.
I hope I'm not becoming The Guy Who Suggests jQuery On Every JavaScript Question, but it really is great.
I've written lots of Javascript and window.onload is a terrible way to do it. It is brittle and waits until every asset of the page has loaded. So if one image takes forever or a resource doesn't timeout until 30 seconds, your code will not run before the user can see/manipulate the page.
Also, if another piece of Javascript decides to use window.onload = function() {}, your code will be blown away.
The proper way to run your code when the page is ready is wait for the element you need to change is ready/available. Many JS libraries have this as built-in functionality.
Check out:
http://docs.jquery.com/Events/ready#fn
http://developer.yahoo.com/yui/event/#onavailable
Definitely use onload. Keep your scripts separate from your page, or you'll go mad trying to disentangle them later.
Some JavaScript frameworks, such as mootools, give you access to a special event named "domready":
Contains the window Event 'domready', which will execute when the DOM has loaded. To ensure that DOM elements exist when the code attempting to access them is executed, they should be placed within the 'domready' event.
window.addEvent('domready', function() {
alert("The DOM is ready.");
});
window.onload on IE waits for the binary information to load also. It isn't a strict definition of "when the DOM is loaded". So there can be significant lag between when the page is perceived to be loaded and when your script gets fired. Because of this I would recommend looking into one of the plentiful JS frameworks (prototype/jQuery) to handle the heavy lifting for you.
#The Geek
I'm pretty sure that window.onload will be called again when a user hits the back button in IE, but doesn't get called again in Firefox. (Unless they changed it recently).
In Firefox, onload is called when the DOM has finished loading regardless of how you navigated to a page.
While I agree with the others about using window.onload if possible for clean code, I'm pretty sure that window.onload will be called again when a user hits the back button in IE, but doesn't get called again in Firefox. (Unless they changed it recently).
Edit: I could have that backwards.
In some cases, it's necessary to use inline script when you want your script to be evaluated when the user hits the back button from another page, back to your page.
Any corrections or additions to this answer are welcome... I'm not a javascript expert.
My take is the former becauase you can only have 1 window.onload function, while inline script blocks you have an n number.
onLoad because it is far easier to tell what code runs when the page loads up than having to read down through scads of html looking for script tags that might execute.