Adding AJAX-based server-side paging and sorting to MvcContrib Grid using jQuery
Edit: Check out my follow-up to this post for more information on the Codeplex demonstration / proof-of-concept project I’m working on.
MvcContrib is an excellent, well known Asp.NET MVC resource adding major functionality on top of the recent 1.0 MVC release. jQuery is an excellent, well-known javascript accelerator library that is tremendously extensible and powerful. Utilizing them together can improve the speed with which you can get an application into production and enhance the configurability and maintainability of same. There are pitfalls working with any framework, yes, but these two libraries, on top of the Asp.NET MVC framework, can help us deliver our data quickly and with an AJAXified (AJAXian?) flair.
jQuery already has many third-party plugins available that help the developer provide a better UI to their users, and on top of any HTML table to boot. Unfortunately, these libraries work with the data available on the client, so any sorting or paging done involves the entire data set that has been delivered to the client. Yes, plenty of commercial components exist that allow a developer to consume a web service or WCF endpoint to provide exactly what we’re going for, but
- A: we’ve all been burned by them before and
- B: let’s do it ourselves anyway.
Now, before we get started, a caveat: While this essay is for the standard MVC Viewengine, I’ve switched to Spark, and may have a typo here or there because of it. Now, to the meat of this course; let’s take a look at MvcContrib, and the simple way of generating a Grid using the HtmlHelper Grid<> extension. For starters, take a look at local guru David Hayden’s short introductory piece on the Grid and Pager Helpers. The syntax for using them are as follows:
<%Html.Grid<AwesomePeople>("peeps",
column => {
column.For(c => c.FirstName);
column.For(c => c.LastName);
column.For(c => c.Age);
}
);
%>
<%= Html.Pager((IPagination)ViewData["peeps"])%>
OK, easy enough. Here we’re utilizing the Grid<> and Pager HtmlHelper extension methods to create a nice rendered grid and pager widget. In order to have the plumbing work correctly we have to do one particular thing in our controller:
public ActionResult Index(int? page)
{
return View(AwesomePeople.FetchAll().AsPagination(page ?? 1, 25));
}
AsPagination wraps your IEnumerable or IQueryable data with IPagination, which adds fields to allow your pager to do what it needs to do — namely, provide total page and item counts, along with IsFirst and IsLast capability.
To work AJAX into this equation we’re going to introduce jQuery.ajax():
$.ajax({
type: "POST",
url: "some.php",
data: "name=John&location=Boston",
success: function(msg){
alert( "Data Saved: " + msg );
}
});
So where to get started? While researching this topic, I came across this article by Mike Gold that gave me some clues about how to handle it. It’s very easy to get jQuery.ajax() and your controller actions playing nice together, but how then to get the grid to render? On top of all of this, MvcContrib Grid<> doesn’t even support sorting! Where do we begin?
For starters, let’s determine how to set up our jQuery.ajax() function. I decided right away that I’m going to need a javascript function that can be used to provide ajax capability for any grid on our page, even when there are multiple grids. To accomplish this, I’ll definitely need to pass into my function the name of the grid’s parent element (I’m rewriting the whole grid markup) and the name of the controller action that will serve as my data source. For sorting purposes, I’ll need to pass in the column to be sorted upon, and for paging purposes I’ll need to pull in the page number whose results I’m looking for. Finally, I also felt that my javascript function is probably the easiest place to maintain my sort order as well — luckily that means I won’t need to pass it in as a parameter. Here’s what I came up with:
function UpdateGrid(griddiv, controller, col, page) {
// when sorting, page is undefined or the same
// when paging, column is the same
if ($(griddiv).attr("currentpage") == undefined && $(griddiv).attr("sortcol") == undefined && $(griddiv).attr("direction") == undefined) {
// init
$(griddiv).attr("currentpage", 1).attr("direction", "ASC").attr("sortcolumn", col);
}
else {
if (page !== undefined && $(griddiv).attr("currentpage") !== page) {
// changing page
$(griddiv).attr("currentpage", page);
}
else {
// page being the same means we're sorting
if ($(griddiv).attr("sortcolumn") == col) {
// same column means we need to switch ascending and descending
if ($(griddiv).attr("direction") == "ASC") {
$(griddiv).attr("direction", "DESC");
}
else {
$(griddiv).attr("direction", "ASC");
}
}
else {
// different column means we're starting with ascending
$(griddiv).attr("direction", "ASC");
}
$(griddiv).attr("sortcolumn", col);
}
}
$.ajax({
type: "POST",
url: controller,
data: ({ ColumnName: $(griddiv).attr("sortcolumn"), PageNum: $(griddiv).attr("currentpage"), Controller: controller, Griddiv: griddiv, Direction: $(griddiv).attr("direction") }),
success: function(msg) {
$(griddiv).html(msg);
},
error: function(XMLHttpRequest, textStatus, errorThrown) {
alert("Data Failed to Sort: " + XMLHttpRequest);
}
});
}
We’re using jQuery.attr() to persist the sorted column name, the current page, and the sort direction on the grid parent element. This way, when a different grid is sorted/paged through, we’ll be able to come back to our previously sorted grid and pick up right where we left off. Our grid parent element can then be coded as such:
<div id="awesome_grid" />
Our controller is very straightforward, and is kind enough to send us back the information we’re sending to it, namely Controller and Griddiv:
public ActionResult Sort(string ColumnName, int? PageNum, string GridDiv, string Controller, string Direction)
{
// page number is pretty much never null
// but with the coalescing operation, why be lazy?
int page = PageNum ?? 1;
// let's have a default sort order. we're hard coded to our Model, so
// it's not a concern if we hard code the default sort order.
string column = (ColumnName ?? "FirstName") == "undefined" ? "FirstName" : ColumnName;
// default sort direction.
string direction = (Direction ?? "ASC") == "undefined" ? "ASC" : Direction;
// Linq to SQL data context
DataLayer.DataContext dc = new DataContext(_conn);
// AsPagination == from the MvcContrib.Pagination namespace.
// also taking advantage of the dynamic query library for AJAX
var peeps = dc.AwesomePeople.OrderBy(column + " " + direction).AsPagination(PageNum ?? 1, pageSize);
ViewData["peeps"] = peeps;
ViewData["controller"] = Controller;
ViewData["griddiv"] = GridDiv;
return View(PeepsGrid);
}
You can see that we’re not returning our sorted column name nor the sort direction. Our javascript function took care of persisting these, so we’re only returning those items that our component will need so that the grid can create sortable headers and our pager widget can create its paging UI. We’ll need to create a separate .aspx page that we’ll use as our grid component, and then use the Grid<> code from above (with some minor changes). We don’t need to include much on our control because we’re combining our model and view in order to get the markup, but then we’re just returning the markup to our earlier jQuery.Ajax() call — we’re not rendering a completely new page.
In order to create a sortable header we need to modify the header of our Grid Column. Where before we had
column.For(c => c.FirstName);
we now need to have
column.For(c => c.FirstName).Header("<th><a href=\"javascript:UpdateGrid('" + ViewData["griddiv"] + "','" + ViewData["controller"] + "','FirstName');\">First Name</a></th>");
This is by far my least favorite thing, but I haven’t extended MvcContrib Grid<> yet. It’s on my list, and I’ll post when I get it done. I did, however, extend the pager component. For starters, you’ve downloaded the MvcContrib source already, right? Fire it up, and navigate to MvcContrib.UI.Pager and open up Pager.cs. Add this code right here:
/// <summary>
/// Renders a pager component from an IPagination datasource, but pointing to a javascript function.
/// </summary>
public class AjaxPager : Pager
{
public AjaxPager(IPagination pagination, HttpRequestBase request, string JSFunction, string Controller, string GridElement) : base(pagination,request)
{
_controller = Controller;
_gridelement = GridElement;
_functionname = JSFunction;
_useAjax = true;
}
}
Next, you’ll need to add those protected variables to the base Pager class:
protected string _controller;
protected string _gridelement;
protected string _functionname;
protected bool _useAjax = false;
Afterwards, modify the CreatePageLink() function to render a javascript link instead of a controller action link:
private string CreatePageLink(int pageNumber, string text)
{
if (_useAjax)
{
return string.Format("<a href=\"javascript:{0}('{1}','{2}',null,'{3}')\">{4}</a>", _functionname, _gridelement, _controller, pageNumber, text);
}
else
{
string queryString = CreateQueryString(_request.QueryString);
string filePath = _request.FilePath;
return string.Format("<a href=\"{0}?{1}={2}{3}\">{4}</a>", filePath, _pageQueryName, pageNumber, queryString, text);
}
}
We now have an active pager component, but no pager extension methods that our HtmlHelper can utilize. To remedy this, open up PagerExtensions.cs, and towards the bottom (right above or below the Pager implementation, for instance), enter this:
public static Pager AjaxPager(this HtmlHelper helper, IPagination pagination, string JSFunction, string Controller, string GridElement)
{
return new AjaxPager(pagination, helper.ViewContext.HttpContext.Request, JSFunction, Controller, GridElement);
}
Add tests if you would like, and recompile MvcContrib.dll. Update your reference or recompile your project to do it automatically, and you should now have access to the .AjaxPager extension method. Use it like so:
<%=Html.AjaxPager(ViewData["peeps"],"UpdateGrid",ViewData["controller"],ViewData["griddiv"])%>
Finally, to get this whole process started, include the following function on your view:
<script type="text/javascript">
$(document).ready(function() {
UpdateGrid("#awesome_grid","/Home/Sort", "FirstName");
});
</script>
This function gets the grid’s parent element and controller action persisted, along with its default sort column. If you had multiple grids, you would have multiple calls to UpdateGrid, and they could each have their own target, action, and default sort column.
And that’s it! It was quite a journey, but by cleverly extending where we can, and utilizing the proper accelerator libraries, we can achieve a sortable, pageable, AJAX-based grid that does most of its processing on your big iron, i.e. your DB or application servers, and minimizes the amount of bandwidth and client processing power needed.
Ideally, I would like to have a library that extended MvcContrib itself, since MvcContrib is under active development. Extending the Pager and Grid helper methods without modifying the code would make them more future-proof. Also, I’m going to work on making a sample project available so that folks can see how everything’s put together.
Summary: One of the biggest challenges of a web-based business application developer involves presenting data to the user in a fast, useable fashion. Sortable, pageable grids solve this dilemma by introducing the arduous process of creating the grids in the first place. Leveraging and extending MvcContrib and jQuery to provide fast, asynchronous updates (from data sorted and paged on the server) to the user is a win.

Is there a full example project available?
I didn’t do a full example with the MVCContrib edits — I was able to get the grid sorting and paging done with jQuery.ajax(). The library and full example are at http://mvcajax.codeplex.com, which is a little something something I put together.
Thanks very much.
I’m just interested in the paging part – Do you have a sample of just paging with no sorting ?
Please email me if you do – I need to get some ajax server side paging into my project and have struggled on this one.
Thanks
Unfortunately, Steve, I don’t have the paging separate from the sorting. Having said that, as long as you’re returning an MvcContrib IPagination object, you’re 95% of the way there.
Now, I haven’t tested what I’m about to throw your way, BUT…once you enable paging/sorting through MvcAjax you should probably be able to use jQuery to strip out the header links and disable sorting.
Do you think it would be worth having an option to remove the sorting feature? In your project, why are you disallowing sorting?