PDA

View Full Version : Ext.ux.grid.plugin.PagingSelectionPersistence



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, [], {});
}
}
});

mitchellsimoens
20 Dec 2011, 12:45 PM
Was waiting for someone to do this. The problem I see is how does one know that there are selections in other pages?

BulletzBill
20 Dec 2011, 1:01 PM
Was waiting for someone to do this. The problem I see is how does one know that there are selections in other pages?
In my own application I just bind a listener to the selection model's 'selectionchange' which updates UI elements outside of the grid panel with the current selected record count using getPersistedSelection().length. I left the plugin pretty bare bones so developers could implement their own ways of showing the user what/how many is selected in their applications.

That being said I could see the usefulness of integrating the display of this information directly into the grid panel itself. Maybe something similar to how GMail handles it in their list view, displaying a strip above the first row in the grid telling the user how many records are selected?

mitchellsimoens
20 Dec 2011, 1:04 PM
In my own application I just bind a listener to the selection model's 'selectionchange' which updates UI elements outside of the grid panel with the current selected record count using getPersistedSelection().length. I left the plugin pretty bare bones so developers could implement their own ways of showing the user what/how many is selected in their applications.

That is where if you use methods to create certain things it is easily overridable. Either that or have an example of how you implemented it.


That being said I could see the usefulness of integrating the display of this information directly in the grid panel itself. Maybe this something similar to how GMail handles it in their list view, displaying a strip above the first row in the grid telling the user how many records are selected?

Using something familiar to lots of people is usually a win!

morfeusz
21 Dec 2011, 2:12 AM
Thank you for this plugin - very useful.

preaction
3 Jan 2012, 9:31 AM
This plugin is perfect for me, so I added some stuff to make it more perfect. I needed a way to set what rows were already selected.

I also ran across an error that happened when a select event was dispatched without any rows in the selection, so I made sure to only try to select rows when there were rows to select.

Attached is a patch.

EDIT: My "fix" for the error (select event being dispatched with an empty selection array) made clearPersistedSelection() stop working. New patch is below, as the forum won't let me edit attachments that I see.



diff --git a/ext-ux/grid/plugin/PagingSelectionPersistence.js b/ext-ux/grid/plugin/PagingSelectionPersistence.js
index 3e3c6ac..f55d0f6 100644
--- a/ext-ux/grid/plugin/PagingSelectionPersistence.js
+++ b/ext-ux/grid/plugin/PagingSelectionPersistence.js
@@ -6,7 +6,8 @@
* Public Methods:
* getPersistedSelection() - retrieve the array of selected records across all pages
* clearPersistedSelection() - deselect records across all pages
- *
+ * setPersistedSelection() - Set an array of selected records across all pages
+ *
*
* @class Ext.ux.grid.plugin.PagingSelectionPersistence
* @extends Ext.AbstractPlugin
@@ -190,5 +193,17 @@ Ext.define('Ext.ux.grid.plugin.PagingSelectionPersistence', {
if(changed) {
this.selModel.fireEvent('selectionchange', this.selModel, [], {});
}
+ },
+
+ setPersistedSelection: function(selection) {
+ var i = 0;
+ if ( !Ext.isArray( selection ) ) {
+ selection = [selection];
+ }
+ this.selection = selection;
+ for ( ; i < selection.length; i++ ) {
+ this.selected[ selection[i].getId() ] = selection[i];
+ }
+ this.onViewRefresh();
}
});

billp
13 Jan 2012, 8:55 AM
I'm having an issue with the headerCheckbox not deselecing when paging or sorting.
My quesiton is here:
http://www.sencha.com/forum/showthread.php?174109-Ext.ux.grid.plugin.PagingSelectionPersistence-headercheckbox-question&p=714398

Any suggestions would be great. Thanks.

BulletzBill
13 Jan 2012, 9:10 AM
@preaction - Nice catch on that error, and definitely makes sense to have a method to set what rows are already selected. I'm going to update my version and the original post with those changes shortly.

@billp - Can't think of anything that would be causing that off the top of my head but I will definitely look into it when I get a chance, just have been rather busy last few weeks and haven't had time to work on it at all. I'll post anything ill find in this thread, so just check back here.

billp
13 Jan 2012, 9:14 AM
@billp - Can't think of anything that would be causing that off the top of my head but I will definitely look into it when I get a chance, just have been rather busy last few weeks and haven't had time to work on it at all. I'll post anything ill find in this thread, so just check back here.

No worries, just noticed it on my usage of it, but that jfiddle page works great
thanks

billp
13 Jan 2012, 2:12 PM
To provide a little more info, it seems that this only happens when you sort only on that one column.
If you select different columns to sort by then the header Check box goes away.

So basically, if i sort on ID, then check all boxes, the header is checked. If i page over or sort in revers on the ID col, the check mark stays. BUT if i sort on a different col, the check box goes away.

In the test on jsfiddle, works as expected, no matter how many times you sort on the same col.

UPDATE (1/17/12):

So it's not that col only. when my results are returned they are already sorted on a Description Col. If i check the header box, they all select as expected. If i resort by that col, or any other, the checkbox stays, even though no boxes are selected at that point. If i resort again on any column the checked header goes away. So it seems it needs a secondary sort to reset the counter for the header.

I'm using 4.0.2a

mazhar.shaikh
5 Jun 2013, 2:38 PM
Using the SelectAll checkbox will only select rows on the current page. What if I want to select all the records on every page.

ashu2289
6 Oct 2013, 11:53 PM
Hi,

I looked at the loading mask window issue in this case, for making your code work with loading mask you need to override the native "onMaskBeforeShow" function of Extjs api within viewConfig.

GridView is an extension to AbstractView which gets the current selection model and clears it all before navigation to the second page in the grid.
Sample implementation would be like this:


viewConfig:{
style:{ overflow:'hidden' },
onMaskBeforeShow: function(){

//Place your custome code here....
//below is just a sample piece of code it will work fine even if you leave this handler empty.

var grid = Ext.getCmp('resultGrid');
var loadingHeight = grid.loadingHeight;
if (loadingHeight) {
grid.setCalculatedSize(undefined, loadingHeight);
}

}

}


I hope it helps you guys :)

mazhar.shaikh
7 Oct 2013, 12:58 PM
Thanks for your reply. Unfortunately I couldnt find a solution to this in time. So I changed the requirement and removed paging from my grid. So that was the workaround I used.

Thanks anyway.