how to persist current row in jqgrid

How to presist current row if grid is opened again or page is refreshed ?

Answer in Persisting jqGrid column preferences describes how to persist column width and some other parameters.

In this answer demo I clicked in some row and pressed F5 . Previous clicked row was not highlighted. How to save / restore current row in local storage ?

Update

If jqGrid column structure is modified in application and user opens application from browser again, restorecolumnstate creates invalid colmodel where some elements are missing. This causes exception in refreshSearchingToolbar which assumes that all colmodel elements are present.

How to fix this ? How to dedect modified colmodol and not to restore colmodel in this case ? Or should restoreColumnState update colModel so that proper array is created ?

**Update 2 **

If myColumnsState.permutation contains nulls $grid.jqGrid("remapColumns", myColumnsState.permutation, true) created invalid colmodel. Here are screenshots from VS debugger immediately before and after remapColumns call

enter image description here

after:

after

I fixed this by chaning code to

    if (isColState && myColumnsState.permutation.length > 0) {
        var i, isnull = false;
        for (i = 0; i < myColumnsState.permutation.length; i = i + 1) {
            if (myColumnsState.permutation[i] == null) {
                isnull = true;
                break;
            }
        }
        if (!isnull) {
            $grid.jqGrid("remapColumns", myColumnsState.permutation, true);
        }

Is this best solution ?


I combined the code from the previous answer about persisting jqGrid column preferences with the code of from another answer where I suggested the code which implemented persistent selection of rows. It's important to mention, that in case of multiselect:true it will be used the array of ids of selected rows which contains all selected even if the rows are on another page. It's very practical and the implementation very simple. So I posted the corresponding feature request, but it's stay till now unanswered.

Now I can present two demos: the first demo which use multiselect: true and the second demo which uses the same code, but with the single selection.

The most important parts of the code which I used you will find below.

One thing is very important to mention: you should modify the value of myColumnStateName in every page which you use. The value of the variable contain the name of the column state in the localStorage. So it you would not change the name you will share state of different tables which can follows to very strange effects. You can consider to use names constructed from the name of the current page or it's URL as the value of myColumnStateName.

var $grid = $("#list"),
    getColumnIndex = function (grid, columnIndex) {
        var cm = grid.jqGrid('getGridParam', 'colModel'), i, l = cm.length;
        for (i = 0; i < l; i++) {
            if ((cm[i].index || cm[i].name) === columnIndex) {
                return i; // return the colModel index
            }
        }
        return -1;
    },
    refreshSerchingToolbar = function ($grid, myDefaultSearch) {
        var postData = $grid.jqGrid('getGridParam', 'postData'), filters, i, l,
            rules, rule, iCol, cm = $grid.jqGrid('getGridParam', 'colModel'),
            cmi, control, tagName;

        for (i = 0, l = cm.length; i < l; i++) {
            control = $("#gs_" + $.jgrid.jqID(cm[i].name));
            if (control.length > 0) {
                tagName = control[0].tagName.toUpperCase();
                if (tagName === "SELECT") { // && cmi.stype === "select"
                    control.find("option[value='']")
                        .attr('selected', 'selected');
                } else if (tagName === "INPUT") {
                    control.val('');
                }
            }
        }

        if (typeof (postData.filters) === "string" &&
                typeof ($grid[0].ftoolbar) === "boolean" && $grid[0].ftoolbar) {

            filters = $.parseJSON(postData.filters);
            if (filters && filters.groupOp === "AND" && typeof (filters.groups) === "undefined") {
                // only in case of advance searching without grouping we import filters in the
                // searching toolbar
                rules = filters.rules;
                for (i = 0, l = rules.length; i < l; i++) {
                    rule = rules[i];
                    iCol = getColumnIndex($grid, rule.field);
                    if (iCol >= 0) {
                        cmi = cm[iCol];
                        control = $("#gs_" + $.jgrid.jqID(cmi.name));
                        if (control.length > 0 &&
                                (((typeof (cmi.searchoptions) === "undefined" ||
                                typeof (cmi.searchoptions.sopt) === "undefined")
                                && rule.op === myDefaultSearch) ||
                                  (typeof (cmi.searchoptions) === "object" &&
                                      $.isArray(cmi.searchoptions.sopt) &&
                                      cmi.searchoptions.sopt.length > 0 &&
                                      cmi.searchoptions.sopt[0] === rule.op))) {
                            tagName = control[0].tagName.toUpperCase();
                            if (tagName === "SELECT") { // && cmi.stype === "select"
                                control.find("option[value='" + $.jgrid.jqID(rule.data) + "']")
                                    .attr('selected', 'selected');
                            } else if (tagName === "INPUT") {
                                control.val(rule.data);
                            }
                        }
                    }
                }
            }
        }
    },
    saveObjectInLocalStorage = function (storageItemName, object) {
        if (typeof window.localStorage !== 'undefined') {
            window.localStorage.setItem(storageItemName, JSON.stringify(object));
        }
    },
    removeObjectFromLocalStorage = function (storageItemName) {
        if (typeof window.localStorage !== 'undefined') {
            window.localStorage.removeItem(storageItemName);
        }
    },
    getObjectFromLocalStorage = function (storageItemName) {
        if (typeof window.localStorage !== 'undefined') {
            return JSON.parse(window.localStorage.getItem(storageItemName));
        }
    },
    myColumnStateName = 'ColumnChooserAndLocalStorage2.colState',
    idsOfSelectedRows = [],
    saveColumnState = function (perm) {
        var colModel = this.jqGrid('getGridParam', 'colModel'), i, l = colModel.length, colItem, cmName,
            postData = this.jqGrid('getGridParam', 'postData'),
            columnsState = {
                search: this.jqGrid('getGridParam', 'search'),
                page: this.jqGrid('getGridParam', 'page'),
                sortname: this.jqGrid('getGridParam', 'sortname'),
                sortorder: this.jqGrid('getGridParam', 'sortorder'),
                permutation: perm,
                selectedRows: idsOfSelectedRows,
                colStates: {}
            },
            colStates = columnsState.colStates;

        if (typeof (postData.filters) !== 'undefined') {
            columnsState.filters = postData.filters;
        }

        for (i = 0; i < l; i++) {
            colItem = colModel[i];
            cmName = colItem.name;
            if (cmName !== 'rn' && cmName !== 'cb' && cmName !== 'subgrid') {
                colStates[cmName] = {
                    width: colItem.width,
                    hidden: colItem.hidden
                };
            }
        }
        saveObjectInLocalStorage(myColumnStateName, columnsState);
    },
    myColumnsState,
    isColState,
    restoreColumnState = function (colModel) {
        var colItem, i, l = colModel.length, colStates, cmName,
            columnsState = getObjectFromLocalStorage(myColumnStateName);

        if (columnsState) {
            colStates = columnsState.colStates;
            for (i = 0; i < l; i++) {
                colItem = colModel[i];
                cmName = colItem.name;
                if (cmName !== 'rn' && cmName !== 'cb' && cmName !== 'subgrid') {
                    colModel[i] = $.extend(true, {}, colModel[i], colStates[cmName]);
                }
            }
        }
        return columnsState;
    },
    updateIdsOfSelectedRows = function (id, isSelected) {
        var index = idsOfSelectedRows.indexOf(id);
        if (!isSelected && index >= 0) {
            idsOfSelectedRows.splice(index, 1); // remove id from the list
        } else if (index < 0) {
            idsOfSelectedRows.push(id);
        }
    },
    firstLoad = true;

myColumnsState = restoreColumnState(cm);
isColState = typeof (myColumnsState) !== 'undefined' && myColumnsState !== null;
idsOfSelectedRows = isColState && typeof (myColumnsState.selectedRows) !== "undefined" ? myColumnsState.selectedRows : [];

$grid.jqGrid({
    // ... some options
    page: isColState ? myColumnsState.page : 1,
    search: isColState ? myColumnsState.search : false,
    postData: isColState ? { filters: myColumnsState.filters } : {},
    sortname: isColState ? myColumnsState.sortname : 'invdate',
    sortorder: isColState ? myColumnsState.sortorder : 'desc',
    onSelectRow: function (id, isSelected) {
        updateIdsOfSelectedRows(id, isSelected);
        saveColumnState.call($grid, $grid[0].p.remapColumns);
    },
    onSelectAll: function (aRowids, isSelected) {
        var i, count, id;
        for (i = 0, count = aRowids.length; i < count; i++) {
            id = aRowids[i];
            updateIdsOfSelectedRows(id, isSelected);
        }
        saveColumnState.call($grid, $grid[0].p.remapColumns);
    },
    loadComplete: function () {
        var $this = $(this), i, count;

        if (firstLoad) {
            firstLoad = false;
            if (isColState) {
                $this.jqGrid("remapColumns", myColumnsState.permutation, true);
            }
            if (typeof (this.ftoolbar) !== "boolean" || !this.ftoolbar) {
                // create toolbar if needed
                $this.jqGrid('filterToolbar',
                    {stringResult: true, searchOnEnter: true, defaultSearch: myDefaultSearch});
            }
        }
        refreshSerchingToolbar($this, myDefaultSearch);
        for (i = 0, count = idsOfSelectedRows.length; i < count; i++) {
            $this.jqGrid('setSelection', idsOfSelectedRows[i], false);
        }
        saveColumnState.call($this, this.p.remapColumns);
    },
    resizeStop: function () {
        saveColumnState.call($grid, $grid[0].p.remapColumns);
    }
});

$grid.jqGrid('navGrid', '#pager', {edit: false, add: false, del: false});
$grid.jqGrid('navButtonAdd', '#pager', {
    caption: "",
    buttonicon: "ui-icon-closethick",
    title: "clear saved grid's settings",
    onClickButton: function () {
        removeObjectFromLocalStorage(myColumnStateName);
        window.location.reload();
    }
});

UPDATED: I forgot to mention that in case of usage multiselect: true option with jqGrid 4.3 it is very important to use the fix which described here. In the first demo I used the modified version of the jquery.jqGrid.src.js which include the bug fix.

UPDATED 2: To make easy to generate unique name of the local storage item used to save the grid state I modified the demos a little. The next version of the multiselect demo and the single select demo use myColumnStateName as the function defined as the following

var myColumnStateName = function (grid) {
        return window.location.pathname + '#' + grid[0].id;
    }

The usage of myColumnStateName are changed correspondingly. Additionally I extended the column state to save the rowNum value.

UPDATED 3: The answer describe how one can use new possibility of free jqGrid to save the grid state.


Oleg's solution generates an error when you refresh the page like below.

Error: Uncaught TypeError: Cannot read property 'el' of undefined

Line: 1936 in jquery.jqGrid.src.js

var previousSelectedTh = ts.grid.headers[ts.p.lastsort].el, newSelectedTh = ts.grid.headers[idxcol].el;

Solution to this is to save the lastsort grid parameter and reset it when load complete like below.

saveColumnState = function(perm) {
...
  columnsState = {
    search: this.jqGrid('getGridParam', 'search'),
    page: this.jqGrid('getGridParam', 'page'),
    sortname: this.jqGrid('getGridParam', 'sortname'),
    sortorder: this.jqGrid('getGridParam', 'sortorder'),
    lastsort: this.jqGrid('getGridParam', 'lastsort'),
    permutation: perm,
    colStates: { }
  },
...
},

loadComplete: function(data) {
...
  if (isColState) {
    $this.jqGrid("remapColumns", myColumnsState.permutation, true);
    if(myColumnsState.lastsort > -1)
      $this.jqGrid("setGridParam", { lastsort: myColumnsState.lastsort });
  }
...
},