ASP.NET MVC 3 HtmlHelper for custom pager

For our existing intranet applications written using web forms we have a web control to do our paging like most web controls it uses viewstate to remember what state it is currently in going forward we are now using MVC so I needed to replace the webcontrol with a HtmlHelper method to create a pager.

I had a look round to find an existing one however all the ones I came across work in slightly different way compared to our existing webcontrol below is screenshot of how it looks:

image

The HtmlHelpers I found seem to work by taking the current page number and populating the page numbers either side so that the page number is always in the middle of the other numbers, our existing web control works by displaying a group of page numbers you can then use the ellipse button to move to the previous/next group of pages, for example:

given the above I can move between all the pages above and the selected will be displayed emboldened, if I want to move to the next group of pages I can either click the ellipse button or if I’m on page 5 I can use the > button both will display the next group starting from 6. After racking my brain I came up with an algorithm to support this with no viewstate being available.

Basically we can use the current page number to find the last page number in this current group, i.e. if were displaying groups of 3 and were on page 4 the last number in this group is 6 the first group is 1-3. We can then use this to find the start number for the group by taking this number and take off the group size – 1 (2 in this case). The only thing we need to provision for is that we haven’t gone over the page count which can be done by checking which is minimum between the last page number for the group or the page count. Once we have the boundaries of the group we can work out which buttons to display and which page to display fairly easily.

Here is the code to demonstrate:

  
public static MvcHtmlString Pager(this HtmlHelper helper, int currentPage, int pageSize, int totalItemCount, object routeValues) 
{  	
    // how many pages to display in each page group const  	
    int cGroupSize = 5;  	
    var pageCount = (int)Math.Ceiling(totalItemCount / (double)pageSize);   	
    
    // cleanup any out bounds page number passed  	
    currentPage = Math.Max(currentPage, 1);  	
    currentPage = Math.Min(currentPage, pageCount);  	
    
    var urlHelper = new UrlHelper(helper.ViewContext.RequestContext, helper.RouteCollection);  	
    var container = new TagBuilder("div");
    container.AddCssClass("pager");  	
    var actionName = helper.ViewContext.RouteData.GetRequiredString("Action");   	
    
    // calculate the last page group number starting from the current page  	
    // until we hit the next whole divisible number  	
    var lastGroupNumber = currentPage;  	
    while ((lastGroupNumber % cGroupSize != 0)) lastGroupNumber++;   	
    
    // correct if we went over the number of pages  	
    var groupEnd = Math.Min(lastGroupNumber, pageCount);   	
    
    // work out the first page group number, we use the lastGroupNumber instead of  	
    // groupEnd so that we don't include numbers from the previous group if we went  	
    // over the page count  	
    var groupStart = lastGroupNumber - (cGroupSize - 1);   	
    
    // if we are past the first page  	
    if (currentPage > 1)  	
    {  		
        var previous = new TagBuilder("a");  		
        previous.SetInnerText("<");  		
        previous.AddCssClass("previous");  		
        var routingValues = new RouteValueDictionary(routeValues);  		
        routingValues.Add("page", currentPage - 1);  		
        previous.MergeAttribute("href", urlHelper.Action(actionName, routingValues));  		
        container.InnerHtml += previous.ToString();  	
    }   	
    
    // if we have past the first page group  	
    if (currentPage > cGroupSize)  	
    {  		
        var previousDots = new TagBuilder("a");  		
        previousDots.SetInnerText("...");  		
        previousDots.AddCssClass("previous-dots");  		
        var routingValues = new RouteValueDictionary(routeValues);  		
        routingValues.Add("page", groupStart - cGroupSize);  		
        previousDots.MergeAttribute("href", urlHelper.Action(actionName, routingValues));  		
        container.InnerHtml += previousDots.ToString();  	
    }   	
    
    for (var i = groupStart; i <= groupEnd; i++)  	
    {  		
        var pageNumber = new TagBuilder("a");  		
        pageNumber.AddCssClass(((i == currentPage)) ? "selected-page" : "page"); 				
        pageNumber.SetInnerText((i).ToString());  		
        var routingValues = new RouteValueDictionary(routeValues);  		
        routingValues.Add("page", i);  		
        pageNumber.MergeAttribute("href", urlHelper.Action(actionName, routingValues));  		
        container.InnerHtml += pageNumber.ToString();  	
    }   	
    
    // if there are still pages past the end of this page group  	
    if (pageCount > groupEnd)  	
    {  		
        var nextDots = new TagBuilder("a");  		
        nextDots.SetInnerText("...");  		
        nextDots.AddCssClass("next-dots");  		
        var routingValues = new RouteValueDictionary(routeValues);  		
        routingValues.Add("page", groupEnd + 1); 
        nextDots.MergeAttribute("href", urlHelper.Action(actionName, routingValues));  		
        container.InnerHtml += nextDots.ToString();  	
    }   	
    
    // if we still have pages left to show  	
    if (currentPage < pageCount)  	
    {  		
        var next = new TagBuilder("a");  		
        next.SetInnerText(">");  		
        next.AddCssClass("next");  		
        var routingValues = new RouteValueDictionary(routeValues);  		
        routingValues.Add("page", currentPage + 1);  		
        next.MergeAttribute("href", urlHelper.Action(actionName, routingValues));  		
        container.InnerHtml += next.ToString();  	
    }   	
    
    return MvcHtmlString.Create(container.ToString());  
}

To use it from the screen you simply do:

@Html.Pager(Model.Page, 20, Model.TotalItems, new {Model.Description})

Hopefully this will help someone who needs a pager to work this way as opposed to the more common offset implementations.

Advertisements

6 thoughts on “ASP.NET MVC 3 HtmlHelper for custom pager

Comments are closed.