Showing posts with label AJAX History. Show all posts
Showing posts with label AJAX History. Show all posts

Friday, 15 May 2009

Remembering Page Index when returning to a List Page

This article is a continuation of the previous Retaining Pager Size in Dynamic Data GridViewPager now we want to retain the page index when moving between pages. For this we will use AJAX History.

Note: Also see this article back in February AJAX History in a Dynamic Data Website

I won’t go over the AJAX History in detail as Mike Ormond has done a great video on it on MSDN Screencasts here Managing Browser History with ASP.NET AJAX and the ASP.NET 3.5 Extensions Preview.

So let’s start.

Fist we will add a global private variable  to hold a reference to the ScriptManager.

private ScriptManager _scriptManager;

Next we need to initialise a few things

/// <summary>
/// Handles the Init event of the Page control.
/// </summary>
/// <param name="sender">The source of the event.</param>
/// <param name="e">
/// The <see cref="System.EventArgs"/> instance 
/// containing the event data.
/// </param>
protected void Page_Init(object sender, EventArgs e)
{
    // get the containing GridView
    _gridView = this.GetParentOfType<GridView>();
    if (_gridView != null)
    {
        // add event to GridView to collect page index changed
        _gridView.PageIndexChanged += GridViewPageIndexChanged;

        // get the datasource on the gridview
        var GridDataSource = _gridView.FindDataSourceControl();
        
        // get the table
        _table = GridDataSource.GetTable();

        // Add OnNavigate handler to restore History points.
        _scriptManager = ScriptManager.GetCurrent(Page);
        if (_scriptManager != null)
            // add the navigate handler
            _scriptManager.Navigate += ScriptManagerOnNavigate;
    }
}

Listing 1 – Page_Init for the GridViewPager

In Listing 1 we get the GridView and ScriptManger, we then add a handler for the PageIndexChanged event on the GridView and add an event handler for the Navigate event of the ScriptManager.

/// <summary>
/// Grids the view page index changed.
/// </summary>
/// <param name="sender">The sender.</param>
/// <param name="e">
/// The <see cref="System.EventArgs"/> 
/// instance containing the event data.
/// </param>
protected void GridViewPageIndexChanged(object sender, EventArgs e)
{
    // add history point
    if (_scriptManager != null)
        _scriptManager.AddHistoryPoint("PageIndex", _gridView.PageIndex.ToString());
}

/// <summary>
/// Scripts the manager on navigate.
/// </summary>
/// <param name="sender">The sender.</param>
/// <param name="e">
/// The <see cref="System.Web.UI.HistoryEventArgs"/> 
/// instance containing the event data.
/// </param>
protected void ScriptManagerOnNavigate(object sender, HistoryEventArgs e)
{
    // restore history point
    if (!String.IsNullOrEmpty(e.State["PageIndex"]))
        _gridView.PageIndex = int.Parse(e.State["PageIndex"]);
}

Listing 2 – Handlers for the PageIndexChanged and Navigate events

Updated: Removed HistoryPointValue = e.State["PageIndex"]; not required. I think HistoryPointValue was originally a global private member variable.

In Listing 2 we are adding history points and restoring them again we also add a history point in Listing 3 if the page index TextBox is changed.

protected void TextBoxPage_TextChanged(object sender, EventArgs e)
{
    if (_gridView == null)
        return;

    int page;
    if (int.TryParse(TextBoxPage.Text.Trim(), out page))
    {
        if (page <= 0)
            page = 1;
        if (page > _gridView.PageCount)
            page = _gridView.PageCount;
        _gridView.PageIndex = page - 1;

        // add history point
        if (_scriptManager != null)
            _scriptManager.AddHistoryPoint("PageIndex", _gridView.PageIndex.ToString());
    }
    TextBoxPage.Text = (_gridView.PageIndex + 1).ToString();
}

Listing 3 – Changes to the TextBoxPage_TextChanged event handler

And that is pretty much it HappyWizard

Friday, 13 February 2009

AJAX History in a Dynamic Data Website

If you want to know a little more about AJAX History have a look at this video on MSDN Screencasts (used to be MSDN .Net Nuggets) Managing Browser History with ASP.NET AJAX and the ASP.NET 3.5 Extensions Preview it is slightly out of date in that some of the properties have been renamed but its essentially correct and a good foundation for AJAX History’s use. There is a second article Managing Browser History on the Client with ASP.NET AJAX and the ASP.NET 3.5 Extensions Preview which may be of interest for some.

The Problem

To appreciate the solution you need to understand the problem. So here’s what happens you have a standard ASP.Net Dynamic Data V1 website with several filters on each list page, there are two things that will irritate your users to do with AJAX partial post back:

1. You make several selections and click back you will go to the previous page not the last selection (try it with and without partial render enabled in site.master EnablePartialRendering="true" see Listing 1).

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title>Dynamic Data Site</title>
    <link href="~/Site.css" rel="stylesheet" type="text/css" />
</head>
<body class="template">
    <h1><span class="allcaps">Dynamic Data Site</span></h1>
    <div class="back">
        <a runat="server" href="~/"><img alt="Back to home page" runat="server" src="DynamicData/Content/Images/back.gif" />Back to home page</a>
    </div>

    <form id="form1" runat="server">
    <div>
        <asp:ScriptManager ID="ScriptManager1" runat="server" EnablePartialRendering="true"/>
        <asp:ContentPlaceHolder id="ContentPlaceHolder1" runat="server">
        </asp:ContentPlaceHolder>
    </div>
    </form>
</body>
</html>

Listing 1 – the default Site.master

2. You click a link from a List page on which you have made several filter selections view the sub page and then click back you reach the page with no filtering applied.

Both of these issues can be really irritating for the user, because A it’s not the expected behaviour and B it’s a pain to have to redo all those filters (after all users are really lazy smile_omg)

Have you watched the video? well if you have no prior knowledge of AJAX History I strongly suggest you have a look at the video after all it's only 13mins 27secs long.

The Solution

First of all this is only my first attempt at this I think once I get into the Dynamic Data V2.0 Preview/Release I will redo this as a class that inherits from UserFilterControlBase to easily give this functionality in filter templates.

<asp:ScriptManager 
    ID="ScriptManager1" 
    runat="server" 
    EnableHistory="true"
    EnableSecureHistoryState="false"
    EnablePartialRendering="true"/>

Listing 2 – the ScriptManager on Site.master alterations

As you can see from the Listing 2 enabling AJAX History is quite easy you just need to add the EnableHistory="true" property to the control. The other property is to disable encryption of the history information that is added to the URL EnableSecureHistoryState="false" this is one of the methods that have been renamed for the RTM version.

Now we need to edit the ~/DynamicData/Content/FilterUserControl.ascx.cs file see Listing 3 for the code all alteration are in BOLD italic.

using System;
using System.Collections.Specialized;
using System.Linq;
using System.Web.UI;
using System.Web.UI.WebControls;

public partial class FilterUserControl : System.Web.DynamicData.FilterUserControlBase
{
    // global variable for holding the History Point value
    private String HistoryPointValue;

    public event EventHandler SelectedIndexChanged
    {
        add
        {
            DropDownList1.SelectedIndexChanged += value;
        }
        remove
        {
            DropDownList1.SelectedIndexChanged -= value;
        }
    }

    public override string SelectedValue
    {
        get
        {
            return DropDownList1.SelectedValue;
        }
    }

    protected void Page_Init(object sender, EventArgs e)
    {
        if (!Page.IsPostBack)
        {
            PopulateListControl(DropDownList1);

            // Set the initial value if there is one
            if (!String.IsNullOrEmpty(InitialValue))
                DropDownList1.SelectedValue = InitialValue;
        }

        // add event handler to capture history point.
        this.SelectedIndexChanged += ThisSelectedIndexChanged;

        // Add OnNavigate handler to restore History points.
        var scriptManager = ScriptManager.GetCurrent(Page);
        if (scriptManager != null)
            scriptManager.Navigate += ScriptManagerOnNavigate;
    }

    protected void ThisSelectedIndexChanged(object sender, EventArgs e)
    {
        var filterRepeater = this.GetParentFilterRepeater();
        if (filterRepeater != null)
        {
            // add History point for the curret state of
            // all filters so we can restore them later.
            var scriptManager = ScriptManager.GetCurrent(Page);
            if (scriptManager != null && scriptManager.IsInAsyncPostBack)
            {
                var nvcHistory = new NameValueCollection();

                var filterControls = filterRepeater.GetFilterControls();
                if (filterControls.Count() > 0)
                {
                    foreach (var filter in filterControls)
                    {
                        var f = filter as FilterUserControl;
                        if (f != null)
                            nvcHistory.Add(f.DataField, f.SelectedValue);
                    }
                    // if we get some filter states add the history point.
                    if (nvcHistory.Count > 0)
                        scriptManager.AddHistoryPoint(nvcHistory, Page.Title);
                }
            }
        }
    }

    protected void ScriptManagerOnNavigate(object sender, HistoryEventArgs e)
    {
        HistoryPointValue = e.State[DataField];
        if (e.State[DataField] == null || e.State[DataField] == "")
            DropDownList1.SelectedIndex = 0;
        else if(HistoryPointValue != null)
        {
            // check if history point is in the DDL and set History Point value
            ListItem item = DropDownList1.Items.FindByValue(HistoryPointValue);
            if (item != null)
                DropDownList1.SelectedValue = HistoryPointValue;
        }
    }
}

Listing 3 – FilterUserControl.ascx.cs file

And finally here are the extension methods use in Listing 3.

using System.Collections.Generic;
using System.Linq;
using System.Web.DynamicData;
using System.Web.UI;
using System.Web.UI.WebControls;

/// <summary>
/// Summary description for ExtensionMethods
/// </summary>
public static class ExtensionMethods
{
    /// <summary>
    /// Get the parent FilterRepeater of the control it's used on.
    /// </summary>
    /// <param name="control">The control to find the parent FilterRepeater of.</param>
    /// <returns>The parent FilterRepeater.</returns>
    public static FilterRepeater GetParentFilterRepeater(this Control control)
    {
        var parentControl = control.Parent;
        while (parentControl != null)
        {
            var FilterRepeater = parentControl as FilterRepeater;
            if (FilterRepeater != null)
                return FilterRepeater;
            else
                parentControl = parentControl.Parent;
        }
        return null;
    }

    /// <summary>
    /// Get a List of FilterUserControlBase filters from the FilterRepeater.
    /// </summary>
    /// <param name="filterRepeater">The FilterRepeater to get a List of filter from</param>
    /// <returns>returns a List of type FilterUserControlBase.</returns>
    public static IEnumerable<UserControl> GetFilterControls(this FilterRepeater filterRepeater)
    {
        var filters = new List<UserControl>();

        foreach (RepeaterItem item in filterRepeater.Items)
        {
            var filter = item.Controls.OfType<UserControl>().FirstOrDefault();
            filters.Add(filter);
        }

        return filters.AsEnumerable();
    }
}

Listing 4 – Extension methods

Now when you skip back and forth through pages you should find it works just like you and your users were expecting.