Thursday 11 August 2011

jqGrid MVC model

I really love jqGrid! If you haven’t used it before – try it! http://www.trirand.com/blog/
To be able to use it with ASP.NET MVC and JSON I created a generic model class to easier reuse it when passing it around in MVC actions in the controller.
To load jqGrid with data (JSON) I call an MVC action like this:
   1: $('#StoreGrid').jqGrid({
   2:         url: '<%=Url.Action("StoreList", "StoreController") %>',
   3:         datatype: 'json',
   4:         mtype: 'POST',
   5:         colNames: ['Store Name', 'Store Address'],
   6:         colModel: [
   7:             { name: 'Name', index: 'Name' },
   8:             { name: 'Address', index: 'Address'},
   9:         ]
  10: ...
Just an example, you get the point. Then in my action I do:
1: public JsonResult StoreList(string sidx, string sord, int page, int rows)
2: {
   3: ...
   4:     // Load data from repository
   5:     IQueryable<Store> stores = _repository.GetAllStores();
   6:     int totalRecords = stores.Count();
   7:     int totalPages = (int)Math.Ceiling(totalRecords/(double)rows);
   8:  
   9:     // Convert business model to view model
  10:     List<StoreListItem> storeListItems =
  11:         stores 
  12:         .Select(s => new StoreListItem(s.Id, s.Name, s.Address)
  13:         .ToList();
  14:  
  15:     // Create JSON data that jqGrid understands :-)
  16:     var jsonData = new JqGridJsonModelView<StoreListItem>
  17:        {
  18:          total = totalPages,
  19:          page = page,
  20:          records = totalRecords,
  21:          rows = storeListItems
  22:        };
  23:     return Json(jsonData, JsonRequestBehavior.AllowGet);
Ignore the repository stuff, paging and sorting is ignored here for simplicity – you shouldn’t – hence the totalPages calculation is redundant (but it wont be if you call e.g. GetStores() and pass paging and sorting parameters to it). Anyway, the key here is the JqGridJsonModelView<T> class. With it I can create any class (in this case StoreListItem) and it will create JSON data that jqGrid understands. Lovely! But what does it do? It’s really simple:
1: /// <summary>
2: /// See http://www.trirand.com/jqgridwiki/doku.php?id=wiki:retrieving_data#json_data
   3: /// </summary>
   4: public class JqGridJsonModelView<T> where T : IHaveJqGridCells
   5: {
   6:     /// <summary>
   7:     /// Total number of pages
   8:     /// </summary>
   9:     public int total { get; set; }
  10:  
  11:     /// <summary>
  12:     /// Current page
  13:     /// </summary>
  14:     public int page { get; set; }
  15:  
  16:     /// <summary>
  17:     /// Total number of records
  18:     /// </summary>
  19:     public int records { get; set; }
  20:  
  21:     public IList<T> rows { get; set; }
  22: }
The property names has a bad naming here, but it must corrolate with jqGrid (http://www.trirand.com/jqgridwiki/doku.php?id=wiki:retrieving_data#json_data). T here is the model, in this case StoreListItem. But what’s IHaveJqGridCells? Again, simple:
1: public interface IHaveJqGridCells
   2: {
   3:     string id { get; set; }
   4:  
   5:     string[] cell { get; }
   6: }
Bad naming, as said before, but it’s what jqGrid uses. So, all we have to do is let StoreListItem implement IHaveJqGridCells:
1: public class StoreListItem : IHaveJqGridCells
2: {
   3:     // Implement interface
   4:     public string id { get; set; }
   5:  
   6:     // Private properties to "disable" serialization (no need to be public anyway)
   7:     readonly string Name;
   8:     readonly string Address;
   9:  
  10:     public StoreListItem(int id, string name, string address)
  11:     {
  12:         this.id = id.ToString();
  13:         
  14:         Name = name;
  15:         Address = address;
  16:     }
  17:  
  18:     // Implement interface
  19:     public string[] cell
  20:     {
  21:         get
  22:         {
  23:             return new[] { Name, Address }
  24:         }
  25:     }
  26:  
Voila! We’re done! Now this class will be serialized into JSON (by MVC action). The serialized result will look like this:
1: {
   2:   "total" : "xxx", 
   3:   "page" : "yyy", 
   4:   "records" : "zzz",
   5:   "rows" : [
   6:     {"id" : "1", "cell" : ["Store 1", "Address 1"]},
   7:     {"id" : "2", "cell" : ["Store 2", "Address 2"]},
   8:       ...
   9:   ]
  10: }
Now it’s really easy to extend the model and/or reuse it.

Hope you like it!

Update (requested by danlimerick):
If you want to do sorting/paging. I usually use Dynamic Linq Query Library and make sure the naming in the jqGrid colModel corrolates to the naming in the repository:
1: IQueryable<Store> stores = _repository.GetStores(sidx, sord, (page - 1), rows);
   2:  
   3: ...
   4:  
   5: public IQueryable<Store> GetStores(string sortIndex, string sortOrder, int page, int rowsPerPage)
   6: {
   7:     return _context.Store
   8:                 .OrderBy(sortIndex + " " + sortOrder)
   9:                 .Skip(page * rowsPerPage)
  10:                 .Take(rowsPerPage);
  11: }
I wouldn’t call it elegant, but it’s a pretty convenient way.

2 comments:

  1. Totally agree that jqGrid is nice. I get why you ignore the sorting, paging etc. but that is the hard part of using jqGrid. Any chance of a follow up post focusing on how to do sorting and paging together and how to implement that in an elegant way in the repository?

    ReplyDelete
  2. Agree!
    The only thing is I don't know any *elegant* way of doing it. The same problem as with Linq2Sql when it comes to paging and sorting.
    I usually use DynamicQuery and pass jqGrid colModel names and make sure the naming correlates to the naming in the repository.

    ReplyDelete