/** * jqGrid extension for manipulating Grid Data * Copyright (c) 2008-2014, Tony Tomov, tony@trirand.com, http://trirand.com/blog/ * Copyright (c) 2014-2018, Oleg Kiriljuk, oleg.kiriljuk@ok-soft-gmbh.com * Dual licensed under the MIT and GPL licenses: * http://www.opensource.org/licenses/mit-license.php * http://www.gnu.org/licenses/gpl-2.0.html **/ /*jslint browser: true, eqeq: true, nomen: true, vars: true, devel: true, unparam: true, plusplus: true, white: true, todo: true */ /*global jQuery, define, exports, module, require */ (function (factory) { "use strict"; if (typeof define === "function" && define.amd) { // AMD. Register as an anonymous module. define([ "jquery", "./grid.base", "./jquery.fmatter", "./grid.common" ], factory); } else if (typeof module === "object" && module.exports) { // Node/CommonJS module.exports = function (root, $) { if (!root) { root = window; } if ($ === undefined) { // require("jquery") returns a factory that requires window to // build a jQuery instance, we normalize how we use modules // that require this pattern but the window provided is a noop // if it's defined (how jquery works) $ = typeof window !== "undefined" ? require("jquery") : require("jquery")(root); } require("./grid.base"); require("./jquery.fmatter"); require("./grid.common"); factory($); return $; }; } else { // Browser globals factory(jQuery); } }(function ($) { "use strict"; var jgrid = $.jgrid, fullBoolFeedback = jgrid.fullBoolFeedback, hasOneFromClasses = jgrid.hasOneFromClasses, base = $.fn.jqGrid, getGuiStateStyles = function (path) { return base.getGuiStyles.call(this, "states." + path); }; // begin module grid.inlinedit var editFeedback = function (o) { var args = $.makeArray(arguments).slice(1); args.unshift(""); args.unshift("Inline"); args.unshift(o); return jgrid.feedback.apply(this, args); }; jgrid.inlineEdit = jgrid.inlineEdit || {}; jgrid.extend({ //Editing editRow: function (rowid, keys, oneditfunc, successfunc, url, extraparam, aftersavefunc, errorfunc, afterrestorefunc, beforeEditRow) { // Compatible mode old versions var oMuligrid = {}, args = $.makeArray(arguments).slice(1); if ($.type(args[0]) === "object") { oMuligrid = args[0]; } else { if (keys !== undefined) { oMuligrid.keys = keys; } if ($.isFunction(oneditfunc)) { oMuligrid.oneditfunc = oneditfunc; } if ($.isFunction(successfunc)) { oMuligrid.successfunc = successfunc; } if (url !== undefined) { oMuligrid.url = url; } if (extraparam != null) { oMuligrid.extraparam = extraparam; } if ($.isFunction(aftersavefunc)) { oMuligrid.aftersavefunc = aftersavefunc; } if ($.isFunction(errorfunc)) { oMuligrid.errorfunc = errorfunc; } if ($.isFunction(afterrestorefunc)) { oMuligrid.afterrestorefunc = afterrestorefunc; } if ($.isFunction(beforeEditRow)) { oMuligrid.beforeEditRow = beforeEditRow; } // last two not as param, but as object (sorry) //if (restoreAfterError !== undefined) { oMuligrid.restoreAfterError = restoreAfterError; } //if (mtype !== undefined) { oMuligrid.mtype = mtype || "POST"; } } // End compatible return this.each(function () { var $t = this, $self = $($t), p = $t.p, cnt = 0, focus = null, svr = {}, editableValues = {}, colModel = p.colModel, opers = p.prmNames; if (!$t.grid) { return; } var o = $.extend(true, { keys: false, oneditfunc: null, successfunc: null, url: null, extraparam: {}, aftersavefunc: null, errorfunc: null, afterrestorefunc: null, restoreAfterError: true, beforeEditRow: null, //mtype: "POST", focusField: true }, jgrid.inlineEdit, p.inlineEditing || {}, oMuligrid), ind = $self.jqGrid("getInd", rowid, true), focusField = o.focusField, td = typeof focusField === "object" && focusField != null ? $(focusField.target || focusField).closest("tr.jqgrow>td")[0] : null; if (ind === false) { return; } if (o.extraparam[opers.oper] !== opers.addoper) { if (!editFeedback.call($t, o, "beforeEditRow", o, rowid)) { return; } } if (($(ind).attr("editable") || "0") === "0" && !$(ind).hasClass("not-editable-row")) { var editingInfo = jgrid.detectRowEditing.call($t, rowid); if (editingInfo != null && editingInfo.mode === "cellEditing") { var savedRowInfo = editingInfo.savedRow, tr = $t.rows[savedRowInfo.id], highlightClass = getGuiStateStyles.call($t, "select"); $self.jqGrid("restoreCell", savedRowInfo.id, savedRowInfo.ic); // remove highlighting of the cell $(tr.cells[savedRowInfo.ic]).removeClass("edit-cell " + highlightClass); $(tr).addClass(highlightClass).attr({ "aria-selected": "true", "tabindex": "0" }); } jgrid.enumEditableCells.call($t, ind, $(ind).hasClass("jqgrid-new-row") ? "add" : "edit", function (options) { var cm = options.cm, $dataFiled = $(options.dataElement), dataWidth = options.dataWidth, tmp, opt, elc, nm = cm.name, edittype = cm.edittype, iCol = options.iCol, editoptions = cm.editoptions || {}; editableValues[nm] = options.editable; if (options.editable === "hidden") { return; } try { tmp = $.unformat.call(this, options.td, { rowId: rowid, colModel: cm }, iCol); } catch (_) { tmp = edittype === "textarea" ? $dataFiled.text() : $dataFiled.html(); } svr[nm] = tmp; // include only editable fields in svr object $dataFiled.html(""); opt = $.extend({}, editoptions, { id: rowid + "_" + nm, name: nm, rowId: rowid, mode: options.mode, cm: cm, iCol: iCol }); if (tmp === " " || tmp === " " || (tmp.length === 1 && tmp.charCodeAt(0) === 160)) { tmp = ""; } elc = jgrid.createEl.call($t, edittype, opt, tmp, true, $.extend({}, jgrid.ajaxOptions, p.ajaxSelectOptions || {})); $(elc).addClass("editable"); $dataFiled.append(elc); if (dataWidth) { // change the width from auto or the value from editoptions // in case of editing ExpandColumn of TreeGrid $(elc).width(options.dataWidth); } jgrid.bindEv.call($t, elc, opt); //Again IE if (edittype === "select" && editoptions.multiple === true && editoptions.dataUrl === undefined && jgrid.msie) { $(elc).width($(elc).width()); } if (focus === null) { focus = iCol; } cnt++; }); if (cnt > 0) { svr.id = rowid; p.savedRow.push(svr); p.editingInfo[rowid] = { mode: "inlineEditing", savedRow: svr, editable: editableValues }; $(ind).attr("editable", "1"); if (focusField) { if (typeof focusField === "number" && parseInt(focusField, 10) <= colModel.length) { focus = focusField; } else if (typeof focusField === "string") { focus = p.iColByName[focusField]; } else if (td != null) { focus = td.cellIndex; } setTimeout(function () { // we want to use ":focusable" var nFrozenColumns = $self.jqGrid("getNumberOfFrozenColumns"), getTdByColIndex = function (iCol) { return p.frozenColumns && nFrozenColumns > 0 && focus < nFrozenColumns ? $t.grid.fbRows[ind.rowIndex].cells[iCol] : ind.cells[iCol]; }, getFocusable = function (elem) { return $(elem).find("input,textarea,select,button,object,*[tabindex]") .filter(":input:visible:not(:disabled)"); }, getFirstFocusable = function () { return getFocusable(p.frozenColumns && nFrozenColumns > 0 ? $t.grid.fbRows[ind.rowIndex] : ind) .first(); }, $fe = getFocusable(getTdByColIndex(focus)); if ($fe.length > 0) { $fe.first().focus(); } else if (typeof o.defaultFocusField === "number" || typeof o.defaultFocusField === "string") { $fe = getFocusable(getTdByColIndex(typeof o.defaultFocusField === "number" ? o.defaultFocusField : p.iColByName[o.defaultFocusField])); if ($fe.length === 0) { $fe = getFirstFocusable(); } $fe.first().focus(); } else { getFirstFocusable().focus(); } }, 0); } if (o.keys === true) { var $ind = $(ind); if (p.frozenColumns) { $ind = $ind.add($t.grid.fbRows[ind.rowIndex]); } $ind.on("keydown", function (e) { if (e.keyCode === 27) { $self.jqGrid("restoreRow", rowid, o.afterrestorefunc); return false; } if (e.keyCode === 13) { var ta = e.target; if (ta.tagName === "TEXTAREA") { return true; } $self.jqGrid("saveRow", rowid, o); return false; } }); } fullBoolFeedback.call($t, o.oneditfunc, "jqGridInlineEditRow", rowid, o); } } }); }, saveRow: function (rowid, successfunc, url, extraparam, aftersavefunc, errorfunc, afterrestorefunc, beforeSaveRow) { // Compatible mode old versions var args = $.makeArray(arguments).slice(1), o = {}, $t = this[0], $self = $($t), p = $t != null ? $t.p : null, editOrAdd, infoDialog = jgrid.info_dialog, isFunction = $.isFunction, fatalErrorFunction = jgrid.defaults != null && isFunction(jgrid.defaults.fatalError) ? jgrid.defaults.fatalError : alert; if (!$t.grid || p == null) { return; } if ($.type(args[0]) === "object") { o = args[0]; } else { if (isFunction(successfunc)) { o.successfunc = successfunc; } if (url !== undefined) { o.url = url; } if (extraparam !== undefined) { o.extraparam = extraparam; } if (isFunction(aftersavefunc)) { o.aftersavefunc = aftersavefunc; } if (isFunction(errorfunc)) { o.errorfunc = errorfunc; } if (isFunction(afterrestorefunc)) { o.afterrestorefunc = afterrestorefunc; } if (isFunction(beforeSaveRow)) { o.beforeSaveRow = beforeSaveRow; } } var getRes = function (path) { return $self.jqGrid("getGridRes", path); }; o = $.extend(true, { successfunc: null, url: null, extraparam: {}, aftersavefunc: null, errorfunc: null, afterrestorefunc: null, restoreAfterError: true, beforeSaveRow: null, ajaxSaveOptions: {}, serializeSaveData: null, mtype: "POST", saveui: "enable", savetext: getRes("defaults.savetext") || "Saving..." }, jgrid.inlineEdit, p.inlineEditing || {}, o); // End compatible // TODO: add return this.each(function(){....} var tmp = {}, tmp2 = {}, postData = {}, editable, k, fr, resp, cv, editingInfo, ind = $self.jqGrid("getInd", rowid, true), $tr = $(ind), opers = p.prmNames, errcap = getRes("errors.errcap"), bClose = getRes("edit.bClose"), isRemoteSave, isError, displayErrorMessage = function (text, relativeElem) { try { var relativeRect = jgrid.getRelativeRect.call($t, relativeElem); infoDialog.call($t, errcap, text, bClose, { top: relativeRect.top, left: relativeRect.left + $($t).closest(".ui-jqgrid").offset().left }); } catch (e) { fatalErrorFunction(text); } }; if (ind === false) { return; } editOrAdd = o.extraparam[opers.oper] === opers.addoper ? "add" : "edit"; if (!editFeedback.call($t, o, "beforeSaveRow", o, rowid, editOrAdd)) { return; } editable = $tr.attr("editable"); o.url = o.url || p.editurl; isRemoteSave = o.url !== "clientArray"; if (editable === "1") { editingInfo = $.jgrid.detectRowEditing.call($t, rowid); jgrid.enumEditableCells.call($t, ind, $tr.hasClass("jqgrid-new-row") ? "add" : "edit", function (options) { var cm = options.cm, formatter = cm.formatter, editoptions = cm.editoptions || {}, formatoptions = cm.formatoptions || {}, valueText = {}, v = jgrid.getEditedValue.call($t, $(options.dataElement), cm, valueText, options.editable); if (cm.edittype === "select" && cm.formatter !== "select") { tmp2[cm.name] = valueText.text; } cv = jgrid.checkValues.call($t, v, options.iCol, undefined, undefined, $.extend(options, { oldValue: editingInfo != null ? editingInfo.savedRow[cm.name] : null, newValue: v, oldRowData: editingInfo != null ? editingInfo.savedRow : null })); if (cv != null && cv[0] === false) { isError = true; displayErrorMessage(cv[1], options.td); return false; } if (formatter === "date" && formatoptions.sendFormatted !== true) { // TODO: call all other predefined formatters!!! Not only formatter: "date" have the problem. // Floating point separator for example v = $.unformat.date.call($t, v, cm); } if (isRemoteSave && editoptions.NullIfEmpty === true) { if (v === "") { v = "null"; } } tmp[cm.name] = v; }); if (isError) { return; } var idname; opers = p.prmNames; if (p.keyName === false) { idname = opers.id; } else { idname = p.keyName; } if (tmp) { tmp[opers.oper] = opers.editoper; if (tmp[idname] === undefined || tmp[idname] === "") { tmp[idname] = jgrid.stripPref(p.idPrefix, rowid); } tmp = $.extend({}, tmp, p.inlineData || {}, o.extraparam); } var validationOptions = { options: o, rowid: rowid, tr: ind, iRow: ind.rowIndex, savedRow: editingInfo.savedRow, newData: tmp, mode: editOrAdd }; if (!editFeedback.call($t, o, "saveRowValidation", validationOptions)) { if (validationOptions.errorText) { displayErrorMessage(validationOptions.errorText, ind); } return; } if (!isRemoteSave) { tmp = $.extend({}, tmp, tmp2); resp = $self.jqGrid("setRowData", rowid, tmp); $tr.attr("editable", "0"); for (k = 0; k < p.savedRow.length; k++) { if (String(p.savedRow[k].id) === String(rowid)) { fr = k; break; } } if (fr >= 0) { p.savedRow.splice(fr, 1); delete p.editingInfo[rowid]; } fullBoolFeedback.call($t, o.aftersavefunc, "jqGridInlineAfterSaveRow", rowid, resp, tmp, o); $tr.removeClass("jqgrid-new-row").off("keydown"); if (ind.id !== p.idPrefix + tmp[idname]) { $self.jqGrid("changeRowid", ind.id, p.idPrefix + tmp[idname]); } } else { $self.jqGrid("progressBar", { method: "show", loadtype: o.saveui, htmlcontent: o.savetext }); postData = $.extend({}, tmp, postData); postData[idname] = jgrid.stripPref(p.idPrefix, postData[idname]); if (p.autoEncodeOnEdit) { $.each(postData, function (n, v) { if (!isFunction(v)) { postData[n] = jgrid.oldEncodePostedData(v); } }); } if (ind.id !== p.idPrefix + postData[idname] && opers.idold != null && !postData.hasOwnProperty(opers.idold)) { postData[opers.idold] = jgrid.stripPref(p.idPrefix, ind.id); } $.ajax($.extend({ url: isFunction(o.url) ? o.url.call($t, postData[idname], editOrAdd, postData, o) : o.url, data: jgrid.serializeFeedback.call($t, isFunction(o.serializeSaveData) ? o.serializeSaveData : p.serializeRowData, "jqGridInlineSerializeSaveData", postData), type: isFunction(o.mtype) ? o.mtype.call($t, editOrAdd, o, postData[idname], postData) : o.mtype, complete: function (jqXHR, textStatus) { $self.jqGrid("progressBar", { method: "hide", loadtype: o.saveui }); // textStatus can be "abort", "timeout", "error", "parsererror" or some text from text part of HTTP error occurs // see the answer http://stackoverflow.com/a/3617710/315935 about xhr.readyState === 4 && xhr.status === 0 if ((jqXHR.status < 300 || jqXHR.status === 304) && (jqXHR.status !== 0 || jqXHR.readyState !== 4)) { var ret, sucret, j; sucret = $self.triggerHandler("jqGridInlineSuccessSaveRow", [jqXHR, rowid, o, editOrAdd, postData]); if (sucret == null || sucret === true) { sucret = [true, tmp]; } if (sucret[0] && isFunction(o.successfunc)) { sucret = o.successfunc.call($t, jqXHR, rowid, o, editOrAdd, postData); } if ($.isArray(sucret)) { // expect array - status, data, rowid ret = sucret[0]; tmp = sucret[1] || tmp; } else { ret = sucret; } if (ret === true) { if (p.autoEncodeOnEdit) { $.each(tmp, function (n, v) { tmp[n] = jgrid.oldDecodePostedData(v); }); } tmp = $.extend({}, tmp, tmp2); $self.jqGrid("setRowData", rowid, tmp); $tr.attr("editable", "0"); for (j = 0; j < p.savedRow.length; j++) { if (String(p.savedRow[j].id) === String(rowid)) { fr = j; break; } } if (fr >= 0) { p.savedRow.splice(fr, 1); delete p.editingInfo[rowid]; } fullBoolFeedback.call($t, o.aftersavefunc, "jqGridInlineAfterSaveRow", rowid, jqXHR, tmp, o); if (sucret[2] != null) { $self.jqGrid("changeRowid", rowid, p.idPrefix + sucret[2]); } else if (ind.id !== p.idPrefix + tmp[idname]) { $self.jqGrid("changeRowid", ind.id, p.idPrefix + tmp[idname]); } $tr.removeClass("jqgrid-new-row").off("keydown"); } else { fullBoolFeedback.call($t, o.errorfunc, "jqGridInlineErrorSaveRow", rowid, jqXHR, textStatus, null, o); if (o.restoreAfterError === true) { $self.jqGrid("restoreRow", rowid, o.afterrestorefunc); } } } }, error: function (res, stat, err) { $self.triggerHandler("jqGridInlineErrorSaveRow", [rowid, res, stat, err, o]); if (isFunction(o.errorfunc)) { o.errorfunc.call($t, rowid, res, stat, err); } else { var rT = res.responseText || res.statusText; try { infoDialog.call($t, errcap, '