Introduction
One way or the other, we are all used to providing paging abilities in our application. Conventional methods of paging may include one of the following:
-
A numbered pager
-
A text pager with links like next, previous, first and last
-
Or a combination of both
With the advent of a lot of new technologies, a lot of websites no longer follow these conventional methods. The pages where paging is needed does it for you automatically as and when you scroll (like "twitter"). Earlier they used to provide a single "More" button which does the same task of loading more data with user intervention, apart from scrolling. You might have guessed, this article is not about the conventional methods. Neither it is about the method where data is loaded when the user scrolls down the page, since I am not a fan of this method :). In this article I am going to discuss 2 methods by which paging could be accomplished using a single button. So, let's get started!!!
Background
I am working in a new project that requires paging. I wanted to get away from the conventional methods and so created this simple method of paging with just a single button. The sample project attached uses bootstrap for the basic layout of the site, which I think you would have guessed when you saw the screenshot above!
Method 1: [Mostly] Pure MVC Paging
In this method, I am going to use MVC partial views mostly and a bit of javascript. Given below is the contents of the Index
action that will get things startes.
@{
ViewBag.Title = "Index";
}
@section pageScripts
{
@Scripts.Render("~/bundles/paging")
}
@{
Html.RenderAction("PostsPartial", "MvcPaging");
}
To begin with, I have a BasePagingController
class that contains action results and methods shared by both the paging methods, which are given below:
public class BasePagingController : Controller
{
public ActionResult PostsPartial()
{
var model = GetModel(0);
return PartialView("PostsPartial", model);
}
public JsonResult CheckPostsStatus(int currentPageNumber)
{
var posts = GetPosts();
var totalPages = (int)Math.Ceiling((decimal)posts.Count() / 5);
return Json((currentPageNumber + 1) < totalPages, JsonRequestBehavior.AllowGet);
}
protected PostViewModel GetModel(int currentPageNumber)
{
var posts = GetPosts();
if (currentPageNumber == 0)
{
return new PostViewModel
{
Posts = posts.Take(NumberOfPosts).ToList()
};
}
var currentPage = currentPageNumber + 1;
return new PostViewModel
{
Posts = posts.Skip((currentPage - 1) * NumberOfPosts).Take(NumberOfPosts).ToList()
};
}
protected List<post> GetPosts()
{
}
private const int NumberOfPosts = 5;
}
</post>
PostPartial
is an action method that returns a partial view with the model it needs. This method is shared by this method of paging and the next method, for creating the initial view of the page. CheckPostsStatus
is another shared action method that is used by both the methods to "see" if there are more pages to follow. In the subsequent sections, I will discuss how even we could get away from this action method altogether. But for now, let's proceed! In the first line I call the GetModel
method to get the required model. I call this method with a 0, since this action is "rendered" when the page is loaded for the first time with the items for page 1.
The GetModel
method first checks the page number passed, if its 0, it's easy, just get the pre-defined (NumberOfPosts
) number of posts and return them. If not, I use a simple method to get the relevant posts according to the page number passed and return them.
Getting back to the PostPartial
action method, once the model is received, a partial view with the name PostPartial
is returned with the model received in the previous step. Let's have a look at this partial view next!
@model MvcPaging.Models.PostViewModel
<div id="content">
@Html.Partial("PostDisplayer", Model)
</div>
<div id="placeholder">
</div>
<div>
<a class="btn btn-more" id="moreBtn">
More <img id="moreBtnImg" src="@Url.Content("~/Content/images/loading.gif")" alt="loading..."/>
</a>
</div>
<input type="hidden" id="currentPage" name="currentPage" value="1"/>
This partial view is one of the most interesting, since it sets the stage for pretty much the entire idea! The first section (div[id="content"]
) contains the call to render the PostDisplayer
partial view. This partial view gets a "model" with a specific number of posts. The post list is iterted an entry for each post is created. Here is the content of this partial view:
@model MvcPaging.Models.PostViewModel
@foreach (var post in Model.Posts)
{
<div class="post-entry" id="@string.Format("post-{0}", post.PostId)">
<div>
<div class="pull-left" title="@post.PostTitle">
@post.PostTitle
</div>
<div class="pull-right" style="color: #BDBDBD">
<b>@post.Category</b>
</div>
</div>
<br/><br/><br/>
@Html.ActionLink("View", "ViewPost", "Details", new { questionId = post.PostId }, new { @class = "btn btn-primary" })
</div>
<hr/>
}
As I said before, for every entry in the posts list, I render a div
with the title in the first line along with the category. Then, in the next line I display a button to "view" the entry.
Now, lets get back to the PostPartial
partial view! Note div[id="placeholder"]
. This div
will be used for loading the content of the subsequent pages, loaded using the "more" button. To begin with, its just empty. Finally the last div
represents the "more" button that will be used to load the subsequent pages. This section just displays a simple button with the text "More". Whenever the button is clicked, an image appears next to this text which is displayed until the request successfully finishes or fails (refer to the screen shot below). Whatever we saw so far is pretty trivial. So let's get to the fun part. Whenever the user clicks on the "More" button, I place an asynchronous request using ajax by passing in the current page number (available in the currentPage
hidden variable, initially set to 1 on page load).
$(function () {
showHideMoreBtn('mvcpaging/checkpostsstatus');
$(document).on('click', '#moreBtn', function (e) {
e.preventDefault();
var currentPgNumber = parseInt($('#currentPage').val());
$('#moreBtnImg').show();
$.ajax({
type: 'GET',
url: siteRoot + 'mvcpaging/loadmoreposts',
data: { 'currentPageNumber': currentPgNumber },
dataType: 'html',
success: function (data) {
$('#placeholder').append(data);
$('html, body').animate({ scrollTop: $('#moreBtn').offset().top }, 1000);
showHideMoreBtn('mvcpaging/checkpostsstatus');
incrementPageNumber();
$('#moreBtnImg').hide();
},
error: function (a, b, ctx) {
$('#moreBtnImg').hide();
alert('error ' + ctx);
}
});
});
});
The sample project uses jQuery 2.0.3, which does not support the .live
method, that is used to bind events to elements that will be added in the future. So, I have used the .on
method to accomplish the same. In this case, line 4 impies that, starting from the document
object, i.e. everything, bind the click
event to an element with id moreBtn
, by calling the callback passed next. Thus when the button is clicked and the callback raised, event argument e is used to prevent the default action, which in this case, for the button is the click event. Then the current page number is found using the hidden variable currentPage
. Then I show the image within the "More" button so that the user knows that some activity is going on.
Then an ajax request is issued by using the $.ajax
method provided by jQuery. LoadMorePosts
is an action method that finds the posts based on the page number passed and returns it. This method is given below:
public ActionResult LoadMorePosts(int currentPageNumber)
{
var model = GetModel(currentPageNumber);
return PartialView("PostDisplayer", model);
}
Once the method successfully returns the partial view (remember? this is the same partial view used in the PostsPartial
partial view), the success
callback of the $.ajax
method is called. In here, I simply append the html returned in to the div[id="placeholder"]
element. Then in the next line, I just do some simple animation to move to the end of the page. In the next line, I call the showHideMoreBtn
method by passing the url to be used for it to issue a request, which is defined in the paging-base.js
file. The task of this function is to pass the current page number to a method called CheckPostsStatus
and then use the return value to determine whether the "More" button appears or not. Given below is the code for this method:
public JsonResult CheckPostsStatus(int currentPageNumber)
{
var posts = GetPosts();
var totalPages = (int)Math.Ceiling((decimal)posts.Count() / 5);
return Json((currentPageNumber + 1) < totalPages, JsonRequestBehavior.AllowGet);
}
Finally, I just increment the page number, and then finally hide the image within the "More" button. On the other hand, if the ajax request fails, the image is hidden and then an alert is displayed to indicate the user of the error. If you think there is more to this method, you are false! This is it, you now have a fully functional page that handles paging in a non-conventional way! Let's get to the next method!
Method 2: Paging using Mustache.js
In the next method, I am going to explain how I used mustache.js, a templating engine, for paging.
A Short Intro to Mustache
Mustache is fairly simple to use! Say you need to say hello to a user. You could define the following template:
Hello {{name}}
Then, when you pass the data as shown below:
{ "name" : "karthik" }
to this template, following is the result:
Hello karthik
Esasy, isn't it? Here is a fiddle you could use to understand this more! For the sake of completeness, here is the javascript required:
$(function(){
var data = { "name": "karthik" };
var template = "Hello {{name}}";
var func = Mustache.compile(template);
var output = func(data);
$('#content').html(output);
});
Here, in the second line the data required is defined. In line 2, the template is defined. Now in the next line, using the Mustache
factory method compile, the template defined earlier is compiled, so that it could be used subsequently. This function returns a "function" that could be called by passing in the relevant data, which is done in the next line. When this function is called by passing in the data, the modified template with data is returned. I then append this to the div defined. Given below is another example where I deal with an array, here is the fiddle:
$(function(){
var data = { "countries": [ {"name" : "India"}, {"name" : "USA"}, { "name": "Sweden"}] };
var template = "<ul>{{#countries}}<li>{{name}}</li>{{/countries}}";
var func = Mustache.compile(template);
var output = func(data);
$('#content').html(output);
});
In this case, the data passed contains an array of objects. Every object in the array contains a name
property with the corresponding value. In the case of this template mustache uses {{#property_name}}...{{/property_name}}
to indicate that what follows is a template for a list. In the above example, countries
is a list (array). The above template creates an unordered list, with each list item being an item in the array. {{name}}
as in the first example is used to print the value of the object in every item in the array.
Back to method 2!
Section before this was a very short introduction to mustache.js! Let me now explain how I put this in to use. MustachePagingController
takes care of the code needed for this method. To begin with, the Index
action method simply returns a view. In this view the PostsPartial
partial is used to display the initial set of posts. This was already discussed in the first method. Code listing below shows the Index
view:
@{
ViewBag.Title = "Index";
}
@section pageScripts
{
@Scripts.Render("~/bundles/paging-mustache")
}
@{
Html.RenderAction("PostsPartial", "MustachePaging");
}
To take care of paging using the mustache templating library, I have the relevant code in the paging-mustache.js
file. PostsPartial
displays the "More" button and this button is wired in the javascript file to append the subsequent page contents in to the "placeholder"
, as shown below:
$(function () {
showHideMoreBtn('mvcpaging/checkpostsstatus');
$(document).on('click', '#moreBtn', function (e) {
e.preventDefault();
var currentPgNumber = parseInt($('#currentPage').val());
$('#moreBtnImg').show();
$.ajax({
type: 'GET',
url: siteRoot + 'mustachepaging/loadmoreposts',
data: { 'currentPageNumber': currentPgNumber },
dataType: 'json',
success: function (data) {
var tmpl = Mustache.compile("{{#Posts}}<div class=\"post-entry\" id=\"post-{{PostId}}\"><div><div class=\"pull-left\"
title=\"{{PostTitle}}\">{{PostTitle}}</div><div class=\"pull-right\" style=\"color:
#BDBDBD\"><b>{{Category}}</b></div></div><br><br><br><a class=\"btn btn-primary\"
href=\"/Details/ViewPost?questionId={{PostId}}\">View</a></div><hr/>{{/Posts}}");
var output = tmpl(data);
$('#placeholder').append(output);
$('html, body').animate({ scrollTop: $('#moreBtn').offset().top }, 1000);
showHideMoreBtn('mvcpaging/checkpostsstatus');
incrementPageNumber();
$('#moreBtnImg').hide();
},
error: function (a, b, ctx) {
$('#moreBtnImg').hide();
alert('error ' + ctx);
}
});
});
});
In the above code listing, lines 14 to 19 are of prime importance. The template is the html required for every entry in the listing of posts: first line with the post title and the category, and few blank lines, finally followed by the "view" button and horizontal line as shown in the following image. The call to LoadMorePosts
returns a list (array) of posts, within a property called Posts
. That's why the format to indicate an array {{#Posts}}...{{/Posts}}
is used in the template string! This is also the reason for returning the list of posts within a property called Posts
within PostViewModel
instead of just returning a list!
After the call to Mustache.compile
in line 18, we get a function that could be used to get the final html result, by passing in the data received as part of the ajax call, which is a list (array). Once the result is received it is appended to the placeholder
and similar steps are carried out as in method 1.
In case you don't like having the template in your .js file, you could have it as a script
tag in your page, as shown below:
<script type="text/template" id="tmpl">
{{#Posts}}<div class=\"post-entry\" id=\"post-{{PostId}}\"><div><div class=\"pull-left\"
title=\"{{PostTitle}}\">{{PostTitle}}</div><div class=\"pull-right\" style=\"color:
#BDBDBD\"><b>{{Category}}</b></div></div><br><br><br><a class=\"btn btn-primary\"
href=\"/Details/ViewPost?questionId={{PostId}}\">View</a></div><hr/>{{/Posts}}
</script>
And then in the .js file, you could get this template using:
var tmpl = $('#tmpl').html();
Now, instead of having the messy code in the .js file, it's available in the variable with the help of the script template! The same steps as before can be used to compile and replace with the relevant values!
Advantages of using Method 2
One of the main advantages of using the 2nd method is limiting the amount of data transferred over the wire. Consider the image given below:
This image is from a sample run captured in fiddler. The amount of data for the first and third entries are more or less the same, so lets ignore them for now. Regarding the 2nd and 4th entries, method 1 response size is about 405 bytes. Whereas the same call in method 2 only requires 198 bytes! That's about 50% savings in the amount of data transferred! Even though it may not sound to be a big deal, it is! Consider what happens if you page 10 / 15 times! Guess now you get the picture! Any amount of data transfer saved is a good thing :) For example, consider the case when the amount of html is much higher than the data itself. In this case, I guess you can imagine the amount of data transfer avoided because only the data was transferred, but not the html itself!
What else?
Now that we have seen the 2 methods, let me throw what else could be done! To begin with the part where the initial set of posts are loaded can be removed and replaced with another method call during the document load event, where the contents of page 0 can be loaded asynchronously instead of loading it during the button click. This would simplify a lot of things!
Another improvement is, instead of having 2 methods - 1 for getting more data and 1 for checking "if" there is more data, they could be combined in to single method by returning an object with the data and the status of the more button.
I leave this part to you, the readers! Other stuff include things like dealing with empty post list, more error handling instead of just alert messages and many more!
History
Version 1.0 released