import 'bootstrap.css';
import '@grapecity/wijmo.styles/wijmo.css';
import './styles.css';
//
import '@angular/compiler';
import { Component, Inject, enableProdMode, NgModule, ViewChild, OnDestroy } from '@angular/core';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { BrowserModule } from '@angular/platform-browser';
import { FormsModule } from '@angular/forms';
import '@grapecity/wijmo.touch';
import * as wjcCore from '@grapecity/wijmo';
import * as wjcGrid from '@grapecity/wijmo.grid';
import { CellMaker, SparklineMarkers } from '@grapecity/wijmo.grid.cellmaker';
import { WjGridModule } from '@grapecity/wijmo.angular2.grid';
import { WjGridGrouppanelModule } from '@grapecity/wijmo.angular2.grid.grouppanel';
import { WjGridFilterModule } from '@grapecity/wijmo.angular2.grid.filter';
import { WjGridSearchModule } from '@grapecity/wijmo.angular2.grid.search';
import { WjInputModule } from '@grapecity/wijmo.angular2.input';
import { AppPipesModule } from './app.pipe';
import { KeyValue, Country, DataService } from './app.data';
import { IExcelExportContext, ExportService } from './app.export';
//
@Component({
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();
}
}
//
@NgModule({
imports: [
WjInputModule,
WjGridModule,
WjGridGrouppanelModule,
WjGridFilterModule,
WjGridSearchModule,
AppPipesModule,
BrowserModule,
FormsModule
],
declarations: [AppComponent],
providers: [DataService, ExportService],
bootstrap: [AppComponent]
})
export class AppModule {
}
//
enableProdMode();
// Bootstrap application with hash style navigation and global services.
platformBrowserDynamic().bootstrapModule(AppModule);
<!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="node_modules/systemjs/dist/system.src.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.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 '@grapecity/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 '@grapecity/wijmo';
import * as wjcGrid from '@grapecity/wijmo.grid';
import * as wjcGridPdf from '@grapecity/wijmo.grid.pdf';
import * as wjcGridXlsx from '@grapecity/wijmo.grid.xlsx';
import * as wjcPdf from '@grapecity/wijmo.pdf';
import * as wjcXlsx from '@grapecity/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 '@grapecity/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/@grapecity/*/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',
'@grapecity/wijmo': 'npm:@grapecity/wijmo/index.js',
'@grapecity/wijmo.input': 'npm:@grapecity/wijmo.input/index.js',
'@grapecity/wijmo.styles': 'npm:@grapecity/wijmo.styles',
'@grapecity/wijmo.cultures': 'npm:@grapecity/wijmo.cultures',
'@grapecity/wijmo.chart': 'npm:@grapecity/wijmo.chart/index.js',
'@grapecity/wijmo.chart.analytics': 'npm:@grapecity/wijmo.chart.analytics/index.js',
'@grapecity/wijmo.chart.animation': 'npm:@grapecity/wijmo.chart.animation/index.js',
'@grapecity/wijmo.chart.annotation': 'npm:@grapecity/wijmo.chart.annotation/index.js',
'@grapecity/wijmo.chart.finance': 'npm:@grapecity/wijmo.chart.finance/index.js',
'@grapecity/wijmo.chart.finance.analytics': 'npm:@grapecity/wijmo.chart.finance.analytics/index.js',
'@grapecity/wijmo.chart.hierarchical': 'npm:@grapecity/wijmo.chart.hierarchical/index.js',
'@grapecity/wijmo.chart.interaction': 'npm:@grapecity/wijmo.chart.interaction/index.js',
'@grapecity/wijmo.chart.radar': 'npm:@grapecity/wijmo.chart.radar/index.js',
'@grapecity/wijmo.chart.render': 'npm:@grapecity/wijmo.chart.render/index.js',
'@grapecity/wijmo.chart.webgl': 'npm:@grapecity/wijmo.chart.webgl/index.js',
'@grapecity/wijmo.chart.map': 'npm:@grapecity/wijmo.chart.map/index.js',
'@grapecity/wijmo.gauge': 'npm:@grapecity/wijmo.gauge/index.js',
'@grapecity/wijmo.grid': 'npm:@grapecity/wijmo.grid/index.js',
'@grapecity/wijmo.grid.detail': 'npm:@grapecity/wijmo.grid.detail/index.js',
'@grapecity/wijmo.grid.filter': 'npm:@grapecity/wijmo.grid.filter/index.js',
'@grapecity/wijmo.grid.search': 'npm:@grapecity/wijmo.grid.search/index.js',
'@grapecity/wijmo.grid.grouppanel': 'npm:@grapecity/wijmo.grid.grouppanel/index.js',
'@grapecity/wijmo.grid.multirow': 'npm:@grapecity/wijmo.grid.multirow/index.js',
'@grapecity/wijmo.grid.transposed': 'npm:@grapecity/wijmo.grid.transposed/index.js',
'@grapecity/wijmo.grid.transposedmultirow': 'npm:@grapecity/wijmo.grid.transposedmultirow/index.js',
'@grapecity/wijmo.grid.pdf': 'npm:@grapecity/wijmo.grid.pdf/index.js',
'@grapecity/wijmo.grid.sheet': 'npm:@grapecity/wijmo.grid.sheet/index.js',
'@grapecity/wijmo.grid.xlsx': 'npm:@grapecity/wijmo.grid.xlsx/index.js',
'@grapecity/wijmo.grid.selector': 'npm:@grapecity/wijmo.grid.selector/index.js',
'@grapecity/wijmo.grid.cellmaker': 'npm:@grapecity/wijmo.grid.cellmaker/index.js',
'@grapecity/wijmo.nav': 'npm:@grapecity/wijmo.nav/index.js',
'@grapecity/wijmo.odata': 'npm:@grapecity/wijmo.odata/index.js',
'@grapecity/wijmo.olap': 'npm:@grapecity/wijmo.olap/index.js',
'@grapecity/wijmo.rest': 'npm:@grapecity/wijmo.rest/index.js',
'@grapecity/wijmo.pdf': 'npm:@grapecity/wijmo.pdf/index.js',
'@grapecity/wijmo.pdf.security': 'npm:@grapecity/wijmo.pdf.security/index.js',
'@grapecity/wijmo.viewer': 'npm:@grapecity/wijmo.viewer/index.js',
'@grapecity/wijmo.xlsx': 'npm:@grapecity/wijmo.xlsx/index.js',
'@grapecity/wijmo.undo': 'npm:@grapecity/wijmo.undo/index.js',
'@grapecity/wijmo.interop.grid': 'npm:@grapecity/wijmo.interop.grid/index.js',
'@grapecity/wijmo.touch': 'npm:@grapecity/wijmo.touch/index.js',
'@grapecity/wijmo.cloud': 'npm:@grapecity/wijmo.cloud/index.js',
'@grapecity/wijmo.barcode': 'npm:@grapecity/wijmo.barcode/index.js',
'@grapecity/wijmo.barcode.common': 'npm:@grapecity/wijmo.barcode.common/index.js',
'@grapecity/wijmo.barcode.composite': 'npm:@grapecity/wijmo.barcode.composite/index.js',
'@grapecity/wijmo.barcode.specialized': 'npm:@grapecity/wijmo.barcode.specialized/index.js',
"@grapecity/wijmo.angular2.chart.analytics": "npm:@grapecity/wijmo.angular2.chart.analytics/index.js",
"@grapecity/wijmo.angular2.chart.animation": "npm:@grapecity/wijmo.angular2.chart.animation/index.js",
"@grapecity/wijmo.angular2.chart.annotation": "npm:@grapecity/wijmo.angular2.chart.annotation/index.js",
"@grapecity/wijmo.angular2.chart.finance.analytics": "npm:@grapecity/wijmo.angular2.chart.finance.analytics/index.js",
"@grapecity/wijmo.angular2.chart.finance": "npm:@grapecity/wijmo.angular2.chart.finance/index.js",
"@grapecity/wijmo.angular2.chart.hierarchical": "npm:@grapecity/wijmo.angular2.chart.hierarchical/index.js",
"@grapecity/wijmo.angular2.chart.interaction": "npm:@grapecity/wijmo.angular2.chart.interaction/index.js",
"@grapecity/wijmo.angular2.chart.radar": "npm:@grapecity/wijmo.angular2.chart.radar/index.js",
'@grapecity/wijmo.angular2.chart.map': 'npm:@grapecity/wijmo.angular2.chart.map/index.js',
"@grapecity/wijmo.angular2.chart": "npm:@grapecity/wijmo.angular2.chart/index.js",
"@grapecity/wijmo.angular2.core": "npm:@grapecity/wijmo.angular2.core/index.js",
"@grapecity/wijmo.angular2.gauge": "npm:@grapecity/wijmo.angular2.gauge/index.js",
"@grapecity/wijmo.angular2.grid.detail": "npm:@grapecity/wijmo.angular2.grid.detail/index.js",
"@grapecity/wijmo.angular2.grid.filter": "npm:@grapecity/wijmo.angular2.grid.filter/index.js",
"@grapecity/wijmo.angular2.grid.grouppanel": "npm:@grapecity/wijmo.angular2.grid.grouppanel/index.js",
"@grapecity/wijmo.angular2.grid.search": "npm:@grapecity/wijmo.angular2.grid.search/index.js",
"@grapecity/wijmo.angular2.grid.multirow": "npm:@grapecity/wijmo.angular2.grid.multirow/index.js",
"@grapecity/wijmo.angular2.grid.sheet": "npm:@grapecity/wijmo.angular2.grid.sheet/index.js",
'@grapecity/wijmo.angular2.grid.transposed': 'npm:@grapecity/wijmo.angular2.grid.transposed/index.js',
'@grapecity/wijmo.angular2.grid.transposedmultirow': 'npm:@grapecity/wijmo.angular2.grid.transposedmultirow/index.js',
"@grapecity/wijmo.angular2.grid": "npm:@grapecity/wijmo.angular2.grid/index.js",
"@grapecity/wijmo.angular2.input": "npm:@grapecity/wijmo.angular2.input/index.js",
"@grapecity/wijmo.angular2.olap": "npm:@grapecity/wijmo.angular2.olap/index.js",
"@grapecity/wijmo.angular2.viewer": "npm:@grapecity/wijmo.angular2.viewer/index.js",
"@grapecity/wijmo.angular2.nav": "npm:@grapecity/wijmo.angular2.nav/index.js",
"@grapecity/wijmo.angular2.directivebase": "npm:@grapecity/wijmo.angular2.directivebase/index.js",
'@grapecity/wijmo.angular2.barcode.common': 'npm:@grapecity/wijmo.angular2.barcode.common/index.js',
'@grapecity/wijmo.angular2.barcode.composite': 'npm:@grapecity/wijmo.angular2.barcode.composite/index.js',
'@grapecity/wijmo.angular2.barcode.specialized': 'npm:@grapecity/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);