History Support in the Microsoft AJAX Library

You can implement history in JavaScript by serializing the current state of the page or a control and storing that object as a history point. As a user performs actions, history points are saved in the Sys.Application object. These history points are accessed as the user navigates with the Back or Forward button, allowing the user to return to previous application contexts.

To enable history support in the Sys.Application object, you need to set the ScriptManager control to enable history. The property EnableHistory enables AJAX history support for all modern browsers by using a combination of a hidden IFrame, form actions, the URL, and the browser’s native history API. Because different browsers implement history differently, the actual implementation can vary depending on what browser you’re using, but the interface stays the same. The constant interface is a huge benefit of the AJAX library because you don’t have to worry about developing code for each browser—the fine folks at Microsoft have done this for you and will continue to support future browsers in service packs and future frameworks.

Note

Note

Some modern browsers, including Internet Explorer 8 and Firefox 3.0, support history through a native JavaScript API, but older browsers require intricate programming to implement history support in JavaScript. Fortunately, as browsers continue to evolve, future releases and service packs of the .NET Framework will add support for new browsers using the current API of the Sys.Application object.

History Support with the ScriptManager Control

To support browsing history, a page must have an instance of the ScriptManager server control with EnableHistory set to true. Certain browsers, such as FireFox, might not need this property enabled through the server control, but it is a requirement for Internet Explorer versions 6 and 7. The following ScriptManager instance allows history support, although this example only adds support for history—you have to add history points and handle the navigate event through custom code.

<asp:ScriptManager runat="server" EnableHistory="true" />

When EnableHistory is set in the ScriptManager, the page elements required to support history for the current browser are included, and the method Sys.Application._enableHistory-InScriptManager is added to the page, setting the enableHistory property of the Sys.Application JavaScript object to true.

Tip

Tip

The ScriptManager’s property EnableSecureHistoryState has no effect on a client-side history implementation. It only encrypts history using partial postbacks with the UpdatePanel. To hide history on the client, you need to implement your own client-side encryption or encoding, but this would only obfuscate the history at best.

History Support with the Client-Side Sys.Application Object

Sys.Application includes the following property, event, and method for history support:

  • enableHistory. This property gets or sets history support through the Sys.Application framework. This property isn’t usually set from custom code; instead it is set by the ScriptManager control during page loading. Custom code can detect whether history is enabled by calling the get_enableHistory method. As a standard AJAX library property, enableHistory is accessed through the methods Sys.Application.get_enableHistory and Sys.Application.set_enableHistory(bool).

  • navigate. This event is fired from Sys.Application when a user navigates to a history point. This event is always fired with an instance of Sys.HistoryEventArgs containing the state of the history point. To add or remove an event handler for the navigate event, use the methods Sys.Application.add_navigate(handler) and Sys.Application.remove_navigate(handler).

  • addHistoryPoint. This method is used by custom code to add history points. To add a history point, call the method Sys.Application.addHistoryPoint(state, title), where the state parameter must be a dictionary of strings that is used to serialize the state of the object, and the title parameter is a string used to set the title of the page in the browser’s history.

To add a history point, use the method Sys.Application.addHistoryPoint with a state parameter that includes a dictionary of key-value pairs that identify the current state of the application. The state parameter will be serialized and stored after the hash (#) in the browser’s URL. The following sample serializes three simple values in history, creating three history points that are accessible via the Web browser’s Back and Forward buttons.

var state = { message : "Hello World!" };
Sys.Application.addHistoryPoint(state, "Welcome");

var italianState = { message : "Ciao mondo!" };
Sys.Application.addHistoryPoint(italianState, "Benvenuto");

var frenchState = { message : "Bonjour monde!" };
Sys.Application.addHistoryPoint(frenchState, "Bienvenue");

By creating these history points with Sys.Application, the browser creates three history entries, titled "Welcome," "Benvenuto," and "Bienvenue." When the user clicks Back or Forward, these state objects are sent in the HistoryEventArgs instance to the navigate event handler. The HistoryEventArgs class is a simple class with one property, state. To create a handler for the navigate event, use the standard AJAX library event handler signature:

function handler(sender, eventArgs){}

The eventArgs parameter is an instance of the HistoryEventArgs class. You can then use the state property to access the serialized state. Because any code could have created the entry point (not necessarily the class you’re currently coding in), be sure to check whether it’s a serialized state object that you want to handle. The following code sample demonstrates a handler for the navigate event, adding Visual Studio IntelliSense support to the method body.

function onHistoryNavigate(sender, eventArgs) {
    if (eventArgs == null) {
        eventArgs = new Sys.HistoryEventArgs();
        throw Error.invalidOperation('Expected Sys.HistoryEventArgs'),
    }
    var state = eventArgs.get_state();
    if (state.message) {
        Sys.Debug.trace(state.message);
    }
}

To add the handler to the navigate event, use the add_navigate method as follows:

Sys.Application.add_navigate(onHistoryNavigate);

As an alternative to adding an event handler through JavaScript, you can define a global history event handler in the ScriptManager control by setting the ClientNavigateHandler property, although this approach isn’t a best practice. Regardless, the following code sample demonstrates how to add the static JavaScript function SOAjax.Examples.OnHistoryNavigate as a global history event handler:

<asp:ScriptManager runat="server" EnableHistory="true"
    ClientNavigateHandler="SOAjax.Examples.OnHistoryNavigate" />

When you add a history point, the navigate event is immediately raised and calls any handlers added to the event. Because the navigate event is raised, you most likely want to use the same event handler that implements history to process the user action—or you might end up with your code being called twice.

Example 10-1 shows the complete code sample for a very simple history implementation. Keep in mind that when the addHistoryPoint method is called, the navigate method is also immediately called. This means that the onHistoryNavigate handler is also called for each history point. After the code runs, the page displays "Bonjour monde!" with two prior history points, and the user is able to navigate through the three application states using the Back and Forward buttons on the browser.

Tip

Tip

Code for this book is available online at http://www.microsoft.com/mspress/companion/9780735625914. The code for this chapter is in the file Chapter 10.zip.

Example 10-1. To implement history, add history points and handle the navigate event (Web/ SimpleHistoryDemo.aspx).

<%@ Page Language="C#" %>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <title>A simple history demo</title>

    <script type="text/javascript">
        function pageLoad() {
            Sys.Application.add_navigate(onHistoryNavigate);

            var state = { message: "Hello World!" };
            Sys.Application.addHistoryPoint(state, "Welcome");

            var italianState = { message: "Ciao mondo!" };
            Sys.Application.addHistoryPoint(italianState, "Benvenuto");

            var frenchState = { message: "Bonjour monde!" };
            Sys.Application.addHistoryPoint(frenchState, "Bienvenue");
        }

        function onHistoryNavigate(sender, historyEventArgs) {
            if (historyEventArgs == null) {
                historyEventArgs = new Sys.HistoryEventArgs();
                throw Error.invalidOperation('Expected Sys.HistoryEventArgs'),
            }
            var state = historyEventArgs.get_state();
            if (state.message) {
                $get('Greeting').innerHTML = state.message;
            }
        }
    </script>
</head>
<body>
    <form id="form1" runat="server">
    <div>
        <asp:ScriptManager runat="server" EnableHistory="true" />
        <div id="Greeting"></div>
    </div>
    </form>
</body>
</html>

Example 10-1 demonstrates a simple history implementation using an event handler and history points. To add history support to an instance of an AJAX control, you need to create a state object that serializes its state and identifies the state with the control, because multiple controls could exist on the page, each implementing history points.

To demonstrate history in a control instance, Example 10-2 shows an example of a control defined as the SOAjax.Examples.DataControl class. This control adds AJAX navigation links to the A elements defined in the control’s DOM element and simulates data loading and rendering by rendering the navigation value in a DIV. The code in Example 10-3 instantiates the control on page loading.

Example 10-2. The DataControl is a simple control simulating a more complex AJAX component (Web/Script/ DataControl.js).

/// <reference name="MicrosoftAjax.js"/>
Type.registerNamespace ('SOAjax.Examples'),

SOAjax.Examples.DataControl = function (element) {
    /// <summary>A control to demonstrate history.</summary>
     SOAjax.Examples.DataControl.initializeBase (this, [element]);
}

SOAjax.Examples.DataControl.prototype = {
    onHistoryNavigate: function(sender, historyEventArgs) {
        ///<summary>Handles browser navigation through history points</summary>

        // Adds intellisense to the method body:
        if (historyEventArgs == null) {
            historyEventArgs = new Sys.HistoryEventArgs();
            throw Error.argumentNull(); }

        // State is a dictionary of params that serialize the current state.
        var state = historyEventArgs.get_state();

        // Only handle history for this control instance
        if (state.controlID && state.controlID == this.get_id()) {
            this.loadData(state.data);
        }
    },

    loadData: function(id) {
        ///<summary>Simulates an AJAX method that would load & render data</summary>
        $get('data', this.get_element()).innerHTML = id;
    },

    onNavigate: function(domEvent, eventArgs) {
        ///<summary>Handles navigation through the AJAX page links</summary>
        var id = domEvent.target.getAttribute('nav'),
        if (Sys.Application.get_enableHistory()) {
            var title = domEvent.target.innerHTML;
            var state = { controlID: this. get_id(), data: id};
            Sys.Application.addHistoryPoint(state, title);
        } else
        this.loadData(id);
    },

    initialize: function() {
        ///<summary>Initialize the component.</summary>
        Sys.Application.set_enableHistory(true);
        SOAjax.Examples.DataControl.callBaseMethod(this, 'initialize'),
        this.historyDelegate = Function.createDelegate(
            this, this.onHistoryNavigate);
        Sys.Application.add_navigate(this.historyDelegate);

        this.navigationDelegate = Function.createDelegate(this, this.onNavigate);
        var element = this.get_element();
        var links = element.getElementsByTagName('A'),
        for (var i = 0; i < links.length; i++) {
            $addHandler(links[i], 'click', this.navigationDelegate);
        }
    },

    dispose: function() {
        ///<summary>Release resources before control is disposed.</summary>
        var element = this.get_element();
        if (element) {
            $clearHandlers(this.get_element());
            var links = element.getElementsByTagName('A'),
            for (var i = 0; i < links.length; i++) {
                $clearHandlers(links[i]);
            }
        }

        if (this.historyDelegate) {
            Sys.Application.remove_navigate(this.historyDelegate);
            delete this.historyDelegate;
        }
        if (this.navigationDelegate) delete this.navigationDelegate;
        SOAjax.Examples.DataControl.callBaseMethod(this, 'dispose'),
    }
}
SOAjax.Examples.DataControl.registerClass(
    'SOAjax.Examples.DataControl', Sys.UI.Control);

Sys.Application.notifyScriptLoaded();

By examining the code in Example 10-2, you can see several details. The onNavigate event handler method is added through a delegate to the DOM element’s click event, a technique we discussed in Chapter 9. This method parses the relevant data from the control that fires the event and serializes it into the state object. This object is then passed to the Sys.Application.addHistoryPoint method to create a history point for this action and a page title for the history point. Because Sys.Application’s addHistoryPoint method fires the navigate event, you don’t need to call the control’s loadData method—the onHistoryNavigate event handler calls it. If you were to add a history point and call loadData from the DOM element’s handler, the loadData method would be called twice. To be sure that your code works even when history support is not enabled, wrap the history code in an if block that checks the enableHistory property of Sys.Application.

The code in Example 10-3 demonstrates a simple page with two DOM elements, each of which creates an instance of the sample DataControl in the page load method. Because the control is coded to handle history only for the current instance, each instance handles its own history.

Example 10-3. The ScriptManager supports history through the EnableHistory attribute (Web/History.aspx).

<%@ Page Language="C#" %>
<html xmlns="http://www.w3.org/1999/xhtml" >
<head>
    <title>History Demo</title>
    <style type="text/css">
        a { color:Blue; text-decoration:'underline'; cursor:pointer; }
        #display { font-size:larger; padding:10px; border:solid 2px black;}
    </style>
    <script language="javascript" type="text/javascript">
        function pageLoad() {
            $create(SOAjax.Examples.DataControl,
                null, null, null,
                $get('HistoryTestControl'));

            $create(SOAjax.Examples.DataControl,
                null, null, null,
                $get('HistoryTestControl2'));
        }
    </script>
</head>
<body>
    <form id="form1" runat="server">
    <asp:ScriptManager runat="server" EnableHistory="true" >
        <Scripts>
            <asp:ScriptReference Path="script/DataControl.js" />
        </Scripts>
    </asp:ScriptManager>

    <div id="HistoryTestControl">
        <span id="display">
            JavaScript Data: <span id="data">[]</span>
        </span>
        <br /><br />
        <div>
            <a nav="1">Test 1</a><br />
            <a nav="2">Test 2</a><br />
            <a nav="3">Test 3</a><br />
            <a nav="4">Test 4</a><br />
            <a nav="5">Test 5</a><br />
        </div>
    </div>

    <div id="HistoryTestControl2">
        <span id="display">
            JavaScript Data: <span id="data">[]</span>
        </span>
        <br /><br />
        <div>
            <a nav="6">Test 6</a><br />
            <a nav="7">Test 7</a><br />
            <a nav="8">Test 8</a><br />
            <a nav="9">Test 9</a><br />
            <a nav="10">Test 10</a><br />
        </div>
    </div>

    </form>
</body>
</html>
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset