Note: This post is part of a long-running series of posts covering the union of jQuery and ASP.NET: jQuery for the ASP.NET Developer.

Topics in this series range all the way from using jQuery to enhance UpdatePanels to using jQuery up to completely manage rendering and interaction in the browser with ASP.NET only acting as a backend API. If the post you're viewing now is something that interests you, be sure to check out the rest of the posts in this series.

Recently, I’ve attended several presentations in which ASP.NET AJAX’s pageLoad() shortcut is demonstrated as interchangeable with jQuery’s $(document).ready() event. The suggestion that both methods are equivalent actually appears to be true in simple demos, but is not the case and is certain to lead to later confusion.

While they seem similar on the surface, $(document).ready() and pageLoad() are very different behind the scenes. Determining the earliest point that it’s safe to modify the DOM requires a bit of black magic, and the two libraries approach that in their own unique ways. Additionally, pageLoad() is overloaded with some extra functionality which may surprise you.

In this post, I’ll clarify the major differences between jQuery and ASP.NET AJAX’s initialization functions, what implications those difference have in practice, and show you a third alternative when working with ASP.NET AJAX.


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.

Under the hood: pageLoad()

Instead of targeting browser-specific optimizations, the ASP.NET AJAX pageLoad() shortcut function is called as a result of a more uniform process.

That process is queued up the same way on all browsers, via a call to setTimeout with a timeout of 0 milliseconds. This trick leverages JavaScript’s single-threaded execution model to (theoretically) push the init event back until just after the DOM has finished loading.

Counter-intuitively, this isn’t the only point at which pageLoad() is called. It is also called after every partial postback. It basically functions as a combination of Application.Init and PageRequestManager.EndRequest.

Danger: UpdatePanels ahead

Since pageLoad() is called after every UpdatePanel refresh, the complications that arise can be initially difficult to grasp. For example, you’ll often see code like this:

<script type="text/javascript"> 
  function pageLoad() { 
    // Initialization code here, meant to run once. 
  } 
</script>
 
<asp:ScriptManager runat="server" />
 
<asp:UpdatePanel runat="server"> 
  <ContentTemplate> 
    <asp:Button runat="server" ID="Button1" /> 
    <asp:Literal runat="server" ID="TextBox1" /> 
  </ContentTemplate> 
</asp:UpdatePanel>

That initialization code will execute on the initial load, and things will seem okay at first. However, pageLoad() will then continue to be called each time Button1 is triggered, resulting in the initialization code running more often than intended.

This problem is similar to the classic ASP.NET mistake of forgetting to test for IsPostBack during the server-side Page_Load event. Depending on the nature of your initialization code, you may not even notice that there’s a problem, but it’s bound to catch up with you eventually.

In the case of initialization code that should run once, $(document).ready() is the ideal solution. It will do exactly what you need and nothing more.

Sometimes, pageLoad() is exactly what you want

While $(document).ready() is ideal for one-time initialization routines, it leaves you hanging if you have code that needs to be re-run after every partial postback. The LiveQuery functionality added in jQuery v1.3+ helps with this, but only works for a limited set of functionality.

For example, what if we wanted to add a jQueryUI datepicker to the TextBox in the previous example? Adding it in $(document).ready() would work great, until a partial postback occurred. Then, the UpdatePanel’s new TextBox element would no longer have the datepicker wired up to it. This is exactly where pageLoad() shines:

<script type="text/javascript">
  function pageLoad() {
    $('#TextBox1').unbind();
    $('#TextBox1').datepicker(); 
  }
</script>
 
<asp:ScriptManager runat="server" />
 
<asp:UpdatePanel runat="server">
  <ContentTemplate>
    <asp:Button runat="server" ID="Button1" />
    <asp:TextBox runat="server" ID="TextBox1" />
  </ContentTemplate>
</asp:UpdatePanel>

By attaching in pageLoad(), our TextBox will now have the datepicker attached to it on initial page load, and have it re-attached after every partial postback.

The call to unbind() is optional in this case, but a good precaution on more complex pages. Else, you run the risk of stacking multiple events on elements that were not refreshed as part of the partial postback.

An ASP.NET AJAX alternative to $(document).ready()

The previous sections should make it easier to decide between jQuery and ASP.NET AJAX’s events, but they assume you’re using both frameworks. What if you’re only using ASP.NET AJAX and want functionality similar to $(document).ready()?

Luckily, ASP.NET AJAX does provide a corresponding event. The Application.Init event fires only one time per page load, and is perfect for onetime initialization tasks. It’s not available through a shortcut function and requires slightly more caution, but serves its purpose:

<asp:ScriptManager runat="server" /> 
 
<script type="text/javascript"> 
  Sys.Application.add_init(function() { 
    // Initialization code here, meant to run once.
  }); 
</script>

Note that the call to Application.add_init is placed after the ScriptManager. This is necessary because the ScriptManager injects its reference to MicrosoftAjax.js in that location. Attempting to reference the Sys object before that point will result in a “sys is undefined” JavaScript error.

If you think that limitation is a bit messy, you are not alone. I’m not a big fan of littering my presentation code with any more inline JavaScript than is necessary. To avoid this clutter, you may alternatively include your Application.Init code in an external file, included via a ScriptReference (see the previous link for an example).

Summary (tl;dr)

$(document).ready()

  • Ideal for onetime initialization.
  • Optimization black magic; may run slightly earlier than pageLoad().
  • Does not re-attach functionality to elements affected by partial postbacks.

pageLoad()

  • Unsuitable for onetime initialization if used with UpdatePanels.
  • Slightly less optimized in some browsers, but consistent.
  • Perfect for re-attaching functionality to elements within UpdatePanels.

Application.Init

  • Useful for onetime initialization if only ASP.NET AJAX is available.
  • More work required to wire the event up.
  • Exposes you to the “sys is undefined” error if you aren’t careful.