How to display stacked header columns in Angular/JS2?

I have the following Grid app. I want to add stacked header columns. I don't see how to do it. I'm using the Angular version, but I would expect this to work in JS2 as well:

import { Grid, GroupSettingsModel } from '@syncfusion/ej2-grids';
import { apexData } from './datasource2';


// How to add these settings to the Grid below?
/*
let stackedHeaderColumns: Object[] = [
{
headerText: "group1",
column: "BAR_SEQUENCE, PRD_PRODUCT_ID"
},
{
headerText: "group2",
column: "BAR_MILL_ENTER_TIME"
}];
*/

let grid: Grid = new Grid({
dataSource: apexData,
allowGrouping: true,
groupSettings: {columns: ["FRP_ID"], showGroupedColumn: false, captionTemplate: '${field} - ${key} - ${count} items' },
columns: [
{ field: "BAR_SEQUENCE", type: "number", headerText: "Seq", allowFiltering: false },
{ field: "PRD_PRODUCT_ID", headerText: "PrdID", allowFiltering: false },
{ field: "BAR_MILL_ENTER_TIME", type: "date", headerText: "BAR_MILL_ENTER_TIME", allowFiltering: false },
{ field: "BAR_CHARGE_TIME", type: "date", headerText: "BAR_CHARGE_TIME", allowFiltering: false },
{ field: "FRP_ID", type: "number", headerText: "FRP_ID", allowFiltering: false },
{ field: "BAR_STATUS", headerText: "BAR_STATUS", allowFiltering: false },
{ field: "BAR_BILLET_ID", type: "number", headerText: "BAR_BILLET_ID", allowFiltering: false }]
});

grid.appendTo('#Grid');

10 Replies

TW Tony Waterman May 1, 2018 09:18 PM UTC

So I think I figured this out. The grid no longer has showStackedHeader or stackedHeaderRows properties. Instead of these the columns themselves are simply built in a list of lists like so:

import { Grid } from '@syncfusion/ej2-grids';
import { apexData } from './datasource2';

let grid: Grid = new Grid({
dataSource: apexData,
allowGrouping: true,
groupSettings: {columns: ["FRP_ID"], showGroupedColumn: false, captionTemplate: '${field} - ${key} - ${count} items' },
columns: [
{
headerText: "group1",
columns: [
{ field: "BAR_SEQUENCE", type: "number", headerText: "Seq", allowFiltering: false },
{ field: "PRD_PRODUCT_ID", headerText: "PrdID", allowFiltering: false }
]
},
{
headerText: "group2",
columns: [{ field: "BAR_MILL_ENTER_TIME", type: "date", headerText: "BAR_MILL_ENTER_TIME", allowFiltering: false }]
},
{ field: "BAR_CHARGE_TIME", type: "date", headerText: "BAR_CHARGE_TIME", allowFiltering: false },
{ field: "FRP_ID", type: "number", headerText: "FRP_ID", allowFiltering: false },
{ field: "BAR_STATUS", headerText: "BAR_STATUS", allowFiltering: false },
{ field: "BAR_BILLET_ID", type: "number", headerText: "BAR_BILLET_ID", allowFiltering: false }]
});

grid.appendTo('#Grid');


PS Pavithra Subramaniyam Syncfusion Team May 2, 2018 01:01 PM UTC

Hi Tony , 

 
Thanks for contacting Syncfusion support. 

 
We are happy to hear that you have found the solution. Please refer to the below Online Documentation link and sample link for your reference. 

 

 

 
Regards, 
Pavithra S. 



RE Remy December 23, 2019 03:59 AM UTC

I believe the header stacking feature has evolved over time. The structure used to look like this with the stacking information being controlled by the "showStackedHeader" and "stackedHeaderRows" properties in the typescript file :

HTML :
< e-columns>
< e-column field='engineSN' headerText='PW900' textAlign='center' [width]="90" [maxWidth]="90" [customAttributes]='{class: "class1"}'> < /e-column>
< e-column field='COLUMN1_REQUIRED' headerText='ASS' textAlign='center' [width]="61" [maxWidth]="61" [customAttributes]='{class: "class1"}'> < /e-column>
< e-column field='COLUMN2_REQUIRED' headerText='RIV' textAlign='center' [width]="61" [maxWidth]="61" [customAttributes]='{class: "class1"}'> < /e-column>
< e-column field='COLUMN3_REQUIRED' headerText='GRD' textAlign='center' [width]="61" [maxWidth]="61" [customAttributes]='{class: "class1"}'> < /e-column>
< e-column field='COLUMN4_REQUIRED' headerText='BAL' textAlign='center' [width]="61" [maxWidth]="61" [customAttributes]='{class: "class1"}'> < /e-column>
< e-column field='COLUMN5_REQUIRED' headerText='ASS' textAlign='center' [width]="61" [maxWidth]="61" [customAttributes]='{class: "class1"}'> < /e-column>
< e-column field='COLUMN6_REQUIRED' headerText='INTER' textAlign='center' [width]="61" [maxWidth]="61" [customAttributes]='{class: "class1"}'> < /e-column>
< e-column field='COLUMN7_REQUIRED' headerText='FINAL' textAlign='center' [width]="61" [maxWidth]="61" [customAttributes]='{class: "class1"}'> < /e-column>
< e-column field='COLUMN8_REQUIRED' headerText='ASS' textAlign='center' [width]="61" [maxWidth]="61" [customAttributes]='{class: "class1"}'> < /e-column>
< /e-columns>
TS :
this.stackedHeaderRows = [{
stackedHeaderColumns:
[
{ headerText: "SECTION 1", column: "COLUMN1_REQUIRED,COLUMN2_REQUIRED,COLUMN3_REQUIRED,COLUMN4_REQUIRED" },
{ headerText: "SECTION 2", column: "COLUMN5_REQUIRED,COLUMN6_REQUIRED,COLUMN7_REQUIRED,COLUMN8_REQUIRED" }
]
}];


What I liked about this structure is that I had more control over the data in the HTML portion of the code. I could do things like (example for one column) :
< e-column headerText='ASS' textAlign="Center" [customAttributes]='{class: "class1"}'>
< ng-template #template let-data>
< div>
< ejs-checkbox #checkbox [checked]='data.COLUMN1_REQUIRED'> < /ejs-checkbox>
< /div>
< /ng-template>
< /e-column>

Now it seems like a new structure is in place and it looks like this :

HTML :
< e-columns>
< e-column field='engineSN' headerText='PW900' textAlign="Center" [customAttributes]='{class:"class1"}' [width]="90" [maxWidth]="90"> < /e-column>
< e-column headerText='SECTION 1' textAlign="Center" [columns]='Section1Column' [customAttributes]='{class: "class1"}'> < /e-column>
< e-column headerText='SECTION 2' textAlign="Center" [columns]='Section2Column' [customAttributes]='{class: "class1", rowspan: 2}'> < /e-column>
< /e-columns>

TS:
this.Section1Column = [
{
field: 'HP TURBINE',
headerText: 'HP TURBINE',
textAlign: 'Center',
customAttributes: { class: 'middleHeaderFormattingWithLeftBorder' },
columns: [
{field: 'COLUMN1_REQUIRED', headerText: 'ASS', width: '60', textAlign: 'Center', maxWidth: 60, customAttributes: { class: 'class1' } },
{field: 'COLUMN2_REQUIRED', headerText: 'RIV', width: '60', textAlign: 'Center', maxWidth: 60, customAttributes: { class: 'class1' } },
{field: 'COLUMN3_REQUIRED', headerText: 'GRD', width: '60', textAlign: 'Center', maxWidth: 60, customAttributes: { class: 'class1' } },
{field: 'COLUMN4_REQUIRED', headerText: 'BAL', width: '60', textAlign: 'Center', maxWidth: 60, customAttributes: { class: 'class1' } }
]
},
{
field: 'HP ROTOR',
headerText: 'HP ROTOR',
textAlign: 'Center',
customAttributes: { class: 'middleHeaderFormattingWithLeftBorder' },
columns: [
{field: 'COLUMN5_REQUIRED', headerText: 'ASS', width: '60', textAlign: 'Center', maxWidth: 60, customAttributes: { class: 'class1' } },
{field: 'COLUMN6_REQUIRED', headerText: 'INT', width: '60', textAlign: 'Center', maxWidth: 60, customAttributes: { class: 'class1' } },
{field: 'COLUMN7_REQUIRED', headerText: 'FINAL', width: '60', textAlign: 'Center', maxWidth: 60, customAttributes: { class: 'class1' } }
]
}
];

this.Section2Column = [
{
field: '-',
headerText: '-',
textAlign: 'Center',
customAttributes: { class: 'middleHeaderFormattingHidden' },
columns: [
{field: 'COLUMN8_REQUIRED', headerText: 'ASS', width: '60', textAlign: 'Center', maxWidth: 60, customAttributes: { class: 'class1' } }
]
}
];

My questions (2) :
1. With this new structure, since the data is no longer controlled in the HTML portion of the code, what is the workaround to achieve the same result as above with the checkbox depending on the value (that previously was in the HTML)?
< e-column headerText='ASS' textAlign="Center" [customAttributes]='{class: "class1"}'>
< ng-template #template let-data>
< div>
< ejs-checkbox #checkbox [checked]='data.COLUMN1_REQUIRED'> < /ejs-checkbox>
< /div>
< /ng-template>
< /e-column>

2. Also, in the typescript, how can I select a class depending on the value of the data? Something to achieve the intent of the following, but obviously this code doesn't work :
this.Section2Column = [
{
field: '-',
headerText: '-',
textAlign: 'Center',
customAttributes: { class: 'middleHeaderFormattingHidden' },
columns: [
{field: 'COLUMN8_REQUIRED', headerText: 'ASS', width: '60', textAlign: 'Center', maxWidth: 60, customAttributes: { class: 'COLUMN8_REQUIRED' ? 'class1' : 'class2' } }
]
}
];


Thank you



TS Thavasianand Sankaranarayanan Syncfusion Team December 24, 2019 01:42 PM UTC

Hi Remy, 

Thanks for your update. 

Before proceeding your query we need the below details, so that we can provide a better solution as soon as possible. 

Query: With this new structure, since the data is no longer controlled in the HTML portion of the code, what is the workaround to achieve the same result as above with the checkbox depending on the value (that previously was in the HTML)? 
 
From this query, we are able to understand that you want to render column template in the Grid. Please confirm whether you want to render template in the normal columns(Html page) or stacked columns(TS page). 

Query: Also, in the typescript, how can I select a class depending on the value of the data? Something to achieve the intent of the following, but obviously this code doesn't work : 
 
We suspect that you want to add custom attributes for the column cell based on the RowData. Is this your requirement or do you have any other requirement. If so please explain more about your requirement. 
 
Regards, 
Thavasianand S. 



RE Remy December 26, 2019 01:27 AM UTC

Hi Thavasianand,

Thank you for the quick reply.

For query #1, I'd like to start by mentioning that I have 3 levels of stacked headers. I'd like to render the template of the stacked columns. The way I understand it is that there is no way to manage the 3 levels of headers AND the data itself in the HTML alone as it used to be without the use of the "columns" attribute as shown below. The "old" version of Syncfusion used to allow this as shown on this page : https://help.syncfusion.com/angular/grid/stackedheader. All the code was in the HTML and the "stackedHeaderRows" and "stackedHeaderColumns" parameters in the TypeScript allowed to determine which columns should be grouped together. With all the code being in the HTML, this allowed far better customization for the columns template. For example, if I wanted to customize my second column to display the data in an input field instead of plain text, to set the height of the input based on a variable, to set the "[disabled]" field based on variables, to limit the field to 2 characters, to force uppercase lettering upon typing and to perform actions on "keyup" and "blur" events, I could do something as the following :


Please correct me if I'm wrong, but from my understanding. with the newer version of Syncfusion, since the "ASS" column (under the "HP TURBINE" section) is a child of 2 other sets of columns (child of "HP TURBINE" that is a itself is a child of "HIGH PRESSURE ROTOR"), it is no longer declared in the HTML. It has to be declared in the Typescript where customization is limited to the "[columns]" parameters available. Here are screenshots of portions of my actual code :

HTML:



Typescript :



ACTUAL RESULT : 


How can I customize the "ASS" column as described (add function calls, customize style based on variables in the TS code, display in input instead of plan text, etc.) ? I am facing the same issue as the person in this thread : https://www.syncfusion.com/forums/141197/editable-checkbox-in-angular-ejs-grid-control. I have a column with boolean value that I want to display in checkboxes but I want the checkboxes to always be enabled. However, since my column is controlled in the TS and not in the HTML, the proposed solution doesn't apply for me.

For query #2, you are correct, I want to apply custom attributes based on the RowData. As you can see in the following screenshot, I am trying to apply a specific class for the first "ASS" column based on the data but that doesn't work :


Thank you,


RN Rahul Narayanasamy Syncfusion Team December 30, 2019 01:28 PM UTC

Hi Remy, 

Thanks for your update. 

We have validated your query with the provided information and we suspect that you want to render a checkbox in stacked header column and also you want to apply custom class to particular stacked column cell. Here, we have achieved your requirement by using template property and queryCellInfo event of the Grid. Find the below code snippets and sample for your reference. 

[code snippets] 
[app.component.ts] 
 ... 
export class AppComponent { 
  ... 
 
  public ngOnInit(): void { 
    ... 
 
    this.orderColumns = [ 
      { 
        ... 
      }, 
      { field: 'Verified', headerText: 'Verified', width: 150, template: '#template' } 
    ]; 
 
    ... 
    ]; 
  } 
  queryCellInfo(args: any): void { 
    if (args.column.field == 'Verified') { 
      var checkbox = new CheckBox({ 
      }); 
      if (args.data.Verified) { 
        checkbox.checked = true; 
        args.cell.classList.add('colorcheck') 
      } 
      checkbox.appendTo(args.cell.getElementsByTagName("input")[0]); 
    } 
  } 
} 
[index.html] 
<style> 
    .e-grid td.e-rowcell.e-templatecell.colorcheck { 
        background-color: yellow !important; 
    } 
</style> 


Please get back to us if you need further assistance. 

Regards, 
Rahul 



RE Remy December 31, 2019 07:57 AM UTC

Hello Rahul,

Thank you for your support. We are definitely on the right track with the proposed solution. Once we have a checkbox displayed (from the original boolean value), how can we add en event listener to monitor any future changes? If the checkbox is originally unchecked and I select it once the page has loaded, I want to update the backend database to reflect the new change. I tried adding the "onchange" event for the checkbox cells and "onblur" for an input, but for some reason it does not pass the desired variables.

queryCellInfoEventArgs(args: QueryCellInfoEventArgs) {

if (args.column.field !== 'enginePrefix' && args.column.field !== 'engineModel' && args.column.field !== 'actions') {

const checkbox = new CheckBox({});

if (args.data[args.column.field]) {
checkbox.checked = true;
}

checkbox.appendTo(args.cell.getElementsByTagName('input')[0]);

args.cell.getElementsByTagName('input')[0].onchange = function() {

// UPDATE BACKEND DATABASE...

};

}


if (args.column.field === 'engineModel') {

args.cell.getElementsByTagName('input')[0].value = args.data['engineModel'];

args.cell.getElementsByTagName('input')[0].onblur = function() {
// GIVES AN EMPTY STRING...
console.log(args.cell.getElementsByTagName('input')[0].value);
};

}

}


Thanks again,
Remy




TS Thavasianand Sankaranarayanan Syncfusion Team January 2, 2020 06:51 AM UTC

Hi Remy, 

Thanks for your update. 

We can bind the checkbox change event in the checkbox model definition itself. Based on the change event listener you can monitor the changes and update in the backend. Please refer the below code example and sample for more information. 

  checkboxChange (e: any) { 
 alert('isChecked:' +  e.checked); 
 console.log ('checkbox, row and column info'); 
console.log(e ); 
console.log(this.grid.getRowInfo(e.event.target)); 
  // UPDATE BACKEND DATABASE... 
  } 
  queryCellInfo(args: any): void { 
    if (args.column.field == 'Verified') { 
      const checkbox = new CheckBox({ 
        checked : args.data.Verified, 
        change: this.checkboxChange.bind(this) 
      }); 
      checkbox.appendTo(args.cell.getElementsByTagName("input")[0]); 
    } 
  } 



Please let us know, if you need further assistance. 

Regards, 
Thavasianand S. 



RE Remy January 2, 2020 07:43 AM UTC

Hi Thavasianand,


That worked like a charm. Thank you so much for your support. It is truly appreciated.

Regards,
Remy


BS Balaji Sekar Syncfusion Team January 3, 2020 07:23 AM UTC

 
Hi Remy, 
  
Thanks for the update. 
  
Please get back to us if you need any further assistance. 
  
Regards, 
Balaji Sekar. 


Loader.
Up arrow icon