Achieving the right look for your DataPager with a custom DataPagerField

One of the new controls introduced in ASP.NET 3.5 is the ListView control. It gives you the same flexibility as the Repeater control, but with the features of the GridView and DetailsView, and then some! One of the niceties of this new control is that it uses the new DataPager control, and that it lets you place this anywhere on your page, not just inside your ListView.

This new DataPager gives you a lot more control over its looks than what was available before. This control leaves all the rendering of the output to one of the DataPagerField objects that you add to it, like so:

ASP.NET
1
2
3
4
5
6
7
8
9
<asp:DataPager runat="server" ID="pager" PageSize="10" >
  <Fields>
    <asp:NumericPagerField />
    <asp:NextPreviousPagerField />
    <asp:TemplatePagerField>
      <PagerTemplate></PagerTemplate>
    </asp:TemplatePagerField>
  </Fields>
</asp:DataPager>

ASP.NET 3.5 ships with three such pager fields: the NextPreviousPagerField, which displays buttons for the next or previous page, the NumericPagerField, which displays a series of buttons for pages, and last but not least the TemplatePagerField, which gives you fine control over the rendered output using a template. This, however, can lead to pretty complex markup in your ASPX page. You can, however, create your own custom DataPagerField, and it isn’t terribly difficult. In this article, I will explain you how.

For one of our customers, we needed a pager that would show a series of buttons, but that would follow the current page and provide a “window” around it, instead of simply “paging the pager” like the NumericPagerField would. Given a ListView with a content of 97 items and a PageSize of 10, we would want to display the following while on the first page:

When the user moves further in the pages, the list of pages would start to follow, and center the current page:

And when at the end, obviously, it would stop scrolling:

The “next” and “prev” buttons would also not move a whole 10 pages, but work the same as the NextPreviousPagerField and move one page up or down.

The HTML required for this look would look like this (as specified by the designer):

ASP.NET
1
2
3
4
5
6
7
<div class=”pager”>
    <a href=”#”>&lt;&lt; prev</a>
    <a href=”#”>1</a>
    <span>2</span>
    <a href=”#”>3</a>
    <a href=”#”>next &gt;&gt;</a>
</div>

At first I felt a combination of two NextPreviousPagerFields and TemplatePagerField would do the trick, but this resulted in bloated and incomprehensible markup. So I turned to implementing my own custom DataPagerField descendant called FollowingPagerField.

Firstly, we create a class that derives from DataPagerField. The documentation states that three methods must be overridden for this to work: CreateField, CreateDataPagers and HandleEvent.

C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class FollowingPagerField : DataPagerField
{
    protected override DataPagerField CreateField()
    {     
        return new FollowingPagerField();
    }

    public override void CreateDataPagers(
        DataPagerFieldItem container, int startRowIndex, 
        int maximumRows, int totalRowCount, int fieldIndex)
    {
    }

    public override void HandleEvent(CommandEventArgs e)
    {
    }
}

CreateField() is required to return a new instance of this class, and is used when cloning the PagerFields collection of the DataPager, so we’re not going to pay further attention to it.

CreateDataPagers() is called by the DataPager, and in this method we must produce all the controls required for rendering. We can use the parameters to calculate the page that we’re on so we can completely render our pager.

HandleEvent() is called when the user clicks on one of the buttons that we render. These buttons contain a CommandName and optionally a CommandArgument that we get here to decide what to do with the DataPager: move a page up or down, or move to a specific page.

I added a couple of properties to allow the user of the component to modify the look a little bit, but these can certainly be expanded upon later:

C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
private string _previousText = "<<";
private string _nextText = ">>";
private int _buttonCount = 5;

public string PreviousText{
    get { return this._previousText; }
    set { this._previousText = value; }
}

public string NextText
{
    get { return this._nextText; }
    set { this._nextText = value; }
}

public int ButtonCount
{
    get { return this._buttonCount; }
    set { this._buttonCount = value; }
}

Implementing CreateDataPagers() was pretty straightforward, using a couple of helper methods:

C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
private void CreatePages(Control container, 
    IEnumerable<int> pages, int current)
{
    foreach (int page in pages)
    {
        if (page == current)
        {
            container.Controls.Add(new HtmlGenericControl 
            { 
                TagName = "span", 
                InnerHtml = page.ToString() 
            });
        }
        else
        {
            container.Controls.Add(new LinkButton
            {
                Text = page.ToString(),
                CommandName = "Page",
                CommandArgument = page.ToString()
            });
        }
    }
}

private void CreateLink(Control container, string caption, 
    string command, bool enabled)
{
    LinkButton button = new LinkButton 
    { 
        Text = caption, 
        CommandName = command, 
        Enabled = enabled 
    };

    if (!enabled)
    {
        button.CssClass = "disabled";
    }

    container.Controls.Add(button);
}

public override void CreateDataPagers(
    DataPagerFieldItem container, int startRowIndex, 
    int maximumRows, int totalRowCount, int fieldIndex)
{
    // The pages here are 0-based. (Page 1 = 0, Page 2 = 1, ...)
    int page = (startRowIndex / maximumRows);
    int totalPages = (totalRowCount / maximumRows);

    // Create a container.
    HtmlGenericControl pagerDiv = new HtmlGenericControl("div");
    pagerDiv.Attributes["class"] = "pager";
    container.Controls.Add(pagerDiv);

    CreateLink(pagerDiv, PreviousText, "Prev", page > 0);

    int firstPage = Math.Max(page - (this._buttonCount / 2), 0);
    int lastPage = Math.Min(firstPage + this._buttonCount, totalPages);
    int count = lastPage - firstPage;

    if (count < this._buttonCount)
    {
        firstPage = Mth.Max(lastPage - this._buttonCount, 0);
        count = lastPage - firstPage;
    }

    CreatePages(pagerDiv, 
        Enumerable.Range(firstPage + 1, count), page + 1);

    CreateLink(pagerDiv, NextText, "Next", (page + 1) < totalPages);
}

A simple container control is created to contain all the elements, which needs to be a div element with a specific CSS class. Again, this can be changed to find the classname in properties, but that’s beside the point here. The heart of the problem is calculating which pages to show.

The HandleEvent() method was even simpler, simply matching the CommandName parameter and parsing the CommandArgument parameter and decide what to do with the current view:

C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
public override void HandleEvent(CommandEventArgs e)
{
    int newStartRowIndex = -1;

    if (e.CommandName.Equals("Prev"))
    {
        if (this.DataPager.StartRowIndex >= this.DataPager.PageSize)
        {
            newStartRowIndex = this.DataPager.StartRowIndex - 
                this.DataPager.PageSize;
        }
    }
    else if (e.CommandName.Equals("Next"))
    {
        if ((this.DataPager.StartRowIndex + this.DataPager.PageSize) < 
            this.DataPager.TotalRowCount)
        {
            newStartRowIndex = this.DataPager.StartRowIndex + 
                this.DataPager.PageSize;
        }
    }
    else if (e.CommandName.Equals("Page"))
    {
        int page;

        if (int.TryParse(e.CommandArgument.ToString(), out page))
        {
            int newIndex = (page - 1) * this.DataPager.PageSize;

            if ((newIndex >= 0) || 
                (newIndex < this.DataPager.TotalRowCount))
            {
                newStartRowIndex = newIndex;
            }
        }
    }

    if (newStartRowIndex >= 0)
    {
        this.DataPager.SetPageProperties(newStartRowIndex, 
            this.DataPager.MaximumRows, true);
    }
}

Since we create the links in the rendered output, we know what to expect when handling this event. I think the code speaks for itself.

Lastly, we need to register our new toy in the page, and instantiate it in the DataPager:

ASP.NET
1
2
3
4
5
6
7
8
9
10
11
12
<%@ Register assembly="OurAssembly" Namespace="OurNamespace" TagPrefix="us" %>

<asp:ListView runat="server" ID="lv" DataSourceID="rs" DataKeyNames="Id">
  <LayoutTemplate>
    <asp:DataPager runat="server" ID="pager" PageSize="5" >
      <Fields>
        <us:FollowingPagerField NextText="next >>" 
           PreviousText="<< prev" ButtonCount="10"/>
      </Fields>
    </asp:DataPager>
  </LayoutTemplate>
</asp:ListView>

So there you have it, a working custom pager by implementing two methods!

Sample Code

The article contains sample code project(s).
You must be logged in to view or download sample code.
Sign in now

Comments

Leave a comment
You must be logged in to post comments.
Sign in now
 
 
CATEGORIES
AnnouncementsDocMaker StudioEngineSharePoint ConnectorWeb DevelopmentWebinarsWorkflow Studio
rss feed