JQGRID: любой простой способ реализовать отмену excel как реализация jqGrid

Отредактировано 5/14/12: я, наконец, смог выпрыгнуть из своей лени и подготовить это, чтобы поделиться фактическим EXCEL LIKE JQGRID.. Выбор ячейки может работать в jsfiddle на нескольких браузерах, но он должен работать нормально в вашем окне разработки. Удачи!!!!

Отредактировано 13.09.11: Это мое первое использование JQGrid. Я использую ver 4.1.2. Я потратил несколько недель на то, чтобы собрать превосходную сетку, и самой сложной задачей было найти правильную информацию о том, как использовать JQGrid. Моя текущая установка имеет множество аякс-обновлений и картинных галерей и использование форматирования jqgrid, но то, что я здесь поставил, является основным кодом, позволяющим использовать JQgrid с подкачкой на стороне сервера, превзойти как копирование и демонстрацию нескольких других функций на jqgrid. Это всего лишь мой способ вернуть всю помощь, которую я получил от этого сообщества.

Для людей, которые просто прыгнули в JQGrid, вы скоро узнаете, что есть некоторая проблема с использованием Textarea в jqgrid. вы можете найти некоторые решения здесь.

Оригинальное сообщение:
Просто дайте небольшое обновление, прежде чем ставить мой вопрос....

Я смог придумать некоторые дополнительные функции на jqgrid, которые я использую (после прохождения многих форумов), включая: copy-paste взад и вперед от Excel до jqgrid, редактирование ячейки при нажатии и dblclick, копировать и вставлять несколько ячеек из одного блока в другой в той же сетке, используя выбор мыши (отсюда Использование Javascript для "суммирования выделенных ячеек" в IE6)

Большинство функций копирования вставляются в IE только на данный момент. Я сохраняю все изменения вместе на кнопку "Сохранить", поэтому все обновления на ячейках отображаются на экране только до тех пор, пока пользователь не нажмет кнопку "Сохранить".

Несмотря на то, что сейчас все еще в движении, мне хотелось бы, чтобы дизайн внедрения на бумаге теперь был, а не позже. Я ищу простой способ для UNDO только для ПОСЛЕДНИХ изменений. Я думал об использовании методов jQuery "data()" и "removeData()" для реализации этого, но если в инфраструктуре jqgrid есть что-то уже существующее, я бы хотел знать. Любые предложения?

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

Большое спасибо заранее!

Ответ 1

Одной из возможностей является сохранение последнего значения в качестве атрибута ячейки, которое может быть выполнено с использованием следующих

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

где rowid - это строка, над которой вы работаете, и colIndex - это номер столбца, в котором вы хотите сохранить значение. Это создаст атрибут lastval, который можно использовать с вашей функцией отмены. Недостатком этого подхода является то, что вся сетка будет обновляться при обновлении, и вы потеряете атрибуты, хранящиеся в сетке.

Предполагая, что это приемлемо, вы можете сохранить последнее значение каждой ячейки, используя

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

где "list" - это идентификатор созданного вами jqGrid.

Вы можете обновить lastval как часть запроса beforeSubmit или другого обратного вызова в зависимости от того, как вы хотите сохранить lastval.

Я уверен, что есть более эффективные методы для выполнения вышеизложенного, но когда значения теряются во время обновления, я не уверен, что это действительно поможет с тем, что вы пытаетесь сделать. Лучшим подходом было бы сохранить эти атрибуты в другом месте DOM или обратно на сервере. Однако, если я правильно читаю приведенные выше комментарии, вы хотите сохранить lastval в самой сетке.

Ответ 2

Вместо реализации, специфичной для jqgrid, одним из способов приблизиться к этому было бы иметь вторичный экземпляр jqgrid, который не связан с каким-либо видимым элементом html.

В любой операции, классифицированной как фиксация, вторичный экземпляр устанавливается как текущий (неизменный) jqgrid, а основной экземпляр (т.е. тот, который фактически отображается) является дубликатом вторичного с внесенным изменением.

Тогда все, что потребуется для операции отмены, состоит в том, чтобы дублировать вторичный экземпляр обратно в первичный отображаемый, следовательно, не нужно знать какое-либо конкретное действие фиксации.

Также будет легко распространяться на несколько операций отмены. Возможно, это был ресурс.