JavaScript DataGrid

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

import 'bootstrap.css'; import '@grapecity/wijmo.styles/wijmo.css'; import './styles.css'; // import '@grapecity/wijmo.touch'; import * as wjcCore from '@grapecity/wijmo'; import * as wjcGrid from '@grapecity/wijmo.grid'; import * as wjcGridFilter from '@grapecity/wijmo.grid.filter'; import * as wjcGridSearch from '@grapecity/wijmo.grid.search'; import * as wjcGridGroupPanel from '@grapecity/wijmo.grid.grouppanel'; import * as wjcInput from '@grapecity/wijmo.input'; import { CellMaker, SparklineMarkers } from '@grapecity/wijmo.grid.cellmaker'; import { KeyValue, Country, DataService } from './data'; import { ExportService } from './export'; // class App { constructor(dataSvc, exportSvc) { this._itemsCount = 500; this._lastId = this._itemsCount; this._dataSvc = dataSvc; this._exportSvc = exportSvc; // initializes data maps this._productMap = this._buildDataMap(this._dataSvc.getProducts()); this._countryMap = new wjcGrid.DataMap(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 data size document.getElementById('itemsCount').addEventListener('change', (e) => { const value = e.target.value; this._itemsCount = wjcCore.changeType(value, wjcCore.DataType.Number); this._handleItemsCountChange(); }); // initializes export const btnExportToExcel = document.getElementById('btnExportToExcel'); this._excelExportContext = new ExcelExportContext(btnExportToExcel); btnExportToExcel.addEventListener('click', () => { this._exportToExcel(); }); document.getElementById('btnExportToPdf').addEventListener('click', () => { this._exportToPdf(); }); // initializes the grid this._initializeGrid(); // initializes items source this._itemsSource = this._createItemsSource(); this._theGrid.itemsSource = this._itemsSource; } close() { const ctx = this._excelExportContext; this._exportSvc.cancelExcelExport(ctx); } _initializeGrid() { // creates the grid this._theGrid = new wjcGrid.FlexGrid('#theGrid', { autoGenerateColumns: false, allowAddNew: true, allowDelete: true, allowPinning: wjcGrid.AllowPinning.SingleColumn, newRowAtTop: true, showMarquee: true, selectionMode: wjcGrid.SelectionMode.MultiRange, validateEdits: false, columns: [ { binding: 'id', header: 'ID', width: 70, isReadOnly: true }, { binding: 'date', header: 'Date', format: 'MMM d yyyy', isRequired: false, width: 130, editor: new wjcInput.InputDate(document.createElement('div'), { format: 'MM/dd/yyyy', isRequired: false }) }, { binding: 'countryId', header: 'Country', dataMap: this._countryMap, width: 145, cellTemplate: (ctx) => { const dataItem = ctx.row.dataItem; if (wjcCore.isUndefined(dataItem) || dataItem === null) { return ''; } const country = this._getCountry(ctx.item); return `<span class="flag-icon flag-icon-${country.flag}"></span> ${country.name}`; } }, { binding: 'price', header: 'Price', format: 'c', isRequired: false, width: 100 }, { binding: 'history', header: 'History', width: 180, align: 'center', allowSorting: false, cellTemplate: this._historyCellTemplate }, { binding: 'change', header: 'Change', align: 'right', width: 115, cellTemplate: (ctx) => { const dataItem = ctx.row.dataItem; if (wjcCore.isUndefined(dataItem) || dataItem === null) { return ''; } const cls = this._getChangeCls(ctx.value); const value = this._formatChange(ctx.value); return `<span class="${cls}">${value}</span>`; } }, { binding: 'rating', header: 'Rating', width: 180, align: 'center', cssClass: 'cell-rating', cellTemplate: this._ratingCellTemplate }, { binding: 'time', header: 'Time', format: 'HH:mm', isRequired: false, width: 95, editor: new wjcInput.InputTime(document.createElement('div'), { format: 'HH:mm', isRequired: false }) }, { binding: 'colorId', header: 'Color', dataMap: this._colorMap, width: 145, cellTemplate: (ctx) => { const dataItem = ctx.row.dataItem; if (wjcCore.isUndefined(dataItem) || dataItem === null) { return ''; } const color = this._getColor(ctx.item); return `<span class="color-tile" style="background: ${color.value}"></span> ${color.value}`; } }, { binding: 'productId', header: 'Product', dataMap: this._productMap, width: 145 }, { binding: 'discount', header: 'Discount', format: 'p0', width: 130 }, { binding: 'active', header: 'Active', width: 100 } ] }); // create the grid search box new wjcGridSearch.FlexGridSearch('#theSearch', { placeholder: 'Search', grid: this._theGrid, cssMatch: '' }); // adds Excel-like filter new wjcGridFilter.FlexGridFilter(this._theGrid, { filterColumns: [ 'id', 'date', 'time', 'countryId', 'productId', 'colorId', 'price', 'change', 'discount', 'rating', 'active' ] }); // adds group panel new wjcGridGroupPanel.GroupPanel('#theGroupPanel', { placeholder: 'Drag columns here to create groups', grid: this._theGrid }); } _getCountry(item) { const country = this._countryMap.getDataItem(item.countryId); return country ? country : Country.NotFound; } _getColor(item) { const color = this._colorMap.getDataItem(item.colorId); return color ? color : KeyValue.NotFound; } _getChangeCls(value) { if (wjcCore.isNumber(value)) { if (value > 0) { return 'change-up'; } if (value < 0) { return 'change-down'; } } return ''; } _formatChange(value) { if (wjcCore.isNumber(value)) { return wjcCore.Globalize.formatNumber(value, 'c'); } if (!wjcCore.isUndefined(value) && value !== null) { return wjcCore.changeType(value, wjcCore.DataType.String); } return ''; } _exportToExcel() { const ctx = this._excelExportContext; if (!ctx.exporting) { this._exportSvc.startExcelExport(this._theGrid, ctx); } else { this._exportSvc.cancelExcelExport(ctx); } } _exportToPdf() { this._exportSvc.exportToPdf(this._theGrid, { countryMap: this._countryMap, colorMap: this._colorMap, historyCellTemplate: this._historyCellTemplate }); } _createItemsSource() { const data = this._dataSvc.getData(this._itemsCount); const view = new wjcCore.CollectionView(data, { getError: (item, prop) => { const displayName = this._theGrid.columns.getColumn(prop).header; return this._dataSvc.validate(item, prop, displayName); } }); view.collectionChanged.addHandler((s, e) => { // 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; } _disposeItemsSource(itemsSource) { if (itemsSource) { itemsSource.collectionChanged.removeAllHandlers(); } } // build a data map from a string array using the indices as keys _buildDataMap(items) { const map = []; for (let i = 0; i < items.length; i++) { map.push({ key: i, value: items[i] }); } return new wjcGrid.DataMap(map, 'key', 'value'); } _handleItemsCountChange() { this._disposeItemsSource(this._itemsSource); this._lastId = this._itemsCount; this._itemsSource = this._createItemsSource(); this._theGrid.itemsSource = this._itemsSource; } } // class ExcelExportContext { constructor(btn) { this._exporting = false; this._progress = 0; this._preparing = false; this._btn = btn; } get exporting() { return this._exporting; } set exporting(value) { if (value !== this._exporting) { this._exporting = value; this._onPropertyChanged(); } } get progress() { return this._progress; } set progress(value) { if (value !== this._progress) { this._progress = value; this._onPropertyChanged(); } } get preparing() { return this._preparing; } set preparing(value) { if (value !== this._preparing) { this._preparing = value; this._onPropertyChanged(); } } _onPropertyChanged() { wjcCore.enable(this._btn, !this._preparing); if (this._exporting) { const percent = wjcCore.Globalize.formatNumber(this._progress, 'p0'); this._btn.textContent = `Cancel (${percent} done)`; } else { this._btn.textContent = 'Export To Excel'; } } } // document.readyState === 'complete' ? init() : window.onload = init; // function init() { const dataSvc = new DataService(); const exportSvc = new ExportService(); const app = new App(dataSvc, exportSvc); window.addEventListener('unload', () => { app.close(); }); }
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <title>GrapeCity Wijmo FlexGrid Overview</title> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <!-- SystemJS --> <script src="node_modules/systemjs/dist/system.src.js"></script> <script src="systemjs.config.js"></script> <script> System.import('./src/app'); </script> </head> <body> <div class="container-fluid"> <!-- filter/navigation --> <div class="row"> <!-- search box --> <div class="toolbar-item col-sm-3 col-md-5"> <div id="theSearch"></div> </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 id="itemsCount" class="form-control"> <option value="5">5</option> <option value="50">50</option> <option value="500" selected>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 id="btnExportToExcel" class="btn btn-default btn-block">Export To Excel</button> </div> <!-- export to PDF --> <div class="toolbar-item col-sm-3 col-md-2"> <button id="btnExportToPdf" class="btn btn-default btn-block">Export To PDF</button> </div> </div> <!-- the grid --> <div id="theGroupPanel"></div> <div id="theGrid"></div> </div> </body> </html>
import * as wjcCore from '@grapecity/wijmo'; import { RequiredValidator, MinNumberValidator, MinDateValidator, MaxNumberValidator, MaxDateValidator } from './validation'; // export class KeyValue { } KeyValue.NotFound = { key: -1, value: '' }; // export class Country { } Country.NotFound = { id: -1, name: '', flag: '' }; // export class DataService { constructor() { this._products = ['Widget', 'Gadget', 'Doohickey']; this._colors = ['Black', 'White', 'Red', 'Green', 'Blue']; this._countries = [ { 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' } ]; this._validationConfig = { '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() { return this._countries; } getProducts() { return this._products; } getColors() { return this._colors; } getHistoryData() { return this._getRandomArray(25, 100); } getData(count) { 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, prop, displayName) { const validators = 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; } } } _getItem(i, year) { 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; } _getRating() { return Math.ceil(Math.random() * 5); } _getRandomIndex(arr) { return Math.floor(Math.random() * arr.length); } _getRandomArray(len, maxValue) { const arr = []; 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'; margin-left: 0.5em; } .change-down { color: darkred; } .change-down:after { content: '\25bc'; margin-left: 0.5em; } .cell-rating { font-size: 12px; } .wj-flexgrid .wj-detail { padding: 4px 16px; } .wj-detail h3 { margin: 10px 0; }
import * as wjcCore from '@grapecity/wijmo'; import * as wjcGrid from '@grapecity/wijmo.grid'; import * as wjcInput from '@grapecity/wijmo.input'; // // *** CustomGridEditor class (transpiled from TypeScript) *** // export class CustomGridEditor { /** * Initializes a new instance of a CustomGridEditor. */ constructor(grid, binding, edtClass, options) { // save references this._grid = grid; this._col = grid.columns.getColumn(binding); // create error tooltip this._errorTip = new wjcCore.Tooltip({ isContentHtml: false, showDelay: 0, cssClass: 'wj-error-tip' }); // create editor this._ctl = new edtClass(document.createElement('div'), options); wjcCore.addClass(this._ctl.hostElement, 'custom-editor'); wjcCore.setCss(this._ctl.hostElement, { position: 'relative', height: '100%', borderRadius: '0px', border: 'none' }); // create hosting element this._host = document.createElement('div'); this._host.appendChild(this._ctl.hostElement); // connect grid events grid.beginningEdit.addHandler(this._beginningEdit, this); grid.sortingColumn.addHandler(() => { this._commitRowEdits(); }); grid.scrollPositionChanged.addHandler(() => { if (this._ctl.containsFocus()) { grid.focus(); } }); grid.selectionChanging.addHandler((s, e) => { if (e.row != s.selection.row) { this._commitRowEdits(); } }); // connect editor events this._ctl.addEventListener(this._ctl.hostElement, 'keydown', (e) => { switch (e.keyCode) { case wjcCore.Key.Tab: case wjcCore.Key.Enter: e.preventDefault(); // TFS 255685 this._closeEditor(true); this._grid.focus(); // forward event to the grid so it will move the selection var evt = document.createEvent('HTMLEvents'); evt.initEvent('keydown', true, true); 'altKey,metaKey,ctrlKey,shiftKey,keyCode'.split(',').forEach((prop) => { evt[prop] = e[prop]; }); this._grid.hostElement.dispatchEvent(evt); break; case wjcCore.Key.Escape: this._closeEditor(false); this._grid.focus(); break; } }); // close the editor when it loses focus this._ctl.lostFocus.addHandler(() => { setTimeout(() => { if (!this._ctl.containsFocus()) { this._closeEditor(true); // apply edits and close editor this._grid.onLostFocus(); // commit item edits if the grid lost focus } }); }); // commit edits when grid loses focus this._grid.lostFocus.addHandler(() => { setTimeout(() => { if (!this._grid.containsFocus() && !CustomGridEditor._isEditing) { this._commitRowEdits(); } }); }); // open drop-down on f4/alt-down this._grid.addEventListener(this._grid.hostElement, 'keydown', (e) => { // open drop-down on f4/alt-down this._openDropDown = false; if (e.keyCode == wjcCore.Key.F4 || (e.altKey && (e.keyCode == wjcCore.Key.Down || e.keyCode == wjcCore.Key.Up))) { var colIndex = this._grid.selection.col; if (colIndex > -1 && this._grid.columns[colIndex] == this._col) { this._openDropDown = true; this._grid.startEditing(true); e.preventDefault(); } } // commit edits on Enter (in case we're at the last row, TFS 268944) if (e.keyCode == wjcCore.Key.Enter) { this._commitRowEdits(); } }, true); // close editor when user resizes the window // REVIEW: hides editor when soft keyboard pops up (TFS 326875) window.addEventListener('resize', () => { if (this._ctl.containsFocus()) { this._closeEditor(true); this._grid.focus(); } }); } // gets an instance of the control being hosted by this grid editor get control() { return this._ctl; } // handle the grid's beginningEdit event by canceling the built-in editor, // initializing the custom editor and giving it the focus. _beginningEdit(grid, args) { // check that this is our column if (grid.columns[args.col] != this._col) { return; } // check that this is not the Delete key // (which is used to clear cells and should not be messed with) var evt = args.data; if (evt && evt.keyCode == wjcCore.Key.Delete) { return; } // cancel built-in editor args.cancel = true; // save cell being edited this._rng = args.range; CustomGridEditor._isEditing = true; // update cell before editing grid.refreshRange(this._rng); // show error if (grid._getShowErrors()) { var error = grid._getError(args.panel, args.row, args.col); this._showError(error); } else { this._clearError(); } // initialize editor host var rcCell = grid.getCellBoundingRect(args.row, args.col), rcBody = document.body.getBoundingClientRect(), ptOffset = new wjcCore.Point(-rcBody.left, -rcBody.top), zIndex = (args.row < grid.frozenRows || args.col < grid.frozenColumns) ? '3' : ''; wjcCore.setCss(this._host, { position: 'absolute', overflow: 'hidden', left: rcCell.left + ptOffset.x - 1, top: rcCell.top + ptOffset.y - 1, width: rcCell.width + 2, height: grid.rows[args.row].renderHeight + 2, backgroundColor: 'transparent', padding: '2px', zIndex: zIndex, }); // initialize editor content this._ctl['value'] = grid.getCellData(this._rng.row, this._rng.col, false); // start editing item var ecv = grid.editableCollectionView, item = grid.rows[args.row].dataItem; if (ecv && item && item != ecv.currentEditItem) { setTimeout(function () { grid.onRowEditStarting(args); ecv.editItem(item); grid.onRowEditStarted(args); }, 50); // wait for the grid to commit edits after losing focus } // activate editor document.body.appendChild(this._host); this._ctl.focus(); setTimeout(() => { // get the key that triggered the editor var key = (evt && evt.charCode > 32) ? String.fromCharCode(evt.charCode) : null; // get input element in the control var input = this._ctl.hostElement.querySelector('input'); // send key to editor if (input) { if (key) { input.value = key; wjcCore.setSelectionRange(input, key.length, key.length); var evtInput = document.createEvent('HTMLEvents'); evtInput.initEvent('input', true, false); input.dispatchEvent(evtInput); } else { input.select(); } } // give the control focus if (!input && !this._openDropDown) { this._ctl.focus(); } // open drop-down on F4/alt-down if (this._openDropDown && this._ctl instanceof wjcInput.DropDown) { this._ctl.isDroppedDown = true; this._ctl.dropDown.focus(); } }, 50); } // close the custom editor, optionally saving the edits back to the grid _closeEditor(saveEdits) { if (this._rng) { var flexGrid = this._grid, ctl = this._ctl; // raise grid's cellEditEnding event var e = new wjcGrid.CellEditEndingEventArgs(flexGrid.cells, this._rng); flexGrid.onCellEditEnding(e); // save editor value into grid if (saveEdits) { if (!wjcCore.isUndefined(ctl['value'])) { this._grid.setCellData(this._rng.row, this._rng.col, ctl['value']); } else if (!wjcCore.isUndefined(ctl['text'])) { this._grid.setCellData(this._rng.row, this._rng.col, ctl['text']); } else { throw 'Can\'t get editor value/text...'; } this._grid.invalidate(); } // close editor and remove it from the DOM if (ctl instanceof wjcInput.DropDown) { ctl.isDroppedDown = false; } this._host.parentElement.removeChild(this._host); this._rng = null; CustomGridEditor._isEditing = false; // raise grid's cellEditEnded event flexGrid.onCellEditEnded(e); } } // commit row edits, fire row edit end events (TFS 339615) _commitRowEdits() { var flexGrid = this._grid, ecv = flexGrid.editableCollectionView; this._closeEditor(true); if (ecv && ecv.currentEditItem) { var e = new wjcGrid.CellEditEndingEventArgs(flexGrid.cells, flexGrid.selection); ecv.commitEdit(); setTimeout(() => { flexGrid.onRowEditEnding(e); flexGrid.onRowEditEnded(e); flexGrid.invalidate(); }); } } _clearError() { this._showError(null); } _showError(error) { var hasError = !!error; wjcCore.toggleClass(this._ctl.hostElement, 'custom-editor-invalid', hasError); this._errorTip.setTooltip(this._ctl.hostElement, null); setTimeout(() => { this._errorTip.setTooltip(this._ctl.hostElement, error); }); } }
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 './data'; // const ExcelExportDocName = 'FlexGrid.xlsx'; const PdfExportDocName = 'FlexGrid.pdf'; const FakeColumn = new wjcGrid.Column(); const FakeRow = new wjcGrid.Row(); // class Fonts { } Fonts.ZapfDingbatsSm = new wjcPdf.PdfFont('zapfdingbats', 8, 'normal', 'normal'); Fonts.ZapfDingbatsLg = new wjcPdf.PdfFont('zapfdingbats', 16, 'normal', 'normal'); // export class IExcelExportContext { } // export class ExportService { startExcelExport(flex, ctx) { if (ctx.preparing || ctx.exporting) { return; } ctx.exporting = false; ctx.progress = 0; ctx.preparing = true; wjcGridXlsx.FlexGridXlsxConverter.saveAsync(flex, { includeColumnHeaders: true, includeCellStyles: 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) { wjcGridXlsx.FlexGridXlsxConverter.cancelAsync(() => { console.log('Export to Excel canceled'); this._resetExcelContext(ctx); }); } exportToPdf(flex, options) { 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) => this._formatPdfItem(e, options) }); } _formatExcelItem(e) { 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; } } _resetExcelContext(ctx) { ctx.exporting = false; ctx.progress = 0; ctx.preparing = false; } _formatPdfItem(e, options) { 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; } } _formatPdfCountryCell(e, countryMap) { 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; } _formatPdfColorCell(e, colorMap) { 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; } _formatPdfChangeCell(e) { 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; } _formatPdfHistoryCell(e, cell) { 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; } _getHistorySvgDataUrlFromCell(cell, width, height) { let dataUrl = null; // extract SVG from provided cell const svg = cell.getElementsByTagName('svg')[0]; if (svg) { const clone = 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; } _formatPdfRatingCell(e) { 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; } _isCountryExist(countryName, countryMap) { const countryId = countryMap.getKeyValue(countryName); if (wjcCore.isUndefined(countryId) || countryId === null) { return false; } if (countryId === Country.NotFound.id) { return false; } return true; } _isColorExist(colorName, colorMap) { const colorId = colorMap.getKeyValue(colorName); if (wjcCore.isUndefined(colorId) || colorId === null) { return false; } if (colorId === KeyValue.NotFound.key) { return false; } return true; } _createCellFromCellTemplate(cellTemplate, data) { 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 class RequiredValidator { validate(name, value) { 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 class MinValueValidator { constructor(minValue, message = '{0} can\'t be less than {1}', format = null) { this.minValue = minValue; this.message = message; this.format = format; } validate(name, value) { if (value < this.minValue) { return wjcCore.format(this.message, { 0: name, 1: this._formatValue(this.minValue) }); } return ''; } } export class MaxValueValidator { constructor(maxValue, message = '{0} can\'t be greater than {1}', format = null) { this.maxValue = maxValue; this.message = message; this.format = format; } validate(name, value) { if (value > this.maxValue) { return wjcCore.format(this.message, { 0: name, 1: this._formatValue(this.maxValue) }); } return ''; } } export class MinNumberValidator extends MinValueValidator { constructor(minValue, message = '{0} can\'t be less than {1}', format = 'n') { super(minValue, message, format); } _formatValue(value) { return wjcCore.Globalize.formatNumber(value, this.format); } } export class MaxNumberValidator extends MaxValueValidator { constructor(maxValue, message = '{0} can\'t be greater than {1}', format = 'n') { super(maxValue, message, format); } _formatValue(value) { return wjcCore.Globalize.formatNumber(value, this.format); } } export class MinDateValidator extends MinValueValidator { constructor(minValue, message = '{0} can\'t be less than {1}', format = 'MM/dd/yyyy') { super(minValue, message, format); } _formatValue(value) { return wjcCore.Globalize.formatDate(value, this.format); } } export class MaxDateValidator extends MaxValueValidator { constructor(maxValue, message = '{0} can\'t be greater than {1}', format = 'MM/dd/yyyy') { super(maxValue, message, format); } _formatValue(value) { return wjcCore.Globalize.formatDate(value, this.format); } }
(function (global) { System.config({ transpiler: 'plugin-babel', babelOptions: { es2015: true }, meta: { '*.css': { loader: 'css' } }, paths: { // paths serve as alias 'npm:': 'node_modules/' }, // map tells the System loader where to look for things map: { 'jszip': 'npm:jszip/dist/jszip.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.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.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', 'jszip': 'npm:jszip/dist/jszip.js', 'bootstrap.css': 'npm:bootstrap/dist/css/bootstrap.min.css', 'css': 'npm:systemjs-plugin-css/css.js', 'plugin-babel': 'npm:systemjs-plugin-babel/plugin-babel.js', 'systemjs-babel-build':'npm:systemjs-plugin-babel/systemjs-babel-browser.js' }, // packages tells the System loader how to load when no filename and/or no extension packages: { src: { defaultExtension: 'js' }, "node_modules": { defaultExtension: 'js' }, } }); })(this);