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

后端 未结 1 788
终归单人心
终归单人心 2021-01-26 23:23

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.

相关标签:
1条回答
  • 2021-01-27 00:14

    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".

    0 讨论(0)
提交回复
热议问题