ASP.NET AJAX In-Depth: Application Events

The goal of this blog entry is simple: I want to understand everything about ASP.NET AJAX Application events. I want to know how application events work under the covers by performing a close examination of the Microsoft AJAX Library source code for the Application object.

The Application object (Sys.Application) supports three application lifecycle events:

· init – You use this event to initialize client components, controls, and behaviors

· load – You use this event to kick-off your application’s client-side code

· unload – You use this event to free up resources used by your application

There are two more lifecycle events that are not really events. If your application includes a pageLoad() function, then this function is called immediately after the Application.load event. If your application includes a pageUnload() function, then this function is called immediately before the Application.unload event.

The page in Listing 1 illustrates when each of these events happen:

Listing 1 – ApplicationEvents.aspx

   1:  <%@ Page Language="C#" %>
   2:  <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
   3:  <html xmlns="http://www.w3.org/1999/xhtml">
   4:  <head runat="server">
   5:      <title>Application Events</title>
   6:  </head>
   7:  <body>
   8:      <form id="form1" runat="server">
   9:      <div>
  10:          <asp:ScriptManager ID="ScriptManager1" runat="server" />
  11:      </div>
  12:      </form>
  13:   
  14:      <script type="text/javascript">
  15:      
  16:        // handle Application init
  17:        Sys.Application.add_init( function() { alert("init!") } );
  18:        
  19:        // handle Application load
  20:        Sys.Application.add_load( function() { alert("load!") } );
  21:        
  22:        // show pageLoad()
  23:        function pageLoad() { alert("pageLoad!"); }
  24:   
  25:        // show pageLoad()
  26:        function pageUnload() { alert("pageUnload!"); }
  27:   
  28:        // handle Application unload
  29:        Sys.Application.add_unload( function() { alert("unload!") } );
  30:      
  31:      </script>
  32:   
  33:   
  34:   
  35:  </body>
  36:  </html>

Notice that the JavaScript script in Listing 1 is placed right before the closing HTML BODY tag. If your JavaScript code refers to objects from the Microsoft AJAX Library, then you must place your script after the Microsoft AJAX Library has been loaded. In Listing 1, the Microsoft AJAX Library is loaded by the server-side ScriptManager control.

When you request the page in Listing 1, alerts are displayed in sequence for the Application.init, Application.load events followed by the pageLoad method call. If you refresh/reload the page, you navigate to a new page, or you close your browser, the pageUnload function is called and the Application.unload event is raised.

How Application Events are Raised

So let’s look at the Application events in more detail by examining the Microsoft AJAX Library source code.

The Microsoft AJAX Library defines a private class named Sys._Application. This class defines the nature of an application for the ASP.NET AJAX Framework. A single instance of this class is created for an application automatically with the following line of code:

Sys.Application = new Sys._Application();

The Application.load and Application.unload events are wired-up in the constructor function for the _Application object. The constructor function includes the following two lines of code:

Sys.UI.DomEvent.addHandler(window, "load", this._loadHandlerDelegate);

Sys.UI.DomEvent.addHandler(window, "unload", this._unloadHandlerDelegate);

The first line of code wires-up the Application.load event to the browser window.load event and the second line of code wires-up the Application.unload event to the browser window.unload event. So browser events drive ASP.NET AJAX Application events.

When the window.load event is raised, the ASP.NET AJAX _loadHandlerDelegate() method is called. This method calls the _Application.initialize() method contained in Listing 2.

Listing 2 – MicrosoftAjax.debug.js _Application.initialize

    function Sys$_Application$initialize() {
        if(!this._initialized && !this._initializing) {
            this._initializing = true;
            window.setTimeout(Function.createDelegate(this, this._doInitialize), 0);
        }
    }

The initialize() method in Listing 2 checks whether the Application has already been initialized, or is currently in the process of being initialized, by checking the private-by-convention _initialized and _initializing properties. Next, the _doInitialize() method is called. Why is the _doInitialize() method called using window.setTimeout? Presumably to work around a browser incompatibility issue (I don’t know anything about the nature of this incompatibility issue).

The important thing to notice here is that an ASP.NET AJAX application can only be initialized once. If you call initialize() multiple times, any call but the first call is ignored.

The _doInitialize() method is contained in Listing 3.

Listing 3 – MicrosoftAjax.debug.js _Application._doInitialize

    function Sys$_Application$_doInitialize() {
        Sys._Application.callBaseMethod(this, 'initialize');
        var handler = this.get_events().getHandler("init");
        if (handler) {
            this.beginCreateComponents();
            handler(this, Sys.EventArgs.Empty);
            this.endCreateComponents();
        }
        this.raiseLoad();
        this._initializing = false;
    }

In the context of this blog entry, the _doInitialize() method is the most important method. This method performs the following actions:

1. Raises the Application init event

2. Raises the Application load event

3. Marks the Application as initialized

Let’s look at step 2 in more detail. The Application load event is raised by calling the raiseLoad() method. The raiseLoad() method is contained in Listing 4.

Listing 4 – MicrosoftAjax.debug.js _Application.raiseLoad

    function Sys$_Application$raiseLoad() {
        var h = this.get_events().getHandler("load");
        var args = new Sys.ApplicationLoadEventArgs(Array.clone(this._createdComponents), !this._initializing);
        if (h) {
            h(this, args);
        }
        if (window.pageLoad) {
            window.pageLoad(this, args);
        }
        this._createdComponents = [];
    }

The raiseLoad() method in Listing 4 raises the Application.load event and calls the pageLoad() function (if it exists) contained in your page. This method creates a new ApplicationLoadEventArgs object and passes this object to any Application.load handlers and the pageLoad function. The ApplicationLoadEventArgs object has two properties:

· Components – This property returns an array of all of the components (including all controls and behaviors) created during the Application.init event

· isPartialLoad -- This property indicates whether the Application load event is happening during Application initialization or whether the Application load event was raised by calling Application.raiseLoad() after the Application was already initialized.

The second parameter requires explanation. Typically, the Application.load event is raised only once. However, if you have an UpdatePanel in a page, then the Application.load event is raised whenever an UpdatePanel updates. For example, the page in Listing 5 contains an UpdatePanel that updates its content every 5 seconds:

Listing 5 – ShowUpdatePanel.aspx

   1:  <%@ Page Language="C#" %>
   2:  <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
   3:  <html xmlns="http://www.w3.org/1999/xhtml">
   4:  <head runat="server">
   5:      <title>Show UpdatePanel</title>
   6:      <script type="text/javascript">
   7:      
   8:        function pageLoad(source, args) 
   9:        {
  10:          alert( "isPartialLoad=" + args.get_isPartialLoad() );
  11:        }
  12:      
  13:      </script>
  14:  </head>
  15:  <body>
  16:      <form id="form1" runat="server">
  17:      <div>
  18:          <asp:ScriptManager ID="ScriptManager1" runat="server" />
  19:   
  20:          <asp:UpdatePanel ID="up1" runat="server">
  21:          <ContentTemplate>
  22:          
  23:              <asp:Timer Interval="5000" runat="server" />
  24:          
  25:              <%= DateTime.Now %>
  26:              
  27:          </ContentTemplate>
  28:          </asp:UpdatePanel>
  29:   
  30:      </div>
  31:      </form>
  32:  </body>
  33:  </html>

The page in Listing 5 includes a pageLoad() method that displays the value of the isPartialLoad property in a browser alert box. When you first request the page, the pageLoad() method is called during Application initialization and the pageLoad() method displays the value false for the isPartialLoad property. However, the page contains an UpdatePanel that refreshes its content every 5 seconds (there is a Timer control in the UpdatePanel that causes the UpdatePanel to refresh). Every 5 seconds, the pageLoad() method is called again and displays the value true.

So that’s it for the Application initialization process. Let’s turn now to an examination of what happens when an ASP.NET AJAX Application unloads.

Remember that the Application constructor function includes the following line of code that wires-up the ASP.NET AJAX Application.unload event to the browser window.unload event:

Sys.UI.DomEvent.addHandler(window, "unload", this._unloadHandlerDelegate);

When the window.unload event is raised (when you navigate to a new page, close the browser, or hit refresh/reload), the _unloadhandlerDelegate() method is called. This method, in turn, calls the Application.dispose() method. The dispose() method is contained in Listing 6.

Listing 6 – MicrosoftAjax.debug.js _Application.dispose

    function Sys$_Application$dispose() {
        if (!this._disposing) {
            this._disposing = true;
            if (window.pageUnload) {
                window.pageUnload(this, Sys.EventArgs.Empty);
            }
            var unloadHandler = this.get_events().getHandler("unload");
            if (unloadHandler) {
                unloadHandler(this, Sys.EventArgs.Empty);
            }
            var disposableObjects = Array.clone(this._disposableObjects);
            for (var i = 0, l = disposableObjects.length; i < l; i++) {
                disposableObjects[i].dispose();
            }
            Array.clear(this._disposableObjects);
            Sys.UI.DomEvent.removeHandler(window, "unload", this._unloadHandlerDelegate);
            if(this._loadHandlerDelegate) {
                Sys.UI.DomEvent.removeHandler(window, "load", this._loadHandlerDelegate);
                this._loadHandlerDelegate = null;
            }
            var sl = Sys._ScriptLoader.getInstance();
            if(sl) {
                sl.dispose();
            }
            Sys._Application.callBaseMethod(this, 'dispose');
        }
    }

The dispose() method in Listing 6 does four important things:

1. If you have created a pageUnload() function in your application, that function is called

2. It calls dispose() on any object that have you have registered with the application.

3. It raises the Application unload event.

4. It disposes the ScriptLoader instance.

The second step disposes any objects registered with your application. All ASP.NET AJAX components (including controls and behaviors) are registered automatically with the Application object when the component is created. The component is registered with the Application in the base Sys.Component constructor function. This step is very important to prevent browser memory leaks. Microsoft Internet Explorer (at least the 6.0 version) is very bad at cleaning up after itself.

Why window.load is Bad

The last subject that we need to examine in this blog entry concerns the window.load event. The ASP.NET AJAX Application initialization process is kicked off by this browser event. However, this is bad. The window.load event happens too slowly.

The problem is that the window.load event is not raised until all images are loaded into a browser page. You typically don’t want to wait until all images are loaded before you start to execute your application logic. You want to initialize your application immediately after all of the DOM elements in your page have been parsed.

This problem with the window.load event was brought to everyone’s attention by Dean Edwards (see http://dean.edwards.name/weblog/2006/06/again/). He proposes a complex solution.

Both Firefox and Opera support a DOMContentLoaded event. This browser event is raised after all of the DOM elements in a page have been parsed and before the window.load event. Unfortunately, Microsoft Internet Explorer does not support the DOMContentLoaded event.

Internet Explorer does support a special attribute that can be used with the HTML SCRIPT tag called the DEFER attribute. When DEFER=”true”, an external JavaScript script is not loaded until after all the DOM elements in a page have been parsed. Therefore, you can use the DEFER attribute (in a hacky way) to simulate the DOMContentLoaded event.

Dean Edwards (and friends) recommend the script in Listing 7 for kicking off your application.

Listing 7 – Cross-Browser DOMContentLoaded

   1:  // Dean Edwards/Matthias Miller/John Resig
   2:   
   3:  function init() {
   4:      // quit if this function has already been called
   5:      if (arguments.callee.done) return;
   6:   
   7:      // flag this function so we don't do the same thing twice
   8:      arguments.callee.done = true;
   9:   
  10:      // kill the timer
  11:      if (_timer) clearInterval(_timer);
  12:   
  13:      // do stuff
  14:  };
  15:   
  16:  /* for Mozilla/Opera9 */
  17:  if (document.addEventListener) {
  18:      document.addEventListener("DOMContentLoaded", init, false);
  19:  }
  20:   
  21:  /* for Internet Explorer */
  22:  /*@cc_on @*/
  23:  /*@if (@_win32)
  24:      document.write("&lt;script id=__ie_onload defer src=javascript:void(0)&gt;&lt;\/script&gt;");
  25:      var script = document.getElementById("__ie_onload");
  26:      script.onreadystatechange = function() {
  27:          if (this.readyState == "complete") {
  28:              init(); // call the onload handler
  29:          }
  30:      };
  31:  /*@end @*/
  32:   
  33:  /* for Safari */
  34:  if (/WebKit/i.test(navigator.userAgent)) { // sniff
  35:      var _timer = setInterval(function() {
  36:          if (/loaded|complete/.test(document.readyState)) {
  37:              init(); // call the onload handler
  38:          }
  39:      }, 10);
  40:  }
  41:   
  42:  /* for other browsers */
  43:  window.onload = init;

Dean Edwards has a nice sample page that you can use to try out the script in Listing 7 at http://dean.edwards.name/my/busted3.html. The page loads an image really slowly so you can detect the difference between the DOMContentLoaded and window.load events.

The ASP.NET AJAX framework takes a different approach to handling the window.load problem. If you use the ScriptManager control to load the Microsoft AJAX Library, then the ScriptManager control injects the following script right before the page’s closing </BODY> tag:

<script type="text/javascript">
//<![CDATA[
Sys.Application.initialize();
//]]>
</script>

Since this script appears after (almost) all of the HTML content contained in the page, you know that when this script executes the DOM is (almost) ready. This is a really simple solution to the window.load problem.

This solution causes the Application.initialize() method to be called twice. The initialize() method is called in the page itself and it is called a second time when the window.load event is raised. That’s okay since the initialize() method ensures that the Application.init event is raised only once. After the initialize() method is first called, a field named _initialized is set to the value true. The second call to initialize() is ignored.

If you are not using the ScriptManager control to add the Microsoft AJAX Library to your pages, then you must be careful to do something similar. Personally, I like using the standalone Microsoft AJAX Library because I am interested in building “pure” client-side applications (You can download the standalone Microsoft AJAX Library from http://msdn2.microsoft.com/en-us/asp.net/bb944808.aspx). The page in Listing 8 illustrates one approach to handling the window.load problem with the standalone library.

Listing 8 – LoadStandAlone.aspx

   1:  <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
   2:  <html xmlns="http://www.w3.org/1999/xhtml">
   3:  <head runat="server">
   4:      <title>Load Standalone</title>
   5:  </head>
   6:  <body>
   7:      <div>
   8:   
   9:      <strong>The DOM Content</strong>
  10:      
  11:      </div>
  12:      
  13:      <script type="text/javascript" src="Microsoft/MicrosoftAjax.debug.js"></script>
  14:      <script type="text/javascript" src="Application.js"></script>
  15:      
  16:  </body>
  17:  </html>

The standalone Microsoft AJAX Library contains both a MicrosoftAjax.js and MicrosoftAjax.debug.js file. You should use the former file when an application is in production and the latter file when debugging an application (The ScriptManager control switches between these two files automatically depending on the value of the ScriptManager ScriptMode attribute and your application’s debug settings).

In Listing 8, a <SCRIPT> tag is used to load the debug version of the Microsoft AJAX Library. I’ve copied the MicrosoftAjax.debug.js file into a folder contained in my website named Microsoft. Furthermore, I’ve added a second <SCRIPT> tag to load my custom JavaScript code for my application. My application code is contained in a file named Application.js. The contents of this file are contained in Listing 9.

Listing 9 – Application.js

   1:  /// <reference name="MicrosoftAjax.js"/>
   2:  Sys.Application.initialize();
   3:   
   4:  Sys.Application.add_load( applicationLoad );
   5:   
   6:  function applicationLoad()
   7:  {
   8:      alert("Ready before the images!");
   9:  }

Notice that the first line of code in Listing 9 is a call to the Application.initialize() method. This call causes the ASP.NET Application to initialize before the window.load event so your AJAX application can start executing before all of the images in your page are loaded.

Summary

In this blog entry, I’ve examined the ASP.NET AJAX Application events in detail. There are two main things that need to be emphasized from the discussion above.

First, the Application load event is raised over and over again when using the UpdatePanel. You should be careful about the code that you add to a pageLoad() function or a Application.load event handler since the code might get executed multiple times.

Second, make sure that you initialize your Application as quickly as possible. By default, an ASP.NET AJAX Application won’t initialize until window.load. If you are not using the ScriptManager control, make sure that you initialize your Application earlier.

4 Comments

  • Nice article Stephen!. I have been reading all the articles you have been providing since Feb!. Excellent content. Keep up the great work.

  • Great one, sharpens the details...

  • Most of the application events does not work as expected in FF and Opera. I have detected that Sys.Application.add_load and pageLoad works in FF only on first navigation to the page. On the next navigations load functions are not invoked. Clearing cache resolves the issue.

    I have custom script referenced on the page. Removing script reference resolves the issue. This script contains new type and class declaration. Does anybody knows the root case and how to avoid this issue?

    FYI. In Opera only Sys.UI.DomEvent.addHandler(window, "load"... works as expected. The rest is not working.

  • from what i uenarstndd the n800 isn't a phone but more a web tablet i would get it if i could put a sim in it also uenarstndd that the viewmy.tv widget will run on an iPhone wish i could have been at the conference.

Comments have been disabled for this content.