# Wednesday, July 01, 2009

Ajax History - a how to - Part 2 - a basic example

Part 1 - Introduction
Part 2 - Basic Example
Part 3 - Complex Example
Part 4 - Final Notes
Bonus - Ajax History and the Memento Pattern
Extra Bonus - Issues with Opera

Handling Ajax history has been a critical issue since Ajax first started. With the advent of .net 3.5 the ability to easily deal with that is rolled into the code. This is part 2, here we will look at a simple example of handling Ajax history to give a basic understanding of the process. If you have not read Part 1, I suggest you do that now. Part 3 will expand on handling Ajax history when the data is more complex.
For simplicity sake we are only going to look at an example of very, very basic history. We are going to look at a page that displays changes made to the time.

A basic assumption is that you are fairly familiar with Ajax (http://ajax.asp.net). We are going to be using the ScriptManager (of course) and an UpdatePanel.
Our basic page looks like this

<%@ Page Language="C#" AutoEventWireup="true" CodeFile="AjaxHistory.aspx.cs" Inherits="AjaxHistory" %>

<!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></title>
</head>
<body>
    <form id="form1" runat="server">
    <asp:ScriptManager runat="server" ID="sm1" EnablePartialRendering="true" />
    <div>
        <asp:UpdatePanel runat="server" ID="upME">
            <ContentTemplate>
                <asp:Button runat="server" ID="uxChangeTime"
                    OnClick="uxChangeTime_Click" Text="-- change:" />
                <asp:Label runat="server" ID="lblTime" />
            </ContentTemplate>
            <Triggers>
                <asp:AsyncPostBackTrigger ControlID="uxChangeTime"
                     EventName="Click" />
            </Triggers>
        </asp:UpdatePanel>
    </div>
    </form>
</body>
</html>

The codebehind sets the label text to be the current date/time on every click:

(c# code)
protected void uxChangeTime_Click(object sender, EventArgs e)
{
    string _currentDateTime = DateTime.Now.ToString("F");
    lblTime.Text = _currentDateTime;
}

(vb code)
Protected Sub uxChangeTime_Click(ByVal sender As Object, &_
  ByVal e As System.EventArgs) Handles uxChangeTime.Click
    Dim _currentDateTime As String = DateTime.Now.ToString("F")
    lblTime.Text = _currentDateTime
End Sub

As you can see this is fairly straightforward. Clicking on the button displays the current DateTime in the label. However, there is no history. Clicking back will not display the last DateTime displayed but either be grayed out (if this was the first page loaded) or take you back to the page before this one.

To enable history is very, very simple in 3.5

  1. modify the ScriptManager, setting a property or two and registering an event (Navigate) for the ScriptManager
  2. modify the Click handler to notify the ScriptManager to record a History Point
  3. handle the event called by the ScriptManager to rollback, when required, the page

Step 1: Modify the ScriptManager

The modifications are very simple - set EnableHistory equal to true and specify what should handle the Navigate event. You can also specify if you want the HistoryState to be 'secure' by setting EnableSecurityHistoryState equal to true, but this is optional.

Your ScriptManager should now look like this:

<asp:ScriptManager runat="server" ID="sm1"
     EnableHistory="true" OnNavigate="sm1_Navigate"
     EnableSecureHistoryState="true"
/>

Step 2: Add an AsyncPostBackTrigger for the Navigate event of the ScriptManager

<asp:UpdatePanel ...>
    ...
    <Triggers>
         <asp:AsyncPostBackTrigger ControlID="uxChangeTime"
                     EventName="Click" />
         <asp:AsyncPostBackTrigger ControlID="sm1" EventName="Navigate" />
    </Triggers>
</asp:UpdatePanel>

Step 3: Modify the Click handler (or whatever event it is that would trigger a HistoryPoint to be recorded

What you are doing here is notifying the ScriptManager what information to store as a HistoryPoint by calling its AddHistoryPoint method. There are three possible overloads and we will look at the other two next blog, but for now we will use one of the simplest, a single key/value string pair to store the current DateTime with the key "myTime":

(c# code)
protected void uxChangeTime_Click(object sender, EventArgs e)
{
    string _currentDateTime = DateTime.Now.ToString("F");
    lblTime.Text = _currentDateTime;
    ScriptManager.GetCurrent(this).AddHistoryPoint("myTime", _currentDateTime);
}

(vb code)
Protected Sub uxChangeTime_Click(ByVal sender As Object, &_
  ByVal e As System.EventArgs) Handles uxChangeTime.Click
    Dim _currentDateTime As String = DateTime.Now.ToString("F")
    lblTime.Text = _currentDateTime
    ScriptManager.GetCurrent(Me).AddHistoryPoint("myTime", _currentDateTime)
End Sub

Step 4: handle the ScriptManager Navigate event

In this event we retrive the State property of the HistoryEventArgs and use that to set the page to be what we want it to be. In this very simple example we will use it to set the value of the label to be the DateTime that that particular page displayed:

(c# code)
protected void sm1_Navigate(object sender, HistoryEventArgs e)
{
    string _historyValue = e.State["myTime"];
    if (_historyValue != null)
    {
        lblTime.Text = _historyValue;
    }
}

(vb code)
Protected Sub sm1_Navigate(ByVal sender As Object, &_
  ByVal e As System.HistoryEventArgs) Handles sm1.Navigate
    Dim _historyValue As String = e.State["myTime"]
    if(_historyValue <> Nothing)
    {
       lblTime.Text = _historyValue
    }
End Sub

Voila! We now have a functional Ajax page with history. Try it!

What has happened is that we are using History Points. When we click the button it determines the current DateTime, displays it, and stores it as a history point. When we click the "Back" button it retrieves the previous history point (which was the current DateTime in string format) and displays that (see chart below).

Next blog: a look at more intensive use of History Points...
btw, for prep, take a looksee at my blog on the Memento pattern and Ajax history to understand my basic approach: http://www.myfriedmind.com/techBlog/2009/05/20/AjaxHistoryAndTheMementoPattern.aspx

Wednesday, September 16, 2009 4:20:13 PM (Central Daylight Time, UTC-05:00)
Nice series, well written.

I found this site while looking for some info on why Ajax history in my application doesn't work in Opera. So, I copied your basic example, and indeed it doesn't work in Opera 10 (nor 9.64). Could you please kindly test it with Opera, and let us know your result? I'm getting javascript error when AddHistoryPoint is hit:
Exception: Sys.WebForms.PageRequestManagerServerErrorException
In function: Sys$WebForms$PageRequestManager$_endPostBack

If I set EnablePartialRendering of the ScriptManager to false it works, but with full-page postback.

Everything works in IE6, IE8, FF, Safari. Only Opera has this problem.
Tomi
Friday, September 18, 2009 4:35:23 PM (Central Daylight Time, UTC-05:00)
Wow! Great catch!!!

I am working on a response, it got too long so I am creating a new blog to explain the 'hack' that I discovered. I want to do a wee bit more research on why the 'hack' works and so I should get this posted by Monday, Tuesday at the latest...

peas,
m
Papabear
Monday, September 21, 2009 12:00:09 PM (Central Daylight Time, UTC-05:00)
Finally got the my testing all typed up. That is the good news. The bad news is that after hours or work I only came up with a quick hack to make it work in Opera. And I cannot even say for sure as to why it works.

:(

Take a looksee here, though, for what I found out.
http://www.myfriedmind.com/techBlog/2009/09/21/Opera9x10xFailingOnAjaxHistoryAndTheHackToFixIt.aspx

Papabear
Comments are closed.