JqGrid Performance - setRowData lags - performance

setRowData takes almost 5 seconds when the grid has 300 rows, 30 cols, and 4 frozen cols.
$("#tbl").jqGrid({
gridview: true,
url: '../controller/GetData',
datatype: "json",
rowNum: pageSize,
viewrecords: true,
loadtext: '',
loadui: 'disable',
rowList: [1000, 2000, 3000],
width: $(window).width() - LeftMargin,
shrinkToFit: false,
pager: '#dvPager',
pagerpos: 'left',
colNames: GetColNames(selectedViewName, viewCols),
colModel: GetColModel(selectedViewName, viewCols),
cmTemplate: {
title: false
},
recordtext: "Displaying {0} - {1} of {2}",
rowattr: function (rowData) {
},
//onCellSelect: function (rowid, iCol, cellcontent, e) {
// e.stopPropagation();
// proto.editcell(rowid, iCol, cellcontent, e);
//},
loadComplete: function (data) {..},
onPaging: function (data) {..},
serializeGridData: function (postData) {..}
});
Can you please guide me with the performance tuning tips?
In GetColModel we are binding columns as follows -
'Discount': {
name: 'Discount', index: 'Discount', width: colModelWidthDict['Discount'], title: false, sortable: false, resizable: colResizable, hidden: !bReadCalculationSellInfo,
formatter: function (cellValue, option, rowObject) {
if (rowObject.level == 0 || (rowObject.Flag == 0 && (rowObject.Discountable && rowObject.Discountable.toLowerCase() == 'no')))
return '';
else {
if (!proto.IsReadOnly(rowObject) && ((rowObject.Flag == 0 && (rowObject.Discountable && rowObject.Discountable.toLowerCase() == 'yes')) || rowObject.Flag > 0))
return proto.GetControls("Discount", option.rowId, "Numeric", rowObject.Discount, 6, 90)
else
return '<span class="amt">' + cellValue + '</span>';
}
},
unformat: function (cellValue, option) {
return cellValue;
},
cellattr: function (rowId, val, rowObject, cm, rdata) {
if (parseInt(rowObject.PPPChngDisc) == 1)
return ' style="color:red"';
}
}
//code for colModel - 1 column above

In general you should prefer to modify only small number of rows. If you modify one row then the web browser have to recalculate position of all other rows (500 rows) of the grid. In the case modifying 300 rows means 300*500 operations for the web browser.
If you fill the whole grid and you use gridview: true option then jqGrid places at the beginning all the data for the grid body in array of strings. After preparing of all information in string format jqGrid place the whole body of the grid as one operation. It's just one assigning innerHTML property of tbody. Thus it's more effective to reload the whole grid as to make modification of many rows of the grid in the loop using setRowData.
So I would recommend you to reload the grid in your case.
Moreover you should consider to reduce the number of rows in the page of grid. If you use rowNum: 500 you just reduce the performance of your page tin many times without any advantage for the user. The user can see about 20 rows of the data only. Only if the user scrolls he/she is able to see another information. In the same way the user could click next page button. Holding 500 rows force web browser make a lot of unneeded work. Modifying of the first row from 500 the web browser have to recalculate position of all 500 rows. Even if the user move the mouse over a row the web browser change the class of the hovering row and it have to recalculate position of all other from 500 rows event the rows are not visible.
One more remark. The implementation of frozen columns in jqGrid 4.6 is slow. Starting with the end of 2014 and publishing 4.7.1 with changing Lisanse and making jqGrid commercial I develop free jqGrid as fork based on jqGrid 4.7 (see the readme for more details). I implemented a lot of changes and new features in the fork. The recent changes includes many improvement of frozen columns feature. If you loads free jqGrid or just try it directly from GitHub by including the corresponding URLs (see the wiki) you will see that the performance of frozen columns is dramatically improved. Additionally frozen columns supports now row and cell editing in all editing modes. I plan to publish free jqGrid 4.9 soon. So I recommend you to try it.
UPDATED: You posted the code, which you use in comments below. I see the main problem in the implementation of Process function which will be called on change in the <input> fields on the grid. I recommend you to try something like this one:
function Process(contrl) {
var $cntrl = $(contrl),
$td = $cntrl.closest("tr.jqgrow>td"),
$tr = $td.parent(),
rowId = $tr.attr("id"),
newValue = $cntrl.val(),
$grid = $("#list"),
p = $grid[0].p,
colModel = p.colModel,
columnName = colModel[$td[0].cellIndex].name, // name of column of edited cell
localRowData = $grid.jqGrid("getLocalRow", rowId),
iColTotal = p.iColByName.total;
// modify local data
localRowData[columnName] = newValue;
localRowData.total = parseInt(localRowData.quantity) * parseFloat(localRowData.unit);
// modify the cell in the cell (in DOM)
//$grid.jqGrid("setCell", rowId, "total", localRowData.total);
$($tr[0].cells[iColTotal]).text(localRowData.total);
return false;
}
One can event improve the code and not use setCell at all. It's important to understand that local grid hold the data in the form of JavaScript object. The access to the data is very quickly. You can modify the data without any side effects existing with DOM elements. In the above code I use only relative search inside of the DOM of the row using closest and parent. It works very quickly. I recommend to save $("#list") in the variable outside of the Process (move var $grid = $("#list") in the outer function) and just use $grid variable. It will make access to the DOM of page very quickly. The line localRowData = $grid.jqGrid("getLocalRow", rowId) get the reference to internal JavaScript data object which represent the internal data of the row. By modifying the data by localRowData[columnName] = newValue; or localRowData.total = ... we make the half of job. One need only to change the value in the DOM on the cell Total. I used iColByName which exist in the latest sources of free jqGrid on GitHub. It get just the column index in colModel by column name. One can find the same information alternatively if one use more old jqGrid. The line $($tr[0].cells[iColTotal]).text(localRowData.total); shows how to assign manually the data in the cell. It's the most quick way to do this, but one assign the data without the usage formatter. For more common case (for example in case of frozen "total" column) one can use the commented row above: $grid.jqGrid("setCell", rowId, "total", localRowData.total);.

Related

jqGrid inline edit: odd behavior with an autocomplete column

I have a jqGrid (using inline editing) with an autocomplete column. When the user selects a value from the autocomplete column, an event handler sets a value on another column, and also sets the value on the autocomplete column to something other than the label returned from the autocomplete source. The two column definitions (complete jsFiddle example here):
{
name: 'cartoonId',
index: 'cartoonId',
width: 90,
editable: false},
{
name: 'cartoon',
index: 'cartoon',
width: 200,
editable: true,
edittype: 'text',
editoptions: {
dataInit: function(elem) {
$(elem).autocomplete({
source: autocompleteSource,
select: function(event, ui){
var rowId = $("#inlineGrid").jqGrid('getGridParam', 'selrow');
if(ui.item){
$("#inlineGrid").jqGrid('setCell', rowId, 'cartoonId', ui.item.CartoonId);
$("#inlineGrid").jqGrid('setCell', rowId, 'cartoon', ui.item.Name);
}
return false;
}
});
}
}},
The problem is that whenever the user selects a value from the autocomplete, either by clicking it or using arrows and pressing the tab key, that cell is no longer editable, and the grid appears to lose focus entirely. If I comment out the line that sets the cartoon cell value, it behaves normally. Is there any way I can get around this behavior? I need the entire row to remain in edit mode, including the cartoon column, until the user completes the edit.
jqGrid 4.4.1
jQuery 1.7.2
jQuery UI 1.8.18
You should rename Name property of the items from the autocompleteSource to value because jQuery UI Autocomplete examines the label and value per default (see the documentation).
You can't use setCell of the 'cartoon' column which is currently in the editing mode. You should remove return false; from select callback too. So the code could looks about the following
dataInit: function (elem) {
$(elem).autocomplete({
source: autocompleteSource,
select: function (event, ui) {
var rowId = $("#inlineGrid").jqGrid('getGridParam', 'selrow');
if (ui.item) {
$("#inlineGrid").jqGrid('setCell', rowId, 'cartoonId',
ui.item.CartoonId);
}
}
});
}
See http://jsfiddle.net/27dLM/38/

JQGrid - toggling multiselect

Is there a way to toggle the multiselect option of a grid?
Changing the multiselect parameter of the grid and calling for a reload has the side-effect of leaving the header behind when disabling or not creating the header column if multiselect was not TRUE upon the grid creation.
The closest I have come is setting multiselect to TRUE upon grid creation and using showCol and hideCol to toggle: $('#grid').showCol("cb").trigger('reloadGrid');
This has a side effect of changing the grid width when toggled. It appears the cb column width is reserved when it is not hidden.
Basically I'm attempting to create a grid with an "edit/cancel" button to toggle the multiselect -- very similar to how the iPhone/iPad handles deleting multiple mail or text messages.
Thank you in advance.
I full agree with Justin that jqGrid don't support toggling of multiselect parameter dynamically. So +1 to his answer in any way. I agree, that the simplest and the only supported way to toggle multiselect parameter will be connected with re-initialize (re-creating) the grid.
So if you need to change the value of multiselect parameter of jqGrid you need first change multiselect parameter with respect of respect setGridParam and then re-creating the grid with respect of GridUnload method for example. See the demo from the answer.
Nevertheless I find your question very interesting (+1 for you too). It's a little sport task at least to try to implement the behavior.
Some remarks for the understanding the complexity of the problem. During filling of the body of the grid jqGrid code calculate positions of the cells based on the value of multiselect parameter (see setting of gi value here and usage it later, here for example). So if you will hide the column 'cb', which hold the checkboxes, the cell position will be calculated wrong. The grid will be filled correctly only if either the column 'cb' not exists at all or if you have multiselect: true. So you have to set multiselect: true before paging or sorting of the grid if the column 'cb' exist in the grid. Even for hidden column 'cb' you have to set multiselect to true. On the other side you have to set multiselect to the value which corresponds the real behavior which you need directly after filling of the grid (for example in the loadComplete).
I hope I express me clear inspite of my bad English. To be sure that all understand me correctly I repeat the same one more time. If you want try to toggle multiselect dynamically you have to do the following steps:
create grid in any way with multiselect: true to have 'cb' column
set multiselect: false and hide 'cb' column in the loadComplete if you want to have single select behavior
set multiselect: true always before refreshing the grid: before paging, sorting, filtering, reloading and so on.
I created the demo which seems to work. It has the button which can be used to toggle multiselect parameter:
In the demo I used the trick with subclassing (overwriting of the original event handle) of reloadGrid event which I described the old answer.
The most important parts of the code from the demo you will find below:
var events, originalReloadGrid, $grid = $("#list"), multiselect = false,
enableMultiselect = function (isEnable) {
$(this).jqGrid('setGridParam', {multiselect: (isEnable ? true : false)});
};
$grid.jqGrid({
// ... some parameters
multiselect: true,
onPaging: function () {
enableMultiselect.call(this, true);
},
onSortCol: function () {
enableMultiselect.call(this, true);
},
loadComplete: function () {
if (!multiselect) {
$(this).jqGrid('hideCol', 'cb');
} else {
$(this).jqGrid('showCol', 'cb');
}
enableMultiselect.call(this, multiselect);
}
});
$grid.jqGrid('navGrid', '#pager', {add: false, edit: false, del: false}, {}, {}, {},
{multipleSearch: true, multipleGroup: true, closeOnEscape: true, showQuery: true, closeAfterSearch: true});
events = $grid.data("events"); // read all events bound to
// Verify that one reloadGrid event hanler is set. It should be set
if (events && events.reloadGrid && events.reloadGrid.length === 1) {
originalReloadGrid = events.reloadGrid[0].handler; // save old
$grid.unbind('reloadGrid');
$grid.bind('reloadGrid', function (e, opts) {
enableMultiselect.call(this, true);
originalReloadGrid.call(this, e, opts);
});
}
$("#multi").button().click(function () {
var $this = $(this);
multiselect = $this.is(":checked");
$this.button("option", "label", multiselect ?
"To use single select click here" :
"To use multiselect click here");
enableMultiselect.call($grid[0], true);
$grid.trigger("reloadGrid");
});
UPDATED: In case of usage jQuery in version 1.8 or higher one have to change the line events = $grid.data("events"); to events = $._data($grid[0], "events");. One can find the modified demo here.
I really like what you are trying to do here, and think it would be a great enhancement for jqGrid, but unfortunately this is not officially supported. In the jqGrid documentation under Documentation | Options | multiselect you can see the Can be changed? column for multiselect reads:
No. see HOWTO
It would be nice if there was a link or more information about that HOWTO. Anyway, this is probably why you are running into all of the weird behavior. You may be able to work around it if you try hard enough - if so, please consider posting your solution here.
Alternatively, perhaps you could re-initialize the grid in place and change it from/to a multi-select grid? Not an ideal solution because the user will have to wait longer for the grid to be set up, but this is probably the quickest solution.
A simpler answer:
<input type="button" value="Multiselect" onclick="toggle_multiselect()">
<script>
function toggle_multiselect()
{
if ($('#list1 .cbox:visible').length > 0)
{
$('#list1').jqGrid('hideCol', 'cb');
jQuery('.jqgrow').click(function(){ jQuery('#list1').jqGrid('resetSelection'); this.checked = true; });
}
else
{
$('#list1').jqGrid('showCol', 'cb');
jQuery('.jqgrow').unbind('click');
}
}
</script>
Where list1 is from <table id="list1"></table>.

jqGrid Custom Formatter Set Cell wont work with options.rowId

Ive been through all the posts, finally got setCell to work with hardcoded values, but not using the options.rowId.
function StatusFormatter(cellvalue, options, rowObject) {
if (cellvalue == 'C'){
jQuery("#list").setCell(options.rowId , 'SOORDLINE', '', { color: 'red' });
jQuery("#list").setCell("[2.000]", 'SOORDLINE', '', { color: 'red' });
jQuery("#list").setCell('[2.000]', 'SOREQDATE', '', { color: 'red' });
jQuery("#list").setCell(options.rowId, 'SOPRICE', '', { color: 'red' });
}
return cellvalue;
};
The FIRST and LAST lines dont work, but the 2 with the hardcoded rowId DO work. I inspected what comes back in the option.rowId and they are the same as the hardcoded values, (just different depending on the row of course. What am I missing? Please help. I dont see any difference between the lines, or values.
EDITED-
I tried the answer, and it seems to be what I need. I tried the following
{ name: 'SOORDLINE', index: 'SOORDLINE', width: 25, search: false ,celattr: function () { return ' style="color: red"'; }
},
To aleast make them all red before I dove into the logic, and it didnt do anything for me.
Sorry, but you use custom formatter in absolute wrong way. The goal of the custom formatter to to provide HTML fragment to fill the content of the cells in the corresponding column. So the StatusFormatter will be called before the row with the id equal to options.rowId will be created. Moreover for performance purpose one use typically gridview: true. in the case the whole content of the grid (the whole body of the grid) will be constructed first as string and only after that will be placed in the grid body in one operation. It improve the performance because after placing of any element web browser have to recalculate position of all other elements on the page.
If you want to set text color on the SOORDLINE cell you should cellattr instead:
celattr: function () { return ' style="color: red"'; }
The celattr can be used also in the form celattr: function (rowId, cellValue, rawObject) {...} and you can test the property of rawObject which represent of the values for any column and return the cell style based on the cell value.
Alternatively you can enumerate the rows inside of loadComplete and set the style on <tr> element instead of setting the same styles for every row. See the answer as an example.

I am using jqGrid and want to save column widths after they are resized

I added the resizeStop event to my grid and it gets called, but I need to save the new column widths in the session and then use the new values to maintain the user preferences for column widths. Currently paging in the grid or reloading resets the column widths.
Here is what I have so far.
resizeStop: function(newwidth, index) {
alert(index + " : " + newwidth);
}
OK, I got it. I store all column widths in a HashMap in a bean I use to save session info. When the resizeStop event is fired I submit the new column size to a controller (I'm using Java and Spring) which updates the values in the HashMap.
Here are the code snippets:
resizeStop: function(newwidth, index) {
var colModel = $("#list").jqGrid('getGridParam','colModel');
$.post("/sessionState/columnWidth/update",
{
column: colModel[index].name,
width: newwidth
}
)
}
and in the colModel:
{name:'Title', index:'title', width: ${uiState.columnWidthMap["Title"]}, jsonmap: 'title', sorttype: "text"}

How to supply a Row ID when using jqGrid 'editGridRow' to create a new row and not the auto generated Row ID jqg1

I'm new to jqGrid, so hopefully someone can point me in the right direction.
Basically, I am using jgGrid to display a list of dates and costs that I have read in from a file that I want the user to be able to amend or add new entries or delete existing entries. Upon the user clicking an onscreen button "Apply" to post back the form , I read out the jqGrid and post back to Server in the form of a JSON string.
My problem is that when I add new rows (via 'editGridRow'), jqGrid is using it's autogenerated jqg1, jg2, jg3, etc and the new rows are being populated at the top of grid instead of at their row id position i.e at the bottom of grid.
I am able to generate RowID's as necessary, however I do not seem to be able to supply them to 'editGridRow' when creating new entries, instead it appears I have to use the keyword "new".
As you are aware the reason I am using editGridRow instead of addRowData is that editGridRow creates a modal dialog for the user to enter the data.
Any help would be appreciated.
This is what I would like to use to supply a row ID:
$("#tableGrid").jqGrid('editGridRow', gridRowID, {reloadAfterSubmit:false});
This is what I have to use to get the code to work:
$("#tableGrid").jqGrid('editGridRow', "new", {reloadAfterSubmit:false});
Here is the code snippet I use for creating the gqGrid in my JSP:
var gridRowID = ${costHistoryEntries}.length
$("document").ready(function() {
$("#tableGrid").jqGrid({
data: ${costHistoryEntries},
datatype: "local",
height: 200,
colNames:['Date', 'Cost'],
colModel:[
{name:'chdate', index:'chdate', width:110, sorttype:"date", editable:true, editrules:{date:true, required:true, custom:true, custom_func:checkDate}, datefmt:'d/m/Y'},
{name:'cost', index:'cost', width:160, align:"right", sorttype:"float", editable:true, editrules:{number:true, required:true}, formatter:'float'},
],
autowidth: true,
loadonce: true,
sortname: 'chdate',
sortorder: "desc",
rowList:[10, 20, 30],
pager: jQuery('#tablePager'),
viewrecords: true,
editurl: '/myurl',
caption: "Cost History Entries" }
);
$("#tableGrid").jqGrid('navGrid', "#tablePager", {edit:true, add:true, del:true, search:true, refresh:true});
$("#addEntry").click(function() {
gridRowID++;
$("#tableGrid").jqGrid('editGridRow', "new", {reloadAfterSubmit:false});
});
});
I also created a button and linked it to "addEntry" as an alternative way to adding a new row using the jqGrid Navigator "add/edit/delete/find/reload" bar. As you can see previous to loading the grid with data I stored the number of entries in gridRowID. What I was hoping to be able to do in the "#addEntry" click function was use the updated gridRowID as a RowID parameter.
As a FYI:
In a previous version of the code I was using the following to load the data into the grid:
var griddata = ${costHistoryEntries};
for (var i=0; i <= griddata.length; i++) {
$("#tableGrid").jqGrid('addRowData', i+1, griddata[i], "last");
}
but found that I could do that same with
data: ${costHistoryEntries},
Both versions correctly create row ID's for the supplied sample data from the server:
[{"chdate":"20/05/2011","cost":"0.111"},{"chdate":"01/06/2011","cost":"0.222"},{"chdate":"07/07/2011","cost":"0.333"}]
My problem is adding new rows of data.
Further update, On the server side, as a test I intercepted the post to /myurl and updated the id from "_empty" to "7" but the new entry in jqGrid still has an autogenerated jqGrid Row ID "jqg1":
Key = chdate, Value = 26/09/2011
Key = oper, Value = add
Key = cost, Value = 14
Key = id, Value = _empty
Updated Key = chdate, Value = 26/09/2011
Updated Key = oper, Value = add
Updated Key = cost, Value = 14
Updated Key = id, Value = 7
I am not quite understand your requirement. You get the input for the jqGrid from the server, but use datatype: "local" instead of the datatype: "json" for example which instruct jqGrid to get ajax request to the server to get the data whenever as needed. Moreover you want to save the date on the server, but use editurl: '/dummyurl' instead of the real server url. The editurl would get the input data from the $("#tableGrid").jqGrid('editGridRow', "new", {reloadAfterSubmit:false}); and should post back the id of the new generated row. Is it not what you want?
So my suggestion would be to use some server based datatype (the bast is datatype: "json") and in the same way use real editurl which save the data on the server (in the database mostly) and place in the server response the id on the new generated data item.
UPDATED: If you use reloadAfterSubmit: false option of the editGridRow you have to use afterSubmit event handler together with reloadAfterSubmit which decode the server response and return the result in the format [true,'',new_id] For example if your server just return new rowid as the data you can use
$("#tableGrid").jqGrid('editGridRow', "new",
{
reloadAfterSubmit: false,
afterSubmit: function (response) {
return [true, '', response.responseText];
}
}
);

Resources