BulletzBill
20 Dec 2011, 12:32 PM
This plugin is loosely based on Ext.ux.grid.RowSelectionPaging, a 3.x plugin by Joeri Sebrechts, and is used to maintain the selection state of rows in a paginated Grid Panel when moving between pages. This is tested in Ext 4.0.7, however I have so far only used it with Ext.selection.CheckboxModel, and it should work with RowModel, but if anyone runs into any issues with it definitely let me know.
Demo: http://jsfiddle.net/Whkbu/2/
(http://jsfiddle.net/Whkbu/2/)
IMPORTANT!
Currently this plugin will only work if loadMask is set to false in the grid's viewConfig. I have been digging through Ext.LoadMask's code to try to find the source of this issue, but no luck yet. If anyone identifies the bug before I can, by all means post your fix here.
Simply enabling this plugin in your grid panel will allow it to maintain the selection state of the rows between pages visually, however using the selection model's getSelection() method will still retain its default behavior and only return the array of selected records for the current page. In order to get the array of ALL selected records across all pages, you must use the plugin's getPersistedSelection() method, like this:
var gridPanel = Ext.create('Ext.grid.Panel', {
...
viewConfig: {
loadMask : false //this setting is currently required for the plugin to work (see above)
},
plugins : [
{ ptype : 'pagingselectpersist' }
]
});
var selection = gridPanel.getPlugin('pagingSelectionPersistence'). getPersistedSelection();
//=> Ext.data.Model[]
Likewise, to clear selected records across all pages, use the plugin's clearPersistedSelection() method.
Also, it should be important to note that the default behavior of the CheckboxModel's header checkbox has been left intact, in that checking it will only select all rows on the current page, and unchecking it will only deselect all rows on the current page. That was just my personal preference, as I feel that the user may be confused as to why records on other pages are selected or deselected when clicking that header checkbox (not to mention, selecting all records across all pages could easily lead to scaling issues for grids with very large datasets). However, I'm sure this could easily be extended by someone else to provided that functionality if it is desired.
/**
* Grid PagingSelectionPersistence plugin
*
* Maintains row selection state when moving between pages of a paginated grid
*
* Public Methods:
* getPersistedSelection() - retrieve the array of selected records across all pages
* clearPersistedSelection() - deselect records across all pages
*
*
* @class Ext.ux.grid.plugin.PagingSelectionPersistence
* @extends Ext.AbstractPlugin
* @author Bill Dami
* @date December 20th, 2011
*/
Ext.define('Ext.ux.grid.plugin.PagingSelectionPersistence', {
alias: 'plugin.pagingselectpersist',
extend: 'Ext.AbstractPlugin',
pluginId: 'pagingSelectionPersistence',
//array of selected records
selection: [],
//hash map of record id to selected state
selected: {},
init: function(grid) {
this.grid = grid;
this.selModel = this.grid.getSelectionModel();
this.isCheckboxModel = (this.selModel.$className == 'Ext.selection.CheckboxModel');
this.origOnHeaderClick = this.selModel.onHeaderClick;
this.bindListeners();
},
destroy: function() {
this.selection = [];
this.selected = {};
this.disable();
},
enable: function() {
var me = this;
if(this.disabled && this.grid) {
this.grid.getView().on('refresh', this.onViewRefresh, this);
this.selModel.on('select', this.onRowSelect, this);
this.selModel.on('deselect', this.onRowDeselect, this);
if(this.isCheckboxModel) {
//For CheckboxModel, we need to detect when the header deselect/select page checkbox
//is clicked, to make sure the plugin's selection array is updated. This is because Ext.selection.CheckboxModel
//interally supresses event firings for selectAll/deselectAll when its clicked
this.selModel.onHeaderClick = function(headerCt, header, e) {
var isChecked = header.el.hasCls(Ext.baseCSSPrefix + 'grid-hd-checker-on');
me.origOnHeaderClick.apply(this, arguments);
if(isChecked) {
me.onDeselectPage();
} else {
me.onSelectPage();
}
};
}
}
this.callParent();
},
disable: function() {
if(this.grid) {
this.grid.getView().un('refresh', this.onViewRefresh, this);
this.selModel.un('select', this.onRowSelect, this);
this.selModel.un('deselect', this.onRowDeselect, this);
this.selModel.onHeaderClick = this.origOnHeaderClick;
}
this.callParent();
},
bindListeners: function() {
var disabled = this.disabled;
this.disable();
if(!disabled) {
this.enable();
}
},
onViewRefresh : function(view, eOpts) {
var store = this.grid.getStore(),
sel = [],
hdSelectState,
rec,
i;
this.ignoreChanges = true;
for(i = store.getCount() - 1; i >= 0; i--) {
rec = store.getAt(i);
if(this.selected[rec.getId()]) {
sel.push(rec);
}
}
this.selModel.select(sel, false);
if(this.isCheckboxModel) {
//For CheckboxModel, make sure the header checkbox is correctly
//checked/unchecked when the view is refreshed depending on the
//selection state of the rows on that page (workaround for possible bug in Ext 4.0.7?)
hdSelectState = (this.selModel.selected.getCount() === this.grid.getStore().getCount());
this.selModel.toggleUiHeader(hdSelectState);
}
this.ignoreChanges = false;
},
onRowSelect: function(sm, rec, idx, eOpts) {
if(this.ignoreChanges === true) {
return;
}
if(!this.selected[rec.getId()])
{
this.selection.push(rec);
this.selected[rec.getId()] = true;
}
},
onRowDeselect: function(sm, rec, idx, eOpts) {
var i;
if(this.ignoreChanges === true) {
return;
}
if(this.selected[rec.getId()])
{
for(i = this.selection.length - 1; i >= 0; i--) {
if(this.selection[i].getId() == rec.getId()) {
this.selection.splice(i, 1);
this.selected[rec.getId()] = false;
break;
}
}
}
},
onSelectPage: function() {
var sel = this.selModel.getSelection(),
len = this.getPersistedSelection().length,
i;
for(i = 0; i < sel.length; i++) {
this.onRowSelect(this.selModel, sel[i]);
}
if(len !== this.getPersistedSelection().length) {
this.selModel.fireEvent('selectionchange', this.selModel, [], {});
}
},
onDeselectPage: function() {
var store = this.grid.getStore(),
len = this.getPersistedSelection().length,
i;
for(i = store.getCount() - 1; i >= 0; i--) {
this.onRowDeselect(this.selModel, store.getAt(i));
}
if(len !== this.getPersistedSelection().length) {
this.selModel.fireEvent('selectionchange', this.selModel, [], {});
}
},
getPersistedSelection: function() {
return [].concat(this.selection);
},
clearPersistedSelection: function() {
var changed = (this.selection.length > 0);
this.selection = [];
this.selected = {};
this.onViewRefresh();
if(changed) {
this.selModel.fireEvent('selectionchange', this.selModel, [], {});
}
}
});
Demo: http://jsfiddle.net/Whkbu/2/
(http://jsfiddle.net/Whkbu/2/)
IMPORTANT!
Currently this plugin will only work if loadMask is set to false in the grid's viewConfig. I have been digging through Ext.LoadMask's code to try to find the source of this issue, but no luck yet. If anyone identifies the bug before I can, by all means post your fix here.
Simply enabling this plugin in your grid panel will allow it to maintain the selection state of the rows between pages visually, however using the selection model's getSelection() method will still retain its default behavior and only return the array of selected records for the current page. In order to get the array of ALL selected records across all pages, you must use the plugin's getPersistedSelection() method, like this:
var gridPanel = Ext.create('Ext.grid.Panel', {
...
viewConfig: {
loadMask : false //this setting is currently required for the plugin to work (see above)
},
plugins : [
{ ptype : 'pagingselectpersist' }
]
});
var selection = gridPanel.getPlugin('pagingSelectionPersistence'). getPersistedSelection();
//=> Ext.data.Model[]
Likewise, to clear selected records across all pages, use the plugin's clearPersistedSelection() method.
Also, it should be important to note that the default behavior of the CheckboxModel's header checkbox has been left intact, in that checking it will only select all rows on the current page, and unchecking it will only deselect all rows on the current page. That was just my personal preference, as I feel that the user may be confused as to why records on other pages are selected or deselected when clicking that header checkbox (not to mention, selecting all records across all pages could easily lead to scaling issues for grids with very large datasets). However, I'm sure this could easily be extended by someone else to provided that functionality if it is desired.
/**
* Grid PagingSelectionPersistence plugin
*
* Maintains row selection state when moving between pages of a paginated grid
*
* Public Methods:
* getPersistedSelection() - retrieve the array of selected records across all pages
* clearPersistedSelection() - deselect records across all pages
*
*
* @class Ext.ux.grid.plugin.PagingSelectionPersistence
* @extends Ext.AbstractPlugin
* @author Bill Dami
* @date December 20th, 2011
*/
Ext.define('Ext.ux.grid.plugin.PagingSelectionPersistence', {
alias: 'plugin.pagingselectpersist',
extend: 'Ext.AbstractPlugin',
pluginId: 'pagingSelectionPersistence',
//array of selected records
selection: [],
//hash map of record id to selected state
selected: {},
init: function(grid) {
this.grid = grid;
this.selModel = this.grid.getSelectionModel();
this.isCheckboxModel = (this.selModel.$className == 'Ext.selection.CheckboxModel');
this.origOnHeaderClick = this.selModel.onHeaderClick;
this.bindListeners();
},
destroy: function() {
this.selection = [];
this.selected = {};
this.disable();
},
enable: function() {
var me = this;
if(this.disabled && this.grid) {
this.grid.getView().on('refresh', this.onViewRefresh, this);
this.selModel.on('select', this.onRowSelect, this);
this.selModel.on('deselect', this.onRowDeselect, this);
if(this.isCheckboxModel) {
//For CheckboxModel, we need to detect when the header deselect/select page checkbox
//is clicked, to make sure the plugin's selection array is updated. This is because Ext.selection.CheckboxModel
//interally supresses event firings for selectAll/deselectAll when its clicked
this.selModel.onHeaderClick = function(headerCt, header, e) {
var isChecked = header.el.hasCls(Ext.baseCSSPrefix + 'grid-hd-checker-on');
me.origOnHeaderClick.apply(this, arguments);
if(isChecked) {
me.onDeselectPage();
} else {
me.onSelectPage();
}
};
}
}
this.callParent();
},
disable: function() {
if(this.grid) {
this.grid.getView().un('refresh', this.onViewRefresh, this);
this.selModel.un('select', this.onRowSelect, this);
this.selModel.un('deselect', this.onRowDeselect, this);
this.selModel.onHeaderClick = this.origOnHeaderClick;
}
this.callParent();
},
bindListeners: function() {
var disabled = this.disabled;
this.disable();
if(!disabled) {
this.enable();
}
},
onViewRefresh : function(view, eOpts) {
var store = this.grid.getStore(),
sel = [],
hdSelectState,
rec,
i;
this.ignoreChanges = true;
for(i = store.getCount() - 1; i >= 0; i--) {
rec = store.getAt(i);
if(this.selected[rec.getId()]) {
sel.push(rec);
}
}
this.selModel.select(sel, false);
if(this.isCheckboxModel) {
//For CheckboxModel, make sure the header checkbox is correctly
//checked/unchecked when the view is refreshed depending on the
//selection state of the rows on that page (workaround for possible bug in Ext 4.0.7?)
hdSelectState = (this.selModel.selected.getCount() === this.grid.getStore().getCount());
this.selModel.toggleUiHeader(hdSelectState);
}
this.ignoreChanges = false;
},
onRowSelect: function(sm, rec, idx, eOpts) {
if(this.ignoreChanges === true) {
return;
}
if(!this.selected[rec.getId()])
{
this.selection.push(rec);
this.selected[rec.getId()] = true;
}
},
onRowDeselect: function(sm, rec, idx, eOpts) {
var i;
if(this.ignoreChanges === true) {
return;
}
if(this.selected[rec.getId()])
{
for(i = this.selection.length - 1; i >= 0; i--) {
if(this.selection[i].getId() == rec.getId()) {
this.selection.splice(i, 1);
this.selected[rec.getId()] = false;
break;
}
}
}
},
onSelectPage: function() {
var sel = this.selModel.getSelection(),
len = this.getPersistedSelection().length,
i;
for(i = 0; i < sel.length; i++) {
this.onRowSelect(this.selModel, sel[i]);
}
if(len !== this.getPersistedSelection().length) {
this.selModel.fireEvent('selectionchange', this.selModel, [], {});
}
},
onDeselectPage: function() {
var store = this.grid.getStore(),
len = this.getPersistedSelection().length,
i;
for(i = store.getCount() - 1; i >= 0; i--) {
this.onRowDeselect(this.selModel, store.getAt(i));
}
if(len !== this.getPersistedSelection().length) {
this.selModel.fireEvent('selectionchange', this.selModel, [], {});
}
},
getPersistedSelection: function() {
return [].concat(this.selection);
},
clearPersistedSelection: function() {
var changed = (this.selection.length > 0);
this.selection = [];
this.selected = {};
this.onViewRefresh();
if(changed) {
this.selModel.fireEvent('selectionchange', this.selModel, [], {});
}
}
});