Ben Nadel
On User Experience (UX) Design, JavaScript, ColdFusion, Node.js, Life, and Love.
I am the chief technical officer at InVision App, Inc - a prototyping and collaboration platform for designers, built by designers. I also rock out in JavaScript and ColdFusion 24x7.
Meanwhile on Twitter
Loading latest tweet...
Ben Nadel at cf.Objective() 2010 (Minneapolis, MN) with:

When Does jQuery Consider The DOM Loaded And Can I Use It Beforehand

By Ben Nadel on

I really like the idea of making your XHTML markup very structure and informative and letting the Javascript events be bound via jQuery once the DOM has loaded. But, to be honest, that is not always something that is practical. Think about a navigational system; I don't want the document to finish loading to make sure that navigation-related Javascript works.

Before I get ahead of myself, I decided to figure out when the jQuery framework things the DOM is fully loaded. This should be when all the HTML has been sent to the client, but, worth a check. Here is a rather simple test that forces the thread to sleep right before it is done:

  • <html>
  • <head>
  • <title>jQuery Document Ready Test</title>
  • <script type="text/javascript" src="jquery-latest.pack.js"></script>
  • <script type="text/javascript">
  •  
  • // When the document loads, write the date to the
  • // paragraph with ID "end".
  • $(
  • function(){
  • $( "#end" ).text(
  • (new Date()).toString()
  • );
  • }
  • );
  •  
  • </script>
  • </head>
  • <body>
  •  
  • <h1>
  • JQuery Document Ready Test
  • </h1>
  •  
  • <!--- Let Javascript write this one. --->
  • <p id="start">
  • <script type="text/javascript">
  • document.write( new Date() );
  • </script>
  • </p>
  •  
  • <!---
  • Leave this one blank for now. JQuery will fill this
  • one in once the document loads.
  • --->
  • <p id="end">
  •  
  • </p>
  •  
  •  
  • <!---
  • Flush the document to make sure the above Javascript can
  • fire before we force the thread to sleep.
  • --->
  • <cfflush />
  •  
  •  
  • <!--- Sleep the thread for three seconds. --->
  • <cfset objThread = CreateObject(
  • "java",
  • "java.lang.Thread"
  • ).CurrentThread().Sleep(
  • JavaCast( "long", (3 * 1000) )
  • ) />
  •  
  • </body>
  • </html>

Running this page gives us the following output:

JQuery Document Ready Test

Thu Mar 15 2007 14:23:09 GMT-0400 (Eastern Daylight Time)

Thu Mar 15 2007 14:23:12 GMT-0400 (Eastern Daylight Time)

As you can see, the "End" time is three seconds behind the "Start" time. Since we use CFFlush to make sure the first one is written immediately, we can clearly see that jQuery considers the DOM fully loaded once all the HTML has been sent to the client. Good to know, but somewhat obvious.

Well, even though jQuery waits until all the HTML is there to run the $( fn{} ) methods, does that mean we have to wait for the DOM to load before we use jQuery features? In this test, we run a very similar page, but this time, we replace the $( fn{} ) method with an inline jQuery request:

  • <html>
  • <head>
  • <title>jQuery Document Ready Test</title>
  • <script type="text/javascript" src="jquery-latest.pack.js"></script>
  • </head>
  • <body>
  •  
  • <h1>
  • JQuery Document Ready Test
  • </h1>
  •  
  • <!--- Let Javascript write this one. --->
  • <p id="start">
  • <script type="text/javascript">
  • document.write( new Date() );
  • </script>
  • </p>
  •  
  • <!---
  • Leave this one blank for now. JQuery will fill this
  • one in when we ask it to.
  • --->
  • <p id="end">
  •  
  • </p>
  •  
  •  
  • <script type="text/javascript">
  •  
  • // Get jQuery to fill in the "End" paragraph before
  • // the document has finished loading.
  • $( "#end" ).text(
  • (new Date()).toString()
  • );
  •  
  • </script>
  •  
  •  
  • <!---
  • Flush the document to make sure the above Javascript can
  • fire before we force the thread to sleep.
  • --->
  • <cfflush />
  •  
  •  
  • <!--- Sleep the thread for three seconds. --->
  • <cfset objThread = CreateObject(
  • "java",
  • "java.lang.Thread"
  • ).CurrentThread().Sleep(
  • JavaCast( "long", (3 * 1000) )
  • ) />
  •  
  • </body>
  • </html>

Running this page gives us the output:

JQuery Document Ready Test

Thu Mar 15 2007 14:26:07 GMT-0400 (Eastern Daylight Time)

Thu Mar 15 2007 14:26:07 GMT-0400 (Eastern Daylight Time)

As you can see, the Javascript write and the jQuery text() method fire one directly after the other. The thread sleep directly after that proves that we can access jQuery features before the DOM is fully loaded.

Now, is it a good idea to do this? Not sure. I think that as long as all the DOM elements in question have already been loaded then you are totally fine. I could certainly imagine doing this some sort of "Ready" function right before the primary page content loads to make sure all pre-primary-content Javascript is hooked up in case the content itself takes a long time to render.



Reader Comments

Interesting find. This is particular useful for code that as to run before the user can see anything at all, especially when the page isn't send to the browser in one big chunk.

On the other hand, I can hardly imagine a case where adding event handlers like clicks to the document can't wait for jQuerys DOM ready event.

I'll give it a try on a particular problem that could benefit from a faster "DOM ready".

Reply to this Comment

I am not worried about the "everyday" examples. But, if you have a site that has constant navigation and variable content (pretty much every site), you have to imagine that every now and then you might have a page whose content takes a long time to load (maybe someone wrote a crappy query and they are accidentally building a table that has 1000 rows)... whatever the reason, as long as it is always works, it seems like a good safety measure to apply events as they can be....

Of course, I am just getting into jQuery, so I might be way off.

Reply to this Comment

Hey Ben, I'm glad Joern chimed in here. He's the team lead for the jQuery Core team so definitely offer him up feedback. He's a cool dude and an awesome developer.

Reply to this Comment

I know of lots of pages that load the header and then access some slow ass content management system and take forever to load up the rest of the content.

There is nothing wrong with having the jQuery to bind the nav included after the nav loads.

The rules are exactly like regular javascript. You cant access something that doesn't exist. Once it's in the DOM then you can play with it.

If you know it might be a delay between NAV and rest of page, then for sure, bind those suckers early.

Glen

Reply to this Comment

Even with the early bind, I think it is still a hell of a lot more elegant that have a lot of JS wrapped up in the HTML markup.

Reply to this Comment

I actually thought of this while sleeping. It probably has bugs, I havent tested, but its in the ballpark.

Assuming you had your header in an include file....the include should look something like:

<ul class="header">
<li title="Home">Home</li>
<li title="Products">Products</li>
<li title="Support">Support</li>
<ul>

Then on the specific page: (let's say the product page)
<inlude header.cfm>
<script>
initHeader("Products");
<script>

Then the jQuery would be:
function initHeader(target) {
var onLink = $("ul.header li[@title=" + target + "]")
$(onlink).addClass("on");

$("ul.header li").hover(function(){
$(this).addClass("hover");
},function(){
$(this).removeClass("hover");
});

$("ul.header li").bind("click", function(){
navigate()
});

}

function navigate() {
var newPage = $(this).attr("title");
location.href = "http://www.mysite.com/" + newPage + ".cfm"
}

Anyway, the point is, the header wouldn't have any information about anything.

Reply to this Comment

I would feel OK doing something like that. I think it still keeps the HTML very clean.

My only nit-picky thing (and I know this was just a demo), but I wouldn't bind click events for navigation. Navigational links should probably exist with or without Javascript.

Reply to this Comment

CreateObject(
"java",
"java.lang.Thread"
).CurrentThread().Sleep(
JavaCast( "long", (3 * 1000) )
)

should be written as

CreateObject(
"java",
"java.lang.Thread"
).Sleep(
JavaCast( "long", (3 * 1000) )
)

since sleep is a static method

Reply to this Comment

Post A Comment

?
You — Get Out Of My Dreams, Get Into My Comments
Live in the Now
Oops!
Comment Etiquette: Please do not post spam. Please keep the comments on-topic. Please do not post unrelated questions or large chunks of code. And, above all, please be nice to each other - we're trying to have a good conversation here.