JavaScript DataGrid (Angular)

FlexGrid is a fast, flexible and familiar JavaScript DataGrid. Our core grid module includes all of the most common features. We also include extensions and a flexible API to customize to grid even further.

This sample shows off many features of FlexGrid including sorting, grouping, searching, Excel-like filtering, DataMaps, custom CellTemplates, sparklines, rich editing, Excel / PDF export, validation, DetailRows, and more.

Try changing the number of data items and notice how the grid remains fast, even with large datasets. FlexGrid achieves this level of performance by automatically virtualizing rows and columns.

Learn about FlexGrid | FlexGrid Documentation | FlexGrid API Reference

This example uses Angular.

import 'bootstrap.css'; import '@mescius/wijmo.styles/wijmo.css'; import './styles.css'; // import '@angular/compiler'; import { Component, Inject, enableProdMode, ViewChild, OnDestroy, ɵresolveComponentResources } from '@angular/core'; import { BrowserModule, bootstrapApplication } from '@angular/platform-browser'; import { FormsModule } from '@angular/forms'; import '@mescius/wijmo.touch'; import * as wjcCore from '@mescius/wijmo'; import * as wjcGrid from '@mescius/wijmo.grid'; import { CellMaker, SparklineMarkers } from '@mescius/wijmo.grid.cellmaker'; import { WjGridModule } from '@mescius/wijmo.angular2.grid'; import { WjGridGrouppanelModule } from '@mescius/wijmo.angular2.grid.grouppanel'; import { WjGridFilterModule } from '@mescius/wijmo.angular2.grid.filter'; import { WjGridSearchModule } from '@mescius/wijmo.angular2.grid.search'; import { WjInputModule } from '@mescius/wijmo.angular2.input'; import { AppPipesModule } from './app.pipe'; import { KeyValue, Country, DataService } from './app.data'; import { IExcelExportContext, ExportService } from './app.export'; // @Component({ standalone: true, providers: [DataService, ExportService], imports: [WjInputModule, WjGridModule, WjGridGrouppanelModule, WjGridFilterModule, WjGridSearchModule, AppPipesModule, BrowserModule, FormsModule], selector: 'app-component', templateUrl: 'src/app.component.html' }) export class AppComponent implements OnDestroy { private _itemsCount: number = 500; private _lastId: number = this._itemsCount; private _dataSvc: DataService; private _exportSvc: ExportService; private _itemsSource: wjcCore.CollectionView; private _productMap: wjcGrid.DataMap<number, KeyValue>; private _countryMap: wjcGrid.DataMap<number, Country>; private _colorMap: wjcGrid.DataMap<number, KeyValue>; private _historyCellTemplate: wjcGrid.ICellTemplateFunction; private _ratingCellTemplate: wjcGrid.ICellTemplateFunction; private _excelExportContext: IExcelExportContext; // references FlexGrid named 'flex' in the view @ViewChild('flex', { static: true }) flex: wjcGrid.FlexGrid; get productMap(): wjcGrid.DataMap<number, KeyValue> { return this._productMap; } get countryMap(): wjcGrid.DataMap<number, Country> { return this._countryMap; } get colorMap(): wjcGrid.DataMap<number, KeyValue> { return this._colorMap; } get itemsSource(): wjcCore.CollectionView { return this._itemsSource; } get itemsCount(): number { return this._itemsCount; } set itemsCount(value: number) { if (this._itemsCount != value) { this._itemsCount = value; this._handleItemsCountChange(); } } get historyCellTemplate(): wjcGrid.ICellTemplateFunction { return this._historyCellTemplate; } get ratingCellTemplate(): wjcGrid.ICellTemplateFunction { return this._ratingCellTemplate; } get excelExportContext(): IExcelExportContext { return this._excelExportContext; } constructor(@Inject(DataService) dataSvc: DataService, @Inject(ExportService) exportSvc: ExportService) { this._dataSvc = dataSvc; this._exportSvc = exportSvc; // initializes items source this._itemsSource = this._createItemsSource(); // initializes data maps this._productMap = this._buildDataMap(this._dataSvc.getProducts()); this._countryMap = new wjcGrid.DataMap<number, Country>(this._dataSvc.getCountries(), 'id', 'name'); this._colorMap = this._buildDataMap(this._dataSvc.getColors()); // initializes cell templates this._historyCellTemplate = CellMaker.makeSparkline({ markers: SparklineMarkers.High | SparklineMarkers.Low, maxPoints: 25, label: 'price history', }); this._ratingCellTemplate = CellMaker.makeRating({ range: [1, 5], label: 'rating' }); // initializes export this._excelExportContext = { exporting: false, progress: 0, preparing: false }; } ngOnDestroy() { const ctx = this._excelExportContext; this._exportSvc.cancelExcelExport(ctx); } getCountry(item: any) { const country = this._countryMap.getDataItem(item.countryId); return country ? country : Country.NotFound; } getColor(item: any) { const color = this._colorMap.getDataItem(item.colorId); return color ? color : KeyValue.NotFound; } getChangeCls(value: any) { if (wjcCore.isNumber(value)) { if (value > 0) { return 'change-up'; } if (value < 0) { return 'change-down'; } } return ''; } exportToExcel() { const ctx = this._excelExportContext; if (!ctx.exporting) { this._exportSvc.startExcelExport(this.flex, ctx); } else { this._exportSvc.cancelExcelExport(ctx); } } exportToPdf() { this._exportSvc.exportToPdf(this.flex, { countryMap: this._countryMap, colorMap: this._colorMap, historyCellTemplate: this._historyCellTemplate }); } private _createItemsSource(): wjcCore.CollectionView { const data = this._dataSvc.getData(this._itemsCount); const view = new wjcCore.CollectionView(data, { getError: (item: any, prop: any) => { const displayName = this.flex.columns.getColumn(prop).header; return this._dataSvc.validate(item, prop, displayName); } }); view.collectionChanged.addHandler((s: wjcCore.CollectionView, e: wjcCore.NotifyCollectionChangedEventArgs) => { // initializes new added item with a history data if (e.action === wjcCore.NotifyCollectionChangedAction.Add) { e.item.history = this._dataSvc.getHistoryData(); e.item.id = this._lastId; this._lastId++; } }); return view; } private _disposeItemsSource(itemsSource: wjcCore.CollectionView): void { if (itemsSource) { itemsSource.collectionChanged.removeAllHandlers(); } } // build a data map from a string array using the indices as keys private _buildDataMap(items: string[]): wjcGrid.DataMap<number, KeyValue> { const map: KeyValue[] = []; for (let i = 0; i < items.length; i++) { map.push({ key: i, value: items[i] }); } return new wjcGrid.DataMap<number, KeyValue>(map, 'key', 'value'); } private _handleItemsCountChange() { this._disposeItemsSource(this._itemsSource); this._lastId = this._itemsCount; this._itemsSource = this._createItemsSource(); } } // // 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 Overview</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"> <div class="row"> <!-- search box --> <div class="toolbar-item col-sm-3 col-md-5"> <wj-flex-grid-search [placeholder]="'Search'" [grid]="flex" cssMatch=""> </wj-flex-grid-search> </div> <!-- data size --> <div class="toolbar-item col-sm-3 col-md-3"> <div class="input-group"> <span class="input-group-addon">Items:</span> <select class="form-control" [(ngModel)]="itemsCount"> <option value="5">5</option> <option value="50">50</option> <option value="500">500</option> <option value="5000">5,000</option> <option value="50000">50,000</option> <option value="100000">100,000</option> </select> </div> </div> <!-- export to Excel --> <div class="toolbar-item col-sm-3 col-md-2"> <button [disabled]="excelExportContext.preparing" (click)="exportToExcel()" class="btn btn-default btn-block"> {{excelExportContext.exporting ? ('Cancel (' + (excelExportContext.progress | percent) + ' done)') : 'Export To Excel'}} </button> </div> <!-- export to PDF --> <div class="toolbar-item col-sm-3 col-md-2"> <button (click)="exportToPdf()" class="btn btn-default btn-block">Export To PDF</button> </div> </div> <!-- group panel --> <wj-group-panel [grid]="flex" [placeholder]="'Drag columns here to create groups'"> </wj-group-panel> <!-- the grid --> <wj-flex-grid #flex [autoGenerateColumns]="false" [allowAddNew]="true" [allowDelete]="true" [allowPinning]="'SingleColumn'" [itemsSource]="itemsSource" [newRowAtTop]="true" [showMarquee]="true" [selectionMode]="'MultiRange'" [validateEdits]="false"> <wj-flex-grid-filter [filterColumns]="['id', 'date', 'time', 'countryId', 'productId', 'colorId', 'price', 'change', 'discount', 'rating', 'active']"> </wj-flex-grid-filter> <wj-flex-grid-column binding="id" header="ID" [width]="70" [isReadOnly]="true"></wj-flex-grid-column> <wj-flex-grid-column binding="date" header="Date" format="MMM d yyyy" [isRequired]="false" [width]="130" [editor]="theInputDate"> </wj-flex-grid-column> <wj-flex-grid-column binding="countryId" header="Country" [dataMap]="countryMap" [width]="145"> <ng-template wjFlexGridCellTemplate cellType="Cell" let-cell="cell"> <span class="flag-icon flag-icon-{{getCountry(cell.item).flag}}"></span> {{getCountry(cell.item).name}} </ng-template> </wj-flex-grid-column> <wj-flex-grid-column binding="price" header="Price" format="c" [isRequired]="false" [width]="100"> </wj-flex-grid-column> <wj-flex-grid-column binding="history" header="History" [width]="180" [align]="'center'" [allowSorting]="false" [cellTemplate]="historyCellTemplate"> </wj-flex-grid-column> <wj-flex-grid-column binding="change" header="Change" [align]="'right'" [width]="115"> <ng-template wjFlexGridCellTemplate [cellType]="'Cell'" let-cell="cell"> <span [ngClass]="getChangeCls(cell.item.change)"> {{cell.item.change | safeCurrency}} </span> </ng-template> </wj-flex-grid-column> <wj-flex-grid-column binding="rating" header="Rating" [width]="180" [align]="'center'" cssClass="cell-rating" [cellTemplate]="ratingCellTemplate"> </wj-flex-grid-column> <wj-flex-grid-column binding="time" header="Time" format="HH:mm" [isRequired]="false" [width]="95" [editor]="theInputTime"> </wj-flex-grid-column> <wj-flex-grid-column binding="colorId" header="Color" [dataMap]="colorMap" [width]="145"> <ng-template wjFlexGridCellTemplate [cellType]="'Cell'" let-cell="cell"> <span class="color-tile" [ngStyle]="{'background': getColor(cell.item).value}"></span> {{getColor(cell.item).value}} </ng-template> </wj-flex-grid-column> <wj-flex-grid-column binding="productId" header="Product" [dataMap]="productMap" [width]="145"> </wj-flex-grid-column> <wj-flex-grid-column binding="discount" header="Discount" format="p0" [width]="130"></wj-flex-grid-column> <wj-flex-grid-column binding="active" header="Active" [width]="100"></wj-flex-grid-column> </wj-flex-grid> <!-- custom editors --> <wj-input-date #theInputDate format="MM/dd/yyyy" [isRequired]="false"> </wj-input-date> <wj-input-time #theInputTime format="HH:mm" [isRequired]="false"> </wj-input-time> </div>
import { Injectable } from '@angular/core'; import * as wjcCore from '@mescius/wijmo'; import { IValidator, RequiredValidator, MinNumberValidator, MinDateValidator, MaxNumberValidator, MaxDateValidator } from './app.validation'; // export class KeyValue { key: number; value: string; static NotFound: KeyValue = { key: -1, value: '' }; } // export class Country { name: string; id: number; flag: string; static NotFound: Country = { id: -1, name: '', flag: '' }; } // @Injectable() export class DataService { private _products: string[] = ['Widget', 'Gadget', 'Doohickey']; private _colors: string[] = ['Black', 'White', 'Red', 'Green', 'Blue']; private _countries: Country[] = [ { id: 0, name: 'US', flag: 'us' }, { id: 1, name: 'Germany', flag: 'de' }, { id: 2, name: 'UK', flag: 'gb' }, { id: 3, name: 'Japan', flag: 'jp' }, { id: 4, name: 'Italy', flag: 'it' }, { id: 5, name: 'Greece', flag: 'gr' } ]; private _validationConfig: { [prop: string]: IValidator[] } = { 'date': [ new RequiredValidator(), new MinDateValidator(new Date('2000-01-01T00:00:00')), new MaxDateValidator(new Date('2100-01-01T00:00:00')) ], 'time': [ new RequiredValidator(), new MinDateValidator(new Date('2000-01-01T00:00:00')), new MaxDateValidator(new Date('2100-01-01T00:00:00')) ], 'productId': [ new RequiredValidator(), new MinNumberValidator(0, `{0} can't be less than {1} (${this._products[0]})`), new MaxNumberValidator( this._products.length - 1, `{0} can't be greater than {1} (${this._products[this._products.length - 1]})` ) ], 'countryId': [ new RequiredValidator(), new MinNumberValidator(0, `{0} can't be less than {1} (${this._countries[0].name})`), new MaxNumberValidator( this._countries.length - 1, `{0} can't be greater than {1} (${this._countries[this._countries.length - 1].name})` ) ], 'colorId': [ new RequiredValidator(), new MinNumberValidator(0, `{0} can't be less than {1} (${this._colors[0]})`), new MaxNumberValidator( this._colors.length - 1, `{0} can't be greater than {1} (${this._colors[this._colors.length - 1]})` ) ], 'price': [ new RequiredValidator(), new MinNumberValidator(0, `Price can't be a negative value`) ] }; getCountries(): Country[] { return this._countries; } getProducts(): string[] { return this._products; } getColors(): string[] { return this._colors; } getHistoryData(): number[] { return this._getRandomArray(25, 100); } getData(count: number): any[] { const data = []; const dt = new Date(); const year = dt.getFullYear(); const itemsCount = Math.max(count, 5); // add items for (let i = 0; i < itemsCount; i++) { const item = this._getItem(i, year); data.push(item); } // set invalid data to demonstrate errors visualization data[1].price = -2000; data[2].date = new Date('1970-01-01T00:00:00'); data[4].time = undefined; data[4].price = -1000; return data; } validate(item: any, prop: string, displayName: string): string { const validators: IValidator[] = this._validationConfig[prop]; if (wjcCore.isUndefined(validators)) { return ''; } const value = item[prop]; for (let i = 0; i < validators.length; i++) { const validationError = validators[i].validate(displayName, value); if (!wjcCore.isNullOrWhiteSpace(validationError)) { return validationError; } } } private _getItem(i: number, year: number): any { const date = new Date(year, i % 12, 25, i % 24, i % 60, i % 60); const countryIndex = this._getRandomIndex(this._countries) const productIndex = this._getRandomIndex(this._products); const colorIndex = this._getRandomIndex(this._colors); const item = { id: i, date: date, time: new Date(date.getTime() + Math.random() * 30 * (24 * 60 * 60 * 1000)), countryId: this._countries[countryIndex].id, productId: productIndex, colorId: colorIndex, price: wjcCore.toFixed(Math.random() * 10000 + 5000, 2, true), change: wjcCore.toFixed(Math.random() * 1000 - 500, 2, true), history: this.getHistoryData(), discount: wjcCore.toFixed(Math.random() / 4, 2, true), rating: this._getRating(), active: i % 4 == 0, size: Math.floor(100 + Math.random() * 900), weight: Math.floor(100 + Math.random() * 900), quantity: Math.floor(Math.random() * 10), description: "Across all our software products and services, our focus is on helping our customers achieve their goals. Our key principles – thoroughly understanding our customers' business objectives, maintaining a strong emphasis on quality, and adhering to the highest ethical standards – serve as the foundation for everything we do." }; return item; } private _getRating(): number { return Math.ceil(Math.random() * 5); } private _getRandomIndex(arr: any[]): number { return Math.floor(Math.random() * arr.length); } private _getRandomArray(len: number, maxValue: number): number[] { const arr: number[] = []; for (let i = 0; i < len; i++) { arr.push(Math.floor(Math.random() * maxValue)); } return arr; } }
@import 'https://cdnjs.cloudflare.com/ajax/libs/flag-icon-css/3.1.0/css/flag-icon.css'; body { font-size: 1.5em; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI Light", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif; } .toolbar-item { margin-bottom: 6px; } .wj-flexgridsearch { width: 100%; } .wj-flexgrid { height: 330px; } .wj-flexgrid .wj-cell { padding: 7px; border: none; } .wj-cell.wj-state-invalid:not(.wj-header)::after { top: -14px; border: 14px solid transparent; border-right-color: red; } .flag-icon { box-shadow: 1px 1px 4px rgba(0, 0, 0, 0.4); } .color-tile { display: inline-block; position: relative; width: 1em; height: 1em; border-radius: 50%; box-shadow: 1px 1px 4px rgba(0, 0, 0, 0.4); vertical-align: middle; } .change-up { color: darkgreen; } .change-up:after { content: '\25b2'; } .change-down { color: darkred; } .change-down:after { content: '\25bc'; } .cell-rating { font-size: 12px; } .wj-flexgrid .wj-detail { padding: 4px 16px; } .wj-detail h3 { margin: 10px 0; }
import { Injectable } from '@angular/core'; import * as wjcCore from '@mescius/wijmo'; import * as wjcGrid from '@mescius/wijmo.grid'; import * as wjcGridPdf from '@mescius/wijmo.grid.pdf'; import * as wjcGridXlsx from '@mescius/wijmo.grid.xlsx'; import * as wjcPdf from '@mescius/wijmo.pdf'; import * as wjcXlsx from '@mescius/wijmo.xlsx'; import { KeyValue, Country } from './app.data'; // const ExcelExportDocName = 'FlexGrid.xlsx'; const PdfExportDocName = 'FlexGrid.pdf'; const FakeColumn: wjcGrid.Column = new wjcGrid.Column(); const FakeRow: wjcGrid.Row = new wjcGrid.Row(); // class Fonts { static ZapfDingbatsSm = new wjcPdf.PdfFont('zapfdingbats', 8, 'normal', 'normal'); static ZapfDingbatsLg = new wjcPdf.PdfFont('zapfdingbats', 16, 'normal', 'normal'); } // export class IExcelExportContext { exporting: boolean; progress: number; preparing: boolean; } // @Injectable() export class ExportService { startExcelExport(flex: wjcGrid.FlexGrid, ctx: IExcelExportContext) { if (ctx.preparing || ctx.exporting) { return; } ctx.exporting = false; ctx.progress = 0; ctx.preparing = true; wjcGridXlsx.FlexGridXlsxConverter.saveAsync(flex, { includeColumnHeaders: true, includeStyles: false, formatItem: this._formatExcelItem.bind(this) }, ExcelExportDocName, () => { console.log('Export to Excel completed'); this._resetExcelContext(ctx); }, err => { console.error(`Export to Excel failed: ${err}`); this._resetExcelContext(ctx); }, prg => { if (ctx.preparing) { ctx.exporting = true; ctx.preparing = false; } ctx.progress = prg / 100.; }, true ); console.log('Export to Excel started'); } cancelExcelExport(ctx: IExcelExportContext) { wjcGridXlsx.FlexGridXlsxConverter.cancelAsync(() => { console.log('Export to Excel canceled'); this._resetExcelContext(ctx); }); } exportToPdf(flex: wjcGrid.FlexGrid, options: any) { wjcGridPdf.FlexGridPdfConverter.export(flex, PdfExportDocName, { maxPages: 100, exportMode: wjcGridPdf.ExportMode.All, scaleMode: wjcGridPdf.ScaleMode.ActualSize, documentOptions: { pageSettings: { layout: wjcPdf.PdfPageOrientation.Landscape }, header: { declarative: { text: '\t&[Page]\\&[Pages]' } }, footer: { declarative: { text: '\t&[Page]\\&[Pages]' } } }, styles: { cellStyle: { backgroundColor: '#ffffff', borderColor: '#c6c6c6' }, altCellStyle: { backgroundColor: '#f9f9f9' }, groupCellStyle: { backgroundColor: '#dddddd' }, headerCellStyle: { backgroundColor: '#eaeaea' }, // Highlight Invalid Cells errorCellStyle: { backgroundColor: 'rgba(255, 0, 0, 0.3)' } }, customCellContent: false, formatItem: (e: wjcGridPdf.PdfFormatItemEventArgs) => this._formatPdfItem(e, options) }); } private _formatExcelItem(e: wjcGridXlsx.XlsxFormatItemEventArgs) { const panel = e.panel; if (panel.cellType !== wjcGrid.CellType.Cell) { return; } // highlight invalid cells if (panel.grid._getError(panel, e.row, e.col)) { const fill = new wjcXlsx.WorkbookFill(); fill.color = '#ff0000'; e.xlsxCell.style.fill = fill; } } private _resetExcelContext(ctx: IExcelExportContext) { ctx.exporting = false; ctx.progress = 0; ctx.preparing = false; } private _formatPdfItem(e: wjcGridPdf.PdfFormatItemEventArgs, options: any) { const panel = e.panel; if (panel.cellType !== wjcGrid.CellType.Cell) { return; } switch (panel.columns[e.col].binding) { case 'countryId': this._formatPdfCountryCell(e, options.countryMap); break; case 'colorId': this._formatPdfColorCell(e, options.colorMap); break; case 'change': this._formatPdfChangeCell(e); break; case 'history': /*** Version #1: get grid cell produced before by a cell template ***/ // const cell = e.getFormattedCell(); // this._formatPdfHistoryCell(e, cell); /*** Version #2: create fake cell from a cell template ***/ const history = e.panel.getCellData(e.row, e.col, false); const cell = this._createCellFromCellTemplate(options.historyCellTemplate, history); this._formatPdfHistoryCell(e, cell); break; case 'rating': this._formatPdfRatingCell(e); break; } } private _formatPdfCountryCell(e: wjcGridPdf.PdfFormatItemEventArgs, countryMap: wjcGrid.DataMap<number, Country>) { e.drawBackground(e.style.backgroundColor); // check whether country exists const countryName = e.data; if (this._isCountryExist(countryName, countryMap)) { // bound rectangle of cell's content area const contentRect = e.contentRect; // draw flag image const image = e.canvas.openImage(`resources/${countryName}.png`); const imageTop = contentRect.top + (contentRect.height - image.height) / 2; e.canvas.drawImage(image, contentRect.left, imageTop); // draw country name e.canvas.drawText(countryName, contentRect.left + image.width + 3, e.textTop); } // cancel standard cell content drawing e.cancel = true; } private _formatPdfColorCell(e: wjcGridPdf.PdfFormatItemEventArgs, colorMap: wjcGrid.DataMap<number, KeyValue>) { e.drawBackground(e.style.backgroundColor); // check whether color exists const colorName = e.data; if (this._isColorExist(colorName, colorMap)) { // bound rectangle of cell's content area const contentRect = e.contentRect; // draw color indicator const imageHeight = Math.min(10, contentRect.height); const imageWidth = 1.33 * imageHeight; const imageTop = contentRect.top + (contentRect.height - imageHeight) / 2; e.canvas.paths .rect(contentRect.left, imageTop, imageWidth, imageHeight) .fillAndStroke(wjcCore.Color.fromString(colorName), wjcCore.Color.fromString('gray')); // draw color name e.canvas.drawText(colorName, contentRect.left + imageWidth + 3, e.textTop); } // cancel standard cell content drawing e.cancel = true; } private _formatPdfChangeCell(e: wjcGridPdf.PdfFormatItemEventArgs) { e.drawBackground(e.style.backgroundColor); // get change value and text const cellData = e.panel.getCellData(e.row, e.col, false); let change = 0; let changeText = ''; if (wjcCore.isNumber(cellData)) { change = cellData; changeText = wjcCore.Globalize.formatNumber(change, 'c'); } else if (!wjcCore.isUndefined(cellData) && cellData !== null) { changeText = wjcCore.changeType(cellData, wjcCore.DataType.String); } // determine whether change is positive or negative let changeIndicator = ''; let changeColor = e.style.color; if (change > 0) { changeIndicator = '\x73' // ▲ changeColor = 'darkgreen'; } else if (change < 0) { changeIndicator = '\x74' // ▼ changeColor = 'darkred'; } // draw change indicator let indent = 10; e.canvas.drawText(changeIndicator, e.contentRect.right - indent, e.contentRect.top + indent, { brush: changeColor, font: Fonts.ZapfDingbatsSm }); // draw change text indent += 3; e.canvas.drawText(changeText, e.contentRect.left, e.textTop, { brush: changeColor, align: wjcPdf.PdfTextHorizontalAlign.Right, width: e.contentRect.width - indent }); // cancel standard cell content drawing e.cancel = true; } private _formatPdfHistoryCell(e: wjcGridPdf.PdfFormatItemEventArgs, cell: HTMLElement) { e.drawBackground(e.style.backgroundColor); // draw history svg const svgUrl = this._getHistorySvgDataUrlFromCell(cell, e.clientRect.width, e.clientRect.height); if (svgUrl) { let cr = e.contentRect; e.canvas.drawSvg(svgUrl, cr.left + 2, cr.top + 2, { width: cr.width - 4, height: cr.height - 4 }); } // cancel standard cell content drawing e.cancel = true; } private _getHistorySvgDataUrlFromCell(cell: HTMLElement, width: number, height: number): string { let dataUrl: string = null; // extract SVG from provided cell const svg = cell.getElementsByTagName('svg')[0]; if (svg) { const clone = <any>svg.cloneNode(true); clone.setAttribute('version', '1.1'); clone.setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns', 'http://www.w3.org/2000/svg'); clone.style.overflow = 'visible'; clone.style.stroke = '#376092'; clone.style.fill = '#376092'; const s = document.createElement('style'); s.setAttribute('type', 'text/css'); s.innerHTML = `<![CDATA[ line { stroke-width: 2; } circle { stroke-width: 0; stroke-opacity: 0; } .wj-marker { fill: #d00000; opacity: 1; } ]]>`; const defs = document.createElement('defs'); defs.appendChild(s); clone.insertBefore(defs, clone.firstChild); const outer = document.createElement('div'); outer.appendChild(clone); dataUrl = 'data:image/svg+xml;base64,' + btoa(unescape(encodeURIComponent(outer.innerHTML))); } return dataUrl; } private _formatPdfRatingCell(e: wjcGridPdf.PdfFormatItemEventArgs) { e.drawBackground(e.style.backgroundColor); // check whether rating is defined let rating = wjcCore.changeType(e.data, wjcCore.DataType.Number); if (wjcCore.isInt(rating)) { const ratingIndicator = '\x48' // ★ const ratingNormalColor = wjcCore.Color.fromRgba(255, 165, 0, 1); // orange const ratingLightColor = wjcCore.Color.fromRgba(255, 165, 0, 0.2); // draw rating indicators const indent = 16; const count = 5; const width = count * indent; const y = e.clientRect.top + indent; let x = e.contentRect.left + (e.contentRect.width - width) / 2; rating = wjcCore.clamp(rating, 1, count); for (let i = 0; i < count; i++) { e.canvas.drawText(ratingIndicator, x, y, { brush: (i < rating) ? ratingNormalColor : ratingLightColor, font: Fonts.ZapfDingbatsLg, height: e.clientRect.height }); x += indent; } } // cancel standard cell content drawing e.cancel = true; } private _isCountryExist(countryName: any, countryMap: wjcGrid.DataMap<number, Country>) { const countryId = countryMap.getKeyValue(countryName); if (wjcCore.isUndefined(countryId) || countryId === null) { return false; } if (countryId === Country.NotFound.id) { return false; } return true; } private _isColorExist(colorName: any, colorMap: wjcGrid.DataMap<number, KeyValue>) { const colorId = colorMap.getKeyValue(colorName); if (wjcCore.isUndefined(colorId) || colorId === null) { return false; } if (colorId === KeyValue.NotFound.key) { return false; } return true; } private _createCellFromCellTemplate(cellTemplate: wjcGrid.ICellTemplateFunction, data: any) { const cell = document.createElement('div'); cellTemplate({ col: FakeColumn, row: FakeRow, value: data, item: null, text: null }, cell); return cell; } }
import * as wjcCore from '@mescius/wijmo'; export interface IValidator { validate(name: string, value: any): string; } export class RequiredValidator implements IValidator { validate(name: string, value: any): string { const message = name + ' is required'; if (wjcCore.isUndefined(value)) { return message; } const str = wjcCore.changeType(value, wjcCore.DataType.String); if (wjcCore.isNullOrWhiteSpace(str)) { return message; } return ''; } } export abstract class MinValueValidator<TValue> implements IValidator { readonly minValue: TValue; readonly message: string; readonly format: string; constructor(minValue: TValue, message: string = '{0} can\'t be less than {1}', format: string = null) { this.minValue = minValue; this.message = message; this.format = format; } validate(name: string, value: any): string { if (value < this.minValue) { return wjcCore.format(this.message, { 0: name, 1: this._formatValue(this.minValue) }); } return ''; } protected abstract _formatValue(value: TValue): string; } export abstract class MaxValueValidator<TValue> implements IValidator { readonly maxValue: TValue; readonly message: string; readonly format: string; constructor(maxValue: TValue, message: string = '{0} can\'t be greater than {1}', format: string = null) { this.maxValue = maxValue; this.message = message; this.format = format; } validate(name: string, value: any): string { if (value > this.maxValue) { return wjcCore.format(this.message, { 0: name, 1: this._formatValue(this.maxValue) }); } return ''; } protected abstract _formatValue(value: TValue): string; } export class MinNumberValidator extends MinValueValidator<number> { constructor(minValue: number, message: string = '{0} can\'t be less than {1}', format: string = 'n') { super(minValue, message, format); } protected _formatValue(value: number): string { return wjcCore.Globalize.formatNumber(value, this.format); } } export class MaxNumberValidator extends MaxValueValidator<number> { constructor(maxValue: number, message: string = '{0} can\'t be greater than {1}', format: string = 'n') { super(maxValue, message, format); } protected _formatValue(value: number): string { return wjcCore.Globalize.formatNumber(value, this.format); } } export class MinDateValidator extends MinValueValidator<Date> { constructor(minValue: Date, message: string = '{0} can\'t be less than {1}', format: string = 'MM/dd/yyyy') { super(minValue, message, format); } protected _formatValue(value: Date): string { return wjcCore.Globalize.formatDate(value, this.format); } } export class MaxDateValidator extends MaxValueValidator<Date> { constructor(maxValue: Date, message: string = '{0} can\'t be greater than {1}', format: string = 'MM/dd/yyyy') { super(maxValue, message, format); } protected _formatValue(value: Date): string { return wjcCore.Globalize.formatDate(value, this.format); } }
(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' } }, 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.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/common/http": "https://cdn.jsdelivr.net/npm/@angular/common@16.2.6/fesm2022/http.mjs", "@angular/core": "https://cdn.jsdelivr.net/npm/@angular/core@16.2.6/fesm2022/core.mjs", "@angular/platform-browser": "https://cdn.jsdelivr.net/npm/@angular/platform-browser@16.2.6/fesm2022/platform-browser.mjs", "@angular/common": "https://cdn.jsdelivr.net/npm/@angular/common@16.2.6/fesm2022/common.mjs", "@angular/compiler": "https://cdn.jsdelivr.net/npm/@angular/compiler@16.2.6/fesm2022/compiler.mjs", "@angular/forms": "https://cdn.jsdelivr.net/npm/@angular/forms@16.2.6/fesm2022/forms.mjs", "@angular/localize": "https://cdn.jsdelivr.net/npm/@angular/localize@16.2.6/fesm2022/localize.mjs", "@angular/platform-browser-dynamic": "https://cdn.jsdelivr.net/npm/@angular/platform-browser-dynamic@16.2.6/fesm2022/platform-browser-dynamic.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);