As discussed in Oleg's answer, here is an adjusted beforeSelectRow that does appended selections.
In my case, our users are selecting a bunch of rows for export, so additional selections does not usually mean they want to start a new selection.
beforeSelectRow: function(rowid, e) {
var $this = $(this), rows = this.rows,
// get id of the previous selected row
startId = $this.jqGrid('getGridParam', 'selrow'),
startRow, endRow, iStart, iEnd, i, rowidIndex;
if (!e.ctrlKey && !e.shiftKey) {
//intentionally left here to show differences with
//Oleg's solution. Just have normal behavior instead.
//$this.jqGrid('resetSelection');
} else if (startId && e.shiftKey) {
//Do not clear existing selections
//$this.jqGrid('resetSelection');
// get DOM elements of the previous selected and
// the currect selected rows
startRow = rows.namedItem(startId);
endRow = rows.namedItem(rowid);
if (startRow && endRow) {
// get min and max from the indexes of the previous selected
// and the currect selected rows
iStart = Math.min(startRow.rowIndex, endRow.rowIndex);
rowidIndex = endRow.rowIndex;
iEnd = Math.max(startRow.rowIndex, rowidIndex);
// get the rowids of selected rows
var selected = $this.jqGrid('getGridParam','selarrrow');
for (i = iStart; i <= iEnd; i++) {
// if this row isn't selected, then toggle it.
// jqgrid will select the clicked on row, so just ingore it.
// note that we still go <= iEnd because we don't know which is start or end.
if(selected.indexOf(rows[i].id) < 0 && i != rowidIndex) {
// true is to trigger onSelectRow event, which you may not need
$this.jqGrid('setSelection', rows[i].id, true);
}
}
}
// clear text selection (needed in IE)
if(document.selection && document.selection.empty) {
document.selection.empty();
} else if(window.getSelection) {
window.getSelection().removeAllRanges();
}
}
return true;
}