Custom Cell Content (Angular)

The FlexGrid control provides a powerful infrastructure for binding cells to data and formatting the cells using CSS, but in some cases that may not be enough. In those situations, use the formatItem event to customize the style or the content presented in each cell. The grid below uses formatItem to format cells with star ratings and sparklines.

Learn about FlexGrid | FlexGrid API Reference

This example uses Angular.

app.component.ts
index.html
app.component.html
styles.css
Copy to CodeMine
import 'bootstrap.css'; import '@mescius/wijmo.styles/wijmo.css'; import './styles.css'; // import '@angular/compiler'; import { Component, enableProdMode, ɵresolveComponentResources } from '@angular/core'; import { BrowserModule, bootstrapApplication } from '@angular/platform-browser'; import * as wjcGrid from '@mescius/wijmo.grid'; import { WjGridModule } from '@mescius/wijmo.angular2.grid'; // @Component({ standalone: true, imports: [WjGridModule, BrowserModule], selector: 'app-component', templateUrl: 'src/app.component.html', }) export class AppComponent { data: any[]; constructor() { this.data = this._getData(); } initializeGrid(flex: wjcGrid.FlexGrid) { flex.formatItem.addHandler((s: any, e: wjcGrid.FormatItemEventArgs) => { if (e.panel == s.cells) { var item = s.rows[e.row].dataItem; switch (s.columns[e.col].binding) { case 'rating': this._formatRatingCell(e.cell, item.rating); break; case 'attendance': this._formatAttendanceCell(e.cell, item.attendance); break; case 'revenue': this._formatRevenueCell(e.cell, item.revenue); break; } } }); } private _formatRatingCell(cell: HTMLElement, rating: number) { let html = '<div aria-label="rating:' + rating + ' stars">'; for (let i = 0; i < rating; i++) { html += '<span class="glyphicon glyphicon-star"></span>'; } html += '</div>'; cell.innerHTML = html; } private _formatAttendanceCell(cell: HTMLElement, data: number[]) { cell.innerHTML = this._getSparklines(data); } private _formatRevenueCell(cell: HTMLElement, data: number[]) { cell.innerHTML = this._getSparkbars(data); } // generate sparklines as SVG private _getSparklines(data: number[]) { let svg = '', min = Math.min.apply(Math, data), max = Math.max.apply(Math, data), x1 = 0, y1 = this._scaleY(data[0], min, max), x2, y2; for (let i = 1; i < data.length; i++) { x2 = Math.round((i / (data.length - 1)) * 100); y2 = this._scaleY(data[i], min, max); svg += '<line x1=' + x1 + '% y1=' + y1 + '% x2=' + x2 + '% y2=' + y2 + '% />'; x1 = x2; y1 = y2; } return this._wrapSvg(svg, 'sparklines'); } private _getSparkbars(data: number[]) { let svg = '', min = Math.min.apply(Math, data), max = Math.max.apply(Math, data), base = Math.min(max, Math.max(min, 0)), basey = this._scaleY(base, min, max), w = Math.round(100 / data.length) - 2, x, y; for (let i = 0; i < data.length; i++) { x = i * Math.round(100 / data.length) + 1; y = this._scaleY(data[i], min, max); svg += '<rect x=' + x + '% width=' + w + '% y=' + Math.min(y, basey) + '% height=' + Math.abs(y - basey) + '% />'; } svg += '<rect x=0% width=100% height=1 y=' + basey + '% opacity=.5 />'; return this._wrapSvg(svg, 'sparkbars'); } private _scaleY(value: number, min: number, max: number) { return 100 - Math.round(((value - min) / (max - min)) * 100); } private _wrapSvg(svg: string, title: string) { return ( '<div aria-label="' + title + '" ' + 'style="width:100%;height:100%;box-sizing:border-box;padding:4px">' + '<svg width="100%" height="100%" style="stroke:currentColor;"><g>' + svg + '</g></svg></div>' ); } private _getData() { return [ { rank: 1, title: 'The Wizard of Oz', rating: 4, attendance: this._getArr(40), revenue: this._getArr(20) }, { rank: 2, title: 'Citizen Kane', rating: 5, attendance: this._getArr(40), revenue: this._getArr(20) }, { rank: 3, title: 'The Godfather', rating: 5, attendance: this._getArr(40), revenue: this._getArr(20) }, { rank: 4, title: 'Metropolis', rating: 4, attendance: this._getArr(40), revenue: this._getArr(20) }, { rank: 5, title: "Singing' in the Rain", rating: 2, attendance: this._getArr(40), revenue: this._getArr(20) }, { rank: 6, title: 'Casablanca', rating: 3, attendance: this._getArr(40), revenue: this._getArr(20) }, { rank: 7, title: 'Alien', rating: 5, attendance: this._getArr(40), revenue: this._getArr(20) }, { rank: 8, title: 'Vertigo', rating: 2, attendance: this._getArr(40), revenue: this._getArr(20) }, { rank: 9, title: 'Gone with the Wind', rating: 4, attendance: this._getArr(40), revenue: this._getArr(20) }, { rank: 10, title: 'Chinatown', rating: 2, attendance: this._getArr(40), revenue: this._getArr(20) }, ]; } private _getArr(len: number) { let arr = []; for (let i = 0; i < len; i++) { arr.push(Math.round(Math.random() * 100)); } return arr; } } // // enableProdMode(); // Resolve resources (templateUrl, styleUrls etc), After resolution all URLs have been converted into `template` strings. ɵresolveComponentResources(fetch).then(() => { // Bootstrap application bootstrapApplication(AppComponent).catch((err) => console.error(err)); });
import 'bootstrap.css'; import '@mescius/wijmo.styles/wijmo.css'; import './styles.css'; // import '@angular/compiler'; import { Component, enableProdMode, ɵresolveComponentResources } from '@angular/core'; import { BrowserModule, bootstrapApplication } from '@angular/platform-browser'; import * as wjcGrid from '@mescius/wijmo.grid'; import { WjGridModule } from '@mescius/wijmo.angular2.grid'; // @Component({ standalone: true, imports: [WjGridModule, BrowserModule], selector: 'app-component', templateUrl: 'src/app.component.html', }) export class AppComponent { data: any[]; constructor() { this.data = this._getData(); } initializeGrid(flex: wjcGrid.FlexGrid) { flex.formatItem.addHandler((s: any, e: wjcGrid.FormatItemEventArgs) => { if (e.panel == s.cells) { var item = s.rows[e.row].dataItem; switch (s.columns[e.col].binding) { case 'rating': this._formatRatingCell(e.cell, item.rating); break; case 'attendance': this._formatAttendanceCell(e.cell, item.attendance); break; case 'revenue': this._formatRevenueCell(e.cell, item.revenue); break; } } }); } private _formatRatingCell(cell: HTMLElement, rating: number) { let html = '<div aria-label="rating:' + rating + ' stars">'; for (let i = 0; i < rating; i++) { html += '<span class="glyphicon glyphicon-star"></span>'; } html += '</div>'; cell.innerHTML = html; } private _formatAttendanceCell(cell: HTMLElement, data: number[]) { cell.innerHTML = this._getSparklines(data); } private _formatRevenueCell(cell: HTMLElement, data: number[]) { cell.innerHTML = this._getSparkbars(data); } // generate sparklines as SVG private _getSparklines(data: number[]) { let svg = '', min = Math.min.apply(Math, data), max = Math.max.apply(Math, data), x1 = 0, y1 = this._scaleY(data[0], min, max), x2, y2; for (let i = 1; i < data.length; i++) { x2 = Math.round((i / (data.length - 1)) * 100); y2 = this._scaleY(data[i], min, max); svg += '<line x1=' + x1 + '% y1=' + y1 + '% x2=' + x2 + '% y2=' + y2 + '% />'; x1 = x2; y1 = y2; } return this._wrapSvg(svg, 'sparklines'); } private _getSparkbars(data: number[]) { let svg = '', min = Math.min.apply(Math, data), max = Math.max.apply(Math, data), base = Math.min(max, Math.max(min, 0)), basey = this._scaleY(base, min, max), w = Math.round(100 / data.length) - 2, x, y; for (let i = 0; i < data.length; i++) { x = i * Math.round(100 / data.length) + 1; y = this._scaleY(data[i], min, max); svg += '<rect x=' + x + '% width=' + w + '% y=' + Math.min(y, basey) + '% height=' + Math.abs(y - basey) + '% />'; } svg += '<rect x=0% width=100% height=1 y=' + basey + '% opacity=.5 />'; return this._wrapSvg(svg, 'sparkbars'); } private _scaleY(value: number, min: number, max: number) { return 100 - Math.round(((value - min) / (max - min)) * 100); } private _wrapSvg(svg: string, title: string) { return ( '<div aria-label="' + title + '" ' + 'style="width:100%;height:100%;box-sizing:border-box;padding:4px">' + '<svg width="100%" height="100%" style="stroke:currentColor;"><g>' + svg + '</g></svg></div>' ); } private _getData() { return [ { rank: 1, title: 'The Wizard of Oz', rating: 4, attendance: this._getArr(40), revenue: this._getArr(20) }, { rank: 2, title: 'Citizen Kane', rating: 5, attendance: this._getArr(40), revenue: this._getArr(20) }, { rank: 3, title: 'The Godfather', rating: 5, attendance: this._getArr(40), revenue: this._getArr(20) }, { rank: 4, title: 'Metropolis', rating: 4, attendance: this._getArr(40), revenue: this._getArr(20) }, { rank: 5, title: "Singing' in the Rain", rating: 2, attendance: this._getArr(40), revenue: this._getArr(20) }, { rank: 6, title: 'Casablanca', rating: 3, attendance: this._getArr(40), revenue: this._getArr(20) }, { rank: 7, title: 'Alien', rating: 5, attendance: this._getArr(40), revenue: this._getArr(20) }, { rank: 8, title: 'Vertigo', rating: 2, attendance: this._getArr(40), revenue: this._getArr(20) }, { rank: 9, title: 'Gone with the Wind', rating: 4, attendance: this._getArr(40), revenue: this._getArr(20) }, { rank: 10, title: 'Chinatown', rating: 2, attendance: this._getArr(40), revenue: this._getArr(20) }, ]; } private _getArr(len: number) { let arr = []; for (let i = 0; i < len; i++) { arr.push(Math.round(Math.random() * 100)); } return arr; } } // // enableProdMode(); // Resolve resources (templateUrl, styleUrls etc), After resolution all URLs have been converted into `template` strings. ɵresolveComponentResources(fetch).then(() => { // Bootstrap application bootstrapApplication(AppComponent).catch((err) => console.error(err)); });
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <title>MESCIUS Wijmo FlexGrid Sparklines</title> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <!-- Polyfills --> <script src="node_modules/core-js/client/shim.min.js"></script> <script src="node_modules/zone.js/fesm2015/zone.min.js"></script> <!-- SystemJS --> <script src="https://cdnjs.cloudflare.com/ajax/libs/systemjs/0.21.5/system.src.js" integrity="sha512-skZbMyvYdNoZfLmiGn5ii6KmklM82rYX2uWctBhzaXPxJgiv4XBwJnFGr5k8s+6tE1pcR1nuTKghozJHyzMcoA==" crossorigin="anonymous"></script> <script src="systemjs.config.js"></script> <script> // workaround to load 'rxjs/operators' from the rxjs bundle System.import('rxjs').then(function (m) { System.set(SystemJS.resolveSync('rxjs/operators'), System.newModule(m.operators)); System.import('./src/app.component'); }); </script> </head> <body> <app-component></app-component> </body> </html>
<div class="container-fluid"> <wj-flex-grid #flex [isReadOnly]="true" [allowResizing]="'None'" [alternatingRowStep]="0" (initialized)="initializeGrid(flex)" [(itemsSource)]="data"> <wj-flex-grid-column [binding]="'rank'" [header]="'#'" [width]="50"></wj-flex-grid-column> <wj-flex-grid-column [binding]="'title'" [header]="'Title'" [width]="'2*'"></wj-flex-grid-column> <wj-flex-grid-column [binding]="'rating'" [header]="'Rating'" [align]="'center'" [cssClass]="'cell-rating'" [width]="'*'"></wj-flex-grid-column> <wj-flex-grid-column [binding]="'attendance'" [header]="'Attendance'" [cssClass]="'cell-attendance'" [width]="'2*'"></wj-flex-grid-column> <wj-flex-grid-column [binding]="'revenue'" [header]="'Revenue'" [cssClass]="'cell-revenue'" [width]="'2*'"></wj-flex-grid-column> </wj-flex-grid> </div>
.wj-flexgrid { max-height: 250px; margin: 10px 0; } .cell-rating { color: gold } .cell-attendance:not(.wj-state-selected):not(.wj-state-multi-selected), .cell-revenue:not(.wj-state-selected):not(.wj-state-multi-selected){ color: blue } body { margin-bottom: 20px; }
(function (global) { SystemJS.config({ transpiler: './plugin-typescript.js', typescriptOptions: { "target": "ES2022", "module": "system", "emitDecoratorMetadata": true, "experimentalDecorators": true, }, baseURL: 'node_modules/', meta: { 'typescript': { "exports": "ts" }, '*.css': { loader: 'systemjs-plugin-css' }, '*.mjs': { format: 'esm' }, }, paths: { // paths serve as alias 'npm:': '' }, packageConfigPaths: [ '/node_modules/*/package.json', "/node_modules/@angular/*/package.json", "/node_modules/@mescius/*/package.json" ], map: { 'core-js': 'https://cdn.jsdelivr.net/npm/core-js@2.6.12/client/shim.min.js', 'typescript': 'https://cdnjs.cloudflare.com/ajax/libs/typescript/5.2.2/typescript.min.js', "rxjs": "https://cdnjs.cloudflare.com/ajax/libs/rxjs/7.8.1/rxjs.umd.min.js", 'systemjs-plugin-css': 'https://cdn.jsdelivr.net/npm/systemjs-plugin-css@0.1.37/css.js', '@mescius/wijmo': 'npm:@mescius/wijmo/index.js', '@mescius/wijmo.input': 'npm:@mescius/wijmo.input/index.js', '@mescius/wijmo.styles': 'npm:@mescius/wijmo.styles', '@mescius/wijmo.cultures': 'npm:@mescius/wijmo.cultures', '@mescius/wijmo.chart': 'npm:@mescius/wijmo.chart/index.js', '@mescius/wijmo.chart.analytics': 'npm:@mescius/wijmo.chart.analytics/index.js', '@mescius/wijmo.chart.animation': 'npm:@mescius/wijmo.chart.animation/index.js', '@mescius/wijmo.chart.annotation': 'npm:@mescius/wijmo.chart.annotation/index.js', '@mescius/wijmo.chart.finance': 'npm:@mescius/wijmo.chart.finance/index.js', '@mescius/wijmo.chart.finance.analytics': 'npm:@mescius/wijmo.chart.finance.analytics/index.js', '@mescius/wijmo.chart.hierarchical': 'npm:@mescius/wijmo.chart.hierarchical/index.js', '@mescius/wijmo.chart.interaction': 'npm:@mescius/wijmo.chart.interaction/index.js', '@mescius/wijmo.chart.radar': 'npm:@mescius/wijmo.chart.radar/index.js', '@mescius/wijmo.chart.render': 'npm:@mescius/wijmo.chart.render/index.js', '@mescius/wijmo.chart.webgl': 'npm:@mescius/wijmo.chart.webgl/index.js', '@mescius/wijmo.chart.map': 'npm:@mescius/wijmo.chart.map/index.js', '@mescius/wijmo.gauge': 'npm:@mescius/wijmo.gauge/index.js', '@mescius/wijmo.grid': 'npm:@mescius/wijmo.grid/index.js', '@mescius/wijmo.grid.detail': 'npm:@mescius/wijmo.grid.detail/index.js', '@mescius/wijmo.grid.filter': 'npm:@mescius/wijmo.grid.filter/index.js', '@mescius/wijmo.grid.search': 'npm:@mescius/wijmo.grid.search/index.js', '@mescius/wijmo.grid.style': 'npm:@mescius/wijmo.grid.style/index.js', '@mescius/wijmo.grid.grouppanel': 'npm:@mescius/wijmo.grid.grouppanel/index.js', '@mescius/wijmo.grid.multirow': 'npm:@mescius/wijmo.grid.multirow/index.js', '@mescius/wijmo.grid.transposed': 'npm:@mescius/wijmo.grid.transposed/index.js', '@mescius/wijmo.grid.transposedmultirow': 'npm:@mescius/wijmo.grid.transposedmultirow/index.js', '@mescius/wijmo.grid.pdf': 'npm:@mescius/wijmo.grid.pdf/index.js', '@mescius/wijmo.grid.sheet': 'npm:@mescius/wijmo.grid.sheet/index.js', '@mescius/wijmo.grid.xlsx': 'npm:@mescius/wijmo.grid.xlsx/index.js', '@mescius/wijmo.grid.selector': 'npm:@mescius/wijmo.grid.selector/index.js', '@mescius/wijmo.grid.cellmaker': 'npm:@mescius/wijmo.grid.cellmaker/index.js', '@mescius/wijmo.nav': 'npm:@mescius/wijmo.nav/index.js', '@mescius/wijmo.odata': 'npm:@mescius/wijmo.odata/index.js', '@mescius/wijmo.olap': 'npm:@mescius/wijmo.olap/index.js', '@mescius/wijmo.rest': 'npm:@mescius/wijmo.rest/index.js', '@mescius/wijmo.pdf': 'npm:@mescius/wijmo.pdf/index.js', '@mescius/wijmo.pdf.security': 'npm:@mescius/wijmo.pdf.security/index.js', '@mescius/wijmo.viewer': 'npm:@mescius/wijmo.viewer/index.js', '@mescius/wijmo.xlsx': 'npm:@mescius/wijmo.xlsx/index.js', '@mescius/wijmo.undo': 'npm:@mescius/wijmo.undo/index.js', '@mescius/wijmo.interop.grid': 'npm:@mescius/wijmo.interop.grid/index.js', '@mescius/wijmo.touch': 'npm:@mescius/wijmo.touch/index.js', '@mescius/wijmo.cloud': 'npm:@mescius/wijmo.cloud/index.js', '@mescius/wijmo.barcode': 'npm:@mescius/wijmo.barcode/index.js', '@mescius/wijmo.barcode.common': 'npm:@mescius/wijmo.barcode.common/index.js', '@mescius/wijmo.barcode.composite': 'npm:@mescius/wijmo.barcode.composite/index.js', '@mescius/wijmo.barcode.specialized': 'npm:@mescius/wijmo.barcode.specialized/index.js', "@mescius/wijmo.angular2.chart.analytics": "npm:@mescius/wijmo.angular2.chart.analytics/index.js", "@mescius/wijmo.angular2.chart.animation": "npm:@mescius/wijmo.angular2.chart.animation/index.js", "@mescius/wijmo.angular2.chart.annotation": "npm:@mescius/wijmo.angular2.chart.annotation/index.js", "@mescius/wijmo.angular2.chart.finance.analytics": "npm:@mescius/wijmo.angular2.chart.finance.analytics/index.js", "@mescius/wijmo.angular2.chart.finance": "npm:@mescius/wijmo.angular2.chart.finance/index.js", "@mescius/wijmo.angular2.chart.hierarchical": "npm:@mescius/wijmo.angular2.chart.hierarchical/index.js", "@mescius/wijmo.angular2.chart.interaction": "npm:@mescius/wijmo.angular2.chart.interaction/index.js", "@mescius/wijmo.angular2.chart.radar": "npm:@mescius/wijmo.angular2.chart.radar/index.js", '@mescius/wijmo.angular2.chart.map': 'npm:@mescius/wijmo.angular2.chart.map/index.js', "@mescius/wijmo.angular2.chart": "npm:@mescius/wijmo.angular2.chart/index.js", "@mescius/wijmo.angular2.core": "npm:@mescius/wijmo.angular2.core/index.js", "@mescius/wijmo.angular2.gauge": "npm:@mescius/wijmo.angular2.gauge/index.js", "@mescius/wijmo.angular2.grid.detail": "npm:@mescius/wijmo.angular2.grid.detail/index.js", "@mescius/wijmo.angular2.grid.filter": "npm:@mescius/wijmo.angular2.grid.filter/index.js", "@mescius/wijmo.angular2.grid.grouppanel": "npm:@mescius/wijmo.angular2.grid.grouppanel/index.js", "@mescius/wijmo.angular2.grid.search": "npm:@mescius/wijmo.angular2.grid.search/index.js", "@mescius/wijmo.angular2.grid.multirow": "npm:@mescius/wijmo.angular2.grid.multirow/index.js", "@mescius/wijmo.angular2.grid.sheet": "npm:@mescius/wijmo.angular2.grid.sheet/index.js", '@mescius/wijmo.angular2.grid.transposed': 'npm:@mescius/wijmo.angular2.grid.transposed/index.js', '@mescius/wijmo.angular2.grid.transposedmultirow': 'npm:@mescius/wijmo.angular2.grid.transposedmultirow/index.js', "@mescius/wijmo.angular2.grid": "npm:@mescius/wijmo.angular2.grid/index.js", "@mescius/wijmo.angular2.input": "npm:@mescius/wijmo.angular2.input/index.js", "@mescius/wijmo.angular2.olap": "npm:@mescius/wijmo.angular2.olap/index.js", "@mescius/wijmo.angular2.viewer": "npm:@mescius/wijmo.angular2.viewer/index.js", "@mescius/wijmo.angular2.nav": "npm:@mescius/wijmo.angular2.nav/index.js", "@mescius/wijmo.angular2.directivebase": "npm:@mescius/wijmo.angular2.directivebase/index.js", '@mescius/wijmo.angular2.barcode.common': 'npm:@mescius/wijmo.angular2.barcode.common/index.js', '@mescius/wijmo.angular2.barcode.composite': 'npm:@mescius/wijmo.angular2.barcode.composite/index.js', '@mescius/wijmo.angular2.barcode.specialized': 'npm:@mescius/wijmo.angular2.barcode.specialized/index.js', 'bootstrap.css': 'npm:bootstrap/dist/css/bootstrap.min.css', 'jszip': 'https://cdnjs.cloudflare.com/ajax/libs/jszip/3.10.1/jszip.min.js', '@angular/core': 'npm:@angular/core/fesm2022/core.mjs', '@angular/core/primitives/signals': 'npm:@angular/core/fesm2022/primitives/signals.mjs', '@angular/core/primitives/event-dispatch': 'npm:@angular/core/fesm2022/primitives/event-dispatch.mjs', '@angular/common': 'npm:@angular/common/fesm2022/common.mjs', '@angular/common/http': 'npm:@angular/common/fesm2022/http.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/http': 'npm:@angular/http/fesm2022/http.mjs', '@angular/router': 'npm:@angular/router/fesm2022/router.mjs', '@angular/forms': 'npm:@angular/forms/fesm2022/forms.mjs', }, // packages tells the System loader how to load when no filename and/or no extension packages: { "./src": { defaultExtension: 'ts' }, "node_modules": { defaultExtension: 'js' }, wijmo: { defaultExtension: 'js', } } }); })(this);