Custom

Sometimes you want to create your own custom CellType. You can draw when the cell is in display mode, customize the editor when the cell is in editing mode, and handle mouse and keyboard interaction with the CellType itself. If the built-in CellTypes don't support the kind of functionality you are looking for, then creating a custom CellType would be the way to go. There are many different properties and functions that you can overwrite to get specific cells to fit your own requirements.

You can customize the CellType by inheriting Base, and implementing the required methods, such as paint, createEditorElement, getEditorValue, and so on. After that you can use your custom CellType. 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 { FivePointedStarCellType, FullNameCellType } 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 sheet = $event.spread.getSheet(0); sheet.suspendPaint(); sheet.setColumnWidth(0, 100); sheet.setColumnWidth(1, 170); const columnInfo = [ { name: "result", displayName: "Result", cellType: new FivePointedStarCellType(), size: 50 }, { name: "person", displayName: "Person", cellType: new FullNameCellType(), size: 170 } ]; const source = [ { result: true, person: {firstName:"LeBron",lastName:"James"}}, { result: false, person: { firstName: "Chris", lastName: "Bosh" } }, { result: true, person: { firstName: "Dwyane", lastName: "Wade" } }, { result: false, person: { firstName: "Mike", lastName: "Miller" } }, { result: true, person: { firstName: "Mike", lastName: "Miller" } }, { result: true, person: { firstName: "Udonis", lastName: "Haslem" } }, { result: true, person: { firstName: "Mario", lastName: "Chalmers" } }, { result: true, person: { firstName: "Joel", lastName: "Anthony" } }, { result: false, person: { firstName: "Shane", lastName: "Battier" } }, { result: false, person: { firstName: "Ray", lastName: "Allen" } }, { result: true, person: { firstName: "James", lastName: "Jones" } }, { result: false, person: { firstName: "Rashard", lastName: "Lewis" } }, { result: true, person: { firstName: "Norris", lastName: "Cole" } }, { result: true, person: { firstName: "Chris", lastName: "Andersen" } }, { result: false, person: { firstName: "Jarvis", lastName: "Varnado" } }, { result: true, person: { firstName: "Juwan", lastName: "Howard" } }, ]; sheet.setDataSource(source); sheet.bindColumns(columnInfo); sheet.resumePaint(); } } @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; //Custom Cell Type export class FivePointedStarCellType extends spreadNS.CellTypes.Base { constructor() { super(); this.size = 10; } paint (ctx, value, x, y, w, h, style, context) { if (!ctx) { return; } ctx.save(); // draw inside the cell's boundary ctx.rect(x, y, w, h); ctx.clip(); ctx.beginPath(); if (value) { ctx.fillStyle = "orange"; } else { ctx.fillStyle = "gray"; } var size = this.size; var dx = x + w / 2; var dy = y + h / 2; ctx.beginPath(); var dig = Math.PI / 5 * 4; ctx.moveTo(dx + Math.sin(0 * dig) * size, dy + Math.cos(0 * dig) * size); for (var i = 1; i < 5; i++) { ctx.lineTo(dx + Math.sin(i * dig) * size, dy + Math.cos(i * dig) * size); } ctx.closePath(); ctx.fill(); ctx.restore(); }; getHitInfo (x, y, cellStyle, cellRect, context) { var xm = cellRect.x + cellRect.width / 2, ym = cellRect.y + cellRect.height / 2, size = 10; var info = { x: x, y: y, row: context.row, col: context.col, cellRect: cellRect, sheetArea: context.sheetArea }; if (xm - size <= x && x <= xm + size && ym - size <= y && y <= ym + size) { info.isReservedLocation = true; } return info; }; processMouseUp (hitInfo) { var sheet = hitInfo.sheet; if (sheet && hitInfo.isReservedLocation) { var row = hitInfo.row, col = hitInfo.col, sheetArea = hitInfo.sheetArea; var newValue = !sheet.getValue(row, col, sheetArea); var spread = sheet.getParent(); spread.commandManager().execute({ cmd: "editCell", sheetName: sheet.name(), row: row, col: col, newValue: newValue }); return true; } return false; }; } export class FullNameCellType extends spreadNS.CellTypes.Base { isEditingValueChanged (oldValue, newValue) { if (newValue.firstName != oldValue.firstName || newValue.lastName != oldValue.lastName) { return true; } return false; }; isReservedKey (e) { //cell type handle tab key by itself return (e.keyCode === GC.Spread.Commands.Key.tab && !e.ctrlKey && !e.shiftKey && !e.altKey); }; paint (ctx, value, x, y, w, h, style, options) { if (value) { spreadNS.CellTypes.Base.prototype.paint.apply(this, [ctx, value.firstName + "." + value.lastName, x, y, w, h, style, options]); } }; updateEditor (editorContext, cellStyle, cellRect) { if (editorContext) { editorContext.style.width = cellRect.width; editorContext.style.height = 100; return { height: 100 }; } }; createEditorElement () { var div = document.createElement("div"); div.setAttribute("gcUIElement", "gcEditingInput"); div.style.backgroundColor = "white"; div.style.overflow = "hidden"; var span1 = document.createElement('span'); span1.style.display = "block"; var span2 = document.createElement("span"); span2.style.display = "block"; var input1 = document.createElement("input"); var input2 = document.createElement("input"); var type = document.createAttribute('type'); type.nodeValue = "text"; var clonedType = type.cloneNode(true); input1.setAttributeNode(type); input2.setAttributeNode(clonedType); div.appendChild(span1); div.appendChild(input1); div.appendChild(span2); div.appendChild(input2); return div; }; getEditorValue (editorContext) { if (editorContext && editorContext.children.length === 4) { var input1 = editorContext.children[1]; var firstName = input1.value; var input2 = editorContext.children[3]; var lastName = input2.value; return { firstName: firstName, lastName: lastName }; } }; setEditorValue (editorContext, value) { if (editorContext && editorContext.children.length === 4) { var span1 = editorContext.children[0]; span1.innerHTML = "First Name:"; var span2 = editorContext.children[2]; span2.innerHTML = "Last Name:"; if (value) { var input1 = editorContext.children[1]; input1.value = value.firstName; var input2 = editorContext.children[3]; input2.value = value.lastName; } } }; }
.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);