1. #1
    Sencha Premium Member
    Join Date
    Jan 2010
    Location
    Vancouver, Canada
    Posts
    133
    Vote Rating
    10
    bogc will become famous soon enough

      1  

    Default GridPanel rowSpan and colSpan

    GridPanel rowSpan and colSpan


    I am asking again the question that was sort of anwered here but this time in the context of ExtJs 4.x.

    I noticed that now, the GridPanel gets rendered to a table element and through the tdAttr one can set the rowspan or colspan. I attempted to use rowspan but the cells that are supposed to be hidden are pushed to the right because they get rendered anyway.

    Below there is some code that I used, in case you want to plug the different pieces into your code and see what happens.

    My questions are these:

    1. Is there a way to suppress the rendering of a cell. If there was, then I could solve my problem by turning off the cells that are hidden

    2. Is this approach ok? I have the feeling that it is a brittle, in the sense that future changes in the table rendering routine could break it.

    3. How hard can it be to change the rendering code to take into account the rowspan/colspan attributes?

    4. Any suggestions? Any things to watch for or stay away from?

    Thank you

    gridpanel:

    Code:
    {
                  xtype: 'gridpanel',
                  id: 'testGridPanel',
                  title: 'My Grid Panel',
                  columnLines: false,
                  rowLines: false,
                  store: 'GridTestDataStore',
                  columns: [
                    {
                      xtype: 'gridcolumn',
                      renderer: function(value, metaData, record, rowIndex, colIndex, store, view) {
    
    
                        return value;
                      },
                      dataIndex: 'Column1',
                      text: 'Column 1'
                    },
                    {
                      xtype: 'gridcolumn',
                      renderer: function(value, metaData, record, rowIndex, colIndex, store, view) {
    
                        return value;
    
                      },
                      dataIndex: 'Column2',
                      text: 'Column 2'
                    },
                    {
                      xtype: 'gridcolumn',
                      renderer: function(value, metaData, record, rowIndex, colIndex, store, view) {
                        var tmp = "";
    
                        switch(rowIndex)
                        {
                          case 0:
                          metaData.tdAttr = 'rowspan="3"';
                          break;
                          //  case 1:
                          //    break;
                          //  case 2:
                          //    break;
                        }
                        return value;
                      },
                      dataIndex: 'Column3',
                      text: 'Column3'
                    },
                    {
                      xtype: 'gridcolumn',
                      text: 'Column4'
                    }
                  ],
                  viewConfig: {
    
                  },
                  listeners: {
                    afterrender: {
                      fn: me.onTestGridPanelAfterRender,
                      scope: me
                    }
                  }
                }
    Model:
    Code:
    Ext.define('MyApp.model.GridTestData', {
      extend: 'Ext.data.Model',
    
      fields: [
        {
          name: 'Column1',
          type: 'string'
        },
        {
          name: 'Column2',
          type: 'string'
        },
        {
          name: 'Column3',
          type: 'string'
        },
        {
          name: 'Column4',
          type: 'string'
        }
      ]
    });
    Store:
    Code:
    Ext.define('MyApp.store.GridTestDataStore', {
      extend: 'Ext.data.Store',
    
      requires: [
        'MyApp.model.GridTestData'
      ],
    
      constructor: function(cfg) {
        var me = this;
        cfg = cfg || {};
        me.callParent([Ext.apply({
          autoLoad: true,
          storeId: 'gridTestDataStore',
          model: 'MyApp.model.GridTestData',
          data: [
            [
              'This is a test of wit',
              'Column 12',
              'This another life test',
              'Column 14'
            ],
            [
              'Column 21',
              'Column 22',
              'This another life test',
              'Column 24'
            ],
            [
              'Column 31',
              'Column 32',
              'This another life test',
              'Column 34'
            ]
          ]
        }, cfg)]);
      }
    });
    Attached Images

  2. #2
    Sencha Premium Member
    Join Date
    Jan 2010
    Location
    Vancouver, Canada
    Posts
    133
    Vote Rating
    10
    bogc will become famous soon enough

      0  

    Default


    Ok, I answered myself #1 and #3. It is possible to do it by developing a feature and manipulating the default metaRowTpl to surround the <td></td> tag with a <tpl if=".."></tpl> tag that generates the <td></td> tag only as long as the cell is not part of a span, or if it is, it has to be the top-left corner.

    Your model (i.e. the store data) has to be enhanced in order to be able to query this information, i.e. to detect whether a cell is part of a span or not.

    The default template for generating the cells is this (http://docs.sencha.com/ext-js/4-0/so...TableChunker):

    Code:
            '<tr class="' + Ext.baseCSSPrefix + 'grid-row {[this.embedRowCls()]}" {[this.embedRowAttr()]}>',
                '<tpl for="columns">',
                    '<td class="{cls} ' + Ext.baseCSSPrefix + 'grid-cell ' + Ext.baseCSSPrefix + 'grid-cell-{columnId} {{id}-modified} {{id}-tdCls} {[this.firstOrLastCls(xindex, xcount)]}" {{id}-tdAttr}>',
                        '<div {unselectableAttr} class="' + Ext.baseCSSPrefix + 'grid-cell-inner {unselectableCls}" style="text-align: {align}; {{id}-style};">{{id}}</div>',
                    '</td>',
                '</tpl>',
            '</tr>'

    The mutateMetaRowTpl has to be overriden in order to insert the <tpl if=""></tpl>. I also discovered the hard way that you cannot simply concatenate the <tpl if=""> to an existing item in metaRowTpl. It needs to be standalone and to have its own item in the metaRowTpl array. The same is true for </tpl>.

    The test that gets included in the if statement depends on your own logic and how the information related to spans flows.

    I also overrode getAdditionalData and I added more data that I used in the <tpl if=""> test


    If you want to add functions and use them in tpl tests you have to override the getFragmentTpl function:

    Code:
      getFragmentTpl: function() {
        return {
            testSpanIndex: this.testSpanIndex
        };
      },
      
      
      testSpanIndex: function(value)
      {
        //debugger;
        return value === null || value === undefined || value == 0;
      }

  3. #3
    Sencha User
    Join Date
    Mar 2011
    Posts
    12
    Vote Rating
    0
    mprogr is on a distinguished road

      0  

    Default


    Hello Bogc,

    I couldn't understand your second post in this issue. Please post full example.

    MProgr

  4. #4
    Sencha Premium Member
    Join Date
    Jan 2010
    Location
    Vancouver, Canada
    Posts
    133
    Vote Rating
    10
    bogc will become famous soon enough

      0  

    Default


    Here is the feature code. Your data store has to support this feature by providing the data for those cells that are row/col spanned, esentially you need to provide RowSpan, ColSpan, RowSpanIndex and ColSpanIndex values for those columns that participate in a span.

    The RowSpanIndex and ColSpanIndex values were not necessarily needed, but because I favor speed I added them as helpers to support the rendering process. Basically, let's say you have a row and you want to create a column span that stretches from column 1 from column 3. Then, in the store, I add the following additional columns:
    column1ColSpan=3
    column1ColSpanIndex=0
    column2ColSpanIndex=1
    column3ColSpanIndex=2

    Then when the grid is rendered anything with the ColSpanIndex > 0 won't be rendered.

    If you have a span that stretches over multiple rows then you need to add the ***RowSpanIndex columns.

    Please note that my solution works for me and it was never designed as a global, generic solution that could be part of ExtJs. It is very likely that you need to adapt the code here to suit your needs. Also the code is very sensitive to ExtJs version/code changes. The mutateMetaRowTpl function might be broken in the next version...

    The code has been tested with 4.1.1 and I also assumed the grid doesn't have paging. If you want to support paging, the code is probably getting more complicated in order to handle those scenarios where the page break cuts through a span.

    Code:
    Ext.define('App.feature.RowColSpanFeature', {
      extend: 'Ext.grid.feature.Feature',
      alias: 'feature.rowcolspan',
    
      getAdditionalData: function (data, idx, record, orig) {
    
    
    
        var headerCt = this.view.headerCt,
                colspan = headerCt.getColumnCount(),
                fullWidth = headerCt.getFullWidth(),
                items = headerCt.query('gridcolumn'),
                itemsLn = items.length;
        //         
        //        debugger;
        //            
        //        if (idx >= 1)
        //        {
        //          id = items[3].id;
        //          delete orig[id];
        //        }
    
        for (var i = 0; i < itemsLn; i++) {
          var fieldName = items[i].dataIndex;
          var spanIndexFieldName = fieldName + 'RowSpanIndex';
          var spanIndex = record.get(spanIndexFieldName);
          if (spanIndex)
            orig[spanIndexFieldName] = spanIndex;
    
          spanIndexFieldName = fieldName + 'ColSpanIndex';
          spanIndex = record.get(spanIndexFieldName);
          if (spanIndex)
            orig[spanIndexFieldName] = spanIndex;
        }
    
        return orig;
      },
    
      //        '<tr class="' + Ext.baseCSSPrefix + 'grid-row {[this.embedRowCls()]}" {[this.embedRowAttr()]}>',
      //            '<tpl for="columns">',
      //                '<td class="{cls} ' + Ext.baseCSSPrefix + 'grid-cell ' + Ext.baseCSSPrefix + 'grid-cell-{columnId} {{id}-modified} {{id}-tdCls} {[this.firstOrLastCls(xindex, xcount)]}" {{id}-tdAttr}>',
      //                    '<div {unselectableAttr} class="' + Ext.baseCSSPrefix + 'grid-cell-inner {unselectableCls}" style="text-align: {align}; {{id}-style};">{{id}}</div>',
      //                '</td>',
      //            '</tpl>',
      //        '</tr>'
      //    
    
      mutateMetaRowTpl: function (metaRowTpl) {
        //debugger;
        metaRowTpl.push(null);
        metaRowTpl.push(null);
        metaRowTpl[3] = '<div {unselectableAttr} class="' + Ext.baseCSSPrefix + 'grid-cell-inner {unselectableCls}" style="{{id}-style};">{{id}}</div>'; //'{{id}}';
        metaRowTpl[8] = metaRowTpl[6];
        metaRowTpl[7] = metaRowTpl[5];
        metaRowTpl[5] = metaRowTpl[4];
        metaRowTpl[4] = metaRowTpl[3];
        metaRowTpl[3] = metaRowTpl[2];
    
        metaRowTpl[2] = '{% /* debugger */ ; out.push("<tpl if=\\\"this.testSpanIndex(values[\'" + values.dataIndex + "ColSpanIndex\']) && this.testSpanIndex(values[\'" + values.dataIndex + "RowSpanIndex\'])\\\">"); %}';
        metaRowTpl[6] = '{% out.push("</tpl>"); %}';
        //debugger;
    
      },
    
      getMetaRowTplFragments: function () {
        return {
          testSpanIndex: this.testSpanIndex
        };
      },
    
      getTableFragments: function () {
        return {
          testSpanIndex: this.testSpanIndex
        };
      },
    
      getFragmentTpl: function () {
        return {
          testSpanIndex: this.testSpanIndex
        };
      },
    
    
      testSpanIndex: function (value) {
    
        return value === null || value === undefined || value === 0;
      }
    
    });
    Here is the renderer that has to accompany the feature (it has to be set for each column of the grid):

    Code:
          function columnRenderer(value, metaData, record, rowIndex, colIndex, store, view)
          {
            var fieldName = view.panel.columns[colIndex].dataIndex;
                
            // Process the spans
            fieldValue = record.get(fieldName + 'RowSpan');
    
            if (fieldValue && fieldValue > 1)
              metaData.tdAttr += ' rowspan="' + fieldValue + '"';
    
            fieldValue = record.get(fieldName + 'ColSpan');
    
            if (fieldValue && fieldValue > 1)
              metaData.tdAttr += ' colspan="' + fieldValue + '"';
    
            return value;
          };

  5. #5
    Sencha User
    Join Date
    Mar 2011
    Posts
    12
    Vote Rating
    0
    mprogr is on a distinguished road

      0  

    Default


    Dear Bogc,

    Thank you so much for your fast reply. Your code is excellent working for me. But CellEditing plugin missing in my grid. And client side column sorting is missing. Can you solve my problem?
    Code:
    Ext.define('App.feature.RowColSpanFeature', {
      extend: 'Ext.grid.feature.Feature',
      alias: 'feature.rowcolspan',
    
    
      getAdditionalData: function (data, idx, record, orig) {
    
    
        var headerCt = this.view.headerCt,
                colspan = headerCt.getColumnCount(),
                fullWidth = headerCt.getFullWidth(),
                items = headerCt.query('gridcolumn'),
                itemsLn = items.length;
    
    
        for (var i = 0; i < itemsLn; i++) {
          var fieldName = items[i].dataIndex;
          var spanIndexFieldName = fieldName + 'RowSpanIndex';
          var spanIndex = record.get(spanIndexFieldName);
          if (spanIndex)
            orig[spanIndexFieldName] = spanIndex;
    
    
          spanIndexFieldName = fieldName + 'ColSpanIndex';
          spanIndex = record.get(spanIndexFieldName);
          if (spanIndex)
            orig[spanIndexFieldName] = spanIndex;
        }
    
    
        return orig;
      },
    
    
      mutateMetaRowTpl: function (metaRowTpl) {
        //debugger;
        metaRowTpl.push(null);
        metaRowTpl.push(null);
        metaRowTpl[3] = '<div {unselectableAttr} class="' + Ext.baseCSSPrefix + 'grid-cell-inner {unselectableCls}" style="{{id}-style};">{{id}}</div>'; //'{{id}}';
        metaRowTpl[8] = metaRowTpl[6];
        metaRowTpl[7] = metaRowTpl[5];
        metaRowTpl[5] = metaRowTpl[4];
        metaRowTpl[4] = metaRowTpl[3];
        metaRowTpl[3] = metaRowTpl[2];
    
    
        metaRowTpl[2] = '{% /* debugger */ ; out.push("<tpl if=\\\"this.testSpanIndex(values[\'" + values.dataIndex + "ColSpanIndex\']) && this.testSpanIndex(values[\'" + values.dataIndex + "RowSpanIndex\'])\\\">"); %}';
        metaRowTpl[6] = '{% out.push("</tpl>"); %}';
        //debugger;
    
    
      },
    
    
      getMetaRowTplFragments: function () {
        return {
          testSpanIndex: this.testSpanIndex
        };
      },
    
    
      getTableFragments: function () {
        return {
          testSpanIndex: this.testSpanIndex
        };
      },
    
    
      getFragmentTpl: function () {
        return {
          testSpanIndex: this.testSpanIndex
        };
      },
    
    
    
    
      testSpanIndex: function (value) {
    
    
        return value === null || value === undefined || value === 0;
      }
    
    
    });
    
    
    Ext.onReady(function(){
        function columnRenderer(value, metaData, record, rowIndex, colIndex, store, view){
            var fieldName = view.panel.columns[colIndex].dataIndex;
            
            // Process the spans
            fieldValue = record.get(fieldName + 'RowSpan');
            
            if (fieldValue && fieldValue > 1)
                metaData.tdAttr = ' rowspan="' + fieldValue + '"';
            
            fieldValue = record.get(fieldName + 'ColSpan');
            
            if (fieldValue && fieldValue > 1)
                metaData.tdAttr = ' colspan="' + fieldValue + '"';
            return value;
        };
        
        new Ext.Viewport({
            layout: 'fit',
            items: {
                xtype: 'grid',
                columnLines: true,
                features: [Ext.create('App.feature.RowColSpanFeature')],
                plugins: [Ext.create('Ext.grid.plugin.CellEditing', { clicksToEdit: 1})],
                store: new Ext.data.ArrayStore({
                    fields: [
                       {name: 'company'},
                       {name: 'price', type: 'float'},
                       {name: 'change', type: 'float'},
                       {name: 'pctChange', type: 'float'},
                       {name: 'lastChange', type: 'date', dateFormat: 'n/j h:ia'},
                       {name: 'industry'},
                       {name: 'industryRowSpan'},
                       {name: 'industryRowSpanIndex'}
                    ],
                    data: [
                        ['3m Co',71.72,0.02,0.03,'4/2 12:00am', 'Manufacturing',3,0],
                        ['Alcoa Inc',29.01,0.42,1.47,'4/1 12:00am', 'Manufacturing',1,1],
                        ['Altria Group Inc',83.81,0.28,0.34,'4/3 12:00am', 'Manufacturing',1,2],
                        ['American Express Company',52.55,0.01,0.02,'4/8 12:00am', 'Finance',1,0],
                        ['American International Group, Inc.',64.13,0.31,0.49,'4/1 12:00am', 'Services',1,0]
                    ],
                    sortInfo: {
                        field: 'industry',
                        direction: 'ASC'
                    }
                }),
                columns: [
                    {header: "Industry", width: 200, sortable: true, dataIndex: 'industry', renderer: columnRenderer},
                    {header: "Company", width: 300, sortable: true, dataIndex: 'company', editor: 'textfield'},
                    {header: "Price", width: 100, sortable: true, renderer: Ext.util.Format.usMoney, dataIndex: 'price', editor: 'numberfield'},
                    {header: "Change", width: 100, sortable: true, dataIndex: 'change', renderer: Ext.util.Format.usMoney},
                    {header: "Last Updated", width: 100, sortable: true, renderer: Ext.util.Format.dateRenderer('m/d/Y'), dataIndex: 'lastChange'}
                ]
            }
        });
    });
    Thank you.
    mprogr

  6. #6
    Sencha Premium Member
    Join Date
    Jan 2010
    Location
    Vancouver, Canada
    Posts
    133
    Vote Rating
    10
    bogc will become famous soon enough

      0  

    Default


    It is interesting what happens... it looks like the celledit plugin doesn't seem to detect properly the cell you clicked on for 2nd and 3rd rows under the Manufacturing industry. If you click on the price cell, for instance the one with the price $29.01, it will show the editor for the Alcoa Inc cell. If you click on the cell in the Change column ($0.42) it will show the editor in the Price column. The code probably needs to be fixed to account for the merged cells.

    I am afraid I can't help you here - I don't have the time to do it at this time. My grid was read-only. If I find the time I will post it though....

  7. #7
    Sencha User
    Join Date
    Mar 2014
    Posts
    2
    Vote Rating
    0
    roni irawan is on a distinguished road

      0  

    Default Merge Column in Grid Panel [EXTJS 3.4]

    Merge Column in Grid Panel [EXTJS 3.4]


    i'm using extjs 3.4 and i want to merge columns in my grid panel. how i can do that? here my code
    Code:
    var grid_nya = new Ext.grid.GridPanel({
            id: 'gPengirimanKartuRM', sm: cbGrid, store: ds_permintaan,        
            autoScroll: true, columnLines: true, height: 375, flex: 1,
            columns: [cbGrid,{
                header: 'No. RM',
                width: 100,
                dataIndex: '',
                sortable: true
            },
            {
                header: 'No. Registrasi',
                width: 100,
                dataIndex: '',
                sortable: true
            },
            {
                header: 'Nama Pasien',
                width: 150,
                dataIndex: '',
                sortable: true
            },{
                header: 'Status<br>Pasien',
                align: 'center',
                width: 75,
                dataIndex: '',
                sortable: true
            },{
                header: 'gridcolumn',
                dataIndex: '',
                text: 'String',
                columns: [
                    {
                        header: 'numbercolumn',
                        dataIndex: '',
                        text: 'Number'
                    },
                    {
                        header: 'datecolumn',
                        dataIndex: '',
                        text: 'Date'
                    }
                ]
            }],        
            bbar: paging
        });

Thread Participants: 2