PDA

View Full Version : Ext.ux.grid.RowSelectionPaging



joeri
26 Feb 2009, 1:01 PM
An annoying problem with paging grids is that they don't remember their selections across paging. This plugin fixes that by preserving selections across paging events and store reloads. It also provides methods to fetch and clear the entire selection (across all pages).

Licensed under http://www.gnu.org/licenses/lgpl-3.0.txt

Ext.ux.grid.RowSelectionPaging.js:

/**
* Ext.ux.grid.RowSelectionPaging plugin for Ext.grid.GridPanel
* A grid plugin that preserves row selections across paging / filtering of the store.
*
* @author Joeri Sebrechts
* @date October 21st, 2009
*
* @class Ext.ux.grid.RowSelectionPaging
* @extends Ext.util.Observable
*/
Ext.ns('Ext.ux.grid');
Ext.ux.grid.RowSelectionPaging = function(config) {
Ext.apply(this, config);
};
Ext.extend(Ext.ux.grid.RowSelectionPaging, Ext.util.Observable, {
init: function(grid) {
this.grid = grid;
this.selections = []; // array of selected records
this.selected = {}; // hash mapping record id to selected state
grid.on('render', function() {
// attach an interceptor for the selModel's onRefresh handler
this.grid.view.un('refresh', this.grid.selModel.onRefresh, this.grid.selModel);
this.grid.view.on('refresh', this.onViewRefresh, this );
// add a handler to detect when the user changes the selection
this.grid.selModel.on('rowselect', this.onRowSelect, this );
this.grid.selModel.on('rowdeselect', this.onRowDeselect, this);
// patch selModel to detect selection cleared events
var scope = this;
this.selModelClearSelections = this.grid.selModel.clearSelections;
this.grid.selModel.clearSelections = function(fast) {
scope.selModelClearSelections.call(this, fast);
scope.onSelectionClear();
};
// and replace the default behavior of the "check all"
if (!this.originalSelectAll && (this.grid.selModel.id == 'checker')) {
this.grid.selModel.onHdMouseDown = function(e, t) {
if(t.className == 'x-grid3-hd-checker'){
e.stopEvent();
var hd = Ext.fly(t.parentNode);
var isChecked = hd.hasClass('x-grid3-hd-checker-on');
if(isChecked){
hd.removeClass('x-grid3-hd-checker-on');
scope.clearSelections();
}else{
hd.addClass('x-grid3-hd-checker-on');
scope.selectAll();
}
}
}
}
}, this);
}, // end init

// private
onViewRefresh: function() {
this.ignoreSelectionChanges = true;
// explicitly refresh the selection model
this.grid.selModel.onRefresh();
// selection changed from view updates, restore full selection
var ds = this.grid.getStore();
var newSel = [];
for (var i = ds.getCount() - 1; i >= 0; i--) {
if (this.selected[ds.getAt(i).id]) {
newSel.push(i);
}
}
this.grid.selModel.selectRows(newSel, false);
this.ignoreSelectionChanges = false;
}, // end onViewRefresh

// private
onSelectionClear: function() {
if (! this.ignoreSelectionChanges) {
// selection cleared by user
// also called internally when the selection replaces the old selection
this.selections = [];
this.selected = {};
}
}, // end onSelectionClear

// private
onRowSelect: function(sm, i, rec) {
if (! this.ignoreSelectionChanges) {
if (!this.selected[rec.id])
{
this.selections.push(rec);
this.selected[rec.id] = true;
}
}
}, // end onRowSelect

// private
onRowDeselect: function(sm, i, rec) {
if (!this.ignoreSelectionChanges) {
if (this.selected[rec.id]) {
for (var i = this.selections.length - 1; i >= 0; i--) {
if (this.selections[i].id == rec.id) {
this.selections.splice(i, 1);
this.selected[rec.id] = false;
break;
}
}
}
}
}, // end onRowDeselect

/**
* Clears selections across all pages
*/
clearSelections: function() {
this.selections = [];
this.selected = {};
this.onViewRefresh();
}, // end clearSelections

/**
* Returns the selected records for all pages
* @return {Array} Array of selected records
*/
getSelections: function() {
return [].concat(this.selections);
}, // end getSelections

/**
* Selects all the rows in the grid, including those on other pages
* Be very careful using this on very large datasets
*/
selectAll: function() {
var ds = this.grid.getStore();
ds.suspendEvents();
ds.load({
params: {start: 0, limit: ds.getTotalCount() },
callback: function() {
this.selections = ds.data.items.slice(0);
this.selected = {};
for (var i = this.selections.length - 1; i >= 0; i--) {
this.selected[this.selections[i].id] = true;
};
ds.resumeEvents();
this.onViewRefresh();
},
scope: this
});
}
});

To use, just load it as a plugin:

var pagingSelection = new Ext.ux.grid.RowSelectionPaging();
...
plugins: [pagingSelection],
...

A demo can be found at http://sebrechts.net/demo/rowselectionpaging/

Update (2009-10-21):
I've added support for a true select all operation (via the checkbox selection model). To get back to original "select all" behavior, create the plugin with "new Ext.ux.grid.RowSelectionPaging({originalSelectAll: true})"
Update (2009-10-22):
Btw, yesterday's update was tested on 2.2 and 3.0, so it should be compatible with both releases.

zhw511006
26 Feb 2009, 5:37 PM
maybe a live demo!

thank you!

joeri
27 Feb 2009, 2:06 AM
I've placed the sample code from the first post online at

http://sebrechts.net/demo/rowselectionpaging/

Just make selections with the checkboxes on different pages and navigate back and forth. You'll see what it does.

mdissel
27 Feb 2009, 6:43 AM
Nice! one little error, you're paging on the server is wrong, it's starting from the last record of the previous page..

As i understand it right, clicking on the 'select all' checkbox will select the current page, but 'deselect' all will remove all selections from all pages... one enhancement could be add ask the user what to do, select all in alle page or only current page, the same for deselecting..

Thanks
Marco

joeri
27 Feb 2009, 1:09 PM
Nice catch, I was copy/pasting out of some test code I made without realizing the paging was buggy. I've fixed the demo code in the first post.

The reason for the deselect all deselecting across all pages is because the plugin overrides the behavior of the selModel's clearSelections method. If you take that part out of the plugin code, it still works just fine, but it exhibits more consistent behavior from the perspective of the "select all" checkbox.

Having it the other way around, with select all selecting across all pages, means performing a separate request to the server to fetch all rows in the dataset. I'm not sure how desirable that behavior would be.

Besides, the select all checkbox is buggy anyway. If you check it, and navigate to the next page, it's still checked.

galdaka
28 Feb 2009, 2:30 AM
Hi,

Excellent work!!

Would be interesting return the selected IDs.

Greetings,

joeri
28 Feb 2009, 6:15 AM
The plugin's getSelections method returns the complete array of selected records, so it would be straightforward to fetch the id's from that.

thiner
2 Jul 2009, 6:41 PM
I see the Demo on your site, it's awesome!=P~
But I got problem when I try to use it in this Example:http://www.extjs.com/deploy/dev/examples/grid/paging.html

It seems like the selected rows' data have been saved into the 'selected' variable, but the 'checked' check box which made in previous action became 'unchecked' when I page-back from other pages.
Can anyone please tell me why?
Thanks a lot.:)

joeri
3 Jul 2009, 1:21 AM
thiner, that example doesn't seem to use selection. Could you post an example of the problem you mentioned?

chaos
3 Jul 2009, 2:21 AM
Very useful plugins!
Actually I don't need it but i would suggest to improve the <shift> key management to work across different pages.
Example:
select one or more records on a page, go in another page and select other records with the <shift> key. Actually it clear the selection in the first page (I'm not saying this is wrong), but it could be usefull to limit the logic of <shift> to the single page.
It could be an option in the config object.

ciao
-Mario

thiner
3 Jul 2009, 4:50 AM
Hi joeri,

You're right, it is not suitable for your extension, but I was trying to improve it, and I found maybe it's impossible, coz the selection function of your plugin base on the 'id' property, and the data from server in that example don't contain it.
I changed the my data source to which include an 'id' property, and it works smoothly now.:D
Any way, thank you for your replying.:)

Rothariger
30 Jul 2009, 12:23 PM
hello,

im using your extension, and i dont know if im using wrong or what, but i have 2 of these in different grids, but in the same page, each grid is in a tabpanel...

the thing is, that the selected and selections arrays, they join together in the last defined item.

its just me?


ps: im trying to load some selected items at the load of the data, this way after load the store of the grid, im reading an extra store with all previous selected/saved items...
im doing grid.plugins[x].selected[id] = record and grid.plugins[x].selections.push(record)...
could be this my problem???

joeri
31 Jul 2009, 12:50 AM
You're going to have to post an example of that one. I can't quite figure out what you're driving at.

Rothariger
31 Jul 2009, 5:20 AM
here is the code...

declaration of the plugin and checkbox selection columns.



psQuestion = new Ext.ux.grid.RowSelectionPaging({id: 'psQuestion'});
psSOVI = new Ext.ux.grid.RowSelectionPaging({id: 'psSOVI'});
var colPOCActive = new Ext.grid.CheckboxSelectionModel({id:'colPOCActive',header:'&nbsp;', dataIndex:'active', width: 30, listeners: { beforerowselect: fnBeforeSelect, beforerowdeselect: fnBeforeSelect, rowdeselect: fnRowDeselect } });
var colQActive = new Ext.grid.CheckboxSelectionModel({id:'colQActive',header:'&nbsp;', dataIndex:'active', width: 30, listeners: { beforerowselect: fnBeforeSelect, beforerowdeselect: fnBeforeSelect } });


and here are the grid declarations


{
xtype: 'editorgrid'
, id: 'egrdData'
, title: titlePOC
, height: 240
, width: 470
, loadMask: true
, view: new Ext.grid.GridView({})
, listeners: {afteredit: fnAfterEdit, beforeedit: fnBeforeEdit, rowmousedown: function(grid) { hinderSelection = true; }}
, enableColumnHide: false
, cm: new Ext.grid.ColumnModel([
colPOCActive,
{header: description,tooltip: description, dataIndex: 'description', width: 120, sortable: true, hideable: false},
{header: quantity,tooltip: quantity, dataIndex: 'quantity', width: 60, sortable: false, resizable: false, hideable: false,
editor: new Ext.form.NumberField({id: 'numQty', maxValue: 9999, maxLength: 4, allowDecimals: false, allowNegative: false}) },
{header: score,tooltip: score, dataIndex: 'score', width: 60, sortable: false, resizable: false, hideable: false,
editor: new Ext.form.NumberField({id: 'numScore', maxValue: 99999, maxLength: 5, allowDecimals: false, allowNegative: false}) }
])
, store: strGRD
, sm: colPOCActive
, plugins: [psSOVI]
, bbar: ptGRD
, tbar: [
description + ': ', ' ',
new Ext.app.SearchField({
store: strGRD
, id: 'sfContracts'
, width:150
, pagingToolbar: ptGRD
}), ' ', ' ', '-', ' '+ score + ': ', {xtype: 'tbtext', id: 'tbtTotalContract', text: '0'}]
},{
xtype: 'editorgrid'
, id: 'egrdQuestions'
, title: questions
, height: 240
, width: 470
, loadMask: true
, view: new Ext.grid.GridView({})
, listeners: {afteredit: fnAfterEditQuestion, beforeedit: fnBeforeEdit, rowmousedown: function() { hinderSelection = true; }}
, enableColumnHide: false
, cm: new Ext.grid.ColumnModel([
colQActive,
{header: description,tooltip: description, dataIndex: 'description', width: 120, sortable: true, hideable: false},
{header: score, tooltip: score, dataIndex: 'score', width: 60, sortable: false, resizable: false, hideable: false,
editor: new Ext.form.NumberField({id: 'numScoreQuestion', maxValue: 99999, maxLength: 5, allowDecimals: false, allowNegative: false}) }
])
, store: strGRDQuestions
, sm: colQActive
, plugins: [psQuestion]
, bbar: ptGRDQuestions
, tbar: [
description + ': ', ' ',
new Ext.app.SearchField({
store: store
, id: 'sfQuestions'
, width:150
, pagingToolbar: ptGRDQuestions
}), ' ', ' ', '-', ' ' + score + ': ', {xtype: 'tbtext', id: 'tbtTotalQuestions', text: '0'}]
}




and here is what i do when the store loads...



// i do this for each store with the respecting plugin...
strLocal.each(function (record){
psSOVI.selected[record.id] = true;
psSOVI.selections.push(record);
});

strLocalQuestion.each(function (record){
psQuestion.selected[record.id] = true;
psQuestion.selections.push(record);
});

// i have tryed with this also....
strLocal.each(function (record){
psSOVI.onRowSelect(null, null, record);
});


strLocalQuestion.each(function (record){
psQuestion.onRowSelect(null, null, record);
});





i dont know..

thanks for any help... i will post if i can make it work...

MasterAM
18 Aug 2009, 11:48 AM
@joeri
I totally agree that the perfect approach to this thing is a plugin.
Just started using it with ExtJS 3.0, on several grids with row selection model in single selection mode.

Works flawlessly so far.

Just one thing worth mentioning is the use of Ext.preg and ptype for lazy loading of plugins.
I added this at the end of the plugin code:


Ext.preg('rowselectionpaging', Ext.ux.grid.RowSelectionPaging);And then, on the grid definition:

...
plugins:[{ptype:'rowselectionpaging'}]
...

Excellent work, again!

@Rothariger,
It's hard to tell w/o seeing a working example.
I suggest stepping through the code when you do the manual selection addition and when a new page renders in the grid (e.g, breaking at the plugin's onViewRefresh method.
That is, of course, if the matter is not resolved already...

jocampo
31 Aug 2009, 9:25 AM
@joeri

Excellent plugin.!! may could you help me.. I am using your plugin but when somebody put directly in the paging bar the number of the page they want and come bact to some page where the had a selection the selection is gone.. The fast solution could be just lock the paging bar to use only the button.. but may you can help finding a better solution.. Something else, I need when somebody use the "select / deselect all" really select all the data in the grid and not only the data in the page.. could you tell me what could be the better way to do it, modify the code of the plugin or just make an extension of the code?

thanks in advance.!

PD: i kind of new using ExtJS and i am using ExtJS 3.0

joeri
31 Aug 2009, 1:40 PM
The problem with a real "select all" is that not all the rows are loaded into the store, so you don't even know what "all" is. You would first have to force the store to load all its rows, then add them all to a selection, and then refresh the grid. I tried a few different approaches to doing that, but none of them were clean enough that I felt they belonged in this plugin.

About your other issue, I can't reproduce that in the demo I included in the first posts. Could you describe step by step how you're doing that?

jocampo
2 Sep 2009, 7:43 AM
@joeri

Thanks for your answare, my first problem about not persisting selected item if i just jump between the pages using the text box in the paging bar was a cache problem.. after i clean it works really good.!!

Now my problem is with the "select/deselect all" issue yet.. I am working on it.. any clue?!

mrbeig9
19 Oct 2009, 10:18 AM
awesome plugin!! loved it.. but did anyone tried the selectall for the grid yet... i m trying on that.. if anyone comeup with something.. please share..

whodat
20 Oct 2009, 8:12 AM
awesome plugin!! loved it.. but did anyone tried the selectall for the grid yet... i m trying on that.. if anyone comeup with something.. please share..

I've implemented the check/select all for the grid

Method added to the class



checkAll: function(){
var i=0;
this.clearSelections();
for(i=0, it = this.grid.store.allData.items, l = it.length; i < l; i++) {
var recordArray=it[i].data;
var rec=this.grid.store.getById(it[i].data['your record's ID']);
this.selections.push(rec);
this.selected[rec.id] = true;
}
this.onViewRefresh();
}

joeri
21 Oct 2009, 12:16 PM
I've updated the first post with a new version that has the selectAll method and behavior that everybody in this thread seems to expect (select all rows, even on hidden pages). I tried to do the implementation as optimal as possible, but on large datasets it's going to be slow to select all.

whodat
22 Oct 2009, 9:27 AM
I've updated the first post with a new version that has the selectAll method and behavior that everybody in this thread seems to expect (select all rows, even on hidden pages). I tried to do the implementation as optimal as possible, but on large datasets it's going to be slow to select all.

What do you consider to be a large data-set?

joeri
22 Oct 2009, 9:57 AM
Well, anything that's counted in thousands of rows really. Depends on the browser and the machine. On my old G4 mini, a thousand rows take a noticeable while, but then that's a really slow machine. I would say "experiment", see what works.

whodat
22 Oct 2009, 10:01 AM
Well, anything that's counted in thousands of rows really. Depends on the browser and the machine. On my old G4 mini, a thousand rows take a noticeable while, but then that's a really slow machine. I would say "experiment", see what works.

I posted my solution on page 2. I was able to get this to selectAll or checkAll unnoticeably for thousands of rows. Though my largest dataset was around 8000 paged locally. I didn't see any noticeable slowness..

joeri
22 Oct 2009, 12:08 PM
I used your solution as inspiration for the changes to the plugin, but it suffers from the same performance issues. Like I said, this G4 is a really slow machine ("supercomputer", yeah right).

mrbeig9
23 Oct 2009, 12:46 PM
Is there any way to add this plugin as well as Filters in my grid. my grid is extended from custom grid where i added GridFilters as plugins to the grid. i also want to add this plugin. so is there any way that i can have both Filters and this plugin. when i add this plugin.. its getting overwritten on the filters plugin...

mrbeig9
24 Oct 2009, 5:22 AM
Thanks for the awesome plugin guys.. it helped me a lot.. and also found out how to add the filters as well as this plugin. my special thanks to joeri..

varad
16 Dec 2009, 10:08 PM
The plugin's getSelections method returns the complete array of selected records, so it would be straightforward to fetch the id's from that.

I am a bit confused about the how to fetch this id's returned by getSelections method.Can you plz help me in this regard,

Thanks in advance
varad

joeri
17 Dec 2009, 12:59 AM
If you look at the source code for the demo in the first post, then you'll see the getSelections method returns an array. This is an array of Record instances that are selected (the same records as exist in the store). You can look at the Record documentation (http://www.extjs.com/deploy/dev/docs/output/Ext.data.Record.html) to see how to use it. It has an id property containing the row it, or you can use the get() method on the Record to fetch any field.

varad
17 Dec 2009, 3:26 AM
Thanks joeri for your help.I was finally able to retrieve the selected records.=P~:))

mrbeig9
20 Jan 2010, 1:28 PM
Hi,

This plugin is gr8! but its not that effective if there are more number of records in the grid. like more than 1000.. The browser keeps on asking "Stop running the script?"

i liked the way u selecting all the rows when loading of the store.. is that the only way to avoid paging?

is there any way that you can make it more effective for selecting rows if its more in number?

the code WhoDat wrote in page 2 is not working. I modified the code.. but its was effective only for the select all in current page..

joeri
21 Jan 2010, 12:26 AM
I'll think about it. I have one more idea on how to speed this up, but it will make the plugin more complex and easier to break, so I'd rather not go there.

brigot
27 Jan 2010, 2:29 AM
hello
thanks for your plugin, but it dont run correctly with my grid.

in fact, if i check all, it select correctly all record ( if i can believe the result of getSelections() ), but if i turn to page 2, 3, .... all record has unchecked ...

This is my code :



...

checkbox_selection = new Ext.grid.CheckboxSelectionModel();
paging_selection = new Ext.ux.grid.RowSelectionPaging();

...

var column_model = new Ext.grid.ColumnModel({
columns: [
row_expander
,checkbox_selection
,
...
]
,isCellEditable: function(col, row){ return cell_editable(col, row); }
});

...

var grid = new xg.EditorGridPanel({
loadMask : true
,store : grid_data
,cm : column_model
,sm : checkbox_selection
,view :
new Ext.grid.GroupingView(
{
forceFit : true
,autoFill : true
,groupTextTpl : grid_view_groupTextTpl
,startCollapsed : true

}
)
,frame : true
,width : largeur_win
,autoHeight : true
,collapsible : false
,renderTo : 'div_grid'
,clicksToEdit : 1

,plugins: [row_expander, paging_selection]
,bbar :
new Ext.PagingToolbar(
{
pageSize : limit
,store : grid_data
,displayInfo : true
,displayMsg : grid_paging_toolbar_displayMsg
,emptyMsg : grid_paging_toolbar_emptyMsg
}
)
,tbar : new Ext.Toolbar()
});


the difference between the sample and my grid is that i use an EditorGridPanel with a GroupingView

Thanks for your help

mrbeig9
27 Jan 2010, 5:51 AM
Did you use the plugin which is in the first page? It worked for me

joeri
27 Jan 2010, 6:00 AM
@brigot: does the demo from the first post work for you? Does your store have a fixed row id for every row? This plugin requires each row to have a persistent row id. If row id's are automatically generated on every page load, the selection behavior won't be correct.

brigot
27 Jan 2010, 7:19 AM
@mrbeig9: yes, of course

@joeri : the id of each row is the id of my database, and it never change.
when i clic on the checkbox, it call the php, and the php do a query without limit.
the same query with a limit is use for show the row.

when i look the return of the function getSelections() i see many Object
if i inspect some one, i see a id, but this id, i think it's the id (html) of the row.
in 'data' i have all information like the 'real id form db', name, date, ....

thanks for your help

joeri
28 Jan 2010, 12:09 AM
brigot: what I meant was: did you set an "idProperty" value on your store?

See the JsonStore documentation for example: http://www.extjs.com/deploy/dev/docs/?class=Ext.data.JsonReader

The idProperty (which defaults to "id") is the field in each row that contains the id for that row. If you don't have an "id" field in your row, and you don't set the idProperty to the field that is your row, the row will have an auto-generated id, something like "ext-record-1". This plugin doesn't work with those id's.

brigot
28 Jan 2010, 12:43 AM
yes, it s set, this is my store :


grid_data = new Ext.data.GroupingStore({
proxy : new Ext.data.HttpProxy({ url: 'index.php' })
,reader : new Ext.data.JsonReader(
{
root: 'enreg'
,totalProperty: 'totalCount'
,idProperty: 'id'
}
,ligne
)
,remoteSort : true
,pruneModifiedRecords : true
,groupField : group_field
,remoteGroup : true
});


and this is a part of the json :


{
"enreg":
[
{"id":"300", "comment":"blablabla", ...... }
,{"id":"301", "comment":null, ........ }
.......
]
,"totalCount":464
}

joeri
28 Jan 2010, 6:25 AM
Then I'm fresh out of ideas on what the cause could be. Maybe it has something to do with being a GroupingStore? I didn't test it with a grouping store.

mrbeig9
4 Feb 2010, 1:12 PM
@Joeri.. i took your idea and worked on it..

It is improved a litte.. but you need to get the end row data from the PagingToolBar..
instead of

for (var i = ds.getCount() - 1; i >= 0; i--) {
if (this.selected[ds.getAt(i).id]) {
newSel.push(i);
}

in onViewRefresh() method.. I used endRow from PagingToolbar to iterate.. as i want to select till the last row of the current page.. It does load everything in this.Selections array..but selecting it does only for the current page..


for (var i = this.endRow - 1; i >= 0; i--) {
if (this.selected[ds.getAt(i).id]) {
newSel.push(i);
}


It cleared my "Stop Running the Script?" browser popup.. but this is not a good solution..

mrbeig

mrbeig9
8 Feb 2010, 8:12 AM
I was playing with SelectAll button I found out that when i filter on a grid and then select all the data its filtered and do something with it.. like copy or delete. Its copy/deleting the wrong data. i.e if i filtered a 20 rows grid.. and filtered data is 4 rows.. when i select all the 4 rows and delete, its deleting the first 4 rows of the grid. Did anyone face this problem? if you found out solution plz share..

I thought its getting the store again in selectAll..



ds = this.grid.getStore();


so i tried in this way

if (this.grid.filters) {
ds1 = this.grid.filter.store;
} else {
ds1 = this.grid.getStore();
}

no use..

let me know if you guys found out something..

mrbeig9
8 Feb 2010, 2:13 PM
Hi,

i got the solution for my previous post

just add few more params to the ds.load in select all method


params: { start: 0, limit: ds1.getTotalCount(), "filter[0][data][type]": this.grid.filter.getFilterData()[0].data.type, "filter[0][data][value]": this.grid.filter.getFilterData()[0].data.value, "filter[0][field]": this.grid.filter.getFilterData()[0].field },

If you are using gridFilters plugin this will be very easy for you.

lkamb
24 Mar 2010, 5:56 AM
I have a large dataset (unfiltered, over 100k records). I would like to be able to use select all the old way, but be able to page forward and select all page-by-page, adding to the total selection. Right now, using originalSelectAll=true, I can select all on the first page, but when I page to the next page, the select all checkbox is still checked. In order to select all on that page, I must uncheck it first, which de-selects the previous selection. (I saw you had noted this bug earlier, before the new select-all functionality.)

Is it possible to have the select all check box unselected when presenting a new page (if that page has not been previously all-selected) ?

thanks,

joeri
24 Mar 2010, 6:22 AM
This bug is present also in the core extjs library. I think with a few changes to the plugin, it's possible to have the behavior you describe. Although it won't be a very clean implementation.

I'll see if I can find the time to try to adapt the plugin for this use case.

lkamb
24 Mar 2010, 6:40 AM
If you have the time, I would certainly be appreciative. I'm not much of a js or extjs expert, so I'm not sure I can be much help. Thanks.

mrbeig9
24 Mar 2010, 9:45 AM
I have a large dataset (unfiltered, over 100k records). I would like to be able to use select all the old way, but be able to page forward and select all page-by-page, adding to the total selection. Right now, using originalSelectAll=true, I can select all on the first page, but when I page to the next page, the select all checkbox is still checked.
were u able to preserve the previous page select All without using this plugin?

joeri
14 Apr 2010, 1:20 PM
Try this:

/**
* Ext.ux.grid.RowSelectionPaging plugin for Ext.grid.GridPanel
* A grid plugin that preserves row selections across paging / filtering of the store.
*
* @author Joeri Sebrechts
* @date April 14th, 2010
*
* @class Ext.ux.grid.RowSelectionPaging
* @extends Ext.util.Observable
*/
Ext.ns('Ext.ux.grid');
Ext.ux.grid.RowSelectionPaging = function(config) {
Ext.apply(this, config);
};
Ext.extend(Ext.ux.grid.RowSelectionPaging, Ext.util.Observable, {
/**
* @cfg {String} selectionMode
* Determines the selection behavior of the "select all" checkbox.`
* Possible values: "all" to select all rows on all pages,
* "page" to select all rows on the current page
* Default: "all".
*/
selectionMode: 'all',

init: function(grid) {
this.grid = grid;
this.selections = []; // array of selected records
this.selected = {}; // hash mapping record id to selected state
grid.on('render', function() {
// attach an interceptor for the selModel's onRefresh handler
this.grid.view.un('refresh', this.grid.selModel.onRefresh, this.grid.selModel);
this.grid.view.on('refresh', this.onViewRefresh, this );
// add a handler to detect when the user changes the selection
this.grid.selModel.on('rowselect', this.onRowSelect, this );
this.grid.selModel.on('rowdeselect', this.onRowDeselect, this);
// patch selModel to detect selection cleared events
var scope = this;
this.selModelClearSelections = this.grid.selModel.clearSelections;
this.grid.selModel.clearSelections = function(fast) {
scope.selModelClearSelections.call(this, fast);
scope.onSelectionClear();
};
// and replace the default behavior of the "check all"
if (this.grid.selModel.id == 'checker') {
this.grid.selModel.onHdMouseDown = function(e, t) {
if(t.className == 'x-grid3-hd-checker'){
e.stopEvent();
var hd = Ext.fly(t.parentNode);
var isChecked = hd.hasClass('x-grid3-hd-checker-on');
if(isChecked){
hd.removeClass('x-grid3-hd-checker-on');
scope.clearSelections();
} else {
hd.addClass('x-grid3-hd-checker-on');
if (scope.selectionMode == 'all') {
// select rows on all pages
scope.selectAll();
} else {
// select only rows on current page
this.selectAll();
};
};
};
};
};
}, this);
}, // end init

// private
onViewRefresh: function() {
this.ignoreSelectionChanges = true;
// explicitly refresh the selection model
this.grid.selModel.onRefresh();
// selection changed from view updates, restore full selection
var ds = this.grid.getStore();
var newSel = [];
for (var i = ds.getCount() - 1; i >= 0; i--) {
if (this.selected[ds.getAt(i).id]) {
newSel.push(i);
};
};
this.grid.selModel.selectRows(newSel, false);
this.ignoreSelectionChanges = false;
this.updateChecker();
}, // end onViewRefresh

// private
updateChecker: function() {
// get the "select all checkbox", and update its checked state
var checker = Ext.fly(this.grid.getView().innerHd).query('.x-grid3-hd-checker')[0];
if (checker) {
var selected = this.grid.selModel.getSelections();
var rowCount = this.grid.getStore().getCount();
// if no rows on the current page are selected
if (selected.length < rowCount) {
Ext.fly(checker).removeClass('x-grid3-hd-checker-on');
// if all rows on the current page are selected
// and we are not in "select across pages" selection mode
} else if (this.selectionMode == 'page'){
Ext.fly(checker).addClass('x-grid3-hd-checker-on');
};
};
},

// private
onSelectionClear: function() {
if (! this.ignoreSelectionChanges) {
// selection cleared by user
// also called internally when the selection replaces the old selection
this.selections = [];
this.selected = {};
this.updateChecker();
};
}, // end onSelectionClear

// private
onRowSelect: function(sm, i, rec) {
if (! this.ignoreSelectionChanges) {
if (!this.selected[rec.id])
{
this.selections.push(rec);
this.selected[rec.id] = true;
this.updateChecker();
};
};
}, // end onRowSelect

// private
onRowDeselect: function(sm, i, rec) {
if (!this.ignoreSelectionChanges) {
if (this.selected[rec.id]) {
for (var pos = this.selections.length - 1; pos >= 0; pos--) {
if (this.selections[pos].id == rec.id) {
this.selections.splice(pos, 1);
this.selected[rec.id] = false;
this.updateChecker();
break;
};
};
};
};
}, // end onRowDeselect

/**
* Clears selections across all pages
*/
clearSelections: function() {
this.selections = [];
this.selected = {};
this.onViewRefresh();
}, // end clearSelections

/**
* Returns the selected records for all pages
* @return {Array} Array of selected records
*/
getSelections: function() {
return [].concat(this.selections);
}, // end getSelections

/**
* Selects all the rows in the grid, including those on other pages
* Be very careful using this on very large datasets
*/
selectAll: function() {
var ds = this.grid.getStore();
ds.suspendEvents();
ds.load({
params: {start: 0, limit: ds.getTotalCount() },
callback: function() {
this.selections = ds.data.items.slice(0);
this.selected = {};
for (var i = this.selections.length - 1; i >= 0; i--) {
this.selected[this.selections[i].id] = true;
};
ds.resumeEvents();
this.onViewRefresh();
},
scope: this
});
}
});

The "originalSelectAll" config was replaced by "selectionMode", which supports two values. "all" is the default, and selects across pages. "page" is the other option, and selects only on the current page. The "select all" checker now tracks the selection state of the current page (with different behavior depending on the selectionMode). Tell me if this works ok for you, then I can replace the code in the first post.

sridharta
21 Apr 2010, 5:32 AM
Hi ,

I appreciate your wonderful effort. Currently I am having a problem. Where should I save the RowSelectionPaging.js and how to include that js as a pluggin. I am a newbie to this paging and pluggins. Can you please help me by giving this information.

sridharta
21 Apr 2010, 5:34 AM
Adding to my previous reply, can you upload the full sample project using the checkbox state management if it is not a problem for you? Thanks in advance.

joeri
21 Apr 2010, 11:27 PM
@sridharta: the complete demo can be downloaded from this link (http://sebrechts.net/demo/rowselectionpaging/rowselectionpaging.zip)

sridharta
21 Apr 2010, 11:31 PM
Thanks Joeri for your timely response.

Raqil
29 Jul 2010, 3:58 AM
Could someone paste a live example how to get te data from selections? :( I know that the getSelections() returns an array, but I don't know how to use it :/ ..

joeri
30 Jul 2010, 1:33 AM
The return value of getSelections() is the same as that function on the RowSelectionModel. It's an array of Record (http://www.sencha.com/deploy/dev/docs/?class=Ext.data.Record) objects.

To iterate over it, do something like this:

var selections = yourPlugin.getSelections();
// get all the lastName fields out of the selected rows
// (supposing there is a field lastName configured in the store)
var lastNames = [];
for (var i = 0; i < selections.length; i++) {
lastNames.push(selections[i].get('lastName'));
}

gislars
10 Aug 2010, 1:56 AM
I'm trying to save the value of an editable column. There for I'm using a EditorGridPanel and its configured with {CheckOnly: true}

What would I change or add to CheckboxSelectionModel for this function?
I tried to ad an handler for afteredit event but somehow I couldn't manage to save the values. It looks to me like the gibberish column get repopulated with every page switch and all edited values get overridden.

Thank you in advance for any hints how I can solve this.

singh.guru
31 Jan 2011, 6:20 AM
hello there,

i must say its a best plugin. it has saved loads of my time. thanks Joeri.

i have one small issue, if you can look into..

lets says on my page one we select 2 checkbox (by clicking on the checkbox), the moment we click on the row, that particular row get selected and all the checked rows get un-checked

you can see it in the live demo provided on the first page.

kindly advice, how can we overcome this issue

joeri
31 Jan 2011, 7:19 AM
If I understand you correctly, you want a click on a row to not unselect the other rows. That is standard behavior of the RowSelectionModel. It has nothing to do with this plugin. The behavior is that ctrl-click toggles a single row selection, shift-click performs a range selection, and regular click selects only the clicked row (and unselects all others).

I suggest looking at the ext source code, in the RowSelectionModel class, check the code for handleMouseDown. The second argument to selectRow must be true to preserve the existing selection.

singh.guru
1 Feb 2011, 7:49 AM
thx, joeri for the pointer,

i will implement this tonight and get back to you..

cheers :)

singh.guru
1 Feb 2011, 10:18 AM
Hi Joeri,

it worked for me, but for some strange reason this plugin is not working on my code, even if i undo all my changes

can you plz help me out in this


var selectionPaging = null;

var store = new Ext.data.GroupingStore({
reader: reader,
autoLoad: {params:{start: 0, limit: 10}},
url: 'http://localhost/ext/examples/grid/data.asp?srchID='+ document.getElementById("txtSearchID").value,
//params :{start:0, limit:20, pgno :1},
sortInfo:{field: 'company', direction: "ASC"},
groupField:'industry'
});
store.setBaseParam('pgno',CurrentPage);
var expander = new Ext.ux.grid.RowExpander({
tpl : new Ext.Template(
'<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<b>Company:</b> {company}</p><br>',
'<p><b>Summary:</b> {industry}</p>'
)
});
var sm2 = new xg.CheckboxSelectionModel({
listeners: {
// On selection change, set enabled state of the removeButton
// which was placed into the GridPanel using the ref config
selectionchange: function(sm) {
if (sm.getCount()) {
grid4.removeButton.enable();
} else {
grid4.removeButton.disable();
}
//GetSelectionSummery(sm);

},
//click: function(sm) {GetSelectionSummery(sm);},
}
});

selectionPaging = new Ext.ux.grid.RowSelectionPaging();

// grid
grid4 = new xg.GridPanel({
id:'button-grid',
store: store,
columns: [
expander,
sm2,
{id:'company',header: "Company", width: 60, sortable: true, dataIndex: 'company'}, //, renderer: getMyPrice
{header: "Price", width: 20, sortable: true, renderer: Ext.util.Format.usMoney, dataIndex: 'price'},
{header: "Change", width: 20, sortable: true, dataIndex: 'change', renderer: Ext.util.Format.usMoney},
{header: "Industry", width: 20, sortable: true, dataIndex: 'industry'},
{header: "Last Updated", width: 20, sortable: true, renderer: Ext.util.Format.dateRenderer('m/d/Y'), dataIndex: 'lastChange'}
],

sm: sm2,
// plugins: expander,
plugins: [selectionPaging],

view: new Ext.grid.GroupingView({
forceFit:true,
startCollapsed:true,
groupTextTpl: '{text} ({[values.rs.length]} {[values.rs.length > 1 ? "Items" : "Item"]})'
}),
// paging bar on the bottom



bbar: new Ext.PagingToolbar({
pageSize: 10,
store: store,
MyCurrentPage:1,
prependButtons: true,
displayInfo: true,
displayMsg: 'Displaying stones {0} - {1} of {2}',
emptyMsg: "<b>No Stones to Display</b>" ,
listeners: {


beforechange:function (paging, params) {

console.log(paging.cursor +"----"+paging.pageSize+"----"+ Math.ceil((paging.cursor + paging.pageSize) / paging.pageSize))

CurrentPage = Math.ceil((paging.cursor + paging.pageSize) / paging.pageSize);
console.debug(paging)
this.MyCurrentPage = CurrentPage
this.store.setBaseParam('pgno',CurrentPage);
}
}

}),
columnLines: true,
// inline buttons
buttons: [
{text:'Add to Cart',tooltip:'Add a new row',iconCls:'add',handler: AddToCart},
{text:'Add to hold list',tooltip:'',iconCls:'option',handler: AddToHoldList}
],
buttonAlign:'left',
// inline toolbars
tbar:[

{text:'Add to Cart', tooltip:'Add a new row', iconCls:'add', handler: AddToCart},
'-',
{text:'Add to hold list', tooltip:'', iconCls:'option',handler: AddToHoldList},
'-',
{text:'Remove Selection',tooltip:'Remove the selected item',iconCls:'remove',ref: '../removeButton',handler: RemoveFromList, disabled: true}

],
width:1250,
height:450,
frame:true,
loadMask: true,
layout:'fit',
title: 'lorem ipusm',
iconCls:'icon-grid',
renderTo: 'grid-Panel'
});



thanks :)

joeri
2 Feb 2011, 12:28 AM
I haven't tested this plugin with a GroupingView. Can you get it to work with a regular view? I could check into making it work with the GroupingView if that is where the problem is located.

singh.guru
2 Feb 2011, 1:30 AM
i dont know.. i can see all my config is fine.. and it stopped working, your plugin was working fine, suddenly it has stopped working...

thanks

Zaphod Beeblebrox
22 Mar 2011, 10:57 AM
Joeri, first... nicely done, thank you for the plug-in, it's working beautifully.

In my application I instantiate a grid with the RowSelectionPaging plug-in in an Ext.Window (data store is connected to MySQL via an Ajax proxy).

Once the user has paged and selected their data and clicked 'save', I read the selectionPaging.getSelections() array of objects and store their selections in a MySQL table via an Ajax call, and then destroy the grid/window.

What I now need to do is on subsequent instantiations set the selected flag for those records already selected and I don't see a public method within the RowSelectionPaging plug-in to permit me to do this. Perhaps this isn't a plug-in specific question but a more general question of the CheckboxSelectionModel. A healthy pointer would be much appreciated...

joeri
23 Mar 2011, 12:06 AM
What I now need to do is on subsequent instantiations set the selected flag for those records already selected and I don't see a public method within the RowSelectionPaging plug-in to permit me to do this.

You're right that this isn't supported. To implement a setSelections is pretty straightforward (untested code, you may need to debug it):


// selection = array of Record
setSelections: function(selection) {
this.selections = [].concat(selection);
this.selected = {};
for (var i = 0; i < this.selections.length; i++) {
this.selected[this.selections[i].id] = true;
}
}

You would have to call that after the plugins are initialized, perhaps in the 'render' event handler.

NoahK17
23 Mar 2011, 10:29 AM
Hi there! Have you tested this plugin with my SmartCheckBoxSelectionModel by chance? This seems to fill in a missing feature I've wanted to add for a long time. Not sure if you're still actively developing this or not, but I figured it couldn't hurt to ask!

joeri
24 Mar 2011, 1:32 AM
No, haven't tested it in that combination. You're welcome to try for yourself.

I plan to revisit this plugin for Ext JS 4, when we do our migration later this year.

Zaphod Beeblebrox
24 Mar 2011, 4:35 AM
So, I've successfully modified the RowSelectionPaging plugin to support the idea of pre-loading selections.

I wanted a widget that reduced the number of form controls instantiated at any given time in the DOM and this plug-in fit the bill perfectly.

However, I also needed to pre-select certain elements from the initial database read. In addition, since this control is not RESTful, we needed to track selections & unselections so the user could either 'save selections' or 'cancel'.

The solution was to add another collection to the plug-in - I called them unselected and unselections - that are maintained in a similar manner to the selected/selections collections.



// add the following just before the 'grid.on('... in the init:
this.unselections = []; // array of unselected records
this.unselected = {}; // hash mapping record id to unselected state
this.selectDbItems(); // pre-select any items from an initial database read
This allowed me to keep track of un-select actions on database-pre-selected items when moving from page to page. With that nailed down, I just needed to add the pre-selection method to the class. This pre-selection method relies on a specific data element called 'selected' in the data store.

You have to change the onViewRefresh method to deal with tracking any unselected data that might have been brought in from the database during a paging action as well as changing the selModel.selectRows call because you now care about preserving selections. So, records in the selected/selections collections plus records in the data with a 'selected' value of 1 (hacki-ness, sorry) but not in the unslected/unselections collecctions (phew!)...


// private
onViewRefresh: function() {
this.ignoreSelectionChanges = true;
// explicitly refresh the selection model
this.grid.selModel.onRefresh();
// selection changed from view updates, restore full selection
var ds = this.grid.getStore();
var newSel = [];
for (var i = ds.getCount() - 1; i >= 0; i--) {
if (this.selected[ds.getAt(i).id] || (ds.getAt(i).data.selected=='1' && !this.unselected[ds.getAt(i).id]) ) {
newSel.push(i);
}
}
this.ignoreSelectionChanges = false;

// we now care about preserving selections - second parm = true
this.grid.selModel.selectRows(newSel, true);

}, // end onViewRefresh

You have to add un-selection management to onRowSelect and onRowDeselect...


// private
onRowSelect: function(sm, i, rec) {
if (! this.ignoreSelectionChanges) {
if (!this.selected[rec.id]) {
this.selections.push(rec);
this.selected[rec.id] = true;

// un-selected management
if(this.unselected[rec.id]) {
for (var i = this.unselections.length - 1; i >= 0; i--) {
if (this.unselections[i].id == rec.id) {
this.unselections.splice(i, 1);
this.unselected[rec.id] = false;
break;
}
}
}

}
}
}, // end onRowSelect

// private
onRowDeselect: function(sm, i, rec) {
if (!this.ignoreSelectionChanges) {

// unselected management
this.unselections.push(rec);
this.unselected[rec.id] = true;

if (this.selected[rec.id]) {
for (var i = this.selections.length - 1; i >= 0; i--) {
if (this.selections[i].id == rec.id) {
this.selections.splice(i, 1);
this.selected[rec.id] = false;
break;
}
}
}
}
}, // end onRowDeselect
And you have to add the following to both the onSelectionClear and clearSelections methods...


this.unselections = [];
this.unselected = {};
Finally, I used a method similar to the 'selectAll; method which forces the data store to retrieve all records in order to parse the ones that have been selected (thus the hacki-ness of this solution as it requires a known column to use as a marker, as well as a full database read which has the risk of causing a pregnant pause on instantiation)



/**
* Selects all the rows in the grid, including those on other pages
* and updates the internal selection model - pruning (splice) the
* records that don't belong.
*
* large data sets could cause slow instantiation
*/
selectDbItems: function() {
var ds = this.grid.getStore();
ds.suspendEvents();
ds.load({
params: {start: 0, limit: ds.getTotalCount() },
callback: function() {
this.selections = ds.data.items.slice(0);
this.selected = {};
for (var i = this.selections.length - 1; i >= 0; i--) {
if(this.selections[i].data.selected=='1') {
this.selected[this.selections[i].id] = true;
} else {
this.selections.splice(i, 1);
}
};
ds.resumeEvents();
},
scope: this
});
} // end selectDbItems

manyu.tomar
15 Aug 2011, 11:16 PM
hi , first of all thanks for this nice plug in .
i was successfully using your plug in in extjs 3 , but while up grading to extjs 4 i am facing the same problem of selection paging , have u revisit this plugin for Ext JS 4 ?.
please reply .


thanks and regards

joeri
16 Aug 2011, 5:23 AM
I haven't had time to implement Ext JS 4 support yet, and am not sure when I will get around to it.

Sorry that I can't offer you anything better than that.

manyu.tomar
17 Aug 2011, 3:04 AM
i am able to update your plugin compatible with extjs 4 , but facing some problem , u might help me with that ,

Code :


selectionPaging = function(config) {
Ext.apply(this, config);
};
Ext.extend(selectionPaging, Ext.util.Observable, {
init: function(grid) {
this.grid = grid;
this.selections = []; // array of selected records
this.selected = {}; // hash mapping record id to selected state
this.ignoreSelectionChanges = '';
grid.on('render', function() {
// attach an interceptor for the selModel's onRefresh handler
this.grid.view.un('refresh', this.grid.selModel.refresh, this.grid.selModel);
this.grid.view.on('refresh', this.onViewRefresh, this );
this.grid.view.headerCt.on('headerclick', this.onHeaderClick, this);
// add a handler to detect when the user changes the selection
this.grid.selModel.on('select', this.onRowSelect, this );
this.grid.selModel.on('select', this.onRowSelect, this );
this.grid.selModel.on('deselect', this.onRowDeselect, this);
this.grid.dockedItems.items[1].on('beforechange', this.pageChange, this ); // not sure about this , looking for another way
}, this);
},

// private
onViewRefresh: function() {
this.ignoreSelectionChanges = true;
// explicitly refresh the selection model
this.grid.selModel.refresh();
// selection changed from view updates, restore full selection
var ds = this.grid.getStore();
for (var i = ds.getCount() - 1; i >= 0; i--) {
if (this.selected[ds.getAt(i).internalId]) {
this.grid.selModel.select(i,true,false);
}
}
this.ignoreSelectionChanges = false;
}, // end onViewRefresh

pageChange: function() {
this.ignoreSelectionChanges = true;
},
// private
onSelectionClear: function() {
if (! this.ignoreSelectionChanges) {
// selection cleared by user
// also called internally when the selection replaces the old selection
this.selections = [];
this.selected = {};
}
}, // end onSelectionClear

// private
onRowSelect: function(sm,rec,i,o) {
if (! this.ignoreSelectionChanges) {
if (!this.selected[rec.internalId])
{
this.selections.push(rec);
this.selected[rec.internalId] = true;
}

}

}, // end onRowSelect
onHeaderClick: function(headerCt, header, e) {
if (header.isCheckerHd) {
e.stopEvent();
var isChecked = header.el.hasCls(Ext.baseCSSPrefix + 'grid-hd-checker-on');
if (isChecked) {
// We have to supress the event or it will scrollTo the change
this.clearSelections();
} else {
// We have to supress the event or it will scrollTo the change
this.selectAll();
}
}
},
// private
onRowDeselect: function(sm,rec,i,o){
if (!this.ignoreSelectionChanges) {
if (this.selected[rec.internalId]) {
for (var i = this.selections.length - 1; i >= 0; i--) {
if (this.selections[i].internalId == rec.internalId) {
this.selections.splice(i, 1);
this.selected[rec.internalId] = false;
break;
}
}
}
}
}, // end onRowDeselect

/**
* Clears selections across all pages
*/
clearSelections: function() {
this.selections = [];
this.selected = {};
this.grid.selModel.deselectAll();
this.onViewRefresh();
}, // end clearSelections

/**
* Returns the selected records for all pages
* @return {Array} Array of selected records
*/
getSelection: function() {
return [].concat(this.selections);
}, // end getSelections

/**
* Selects all the rows in the grid, including those on other pages
* Be very careful using this on very large datasets
*/
selectAll: function() {
var ds = this.grid.getStore();
ds.suspendEvents();
ds.load({ //problem is , when i load this store (ds), the store of grid take this new store and reload the data
start: 0,
limit: ds.getTotalCount() ,
callback: function() {
this.selections = ds.data.items.slice(0);
this.selected = {};
for (var i = this.selections.length - 1; i >= 0; i--) {
this.selected[this.selections[i].internalId] = true;
};
ds.resumeEvents();
this.onViewRefresh();
},
scope: this
});
}
});

i guess suspendEvents() function is not working properly .
therefore with load of ds in selectAll() , grid renders tha new data .
plz do reply .

thanks and regards

joeri
17 Aug 2011, 11:28 PM
I don't know what to advise to you without downloading a copy of Ext 4 and porting it over myself.

I will get around to it at some point, but not soon.

manyu.tomar
18 Aug 2011, 4:13 AM
Thanks for your concern . For now , i am able to get rid of that error by cloning the grid store
and filling ' selection ' array by loading this cloned store .
:)