Thursday, July 9, 2009

A web user control for custom pager navigation

July 14 2009
using .NET 3.5, VS 2008, and Facebook Toolkit 2.0, MS SQL Server 2005

This is part 2 of a series of articles on creating an AJAX paged data control on a Facebook canvas application. The previous article, below, describes how to retrieve a single page of data from a larger query result set, building a method like:

public static Dictionary<long, Thing> getThingsPaged(long startItemNum, long numItemsPerPage)



So, the next step is to construct a page navigation control. The requirements are:

  • Full navigation - first, last, next, prev and up to 5 numbered navigation links displayed;
  • Invalid page numbers are not displayed ( number links > total number of pages);
  • It must be built so that it is reusable, not bound to any particular data control or page implementation.

First, we need to layout the basic control UI. In order to meet the 3rd requirement, I am implementing this as a web user control (WUC). One thing I like about using WUCs is that you can define public properties that can be set declaratively or in server side code in the containing page. The pager WUC defines the following properties:

(excerpt from
wucAjaxTest_PagerControl.ascx.cs)

public long CurrentPage...

public long NumItems...

public long NumItemsPerPage...

public String AjaxPageRequestFunctionName...

// standard get and set declarations omitted


These should all be self-explanatory, except maybe the last one: AjaxPageRequestFunctionName. This string is the name of the JavaScript function in the containing page which will respond to the page navigation event. A typical invocation of this control might look like this:

(An example of an implementation of the pager and associated JavaScript function)

<%@ Register Src="~/wucAjaxTest_PagerControl.ascx" TagPrefix="ajxPager" TagName="ajxPager" %>

...

<asp:GridView ID="ThingGrid" runat="server">asp:GridView>

...

<%--set default value for CurrentPage, NumItemsPerPage --%>

<%--NumItems should be set by querying total count from datasource on page initialization --%>

<ajxPager:ajxPager ID="pager" runat="server" CurrentPage="1" NumItems="<%= TotalItems %>" NumItemsPerPage="10" AjaxPageRequestFunctionName="handleThingGridPageEvent" />

...

<script type="text/javascript">

function handleThingGridPageEvent(pageNum) {... load the requested page of data into the grid ...}
script>


This allows you to have multiple pager controls per page, each making requests using a different JavaScript function. This function must accept a single parameter, the new page number to navigate to, and each function is bound to a specific control that it updates. Can you write it such that one JavaScript function handles all requests for all objects? Yes. Go right ahead
J But this is my example and this is how I chose to do it…

The actual implementation of this function is irrelevant to the pager control. The ultimate goal of this series of articles it to implement code that uses Facebook’s Ajax library to update a bound data control, so my function will make post request through the Facebook AJAX object. This pager control, however does not know or care about Facebook or grid views or any of that… it simply fires the given method with the configured page number. Therefore, this article will not address the contents of the JavaScript function. Let me leave it at this – a server side handler uses the paged data query method defined in the previous article to rebind the gridview.

When the grid is rebound, the pager control is also refreshed, and its page load event fires. The control dynamically configures the navigation links to invoke the JavaScript function, setting each link up to pass the appropriate page number. The navigation links are re-drawn when it refreshes the control, so that new page number links are displayed and the page numbers referred to in the "prev" and "next" links are updated. The code-behind sets the OnClick attribute as well as the display text of the UI controls dynamically.

(entire text of wucAjaxTest_PagerControl.ascx)

<%@ Control Language="C#" AutoEventWireup="true" CodeFile="wucAjaxTest_PagerControl.ascx.cs" Inherits="wucAjaxTest_PagerControl" %>

<div id="divThePager">

<a href="#" runat="server" id="_first">firsta>&nbsp

<a href="#" runat="server" id="_prev">preva>&nbsp

<a href="#" runat="server" id="_n0"><%= n0Text %>a>&nbsp

<a href="#" runat="server" id="_n1"><%= n1Text %>a>&nbsp

<a href="#" runat="server" id="_n2"><%= n2Text %>a>&nbsp

<a href="#" runat="server" id="_n3"><%= n3Text %>a>&nbsp

<a href="#" runat="server" id="_n4"><%= n4Text %>a>&nbsp

<a href="#" runat="server" id="_next">nexta>&nbsp

<a href="#" runat="server" id="_last">lasta>&nbsp

div>

(excerpt from wucAjaxTest_PagerControl.ascx.cs)

public partial class wucAjaxTest_PagerControl : System.Web.UI.UserControl

{

// declarations omitted

protected void Page_Load(object sender, EventArgs e)

{

long startPage = 1;

long numPages = (NumItems / NumItemsPerPage);

// because this is integer math,

// when the number of items is not an exact

// multiple of the number of items per page

// the number of pages needs to be incremented

if (numPages * NumItemsPerPage <>

// show 5 page number links, and the current

// page is the middle number. Unless current page

// is less than three, which would cause negative

// page numbers to appear

if (CurrentPage < startpage =" 1;

else startPage = CurrentPage - 2;

// derive the display text for the page number links

n0Text = startPage.ToString();

n1Text = (startPage + 1).ToString();

n2Text = (startPage + 2).ToString();

n3Text = (startPage + 3).ToString();

n4Text = (startPage + 4).ToString();

// derive prev and next page numbers

// although these values are not used for display like the page numebr links above

if (CurrentPage == 1) prevText = "1";

else prevText = (CurrentPage - 1).ToString();

if (CurrentPage == numPages) nextText = numPages.ToString();

else nextText = (CurrentPage + 1).ToString();

// set the OnClick attributes

_first.Attributes.Add("OnClick", String.Format("{0}({1})",_ajaxPageRequestFunctionName, "1"));

_last.Attributes.Add("OnClick", String.Format("{0}({1})", _ajaxPageRequestFunctionName, numPages.ToString()));

_prev.Attributes.Add("OnClick", String.Format("{0}({1})", _ajaxPageRequestFunctionName, prevText));

_next.Attributes.Add("OnClick", String.Format("{0}({1})", _ajaxPageRequestFunctionName, nextText));

_n0.Attributes.Add("OnClick", String.Format("{0}({1})", _ajaxPageRequestFunctionName, n0Text));

_n1.Attributes.Add("OnClick", String.Format("{0}({1})", _ajaxPageRequestFunctionName, n1Text));

_n2.Attributes.Add("OnClick", String.Format("{0}({1})", _ajaxPageRequestFunctionName, n2Text));

_n3.Attributes.Add("OnClick", String.Format("{0}({1})", _ajaxPageRequestFunctionName, n3Text));

_n4.Attributes.Add("OnClick", String.Format("{0}({1})", _ajaxPageRequestFunctionName, n4Text));

Next, the control must take into account more special conditions: when the total number of pages is less than 5, or when the current page number is very close to the maximum page number.

// for small number of pages, special handling is needed for page links

if (numPages <>

{

_n4.Visible = false;

if (numPages <>

{

_n3.Visible = false;

if (numPages <>

{

_n2.Visible = false;

if (numPages <>

{

_n1.Visible = false;

}

}

}

}

// at the end of the page list, may need to hide them as well

if (numPages - CurrentPage < visible =" false;

if (numPages - CurrentPage == 0) _n3.Visible = false;

}

}

So, that’s it for the pager control. The next article will describe the rendering of FBML and how that works with web user controls, and an introduction to JSON. After that we can look at the structure of the canvas application, the containing page, and how Facebook JavaScript implements Ajax.

No comments:

Post a Comment