/** * jqGrid extension for cellediting 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 **/ /** * all events and options here are added anonymous and not in the base grid * since the array is to big. Here is the order of execution. * From this point we use jQuery isFunction * formatCell * beforeEditCell, * onSelectCell (used only for non-editable cells) * afterEditCell, * beforeSaveCell, (called before validation of values if any) * beforeSubmitCell (if cellsubmit remote (Ajax)) * afterSubmitCell(if cellsubmit remote (Ajax)), * afterSaveCell, * errorCell, * serializeCellData - new * Options * cellsubmit ("remote","clientArray") (added in grid options) * cellurl * ajaxCellOptions **/ /*jshint eqeqeq:false */ /*global jQuery, define, exports, module, require */ /*jslint browser: true, eqeq: true, plusplus: true, vars: true, white: true, todo: true */ (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($, root); return $; }; } else { // Browser globals factory(jQuery); } }(function ($) { "use strict"; var jgrid = $.jgrid, feedback = function () { // short form of $.jgrid.feedback to save usage this.p as the first parameter var args = $.makeArray(arguments); args.unshift(""); args.unshift(""); args.unshift(this.p); return jgrid.feedback.apply(this, args); }; // begin module grid.celledit var getTdByColumnIndex = function (tr, iCol) { var $t = this, frozenRows = $t.grid.fbRows; tr = frozenRows != null && frozenRows[0].cells.length > iCol ? frozenRows[tr.rowIndex] : tr; return tr != null && tr.cells != null ? $(tr.cells[iCol]) : $(); }, safeHeightSet = function ($elem, newHeight) { var height = $elem.height(); if (Math.abs(height - newHeight) >= 1 && newHeight > 0) { $elem.height(newHeight); height = $elem.height(); if (Math.abs(newHeight - height) >= 1) { $elem.height(newHeight + Math.round((newHeight - height))); } } }; jgrid.extend({ editCell: function (iRow, iCol, ed) { return this.each(function () { var $t = this, $self = $($t), p = $t.p, nm, tmp, $td, cm, rows = $t.rows; if (!$t.grid || p.cellEdit !== true || rows == null || rows[iRow] == null) { return; } iRow = parseInt(iRow, 10); // we change iRow and rows[iRow] can be change too iCol = parseInt(iCol, 10); if (isNaN(iRow) || isNaN(iCol)) { return; } var tr = rows[iRow], rowid = tr != null ? tr.id : null, $tr = $(tr), edittype, iColOld = parseInt(p.iCol, 10), iRowOld = parseInt(p.iRow, 10), $trOld = $(rows[iRowOld]), savedRow = p.savedRow; // select the row that can be used for other methods if (rowid == null) { return; } p.selrow = rowid; if (!p.knv) { $self.jqGrid("GridNav"); } // check to see if we have already edited cell if (savedRow.length > 0 && $trOld.length > 0) { // prevent second click on that field and enable selects if (ed === true) { if (iRow === iRowOld && iCol === iColOld) { return; } } // save the cell $self.jqGrid("saveCell", savedRow[0].id, savedRow[0].ic); } else { setTimeout(function () { $("#" + jgrid.jqID(p.knv)).attr("tabindex", "-1").focus(); }, 1); } cm = p.colModel[iCol]; nm = cm.name; if (nm === "subgrid" || nm === "cb" || nm === "rn") { return; } $td = getTdByColumnIndex.call($t, tr, iCol); var editable = cm.editable, mode = "cell"; if ($.isFunction(editable)) { editable = editable.call($t, { rowid: rowid, iCol: iCol, iRow: iRow, cmName: nm, cm: cm, mode: mode }); } var highlightClasses = $self.jqGrid("getGuiStyles", "states.select", "edit-cell"), hoverClasses = $self.jqGrid("getGuiStyles", "states.hover", "selected-row"); if (editable === true && ed === true && !$td.hasClass("not-editable-cell")) { if (!p.noCellSelection) { if (iColOld >= 0 && iRowOld >= 0) { getTdByColumnIndex.call($t, $trOld[0], iColOld).removeClass(highlightClasses); $trOld.removeClass(hoverClasses); } $td.addClass(highlightClasses); $tr.addClass(hoverClasses); } if (!cm.edittype) { cm.edittype = "text"; } edittype = cm.edittype; try { tmp = $.unformat.call($t, $td, { rowId: rowid, colModel: cm }, iCol); } catch (ex) { tmp = edittype === "textarea" ? $td.text() : $td.html(); } if (p.autoEncodeOnEdit) { tmp = jgrid.oldDecodePostedData(tmp); } if (tmp === " " || tmp === " " || (tmp.length === 1 && tmp.charCodeAt(0) === 160)) { tmp = ""; } if ($.isFunction(p.formatCell)) { var tmp2 = p.formatCell.call($t, rowid, nm, tmp, iRow, iCol); if (tmp2 !== undefined) { tmp = tmp2; } } feedback.call($t, "beforeEditCell", rowid, nm, tmp, iRow, iCol); savedRow.push({ id: iRow, ic: iCol, name: nm, v: tmp }); p.editingInfo[rowid] = { mode: "cellEditing", savedRow: savedRow[savedRow.length - 1], editable: {} }; p.editingInfo[rowid].editable[nm] = editable; var opt = $.extend({}, cm.editoptions || {}, { id: iRow + "_" + nm, name: nm, rowId: rowid, mode: mode, cm: cm, iCol: iCol }); var elc = jgrid.createEl.call($t, edittype, opt, tmp, true, $.extend({}, jgrid.ajaxOptions, p.ajaxSelectOptions || {})), $dataFiled = $td, editingColumnWithTreeGridIcon = p.treeGrid === true && nm === p.ExpandColumn; if (editingColumnWithTreeGridIcon) { $dataFiled = $td.children("span.cell-wrapperleaf,span.cell-wrapper"); } $dataFiled.html("").append(elc).attr("tabindex", "0"); if (editingColumnWithTreeGridIcon) { // && elc.style.width === "100%" $(elc).width($td.width() - $td.children("div.tree-wrap").outerWidth()); } jgrid.bindEv.call($t, elc, opt); if (p.frozenColumns && iCol < $self.jqGrid("getNumberOfFrozenColumns")) { safeHeightSet($($t.rows[tr.rowIndex].cells[iCol]), $td.height()); } setTimeout(function () { $(elc).focus(); }, 0); $("input, select, textarea", $td).on("keydown", function (e) { if (e.keyCode === 27) { if ($("input.hasDatepicker", $td).length > 0) { if ($(".ui-datepicker").is(":hidden")) { $self.jqGrid("restoreCell", iRow, iCol); } else { $("input.hasDatepicker", $td).datepicker("hide"); } } else { $self.jqGrid("restoreCell", iRow, iCol); } } //ESC if (e.keyCode === 13 && !e.shiftKey) { $self.jqGrid("saveCell", iRow, iCol); // Prevent default action return false; } //Enter if (e.keyCode === 9) { if (!$t.grid.hDiv.loading) { if (e.shiftKey) { $self.jqGrid("prevCell", iRow, iCol); //Shift TAb } else { $self.jqGrid("nextCell", iRow, iCol); //Tab } } else { return false; } } e.stopPropagation(); }); feedback.call($t, "afterEditCell", rowid, nm, tmp, iRow, iCol); } else { if (!p.noCellSelection) { if (iColOld >= 0 && iRowOld >= 0) { getTdByColumnIndex.call($t, $trOld[0], iColOld).removeClass(highlightClasses); $trOld.removeClass(hoverClasses); } $td.addClass(highlightClasses); $tr.addClass(hoverClasses); } tmp = $td.html().replace(/ /ig, ""); feedback.call($t, "onSelectCell", rowid, nm, tmp, iRow, iCol); } p.iCol = iCol; p.iRow = iRow; }); }, saveCell: function (iRow, iCol) { return this.each(function () { var $t = this, $self = $($t), p = $t.p, grid = $t.grid, infoDialog = jgrid.info_dialog, jqID = jgrid.jqID; if (!grid || p.cellEdit !== true) { return; } var errors = $self.jqGrid("getGridRes", "errors"), errcap = errors.errcap, edit = $self.jqGrid("getGridRes", "edit"), bClose = edit.bClose, savedRow = p.savedRow, fr = savedRow.length >= 1 ? 0 : null; if (fr !== null) { var tr = $t.rows[iRow], rowid = tr != null ? tr.id : null, $tr = tr != null ? $(tr) : $(), cm = p.colModel[iCol], nm = cm.name, vv, $td = getTdByColumnIndex.call($t, tr, iCol), valueText = {}, v = jgrid.getEditedValue.call($t, $td, cm, valueText); // The common approach is if nothing changed do not do anything if (v !== savedRow[fr].v) { vv = $self.triggerHandler("jqGridBeforeSaveCell", [rowid, nm, v, iRow, iCol]); if (vv !== undefined) { v = vv; } if ($.isFunction(p.beforeSaveCell)) { vv = p.beforeSaveCell.call($t, rowid, nm, v, iRow, iCol); if (vv !== undefined) { v = vv; } } var cv = jgrid.checkValues.call($t, v, iCol, undefined, undefined, { oldValue: savedRow[fr].v, newValue: v, cmName: nm, rowid: rowid, iCol: iCol, iRow: iRow, cm: cm, tr: tr, td: $td, mode: "cell" }), formatoptions = cm.formatoptions || {}; if (cv == null || cv === true || cv[0] === true) { var addpost = $self.triggerHandler("jqGridBeforeSubmitCell", [rowid, nm, v, iRow, iCol]) || {}; if ($.isFunction(p.beforeSubmitCell)) { addpost = p.beforeSubmitCell.call($t, rowid, nm, v, iRow, iCol); if (!addpost) { addpost = {}; } } if ($("input.hasDatepicker", $td).length > 0) { $("input.hasDatepicker", $td).datepicker("hide"); } if (cm.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 (p.cellsubmit === "remote") { if (p.cellurl) { var postdata = {}; postdata[nm] = v; var opers = p.prmNames, idname = opers.id, oper = opers.oper; postdata[idname] = jgrid.stripPref(p.idPrefix, rowid); postdata[oper] = opers.editoper; postdata = $.extend(addpost, postdata); if (p.autoEncodeOnEdit) { $.each(postdata, function (n, val) { if (!$.isFunction(val)) { postdata[n] = jgrid.oldEncodePostedData(val); } }); } $self.jqGrid("progressBar", { method: "show", loadtype: p.loadui, htmlcontent: $self.jqGrid("getGridRes", "defaults.savetext") || "Saving..." }); grid.hDiv.loading = true; $.ajax($.extend({ url: $.isFunction(p.cellurl) ? p.cellurl.call($t, p.cellurl, iRow, iCol, rowid, v, nm) : p.cellurl, //data :$.isFunction(p.serializeCellData) ? p.serializeCellData.call($t, postdata) : postdata, data: jgrid.serializeFeedback.call($t, p.serializeCellData, "jqGridSerializeCellData", postdata), type: "POST", complete: function (jqXHR) { grid.endReq.call($t); if ((jqXHR.status < 300 || jqXHR.status === 304) && (jqXHR.status !== 0 || jqXHR.readyState !== 4)) { var ret = $self.triggerHandler("jqGridAfterSubmitCell", [$t, jqXHR, postdata.id, nm, v, iRow, iCol]) || [true, ""]; if (ret === true || (ret[0] === true && $.isFunction(p.afterSubmitCell))) { ret = p.afterSubmitCell.call($t, jqXHR, postdata.id, nm, v, iRow, iCol); } if (ret == null || ret === true || ret[0] === true) { $self.jqGrid("setCell", rowid, iCol, v, false, false, true); $td.addClass("dirty-cell"); $tr.addClass("edited"); feedback.call($t, "afterSaveCell", rowid, nm, v, iRow, iCol); savedRow.splice(0, 1); delete p.editingInfo[rowid]; } else { infoDialog.call($t, errcap, ret[1], bClose); $self.jqGrid("restoreCell", iRow, iCol); } } }, error: function (jqXHR, textStatus, errorThrown) { $self.triggerHandler("jqGridErrorCell", [jqXHR, textStatus, errorThrown]); if ($.isFunction(p.errorCell)) { p.errorCell.call($t, jqXHR, textStatus, errorThrown); $self.jqGrid("restoreCell", iRow, iCol); } else { infoDialog.call($t, errcap, jqXHR.status + " : " + jqXHR.statusText + "
" + textStatus, bClose); $self.jqGrid("restoreCell", iRow, iCol); } } }, jgrid.ajaxOptions, p.ajaxCellOptions || {})); } else { try { infoDialog.call($t, errcap, errors.nourl, bClose); $self.jqGrid("restoreCell", iRow, iCol); } catch (ignore) { } } } if (p.cellsubmit === "clientArray") { $self.jqGrid("setCell", rowid, iCol, cm.edittype === "select" && cm.formatter !== "select" ? valueText.text : v, false, false, true); $td.addClass("dirty-cell"); $tr.addClass("edited"); feedback.call($t, "afterSaveCell", rowid, nm, v, iRow, iCol); if (p.frozenColumns && iCol < $self.jqGrid("getNumberOfFrozenColumns")) { try { $t.rows[tr.rowIndex].cells[iCol].style.height = ""; } catch (ignore) { } } savedRow.splice(0, 1); delete p.editingInfo[rowid]; } } else { try { setTimeout(function () { var relativeRect = jgrid.getRelativeRect.call($t, $td); infoDialog.call($t, errcap, v + " " + cv[1], bClose, { top: relativeRect.top, left: relativeRect.left + $($t).closest(".ui-jqgrid").offset().left }); }, 50); $self.jqGrid("restoreCell", iRow, iCol); } catch (ignore) { } } } else { $self.jqGrid("restoreCell", iRow, iCol); } } setTimeout(function () { $("#" + jqID(p.knv)).attr("tabindex", "-1").focus(); }, 0); }); }, restoreCell: function (iRow, iCol) { return this.each(function () { var $t = this, p = $t.p, tr = $t.rows[iRow], rowid = tr.id, v, cm, formatoptions; if (!$t.grid || p.cellEdit !== true) { return; } var savedRow = p.savedRow, $td = getTdByColumnIndex.call($t, tr, iCol); if (savedRow.length >= 1) { // datepicker fix if ($.isFunction($.fn.datepicker)) { try { $("input.hasDatepicker", $td).datepicker("hide"); } catch (ignore) { } } cm = p.colModel[iCol]; if (p.treeGrid === true && cm != null && cm.name === p.ExpandColumn) { $td.children("span.cell-wrapperleaf,span.cell-wrapper").empty(); } else { $td.empty(); } $td.attr("tabindex", "-1"); v = savedRow[0].v; if (cm != null) { formatoptions = cm.formatoptions || {}; if (cm.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); } $($t).jqGrid("setCell", rowid, iCol, v, false, false, true); if (p.frozenColumns && iCol < $($t).jqGrid("getNumberOfFrozenColumns")) { try { $t.rows[tr.rowIndex].cells[iCol].style.height = ""; } catch (ignore) { } } } feedback.call($t, "afterRestoreCell", rowid, v, iRow, iCol); savedRow.splice(0, 1); delete p.editingInfo[rowid]; } setTimeout(function () { $("#" + p.knv).attr("tabindex", "-1").focus(); }, 0); }); }, nextCell: function (iRow, iCol) { return this.each(function () { var $t = this, $self = $($t), p = $t.p, nCol = false, i, editable, cm, rows = $t.rows; if (!$t.grid || p.cellEdit !== true || rows == null || rows[iRow] == null) { return; } // try to find next editable cell for (i = iCol + 1; i < p.colModel.length; i++) { cm = p.colModel[i]; editable = cm.editable; if ($.isFunction(editable)) { editable = editable.call($t, { rowid: rows[iRow].id, iCol: i, iRow: iRow, cmName: cm.name, cm: cm, mode: "cell" }); } if (editable === true) { nCol = i; break; } } if (nCol !== false) { $self.jqGrid("editCell", iRow, nCol, true); } else { if (p.savedRow.length > 0) { $self.jqGrid("saveCell", iRow, iCol); } } }); }, prevCell: function (iRow, iCol) { return this.each(function () { var $t = this, $self = $($t), p = $t.p, nCol = false, i, editable, cm, rows = $t.rows; if (!$t.grid || p.cellEdit !== true || rows == null || rows[iRow] == null) { return; } // try to find next editable cell for (i = iCol - 1; i >= 0; i--) { cm = p.colModel[i]; editable = cm.editable; if ($.isFunction(editable)) { editable = editable.call($t, { rowid: rows[iRow].id, iCol: i, iRow: iRow, cmName: cm.name, cm: cm, mode: "cell" }); } if (editable === true) { nCol = i; break; } } if (nCol !== false) { $self.jqGrid("editCell", iRow, nCol, true); } else { if (p.savedRow.length > 0) { $self.jqGrid("saveCell", iRow, iCol); } } }); }, GridNav: function () { return this.each(function () { var $t = this, $self = $($t), p = $t.p, grid = $t.grid, i, kdir; if (!grid || p.cellEdit !== true) { return; } var bDiv = grid.bDiv; // trick to process keydown on non input elements p.knv = p.id + "_kn"; var selection = $("
"); function scrollGrid(iR, iC, tp) { var tr = $t.rows[iR]; if (tp.substr(0, 1) === "v") { var ch = bDiv.clientHeight, st = bDiv.scrollTop, nRot = tr.offsetTop + tr.clientHeight, pRot = tr.offsetTop; if (tp === "vd") { if (nRot >= st + ch) { bDiv.scrollTop = bDiv.scrollTop + tr.clientHeight; } } if (tp === "vu") { if (pRot < st) { bDiv.scrollTop = bDiv.scrollTop - tr.clientHeight; } } } if (tp === "h") { var cw = bDiv.clientWidth, sl = bDiv.scrollLeft, td = tr.cells[iC], nCol = td.offsetLeft + td.clientWidth, pCol = td.offsetLeft; if (nCol >= cw + parseInt(sl, 10)) { bDiv.scrollLeft = bDiv.scrollLeft + td.clientWidth; } else if (pCol < sl) { bDiv.scrollLeft = bDiv.scrollLeft - td.clientWidth; } } } function findNextVisible(iC, act) { var ind = 0, j, colModel = p.colModel; if (act === "lft") { ind = iC + 1; for (j = iC; j >= 0; j--) { if (colModel[j].hidden !== true) { ind = j; break; } } } if (act === "rgt") { ind = iC - 1; for (j = iC; j < colModel.length; j++) { if (colModel[j].hidden !== true) { ind = j; break; } } } return ind; } $(selection).insertBefore(grid.cDiv); $("#" + p.knv) .focus() .keydown(function (e) { var iRowOld = parseInt(p.iRow, 10), iColOld = parseInt(p.iCol, 10); kdir = e.keyCode; if (p.direction === "rtl") { if (kdir === 37) { kdir = 39; } else if (kdir === 39) { kdir = 37; } } switch (kdir) { case 38: if (iRowOld - 1 > 0) { scrollGrid(iRowOld - 1, iColOld, "vu"); $self.jqGrid("editCell", iRowOld - 1, iColOld, false); } break; case 40: if (iRowOld + 1 <= $t.rows.length - 1) { scrollGrid(iRowOld + 1, iColOld, "vd"); $self.jqGrid("editCell", iRowOld + 1, iColOld, false); } break; case 37: if (iColOld - 1 >= 0) { i = findNextVisible(iColOld - 1, "lft"); scrollGrid(iRowOld, i, "h"); $self.jqGrid("editCell", iRowOld, i, false); } break; case 39: if (iColOld + 1 <= p.colModel.length - 1) { i = findNextVisible(iColOld + 1, "rgt"); scrollGrid(iRowOld, i, "h"); $self.jqGrid("editCell", iRowOld, i, false); } break; case 13: if (iColOld >= 0 && iRowOld >= 0) { $self.jqGrid("editCell", iRowOld, iColOld, true); } break; default: return true; } return false; }); }); }, getChangedCells: function (mthd) { var ret = []; if (!mthd) { mthd = "all"; } this.each(function () { var $t = this, p = $t.p, htmlDecode = jgrid.htmlDecode, rows = $t.rows; if (!$t.grid || p.cellEdit !== true) { return; } $(rows).each(function (j) { var res = {}; if ($(this).hasClass("edited")) { var tr = this; $(this.cells).each(function (i) { var cm = p.colModel[i], nm = cm.name, $td = getTdByColumnIndex.call($t, tr, i); // $td = $(this); if (nm !== "cb" && nm !== "subgrid" && nm !== "rn" && (mthd !== "dirty" || $td.hasClass("dirty-cell"))) { try { res[nm] = $.unformat.call($t, $td[0], { rowId: rows[j].id, colModel: cm }, i); } catch (e) { res[nm] = htmlDecode($td.html()); } } }); res.id = this.id; ret.push(res); } }); }); return ret; } }); // end module grid.celledit }));