JQGRID:任何简单的方法来实现像jqGrid实现的Excel的撤消

编辑于2012年5月14日:我终于能够跳出我的懒惰,并准备分享实际的EXCEL LIKE JQGRID实施 。 在less数浏览器上,jsfiddle中的单元格select可能会很奇怪,但它应该在您的开发框上正常工作。 玩的开心!!!!

编辑于9/13/11:这是我第一次使用JQGrid。 我正在使用版本4.1.2。 我花了几个星期的时间来把Excel这样的网格放在一起,最大的挑战是find正确的关于如何使用JQGrid的信息。 我目前的设置有很多ajax更新和图像画廊和使用jqgrid格式化,但是我在这里放的是能够使用JQgrid与服务器端分页的主要代码,擅长复制粘贴和演示其他几个functionjqGrid的。 这是我从这个社区得到的所有帮助的回报。

对于刚刚跳到JQGrid的人来说,你很快就会发现在jqgrid中使用Textarea有一些挑战。 你可以在这里find一些解决scheme

原文:
只是为了在提出我的问题之前给点小更新….

我已经能够在我使用的jqgrid(在经过许多论坛之后)上提供一些附加function,包括:从Excel复制粘贴到jqgrid,在按键和dblclick上编辑单元格,复制和粘贴多个单元格从一个块到另一个在同一个网格上使用鼠标select(从这里使用Javascript来'总结选定的单元格在IE6中 )

大多数复制粘贴function只能在IE上运行。 我将所有更改一起保存在“保存”button上,使单元格上的所有更新都显示在屏幕上,直到用户点击“保存”button。

虽然目前情况仍然不变,但现在我想在纸面上实现devise,而不是以后。 我正在寻找一个简单的方法来UNDO只有最后的变化。 我一直在考虑使用jQuery的“data()”和“removeData()”方法来实现这一点,但如果有任何已经存在的jqgrid框架,将帮助,我想知道。 有什么build议么??

<style type="text/css"> .sel {background-color: #96B9DC !important; } .altered {} </style> <script type="text/javascript"> var enableOnSelectEvent = false; // handle text selection </script> <div style="width:100%; background-color:#FFF; border:1px solid #000;"><input id="btnsavechanges" value="Save Changes" style="width:120px;" class="formbutton ui-corner-all" type="button" onclick="getChanges(); return false;" /></div> <table id="grd_asset" width="100%" onSelectStart="return enableOnSelectEvent;"></table> <div id="pfrmac" style='width:100%;'></div> <input type="hidden" id="hidSelected" value="" /> <!-- copy content from the grid cells --> <input type="hidden" id="hidCopiedText" value="" /> <!-- Start and End of cell selection --> <input type="hidden" id="hidStartCell" value="" /> <input type="hidden" id="hidEndCell" value="" /> <!-- Start and End of last modified cell(s) --> <input type="hidden" id="hidModStartCell" value="" /> <input type="hidden" id="hidModEndCell" value="" /> <script type="text/javascript"> /*************************************************/ /**************** Grid Utilities ****************/ /*************************************************/ FnGrid = function () { this.GridColumns = function () { return assetGrid.jqGrid('getGridParam', 'colModel'); } this.GetSelCells = function () { return assetGrid.find("td.sel"); } this.ClearSelection = function () { assetGrid.find("td").removeClass("sel"); } this.ClearSavedHistory = function () { assetGrid.removeData(); } this.ClearMarkedChanges = function () { assetGrid.find("tr").removeClass("altered"); } this.GetRowCells = function (cell) { return cell.parent().children("td") } this.GetRowId = function (cell) { var row = cell.closest('tr.jqgrow'); return row.attr('id'); } this.GetRowIndex = function (cell) { var cellrow = cell.parent(); return cellrow.parent().children("tr").index(cellrow); } this.GetColIndex = function (cell) { return cell.parent().children("td").index(cell); } this.IsInEditMode = function () { var savedRows = assetGrid.getGridParam('savedRow'); return (savedRows && savedRows.length > 0); } this.PutCellInEdit = function (cell, irow, icol, edit) { assetGrid.editCell(irow, icol, edit); // transfer focus to the input var inp = $(cell).children("input") if (inp && inp.length > 0) { $(inp[0]).val(''); $(inp[0]).focus(); } } this.HandleEditMode = function (cell, e) { var ctrl = e.ctrlKey; var alt = e.altKey; var keyCode = (e.keyCode ? e.keyCode : e.which); if (keyCode) { if (keyCode >= 32 && keyCode <= 126 && !ctrl && !alt) { // switch the cell to edit mode if not already if (!($(cell).hasClass("edit-cell"))) { this.PutCellInEdit(cell, this.GetRowIndex($(cell)), this.GetColIndex($(cell)), true); } } } return true; } this.HandleInputNavigation = function (ele, evt) { evt = window.event || evt; switch (evt.keyCode) { // down arrow case 40: if (!$(ele).parent().hasClass("altered")) $(ele).parent().addClass("altered"); irow = this.GetRowIndex($(ele).parent()); icol = this.GetColIndex($(ele).parent()) var prevcell = irow + "," + icol; $("#hidModStartCell").val(prevcell); $("#hidModEndCell").val(prevcell); downele = $(ele).parent() .parent() .next() .children("td")[this.GetColIndex($(ele).parent())]; this.ClearSelection(); assetGrid.editCell(this.GetRowIndex($(downele)), this.GetColIndex($(downele)), true); break; // up arrow case 38: if (!$(ele).parent().hasClass("altered")) $(ele).parent().addClass("altered"); irow = this.GetRowIndex($(ele).parent()); icol = this.GetColIndex($(ele).parent()) var prevcell = irow + "," + icol; $("#hidModStartCell").val(prevcell); $("#hidModEndCell").val(prevcell); topele = $(ele).parent() .parent() .prev() .children("td")[this.GetColIndex($(ele).parent())]; if (this.GetRowIndex($(topele)) <= 0) break; this.ClearSelection(); assetGrid.editCell(this.GetRowIndex($(topele)), this.GetColIndex($(topele)), true); break; } } } var autocomp = new AutoCompleteRequest(); var lastSel = ""; var assetGrid = $('#grd_asset'); var start = null; var fnassetgrid = new FnGrid(); var lastSel = -1; function selectTo(cell) { if (start == null) return; fnassetgrid.ClearSelection(); var stop = $(cell); var tbl = start.closest("table"); var rs = tbl.children("tbody").children("tr"); var r0 = rs.index(start.parent()), c0 = fnassetgrid.GetColIndex(start); var r1 = rs.index(stop.parent()), c1 = fnassetgrid.GetColIndex(stop); var concat = ""; for (var i = r0; i <= r1; i++) { var cells = $(rs.get(i)).children("td"); var rowid = 0; for (var j = c0; j <= c1; j++) { var cell = $(cells.get(j)); if (rowid == 0) rowid = fnassetgrid.GetRowId(cell); if (cell.is(":hidden")) continue; cell.addClass("sel"); concat += assetGrid.getCell(rowid, j) + "\t"; } if (concat.lastIndexOf("\t") == concat.length - 1) concat = concat.substring(0, concat.lastIndexOf("\t")); concat += escape("\r\n"); } $("#hidSelected").val(concat.trim()); } $(document).ready(function () { /*************************************************/ /******************* THE GRID *******************/ /*************************************************/ assetGrid.jqGrid({ ajaxGridOptions: { contentType: "application/json; charset=utf-8", type: "POST" }, url: '../api/yourservices.asmx/GetData', datatype: 'json', serializeGridData: function (postData) { if (postData.searchField === undefined) postData.searchField = null; if (postData.searchString === undefined) postData.searchString = null; if (postData.searchOper === undefined) postData.searchOper = null; if (postData.filters === undefined) postData.filters = null; return JSON.stringify(postData); }, colNames: [' ', 'AssetId', 'Item#', 'Make', 'Description'], colModel: [ { name: 'ctrls', width: 80, fixed: true, sortable: false, resize: false, formatter: 'actions', formatoptions: { keys: true } }, { name: 'AssetID', label: 'AssetID', width: 65, key: true, hidden: true }, { name: 'Sequence', label: 'Item#', width: 50, align: "right", sorttype: 'int', sortable: true, editoptions: { dataEvents: [{ type: 'keydown', fn: function (e) { fnassetgrid.HandleInputNavigation(this, e); } }]} }, { name: 'Make', label: 'Make', width: 105, editable: true, edittype: 'text', editoptions: { size: 18, dataEvents: [{ type: 'focus', fn: function (e) { $(this).autocomplete({ source: autocomp.source, delay: autocomp.delay, minLength: autocomp.minLength }); $(this).bind("autocompleteopen", autocomp.open); $(this).bind("autocompleteclose", autocomp.close); } }] } }, { name: 'Description', label: 'Description', fixed: false, editable: true, edittype: 'textarea', unformat: unfrmttextarea, editoptions: { rows: "10", cols: "40"} } ], rowNum: 10, /* no of recs in a grid */ width: 1330, rowList: [10, 20, 30], /* array to construct a select box element in the pager */ pager: '#pfrmac', sortname: 'AssetID', /* initial sorting column */ viewrecords: true, /* display the number of total records on the pager bar */ pginput: true, sortorder: "desc", cellEdit: true, shrinkToFit: true, jsonReader: { root: function (obj) { return obj.d.SearchResultSet; }, page: function (obj) { return obj.d.PageNum; }, // current page of the query total: function (obj) { return obj.d.TotalPages; }, // total pages for the query records: function (obj) { return obj.d.TotalNoOfSearchResultItems; }, id: "AssetID", repeatitems: false, userdata: function (obj) { extendUserSession(); return { "Error": obj.d.Error, "SearchResultSet": obj.d.SearchResultSet } } }, loadonce: false, caption: "Asset list", height: '100%', cellsubmit: 'clientArray', beforeEditCell: function (rowid, cellname, value, iRow, iCol) { enableOnSelectEvent = true; }, beforeSaveCell: function (rowid, cellname, value, iRow, iCol) { savedrow = assetGrid.getGridParam('savedRow'); if (savedrow && savedrow.length > 0) { if (savedrow[0].id == iRow && savedrow[0].ic == iCol && savedrow[0].v != value) { tr = $('#' + rowid); if (tr && !tr.hasClass("altered")) { tr.addClass("altered"); there_are_unsaved_changes = 1; } } } }, afterSaveCell: function (rowid, cellname, value, iRow, iCol) { enableOnSelectEvent = false; }, afterRestoreCell: function (rowid, value, iRow, iCol) { enableOnSelectEvent = false; }, loadComplete: function (data) { if (assetGrid.getGridParam('userData').Error && assetGrid.getGridParam('userData').Error != '') alert("Error: " + assetGrid.getGridParam('userData').Error); }, gridComplete: function () { rowindex = 1; rows = assetGrid.find("tr"); if (rows && rows.length > 1) { for (i = 1; i < rows.length; i++) { $(rows[i]).find("td").each(function (evt) { evt = window.event || evt; start = $(this); colindex = fnassetgrid.GetColIndex(start); if (colindex > 0) { $(this).click(function () { if (!($(this).hasClass("edit-cell"))) return false; }).dblclick(function () { if (!($(this).hasClass("edit-cell"))) { fnassetgrid.PutCellInEdit(this, fnassetgrid.GetRowIndex($(this)), fnassetgrid.GetColIndex($(this)), true); return; } else return true; }).mousedown(function () { if (fnassetgrid.IsInEditMode()) return true; start = $(this); selectTo(this); return false; }).mouseover(function () { if (fnassetgrid.IsInEditMode()) return true; selectTo(this); }).mouseup(function () { if (fnassetgrid.IsInEditMode()) return true; selectTo(this); $("#hidEndCell").val(fnassetgrid.GetColIndex($(this))); start = null; }).keypress(function (e) { fnassetgrid.HandleEditMode(this, e); }); } }); rowindex++; } } } }); function unfrmttextarea(cellvalue, options, cellobject) { return cellvalue; } $("body").mouseup(function () { start = null; }); /*************************************************/ /*********** Global KEYUP integration ***********/ /*************************************************/ $(assetGrid).keyup(function (e) { var ctrl = e.ctrlKey var key = e.charCode || e.keyCode || 0; if ((ctrl && key == 88) /* CUT */ || (ctrl && key == 67) /* COPY */ || (ctrl && key == 86) /* PASTE */ || (ctrl && key == 90) /* UNDO */) { if ((ctrl && key == 88) /* CUT */ || (ctrl && key == 67) /* COPY */) { if (fnassetgrid.IsInEditMode()) return true; CopyToClipboard("hidSelected"); var selectedCells = fnassetgrid.GetSelCells(); if (selectedCells && selectedCells.length > 0) { $("#hidStartCell").val(fnassetgrid.GetRowIndex($(selectedCells[0])) + "," + fnassetgrid.GetColIndex($(selectedCells[0]))); $("#hidEndCell").val(fnassetgrid.GetRowIndex($(selectedCells[selectedCells.length - 1])) + "," + fnassetgrid.GetColIndex($(selectedCells[selectedCells.length - 1]))); $("#hidCopiedText").val($("#hidSelected").val()); } else { $("#hidStartCell").val(''); $("#hidEndCell").val(''); } if (ctrl && key == 88) /* CUT */{ assetGrid.find("td.sel").each(function () { row = $(this).closest('tr.jqgrow'); rowId = row.attr('id'); assetGrid.setCell(rowId, (fnassetgrid.GridColumns())[fnassetgrid.GetColIndex($(this))].name, '', '', '', true); }); fnassetgrid.ClearSelection(); } } else if (ctrl && key == 86) /* PASTE */{ var clipboardata = getClipboardData(); if (get_objtype(clipboardata) != "[object String]") { alert("The data you are pasting either is empty or incompatible"); return false; } pasteinfo(assetGrid, clipboardata); } else if ((ctrl && key == 90) /* UNDO */) { // TBD : No jqgrid features available to get the help } return false; // prevent bubbling } else return true; // let it bubble }); }); /********************************************************************/ /*********** Method to retrieve and submit altered asset information ***********/ /********************************************************************/ function getChanges() { var editedxml = "<?xml version='1.0' encoding='utf-8' ?\>\n"; editedxml += "<ASSETS>\n"; assetGrid.find("tr.altered").each(function () { editedxml += "<ASSET>\n"; $(this).children("td").each(function () { colindex = fnassetgrid.GetColIndex($(this)); if (colindex > 0) { editedxml += "<" + (fnassetgrid.GridColumns())[colindex].name.toUpperCase() + ">" + $(this).text().trim() + "</" + (fnassetgrid.GridColumns())[colindex].name.toUpperCase() + ">\n"; } }) editedxml += "</ASSET>\n"; }) editedxml += "</ASSETS>"; fnassetgrid.ClearMarkedChanges(); //TBD: submit XML to an AJAX service } var _browserPasteData = null; function getClipboardData() { if (_browserPasteData) // Safari/Chrome logic return _browserPasteData; if (window.clipboardData) // IE logic { return window.clipboardData.getData("Text"); } else if (typeof (netscape) != "undefined") // Firefox logic { netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect'); var clip = Components.classes["@mozilla.org/widget/clipboard;1"].createInstance(Components.interfaces.nsIClipboard); var trans = Components.classes["@mozilla.org/widget/transferable;1"].createInstance(Components.interfaces.nsITransferable); trans.addDataFlavor("text/unicode"); clip.getData(trans, clip.kGlobalClipboard); var str = new Object(); var len = new Object(); trans.getTransferData("text/unicode", str, len); if (str) return str.value.QueryInterface(Components.interfaces.nsISupportsString).toString(); } return null; } // In Safari/Chrome the clipboard data can only be accessed // from the onpaste event. In this sample the event is handled // off the body element: <body onpaste="browserPaste(event)"> function browserPaste(e) { _browserPasteData = e.clipboardData && e.clipboardData.getData ? e.clipboardData.getData('text/plain') : null; } function pasteinfo(objGrid, info) { selectedCells = fnassetgrid.GetSelCells(); firstcell = $(selectedCells[0]); firstselectedcolindex = fnassetgrid.GetColIndex(firstcell); rowcellscount = fnassetgrid.GetRowCells(firstcell).length; if (firstselectedcolindex == 0) { alert("You cannot paste into an non-editable column"); return false; } if (selectedCells && selectedCells.length > 0) { // if the clipboard info is from the asset grid if (info && info == $("#hidCopiedText").val()) { // get the index values of last copied source cell hidStartCell = -1; if ($("#hidStartCell").val() != '' && $("#hidStartCell").val().split(',').length > 1) { hidStartCell = $("#hidStartCell").val().split(',')[1]; } // if columns of source and dest do not match, throw warning if (firstselectedcolindex != hidStartCell) { if (!confirm("The data you are pasting comes from a different set of \ncolumns than those that you are pasting into.\n\nAre you sure you want to paste into these columns?")) return false; } } $("#hidModStartCell").val(fnassetgrid.GetRowIndex(firstcell) + "," + firstselectedcolindex); var prevcell = null; // remove the last "line break" and break clipboard info into lines datarows = unescape(info).replace(/\r\n$/, '').split("\r\n"); if (datarows && datarows.length > 0) { currentrow = firstcell.parent(); currentcell = firstcell; // if the source is a single cell, allow it to be pasted over multiple cells if (datarows.length == 1 && datarows[0].split("\t").length == 1) { copydata = datarows[0].split("\t"); $.each(selectedCells, function (index, value) { prevcell = $(value); if (!prevcell.parent().hasClass("altered")) { prevcell.parent().addClass("altered"); there_are_unsaved_changes = 1; } var rowId = prevcell.closest('tr.jqgrow').attr('id'); var icol = fnassetgrid.GetColIndex(prevcell); assetGrid.setCell(rowId, (fnassetgrid.GridColumns())[icol].name, copydata[0], '', '', true); }); } else { for (i = 0; i < datarows.length && currentrow.length > 0; ++i) { if (datarows[i] == '') break; // break each lines into columns datarows[i] = datarows[i].split("\t"); var row = null; var rowId = null; var rowindex = null; for (j = 0; j < datarows[i].length && currentcell.length > 0; ++j) { // mark the row as altered if (!currentcell.parent().hasClass("altered")) { currentcell.parent().addClass("altered"); there_are_unsaved_changes = 1; } // for each outer iteration get the rowid if (row == null) { row = (currentcell).closest('tr.jqgrow'); rowId = row.attr('id'); } var icol = fnassetgrid.GetColIndex(currentcell); assetGrid.setCell(rowId, (fnassetgrid.GridColumns())[icol].name, datarows[i][j], '', '', true); prevcell = currentcell; // advance to the next visible cell -- only consider pasting into visible columns do { currentcell = currentcell.next(); } while ((currentcell.length > 0) && currentcell.is(":hidden")) } currentrow = currentrow.next(); currentcell = $(currentrow.children("td")[firstselectedcolindex]); } } } } if (prevcell.length > 0) $("#hidModEndCell").val(fnassetgrid.GetRowIndex(prevcell) + "," + fnassetgrid.GetColIndex(prevcell)); } </script> 

提前谢谢了!

一种可能性是将最后一个值存储为可以使用以下方法完成的单元属性

 $('#' + rowid + ' > td:eq(' + colIndex + ')').attr('lastval', valueToSave); 

其中rowid是您正在处理的行,而colIndex是您希望保存该值的列的编号。这将创build一个名为lastval的属性,可用于您的撤消function。 这种方法的缺点是整个网格将刷新更新,你将失去网格中存储的属性。

假设这是可以接受的,那么你可以保存每个单元格的最后一个值

 loadComplete: function() { $("#list").find("td").each(function(index, elem) { $(elem).attr('lastval', $(elem).html()); }); }, 

其中'list'是你最初创build的jqGrid的id。

您可以将lastval作为beforeSubmit或其他callback的一部分进行更新,具体取决于您希望如何维护lastval。

我相信有更高效的技术来完成上述操作,但是在刷新过程中,值会丢失,我不确定这对于你所要做的事情是否有帮助。 一个更好的方法是将这些属性存储在DOM中的其他地方,或者存储在服务器上。 但是,如果我正确阅读上面的注释,你想保持在网格本身lastval。

而不是一个特定于jqgrid的实现,解决这个问题的一种方法是创build一个没有绑定到任何可见html元素的辅助jqgrid实例。

在任何操作分类为提交,辅助实例被设置为当前(未改变)的jqgrid,并且主要实例(即实际显示的实例)是次要副本,并进行所需的更改。

那么撤销操作所需要的就是将辅助实例复制回主要显示的实例,因此不需要任何关于特定提交操作的知识。

也很容易扩展到多个撤消操作。 可能是一个资源猪虽然。