Jqgrid using cellattr to toggle a class on seperate cell - jqgrid

in my col model i have two cells 'Status' and 'HiddenStatus'. The reason for this is 'Status' is translatable so this way is better rather than checking the value for each language. As you can see below i am attempting to set the class of the 'Status' cell based on the value of the 'HiddenStatus' cell. However this is not working as i hoped since the class is not being set correctly.
I believe the issue i am having is, 'getCell' is used to return the value rather than an object. How can i get the cell as an object so i can then manage what class i add or remove.
{name: 'Status', width: 70, index: 'Status'},
{name: 'HiddenStatus', width: 70, hidden: true, cellattr : function(rowId, cellValue, rawObject, cm, rdata){
var statusCell = $(this).jqGrid('getCell',rowId,'Status');
if(cellValue != "Assigned"){
$(statusCell).removeClass('status-assigned');
return '';
}
if(cellValue == "Assigned"){
$(statusCell).addClass('status-assigned');
return '';
}
}},

First of all, it's important to understand what cellattr do. Any changes on the existing page are expensive. Thus jqGrid try to build the whole HTML fragment with the table body as one long string. Every column will be used to build the cells (<td> elements) of the corresponding columns of the grid. The callback cellattr will be called during building the cell and can be used to set attributes on the cell. The cellattr callback have to return the string. If it returns, for example, " class='status-assigned'" then the <td> will have the class (<td class='status-assigned'>...</td>).
The callback cellattr will be called before the grid will be created. Thus one can't access to the grid using $(this).jqGrid('getCell',rowId,'Status'), for example.
If you need to set class status-assigned conditionally on the cells of the column Status then you should define cellattr callback in the column Status. Inside the callback you can use rawObject.HiddenStatus or rdata.HiddenStatus. One don't need typically to create hidden columns like HiddenStatus. Instead of that, it's enough to use include all properties of input data, which one need, in additionalProperties option of jqGrid. For example, additionalProperties: ["HiddenStatus"].
Your original code could be modified to the following:
{
name: 'Status', width: 70,
cellattr: function (rowId, cellValue, item) {
if (item.HiddenStatus === "Assigned") {
return " class='status-assigned'";
}
}
}
See the old answer for an example of usage cellattr.

Related

JqGrid Performance - setRowData lags

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);.

jqGrid: setCell clears the column value

I am trying to update the cell value of a particular column in my grid. I am using local JSON for populating the grid datatype: "local".
The column definition of that column is as follows:-
{
name: 'details',
index: 'details',
label: 'Details',
editable: false,
align: 'center',
formatter: function(cellvalue, options, rowObject) {
if (rowObject['verified']) {
return 'sdfsfsd'; // actually hyperlink for the cellvalue
}
return '';
}
}
I am using the following line of code to update the cell value:-
// rowid is 1
grid.jqGrid('setCell', 1, 'details', 'DONE!');
Also tried like this:
// the last parameter true to force update
grid.jqGrid('setCell', 1, 'details', 'DONE!', null, null, true);
However, the cell value is cleared out (cell becomes empty, <td> content is empty). This happens only for this column, and not for other columns in the grid. What am I missing?
Thanks
Vivek Ragunathan
If you use custom formatter you should specify unformatter too. The function formatter will be used to set the value in the cell. At the beginning of cell editing jqGrid need to get the value from the cell. The function unformat do the job.

Dynamically change a column's editable property with select box

I am using form editing. I would like to disable certain fields in my add and edit forms based on the selection from a drop down box. What event is best to use to trigger this? I have attempted using dataEvents:
{ name:'type_cd',
edittype:'select',
editoptions:{
dataUrl:'functions.php',
dataEvents:[{
type:'change',
fn: function(e){
$(this).setColProp('cntrct_id',{
editoptions:{editable:false}
});
}
}]
}
},
This has no visible effect on my form fields, but I know that it's being reached because I can get an alert message from it if I put one in.
EDIT
If I submit the form, the next time I open it, the column that was set to editable:false will not appear. This is a step in the right direction, BUT I want it to immediately be uneditable. Really, I would like it to be visible, but disabled (i.e. disabled:true)
First of all dataEvents allows you to register callbacks on elements of edit elements. Inside of callbacks this will be initialized to the DOM element which will be bound. So $(this) inside of change handler it will be wrapper on <select> element and not on the grid. The usage of $(this).setColProp will be incorrect.
To disable some input field in Add/Edit form you can use the fact that all input elements get the same id like the value of name property on the corresponding column in colModel. So if you need to disable input of cntrct_id you can set disabled property to true on the element with id="cntrct_id"
{
name: 'type_cd',
edittype: 'select',
editoptions: {
dataUrl: 'functions.php',
dataEvents: [{
type: 'change',
fn: function (e) {
// disable input/select field for column 'cntrct_id'
// in the edit form
$("#cntrct_id").prop("disabled", true);
}
}]
}
}
It's important to understand that editoptions will be used for any existing editing modes (form editing, inline editing and cell editing). If you want to write the code of dataEvents which supports all editing modes you have to detect editing mode and use a little other ids of editing fields. The code (not tested) can be about as below
{
name: 'type_cd',
edittype: 'select',
editoptions: {
dataUrl: 'functions.php',
dataEvents: [{
type: 'change',
fn: function (e) {
var $this = $(e.target), $td, rowid;
// disable input/select field for column 'cntrct_id'
if ($this.hasClass("FormElement")) {
// form editing
$("#cntrct_id").prop("disabled", true);
} else {
$td = $this.closest("td");
if ($td.hasClass("edit-cell") {
// cell editing
// we don't need to disable $("#cntrct_id")
// because ONLY ONE CELL are edited in cell editing mode
} else {
// inline editing
rowid = $td.closest("tr.jqgrow").attr("id");
if (rowid) {
$("#" + $.jgrid.jqID(rowid) + "_cntrct_id")
.prop("disabled", true);
}
}
}
}
}]
}
}
The last remark. If you still use old version of jQuery (before jQuery 1.6) which don't support prop method you have to use attr instead.
#Oleg: This is working(could get the alert messages) but its not disabling the field.
Should the form field require any special values?

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.

while the select editoption posts the value (or id) of the selected list item, autocomplete posts the label - i need id posted

While both autocomplete and select in jqgrid editform place the selected label into the cell, select will place the value (id) in the postdata array where autocomplete will place the label into the postdata array.
is there a way to get the editoption's autocomplete to post the item value (id) instead of the label?
here is the jqgrid code segment i'm using autocomplete in...
$('#tab3-grid').jqGrid({
colNames:['Workorder', 'wo.CUID',.....],
colModel:[
.
.
.
{name:'wo.CUID', index:'cu.LastName', width:120, fixed:true, align:'center', sortable:true, editable:true, edittype:'text',
editoptions:{dataInit:function(el){$(el).autocomplete({ source: 'php/customer-ac-script.php'
, minLength: 1
})
}
},
formoptions:{rowpos: 1, label:'Customer', elmprefix:'* '},
editrules:{required:true}
},
.
.
.
$('#tab3-grid').jqGrid('navGrid', '#tab3-pager',
{view:true, closeOnEscape:true, cloneToTop:true}, // general parameters that apply to all navigation options below.
{jqModal:true, navkeys:[true,38,40], savekey:[true,13]}, // edit options.
{jqModal:true, navkeys:[true,38,40], savekey:[true,13], reloadAfterSubmit:false, afterSubmit: addRecordID}, // add options.
{jqModal:true, afterSubmit: serverMessage}, // del options.
{jqModal:true}, // search options.
{jqModal:true, navkeys:[true,38,40]} // view options.
);
The php code segment:
// construct autocomplete select.
$i = 0;
while($row = mysql_fetch_assoc($result)) {
$output[$i][$crudConfig['id']] = $row['CUID'];
$output[$i][$crudConfig['value']] = $row['LastName'];
logMsg(__LINE__,'I','cu.CUID: '.$row['CUID'].', cu.LastName: '.$row['LastName']);
$i++;
}
// encode to json format and send output back to jqGrid table.
echo json_encode($output);
logMsg(__LINE__,'I','Send json output back to jqGrid table: '.json_encode($output));
Would it be as simple as calling a function under the autocomplete select event or the grid before or after editform submit?
Also, i noticed this note in the jqgrid doc's for datainit: that says...
Note: Some plugins require the position of the element in the DOM and
since this event is raised before inserting the element into the DOM
you can use a setTimeout function to accomplish the desired action.
Would the lack of including the settimeout function be causing the problem?
The server code which provide the JSON response on the autocomplete request has id and value properties. On the other side the standard behavior of jQuery UI Autocomplete is to use label and value properties (see "Datamodel" in the documentation). The value of label property (if any exist) will be used to display in the contextmenu. The value of value property will be placed in the <input> field after the user choose the item from the contextmenu. The value of label property can has HTML markup, but the value of value property must be the text.
So I see the problem as pure problem of the usage of jQuery UI Autocomplete independent on jqGrid. If I understand correct your question you can solve your problem by modification your server side code.
Oleg's answer clarifying the data model for jquery UI's autocomplete, has allowed me to move forward and understand that autocomplete has nothing to do with constructing and sending the postdata array to the server, jqgrid's editform handles it. With that knowledge, i was able to answer my original question and successfully integrate autocomplete into jqgrid. So, in the interest of sharing, i'd like to show you all my motivation and solution.
By default, selecting a label from the autocomplete list put's the value of the selected label/value pair into the text box. All the editform cares about when you submit is what's in the edit fields. So when you submit the editform, the cell's postdata element value will again contain the value of the autocomplete text box. But what if while wanting to post the value of the label/value pair, you want the label of the label/value pair displayed in the text box? You have a problem! How do you get the value of the label/value pair posted to the server?
Well, after spending a few days on it, it turns out to be quite simply. While i'm sure there is more than one solution, here is mine:
add a hidden id column in the grid
define the select: and focus: events in the autocomplete function
in the select: function; insert the selected label into the text box (optional), disable the default behavior of autocomplete, then set the cell of the hidden column to the value of the selected label/value pair
in the focus: function; insert the selected label into the text box(optional), disable the default behavior of autocomplete
add an "onclickSubmit:" event to the navgrid edit options with function name something like "fixpostdata"
in the "fixpostdata" function; get the cell value of the hidden column and insert it into the postdata element associated with the cell.
The following are the grid and javascript code segments i used…
grid segments
{name:'wo_CUID', index:'wo_CUID', width: 70, hidden: true},
{name:'wo.CUID', index:'cu.LastName', width:120, sortable:true, editable:true, edittype:'text',
editoptions:{
dataInit:function(el){ // el contains the id of the edit form input text box.
$(el).autocomplete({
source: 'php/customer-ac-script.php',
minLength: 1,
select: function(event, ui){event.preventDefault();
$(el).val(ui.item.label);
var rowid = $('#tab3-grid').getGridParam('selrow');
// set the hidden wo_CUID cell with selected value of the selected label.
$('#tab3-grid').jqGrid('setCell', rowid,'wo_CUID',ui.item.value);},
focus: function(event, ui) {event.preventDefault();
$(el).val(ui.item.label);}
})
}
},
formoptions:{rowpos: 1, label:'Customer', elmprefix:'* '},
editrules:{required:true}
},
.
.
$('#tab3-grid').jqGrid('navGrid', '#tab3-pager',
{view:true, closeOnEscape:true, cloneToTop:true},
{jqModal:true, navkeys:[false,38,40], onclickSubmit: fixpostdata}, // edit options.
.
.
javascript function
// define handler function for 'onclickSubmit' event.
var fixpostdata = function(params, postdata){
var rowid = $('#tab3-grid').getGridParam('selrow');
var value = $('#tab3-grid').jqGrid('getCell', rowid,'wo_CUID');
postdata['wo.CUID'] = value;
return;
}
The fixpostdata function fires when you submit the editform but befor the postdata array is sent to the server. At this point you replace the cell's postdata element value with whatever you want. In this case, the value of the label/value pair stored in the hidden column cell. When the function returns, the modified postdata array is sent to the server.
Done!

Resources