Let's say that I have a simple repeater in my user control:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<asp:Repeater ID="Repeater1" runat="server"> | |
<HeaderTemplate> | |
<ul id="navi"> | |
</HeaderTemplate> | |
<ItemTemplate> | |
<li> | |
<a href='<%#DataBinder.Eval(Container.DataItem, "ArticleURL")%>'> | |
<%#DataBinder.Eval(Container.DataItem, "Name")%> | |
</a> | |
</li> | |
</ItemTemplate> | |
<FooterTemplate> | |
</ul> | |
</FooterTemplate> | |
</asp:Repeater> |
I'm going to add a placeholder for my paging controls.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<table> | |
<tr><td> | |
<asp:PlaceHolder ID="plcPaging" runat="server" > | |
<asp:LinkButton ID="prevLnk" Text = "Prev" runat="server" OnClick="prev_Click"/> | |
<asp:Label ID="prevspacer" Text = " " runat="server" /> | |
<asp:LinkButton ID="lnkPage1" Text = "1" runat="server" OnClick="lnk_Click"/> | |
<asp:Label ID="spacer1" Text = " | " runat="server" /> | |
<asp:LinkButton ID="lnkPage2" Text = "2" runat="server" OnClick="lnk_Click"/> | |
<asp:Label ID="spacer2" Text = " | " runat="server" /> | |
<asp:LinkButton ID="lnkPage3" Text = "3" runat="server" OnClick="lnk_Click"/> | |
<asp:Label ID="spacer3" Text = " | " runat="server" /> | |
<asp:LinkButton ID="lnkPage4" Text = "4" runat="server" OnClick="lnk_Click"/> | |
<asp:Label ID="spacer4" Text = " | " runat="server" /> | |
<asp:LinkButton ID="nextLnk" Text = "Next" runat="server" OnClick="next_Click"/> | |
</asp:PlaceHolder> | |
</td></tr> | |
</table> |
Notice how i'm using only 4 link buttons, which might not be adequate for most situations. So the idea is to dynamically change the properties of these link buttons depending on the current page.
Once we have a way to keep track of the current page we are on as well as the total number of data items(or pages), we are well on our way to finishing our control.
To deal with keeping track of the current page and total number of data items, i'm going to use control state here. I could have used Viewstate instead for simplicity but there's a possibility of viewstates being turned off by another developer using my control, and in a critical feature such as paging, it's better to store state in a control state.
Here's the code to make that work:
I'm using a structure to store my state specific properties here. The SaveControlState and LoadControlState methods are used by ASP.NET to serialize and deserialize my structure into control state.
The things remaining to do are to load data and also to format our control dynamically based on the page we are at. Let's look at loading data first:
I initialize page load to fire our data loading method the first time the page is called.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
protected void Page_Load(object sender, EventArgs e) | |
{ | |
if (!Page.IsPostBack) | |
LoadData(itemsPerPage, 0); | |
} |
The actual loading is done like this:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
private void LoadData(int take, int pageSize) | |
{ | |
//somehow return all the objects you need. could be a datatable instead of a list of objects | |
List articles = GetAllItems(); | |
//set total item count | |
RowCount = articles.Count; | |
var topDocs = (from c in articles select c).Skip(pageSize).Take(take); | |
PagedDataSource pagedSrc = new PagedDataSource(); | |
pagedSrc.DataSource = topDocs; | |
pagedSrc.PageSize = itemsPerPage; | |
pagedSrc.AllowCustomPaging = true; | |
pagedSrc.AllowPaging = true; | |
this.Repeater1.DataSource = topDocs; | |
this.Repeater1.DataBind(); | |
} |
The important thing to note here is that we use a PagedDataSource as our datasource for the repeater, not the data items directly. This is because the repeater control does not support paging out of the box. To enable paging, we need to use the PagedDataSource as an adapter, whose properties such as the size of the page can now be set.
For formatting the output of the page links, I use the following method:
Here n has been set to 4, since we are using 4 linkbuttons in our ascx file. If we wish to add more buttons and spacers (which are to add seperation between the buttons), it would be as simple as adding those controls following the same naming convention as the other buttons, and changing the value of n.
For eg. let's say that we have 7 pages, and are currently in the second page.
The paging would look like " Prev 2 3 4 5 Next"
2 would be disabled. If you look at the ascx file again, you'll notice that the linkbuttons were hardcoded with values 1 to 4. The values have now been dynamically changed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/// <summary> | |
/// sets the paging control's properties | |
/// </summary> | |
protected void FormatPagingControl() | |
{ | |
//number of page links | |
int n = 4; | |
int lastpage = ((RowCount -1) / itemsPerPage) + 1; | |
int startPage = CurrentPage; | |
int lastPageToDisplay = Math.Min(lastpage, startPage + (n-1)); | |
//if we have only page, do not show paging controls | |
if (RowCount <= itemsPerPage) | |
{ | |
plcPaging.Visible = false; | |
return; | |
} | |
else plcPaging.Visible = true; | |
//in the case that current page is not in the first n pages and belongs to last (n-1) pages | |
//current page will not be the first page link. | |
int numberOfPagesDisplayed = lastPageToDisplay - startPage + 1; | |
//make sure we display maximum number of page links as possible. | |
if(numberOfPagesDisplayed < n) | |
{ | |
//push start page back | |
startPage -= (n - numberOfPagesDisplayed); | |
} | |
if (startPage < 1) | |
startPage = 1; | |
/* | |
* Now, using startpage and lastPageToDisplay,perform render | |
* */ | |
//previous and next buttons | |
prevLnk.Visible = (startPage > 1); | |
nextLnk.Visible = (lastPageToDisplay < lastpage); | |
//page links and spacers | |
for (int i = 1; i <= n; ++i) | |
{ | |
LinkButton lnk = ((plcPaging.FindControl("lnkPage" + i)) as LinkButton); | |
Label spacer = ((plcPaging.FindControl("spacer" + i)) as Label); | |
int displayNum = startPage + i - 1; | |
//visible if page's number is less than or equal to last page to display | |
spacer.Visible = lnk.Visible = displayNum <= lastPageToDisplay; | |
//make final spacer invisible | |
if (i != 1 && lnk.Visible == false) | |
((plcPaging.FindControl("spacer" + (i - 1).ToString())) as Label).Visible = false; | |
//set text | |
lnk.Text = displayNum.ToString(); | |
//grey out if page represents current page | |
lnk.Enabled = (CurrentPage != displayNum); | |
} | |
} |
The following code performs event handling for the page buttons. If you look at the LoadItemsBasedOnCurrentPage() method, you will find that the method the call to LoadData fetches only items we will require on the current page. This results in a smaller payload of information being sent at each access to a page.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
protected void LoadItemsBasedOnCurrentPage() | |
{ | |
int take = CurrentPage * itemsPerPage; | |
int skip = CurrentPage == 1 ? 0 : take - itemsPerPage; | |
LoadData(take, skip); | |
} | |
protected void prev_Click(object sender, EventArgs e) | |
{ | |
LinkButton lnk = sender as LinkButton; | |
CurrentPage--; | |
LoadItemsBasedOnCurrentPage(); | |
} | |
protected void lnk_Click(object sender, EventArgs e) | |
{ | |
LinkButton lnk = sender as LinkButton; | |
CurrentPage = int.Parse(lnk.Text); | |
LoadItemsBasedOnCurrentPage(); | |
} | |
protected void next_Click(object sender, EventArgs e) | |
{ | |
LinkButton lnk = sender as LinkButton; | |
CurrentPage++; | |
LoadItemsBasedOnCurrentPage(); | |
} |
That's about it. All you need to do is to add you own method to get all the data items, change items per page (which i have deliberately set to the ridiculous value of 2) and bind them however you see fit.
This example, can with a bit of care, be easily extended to support "jump to first, last" or having .. paging feature, where you can jump to the middle of the page list. However, I have not included that for the sake of simplicity.
Happy plagiarizing. Just kidding, you have my blessings.