I am using jqGrid (inlineNav
) with data from azure service and interested in learning about inline editing and error handling with azure mobile service table.
Inline editing mode of jqGrid provide three base methods needed for implementing of editing: editRow, restoreRow and saveRow. The method addRow add empty row and then uses internally editRow
for start editing. If one use keys: true
option of editRow
then one don't need to call saveRow
explicitly. editRow
do it internally if the user press Enter key in the editing field. The user can use Esc key to cancel editing. editRow
calls internally restoreRow
in the case instead of saveRow
.
jqGrid introduced later formatter: "actions"
, addRow and inlineNav which simplify a little the usage of inline editing if one needs to have some buttons for starting editing and saving the data. The most problems of usage of formatter: "actions"
and inlineNav are in the correct usage of parameters and because the methods provide you less control on usage of parameters. Additionally inlineNav
had many bugs which are fixed only in version 4.4.5.
Sorry, for so long common text, but I want just explain why I want answer on your question first without usage of inlineNav
and then provide solution which use the method.
The most simple way to use inline editing for edit existing rows is the following. One starts just editRow
inside of ondblClickRow
. The user can press key Enter to save the editing row on the server or press the key Esc to discard the changes. The corresponding code will be about the following:
var azureHeaders = { "X-ZUMO-APPLICATION": "myApplicationKey" },
myTableURL = "https://oleg.azure-mobile.net/tables/Products";
$("#grid").jqGrid({
url: myTableURL,
datatype: "json",
prmNames: {search: null, nd: null, sort: null, rows: null},
ajaxGridOptions: { contentType: "application/json", headers: azureHeaders },
jsonReader: { repeatitems: false, root: function (obj) { return obj; } },
ondblClickRow: function (rowid) {
var $self = $(this);
$self.jqGrid("editRow", rowid, {
mtype: "PATCH",
keys: true,
url: myTableURL + "/" +
$.jgrid.stripPref($self.jqGrid("getGridParam", "idPrefix"), rowid)
});
},
ajaxRowOptions: { contentType: "application/json", headers: azureHeaders },
serializeRowData: function (postData) {
var dataToSend = $.extend(true, {}, postData);
if (dataToSend.hasOwnProperty("oper")) {
delete dataToSend.oper;
}
if (dataToSend.hasOwnProperty("id")) {
delete dataToSend.id;
}
return JSON.stringify(dataToSend);
},
gridview: true,
loadonce: true,
autoencode: true,
... // other parameters of jqGrid
});
(To make the code easier I removed any error handling during loading of data or saving of editing results)
The above example works. The user can view the data, select rows, make local paging etc. On the other side the user can double-click on the row to edit it. It's important to understand that design inline editing allows editing multiple lines at the same side. The user can make double-click on one row, make some modifications, then make double-click on another row, makes some other modifications. Finally the user can finish editing of every row either by pressing on Enter or Esc key to save or to discard the current changes of the line.
At the beginning of editing of every row we set url
option of editRow
which will be associated with the row of grid. If the user press Enter key the method editRow
calls internally saveRow
with the same parameters.
After you understand how inline editing work I can explain how all works in case of usage inlineNav
. If you examine the code of inlineNav
(see here) then you will see that it uses mostly the method navButtonAdd which add custom button to navigator bar. Inside of onClickButton
callback it calls addRow
, editRow
, saveRow
or restoreRow
. The version 4.4.5 fixes many bugs in inlineNav
(see here, here, here, here and here), but it still don't solves not all existing problems. For example if you permit to edit multiple rows at the same time (if you use currently undocumented option restoreAfterSelect: false
of inlineNav
) then because of usage $t.p.savedRow[0].id
expression to get the rowid the code of jqGrid can use wrong rowid for saving or discarding of row. So you should not use option restoreAfterSelect: false
in the current version of jqGrid.
The main problem of inlineNav
in my opinion is that one use not the same options for saving or discarding which one has during initializing of editing of rows. I mean that inlineNav
calls saveRow
or restoreRow
*not with the same options which was used for call of editRow
. If one changes for example the url
property of editRow
so that RESTfull url
with id
of row will be used the call of saveRow
will be not made with the same options if the user clicks on "Save" button.
Moreover there are exist no callback which can be used to modify the current options (to modify url
mostly) if the user click on saveRow
. Nether inlineNav
nor saveRow
have currently (in jqGrid 4.4.5 or lower) such callbacks.
The only way to solve the problem which I see is:
restoreAfterSelect: false
optionssave: false
option of inlineNavinlineNav
and modify the url
option of manually before one calls saveRow
. In other words one should re-implement "Save" button of inlineNav
.An example of the corresponding implementation you can find below. I used loadonce: true
option. If one have large table and prefer server side paging then one will need make changes of some parameters correspond with my previous answer on your question. Additionally I removed error handling to simplify the code a little:
var $grid = $("#list"),
azureHeaders = { "X-ZUMO-APPLICATION": "myApplicationKey" },
myTableURL = "https://oleg.azure-mobile.net/tables/Products",
inlineNavParams = {
save: false, // we want to add Save button manually. So we needn't no standard button
editParams: { mtype: "PATCH" },
addParams: {
addRowParams: {
//mtype: "POST", // default value
aftersavefunc: function (rowid, response) {
var rowData = $.parseJSON(response.responseText),
newId = rowData.id,
$self = $(this),
p = $self.jqGrid("getGridParam"), // get all parameters as object
idPrefix = p.idPrefix,
oldId = $.jgrid.stripPref(idPrefix, rowid),
selrow = p.selrow,
selArrayRow = p.selarrrow,
dataIndex = p._index,
keyIndex = p.keyIndex,
colModel = p.colModel,
localRowData = $self.jqGrid("getLocalRow", rowid),
i;
// update id in the _index
if (dataIndex != null && dataIndex[oldId] !== undefined) {
dataIndex[newId] = dataIndex[oldId];
delete dataIndex[oldId];
}
// update id value in the data
if (localRowData.hasOwnProperty("_id_")) {
localRowData._id_ = newId;
}
if (keyIndex !== false) {
for (i = 0; i < colModel.length; i++) {
if (colModel[i].key) {
if (localRowData.hasOwnProperty(colModel[i].name)) {
// update the value of the column
localRowData[colModel[i].name] = idPrefix + newId;
$self.jqGrid("setCell", rowid, i, newId);
}
break; // one can have only one column with key:true
}
}
}
// update id attribute in <tr>
$("#" + $.jgrid.jqID(rowid)).attr("id", idPrefix + newId);
// update id of selected row
if (selrow === rowid) {
$self.jqGrid("setGridParam", { selrow: idPrefix + newId });
}
// update id in selarrrow array
// in case of usage multiselect:true option
if ($.isArray(selArrayRow)) {
i = $.inArray(rowid, selArrayRow);
if (i >= 0) {
selArrayRow[i] = idPrefix + newId;
}
}
// the next line is required if we use ajaxRowOptions: { async: true }
$self.jqGrid("showAddEditButtons");
}
}
}
};
// set common options which we want to use in inline editing
$.extend(true, $.jgrid.inlineEdit, {
keys: true,
afterrestorefunc: function () {
$(this).jqGrid("showAddEditButtons");
},
aftersavefunc: function () {
$(this).jqGrid("showAddEditButtons");
},
});
$grid.jqGrid({
colModel: [
{ name: "id", key: true, width: 100 }, // optional column
{ name: "Name", width: 450, editable: true }
],
// the parameters below are needed to load the grid data from the server
// we use loadonce: true option below. One can use server side pading instead.
// see https://stackoverflow.com/a/15979809/315935 for the changes
url: myTableURL,
datatype: "json",
prmNames: {search: null, nd: null, sort: null, rows: null},
ajaxGridOptions: { contentType: "application/json", headers: azureHeaders },
jsonReader: {
repeatitems: false,
root: function (obj) { return obj; }
},
gridview: true,
autoencode: true,
loadonce: true,
// we implement additionally inline editing on double-click.
// it's optional step in case of usage inlineNav
ondblClickRow: function (rowid) {
var $self = $(this);
$self.jqGrid("editRow", rowid, {
mtype: "PATCH",
keys: true,
url: myTableURL + "/" +
$.jgrid.stripPref($self.jqGrid("getGridParam", "idPrefix"), rowid)
});
},
// next options are important for inline editing
ajaxRowOptions: { contentType: "application/json", headers: azureHeaders },
editurl: myTableURL,
serializeRowData: function (postData) {
var dataToSend = $.extend(true, {}, postData); // make copy of post data
if (dataToSend.hasOwnProperty("oper")) {
delete dataToSend.oper;
}
if (dataToSend.hasOwnProperty("id")) {
delete dataToSend.id;
}
return JSON.stringify(dataToSend);
},
rowNum: 2,
rowList: [2, 5, 10],
sortname: "Name",
sortorder: "desc",
viewrecords: true,
rownumbers: true,
height: "auto",
pager: "#pager"
caption: "Windows Azure Mobile Services REST API"
}).jqGrid("navGrid", "#pager", { edit: false, add: false, del: false, search: false });
$grid.jqGrid("inlineNav", "#pager", inlineNavParams);
$grid.jqGrid("navButtonAdd", "#pager", {
caption: $.jgrid.nav.savetext || "",
title: $.jgrid.nav.savetitle || "Save row",
buttonicon: "ui-icon-disk",
id: $grid[0].id + "_ilsave",
onClickButton: function () {
var $self = $(this),
gridIdSelector = $.jgrid.jqID(this.id),
savedRow = $self.jqGrid("getGridParam", "savedRow"),
prmNames = $self.jqGrid("getGridParam", "prmNames"),
editUrl = $self.jqGrid("getGridParam", "editurl"),
rowid = savedRow != null ? savedRow[0].id : "",
id = $.jgrid.stripPref($self.jqGrid("getGridParam", "idPrefix"), rowid),
tmpParams = {};
if (rowid != null) {
if ($("#" + $.jgrid.jqID(rowid), "#" + gridIdSelector).hasClass("jqgrid-new-row")) {
if (!inlineNavParams.addParams.addRowParams.extraparam) {
inlineNavParams.addParams.addRowParams.extraparam = {};
}
inlineNavParams.addParams.addRowParams.extraparam[prmNames.oper] = prmNames.addoper;
tmpParams = inlineNavParams.addParams.addRowParams;
} else {
if (!inlineNavParams.editParams.extraparam) {
inlineNavParams.editParams.extraparam = {};
}
inlineNavParams.editParams.extraparam[prmNames.oper] = prmNames.editoper;
inlineNavParams.editParams.url = editUrl + "/" + id;
tmpParams = inlineNavParams.editParams;
}
if ($self.jqGrid("saveRow", rowid, tmpParams)) {
$self.jqGrid("showAddEditButtons");
}
} else {
$.jgrid.viewModal("#alertmod", {gbox: "#gbox_" + gridIdSelector, jqm: true});
$("#jqg_alrt").focus();
}
}
});
$("#" + $grid[0].id + "_ilsave").addClass("ui-state-disabled");
How you can see the most complex is the implementation of aftersavefunc
callback of the addRowParams
parameter. I plan to post later my suggestion to trirand which extends the code of inline editing, but simplify the code of aftersavefunc
callback so that it could be just
aftersavefunc: function (rowid, response) {
return response.responseText ?
$.parseJSON(response.responseText).id :
undefined;
}
All other things should jqGrid do internally if the type of the value returned by aftersavefunc
is not "undefined".