Persisting jqGrid column preferences
Asked Answered
S

1

27

I've got a few jqGrids on my ASP.NET MVC 3 application that have a number of columns. I added the following to the column definitions to default some columns to be hidden:

colModel: [
   { name: 'IceCreamID', hidden: true},
   { name: 'RecipeID', hidden: true }

and this works nicely. Those columns aren't visible on my grid.

Then I added this to implement the column chooser:

var grid = $('#icecreamGrid');
grid.jqGrid('navButtonAdd', '#icecreamPager',
{ caption: "Columns", buttonicon: "ui-icon-calculator",
  title: "Choose Columns",
  onClickButton: function() {
     grid.jqGrid('columnChooser');
  }
});

Great, brings up the column chooser now. I then added the following to columns I never wanted to show up in the column chooser:

colModel: [
   { name: 'IceCreamID', hidden: true, hidedlg: true},

So I can now hide/show columns just fine. Now, how would you persist this information? DB? As a cookie? Other way? Is there a preferred way to store this sort of information that is really a user preference rather than something related to the data itself?


More Info

Based on Oleg's comment below, I want to provide a little more information.

The point here is that I've got grids with 10-15 columns which could be display based on the user's preference. For a simple example, one of my grid's has the following 9 columns:

IceCream|ShortName|HasNuts|SugarAdded|LimitedRun|PromoItem|Facility|FirstRun|LastRun

Users can hide/show any of these 9 columns based on their personal preferences.

What I want to do is provide a way to persist which columns a particular user wants to see so that s/he doesn't have to re-choose those columns to view each time the page with that grid is shown.

Spurtle answered 7/12, 2011 at 21:32 Comment(6)
The answer on your question can be hard depend on your requirements. The example with IceCreamID I find not the best one because I personally not understand why you need to place the information in the grid. You can place the information as the row id for example. You can use additionally composed rowids like 'IceCreamID'+'_'+'RecipeID'. In any way you should post more information about your grid and the environment.Miniver
Oleg, see my additional info in the question.Spurtle
Do you tried the code which I posted you 5 days ago? It would be nice to see any your comments of my answer.Miniver
@Miniver - yes, I saw this morning. Thanks. Been on other task for a few days. Sure I'll be commenting about it shortly. :)Spurtle
my new answer contains extended version on my answer on your question and so can be probably interesting for you.Miniver
@Spurtle wat itsmatt not commented yet (for Oleg)?Sheffy
M
41

I found you question very interesting. The question about saving the user state of grid are interesting in many cases. There are some interesting answers on such problems which uses cookie (see here for example).

In my opinion saving of the grid state in database on the server or in the localStorage is better way as the usage of cookie. The best way depends on the project's requirements in which you use it. For example the usage of the database storage on the server allows you to implement roaming state of the grid. If you use the localStorage instead of cookies the user preferences will be lost if the user goes to another computer or just if the user will use another web browser on the same computer.

Another problem with the grid state is the maintenance. The information about the columns of the grid you hold typically in the JavaScript or HTML files and not in the database. In the case the both sources can be not synchronous on the changes in the grid. Different scenarios of the update problem could you easy imagine. Nevertheless the advantages of user's preferences so large in some scenarios that the problems with small disadvantages are not so important and can be solved relatively easy.

So I'll spend some time to implement two demos which shows how it can be implemented. I used localStorage in my demos because of many reasons. I mention only two from there:

  1. Cookies is the way which send permanently different information to or from the server which is not really requited. It increases the size of HTTP header and decreases the performance of the web site (see here for example).
  2. Cookies have very hard restrictions. Corresponds to the section 6.3 of rfc2109 or 6.1 of rfc6265: At least 4096 bytes per cookie, at least 50 cookies per domain (20 in rfc2109), at least 3000 cookies total (300 in rfc2109). So the cookies one can't use to save too many information. For example if you would save state of every grid of every your web page you can quickly achieve the limits.

On the other side localStorage are supported by all modern browsers and will be supported in Internet Explorer starting with IE8 (see here). The localStorage will be automatically saved per origins (like a1.example.com, a2.example.com, a3.example.com, etc) and has arbitrary limit of 5 MB per origin (see here). So if you use the space carefully you will far from the any limits.

So I used in my demos the localStorage. I should additionally mention that there are some plugins like jStorage which use localStorage if it's supported by the browser and use another storage, but the same interface for you in case of old browsers like IE6/IE7. In the case you has only less size of storage: 128 kB instead of 5 MB, but it's better as 4K which one has for cookies (see here).

Now about the implementation. I creates two demos: this and it's extended version: this.

In the first demo the following states of grid will be saved and automatically restored on the page reload (F5 in the most web browsers):

  • which column are hidden
  • the order of columns
  • the width of every column
  • the name of the column by which the grid will be sorted and the sort direction
  • the current page number
  • the current filter of the grid and the flag whether the filter are applied. I used multipleSearch: true setting in the grid.

In the same way one can extend (or reduce) the list of options which are the part of the saved grid state.

The most important parts of the code from the demo you will find below:

var $grid = $("#list"),
    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 $.parseJSON(window.localStorage.getItem(storageItemName));
        }
    },
    myColumnStateName = 'ColumnChooserAndLocalStorage.colState',
    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,
                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;
    },
    firstLoad = true;

myColumnsState = restoreColumnState(cm);
isColState = typeof (myColumnsState) !== 'undefined' && myColumnsState !== null;

$grid.jqGrid({
    // ... other 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',
    loadComplete: function () {
        if (firstLoad) {
            firstLoad = false;
            if (isColState) {
                $(this).jqGrid("remapColumns", myColumnsState.permutation, true);
            }
        }
        saveColumnState.call($(this), this.p.remapColumns);
    }
});
$grid.jqGrid('navButtonAdd', '#pager', {
    caption: "",
    buttonicon: "ui-icon-calculator",
    title: "choose columns",
    onClickButton: function () {
        $(this).jqGrid('columnChooser', {
            done: function (perm) {
                if (perm) {
                    this.jqGrid("remapColumns", perm, true);
                    saveColumnState.call(this, perm);
                }
            }
        });
    }
});
$grid.jqGrid('navButtonAdd', '#pager', {
    caption: "",
    buttonicon: "ui-icon-closethick",
    title: "clear saved grid's settings",
    onClickButton: function () {
        removeObjectFromLocalStorage(myColumnStateName);
    }
});

Be carefully to define myColumnStateName (the value `'ColumnChooserAndLocalStorage.colState'``) in the demo) to different values on the different pages.

The second demo is the extension of the first one using the technique from my old answer to your another question. The demo use the searching toolbar and synchronize additionally information between the advanced searching form and the searching toolbar.

UPDATED: The next answer contains extended version of the code included above. It shows how to persist the selected rows (or row) additionally. Another answer shows how to persist the list of expanded nodes of the tree grid and expand the nodes on the relaoding of the page.

Miniver answered 8/12, 2011 at 19:8 Comment(23)
Excellent. Pressing "clear saved settings button" should reload grid also IMHO.Outwards
@Andrus: I personally like the solution too. I am sure that the idea can be improved and simplified for the usage. The reload of grid if the good point, but what one need to do is more as just call of $(this).trigger('reloadGrid',[{page:1}]) because simple reload will save the current setting, but I understand what you mean.Miniver
I added window.location.reload() as quick and dirty fix. This shows original settins.Outwards
@Andrus: I agree, it could be good workaround for the first time.Miniver
A good solution, Oleg. Thanks! I need to think more about reuse for this though since I've got several grids. Thanks for the link to jStorage. I'm going to look at this too. Now, one thing I haven't quite worked out yet is minimum grid width so that the pager bar doesn't get squashed if the user picks only a few columns.Spurtle
@itsmatt: You are welcome! About the pager: look at the demo from the "UPDATED" part of the answer. It uses normalizePagers and some additional CSS classes. The way will not full solve the problem with pager, but it can improve the visibility. You can combine it with setting "min-width" CSS property.Miniver
@Oleg: if columns are reordered using mouse, new column order is not persisted. How to persist new column order in this case also ?Outwards
@Andrus: see my other recent answer which shows how to monitor reordering of the columns. One should just call saveColumnState inside of sortable defined as function.Miniver
@Miniver JSON.parse(window.localStorage.getItem(storageItemName)); stops javascript execution in Android. (You can test this using live device remotely in Samsung Test lab). After replacing JSON.parse with $.parseJSON it works in Android also.Outwards
@Andrus: Thanks! I agree that it's better to use $.parseJSON. I made the same changes in other demos, but forget to change it here. I'll update the text of the answer.Miniver
@Oleg: Can we replace JSON.stringify() also so that json2.js is not required?Outwards
@Andrus: I don't understand the problem. Why is the problem to include json2.js? You have to save object as string. So you have to use some kind of serialization. If you don't like json2.js you can use json3.js for example.Miniver
I thought that maybe jquery or javascript already has similar function so using it eliminates need to load this additional javascript file.Outwards
@Andrus: All modern browsers has already native implementation of JSON.stringify, but to support old web browsers you have to include json2.js. The code of JSON.stringify test firts whether JSON already defined. If it's defined the json2.js do nothing and the native web browser implementation will be used. So json2.js defines JSON.stringify only for old web browsers.Miniver
@Miniver - Thanks for the great demos. In my case, I have loadonce=1, scroll=1, datatype='json', height='500px', I set the 'url' (not using 'data') - and I finally figured out how to make it work perfectly. I'm deleting prior comment asking for help. Thanks for all you do!!Naos
@bkwdesign: You are welcome! I'm glad that my answers could help you.Miniver
bug: tanks Oleg. in done event perm is relation for current event. your demo reordering column is not true . I'm offer before save perm get in method 'this.jqGrid("getGridParam", "remapColumns")'.thanks.Concertante
@MJVakili: I think you are right. I have to do another things now and later will make additional tests.Miniver
@MJVakili: Try the demo which is modification of the first demo from my old answer with another order of saveColumnState.call(this, perm); and this.jqGrid("remapColumns", perm, true);. At the first changing of the order of columns it seems to work, but if one reloads the page, makes one more reordering of columns and reloads the page one more time then one will see wrong order.Miniver
@MJVakili: I suppose that one has to save column name instead of column indexes. The usage of remapColumns with indexes is not good in general by design too. I planed to introduce remapColumnsByName in free jqGrid and to use it internally. The old remapColumns will be hold only for compatibility reasons and it will calls new remapColumnsByName internally.Miniver
This would be great addition. If new columns are changed to jqgrid and some old columns are removed by changing code in server side, depending on user rights for example, current save method shows wrong columns. It does not even dedect colmodel change and will not revert to default layout.Outwards
I am integrating the same thing in my jqgrid. I cannot understand what is cm in myColumnsState = restoreColumnState(cm); Can anyone answer me?Elwaine
@MarikkaniChelladurai: The code in the answer is a fragment from the code of the demo (see some lines before the code in the answer). You will find the definition of cm variable. In general, the answer is very old. Look at more recent answer better for more recent code.Miniver

© 2022 - 2024 — McMap. All rights reserved.