Another early look at jQuery UI Grid in ASP.NET MVC – Data Types, Dataview, Pager, Sorting and Filtering

More than a year ago I took my first early look at jQuery UI Grid. Since that time a lot has changed in the plugin (and a lot will as the roadmap is placing Grid in 2.1 version of jQuery UI), so I've decided to take another early look at it.

This blog post talks about jQuery UI Grid plugin in Stage 3 of development, which is a pre-release version. Specific technical details may change before the final release of this product.

This is a minimal set of CSS and JavaScript files required by the plugin:

<script src="@Url.Content("~/Scripts/jquery-1.7.2.min.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/jquery.tmpl.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/ui/jquery.ui.core.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/ui/jquery.ui.widget.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/ui/jquery.ui.observable.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/ui/jquery.ui.dataview.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/ui/jquery.ui.grid.js")" type="text/javascript"></script>

You can find Grid specific files in its branch at GitHub (there is also a branch which is using JsRender instead of jQuery Templates).

The Grid is design to be used on table element. By default it will use the th elements text and data-** attributes for setting its columns and rowTemplate* options. Thanks to that following markup is all what we need:

<table id="products">
  <thead>
    <tr>
      <th data-type="number" data-property="Id">Id</th>
      <th data-type="string" data-property="Name">Name</th>
      <th data-type="string" data-property="Supplier.Name">Supplier</th>
      <th data-type="string" data-property="Category.Name">Category</th>
      <th data-type="string" data-property="QuantityPerUnit">Quantity Per Unit</th>
      <th data-type="number" data-property="UnitPrice">Unit Price</th>
      <th data-type="number" data-property="UnitsInStock">Units In Stock</th>
    </tr>
  </thead>
  <tbody>
  </tbody>
</table>

If the default behaviour doesn't give the desired effect, developer can provide values for columns and rowTemplate himself. The rowTemplate option should be set to jQuery Templates template and the columns option should be set to an array of objects.

Currently planned low-level abstraction for retrieving data in jQuery UI project is Dataview, there are also three reusable extensions than we can play with: localDataview, odataDataview and preloaderDataview. In my sample projects I'm already using LINQ Dynamic Query Library so I thought it would be nice to have Dataview which would generate sorting and filtering expressions in a way I need them. General structure of Dataview extension looks like this:

(function($, undefined) {
  $.widget('ui.linqDataview', $.ui.dataview, {
    widgetEventPrefix: 'dataview',
      options: {
        //Here you can provide any additional options you want
        ...
        //This function will be called by Dataview to retrieve data
        source: function (request, response) {
          //'request' contains informations for data scope:
          //- request.paging.offset
          //- request.paging.limit
          //- request.sort.length
          //- request.filter (with request.filter[property].operator and request.filter[property].value)
          ...
          //when you fetch the data the 'response' function should be called
          //with actual data as first parameter and total rows count as second
        }
      }
  });
}(jQuery));

You can find the implementation for my usage of LINQ Dynamic Query Library purposes here. Thanks to this the action method for serving data can be as simple as this:

public ActionResult Products(int skip, int take, string orderby, string filter)
{
  var products = null;  
  if (!String.IsNullOrWhiteSpace(filter))
    products = _northwindContext.Products.Where(filter);
  else
    products = _northwindContext.Products;

  return Json(new
  {
    rows = products.Include(p =&gt; p.Category).Include(p =&gt; p.Supplier).OrderBy(orderby).Skip(skip).Take(take),
    count = products.Count()
  }, JsonRequestBehavior.AllowGet);
}

The LINQ Dynamic Query Library will do the rest.

On the client side I just need to reference the script file with the Dataview extension and initialize the widgets:

...
<script src="@Url.Content("~/Scripts/grid-spf/dataview-linq.js")" type="text/javascript"></script>
...
<script type="text/javascript">
  $(document).ready(function () {
    //Create Dataview based on the extension
    var products = $.ui.linqDataview({
      //Default sorting
      sort: ['Id'],
      //Action URL
      resource: '@Url.Action("Products")'
    });

    //Initialize the grid
    $('#products').grid({
      source: products
    });

    //Load from Dataview
    products.refresh();
  });
</script>

To make full use of Dataview capabilities we should add pager, sorting and filtering. We can find basic implementation for those features in grid-spf folder at GitHub. Before using those a placeholder for the pager needs to be added to the table element:

<table id="products">
  ...
  <tfoot>
    <tr>
      <td colspan="7">
        <span id="pager"></span>
      </td>
    </tr>
  </tfoot>
</table>

Now it's time to add remaining references and extend the initialization code:

...
<script src="@Url.Content("~/Scripts/ui/jquery.ui.button.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/grid-spf/pager.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/grid-spf/grid-sort.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/grid-spf/grid-filter.js")" type="text/javascript"></script>
...
<script type="text/javascript">
  $(document).ready(function () {
    ...

    //Initialize the grid
    $('#products').grid({
      source: products
    })
    //Enable sorting
    .gridSort()
    //Enable filtering
    .gridFilter();

    //Initialize pager
    $("#pager").pager({
      source: products
    });

    //Load from Dataview
    products.refresh();
  });
</script>

Everything is up and running now. The complete project is available in my svn repository at CodePlex.