We launched new forums in March 2019—join us there. In a hurry for help with your website? Get Help Now!
    • 10378
    • 375 Posts
    Hi there,

    I'm struggling with this a few days now so I try to get help here. I'm not so into JavaScript - so probably there is a very simple solution to this problem.

    I have a grid component which I need to use multiple times on the same CMP. The grid utilizes a checkbox selection model extended with special methods provided by @theboxer (John Peca) to preserve selections across pagination.

    This is the grid definition (shortened a bit):

    GoodNewsResource.grid.CollectResources = function(config) {
        config = config || {};
        
        this.sm = new Ext.grid.CheckboxSelectionModel({
            listeners: {
                'rowselect': function(sm,rowIndex,record) {
                    this.rememberRow(record);
                },scope: this
                ,'rowdeselect': function(sm,rowIndex,record) {
                    this.forgetRow(record);
                },scope: this
            }
        });
            
    	Ext.applyIf(config,{
    		title: _('resources_active')
    		,id: 'goodnewsresource-'+config.baseParams.collectionInternalName+'-grid'
            ,url: GoodNewsResource.connector_url
    		,fields: [
    		    'id'
    		    ,'pagetitle'
    		...
    		...
    	});
    	GoodNewsResource.grid.CollectResources.superclass.constructor.call(this,config);
        this.store.on('load',this.restoreSelection,this);
        this.getView().on('refresh',this.refreshSelection,this);
    };
    Ext.extend(GoodNewsResource.grid.CollectResources,MODx.grid.Grid,{
        selectionRestoreFinished: false
        ,selectedRecords: []
        ,rememberRow: function(record) {
            if (this.selectedRecords.indexOf(record.id) == -1){
                this.selectedRecords.push(record.id);
            }
            console.log('Array selectedRecords: '+this.selectedRecords);
        }
        ,forgetRow: function(record) {
            this.selectedRecords.remove(record.id);
            console.log('Array selectedRecords: '+this.selectedRecords);
        }
        ,refreshSelection: function() {
            var rowsToSelect = [];
            Ext.each(this.selectedRecords,function(item){
                rowsToSelect.push(this.store.indexOfId(item));
            },this);
            this.getSelectionModel().selectRows(rowsToSelect);
        }
        ,restoreSelection: function() {
            // only execute on initial store load!
            if (!this.selectionRestoreFinished) {
                var collection = this.config.baseParams.collectionIds.split(',');
                console.log(this.config.baseParams.collectionInternalName+' from DB: '+this.config.baseParams.collectionIds);
    
                var rowIndex;
                for (var i=0; i<collection.length; ++i) {
                    rowIndex = this.store.find('id',collection[i]);
                    this.getSelectionModel().selectRow(rowIndex,true);
                }
    
                this.selectionRestoreFinished = true;
            }
        }
        ,getSelectedAsList: function(){
            return this.selectedRecords.join();
        }
    });
    Ext.reg('goodnewsresource-grid-collect-resources',GoodNewsResource.grid.CollectResources);
    



    This grid is dynamically embedded inside tab containers up to 3 times by it's x-type. Each time the grid is called it gets custom baseParams:

                ...
    
                cTabs.push({
                    id: 'goodnewsresource-collection1-tab'
                    ,autoHeight: true
                    ,title: _('goodnews.mailing_resource_collection')+config.record.collection1Name
                    ,layout: 'form'
                    ,anchor: '100%'
                    ,items: [{
                        html: '<p>'+_('goodnews.mailing_resource_collection_desc')+'</p>'
                        ,bodyCssClass: 'panel-desc'
                        ,border: false
                    },{
                        xtype: 'goodnewsresource-grid-collect-resources'
                        ,baseParams: {
                            action: 'mgr/collection/getResourceList'
                            ,parentIds: config.record.collection1Parents
                            ,collectionIds: config.record.collection1 || ''
                            ,collectionInternalName: 'collection1'
                        }
                    }]
                });
    
                ...
    
                cTabs.push({
                    id: 'goodnewsresource-collection2-tab'
                    ,autoHeight: true
                    ,title: _('goodnews.mailing_resource_collection')+config.record.collection2Name
                    ,layout: 'form'
                    ,anchor: '100%'
                    ,items: [{
                        html: '<p>'+_('goodnews.mailing_resource_collection_desc')+'</p>'
                        ,bodyCssClass: 'panel-desc'
                        ,border: false
                    },{
                        xtype: 'goodnewsresource-grid-collect-resources'
                        ,baseParams: {
                            action: 'mgr/collection/getResourceList'
                            ,parentIds: config.record.collection2Parents
                            ,collectionIds: config.record.collection2 || ''
                            ,collectionInternalName: 'collection2'
                        }
                    }]
                });
    
               ...
    


    The problem is, that the selectedRecords variable always holds the checkbox selections combined from all embedded grids, although each grid gets its own unique id.

    So calling:

            var rc1grid = Ext.getCmp('goodnewsresource-collection1-grid');
            var rc1IDs = '';
                rc1IDs = rc1grid.getSelectedAsList();
    


    and

            var rc2grid = Ext.getCmp('goodnewsresource-collection2-grid');
            var rc2IDs = '';
                rc2IDs = rc2grid.getSelectedAsList();
    


    both returns the selected records from collection1-grid and collection2-grid combined.

    What am I doing wrong here? Is this the complete wrong way of using defined components multiple times in same page?
    I'd be happy if somebody could push me in the right direction!

    This question has been answered by multiple community members. See the first response.

    [ed. note: gadgetto last edited this post 11 years, 10 months ago.]
      Freelancer @bitego http://www.bitego.com
      ---
      GoodNews - one of the most advanced and integrated Group Mailer premium add-ons for MODX Revolution!
      More infos here: http://www.bitego.com/extras/goodnews/
      • 29661
      • 116 Posts
      Hi,

      I would guess that the grid shares it's definition across all instances on one page, so it shares the variable for storing selected rows.

      If I am correct, than I would try following updates:

      ,rememberRow: function(record) {
              if (this.selectedRecords[this.config.id].indexOf(record.id) == -1){
                  this.selectedRecords[this.config.id].push(record.id);
              }
              console.log('Array selectedRecords: '+this.selectedRecords[this.config.id]);
          }
          ,forgetRow: function(record) {
              this.selectedRecords[this.config.id].remove(record.id);
              console.log('Array selectedRecords: '+this.selectedRecords[this.config.id]);
          }
          ,refreshSelection: function() {
              var rowsToSelect = [];
              Ext.each(this.selectedRecords[this.config.id],function(item){
                  rowsToSelect.push(this.store.indexOfId(item));
              },this);
              this.getSelectionModel().selectRows(rowsToSelect);
          }
          ,getSelectedAsList: function(){
              return this.selectedRecords[this.config.id].join();
          }


      Hope it will work, I did it on the fly, didn't test it smiley The thoughts behind are that you'll store the selections to the array based on your grid ID.
        --
        John
        @theboxer
        • 10378
        • 375 Posts
        Thanks @theboxer!
        Will try when I'm home! Looks plausible!

        I thought that if I give each grid a unique id the variables should also be handled as unique (like in a class model with its instances).
        I still have to lear a lot in JavaScript ...
          Freelancer @bitego http://www.bitego.com
          ---
          GoodNews - one of the most advanced and integrated Group Mailer premium add-ons for MODX Revolution!
          More infos here: http://www.bitego.com/extras/goodnews/
          • 10378
          • 375 Posts
          Implementing your hints - the grids now works as expected using the same definition across all instances on one page! Cool!

          I only had to remove this lines from the grid extension:

              ...
              selectionRestoreFinished: false
              ,selectedRecords: []
              ...
          


          and move the definition of this.key to the grid function:

          GoodNewsResource.grid.CollectResources = function(config) {
              config = config || {};
              
              this.key = config.baseParams.collectionInternalName;
              
              this.selectedRecords = {};
              this.selectedRecords[this.key] = [];
              
              this.selectionRestoreFinished = {};
              this.selectionRestoreFinished[this.key] = false;
              ...
          


          this is the working code:

              rememberRow: function(record) {
                  if (this.selectedRecords[this.key].indexOf(record.id) == -1){
                      this.selectedRecords[this.key].push(record.id);
                  }
                  console.info('Array selectedRecords['+this.key+']: '+this.selectedRecords[this.key]);
              }
              ,forgetRow: function(record) {
                  this.selectedRecords[this.key].remove(record.id);
                  console.info('Array selectedRecords['+this.key+']: '+this.selectedRecords[this.key]);
              }
              ,refreshSelection: function() {
                  var rowsToSelect = [];
                  Ext.each(this.selectedRecords[this.key],function(item){
                      rowsToSelect.push(this.store.indexOfId(item));
                  },this);
                  this.getSelectionModel().selectRows(rowsToSelect);
              }
              ,restoreSelection: function() {
                  // only execute on initial store load for each collection!
                  if (!this.selectionRestoreFinished[this.key]) {
                      var collection = this.config.baseParams.collectionIds.split(',');
                      console.info(this.key+' from DB: '+this.config.baseParams.collectionIds);
          
                      var rowIndex;
                      for (var i=0; i<collection.length; ++i) {
                          rowIndex = this.store.find('id',collection[i]);
                          this.getSelectionModel().selectRow(rowIndex,true);
                      }
                      this.refresh(); // needed because otherwise the selections in the second grid are only shown after manually refreshing!
                      this.selectionRestoreFinished[this.key] = true;
                  }
              }
              ,getSelectedAsList: function(){
                  return this.selectedRecords[this.key].join();
              }
          


          I'm nearly at the finish line! Except restoring the selections from the database isn't fully working as there are only those records reselected which are on the first page of the grid.

          This is the code which should restore the previously (correctly!) saved selections from database:
          (this.config.baseParams.collectionIds holds the comma separated list of ids which are restored from database)

              ...
              this.store.on('load',this.restoreSelection,this);
              this.getView().on('refresh',this.refreshSelection,this);
              ...
          


              ....
              ,restoreSelection: function() {
                  // only execute on initial store load for each collection!
                  if (!this.selectionRestoreFinished[this.key]) {
                      var collection = this.config.baseParams.collectionIds.split(',');
                      console.info(this.key+' from DB: '+this.config.baseParams.collectionIds);
          
                      var rowIndex;
                      for (var i=0; i<collection.length; ++i) {
                          rowIndex = this.store.find('id',collection[i]);
                          this.getSelectionModel().selectRow(rowIndex,true);
                      }
                      this.refresh(); // needed because otherwise the selections in the second grid are only shown after manually refreshing!
                      this.selectionRestoreFinished[this.key] = true;
                  }
              }
              ....
          


          Any idea how to achieve this?

          @theboxer:
          Btw, if I can get this to work, it could be a nice add-on to your tutorial in your blog: "saving and restoring selections to and from database"! [ed. note: gadgetto last edited this post 11 years, 10 months ago.]
            Freelancer @bitego http://www.bitego.com
            ---
            GoodNews - one of the most advanced and integrated Group Mailer premium add-ons for MODX Revolution!
            More infos here: http://www.bitego.com/extras/goodnews/
            • 9207 ☆ A M B ☆
            • 2,475 Posts
            I know you're not looking for negative Nancy replies, but this type of convoluted problem is so common with ExtJS that it's turned me off to ExtJS entirely. It is possible to build CMP's without ExtJS. I've outlined how to build CMP's without ExtJS using Repoman: https://github.com/craftsmancoding/repoman/wiki/Tutorial--Custom-Manager-Page
              • 10378
              • 375 Posts
              Quote from: Everettg_99 at Dec 01, 2014, 12:12 PM
              I know you're not looking for negative Nancy replies, but this type of convoluted problem is so common with ExtJS that it's turned me off to ExtJS entirely. It is possible to build CMP's without ExtJS. I've outlined how to build CMP's without ExtJS using Repoman: https://github.com/craftsmancoding/repoman/wiki/Tutorial--Custom-Manager-Page

              Hi, thanks for the hint to the tutorial! Will have a look at it.

              The problem above is not convoluted, I only needed to post a lot of code to explain the situation.
              IMHO: I also don't think that it will be less "complicated" to build CMPs with other JS Libraries. Each of them has its weaknesses too. And using a less "convoluted" JS framework will shift the problems to other areas...
                Freelancer @bitego http://www.bitego.com
                ---
                GoodNews - one of the most advanced and integrated Group Mailer premium add-ons for MODX Revolution!
                More infos here: http://www.bitego.com/extras/goodnews/
              • discuss.answer
                • 29661
                • 116 Posts
                Well, yeah, you can't restore it that way. Grid doesn't load all your records, that would make pagination useless smiley

                Why you don't fill with your IDs the selectedRecords variable and than probably you will need to call refreshSelection method.
                  --
                  John
                  @theboxer
                • discuss.answer
                  • 10378
                  • 375 Posts
                  I finally solved it!

                  I now use the rememberRow function to initially fill the selectedRecords array with the ids restored from database.

                  It was a bit tricky as I didn't know that an ext store holds the id values of its elements as integers... found out the hard way that you can't compare integers with strings in JavaScript. smiley

                  Thanks for your help!
                    Freelancer @bitego http://www.bitego.com
                    ---
                    GoodNews - one of the most advanced and integrated Group Mailer premium add-ons for MODX Revolution!
                    More infos here: http://www.bitego.com/extras/goodnews/
                    • 49570
                    • 1 Posts
                    Why you don't fill with your IDs the selectedRecords variable and than probably you will need to call refreshSelection method.
                      remo
                      • 10378
                      • 375 Posts
                      Quote from: djrizzz at Dec 20, 2014, 12:22 AM
                      Why you don't fill with your IDs the selectedRecords variable and than probably you will need to call refreshSelection method.

                      Hi djrizzz,

                      Using the rememberRow function to initially fill the selectedRecords array is a workaround. If you initially fill the selectedRecords array with ID values you need to typecast the whole array, as the ID values in an ext store are integer values. I tried to typecast the array but the didn't work as expected...

                      Here is the whole working code which handles the checkboxSelectionModel. If you find a more elegant way (without using other workarounds) I'd be very happy to implement it:

                      /**
                       * Grid to collect resource content
                       * 
                       * @class GoodNewsResource.grid.CollectResources
                       * @extends MODx.grid.Grid
                       * @param {Object} config An object of config properties
                       * @xtype goodnewsresource-grid-collect-resources
                       */
                      GoodNewsResource.grid.CollectResources = function(config) {
                          config = config || {};
                          
                          this.key = config.baseParams.collectionInternalName;
                          this.selectedRecords = [];
                          this.selectedRecords[this.key] = [];
                          this.selectionRestoreFinished = false;
                          
                          // Initially fill selectedRecords array with restored ids from database
                          // (converted to integer as the store holds its record ids as integers!)
                          var tmpArray = config.baseParams.collectionIds.split(',');
                          var len = tmpArray.length;
                          for (var i=0; i<len; ++i) {
                              var id = parseInt(tmpArray[i],10);
                              this.rememberRow(id);
                          }
                          
                          this.sm = new Ext.grid.CheckboxSelectionModel({
                              listeners: {
                                  'rowselect': function(sm,rowIndex,record) {
                                      this.rememberRow(record.id);
                                  },scope: this
                                  ,'rowdeselect': function(sm,rowIndex,record) {
                                      this.forgetRow(record.id);
                                  },scope: this
                              }
                          });
                              
                          this.exp = new Ext.grid.RowExpander({
                              tpl : new Ext.Template(
                                  '<p>{preview}</p>'
                              )
                          });
                      
                      	Ext.applyIf(config,{
                      		title: _('resources_active')
                      		,id: 'goodnewsresource-'+config.baseParams.collectionInternalName+'-grid'
                              ,url: GoodNewsResource.connector_url
                      		,fields: [
                      		    'id'
                      		    ,'pagetitle'
                      		    ,'preview'
                      		    ,'publishedon'
                      		    ,'createdon'
                      		    ,'parent'
                              ]
                              ,cls: 'main-wrapper'
                              ,remoteSort: true
                              ,sortBy: 'publishedon' // default sort field of grouped grid (this is not the grouping field!)
                              ,sortDir: 'DESC'       // (the grouping field is pre-sorted in getlist processor)
                      		,paging: true
                              ,emptyText: _('goodnews.mailing_rc_resources_none')
                              ,preventRender: true
                              ,autoExpandColumn: 'pagetitle'
                              ,sm: this.sm
                              ,plugins: [this.exp]
                              ,columns: [
                              this.sm
                              ,this.exp
                              ,{
                                  header: _('id')
                                  ,dataIndex: 'id'
                                  ,width: 40
                                  ,sortable: true
                              },{
                                  header: _('page_title')
                                  ,dataIndex: 'pagetitle'
                                  ,width: 260
                                  ,sortable: true
                              },{
                                  header: _('publishedon')
                                  ,dataIndex: 'publishedon'
                                  ,width: 90
                                  ,sortable: true
                              },{
                                  header: _('createdon')
                                  ,dataIndex: 'createdon'
                                  ,width: 90
                                  ,sortable: true
                              },{
                                  header: _('goodnews.mailing_rc_container')
                                  ,dataIndex: 'parent'
                                  ,width: 120
                                  ,sortable: true
                              }]
                              ,tbar:['->',{
                                  xtype: 'modx-combo'
                                  ,id: 'goodnewsresource-'+config.baseParams.collectionInternalName+'-parent-filter'
                                  ,emptyText: _('goodnews.mailing_rc_parent_filter')
                                  ,width: 300
                                  ,listWidth: 300
                                  ,displayField: 'container'
                                  ,valueField: 'id'
                                  ,value: 'all'
                                  ,store: new Ext.data.JsonStore({
                                      url: GoodNewsResource.connector_url
                                      ,baseParams: {
                                          action: 'mgr/collection/getParentList'
                                          ,addAllOption: true
                                          ,parentIds: config.baseParams.parentIds
                                      }
                                      ,fields: ['id','container']
                                      ,root: 'results'
                                  })
                                  ,listeners: {
                                      'select': {fn: this.filterByParent, scope: this}
                                  }
                              }]
                              ,listeners: {
                                  // little workaround as we cant differentiate if store is loaded for the first time or refreshed
                                  'click': {fn:this.makeDirty,scope:this}
                              }
                      	});
                      	GoodNewsResource.grid.CollectResources.superclass.constructor.call(this,config);
                          this.store.on('load',this.refreshSelection,this);
                      };
                      Ext.extend(GoodNewsResource.grid.CollectResources,MODx.grid.Grid,{
                          rememberRow: function(resourceid) {
                              if (this.selectedRecords[this.key].indexOf(resourceid) == -1){
                                  this.selectedRecords[this.key].push(resourceid);
                              }
                          }
                          ,forgetRow: function(resourceid) {
                              this.selectedRecords[this.key].remove(resourceid);
                          }
                          ,refreshSelection: function() {
                              var rowsToSelect = [];        
                              Ext.each(this.selectedRecords[this.key],function(item){
                                  rowsToSelect.push(this.store.indexOfId(item));
                              },this);       
                              Ext.getCmp(this.config.id).getSelectionModel().selectRows(rowsToSelect);
                              
                              // workaround:
                              // @todo: find fix for: checkboxes are only checked in first grid
                              if (!this.selectionRestoreFinished) {
                                  this.refresh(); 
                                  this.selectionRestoreFinished = true;
                              }
                          }
                          ,getSelectedAsList: function(){
                              return this.selectedRecords[this.key].join();
                          }
                          ,makeDirty: function() {
                              Ext.getCmp('goodnewsresource-'+this.config.baseParams.collectionInternalName).fireEvent('change');
                          }
                          ,filterByParent: function(combo) {
                              var s = this.getStore();
                              s.baseParams.parentfilter = combo.getValue();
                              this.getBottomToolbar().changePage(1);
                              this.refresh();
                          }
                      });
                      Ext.reg('goodnewsresource-grid-collect-resources',GoodNewsResource.grid.CollectResources);
                      
                        Freelancer @bitego http://www.bitego.com
                        ---
                        GoodNews - one of the most advanced and integrated Group Mailer premium add-ons for MODX Revolution!
                        More infos here: http://www.bitego.com/extras/goodnews/