How to replace remapColums with remapColumnsByName in free jqgrid
First of all I want to mention that the code described in the old answer works not always correct. To explain the problem you can open the single row select demo for example and uses column chooser multiple times before reloading the grid. For example you can open column chooser first and change position of "Clients" column after "Tax" column. You will see correct results in the grid. Then you can open column chooser once more and move "Date" column after "Clients" column for example. You will see the columns in the order "Amount", "Tax", "Client", "Date", ... Now you can reload the page. You will see that the reloaded page have wrong order of columns: "Client", "Amount", "Tax", "Date", ... The reason on the problem: permutation
used by column chooser or by remapColumns
uses integer position of columns relatively to current order of columns. It makes saving of column order more complex. One have to hold original column order and recalculates always the values from permutation
array to reordering of original colModel
.
Alternatively one can saves column names instead of arrays with changed column position relatively to original column model. In other words one should replace permutation
property of columnsState
to something like cmOrder
with array of column names in the grid, which choosed the user last time.
The method remapColumnsByName
is very simple. It works like the method remapColumns
, but its first parameter is array of column names instead of array of integer indexes.
The demo is quick and dirty changing of the single row select demo to use cmOrder
property instead of permutation
property in columnsState
and to use the method remapColumnsByName
additionally. If you would repeat the same test like I described at the beginning of my answer you will see that new demo don't have the bug which I described before.
The most important parts of the demo which is different from original demo you will find below:
var getColumnNamesFromColModel = function () {
var colModel = this.jqGrid("getGridParam", "colModel");
return $.map(colModel, function (cm, iCol) {
// we remove "rn", "cb", "subgrid" columns to hold the column information
// independent from other jqGrid parameters
return $.inArray(cm.name, ["rn", "cb", "subgrid"]) >= 0 ? null : cm.name;
});
},
saveColumnState = function () {
var p = this.jqGrid("getGridParam"), colModel = p.colModel, i, l = colModel.length, colItem, cmName,
postData = p.postData,
columnsState = {
search: p.search,
page: p.page,
rowNum: p.rowNum,
sortname: p.sortname,
sortorder: p.sortorder,
cmOrder: getColumnNamesFromColModel.call(this),
selectedRows: idsOfSelectedRows,
colStates: {}
},
colStates = columnsState.colStates;
if (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(this), columnsState);
},
...
moreover the loadComplete
callback which restore the order of the columns is the following
loadComplete: function () {
var $this = $(this), p = $this.jqGrid("getGridParam"), i, count;
if (firstLoad) {
firstLoad = false;
if (isColState && myColumnsState.cmOrder != null && myColumnsState.cmOrder.length > 0) {
// We compares the values from myColumnsState.cmOrder array
// with the current names of colModel and remove wrong names. It could be
// required if the column model are changed and the values from the saved stated
// not corresponds to the
var fixedOrder = $.map(myColumnsState.cmOrder, function (name) {
return p.iColByName[name] === undefined ? null : name;
});
$this.jqGrid("remapColumnsByName", fixedOrder, 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);
},
I want to repeat that the code from the new demo is far from be perfect. I just used the old code and fixed it to make it working in free jqGrid and by using new remapColumnsByName
method.
JQGrid: Resetting the Columns to original Order and state
Restore hidden
state of all columns is very simple and you found already the corresponding code examples. Thus I answer how to reset the column order.
The problem is that the method remapColumns
which can be used to reorder the columns use column numbers instead of column names. The same problem exist in many other methods or internal parameters of jqGrid. To use the method remapColumns
you need to use the index of columns based on the current index order. After the user changed the order of columns multiple times it would be difficult to provide the column numbers which you want to have relatively to the current column order. It would be much more easy to hold the names of columns instead of column indexes.
I develop free jqGrid as a fork of jqGrid after Tony had changed the licence agreement of jqGrid and renamed it to Guriddo jqGrid JS. I implemented many changes in jqGrid which disturbs my and have implemented many new features. One from the changes was moving all internal options to holding names instead of indexes as far it was possible without breaking the compatibility to the previous versions. I added the method remapColumnsByName
, which simplifies the usage of column remapping (see the answer with the demo).
The implementation of your requirements in free jqGrid could be very simple. You need just save the original column order in an array and then use it as the parameter of remapColumnsByName
to reset the columns
$("#grid1").jqGrid({
...
onInitGrid: function () {
var p = $(this).jqGrid("getGridParam");
// save names of columns in custom option of jqGrid
p.originalColumnOrder = $.map(p.colModel, function (cm) {
return cm.name;
});
//alert(JSON.stringify(p.originalColumnOrder));
}
}).jqGrid("navGrid", { add: false, edit: false, del: false })
.jqGrid("navButtonAdd", {
buttonicon: "fa-repeat",
caption: "",
title: "Reset original column order",
onClickButton: function () {
var $self = $(this), p = $self.jqGrid("getGridParam");
$self.jqGrid("remapColumnsByName", p.originalColumnOrder, true);
}});
See the demo https://jsfiddle.net/OlegKi/r6b2os5b/2/
If you can't migrate to free jqGrid because of some reasons then you have to simulate the same behavior. You should save original column order as array of column names and then to convert the names to indexes directly before calling of remapColumns
. It should work too.
jqGrid to reorder column by index name dynamically
Please write allays which version of jqGrid you use (or can use) and from, which fork of jqGrid. Moreover it's important to write which fork of jqGrid you use (free jqGrid, commercial Gurrido jqGrid JS or an old jqGrid in version <=4.7).
I agree with the problem of the usage column indexes instead of column names. It's the reason why I made many changes in the free jqGrid fork, which I develop starting with making the main old fork commercial (see the post) in the version 4.7.1. I introduced the method remapColumnsByName
together with the old method remapColumns
. The answer provides the demo, which demonstrates the usage of remapColumnsByName
. Moreover free jqGrid holds mostly column names instead of column indexes internally and it has the helper option iColByName
, which helps to get the current index of the column by the column name.
I recommend you to upgrade jqGrid, which you use to the current (4.13.4) version of free jqGrid and to use remapColumnsByName
.
Where can I use jqgrid remapColumns function?
In the answer you can find an example how to use remapColumns
and I hope another helpful information for you. In the demo I use remapColumns
once inside of loadComplete
. I don't understand why you want to change the column order on every request to the server.
UPDATED: The modified demo don't have the effect which you described. The reason was that one called in the old demo the method remapColumns
with empty ([]
) permutation parameter and remapColumns
works incorrect in the case. So I included additional test for myColumnsState.permutation.length > 0
.
UPDATED 2: Free jqGrid provides the method remapColumnsByName
additionally to remapColumns
. The first parameter of remapColumnsByName
is array of column names, which should be on the grid. Columns "subgrid"
, "cb"
and "rn"
could be included or not. The understanding of the usage of remapColumnsByName
is much more easy as the usage of remapColumns
especially in situations where the order of columns could be changes by the user (by columnChooser
, wor example). It's strictly recommended to use remapColumnsByName
instead of remapColumns
.
how to persist current row in jqgrid
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.
Avoiding data corruption if column state is saved, new column is defined in server and data is saved in edit
I know the problem very good! One need to implement some kind of validating checks of the previously saved state of the grid before the usage. The deepness of checks could depend on the exact requirements of your application and from the information which one knows exactly. The most opened and unclear thing: should one make some correction/fixing of the previously saved state or should one discard the state on the first small error? The answer on the question depends on the project where jqGrid are used. Deep fixing could include fixing of sorting parameter and modifying previously saved filter. Another example: the state could include ids of selected rows, but the fixing of the part of the state could be bad idea in the common case. One loading of the data could imply one setting of selected rows, but loading of another data (unfiltered for example) could do have the rows and the rows should be do selected. There are no best choice in the case, all depends on the exact project requirements. In any way the implementation of the state validation/fixing isn't a simple code.
Only because of the complexity of the problems of validation of previously saved state and the existence of different scenarios of validation I didn't implemented such feature in free jqGrid. Any good implementation needs time and the resulting code will be not simple. It will have some options for some typical scenarios. I would like to implement the feature in the future, but I just didn't found the time for the implementation, because I have to do my main job to earn money for my family and I still try to help other people in the community who have small, but important, for the person, problems with jqGrid of free jqGrid.
free-jqgrid: easier way to save, load and apply filter data including filter-toolbar text and page-settings?
The answer with the demo provides an example of the implementation. The method refreshSerchingToolbar
is relatively long. On the other side the code is still not full. It's not restore the state of operands, if the option searchOperators: true
is used. I wanted to rewrite many parts of the filterToolbar
method to make easier the saving/restoring of the the state of the filter toolbar or refreshing based on the changed postData
. It's just the problem of the time and nothing more. What you describe is close to the feature forceClientSorting: true
, which was difficult to implement in original code of jqGrid 4.7, but it was easy to implement after I rewrote large part of processing of the server response. The same problem is with the code of filterToolbar
. The changes of filterToolbar
is still in my TODO list and I'll made the corresponding changes soon.
Persisting Row order and row width, free-jqGrid
it' difficult to answer on questions with long code without having the possibility to debug it. Nevertheless I suppose that your problem is in the usage of restoreColumnState
which you call in synchronous way: myColumnsState = restoreColumnState.call($grid, cm);
in the code, but it calls getObjectFromLocalStorage
which works asynchronously. The data return $.parseJSON(data.colState);
returned from success
callback of jquery.ajax
will be just ignored. To fit the code you can rewrite the code to create the whole grid with the used state inside of success
callback.
free-jqGrid External Filtering Used With Grid's beforeRequest() or onPaging() Event
It's difficult to answer on your question because you didn't posted code fragments, which shows how you use jqGrid and because the total number of data, which could be needed to display in all pages isn't known.
In general there are two main alternatives implementing of custom filtering:
- server side filtering
- client side filtering
One can additionally use a mix from both filtering. For example, one can load from the server all invoices based on some fixed filters (all invoices of specific user or all invoices of one organization, all invoices of the last month) and then use loadonce: true, forceClientSorting: true
options to sort and to filter the returned data on the client side. The user could additionally to filter the subset of data locally using filter toolbar of searching dialog.
The performance of client side is essentially improved last years and loading relatively large JSON data from the server could be done very quickly. Because of that Client-Side-Filtering is strictly recommended. For better understanding the performance of local sorting, filtering and paging I'd recommend you to try the functionality on the demo. You will see that the timing of local filtering of the grid with 5000 rows and 13 columns is better as you can expect mostly from the round trip to the server and processing of server side filtering on some very good organized database. It's the reason why I recommend to consider to use client side sorting (or loadonce: true, forceClientSorting: true
options) as far it's possible.
If you need to filter data on the server then you need just send additional parameters to the server on every request. One can do that by including additional parameters in postData
. See the old answer for additional details. Alternatively one can use serializeGridData
to extend/modify the data, which will be set to the server.
After the data are loaded from the server, it could be sorted and filtered locally before the first page of data will be displayed in the grid. To force local filtering one need just add forceClientSorting: true
additionally to well known loadonce: true
parameter. It force applying local logic on the data returned from the server. Thus one can use postData.filters
, search: true
to force additional local filtering and sortname
and sortorder
parameter to force local sorting.
One more important remark about using hidden columns. Every hidden column will force creating DOM elements, which represent unneeded <td>
elements. The more DOM elements you place on the page the more slow will be the page. If local data will be used (or if loadonce: true
be used) then jqGrid hold data associated with every row twice: once as JavaScript object and once as cells in the grid (<td>
elements). Free jqGrid allows to use "additional properties" instead of hidden columns. In the case no data will be placed in DOM of the grid, but the data will be hold in JavaScript objects and one able to sort or filter by additional properties in the same way like with other columns. In the simplest way one can remove all hidden columns and to add additionalProperties
parameter, which should be array of strings with the name of additional properties. Instead of strings elements of additionalProperties
could be objects of the same structures like colModel
. For example, additionalProperties: [{ name: "taskId", sorttype: "integer"}, "isFinal"]
. See the demo as an example. The input data of the grid can be seen here. Another demo shows that searching dialog contains additional properties additionally to jqGrid column. The commented part columns
of searching
shows more advanced way to specify the list and the order of columns and additional properties displayed in searching dialog.
Related Topics
Canvas.Todataurl() Securityerror
Format Date to Mm/Dd/Yyyy in JavaScript
Remove Items from Array with Splice in for Loop
Convert String in Dot Notation to Get the Object Reference
Using Http Rest APIs with Angular 2
When Does Js Interpret {} as an Empty Block Instead of an Empty Object
How to Map More Than One Property from an Array of Objects
Is the Promise Constructor Callback Executed Asynchronously
How to Guarantee That My Enums Definition Doesn't Change in JavaScript
How Does the (Function() {})() Construct Work and Why Do People Use It
Merge JavaScript Objects in Array with Same Key
How to Calculate Md5 Hash of a File Using JavaScript
Can Es6 Template Literals Be Substituted at Runtime (Or Reused)
How to Strip All Punctuation from a String in JavaScript Using Regex