While there are better choices (ExtJS for instance) for component and architecture libraries, some time ago someone entrenched our current system in JQuery and JQueryUI. But, since JQueryUI is already so prevelent within the system, it would be very time consuming to replace it. JQueryUI isn't bad, by any means, just incomplete, from the standpoint of building "applications". "But, JQueryUI is the bomb! How can you say such things?" Well...

When writing "applications", there are several key components missing from JQueryUI (Trees, Menus, Charts, etc), but one of the most important is a robust Data Grid. Luckily, the JQuery Plugin system is large, so there are options. On the other side of the coin, we need something that at least appears to be like it's JQueryUI counterparts (skinned by ThemeRoller), even if it's architecture is inconsistent. In an effort to provide a consistent user experience, and create visual and usability "standards" within our system, we undertook evaluating components that would work with JQueryUI while also meeting our application's standards and needs. For the Data Grid, we finally decided on jqGrid.

jqGrid is a free and open source JQuery plugin for the dynamic representation of tabular data. The primary goals of it's developer are two fold: speed, and independence from server-side technology and database backend. In jqGrid, we gain a component that is capable of integrating with our backend systems, automatically appears like our other interface components, allows for heavy conditional customization of output, and has a robust API for data manipulation, as well as hooks for working with other aspects of JQueryUI (like Drag and Drop and Sorting).

First things first, you need some files. There are a few dependencies, specifically JQuery and JQueryUI. Since every page of our application uses these, they are already included in our page header custom tag. After this, we need to jqGrid files. These are already in our codebase, but they can be built from the jqGrid Download Page. Our files include all of the jqGrid packages, but it is possible to build a file that only includes the pieces you need for your implementation. For instance, in our example we use a custom jqGrid build excluding all of the features of the following subclasses: Editing, Subgrid, Grouping, and TreeGrid. Should you require any of those packages, it is easy to select only the options needed to trim your js files. (One item of note: It looks like the custom build only trims the minified file of the unneeded packages, whereas the 'src' file maintains all of the packages.)

Script and Style Includes

view plain print about
1<link type="text/css" rel="stylesheet" href="/resources/scripts/jquery-plugins/UI/css/ui-lightness/jquery-ui-1.8.16.custom.css" /><!-- JQueryUI CSS -->
2<link type="text/css" rel="stylesheet" href="/resources/scripts/jquery-plugins/jqgrid-4.3.0-custom/css/ui.jqgrid.css" /><!-- jqGrid CSS -->
3<link type="text/css" rel="stylesheet" href="/resources/styles/custom/jqGridDemo.css" /><!-- Custom CSS for our app -->
4<script type="text/javascript" src="/resources/scripts/jquery/jquery-1.4.4.js"></script><!-- JQuery -->
5<script type="text/javascript" src="/resources/scripts/jquery-plugins/UI/js/jquery-ui-1.8.16.custom.min.js"></script><!-- JQueryUI -->
6<script type="text/javascript" src="/resources/scripts/jquery-plugins/jqGrid-4.3.0-custom/js/i18n/grid.locale-en.js"></script><!-- jqGrid Localization File -->
7<script type="text/javascript" src="/resources/scripts/jquery-plugins/jqGrid-4.3.0-custom/js/jquery.jqGrid.min.js"></script><!-- jqGrid -->
8<script type="text/javascript" src="/resources/scripts/custom/jqGridDemo.js"></script><!-- Custom application script -->

You can always combine and compress files as needed, but for this we'll show our stylesheets and scripts in their load order. A very important piece here is the jqGrid Localization File. jqGrid includes multiple localization files, and it's important to include the one needed for your application, as this will set the text used in controls of the grid, date formatting, and more. Your grid will not work if you do not include a localization file.

Our next step is to add the few bits of html needed for our grid. jqGrid includes a package to convert an existing Table to Grid, but we'll be pulling all of our data from the server in the form of JSON, so we only need a few minor bits of html to get ready:

view plain print about
1<div id="gridCont1" class="gridCont">
2    <table id="gridTest" class="gTable"></table>
3    <div id="pager" class="gTable"></div>
4</div>

What I've included here are basically three key pieces. First, I have an overall container object for our grid, to which I've given a class of gridCont. I'm not going to use this container right away, but I'm including it here for future revision. Next I include the table element that will be the actual grid object, giving it an id of gridTest, so I have a hard reference for my JQuery object selector. Last, our grid will be paged, so I include a div element to be used as the grid's pager bar.

Now that we have our html it's time to start building our custom script. Most of what we need will go inside a document ready statement, but first I want to define a global variable for our script:

jqGridDemo.js - figure 1

view plain print about
1var gridCols = {set:false};

I'm defining this outside the document ready because later I'll need access to it outside the document ready statement. Next I'm going to create a var reference to our grid's primary html object that will be available throughout our document ready statement:

jqGridDemo.js - figure 2

view plain print about
1var gridCols = {set:false};
2
3$(document).ready(function(){
4    var grid = $('#gridTest'); // JQuery object reference of table used for our grid display
5    ...
6});

OK, now we get down to the nitty gritty. It's time to start defining the grid itself. Like most other JQuery plugins, jqGrid requires a configuration object to define it's appearance and general actions. You can find all of the available configuration options on the Options page of the jqGrid Wiki. Here we'll start with a very basic configuration, which we'll explain in blocks. First, we'll start with some of the basic display options:

jqGrid Config - basic display options

view plain print about
1grid.jqGrid({
2    width: 800,
3 height: 350,
4 blockui: true,
5 altRows: true,
6 deepemtpy: true,
7 ...
8});

These are just a few basic options for a minimal display. We've included the width and height of our grid, set it to block any user interaction during record load (during ajax calls), asked that we use alternating row colors for even and odd rows, and set it to use JQuery's empty() method when resetting cell contents.

After this, we want to setup some of the options related to our paging bar. You'll remember I said we wanted to use paged datasets. This is very important when working with a large amount of records. By paging our requests, we restrict our returned data to small, managable chuncks, which (hopefully) speeds query processing, reduces individual data transfer, and limits DOM writes for record rendering to a predefined number of records at a time.

jqGrid Config - pager options

view plain print about
1grid.jqGrid({
2    ...
3 pager: '#pager',
4 toppager: true,
5 pagerpos: 'left',
6 rowNum: 50,
7 rowList: [10,20,30,40,50],
8 viewrecords: true,
9 ...
10});

Here we've created a pager, and set it to the pager div element we put in the html. Normally the pager is only at the bottom, but by adding the toppager option we've told jqGrid to also replicate the pager at the top of the grid. We've also set the position of the main pager controls to the left, said that we want it to 50 records at a time, and used the option that creates a dropdown where the user can change the number of records returned on requests. The last bit creates the 'View 1 - 50 of x' bit on the pager bar.

Next we look at a few options for defining column display and sorting:

jqGrid Config - column and sorting options

view plain print about
1grid.jqGrid({
2    ...
3 sortname: 'ID',
4 sortorder: 'desc',
5 colModel: [
6     {name: 'ID'},
7 {name: 'TITLE'},
8 {name: 'POSTED'},
9 {name: 'VIEWS'}
10 ],
11 ...
12});

There are only a few pieces here. I've identified which column is sorted first, and in which order. I've also set the absolute minimum column model. Since I'm using ColdFusion on the backend, which returns serialized query results with capitalized column names, I use capitalized names here. We'll override this later when we begin refining our Column Model.

The last thing we're going to define are those options dealing with data. We're going to populate our demo grid with data from the server, which we'll return in JSON format. jqGrid has support for dealing with a number of different datatypes (json, xml, javascript, etc). Since ColdFusion's JSON format for it's native query object is a bit different than the standard, we're going to use the function datatype. The 'standard' JSON recordset return looks a little like this:

'Standard' JSON Recordset

view plain print about
1{
2    data: [
3     {
4     id: "FF318BAB-3048-71C2-17E1634637074ECF",
5 title: "The ColdFusion 8 AJAX Components Debate",
6 posted: "June, 05 2007 23:51:00",
7 views: 7667
8 },
9 {
10     id: "FD48C350-3048-71C2-17244BE88532DEC9",
11            title: "ColdFusion (7 or 8) With Apache Pt 2: Multi-Instance Configuration",
12            posted: "June, 08 2007 15:06:00",
13            views: 0
14 }
15 ]
16}

ColdFusion's JSON return, of it's native query object, is quite a bit slimmer by not repeating the column names with each record and having each record as a simple array rather than an object. We also need to add in some keys for working with paged recordsets. Our server side process will generate a JSON object similar to this:

Server Side JSON Return

view plain print about
1{
2    "totalCount": 4.0,
3    "message": "",
4    "success": true,
5    "recordCount": 169,
6    "pageIndex": 1,
7    "data": {
8        "COLUMNS": [
9            "ID",
10            "TITLE",
11            "POSTED",
12            "VIEWS"
13        ],
14        "DATA": [
15            [
16                "FF318BAB-3048-71C2-17E1634637074ECF",
17                "The ColdFusion 8 AJAX Components Debate",
18                "June, 05 2007 23:51:00",
19                7667
20            ],
21            [
22                "FD48C350-3048-71C2-17244BE88532DEC9",
23                "ColdFusion (7 or 8) With Apache Pt 2: Multi-Instance Configuration",
24                "June, 08 2007 15:06:00",
25                0
26            ],
27 ...
28        ]
29    }
30}

You can see the difference. Because of this difference we can not use jqGrid's standard json or jsonstring datatypes, so we'll have to write a function to parse the return data. For now, we'll just put a placer in our file:

jqGridDemo.js - in our document ready

view plain print about
1/*
2 * FUNCTION populateGrid
3 * Used as the 'datatype' attribute of the jqGrid config object, this method
4 * is used to handle the ajax calls and data manipulation needed to populate
5 * data within our jqGrid instance.
6 * @postdata (object) - this is the object passed as the 'postData' attribute
7 *                         of our jqGrid instance.
8 */

9var populateGrid = function (postdata) {
10    // request and parsing code will go here
11};

Then we start putting together our config options:

jqGrid Config - remote data options

view plain print about
1grid.jqGrid({
2    ...
3 prmNames: {page: "pageIndex", sort: "sortCol", order: "sortDir", rows: "pageSize"},
4 postData: {method: "getEntries", returnFormat: "JSON"},
5 datatype: populateGrid,
6 jsonReader: {
7        id: "ID",
8        root: function(obj){return obj.data.DATA;},
9        page: "pageIndex",
10        total: function(obj){return parseInt(obj.totalCount);},
11        records: function(obj){return parseInt(obj.recordCount);},
12        cell:""
13    }
14});

There are a few things going on here, so we'll cover them one by one. First, we're changing the names of the params that the grid will send to the server on each request. This isn't necessary (you can always write your methods to use the default jqGrid field names) but I wanted certain param names for certain things. The postData option is for additional data to send with each request. We use this option to tell ColdFusion what method to call for our data, and that we want our data automatically serialized into a JSON object. For out datatype we are using the populateGrid function we previously defined. When using a function datatype, the postData is automatically sent to any function you define.

The last option here is a configuration object for the jsonReader. The data we will retrieve from the server is still JSON, and the jsonReader is how we define how jqGrid interprets the data returned from the server. The id and page options reference specific key names within our JSON, with the id referencing a specific index in the Column Model, and the page referencing a key in the base JSON. The root, total and records options are set (for us) to use functions that return the object location of their specific bits. Each function takes the JSON as it's only argument, with a return of the specific key containing that bit of data. Notice, in those options that must return a numeric value, that we are using the parseInt() JavaScript method. Different dot revisions of the ColdFusion server will treat numeric values differently, and we are doing this to automatically convert, in the event that a string representation of the number was returned. The cell option needs to be present, but empty. This is something that will ultimately be defined by the function we're using for the datatype option. All of this is layed out in the jqGrid documentation of working with JSON Data.

Loading our page right now will give us a basic grid.

You'll notice that we have a grid, but no data. No, we haven't written our data model for our grid yet. In our next post on this, we'll write our initial data model, and fill out our datatype function. You can find sample code in the Download link at the bottom of this post.