1. #1
    Sencha User BulletzBill's Avatar
    Join Date
    Mar 2010
    Location
    New York
    Posts
    138
    Vote Rating
    0
    BulletzBill is on a distinguished road

      0  

    Default Ext.ux.grid.plugin.PagingSelectionPersistence

    Ext.ux.grid.plugin.PagingSelectionPersistence


    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/

    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:
    Code:
    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.

    Code:
    /**
     * 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, [], {});
            }
        }
    });

  2. #2
    Sencha - Senior Forum Manager mitchellsimoens's Avatar
    Join Date
    Mar 2007
    Location
    Gainesville, FL
    Posts
    37,525
    Vote Rating
    872
    mitchellsimoens has a reputation beyond repute mitchellsimoens has a reputation beyond repute mitchellsimoens has a reputation beyond repute mitchellsimoens has a reputation beyond repute mitchellsimoens has a reputation beyond repute mitchellsimoens has a reputation beyond repute mitchellsimoens has a reputation beyond repute mitchellsimoens has a reputation beyond repute mitchellsimoens has a reputation beyond repute mitchellsimoens has a reputation beyond repute mitchellsimoens has a reputation beyond repute

      0  

    Default


    Was waiting for someone to do this. The problem I see is how does one know that there are selections in other pages?
    Mitchell Simoens @SenchaMitch
    Sencha Inc, Senior Forum Manager
    ________________
    Check out my GitHub, lots of nice things for Ext JS 4 and Sencha Touch 2
    https://github.com/mitchellsimoens

    Think my support is good? Get more personalized support via a support subscription. https://www.sencha.com/store/

    Need more help with your app? Hire Sencha Services services@sencha.com

    Want to learn Sencha Touch 2? Check out Sencha Touch in Action that is in print!

    When posting code, please use BBCode's CODE tags.

  3. #3
    Sencha User BulletzBill's Avatar
    Join Date
    Mar 2010
    Location
    New York
    Posts
    138
    Vote Rating
    0
    BulletzBill is on a distinguished road

      0  

    Default


    Quote Originally Posted by mitchellsimoens View Post
    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?

  4. #4
    Sencha - Senior Forum Manager mitchellsimoens's Avatar
    Join Date
    Mar 2007
    Location
    Gainesville, FL
    Posts
    37,525
    Vote Rating
    872
    mitchellsimoens has a reputation beyond repute mitchellsimoens has a reputation beyond repute mitchellsimoens has a reputation beyond repute mitchellsimoens has a reputation beyond repute mitchellsimoens has a reputation beyond repute mitchellsimoens has a reputation beyond repute mitchellsimoens has a reputation beyond repute mitchellsimoens has a reputation beyond repute mitchellsimoens has a reputation beyond repute mitchellsimoens has a reputation beyond repute mitchellsimoens has a reputation beyond repute

      0  

    Default


    Quote Originally Posted by BulletzBill View Post
    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.

    Quote Originally Posted by BulletzBill View Post
    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!
    Mitchell Simoens @SenchaMitch
    Sencha Inc, Senior Forum Manager
    ________________
    Check out my GitHub, lots of nice things for Ext JS 4 and Sencha Touch 2
    https://github.com/mitchellsimoens

    Think my support is good? Get more personalized support via a support subscription. https://www.sencha.com/store/

    Need more help with your app? Hire Sencha Services services@sencha.com

    Want to learn Sencha Touch 2? Check out Sencha Touch in Action that is in print!

    When posting code, please use BBCode's CODE tags.

  5. #5
    Sencha User
    Join Date
    Apr 2009
    Posts
    48
    Vote Rating
    0
    morfeusz is on a distinguished road

      0  

    Default


    Thank you for this plugin - very useful.

  6. #6
    Sencha User preaction's Avatar
    Join Date
    Sep 2011
    Posts
    22
    Vote Rating
    1
    preaction is on a distinguished road

      0  

    Default Add setSelectionPersistence method and fix a selection bug

    Add setSelectionPersistence method and fix a selection bug


    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.

    Code:
    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();
         }
     });
    Attached Files
    Last edited by preaction; 3 Jan 2012 at 10:28 AM. Reason: remove conditional call to selModel.select()

  7. #7
    Sencha User
    Join Date
    Apr 2011
    Posts
    56
    Vote Rating
    0
    billp is on a distinguished road

      0  

    Default


    I'm having an issue with the headerCheckbox not deselecing when paging or sorting.
    My quesiton is here:
    http://www.sencha.com/forum/showthre...stion&p=714398

    Any suggestions would be great. Thanks.

  8. #8
    Sencha User BulletzBill's Avatar
    Join Date
    Mar 2010
    Location
    New York
    Posts
    138
    Vote Rating
    0
    BulletzBill is on a distinguished road

      0  

    Default


    @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.

  9. #9
    Sencha User
    Join Date
    Apr 2011
    Posts
    56
    Vote Rating
    0
    billp is on a distinguished road

      0  

    Default


    Quote Originally Posted by BulletzBill View Post
    @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

  10. #10
    Sencha User
    Join Date
    Apr 2011
    Posts
    56
    Vote Rating
    0
    billp is on a distinguished road

      0  

    Default


    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