6.4. Adding History Support

Now that you have seen the AJAX enabled application, you are ready to see how start controlling the navigation history. History support is built into the framework, but it is disabled by default because it performs some initialization on the page that would be unnecessary if you weren't using the feature. For example, it requires a hidden IFRAME to work on some browsers, which is created for you automatically. To enable history support, set the EnableHistory property on the ScriptManager.

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

Figure 6-3. Figure 6-3

When using history there are two tasks you are mainly concerned with: creating history points and handling navigation events. To create a history point, you associate a value with it by calling the AddHistoryPoint method on the ScriptManager.

ScriptManager1.AddHistoryPoint(key, value, title);

The value is important because you use it to restore state when a navigation event occurs. History maintains values as a list of key-value pairs, so it is possible for multiple components on a page to maintain history points without interfering with one another, if they use different keys. In addition to the key and value, you can optionally associate a title with the history point, which will appear as the page title in the browser's history.

When the user uses one of the history points you have added, the ScriptManager raises a Navigate event. The event receives a HistoryEventArgs instance, which contains the key-value collection at that point in history. Here, you retrieve the same value you added when calling AddHistoryPoint, which you can use to restore the state of the application as appropriate.

ScriptManager1.Navigate +=
    new EventHandler<HistoryEventArgs>(ScriptManager1_Navigate);

private void ScriptManager1_Navigate(object sender, HistoryEventArgs e)
{
    string value = e.State["key"];
}

It is important to know that when adding history points from the server-side, navigation events are handled by performing a postback. Later you will see how to add history points purely on the client, which does not necessarily require a postback to process. Because navigation events are a form of asynchronous postback, it's important that you do not add a history point while navigating. Clicking the back button should "consume" a history point, not create a new one! Depending on your scenario, you may or may not know whether an event that is occurring was caused by a normal user operation or a navigation event. Thankfully, when in doubt you can always check the IsNavigating property.

if (!ScriptManager1.IsNavigating) {
    // add history point
}

That's all there is to it. Adding history points and handling the navigation event is all you have to do. Listing 6-4 (AlbumSearchHistory.aspx) is the markup for the history enabled album search page. The differences from the previous version are highlighted.

Example 6-4. AlbumSearchHistory.aspx
<%@ Page Language="C#" CodeFile="AlbumSearchHistory.aspx.cs"
    Inherits="AlbumSearchHistory" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title>Phish Album Search</title>
</head>
<body>
    <form id="form1" runat="server">
    <asp:ScriptManager ID="ScriptManager1" runat="server"
        EnableHistory="true" />
    <asp:UpdatePanel ID="UpdatePanel1" runat="server">
        <ContentTemplate>
        <asp:TextBox id="txtSearch" runat="server" />
        <asp:Button ID="cmdSearch" runat="server" Text="Search" />
        <asp:ListView ID="AlbumList" runat="server">
            <LayoutTemplate>
                <p>
                Sort Order:
                [<asp:LinkButton ID="SortByTitle" runat="server"
                    CommandName="Sort" CommandArgument="Title"
                    Text="Title" /> |
                <asp:LinkButton ID="SortByDate" runat="server"
                    CommandName="Sort" CommandArgument="ReleaseDate"
                    Text="Release Date" />]
                </p>
                <div id="itemPlaceholder" runat="server"></div>
            </LayoutTemplate>
<ItemTemplate>
    <div style="font-weight:bold">
        <%# Eval("Title") %>
    </div>
    <div style="font-style:italic">
       Released

<%# Eval("ReleaseDate", "{0:MMM dd, yyyy}") %>
                </div>
            </ItemTemplate>
            <ItemSeparatorTemplate>
                <hr />
            </ItemSeparatorTemplate>
        </asp:ListView>
        </ContentTemplate>
    </asp:UpdatePanel>
    </form>
</body>
</html>

Listing 6-5 (AlbumSearchHistory.aspx.cs) is the history-enabled code-behind and highlights the differences from the previous version.

Example 6-5. AlbumSearchHistory.aspx.cs
using System;
using System.Linq;
using System.Web.UI;
using Wrox.ASPAJAX.Samples;

public partial class AlbumSearchHistory : System.Web.UI.Page
{
    protected override void OnInit(EventArgs e) {
        cmdSearch.Click += cmdSearch_Click;
        AlbumList.Sorting += AlbumList_Sorting;
        ScriptManager1.Navigate +=
            new EventHandler<HistoryEventArgs>(ScriptManager1_Navigate);
        base.OnInit(e);
    }

    private void ScriptManager1_Navigate(object sender, HistoryEventArgs e) {
        string term = e.State["term"];
        if (!String.IsNullOrEmpty(term)) {
            // restore the search results
            ViewState["CurrentTerm"] = txtSearch.Text = term;
            AlbumList.DataSource = FindAlbums(term, "");
            AlbumList.DataBind();
        }
        else {
            // navigated to the original state,
            // restore page with no results
            ViewState["CurrentTerm"] = txtSearch.Text = "";
            AlbumList.DataSource = null;
            AlbumList.DataBind();
        }
   }

private void AlbumList_Sorting(object sender,
    System.Web.UI.WebControls.ListViewSortEventArgs e) {
    AlbumList.DataSource = FindAlbums(ViewState["CurrentTerm"] as string,
    e.SortExpression);
    AlbumList.DataBind();
    }

    private void cmdSearch_Click(object sender, EventArgs e) {
        string term;
        ViewState["CurrentTerm"] = term = txtSearch.Text;
        AlbumList.DataSource = FindAlbums(term, "");
        AlbumList.DataBind();

        ScriptManager1.AddHistoryPoint("term", term,
            "Phish Album Search - "" + Server.HtmlEncode(term) + """);
    }

    protected override void Render(HtmlTextWriter writer) {
        string term = ViewState["CurrentTerm"] as string;
        if (!String.IsNullOrEmpty(term)) {
            Page.Title = "Phish Album Search - "" +
            Server.HtmlEncode(term) + """;
        }
        else {
            Page.Title = "Phish Album Search";
        }
        base.Render(writer);
    }

    private object FindAlbums(string searchTerm, string orderBy) {
        return from album in Album.PhishAlbums
                where album.Title.IndexOf(searchTerm,
                    StringComparison.OrdinalIgnoreCase) != −1
                orderby orderBy == "Title" ? (object)album.Title :
                    (object)album.ReleaseDate
                   select album;
    }
}

The cmdSearch_Click event fires when the user searches for an album. That is the only time history points to be added for this Web page. Notice that a history point is not added when the user sorts the results in the AlbumList_Sorting event.

The ScriptManager1_Navigate event handler is responsible for restoring the page state to what it should be based on the information added to the history point the user is navigating to. The search term is stored in history state, so we need to rerun the search term. But that is not all that needs to be done. When the user clicks back, the page performs an asynchronous postback. Only the changes you perform in the Navigate event cause changes in the page's resulting state. You are in complete control over the new state of the form. You should of course try to restore the state to what it actually was when the history point was added, but that may sometimes mean more than you imagine. For example, in this Web page besides rerunning the search term, the value of the Search TextBox must be reset to that term. Otherwise, the results are updated, but the TextBox still holds the same value! That might mean displaying the results for "blue" but seeing "live" in the textbox.

So when setting history points, be sure to take note of all the things on the page that the user might change, and be sure to store what state information makes sense in the history point. Not everything will be worth the effort of saving, so it is a judgment call only you can make for your particular page. For example, the state of a dynamically expanding TreeView or Menu control that may just happen to be on the side of the page may not be worth storing. Try to find the minimum set of data required.

Figure 6-4 shows the results of running the history-enabled album search application, performing the same sequence of searches used in the original search.

Figure 6-4. Figure 6-4

Compare the back button menu with that in Figure 6-1. Now there are no longer any duplicate entries! Clicking back and forward navigates through search terms, regardless of any sorting operations done between them.

Also interesting to note, the "click" sound you normally hear when navigating across pages in Internet Explorer doesn't occur when performing asynchronous postbacks, but it does when a navigation point has been added. You should hear the click sound when clicking the search button, but not when sorting, which adds further feedback to the user that they have performed an operation that the page considers a navigation event.

Remember the refresh button problem? In the original application, refreshing caused the browser to display a scary dialog. (Refer to Figure 6-2.) When including AJAX support, the dialog went away but refreshing reset the state of the page. In the history-enabled version, as an added bonus, refreshing the page does exactly what you would expect! It works because AJAX history state is stored in the document fragment, which is part of the URL as shown in Figure 6-5. Since it is part of the URL, it survives refreshes, and the state it represents is pushed through to the ScriptManager's navigate event.

Figure 6-5. Figure 6-5

..................Content has been hidden....................

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