1

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");
        },
    });
Mohit
  • 39
  • 1
  • 2
  • 10
  • I modified a little your code. I did the following 1) removed `index` property from all columns of colModel where the value of `index` is the same as the value of `name`. 2) changed formatting of the code so that the code could be read without usage of horizontal scrolling permanently 3) brought colors in the code (see [here](http://meta.stackexchange.com/questions/63800/interface-options-for-specifying-language-prettify)) 4) improved `serializeGridData` for the case `postData.sidx` is empty. You can additional consider whether you need to show `id` column. You can remove it. – Oleg Apr 15 '13 at 08:41
  • I need to keep id column. Does it work for you? I still get same error messages. – Mohit Apr 15 '13 at 12:20
  • all code which I posted is from workings demos. Which error exactly you have? Which demo demonstrate the problem? – Oleg Apr 15 '13 at 12:22
  • I updated details about error after your answer. Please take a look on last 3 comments. Let me know if any additional info is required. – Mohit Apr 15 '13 at 16:24
  • Sorry, now I see the comments and I tried the code. The error: you forget to include `ajaxRowOptions: { contentType: "application/json", headers: headers: {"X-ZUMO-APPLICATION": "YourKey"} }`. See the code from my answer. It modify `$.ajax` parameters during saving in the same way like option `ajaxGridOptions` option modify it during getting the data for the grid body. – Oleg Apr 15 '13 at 16:55
  • yes, it worked fine now. thanks again. Eagerly waiting for your answer with inlineNav enabled for editing and saving a new/existing row. – Mohit Apr 15 '13 at 19:09
  • I posted you before HTML code which contains the full working solution. So you can makes your own solution in absolutely the same way. I wanted just make the code more clean and to post more comments what I do any why it's really needed. I'll post new version of the code later. – Oleg Apr 15 '13 at 19:17
  • During posting the answer I try to analyse implementations problems in the current code of jqGrid. During writing answers on your question I posted some bug reports (see [here](http://www.trirand.com/blog/?page_id=393/bugs/ajax-complete-callback-of-form-editing-can-interpret-successful-responds-as-errors/#p28642) and [here](http://www.trirand.com/blog/?page_id=393/bugs/id-are-saved-in-wrong-way-in-case-of-usage-loadonce-true-with-idprefix/#p28645)) which are already fixed (see [here](https://github.com/tonytomov/jqGrid/commit/a66553b22a3fd75a21ae6e66adb374502298bd79) ...). So it takes time... – Oleg Apr 15 '13 at 19:52
  • I appended my answer with the code which can be used for inline editing with respect of `inlineNav`. – Oleg Apr 15 '13 at 22:55
  • Thanks Oleg for updating implementation with inlineNav. It works smoothly. I have some doubts as follows : 1. If you see in azure db table, there is an "id" corresponding to every row. Now while saving the data from jqGrid, it uses the "rowid" which is rowid in jqGrid (can be seen by setting "rownumbers: true"). If these two numbers (azure table row id and jqGrid rowid) does not match, then it will lead to wrong row update in azure db table. What is best way to prevent possibility of same? – Mohit Apr 16 '13 at 19:23
  • 2. When we do "Add a new row" and azure table row "id" field is also displayed in a column in jqGrid, then row data gets saved in azure db but at client side, in jdGrid, "id" column shows weird string (like jpg2) instead of "id" generated at server side for new row. How can we keep both in sync? – Mohit Apr 16 '13 at 19:23
  • 1) you use `jsonReader` having `repeatitems: false`. Default value of `id` property in `jsonReader` is `"id"`. So the rowid in the grid will be get from `id` of property of the data. So the rowid (id `id` attribute of every row element ``) will be the same as `id` in the database. It was the reason why I recommended you to remove `id` column from the grid. It holds *duplicate information* 2) if you do need to display `id` column you need add `key: true` property in the grid. Additionally the code of `aftersavefunc` from `addParams.addRowParams` must be more complicated. I updated the code – Oleg Apr 16 '13 at 20:36
  • I was not aware that "rowid in the grid will be get from id of property of the data". If this is case, then I will use "rownumbers: true" instead of keeping my id column. Then we won't need any special handling for "id" column on adding a new row – Mohit Apr 18 '13 at 16:51
  • Sorry, but `rownumbers: true` option is absolutely independent from the usage of `id`. The design if jqGrid is so the **every row of grid (`` element) *must* have `id` attribute (the rowid)**. So you should specify the value of `id`. The best practice for remote data (`datatype: "json"`) is the usage of `id` from the database. If you want to display column with `id` value then you should place `key: true` to inform jqGrid that the value from the column is unique and it should be used as the value of `id` attribute of rows additionally. Do you tried to use the code which I suggested? – Oleg Apr 18 '13 at 17:39
  • now going with your suggestion, I am not puuting explicit column "id" and using "rownumbers: true" as this is redundant information. Everything works fine till now. – Mohit Apr 18 '13 at 18:04
  • After this, I am trying to combine both paging and inline editing codes in a single unit. But getting error while initial loading of grid. On debugging, I could see that in network call "$skip=NaN", so I debugged "serializeGridData" function and I could see that value of postData.rows ($skip: (parseInt(postData.page, 10) - 1) * postData.rows) is undefined. My understanding was that to solve this, I have to define rowNum parameter of jqGrid but it is still not working after doing "rowNum: 10". Any idea here? – Mohit Apr 18 '13 at 18:04
  • Updated original question with combined code for reference – Mohit Apr 18 '13 at 18:18
  • First of all I repeat that the value from `rownumbers: true` is **not the same as `id`**. If you sort grid (sort `id` column for example) in another direction you will have different values in row number column and in `id` column. Next if you delete item which have `id=1` then you will have no more items with `id=1`. About another problem: one should debug such problems. I suppose that you made an error during the modification. Do you have the demo online now? Where? How many rows will be in the grid in the final solution? By the way if the *current* question is solved you should accept it. – Oleg Apr 18 '13 at 18:41
  • I did debugging and found out issue. prmNames: {search: null, nd: null, sort: null, rows: null} here we have define "rows: null". This was causing issue undefined for skip parameter. I have updated merge code for reference of others. Please remember a big thanks goes to Oleg as majority of code is from Oleg. – Mohit Apr 21 '13 at 05:27
  • Please check code update 3 – Mohit Apr 21 '13 at 05:31
  • I posted you before [the answer](http://stackoverflow.com/a/15979809/315935) which shows how to implement server side paging. You should use `prmNames: { search: null, nd: null }` to remove `_search` and `nd` options which will be not recognized by the server. You need use `serializeGridData` too. Other options should be stayed. – Oleg Apr 21 '13 at 05:35
  • I can't check or debug every code which you wrote. Sorry. If the code from "update 3" work then it's OK. – Oleg Apr 21 '13 at 05:39
  • Yes I am using code update 3 and everything is working smooth now. – Mohit Apr 21 '13 at 05:49
  • Oleg : now I understood that rownumers: true is different from "id". I do not want to show "id" column to my end customer and still want him to allow to edit and add new rows, so now I am using "hidden : true" with my "id" column and using "rownumbers : true". { name: 'id', index: 'id', width: 70, editable: false, hidden: true } – Mohit Apr 21 '13 at 05:50
  • So I am seeing one small problem : after adding a new row, if I want to delete it, I select it again but no delete button available. For delete part, I have posted another question http://stackoverflow.com/questions/15977761/usage-inlinenav-for-inline-editing-of-azure-mobile-services-tables-how-to-add-n and marking this one as answered. Thanks to Oleg a lot – Mohit Apr 21 '13 at 05:52
  • You can open Developer Tools of Internet Explorer, Google Chrome, Firibug and so on and verify that the value of `id` attribute of every row (``) hold already the `id` from the **server table**. If there are exist hidden column it will get the place on DOM. Why one need place duplicate of information on the page? So You should hold hidden row only if you want to show it to the user in some scenarios. If you remove `id` column from the grid you will don't miss anything. – Oleg Apr 21 '13 at 06:15
  • About usage of Delete: I posted you for long time the code which shows how to use delete. So writing an answer is mostly for other people or for more description of the code. – Oleg Apr 21 '13 at 06:21
  • @Oleg : I looked at code which you posted to me. I could see that you extended jqGrid delete functionality to work it properly with azure service. But my question is around having a "delete" button in "inlineNav". jqGrid does not provide it and I do not see any parameter also to enable same like "del : true". I think I have to add a new custom button for delete as you added for "save". Please let me know if I am thinking in wrong direction. – Mohit Apr 21 '13 at 07:13
  • `del : true` is *default* option of [navGrid](http://www.trirand.com/jqgridwiki/doku.php?id=wiki:navigator#parameters). – Oleg Apr 21 '13 at 07:18
  • see [the answer](http://stackoverflow.com/a/7140029/315935), [this one](http://stackoverflow.com/a/2850524/315935) or [this one](http://stackoverflow.com/a/7365228/315935) – Oleg Apr 21 '13 at 07:26
  • I was using "del:false" while defining "navgrid" and I was defining "del:true" in inlinenavparams. Looks like it does not work this way for del paramteter. Now I have removed "del:false" from navgrid definition and I can see delete button. When I delete a row by clicking delete button, a popup opens for confirmation. On confirming, row gets deleted successfully in azure(checked with fiddler and in db also) but still delete popup does not disappear. Any idea? – Mohit Apr 21 '13 at 08:40
  • I suppose that you don't implemented [the bug fix](http://www.trirand.com/blog/?page_id=393/bugs/ajax-complete-callback-of-form-editing-can-interpret-successful-responds-as-errors/#p28642) in your copy of `jquery.jqGrid.src.js`. I will post my answer (description of the problem) on your question later. – Oleg Apr 21 '13 at 09:26
  • yes...suggested one is correct fix...now delete also works fine. Thanks again. – Mohit Apr 21 '13 at 15:08

1 Answers1

0

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:

  1. never use restoreAfterSelect: false options
  2. use save: false option of inlineNav
  3. 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".

Community
  • 1
  • 1
Oleg
  • 220,925
  • 34
  • 403
  • 798
  • Thanks Oleg fro such a detailed explanation. This always helps a lot in understanding things better. As I don't want multiedit functionality, I have set "restoreAfterSelect: true". Although I have to use inlineNav for editing, saving & delete, but to learn more I tried doubleclick, enter & esc mechanism also as you have defined above. I can see that requested URL is proper for edit operation with PATCH method but request body is empty, instead of containing row data. – Mohit Apr 15 '13 at 04:22
  • I can see serializeRowData is also getting called properly, so not sure why request body is empty. Do we need to set some other property explicitly? I have updated question with my code. – Mohit Apr 15 '13 at 04:23
  • Error popup says "{code:400, error:"Error:content type'application/x-www-form-urlencoded;charset=UTF-8'is not supported"}". If needed, you can take a look on same on page4.html with url which I provided you on email. – Mohit Apr 15 '13 at 04:37