By now our demo is really beginning to flesh out a bit. You've probably created your own grid, read through some of the extensive documentation, and started to figure a few things out on your own. In our series, we've created a basic grid, populated it with remote data, and refined our column configuration a bit. This is a lot of information, encompassing in depth explanations on many key basic grid configuration options, writing our paging query, ajax data handling, and more. Now, let's get into some 'grid' stuff that might not be so obvious.

Toolbars

First, we're going to set ourselves up for the future. We're going to want to see some data, but in order to do so we're going to need some buttons to click on. This is a good time to talk about jqGrid's toolbar implementation. jqGrid makes it easy to add toolbars to it's display, but it's a bit of a departure from standard JQuery plugin behavior, as it doesn't allow you to create a toolbar from an existing DOM element, so you have to use script to define and create your toolbar and it's associated elements. The first thing you have to do is add the configuration attribute to your jqGrid config object:

jqGridDemo.js - Toolbar Config

view plain print about
1grid.jqGrid({
2    ...
3    toolbar:[true,"top"],
4    ...
5});

The toolbar config option takes a two element array for an argument. The first (boolean) element enables the toolbar. The second element defines the toolbar's placement. This value can be "top", "bottom" or "both". Adding this configuration attribute will cause jqGrid to automatically create a div for each toolbar defined. It uses a specific naming convention, every time, to create the toolbars: top - "t_" + grid element's id, bottom - "tb_" + grid element's id (i.e.: "t_gridTest"). If you ran your template now, you would see that an empty toolbar has been added to your grid display:

Now that we have a toolbar, we need to add some buttons. JQueryUI has a button object, for nice consistent buttons, so we'll create the DOM elements needed on the toolbar first, then make them into JQUI buttons.

jqGridDemo.js - Toolbar Buttons

view plain print about
1$('#t_gridTest').append('<button id=\"addButton\">Add</button><button id=\"exportButton\">Export</button>').addClass('customToolbar');

jqGridDemo.js - JQUI Buttons

view plain print about
1$('button#addButton').button({icons: {primary: 'add'}}).click(function(ev){
2    ev.preventDefault();
3    
4    return false;
5});
6
7$('button#exportButton').button({icons: {primary: 'page_white_go'}, disabled: true}).click(function(ev){
8    ev.preventDefault();
9    
10    return false;
11});

Here we've created an Add and an Export button on our toolbar, while also adding a new CSS class (customToolbar). Then I made JQUI buttons of our new toolbar buttons, applying appropriate icon classes (defined in our css file). I've also disabled the Export button for the moment, but we'll go into that later.

OK, now we have a toolbar with some nice, pretty buttons on it, let's start talking about some more grid-centric things, like selection models. Data grids serve a wide range of purposes. In the basic model, we were just viewing data, even if we did allow for sorting and paging. We could've just created a table in DOM and used jqGrid's tableToGrid method to make a simple view table. But, chances are you're writing a Data Grid implementation to interact with data. And a common action is to select a record (or records) to interact with.

Multiselect

There are, essentially, three types of selection actions with a Data Grid: individual record selection, multiple record selection, and specific cell selection. The act of any of these selections is an event, and jqGrid includes the ability to add custom Event Handlers directly to the grid configuration. In our implementation, I want to show working with multiple selections, so we need to add a few configuration options. First, let's setup for multiselection:

jqGridDemo.js - Multiselect Configuration

view plain print about
1grid.jqGrid({
2    ...
3    multiselect: gridMultiSelect >
0,
4    multiselectWidth: 25,
5    ...
6});

These are direct grid configuration options. The first, multiselect, is a boolean value that, if true, will automatically create a column of checkboxes on our grid. The second option, multiselectWidth, defines the width of that checkbox column. Notice that I've tied the multiselect attribute to the gridMultiSelect global variable I added a few posts back. If we change that global variable now (gridMultiSelect = 1) you will see it's effects when you reload the page.

And now we have checkboxes for multiple record selection. The user can select, or deselect, records at will, or even 'select all' by clicking the checkbox in the header column (NOTE: this only selects all currently loaded records in the grid). That gridMultiSelect variable is also an offset value, when calculating column positions in our JSON remapping. The inclusion of the checkbox column (multiselect) requires us to shift our positions by one space.

Now there are two things we have to think about: 1) What do we do when a user clicks a row (or selects 'all')? and 2) How do we know what's been selected?

Well, there are all kinds of things you can do with the records selected. What we'll talk about, in our example, is correcting a small issue with jqGrid itself. Let me explain. jqGrid keeps an array of id's, for all selected records, in the read-only grid attribute selarrrow. You can programmatically access those at any time by grabbing that variable from jqGrid.

view plain print about
1grid.jqGrid('getGridParam','selarrrow');

Id's

But if you play with it a bit you'll discover a few things. The first thing you'll figure out is that we aren't (yet) getting the ID of our records, getting a generic rowcount for our id instead. This is because we've used the function datatype, and remapped our JSON to our column configuration, so the id attribute of the jsonReader is basically being ignored. (I've presented a fix for this to the jqGrid team, which they will hopefully implement in the future.) For this reason, we have to set a key in our column model. This will setup an internal keyIndex variable that jqGrid uses in it's processes. Under normal circumstances, the key attribute would be defined on your ID column in the column model. When remapping JSON output, though, this isn't how this would be accomplished. You would need to review your incoming JSON, figure out the index of the ID column in the response, then apply that key attribute to that column index in the column model configuration. In our example the ID column is still first, lining up correctly, but in more complex column models and queries this can be different. Here's how you add that to the column model configuration.

jqGridDemo.js - Column Model Configuration

view plain print about
1colModel: [
2    {name: 'ID', hidden: true, key: true},
3    ...
4],

The importance in this can be viewed in the output. Here's a sample of the before and after difference in what jqGrid generates:

Grid Row Output - Before Key Definition

view plain print about
1<tr id="1" class="ui-widget-content jqgrow ui-row-ltr" role="row" tabindex="-1">

Grid Row Output - After Key Definition

view plain print about
1<tr id="FF318BAB-3048-71C2-17E1634637074ECF" class="ui-widget-content jqgrow ui-row-ltr" role="row" tabindex="-1">

Why is this important? When you go to the next page of records, if you hadn't applied the key then the first record displayed would also show an id of 1. And this now brings us to the bit that we're going to address with jqGrid. If you select a few rows, pull the selarrrow, then page and select some more and check again, you will see that jqGrid isn't tracking id's across paging requests. This means that, if you go back to page 1, your original selections are no longer checked. For some this may be ok, but many users will expect different behavior. This is part of a developer's life, is anticipating user behavior, and adjusting to meet good usability guidelines.

Event Handlers

So, what is expected behavior? If a user selects a record, then goes to another page, and then returns to the original page, then the user will expect their selections to have been maintained across paging requests. This becomes especially important to users working with very large amounts of data. So, how do we handle this? Well, we setup an Event Handler for our selections, and programmatically control it. First, let's create another global variable to hold selected id's.

view plain print about
1var gridCols = {set:false},
2    gridMultiSelect = 1,
3    selArr = [];

This is just an array, just like jqGrid uses internally. Next thing we'll do is setup some Event Handlers. Any time a selection (or deselection) is made, then we need to control the contents of our new selArr array. First we'll setup a method to apply to our onSelectRow grid Event Handler.

jqGridDemo.js - rowSelectionHandler

view plain print about
1var rowSelectionHandler = function (id, status) {
2    // process code here
3};

jqGridDemo.js - Grid Configuration Event Handler

view plain print about
1grid.jqGrid({
2    ...
3    onSelectRow: rowSelectionHandler,
4    ...
5});

A review of jqGrid's Events documentation shows that the onSelectRow attribute will apply an event handler, and passes two arguments: id - the record id of the row being selected, and status - a boolean value noting selection (true) or deselection (false). jqGrid's onSelectAll attribute is similar, passing idArr - an array of selected row id's, and status - if all were selected or deselected. We'll setup a handler for that as well.

jqGridDemo.js - selectAllHandler

view plain print about
1var selectAllHandler = function (id, status) {
2    // process code here
3};

jqGridDemo.js - Grid Configuration Event Handler

view plain print about
1grid.jqGrid({
2    ...
3    onSelectAll: selectAllHandler,
4    ...
5});

Now, it may be that we'll want to use these two methods for something other than keeping track of our new array. Ultimately, the work for handling our new array is the same for either, we just have to code in handling the difference between a single id being passed, or an array of id's being passed. We'll setup a separate method for the array manipulation, and call it from our new handler methods.

jqGridDemo.js - Handlers and selectionManager

view plain print about
1var rowSelectionHandler = function (id, status) {
2    selectionManager(id, status);
3 // anything else
4};
5
6var selectAllHandler = function (idArr, status) {
7    selectionManager(idArr, status);
8 // anything else
9};
10
11var selectionManager = function (id, status) {
12    // was it checked (true) or unchecked (false)
13    if(status){
14        // if it's just one id (not array)
15        if(!$.isArray(id)){
16            // if it's not already in the array, then add it
17            if($.inArray(id,selArr) < 0){selArr.push(id)}
18        } else {
19            // which id's aren't already in the 'selected' array
20            var tmp = $.grep(id,function(item,ind){
21                return $.inArray(item,selArr) < 0;
22            });
23            // add only those unique id's to the 'selected' array
24            $.merge(selArr,tmp);
25        }
26    } else {
27        // if it'
s just one id (not array)
28        if(!$.isArray(id)){
29            // remove that one id
30            selArr.splice($.inArray(id,selArr),1);
31        } else {
32            // give me an array without the 'id's passed
33            // (resetting the 'selected' array)
34            selArr = $.grep(selArr,function(item,ind){
35                return $.inArray(item,id) >
-1;
36            },true);
37        }
38    }
39    $('#t_gridTest button#exportButton').button((selArr.length >
0)?'enable':'disable');
40};

I've tried to comment the code above to explain our new selectionManager method. The only line I haven't explained is the last, which either enables or disables the Export button in the toolbar, depending on if any items are selected or not. Now, if you made selections, paged through and made other, deselected items, even selected 'all', you could look at the selArr variable and see that we are now tracking all selected records across paging requests. The only thing missing is that these records aren't selected (visually) when you return to a page. We can rectify that by applying a handler to our gridComplete attribute.

jqGridDemo.js - - Grid Configuration Event Handler

view plain print about
1grid.jqGrid({
2    ...
3    gridComplete: gridLoadInit,
4    ...
5});

jqGridDemo.js - gridLoadInit

view plain print about
1var gridLoadInit = function () {
2    // if the 'selected' array has length
3    // then loop current records, and 'check'
4    // those that should be selected
5    if(selArr.length >
0){
6        var tmp = grid.jqGrid('getDataIDs');
7        $.each(selArr, function(ind, val){
8            var pos = $.inArray(val, tmp);
9            if(pos > -1){
10                grid.jqGrid('setSelection',val);
11            }
12        });
13    }
14};

jqGrid's gridComplete attribute allows us to define an Event Handler that fires once data has loaded into the grid (this is not to be confused with loadComplete, which occurs after every server request.) What we're saying here is, after the data is rendered in the grid we will loop the selArr array and 'check' any id's that match any records displayed in our grid. This gridLoadInit method, that we've created, will now run anytime the grid's data is reloaded (initial load, paging requests, sorting, etc).

And so we've created a solution to rectify a small oversight within jqGrid. In the process we covered the importance, and the process, behind properly identifying a key column, in setting proper row id's, how to add a checkbox column through the multiselect attribute, how to apply event handlers to our grid, as well as adding a toolbar and filling it with controls. Next post we'll get to binding event handlers to our Action column icons, and search for some other nuggets to impart. Until then, I hope this all helps someone, and sample code is located in the Download link at the bottom of the post.