jqGrid - Toolbar search with auto complete from server - using json - search

After reviewing these 2 questions and Oleg great answers
can jqgrid support dropdowns in the toolbar filter fields
jqGrid toolbar search with autocomplete using json data
I am trying to implement this feature with autocomplete of jQgrid toolbar search with json data coming form the server.
My code:
myGrid.jqGrid({
url: './WebService.asmx/ViewNQueryData',
datatype: 'json',
mtype: 'POST',
ajaxGridOptions: { contentType: 'application/json; charset=utf-8' },
serializeGridData: function (postData) {
if (postData.filters === undefined) postData.filters = null;
return JSON.stringify(postData);
},
jsonReader: {
root: function (obj) { return obj.d.rows; },
page: function (obj) { return obj.d.page; },
total: function (obj) { return obj.d.total; },
records: function (obj) { return obj.d.records; }
},
colModel: columnModel,
colNames: columnNames,
rowNum: 10,
rowList: [10, 20, 300],
sortname: 'ID',
sortorder: "asc",
sortable: true,
pager: "#ViewNQueryPager",
viewrecords: true,
gridview: true,
height: 250,
shrinkToFit: true,//If using frozen coulmns change to false.
grouping: true,
groupingView: {
groupField: ['ID'],
//groupColumnShow: [false],
//groupText: ['<b>{0} - {1} Item(s)</b>'],
groupSummary: [true],
groupOrder: ['asc'],
groupDataSorted: true,
showSummaryOnHide: true
},
footerrow: true,
userDataOnFooter: true,
gridComplete: function () {
$('#totalRecordsFound').html(myGrid.jqGrid('getGridParam', 'records') + " Customers");
},
loadError: function () {
alert("Error fetching data");
}
}).jqGrid('navGrid', '#ViewNQueryPager',
{ edit: false, add: false, del: false, search: true, view: true }, //option
{}, // use default settings for edit
{}, // use default settings for add
{}, // delete instead that del:false we need this
{multipleSearch: true, multipleGroup: true, showQuery: true, onSearch: function (response) { showQueryDetails(); } },
{ height: 250, jqModal: false, closeOnEscape: true} // view options
);
myGrid.jqGrid('setColProp', 'FirstName',
{
searchoptions: {
sopt: ['cn'],
dataInit: function (elem) {
$(elem).autocomplete({
source: './WebService.asmx/ViewNQueryData',
minLength: 1
});
}
}
});
myGrid.jqGrid('filterToolbar', { stringResult: true, searchOnEnter: true });
But when i type in the search box the request doesnt get to my webService, and i get on exception in the browser, that he is trying to to this:
http://localhost:50949/WebService.asmx/ViewNQueryData?term=p
but :
Failed to load resource: the server responded with a status of 500 (Internal Server Error)
My web service:
public JqGridData ViewNQueryData(int page, int rows, string sidx, string sord, bool _search, string filters)
{
if (_search && !String.IsNullOrEmpty(filters))
{
JavaScriptSerializer serializer = new JavaScriptSerializer();
jqGridSearchFilter searchFilter =
serializer.Deserialize<jqGridSearchFilter>(filters);
// use the searchFilter here
}
List<Person> allGridRows = JsonHelper.GetPersons();
int recordsCount = allGridRows.Count;
int startIndex = (page - 1) * rows;
int endIndex = (startIndex + rows < recordsCount) ?
startIndex + rows : recordsCount;
List<TableRow> gridRowsForResponse = new List<TableRow>(rows);
for (int i = startIndex; i < endIndex; i++)
{
gridRowsForResponse.Add(new TableRow()
{
id = i,
cell = new List<string>(3) {
allGridRows[i].ID.ToString(),
allGridRows[i].FirstName,
allGridRows[i].LastName
}
});
}
return new JqGridData()
{
total = (recordsCount + rows - 1) / rows,
page = page,
records = recordsCount,
rows = gridRowsForResponse
};
}
What am i doing wrong? missing something?
Also what do i need to return from the server? the regular JSON that the grid needs?

The error is that you use the same URL './WebService.asmx/ViewNQueryData' for both jQuery UI Autocomplete and the the main grid url.
The main grid url should call web method having (int page, int rows, string sidx, string sord, bool _search, string filters) parameters and return the JSON data in the format
{
"d": {
"__type": "JqGridData",
"total": 3,
"page": 1,
"records": 24,
"rows": [
{"id": 10, "cell": ["1", "Prabir", "Shrestha"]},
{"id": 20, "cell": ["2", "Scott", "Gu"]},
{"id": 43, "cell": ["4", "Bill", "Gates"]}
]
}
}
On the other side the web method for the jQuery UI Autocomplete should has only one input parameter term and return back the data in the format like
["Bill", "Scott"]
or
[
{
"label": "Crab-Plover",
"value": "Crab-Plover"
},
{
"id": "Oenanthe isabellina",
"label": "Isabelline Wheatear",
"value": "Isabelline Wheatear"
}
]
See "Datamodel" part of the jQuery UI Autocomplete documentation.
Because you use ASMX web services which wrap the returned JSON data in d property ({d:{...}}) you have to use some additional modifications to provide the data for jQuery UI Autocomplete in one of supported format. For example you can use source parameter of Autocomplete in callback form instead of the simple URL string. See the answer (or this one) for example.

Related

Why is the Tabulator getRows() function not working?

This seems like the simplest of requests but I can't seem to retrieve a set of rows from a Tabulator object.
Here's the code which instantiates the Tabulator object.........
function TabulatorInvitees(divId, companyName, userEmail) {
try {
var table = new Tabulator(divId, {
columns: [
{
title: "<div style='width:20%; float:left; text-align:left; color:blue; font-size:14px;'>Vendor Invitees</div>",
columns: [
{ title: "Id", field: "Id", visible: false },
{ title: "Added", field: "Added", visible: false },
{ title: "Changed", field: "Changed", visible: false },
{ title: "MarkedForExclusion", field: "MarkedForExclusion", visible: false },
{ title: "Email Address", field: "Email", widthGrow: 1, responsive: 0, hozAlign: "center", editor: "input", visible: true },
{ title: "First Name", field: "FirstName", widthGrow: 0.5, responsive: 1, hozAlign: "center", editor: "input", visible: true },
{ title: "Last Name", field: "LastName", widthGrow: 0.5, responsive: 1, hozAlign: "center", editor: "input", visible: true }
]
},
{
title: tabulatorAddUser(companyName),
field: "ManageRows",
widthGrow: 0.25,
responsive: 2,
hozAlign: "center",
formatter: "tickCross",
headerClick: function (e, row) {
row.getTable().addRow({ Id: 0, Added: true }, false);
},
cellClick: function (e, cell) {
tabulatorFreezeUnfreezeDelete(cell.getRow());
}
},
],
data: [],
height: "100%",
layout: "fitColumns", // required when using 'widthGrow'
placeholder: tabulatorPlaceholder(companyName), //display message to user on empty table
reactiveData: true, //enable reactive data
responsiveLayout: "collapse",
rowContextMenu: tabulatorContextMenu(),
});
table.on("rowTapHold", function (e, row) {
// from Tabulator documentation: "The rowTapHold event is triggered when a user taps on a row on a touch display and holds their finger down for over 1 second."
//e - the tap event object
//row - row component
tabulatorContextMenu();
});
table.on("tableBuilt", function () {
if (companyName.length > 0) {
table.setData(getDataSync({ caseSelector: "VendorMgmt_EmployeeList", companyCode: companyName, userEmail: userEmail }));
}
else {
table.setData([]);
}
});
}
catch (error) {
console.log(error);
}
}
The setData() function makes a call to a database function which returns three rows, similar to the following:
The following JQuery function is called when a radio button is clicked....
$(".vendorStatus").click(function (e) {
const status = e.target.value;
const tbls = Tabulator.findTable("#divVendorEmployees");
const tbl = tbls[0];
const tblRows = tbl.getRows();
console.log("tbls.length", tbls.length);
console.log("tblRows", tblRows);
});
The browser console indicates a table has been found (tbls.length = 1) but the tblRows array is empty:
I see the three rows in my Tabulator but I am not able to recall them programmatically. It seems like a simple problem which should have a simple answer.
I am using the most recent version of Tabulator (v5.4).
Any assistance is greatly appreciated.
After much searching, I finally came to the realization the DOM element associated with the Tabulator instance must be managed when attempting to refresh or replace data. I've implemented a method which allows me to delete and rebuild the DOM element each time I need to save data to my database and subsequently refresh my Tabulator instance.
Here's the code...
function refreshTabulatorObject(obj) {
let parentId = obj.parentId;
let childId = obj.childId;
//Empty and remove the current version of the [Tabulator] object.
const tables = Tabulator.findTable(childId);
if (tables.length > 0) {
var table = Tabulator.findTable(childId)[0];
table.setData([]);
}
//Remove the existing <div> from the DOM.
$(childId).remove();
//Re-create the <div> element for the [Tabulator] object and append it to the DOM.
var parentDiv = document.getElementById(parentId);
parentDiv.innerHTML = "";
var childDiv = document.createElement("div");
childDiv.setAttribute("id", childId);
parentDiv.appendChild(childDiv);
//Re-create the [Tabulator] object.
TabulatorInvitees("#" + childId, obj.companyName);
}
I'm sure those of you with a more intimate knowledge of Tabulator would likely suggest a more elegant method, however, this one appears to work and I've already spent far more time on this issue that I had anticipated. Unfortunately, elegance is not a luxury I can afford at this point.
I hope this solution might be of help to some other struggling soul.

Export To Excel filtered data with Free jqgrid 4.15.4 in MVC

I have a question regarding Export to Excel in free-jqgrid 4.15.4. I want to know how to use this resultset {"groupOp":"AND","rules":[{"field":"FirstName","op":"eq","data":"Amit"}]} into my Business Logic Method.
Just for more clarification, I've using OfficeOpenXml and if I don't use filtered resultset(aforementioned) it is working fine and I'm able to download file with full records in an excel sheet. But I'm not sure what to do or how to utilize the resultset {"groupOp":"AND","rules":[{"field":"FirstName","op":"eq","data":"Amit"}]}
If required I can share my controller and BL code.
I have added a fiddle which shows implementation of Export to Excel button in jqGrid pager.
Before coming to here, I've read and tried to understand from following questions:
1] jqgrid, export to excel (with current filter post data) in an asp.net-mvc site
2] Export jqgrid filtered data as excel or CSV
Here is the code :
$(function () {
"use strict";
var mydata = [
{ id: "10", FirstName: "test", LastName: "TNT", Gender: "Male" },
{ id: "11", FirstName: "test2", LastName: "ADXC", Gender: "Male" },
{ id: "12", FirstName: "test3", LastName: "SDR", Gender: "Female" },
{ id: "13", FirstName: "test4", LastName: "234", Gender: "Male" },
{ id: "14", FirstName: "test5", LastName: "DAS", Gender: "Male" },
];
$("#list").jqGrid({
data: mydata,
colNames: ['Id', 'First Name', 'Last Name', 'Gender'],
colModel: [
{
label: "Id",
name: 'Id',
hidden: true,
search: false,
},
{
label: "FirstName",
name: 'FirstName',
searchoptions: {
searchOperators: true,
sopt: ['eq', 'ne', 'lt', 'le','ni', 'ew', 'en', 'cn', 'nc'],
}, search: true,
},
{
label: "LastName",
name: 'LastName',
searchoptions: {
searchOperators: true,
sopt: ['eq', 'ne', 'lt', 'ni', 'ew', 'en', 'cn', 'nc'],
}, search: true,
},
{
label: "Gender",
name: 'Gender',
search: true, edittype: 'select', editoptions: { value: 'Male:Male;Female:Female' }, stype: 'select',
},
],
onSelectRow: function (id) {
if (id && id !== lastsel) {
jQuery('#list').restoreRow(lastsel);
jQuery('#list').editRow(id, true);
lastsel = id;
}
},
loadComplete: function (id) {
if ($('#list').getGridParam('records') === 0) {
//$('#grid tbody').html("<div style='padding:6px;background:#D8D8D8;'>No records found</div>");
}
else {
var lastsel = 0;
if (id && id !== lastsel) {
jQuery('#list').restoreRow(lastsel);
jQuery('#list').editRow(id, true);
lastsel = id;
}
}
},
loadonce: true,
viewrecords: true,
gridview: true,
width: 'auto',
height: '150px',
emptyrecords: "No records to display",
iconSet:'fontAwesome',
pager: true,
jsonReader:
{
root: "rows",
page: "page",
total: "total",
records: "records",
repeatitems: false,
Id: "Id"
},
});
jQuery("#list").jqGrid("navButtonAdd", {
caption: "",
buttonicon: "fa-table",
title: "Export To Excel",
onClickButton: function (e) {
var projectId = null;
var isFilterAreUsed = $('#grid').jqGrid('getGridParam', 'search'),
filters = $('#grid').jqGrid('getGridParam', 'postData').filters;
var Urls = "/UsersView/ExportToExcel_xlsxFormat?filters="+ encodeURIComponent(filters); //' + encodeURIComponent(filters);/
if (totalRecordsCount > 0) {
$.ajax({
url: Urls,
type: "POST",
//contentType: "application/json; charset=utf-8",
data: { "searchcriteria": filters, "projectId": projectId, "PageName": "MajorsView" },
//datatype: "json",
success: function (data) {
if (true) {
window.location = '/UsersView/SentFiletoClientMachine?file=' + data.filename;
}
else {
$("#resultDiv").html(data.errorMessage);
$("#resultDiv").addClass("text-danger");
}
},
error: function (ex) {
common.handleAjaxError(ex.status);
}
});
}
else {
bootbox.alert("There are no rows to export in the Participant List")
if (dialog) {
dialog.modal('hide');
}
}
}
});
});
https://jsfiddle.net/ap43xecs/10/
There are exist many option to solve the problem. The simplest one consist of sending ids of filtered rows to the server instead of sending filters parameter. Free jqGrid supports lastSelectedData parameter and thus you can use $('#grid').jqGrid('getGridParam', 'lastSelectedData') to get the array with items sorted and filtered corresponds to the current filter and sorting criteria. Every item of the returned array should contain Id property (or id property) which you can use on the server side to filter the data before exporting.
The second option would be to implement server side filtering based on the filters parameter, which you send currently to the server. The old answer (see FilterObjectSet) provides an example of filtering in case of usage Entity Framework. By the way, the answer and another one contain code, which I used for exporting grid data to Excel using Open XML SDK. You can compare it with your existing code.
In some situations it could be interesting to export grid data to Excel without writing any server code. The corresponding demo could be found in the issue and UPDATED part of the answer.

jtable child table not POSTing key value

I have a Jquery jtable that has a child table. As far as I can see it is set up as per the example in the jtable demos. The main tables= (contacts) and the child tables (categories) display without any problem. My problem is that the delete action on the category child table is not posting the row key value (categoryID) as I would expect it to and I cannot see why not. The similar action on the main table posts its just fine. Note the two console.log lines in the code below that output the postData variable, the first one reports the ID of the contact table line (ID), but the second one prints an empty array instead of the CategoryID. Any help appreciated.
Thanks
function ReturnAjax(theurl, postdata, errorfn) {
return $.ajax({
url: theurl,
type: 'POST',
dataType: 'json',
data: postdata,
cache: false,
error: errorfn
});
}
$('#ContactsTableContainer').jtable({
title: 'Contacts',
paging: true,
pageSize: 30,
sorting: true,
defaultSorting: 'LastName ASC',
selecting: true,
selectOnRowClick: true,
openChildAsAccordion: true,
deleteConfirmation: false,
actions: {
listAction: function(postData, jtParams) {
console.log("ContactsTableContainer - Loading list from custom function...");
return $.Deferred(function($dfd) {
$.ajax({
url: 'ContactsData.php?action=list&jtStartIndex=' + jtParams.jtStartIndex + '&jtPageSize=' + jtParams.jtPageSize + '&jtSorting=' + jtParams.jtSorting,
type: 'POST',
dataType: 'json',
data: postData,
success: function(data) {
if(data['RowIDs']) { RowIDs = data['RowIDs'].toString().split(','); }
$dfd.resolve(data);
},
error: MyError
});
});
},
deleteAction: function(postData) {
console.log('deleting from contacts - custom function..., '+JSON.stringify(postData));
$.when(
ReturnAjax(
'ContactsData.php?action=list&ContactID='+postData['ID'],
postData,
MyError
)
).then(
function(data) {
if (data.Result != 'OK') { alert(data.Message); }
var msg = '';
var len = data.Records.length;
if(len>0) {
msg = '\t'+data.Records[0].Category;
for(var i=1 ; i<len ; i++) { msg += '\n\t'+data.Records[i].Category; }
msg = 'Contact is in the following categories\n'+msg;
}
msg += '\n\nConfirm deletion of this contact';
if(confirm(msg)) {
$.when(
ReturnAjax(
'ContactsData.php?action=delete',
postData,
MyError
)
).done(
$('#ContactsTableContainer').jtable('reload')
);
} else {
$('#ContactsTableContainer').jtable('reload'); // Had to put this here to ensure that same delete button could be used again
}
}
).fail( function() { console.log('ajax call went wrong'); } );
}, // end of delete action
}, // end of actions
fields: {
ID: {
key: true,
create: false,
edit: false,
list: false,
visibility: 'hidden'
},
Categories: {
title: '',
width: '5%',
sorting: false,
create: false,
display: function(contact) {
var $img = $('<img src="Images/layers.png" title="Show contact\'s categories" />');
//Open child table when user clicks the image
$img.click(function() {
console.log('display function (contact)..., '+JSON.stringify(contact));
$('#ContactsTableContainer').jtable(
'openChildTable',
$img.closest('tr'), //Parent row
{
title: contact.record.Name + ' - Categories',
selecting: true,
selectOnRowClick: true,
actions: {
listAction: 'ContactsData.php?action=list&ContactID=' + contact.record.ID,
deleteAction: function(postData) {
console.log('deleting from custom category function..., '+JSON.stringify(postData));
$.when(
ReturnAjax(
'ContactsData.php?action=deleteAssignment&ContactID=' + contact.record.ID,
postData,
MyError
)
).done(
$('#ContactsTableContainer').jtable('reload')
);
}
},
fields: {
CategoryID: { key: true, create: false, edit: false, list: false, visibility: 'hidden' },
ContactID: { type: 'hidden', defaultValue: contact.record.ID },
Category: { title: 'Category' }
}
},
function(data) { data.childTable.jtable('load'); }
);
});
//Return image to show on the person row
return $img;
}
},
FirstName: {
  title: 'Forename',
  width: '25%',
},
LastName: {
  title: 'Surname',
  width: '25%',
},
HomePhone: {
title: 'Phone',
width: '15%',
sorting: false,
},
Mobile: {
title: 'Mobile',
width: '15%',
sorting: false,
},
Email: {
title: 'Email',
width: '20%',
sorting: false,
},
Name: {
  type: 'hidden'
},
}
});
//Load list from server
$('#ContactsTableContainer').jtable('load');
OK, I solved it, sorry to bother anyone who may have spent time looking at this. The problem was that my child table variable names were wrong they should have been category_ID and Contact_ID

Adding a column to a dstore backed dgrid

I have a grid with five columns - username, email, enabled, locked and remove.
Username, email, enabled and locked are sourced from the server. Remove is a client-side element used to indicate that a row should be removed.
I would like to either inject the default value of remove in the store on the client side as the grid content is loading, or set it as the user interacts with the CheckBox widget.
How can I catch the code which is requesting the objects from the server and add another column?
Or, is there a better way to do this.
var TrackableRest = declare([Rest, SimpleQuery, Trackable]);
var store = new TrackableRest({target: '/api/users', useRangeHeaders: true, idProperty: 'username'});
aspect.after(store, "fetch", function (deferred) {
return deferred.then(function (response) {
response.remove = false;
return json(response);
})
});
var grid = new (declare([OnDemandGrid, Selection, Editor]))({
collection: store,
className: "dgrid-autoheight",
columns: {
username: {
label: core.username
},
email: {
label: core.email
},
enabled: {
label: core.enabled,
editor: CheckBox,
editOn: "click",
sortable: false,
renderCell: libGrid.renderGridCheckbox
},
locked: {
label: core.locked,
editor: CheckBox,
editOn: "click",
sortable: false,
renderCell: libGrid.renderGridCheckbox
},
remove: {
editor: CheckBox,
editorArgs: {"checked": false},
editOn: "click",
label: core.remove,
sortable: false,
className: "remove-cb",
renderHeaderCell: function (node) {
var inp = domConstruct.create("input", {id: "cb-all", type: "checkbox"});
return inp;
},
renderCell: libGrid.renderGridCheckbox
}
},
selectionMode: "none"
}, 'grid');
In addition, I don't want to send the remove column to the server.
My final implementation was to code the remove column like so:
remove: {
editor: CheckBox,
label: core.remove,
sortable: false,
className: "remove-cb",
renderHeaderCell: function (node) {
var inp = domConstruct.create("input", {id: "cb-all", type: "checkbox"});
return inp;
}
}
The code to perform the removes is as follows:
var removeBtn = new Button({
label: core.remove
}, 'user-remove-btn');
removeBtn.startup();
removeBtn.on("click", function (event) {
var markedForDeletion = query(".dgrid-row .remove-cb input:checked", "user-grid");
if( markedForDeletion.length > 0 ) {
lib.confirmAction(core.areyousure, function () {
markedForDeletion.forEach(function (node) {
var row = grid.row(node);
store.remove(row.data.username);
});
});
}
});
Thus the remove column became a client-side only control that was handled by the grid and the event handler.

How to implement inline editing (Edit) of Azure Mobile Services Tables. How to process errors handling

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.
Please share thoughts.
Code update 1: Code update based on Oleg's suggested way of using ondblClickRow, Enter and Escape machanism
$("#list4").jqGrid({
url: myTableURL,
datatype: "json",
height: "auto",
colNames: ['RowNo', 'RouteId', 'Area'],
colModel: [
{ name: 'id', width: 70, editable: false },
{ name: 'RouteId', width: 70 },
{ name: 'Area', width: 150 }}
],
cmTemplate: { editable: true, editrules: { required: true} },
rowList: [10, 20, 30],
rowNum: 10,
sortname: "id",
prmNames: { search: null, nd: null },
ondblClickRow: function (rowid) {
var $self = $(this);
$self.jqGrid("editRow", rowid, {
mtype: "PATCH",
keys: true,
url: myTableURL + "/" +
$.jgrid.stripPref($self.jqGrid("getGridParam", "idPrefix"), rowid)
});
},
ajaxGridOptions: {
contentType: "application/json",
headers: {
"X-ZUMO-APPLICATION": "myKey"
}
},
serializeGridData: function (postData) {
if (postData.sidx) {
return {
$top: postData.rows,
$skip: (parseInt(postData.page, 10) - 1) * postData.rows,
$orderby: postData.sidx + " " + postData.sord,
$inlinecount: "allpages"
};
} else {
return {
$top: postData.rows,
$skip: (parseInt(postData.page, 10) - 1) * postData.rows,
$inlinecount: "allpages"
};
}
},
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);
},
beforeProcessing: function (data, textStatus, jqXHR) {
var rows = parseInt($(this).jqGrid("getGridParam", "rowNum"), 10);
data.total = Math.ceil(data.count / rows);
},
jsonReader: {
repeatitems: false,
root: "results",
records: "count"
},
loadError: function (jqXHR, textStatus, errorThrown) {
alert('HTTP status code: ' + jqXHR.status + '\n' +
'textStatus: ' + textStatus + '\n' +
'errorThrown: ' + errorThrown);
alert('HTTP message body (jqXHR.responseText): ' + '\n' + jqXHR.responseText);
},
pager: '#pager1',
viewrecords: true,
caption: "Schedule Data",
gridview: true,
autoencode: true
});
Combined code of inline editing and server side paging :
var $grid = $("#list4"),
azureHeaders = { "X-ZUMO-APPLICATION": "mykey" },
myTableURL = "https://mohit.azure-mobile.net/tables/Schedules",
inlineNavParams = {
save: false, // we want to add Save button manually. So we needn't no standard button
edit: true, add: true, del: true,
editParams: { mtype: "PATCH" },
addParams: {
addRowParams: {
//mtype: "POST", // default value
aftersavefunc: function (rowid, response) {
var rowData = $.parseJSON(response.responseText),
newId = rowData.id,
$self = $(this),
idPrefix = $self.jqGrid("getGridParam", "idPrefix", newId),
selrow = $self.jqGrid("getGridParam", "selrow", newId),
selArrayRow = $self.jqGrid("getGridParam", "selarrrow", newId),
oldId = $.jgrid.stripPref(idPrefix, rowid),
dataIndex = $self.jqGrid("getGridParam", "_index", newId),
i;
// update id in the _index
if (dataIndex != null && dataIndex[oldId] !== undefined) {
dataIndex[newId] = dataIndex[oldId];
delete dataIndex[oldId];
}
// 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({
colNames: ['RouteId', 'Area'],
colModel: [
{ name: 'RouteId', index: 'RouteId', width: 70 },
{ name: 'Area', index: 'Area', width: 150 }
],
cmTemplate: { editable: true, editrules: { required: 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 http://stackoverflow.com/a/15979809/315935 for the changes
url: myTableURL,
rownumbers: true,
datatype: "json",
rowNum: 10,
rowList: [10, 20, 30],
prmNames: {search: null, nd: null, sort: null, rows: null},
ajaxGridOptions: { contentType: "application/json", headers: azureHeaders },
// jsonReader: {
// repeatitems: false,
// root: function (obj) { return obj; }
// },
jsonReader: {
repeatitems: false,
root: "results",
records: "count"
},
loadError: function (jqXHR, textStatus, errorThrown) {
alert('HTTP status code: ' + jqXHR.status + '\n' +
'textStatus: ' + textStatus + '\n' +
'errorThrown: ' + errorThrown);
alert('HTTP message body (jqXHR.responseText): ' + '\n' + jqXHR.responseText);
},
gridview: true,
autoencode: true,
height: "auto",
// 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);
if (dataToSend.hasOwnProperty("oper")) {
delete dataToSend.oper;
}
if (dataToSend.hasOwnProperty("id")) {
delete dataToSend.id;
}
return JSON.stringify(dataToSend);
},
serializeGridData: function (postData) {
if (postData.sidx) {
return {
$top: postData.rows,
$skip: (parseInt(postData.page, 10) - 1) * postData.rows,
$orderby: postData.sidx + " " + postData.sord,
$inlinecount: "allpages"
};
} else {
return {
$top: postData.rows,
$skip: (parseInt(postData.page, 10) - 1) * postData.rows,
$inlinecount: "allpages"
};
}
},
beforeProcessing: function (data, textStatus, jqXHR) {
var rows = parseInt($(this).jqGrid("getGridParam", "rowNum"), 10);
data.total = Math.ceil(data.count/rows);
},
viewrecords: true,
rownumbers: true,
height: "auto",
pager: "#pager1",
caption: "Windows Azure Mobile Services REST API"
}).jqGrid("navGrid", "#pager1", { edit: false, add: false, del: false, search: false });
$grid.jqGrid("inlineNav", "#pager1", inlineNavParams);
$grid.jqGrid("navButtonAdd", "#pager1", {
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");
Code update 3 :
var $grid = $("#list4");
var myTableURL = 'https://mohit.azure-mobile.net/tables/Schedules';
var azureHeaders = { "X-ZUMO-APPLICATION": ", mykey" };
$grid.jqGrid({
url: myTableURL,
editurl: myTableURL,
datatype: "json",
height: "auto",
colNames: ['RowNo', 'RouteId', 'Area', 'BusStop', 'Seater', 'Lat', 'Long', 'Timing', 'FromTo', 'KeyPoint'],
colModel: [
{ name: 'id', index: 'id', width: 70, editable: false },
{ name: 'RouteId', index: 'RouteId', width: 70 }
],
cmTemplate: { editable: true, editrules: { required: true} },
rowList: [10, 20, 30],
rowNum: 10,
sortname: "id",
prmNames: { search: null, nd: null},
ondblClickRow: function (rowid) {
var $self = $(this);
$self.jqGrid("editRow", rowid, {
mtype: "PATCH",
keys: true,
url: myTableURL + "/" + $.jgrid.stripPref($self.jqGrid("getGridParam", "idPrefix"), rowid)
});
},
ajaxGridOptions: { contentType: "application/json", headers: azureHeaders },
ajaxRowOptions: { contentType: "application/json", headers: azureHeaders },
serializeGridData: function (postData) {
if (postData.sidx) {
return {
$top: postData.rows,
$skip: (parseInt(postData.page, 10) - 1) * postData.rows,
$orderby: postData.sidx + " " + postData.sord,
$inlinecount: "allpages"
};
}
else {
return {
$top: postData.rows,
$skip: (parseInt(postData.page, 10) - 1) * postData.rows,
$inlinecount: "allpages"
};
}
},
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);
},
beforeProcessing: function (data, textStatus, jqXHR) {
var rows = parseInt($(this).jqGrid("getGridParam", "rowNum"), 10);
data.total = Math.ceil(data.count / rows);
},
jsonReader: {
repeatitems: false,
root: "results",
records: "count"
},
loadError: function (jqXHR, textStatus, errorThrown) {
alert('HTTP status code: ' + jqXHR.status + '\n' +
'textStatus: ' + textStatus + '\n' +
'errorThrown: ' + errorThrown);
alert('HTTP message body (jqXHR.responseText): ' + '\n' + jqXHR.responseText);
},
pager: '#pager1',
viewrecords: true,
caption: "Bus Schedule Data",
gridview: true,
autoencode: true
});
inlineNavParams = {
save: false, // we want to add Save button manually. So we needn't no standard button
edit: true, add: true, del: true,
editParams: { mtype: "PATCH" },
addParams: {
addRowParams: {
aftersavefunc: function (rowid, response) {
var rowData = $.parseJSON(response.responseText),
newId = rowData.id,
$self = $(this),
idPrefix = $self.jqGrid("getGridParam", "idPrefix", newId),
selrow = $self.jqGrid("getGridParam", "selrow", newId),
selArrayRow = $self.jqGrid("getGridParam", "selarrrow", newId),
oldId = $.jgrid.stripPref(idPrefix, rowid),
dataIndex = $self.jqGrid("getGridParam", "_index", newId),
i;
if (dataIndex != null && dataIndex[oldId] !== undefined) {
dataIndex[newId] = dataIndex[oldId];
delete dataIndex[oldId];
}
$("#" + $.jgrid.jqID(rowid)).attr("id", idPrefix + newId);
if (selrow === rowid) {
$self.jqGrid("setGridParam", { selrow: idPrefix + newId });
}
if ($.isArray(selArrayRow)) {
i = $.inArray(rowid, selArrayRow);
if (i >= 0) {
selArrayRow[i] = idPrefix + newId;
}
}
$self.jqGrid("showAddEditButtons");
}
}
}
};
$grid.jqGrid("navGrid", "#pager1", { edit: false, add: false, del: false, search: false });
$grid.jqGrid("inlineNav", "#pager1", inlineNavParams);
$grid.jqGrid("navButtonAdd", "#pager1", {
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();
}
}
});
$.extend(true, $.jgrid.inlineEdit, {
keys: true,
afterrestorefunc: function () {
$(this).jqGrid("showAddEditButtons");
},
aftersavefunc: function () {
$(this).jqGrid("showAddEditButtons");
},
});
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:
never use restoreAfterSelect: false options
use save: false option of inlineNav
add custom "Save" button which looks like the corresponding button of inlineNav 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".

Resources