Custom Header

SpreadJS supports a custom CellType for row and column headers to assist with processing row and column data. This enables you to add functionality to the headers themselves, such as a button or checkbox to filter items that you can apply to multiple columns or rows.

To create a custom header cell type, create a custom header cell type based on the RowHeader or ColumnHeader, or another built-in CellType. For example: For your custom header cell type, provide the following methods to process mouse events. getHitInfo: Gets an object that contains hit information that will be provided to processMouse methods. processMouseDown: Provides the action to perform for mouse button down. processMouseMove: Provides the action to perform for mouse move. processMouseUp: Provides the action to perform for mouse button up. processMouseEnter: Provides the action to perform for mouse enter. processMouseLeave: Provides the action to perform for mouse leave. Here's a sample that uses the getHitInfo method. The ButtonClicked event is available for CheckBox, Button, and HyperLink on row and column header cells after overriding the getHitInfo method. You can process the ButtonClicked event by binding to Spread or Sheet. For example:
// @ts-ignore import { Component, NgModule, enableProdMode } from '@angular/core'; // @ts-ignore import { BrowserModule } from '@angular/platform-browser'; // @ts-ignore import { FormsModule } from '@angular/forms'; // @ts-ignore import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; // @ts-ignore import { SpreadSheetsModule } from '@mescius/spread-sheets-angular'; // @ts-ignore import GC from '@mescius/spread-sheets'; import { HighlightColumnItemsCellType, TopItemsCellType, HeaderCheckBoxCellType, SortHyperlinkCellType, HighlightRowItemsCellType } from "./app.data"; import './styles.css'; const spreadNS = GC.Spread.Sheets; @Component({ selector: 'app-component', templateUrl: 'src/app.component.html' }) export class AppComponent { spread: GC.Spread.Sheets.Workbook; hostStyle = { width: '100%', height: '100%', overflow: 'hidden', float: 'left' }; initSpread($event: any) { const spread = $event.spread; var rowCount = 50, columnCount = 20; var sheet = spread.getSheet(0); sheet.suspendPaint(); sheet.setRowCount(rowCount); sheet.setColumnCount(columnCount); sheet.setColumnWidth(0, 60, spreadNS.SheetArea.rowHeader); this.fillSampleData(sheet, new spreadNS.Range(0, 0, rowCount, columnCount)); sheet.setCellType(0, 1, new HighlightColumnItemsCellType(), spreadNS.SheetArea.colHeader); sheet.setCellType(0, 3, new TopItemsCellType(10), spreadNS.SheetArea.colHeader); sheet.setCellType(0, 5, new HeaderCheckBoxCellType(120), spreadNS.SheetArea.colHeader); sheet.setCellType(0, 7, new SortHyperlinkCellType(), spreadNS.SheetArea.colHeader); sheet.setCellType(9, 0, new HighlightRowItemsCellType(), spreadNS.SheetArea.rowHeader); sheet.resumePaint(); spread.bind(spreadNS.Events.ButtonClicked, (e: MouseEvent, args: any) => { var sheet = args.sheet, sheetArea = args.sheetArea, row = args.row, col = args.col, tag = sheet.getTag(row, col, sheetArea), cellType = sheet.getCellType(row, col, sheetArea); if (cellType instanceof TopItemsCellType) { var count = cellType.count; sheet.setTag(row, col, !tag, sheetArea); var range = new spreadNS.Range(-1, col, -1, 1); var rowFilter = new spreadNS.Filter.HideRowFilter(range); sheet.rowFilter(rowFilter); rowFilter.filterButtonVisible(false); var condition = new spreadNS.ConditionalFormatting.Condition(spreadNS.ConditionalFormatting.ConditionType.top10Condition, { type: spreadNS.ConditionalFormatting.Top10ConditionType.top, expected: count }); rowFilter.addFilterItem(col, condition); if (!tag) { rowFilter.filter(); } else { rowFilter.unfilter(); } } }); } fillSampleData(sheet: GC.Spread.Sheets.Worksheet, range: GC.Spread.Sheets.Range) { for (var i = 0; i < range.rowCount; i++) { for (var j = 0; j < range.colCount; j++) { sheet.setValue(range.row + i, range.col + j, Math.ceil(Math.random() * 300) - 100); } } } } @NgModule({ imports: [BrowserModule, SpreadSheetsModule, FormsModule], declarations: [AppComponent], exports: [AppComponent], bootstrap: [AppComponent] }) export class AppModule { } enableProdMode(); // Bootstrap application with hash style navigation and global services. platformBrowserDynamic().bootstrapModule(AppModule);
<!doctype html> <html style="height:100%;font-size:14px;"> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <link rel="stylesheet" type="text/css" href="$DEMOROOT$/en/angular/node_modules/@mescius/spread-sheets/styles/gc.spread.sheets.excel2013white.css"> <!-- Polyfills --> <script src="$DEMOROOT$/en/angular/node_modules/core-js/client/shim.min.js"></script> <script src="$DEMOROOT$/en/angular/node_modules/zone.js/fesm2015/zone.min.js"></script> <!-- SystemJS --> <script src="$DEMOROOT$/en/angular/node_modules/systemjs/dist/system.js"></script> <script src="systemjs.config.js"></script> <script> // workaround to load 'rxjs/operators' from the rxjs bundle System.import('rxjs').then(function (m) { System.import('@angular/compiler'); System.set(SystemJS.resolveSync('rxjs/operators'), System.newModule(m.operators)); System.import('$DEMOROOT$/en/lib/angular/license.ts'); System.import('./src/app.component'); }); </script> </head> <body> <app-component></app-component> </body> </html>
<div class="sample-tutorial"> <gc-spread-sheets [hostStyle]="hostStyle" (workbookInitialized)="initSpread($event)"> <gc-worksheet></gc-worksheet> </gc-spread-sheets> </div>
import GC from "@mescius/spread-sheets"; const spreadNS = GC.Spread.Sheets; // Define highlight cell types export class HighlightColumnItemsCellType extends spreadNS.CellTypes.ColumnHeader { constructor() { super(); this.RADIUS = 10; this.HIGHLIGHT_COLOR = "rgb(40, 171, 240)"; this.NORMAL_COLOR = "rgb(128, 255, 255)"; this.HIGHLIGHT_TIP = "Remove highlight."; this.NORMAL_TIP = "Highlight negative numbers."; spreadNS.CellTypes.ColumnHeader.apply(this); } paint (ctx, value, x, y, width, height, style, context) { spreadNS.CellTypes.ColumnHeader.prototype.paint.apply(this, arguments); var tag = context.sheet.getTag(context.row, context.col, context.sheetArea); var RADIUS = this.RADIUS; ctx.save(); ctx.beginPath(); ctx.arc(x + width - RADIUS, y + height / 2, RADIUS / 2, 0, Math.PI * 2); ctx.fillStyle = (tag && tag.color) || this.NORMAL_COLOR; ctx.fill(); ctx.restore(); }; getHitInfo (x, y, cellStyle, cellRect, context) { var RADIUS = this.RADIUS; var centerX = cellRect.x + cellRect.width - RADIUS, centerY = cellRect.y + cellRect.height / 2; var hitInfo = {x: x, y: y, row: context.row, col: context.col, cellRect: cellRect, sheetArea: context.sheetArea, sheet: context.sheet}; if (Math.abs(x - centerX) <= RADIUS && Math.abs(y - centerY) <= RADIUS) { hitInfo.isReservedLocation = true; } return hitInfo; }; processMouseDown (hitInfo) { this._hideTip(); }; processMouseMove (hitInfo) { if (hitInfo.isReservedLocation) { var sheet = hitInfo.sheet; var offset = { top:sheet.getParent().getHost().offsetTop, left:sheet.getParent().getHost().offsetLeft } var tag = sheet.getTag(hitInfo.row, hitInfo.col, hitInfo.sheetArea); this._showTip(offset.top + hitInfo.y + 20, offset.left + hitInfo.x + 20, (tag && tag.tip) || this.NORMAL_TIP); } else { this._hideTip(); } }; processMouseUp (hitInfo) { if (hitInfo.isReservedLocation) { var sheet = hitInfo.sheet; var tag = sheet.getTag(hitInfo.row, hitInfo.col, hitInfo.sheetArea); if (!tag) {//first time tag = {color: this.HIGHLIGHT_COLOR, tip: this.HIGHLIGHT_TIP}; sheet.setTag(hitInfo.row, hitInfo.col, tag, hitInfo.sheetArea); var style = new spreadNS.Style(); style.foreColor = "red"; var ranges = [new spreadNS.Range(-1, hitInfo.col, -1, 1)]; var rule = sheet.conditionalFormats.addCellValueRule(spreadNS.ConditionalFormatting.ComparisonOperators.lessThan, 0, 0, style, ranges); tag.rule = rule; tag.isHighlighed = true; } else if (!tag.isHighlighed) { tag.color = this.HIGHLIGHT_COLOR; tag.tip = this.HIGHLIGHT_TIP; sheet.conditionalFormats.addRule(tag.rule); tag.isHighlighed = true; } else { tag.color = this.NORMAL_COLOR; tag.tip = this.NORMAL_TIP; sheet.conditionalFormats.removeRule(tag.rule); tag.isHighlighed = false; } var offset = { top:sheet.getParent().getHost().offsetTop, left:sheet.getParent().getHost().offsetLeft } this._showTip(offset.top + hitInfo.y + 20, offset.left + hitInfo.x + 20, (tag && tag.tip) || this.NORMAL_TIP); } }; processMouseEnter (hitInfo) { }; processMouseLeave (hitInfo) { this._hideTip(); }; _showTip (top, left, tip) { if (!this._tipElement) { var span = document.createElement("span"); span.style.position = "absolute"; span.style.background = "#EEEEEE"; span.style.border = "1px solid black"; span.style.boxShadow = "5px 5px 5px rgba(0,0,0,0.4)"; span.style.fontSize = "14px"; document.body.insertBefore(span, null); this._tipElement = span; } var tipElement = this._tipElement; tipElement.textContent = tip; var spanStyle = tipElement.style; spanStyle.top = top + "px"; spanStyle.left = left + "px"; }; _hideTip () { if (this._tipElement) { document.body.removeChild(this._tipElement); this._tipElement = undefined; } }; } export class HighlightRowItemsCellType extends spreadNS.CellTypes.RowHeader { constructor() { super(); this.RADIUS = 10; this.HIGHLIGHT_COLOR = "rgb(40, 171, 240)"; this.NORMAL_COLOR = "rgb(128, 255, 255)"; this.HIGHLIGHT_TIP = "Remove highlight."; this.NORMAL_TIP = "Highlight negative numbers."; spreadNS.CellTypes.RowHeader.apply(this); } paint (ctx, value, x, y, width, height, style, context) { spreadNS.CellTypes.RowHeader.prototype.paint.apply(this, arguments); var tag = context.sheet.getTag(context.row, context.col, context.sheetArea); var RADIUS = this.RADIUS; ctx.save(); ctx.beginPath(); ctx.arc(x + width - RADIUS, y + height / 2, RADIUS / 2, 0, Math.PI * 2); ctx.fillStyle = (tag && tag.color) || this.NORMAL_COLOR; ctx.fill(); ctx.restore(); }; getHitInfo (x, y, cellStyle, cellRect, context) { var RADIUS = this.RADIUS; var centerX = cellRect.x + cellRect.width - RADIUS, centerY = cellRect.y + cellRect.height / 2; var hitInfo = {x: x, y: y, row: context.row, col: context.col, cellRect: cellRect, sheetArea: context.sheetArea, sheet: context.sheet}; if (Math.abs(x - centerX) <= RADIUS && Math.abs(y - centerY) <= RADIUS) { hitInfo.isReservedLocation = true; } return hitInfo; }; processMouseDown (hitInfo) { this._hideTip(); }; processMouseMove (hitInfo) { if (hitInfo.isReservedLocation) { var sheet = hitInfo.sheet; var offset = { top:sheet.getParent().getHost().offsetTop, left:sheet.getParent().getHost().offsetLeft } var tag = sheet.getTag(hitInfo.row, hitInfo.col, hitInfo.sheetArea); this._showTip(offset.top + hitInfo.y + 20, offset.left + hitInfo.x + 20, (tag && tag.tip) || this.NORMAL_TIP); } else { this._hideTip(); } }; processMouseUp (hitInfo) { if (hitInfo.isReservedLocation) { var sheet = hitInfo.sheet; var tag = sheet.getTag(hitInfo.row, hitInfo.col, hitInfo.sheetArea); if (!tag) {//first time tag = {color: this.HIGHLIGHT_COLOR, tip: this.HIGHLIGHT_TIP}; sheet.setTag(hitInfo.row, hitInfo.col, tag, hitInfo.sheetArea); var style = new spreadNS.Style(); style.foreColor = "red"; var ranges = [new spreadNS.Range(hitInfo.row, -1, 1, -1)]; var rule = sheet.conditionalFormats.addCellValueRule(spreadNS.ConditionalFormatting.ComparisonOperators.lessThan, 0, 0, style, ranges); tag.rule = rule; tag.isHighlighed = true; } else if (!tag.isHighlighed) { tag.color = this.HIGHLIGHT_COLOR; tag.tip = this.HIGHLIGHT_TIP; sheet.conditionalFormats.addRule(tag.rule); tag.isHighlighed = true; } else { tag.color = this.NORMAL_COLOR; tag.tip = this.NORMAL_TIP; sheet.conditionalFormats.removeRule(tag.rule); tag.isHighlighed = false; } var offset = { top:sheet.getParent().getHost().offsetTop, left:sheet.getParent().getHost().offsetLeft } this._showTip(offset.top + hitInfo.y + 20, offset.left + hitInfo.x + 20, (tag && tag.tip) || this.NORMAL_TIP); } }; processMouseEnter (hitInfo) { }; processMouseLeave (hitInfo) { this._hideTip(); }; _showTip (top, left, tip) { if (!this._tipElement) { var span = document.createElement("span"); span.style.position = "absolute"; span.style.background = "#EEEEEE"; span.style.border = "1px solid black"; span.style.boxShadow = "5px 5px 5px rgba(0,0,0,0.4)"; span.style.fontSize = "14px"; document.body.insertBefore(span, null); this._tipElement = span; } var tipElement = this._tipElement; tipElement.textContent = tip; var spanStyle = tipElement.style; spanStyle.top = top + "px"; spanStyle.left = left + "px"; }; _hideTip () { if (this._tipElement) { document.body.removeChild(this._tipElement); this._tipElement = undefined; } }; } // Define button cell type export class TopItemsCellType extends spreadNS.CellTypes.Button { constructor(count) { super(); spreadNS.CellTypes.Button.apply(this); count = +count || 10; if (!count || isNaN(count) || count < 0) { count = 10; } this.count = count; this.text("Top " + count); } getHitInfo (x, y, cellStyle, cellRect, context) { var self = this; var leftX = cellRect.x + self.marginLeft(), rightX = cellRect.x + cellRect.width - self.marginRight(), topY = cellRect.y + self.marginTop(), bottomY = cellRect.y + cellRect.height - self.marginBottom(); var info = {x: x, y: y, row: context.row, col: context.col, cellRect: cellRect, sheetArea: context.sheetArea, sheet: context.sheet}; if (leftX <= x && x <= rightX && topY <= y && y <= bottomY) { info.isReservedLocation = true; } return info; }; } // Define checkbox cell type var basePaint = spreadNS.CellTypes.CheckBox.prototype.paint; export class HeaderCheckBoxCellType extends spreadNS.CellTypes.CheckBox { constructor(value) { super(); spreadNS.CellTypes.CheckBox.apply(this); value = +value || 100; if (!value || isNaN(value) || value < 0) { value = 100; } this.value = value; this.caption(">" + value); } paint (ctx, value, x, y, width, height, style, context) { var tag = !!(context.sheet.getTag(context.row, context.col, context.sheetArea) || false); basePaint.apply(this, [ctx, tag, x, y, width, height, style, context]); }; getHitInfo (x, y, cellStyle, cellRect, context) { if (context) { return {x: x, y: y, row: context.row, col: context.col, cellRect: cellRect, sheetArea: context.sheetArea, isReservedLocation: true, sheet: context.sheet}; } return null; }; createEditorElement(){ return null; } processMouseDown (hitInfo) { this._isMouseDown = true; }; processMouseUp (hitInfo) { if (this._isMouseDown) { this.doFilter(hitInfo); this._isMouseDown = false; } return true; }; doFilter (hitInfo) { var value = this.value, sheet = hitInfo.sheet, row = hitInfo.row, col = hitInfo.col, sheetArea = hitInfo.sheetArea; var tag = sheet.getTag(row, col, sheetArea); sheet.setTag(row, col, !tag, sheetArea); var rowFilter = new spreadNS.Filter.HideRowFilter(new spreadNS.Range(-1, col, -1, 1)); sheet.rowFilter(rowFilter); rowFilter.filterButtonVisible(false); var condition = new spreadNS.ConditionalFormatting.Condition(spreadNS.ConditionalFormatting.ConditionType.numberCondition, { compareType: spreadNS.ConditionalFormatting.GeneralComparisonOperators.greaterThan, expected: value }); rowFilter.addFilterItem(col, condition); if (!tag) { rowFilter.filter(); } else { rowFilter.unfilter(); } }; } // Define hyperlink cell type export class SortHyperlinkCellType extends spreadNS.CellTypes.HyperLink { constructor() { super(); spreadNS.CellTypes.HyperLink.apply(this); this.text("Sort"); } getHitInfo (x, y, cellStyle, cellRect, context) { if (context) { return { x: x, y: y, row: context.row, col: context.col, cellRect: cellRect, cellStyle: cellStyle, sheetArea: context.sheetArea, isReservedLocation: true, sheet: context.sheet }; } return null; }; processMouseDown (hitInfo) { }; processMouseMove (hitInfo) { }; processMouseUp (hitInfo) { var sheet = hitInfo.sheet, sheetArea = hitInfo.sheetArea, row = hitInfo.row, col = hitInfo.col; var tag = !(sheet.getTag(row, col, sheetArea) || false); sheet.setTag(row, col, tag, sheetArea); sheet.sortRange(0, 0, sheet.getRowCount(), sheet.getColumnCount(), true, [{ index: col, ascending: tag }]); }; processMouseEnter (hitInfo) { }; processMouseLeave (hitInfo) { }; }
.sample-tutorial { position: relative; height: 100%; overflow: hidden; } .sample-spreadsheets{ height: 100%; } body { position: absolute; top: 0; bottom: 0; left: 0; right: 0; }
(function (global) { System.config({ transpiler: 'ts', typescriptOptions: { tsconfig: true }, meta: { 'typescript': { "exports": "ts" }, '*.css': { loader: 'css' } }, paths: { // paths serve as alias 'npm:': 'node_modules/' }, // map tells the System loader where to look for things map: { 'core-js': 'npm:core-js/client/shim.min.js', 'zone': 'npm:zone.js/fesm2015/zone.min.js', 'rxjs': 'npm:rxjs/dist/bundles/rxjs.umd.min.js', '@angular/core': 'npm:@angular/core/fesm2022', '@angular/common': 'npm:@angular/common/fesm2022/common.mjs', '@angular/compiler': 'npm:@angular/compiler/fesm2022/compiler.mjs', '@angular/platform-browser': 'npm:@angular/platform-browser/fesm2022/platform-browser.mjs', '@angular/platform-browser-dynamic': 'npm:@angular/platform-browser-dynamic/fesm2022/platform-browser-dynamic.mjs', '@angular/common/http': 'npm:@angular/common/fesm2022/http.mjs', '@angular/router': 'npm:@angular/router/fesm2022/router.mjs', '@angular/forms': 'npm:@angular/forms/fesm2022/forms.mjs', 'jszip': 'npm:jszip/dist/jszip.min.js', 'typescript': 'npm:typescript/lib/typescript.js', 'ts': './plugin.js', 'tslib':'npm:tslib/tslib.js', 'css': 'npm:systemjs-plugin-css/css.js', 'plugin-babel': 'npm:systemjs-plugin-babel/plugin-babel.js', 'systemjs-babel-build':'npm:systemjs-plugin-babel/systemjs-babel-browser.js', '@mescius/spread-sheets': 'npm:@mescius/spread-sheets/index.js', '@mescius/spread-sheets-angular': 'npm:@mescius/spread-sheets-angular/fesm2020/mescius-spread-sheets-angular.mjs', '@grapecity/jsob-test-dependency-package/react-components': 'npm:@grapecity/jsob-test-dependency-package/react-components/index.js' }, // packages tells the System loader how to load when no filename and/or no extension packages: { src: { defaultExtension: 'ts' }, rxjs: { defaultExtension: 'js' }, "node_modules": { defaultExtension: 'js' }, "node_modules/@angular": { defaultExtension: 'mjs' }, "@mescius/spread-sheets-angular": { defaultExtension: 'mjs' }, '@angular/core': { defaultExtension: 'mjs', main: 'core.mjs' } } }); })(this);