jqGrid multiselect: a little improvement
The jqGrid plugin is fantastic. It provides an improved user experience and makes your web application stand out from the rest. But sometimes the default behaviour it has is not what we like, and those are the times when we have to dive into the source code to tweak it. And today was one of those times for me.
We’ve implemented the jqGrid plugin in a couple of pages in our project at work. We needed some multiselect in one of them. This option seems very easy to implement: just add a couple of options at creation time and you’re done… well, not quite. JqGrid’s multiselect default behaviour is not really the standard one. I mean, it is a multiselect, just not what the regular user might be used to.
What I was expecting from a multiselect behaviour is to behave just as normal as long as no special key is pressed. I mean, if you have a row selected and click on another with no other key pressed, then it should select the new one and deselect the old row. Well, jqGrid’s standard options lets you choose between always regular behaviour, or always multiselect. You can’t have multiselect only when a special key is pressed.
I didn’t like it. So I changed it. So here I’m posting the code that needs to be changed in case somebody wants this behaviour too. I wrote some comments throughout the code to clarify what I’m doing a bit.
Please note that these changes are made to the 3.4.4 version of jqGrid. If you’re using some other version, consult first whether this behaviour is not already implemented… or even if these changes can be made at all.
File grid.base.js. Line 193 – function setSelection:
// We get a parameter telling us whether the special multiselect key has been pressed
// This parameter might not always be passed
$.fn.setSelection = function(selection,onsr,sd,multiselectKeyPressed) {
return this.each(function(){
var $t = this, stat,pt, ind;
onsr = onsr === false ? false : true;
if(selection===false) {pt = sd;}
else { ind = $($t).getInd($t.rows,selection); pt=$($t.rows[ind]);}
selection = $(pt).attr("id");
if (!pt.html()) {return;}
if($t.p.selrow && $t.p.scrollrows===true) {
var olr = $($t).getInd($t.rows,$t.p.selrow);
var ner = $($t).getInd($t.rows,selection);
if(ner >=0 ){
if(ner > olr ) {
scrGrid(ner,'d');
} else {
scrGrid(ner,'u');
}
}
}
// If multiselect is enabled but the key has not been pressed, it's as if multiselect was disabled
if(!$t.p.multiselect || (multiselectKeyPressed != null && !multiselectKeyPressed)) {
if($(pt).attr("class") !== "subgrid") {
if( $t.p.selrow ) {
// This only deselects the last selected row. Only one row was supossed to be selected simultaneously
// $("tr#"+$t.p.selrow.replace(".", "\\."),$t.grid.bDiv).removeClass("selected");
// This way we consider there may be more than one selected row. Deselects all selected rows:
$("#" + this.id + " tr.selected").removeClass("selected");
}
$t.p.selrow = selection;
$(pt).addClass("selected");
if ($t.p.multiselect) {
$t.p.selarrrow = new Array(); // Empty the array containing selected rows
$t.p.selarrrow.push($t.p.selrow); // Insert the new selected row
}
if( $t.p.onSelectRow && onsr) { $t.p.onSelectRow($t.p.selrow, true); }
}
} else {
$t.p.selrow = selection;
var ia = $.inArray($t.p.selrow,$t.p.selarrrow);
if (!$(pt).hasClass("selected") && ia != -1) ia = -1; // Might report as not in the array
if ( ia === -1 ){
if($(pt).attr("class") !== "subgrid") { $(pt).addClass("selected");}
stat = true;
$("#jqg_"+$t.p.selrow.replace(".", "\\.") ,$t.rows).attr("checked",stat);
$t.p.selarrrow.push($t.p.selrow);
if( $t.p.onSelectRow && onsr) { $t.p.onSelectRow($t.p.selrow, stat); }
} else {
if($(pt).attr("class") !== "subgrid") { $(pt).removeClass("selected");}
stat = false;
$("#jqg_"+$t.p.selrow.replace(".", "\\.") ,$t.rows).attr("checked",stat);
$t.p.selarrrow.splice(ia,1);
if( $t.p.onSelectRow && onsr) { $t.p.onSelectRow($t.p.selrow, stat); }
var tpsr = $t.p.selarrrow[0];
$t.p.selrow = (tpsr==undefined) ? null : tpsr;
}
}
function scrGrid(iR,tp){
var ch = $($t.grid.bDiv)[0].clientHeight,
st = $($t.grid.bDiv)[0].scrollTop,
nROT = $t.rows[iR].offsetTop+$t.rows[iR].clientHeight,
pROT = $t.rows[iR].offsetTop;
if(tp == 'd') {
if(nROT >= ch) { $($t.grid.bDiv)[0].scrollTop = st + nROT-pROT; }
}
if(tp == 'u'){
if (pROT < st) { $($t.grid.bDiv)[0].scrollTop = st - nROT+pROT; }
}
}
});
};
Line 1433: event handling:
$(ts).before(grid.hDiv).css("width", grid.width+"px").click(function(e) {
evt = window.event || e; // Get the event
td = (e.target || e.srcElement);
if (td.href) { return true; }
var scb = $(td).hasClass("cbox");
ptr = $(td,ts.rows).parents("tr.jqgrow");
if($(ptr).length === 0 ) {return false;}
var cSel = true;
if(bSR) { cSel = bSR(ptr.attr("id"));}
if(cSel === true) {
if(ts.p.cellEdit === true) {
if(ts.p.multiselect && scb){
$(ts).setSelection(false,true,ptr);
} else {
ri = ptr[0].rowIndex;
ci = !$(td).is('td') ? $(td).parents("td:first")[0].cellIndex : td.cellIndex;
try {$(ts).editCell(ri,ci,true,true);} catch (e) {}
}
} else if ( !ts.p.multikey || !e[ts.p.multikey] ) { // Check if the special multiselect key was pressed
if(ts.p.multiselect && ts.p.multiboxonly) {
// Pass whether the special multiselect key was pressed
if(scb){$(ts).setSelection(false,true,ptr,e[ts.p.multikey]);}
} else {
// Pass whether the special multiselect key was pressed
$(ts).setSelection(false,true,ptr,e[ts.p.multikey]);
}
} else {
if(e[ts.p.multikey]) {
$(ts).setSelection(false,true,ptr);
} else if(ts.p.multiselect && scb) {
scb = $("[id^=jqg_]",ptr).attr("checked");
$("[id^=jqg_]",ptr).attr("checked",!scb);
}
}
if(onSC) {
ri = ptr[0].id;
ci = !$(td).is('td') ? $(td).parents("td:first")[0].cellIndex : td.cellIndex;
onSC(ri,ci,$(td).html(),td);
}
}
e.stopPropagation();
})
But there’s still more. One of the great features of jqGrid is subgrid. This feature allows having a nested grid inside a row. When enabling the subgrid option, you’ll get one checkbox column. But this column it’s not really necessary, you might opt for hiding it out. If you do so when you have multiselect enabled, then the subgrids will look weird… they will have moved to the right, leaving an ugly blank space to the left… what happened?
When having multiselect enabled, the subgrid assumes you’ll always display the checkbox column. That’s all. So we can go the subgrid code and tweak it to check if the checkbox column is hidden.
File grid.subgrid.js, line 20 – Click event handler:
.click( function(e) {
if($(this).hasClass("sgcollapsed")) {
pID = $("table:first",ts.grid.bDiv).attr("id");
res = $(this).parent();
var atd= pos==1?'< td>< /td>':'';
// Begin modification
// To reach the grid we need $(this).parent().parent().parent()
$($(this).parent().parent().parent()[0].p.colModel).each(function(i) {
// If CB column exists and it's hidden, there's one cell less
if (this.name == 'cb' && this.hidden) atd= pos==1?'':'';
});
// End modification
_id = $(res).attr("id");
bfsc =true;
if($.isFunction(ts.p.subGridBeforeExpand)) {
bfsc = ts.p.subGridBeforeExpand(pID+"_"+_id,_id);
}
I posted the code here just to see what the changes are. If you’re going to be using this modification, probably it’s going to be easier if you just download the modified files and replace them with the ones you have: grid.base.js and grid.subgrid.js . Remember this modification is ONLY for version 3.4.4 of jqGrid!
Hope this helps! If you have any doubt, don’t hesitate to drop me a line!

