jqGrid and ASP.NET MVC - Configuration Import/Export

Today I'm going to write few words about configuration import and export in jqGrid. This feature can be very helpful, if you wish to allow your users with some customization (in our example we will add column chooser, so users can influence what they see).
We should start by adding standard placeholders for the grid:
<table id="jqgProducts" cellpadding="0" cellspacing="0"></table>
<
div id="jqgpProducts"></div>

Now we will write all necessary javascript:
<link href="../../Content/jquery-ui-1.7.2.custom.css" rel="stylesheet" type="text/css" />
<
link href="../../Content/ui.jqgrid.css" rel="stylesheet" type="text/css" />
<
link href="../../Content/ui.multiselect.css" rel="stylesheet" type="text/css" />
<
script src="/Scripts/grid.locale-en.js" type="text/javascript"></script>
<
script src="/Scripts/jquery.jqGrid.min.js" type="text/javascript"></script>
<
script src="/Scripts/ui.multiselect.js" type="text/javascript"></script>
<
script type="text/javascript">
  $(document).ready(function() {
    //Calling jqGridImport, which will import configuration
    $('#jqgProducts').jqGridImport({
      //We are expecting data in JSON format
      imptype: 'json',
      //URL to our action, which will return configuration
      impurl: '<%=Url.Action("ProductsGridConfiguration", "Home") %>',
      //Method for the request
      mtype: 'GET',
      //Addidtional data, which will be passed to our action
      impData: { id: 'jqgProducts' },
      //We inform the grid which property in object will hold configuration
      jsonGrid: { config: 'Settings' },
      //Function which will be called when import ends
      importComplete: function() {
        //We set up navigator wihout any standard buttons
        $('#jqgProducts').jqGrid('navGrid', '#jqgpProducts', {
          edit: false,
          add: false,
          del: false,
          search: false
        //because we will add our own button
        }).jqGrid('navButtonAdd', '#jqgpProducts', {
          //Our button text
          caption: 'Columns',
          //Our button icon (jQuery UI class)
          buttonicon: 'ui-icon-wrench',
          //Our button position
          position: 'last',
          //Our button tooltip
          title: 'Select columns',
          //Function which will be called on click
          onClickButton: function() {
            //We are showing column chooser
            $('#jqgProducts').jqGrid('columnChooser', {
              //Function which will be called when column chooser is closed
              done: function(perm) {
                //We check if user has accepted
                if (perm) {
                  //First we are resizing grid
                  var gridWidth = this.jqGrid('getGridParam', 'width');
                  this.jqGrid('setGridWidth', gridWidth);
                  //then we remap columns
                  this.jqGrid('remapColumns', perm, true);
                  //and in the end we want to post configuration
                  $.ajax({
                    type: 'POST',
                    //We are posting configuration in JSON
                    contentType: 'application/json; charset=utf-8',
                    //URL to our action, which store configuration
                    url: '<%=Url.Action("ProductsGridConfiguration", "Home") %>',
                    //Calling jqGridExport for our data
                    data: $('#jqgProducts').jqGridExport({
                      //We are exporting in JSON
                      exptype: 'jsonstring',
                      //We inform the grid what should be the name of property
                      root: 'Settings'
                    })
                  });
                }
              }
            });
          }
        });
      }
    });
  });
</script>

That's a lot of javascript. I hope that comments make it clear enough. We are exporting in JSON but also XML can be used as an alternative. There is also a possibility to import some data with configuration, but I won't dig into this at the moment.
Before we start writing our actions, we have to prepare a model binder for JSON, otherwise we won't be able to accept configuration:
/// <summary>
///
Model binder which allows binding JSON data to objects
/// </summary>
public class JsonModelBinder : IModelBinder
{
  #region IModelBinder Members
  public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
  {
    if (controllerContext == null)
      throw new ArgumentNullException("controllerContext");
    if (bindingContext == null)
      throw new ArgumentNullException("bindingContext");

    var serializer = new DataContractJsonSerializer(bindingContext.ModelType);
    return serializer.ReadObject(controllerContext.HttpContext.Request.InputStream);
  }
  #endregion
}

Now we can prepare classes for storing jqGrid configuration:
/// <summary>
///
jqGrid configuration container
/// It can have two properties:
/// - one for settings
/// - one for data
/// </summary>
[DataContract]
[ModelBinder(typeof(JsonModelBinder))]
public sealed class JqGridConfiguration
{
  #region Properties
  [DataMember]
  public JqGridSettings Settings { get; set; }
  #endregion
}

/// <summary>
///
jqGrid settings container
/// Description for every option is available here:
/// http://www.trirand.com/jqgridwiki/doku.php?id=wiki:options
/// Not all jqGrid options are included in this class (it can be extended)
/// </summary>
[DataContract]
public sealed class JqGridSettings
{
  #region Properties
  [DataMember]
  public JqGridColumnModel[] colModel { get; set; }

  ...
  #endregion

  ...
}

/// <summary>
///
jqGrid colModel options
/// Description for every option:
/// http://www.trirand.com/jqgridwiki/doku.php?id=wiki:colmodel_options
/// Not all jqGrid colModel options are included in this class (it can be extended)
/// </summary>
[DataContract]
public sealed class JqGridColumnModel
{
  ...
}

As you can see, I have skipped most of JqGridSettings and JqGridColumnModel implementation. Those classes contain only properties which represent jqGrid options.
Finally, we can write controller actions. Data will be provided by the action written in basics sample, so I will focus on actions which import and export configuration:
/// <summary>
///
Exports configuration for jqGrid in json
/// </summary>
/// <param name="id">
The jqGrid identifier</param>
/// <returns>The jqGrid configuration in json format</returns>
[NoCache]
public JsonResult ProductsGridConfiguration(string id)
{
  JqGridConfiguration configuration = null;
  //Do we already have configuration in Session
  if (Session[id] != null)
    //If yes, the use it
    configuration = (JqGridConfiguration)Session[id];
  else
    //If no, then create one
    configuration = new JqGridConfiguration()
    {
      Settings = new JqGridSettings
      {
        id = id,
        colNames = new string[] { "ProductID", "ProductName", "Supplier", "Category", "QuantityPerUnit", "UnitPrice", "UnitsInStock" },
        colModel = new JqGridColumnModel[]
        {
          new JqGridColumnModel() { name = "ProductID", align = "left" },
          new JqGridColumnModel() { name = "ProductName", align = "left" },
          new JqGridColumnModel() { name = "Supplier", align = "left" },
          new JqGridColumnModel() { name = "Category", align = "left" },
          new JqGridColumnModel() { name = "QuantityPerUnit", align = "left" },
          new JqGridColumnModel() { name = "UnitPrice", align = "left" },
          new JqGridColumnModel() { name = "UnitsInStock", align = "left" }
        },
        remapColumns = new int[] { 0, 1, 2, 3, 4, 5, 6 },
        rowNum = 10,
        sortname = "ProductID",
        sortorder = "asc",
        viewrecords = true
      }
    };

  //Those settings should be set always "just in case"
  configuration.Settings.url = Url.Action("ProductsGridData", "Home");
  configuration.Settings.datatype = "json";
  configuration.Settings.mtype = "GET";
  configuration.Settings.pager = "#jqgpProducts";
  configuration.Settings.width = "auto";
  configuration.Settings.height = "auto";

  //Return configuration
  return Json(configuration);
}

/// <summary>
///
Imports configuration for jqGrid
/// </summary>
/// <param name="configuration">
The jqGrid configuration</param>
/// <returns>An empty result</returns>
[AcceptVerbs(HttpVerbs.Post)]
public EmptyResult ProductsGridConfiguration(JqGridConfiguration configuration)
{
  //Store configuration in Session
  Session[configuration.Settings.id] = configuration;

  //Return empty result, it will be ignored
  return new EmptyResult();
}

Now we can go ahead and test it. You can download sample application from here, I hope you will find it useful.

5 comments:

Jeff Tanner said...

Hi

I am currently using jqgrid on MVC 2. My controller code is getting called via the url, but it just not populating the rows.

Is it possible you could provide a ZIP download for your project?

Tomasz Pęczek said...

Entire sample application for jqGrid can be downloaded as .zip file from my CodePlex repository (http://tpeczek.codeplex.com/releases/view/36997) - but it's for ASP.NET MVC 1. The most common reason of why this sample will not work with ASP.NET MVC 2 is security change in JsonResult. By default JsonResult throws an Exception if you try to use it with GET verb, you have to allow it like this: return Json(data, JsonRequestBehavior.AllowGet);

William Trout said...

The codeplex ZIP does not include this revision. You must use the SVN link provided.

Beemancdzi said...

The codeplex ZIP does not include this revision. You must use the SVN link provided.

Anonymous said...

Unfortunately, the export settings option does not work when using the formatting or integration with jq UI, because web.mvc.lib, carefully adds javascript values the table options.