7

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 ?

Community
  • 1
  • 1
Andrus
  • 26,339
  • 60
  • 204
  • 378

2 Answers2

8

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.

Community
  • 1
  • 1
Oleg
  • 220,925
  • 34
  • 403
  • 798
  • Critical patch from http://www.trirand.com/blog/?page_id=393/bugs/exception-after-successful-delete-in-complete-event-handler is not applied. Everysuccessful remote delete causes exception. – Andrus Dec 18 '11 at 08:54
  • @Andrus: In the demos which I posted there are no Delete, Add or Edit operation. How you can try no exception will be thrown. – Oleg Dec 18 '11 at 09:03
  • tis was general comment, sorry. How to re-use this code for multiple grids in same and different pages ? Can jqgrid extended or subclassed with this code or other idea? – Andrus Dec 18 '11 at 10:21
  • if only pager navigation buttons pressed, current page number is not saved. Page size is not saved. – Andrus Dec 18 '11 at 11:32
  • 1
    @Andrus: To have less misunderstanding how to use `myColumnStateName` in case of many grids on the page I modified the demos a little. Additionally I extended the column state to save the `rowNum` value. – Oleg Dec 18 '11 at 12:02
  • Thank you very much. In my case grid is constructed from one querystring parameter so I cannot use this column state name creation. I added var myColumnStateName to site master and assign it in index.aspx file. Works if page contains single grid only. – Andrus Dec 18 '11 at 12:40
  • @Andrus: You should just modify the algorithm of assignment of `myColumnStateName` so that the most important part of `querystring` will be included in the `myColumnStateName`. Probably you should set the `myColumnStateName` in the ASPX file instead of the site master page. – Oleg Dec 18 '11 at 12:54
  • thank you. Page contains two grid whose settigns needs persisted. Should I duplicate all this code for both grids or is it possible to re-use code without duplication? – Andrus Dec 18 '11 at 16:59
  • @Andrus: Sorry, but I don't understand your current problem. The last version of [the code](http://www.ok-soft-gmbh.com/jqGrid/ColumnChooserAndLocalStorage2_.htm) of `saveColumnState` and `restoreColumnState` can be used with different grids. Instead of `saveColumnState.call($grid, $grid[0].p.remapColumns);` you can also use `saveColumnState.call($(this), this.p.remapColumns);` in the most cases which allow to share the code more easy between the grids. So you should not duplicate the code, just use it for two grids. – Oleg Dec 18 '11 at 18:19
  • If `multiboxonly: true` is used and selection is cleared by clickin in some other column, pressing F5 shows previously selected rows also. How to persist selected columns if `multiboxonly: true` setting is used ? – Andrus Dec 24 '11 at 19:06
  • If sort column name is not specified or wrong, jGrid throws error on such sort colun name. I created pull request to fix this https://github.com/kobruleht/jqGrid/commit/22efac0799b6498c5b04233a23b34a66864e631c For unknow reason it looks like this was merged with previous requestf, actually those are two different ones – Andrus Dec 24 '11 at 20:54
  • if grid columns removed in application code, restorecolumnsState creates invalid colModel where come array elements are missing. How to fix this ? I updated question. – Andrus Feb 04 '12 at 12:01
  • @Andrus: Sorry, but I don't understand what you mean. How you remove the column? jqGrid supports only hiding and no removing of the columns. Which `restorecolumnsState` function you mean? I don't find it in my code. – Oleg Feb 04 '12 at 12:09
  • It looks like is was related to permutation contianing null values. I updated question, added screenshots and proposed code which seems to fix the issue. – Andrus Feb 04 '12 at 13:04
  • @Andrus: OK! What is much more interesting is: why you had the `permutation` which contains `null`. I suppose the origin of the problem was earlier when the buggy `permutation` was saved. – Oleg Feb 04 '12 at 13:33
  • This occurs if columns are deleted from column definition table used to render jqgrid. Fix in question is not sufficient. If pemutation array length is greater than colModel length, remapColumns increases colModel lenght! After that colModel contains undefined elements. How to check is the permutation valid for colModel and call remapColumns only if permutation is valid or other idea to fix ? – Andrus Feb 04 '12 at 13:50
  • 1
    Replacing null check with `var cm = $grid.jqGrid('getGridParam', 'colModel')'` and check ` && myColumnsState.permutation.length == cm.length` seems to fix this – Andrus Feb 04 '12 at 14:02
  • @Andrus: It seems be a good suggestion! So `if (isColState) { $this.jqGrid("remapColumns", myColumnsState.permutation, true); }` inside of `loadComplete` should be changed to `if (isColState && myColumnsState.permutation.length === cm.length) { $this.jqGrid("remapColumns", myColumnsState.permutation, true); }` where `cm` should be defined as `cm = $grid.jqGrid('getGridParam', 'colModel')` at the beginning of `loadComplete`. Is it the solution? – Oleg Feb 04 '12 at 14:38
  • Yes. Earlier you suggested to use `myColumnsState.permutation.length > 0` If zero length colmodel is created this check is still required. This can removed if we can assume that colmodel contais always at least one element. – Andrus Feb 04 '12 at 23:23
  • I had to add a check for cmi.stype=="select" to see if a select elements need populating then i had to use this code if(cmi.stype=="select") { control = $("select#gs_" + $.jgrid.jqID(cmi.name)); } placed just after the control is obtained – Farrukh Subhani Jul 21 '14 at 19:05
  • In Apple mobile phone with Safari attemp to it causes `QuotaExceededError: DOM Exception 22: An attempt was made to add something to storage that exceeded the quota.` How to fix this for mobile safari? In mobile Chrome and in iPad it works. Maybe to turn persisting silently off in mobile Safari. – Andrus Nov 29 '14 at 19:15
  • @Andrus: Sorry, but if you can reproduces the problem you should debug it yourself. For me all looks just so that the local storage of the IPhone is full, or you try to save very large data in the storage. – Oleg Nov 29 '14 at 20:13
  • Google search about this error shows that mobile safari reports localstorage presence but actually does not allow to use it. Code in answer should not try to save settings in this case. No idea how to implement this for this code. Currently empty page is displayed in mobile safari if this code is used. jqgrid does not appear at all. – Andrus Nov 29 '14 at 21:11
  • @Andrus: You can just detect Safari of iPhone or just include try-catch for the exception and don't use localStorage. – Oleg Nov 29 '14 at 21:25
4

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 });
  }
...
},
flynhigh
  • 161
  • 1
  • 3