グリッド:カスタムエディタ (Angular)

FlexGridはデフォルトで効率的なExcelスタイルの編集を提供しますが、編集の動作をカスタマイズすることができます。この例では、Wijmoコントロールをグリッドエディタとして使用できるようにするCustomGridEditorクラスを定義します。

このサンプルはAngularを使用しています。

import 'bootstrap.css'; import '@grapecity/wijmo.styles/wijmo.css'; import './styles.css'; // import { Component, enableProdMode, NgModule } from '@angular/core'; import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; import { BrowserModule } from '@angular/platform-browser'; import * as wjcCore from '@grapecity/wijmo'; import * as wjcInput from '@grapecity/wijmo.input'; import * as wjcGrid from '@grapecity/wijmo.grid'; import { WjInputModule } from '@grapecity/wijmo.angular2.input'; import { WjGridModule } from '@grapecity/wijmo.angular2.grid'; import { CustomGridEditor } from './custom-grid-editor'; // @Component({ selector: 'app-component', templateUrl: 'src/app.component.html' }) export class AppComponent { data: any[]; private _countries = 'アメリカ,ドイツ,イギリス,日本,イタリア,ギリシャ'.split(','); private _products = [ { id: 0, name: 'ウィジェット', unitPrice: 23.43 }, { id: 1, name: 'ガジェット', unitPrice: 12.33 }, { id: 2, name: 'ツール', unitPrice: 53.07 } ]; constructor() { this.data = this._getData(); } initializeGrid(flex: wjcGrid.FlexGrid) { // add custom editors to the grid new CustomGridEditor(flex, 'date', wjcInput.InputDate, { format: 'd' }); new CustomGridEditor(flex, 'time', wjcInput.InputTime, { format: 't', min: new Date(2000, 1, 1, 7, 0), max: new Date(2000, 1, 1, 22, 0), step: 30 }); new CustomGridEditor(flex, 'country', wjcInput.ComboBox, { itemsSource: this._countries }); new CustomGridEditor(flex, 'amount', wjcInput.InputNumber, { format: 'n2', step: 10 }); // // create an editor based on a ComboBox let multiColumnEditor = new CustomGridEditor(flex, 'product', wjcInput.ComboBox, { headerPath: 'name', displayMemberPath: 'name', itemsSource: this._products }); // // customize the ComboBox to show multiple columns let combo = <wjcInput.ComboBox>multiColumnEditor.control; combo.listBox.formatItem.addHandler((s: any, e: wjcInput.FormatItemEventArgs) => { e.item.innerHTML = '<table><tr>' + '<td style="width:30px;text-align:right;padding-right:6px">' + e.data.id + '</td>' + '<td style="width:100px;padding-right:6px"><b>' + e.data.name + '</b></td>' + '<td style="width:80px;text-align:right;padding-right:6px">' + wjcCore.Globalize.format(e.data.unitPrice, 'c') + '</td>' + '</tr></table>'; }); } private _getData() { let data = []; let dt = new Date(); for (let i = 0; i < 100; i++) { data.push({ id: i, date: new Date(dt.getFullYear(), i % 12, 25, i % 24, i % 60, i % 60), time: new Date(dt.getFullYear(), i % 12, 25, i % 24, i % 60, i % 60), country: this._countries[Math.floor(Math.random() * this._countries.length)], product: this._products[Math.floor(Math.random() * this._products.length)].name, amount: Math.random() * 10000 - 5000, discount: Math.random() / 4 }); } return data; } } // @NgModule({ imports: [WjGridModule, WjInputModule, BrowserModule], declarations: [AppComponent], 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>GrapeCity Wijmo FlexGrid Custom Editors</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/dist/zone.min.js"></script> <!-- SystemJS --> <script src="node_modules/systemjs/dist/system.js"></script> <script src="systemjs.config.js"></script> <script> // workaround to load 'rxjs/operators' from the rxjs bundle System.import('rxjs').then(function (m) { System.set(SystemJS.resolveSync('rxjs/operators'), System.newModule(m.operators)); System.import('./src/app.component'); }); </script> </head> <body> <app-component></app-component> </body> </html>
<div class="container-fluid"> <wj-flex-grid #flex [keyActionTab]="'CycleOut'" (initialized)="initializeGrid(flex)" [(itemsSource)]="data"> <wj-flex-grid-column [binding]="'id'" [header]="'ID'" [width]="40" [isReadOnly]="true"></wj-flex-grid-column> <wj-flex-grid-column [binding]="'date'" [header]="'日付'" [format]="'d'"></wj-flex-grid-column> <wj-flex-grid-column [binding]="'time'" [header]="'時刻'" [format]="'t'"></wj-flex-grid-column> <wj-flex-grid-column [binding]="'country'" [header]="'国'"></wj-flex-grid-column> <wj-flex-grid-column [binding]="'product'" [header]="'商品'"></wj-flex-grid-column> <wj-flex-grid-column [binding]="'amount'" [header]="'金額'" [format]="'n2'"></wj-flex-grid-column> </wj-flex-grid> </div>
.wj-flexgrid { height: 300px; margin-bottom: 12px; } .wj-flexgrid .wj-cell { padding: 6px 3px; } body { margin-bottom: 24px; }
import * as wjcCore from '@grapecity/wijmo'; import * as wjcInput from '@grapecity/wijmo.input'; import * as wjcGrid from '@grapecity/wijmo.grid'; export class CustomGridEditor { _grid: wjcGrid.FlexGrid; _col: wjcGrid.Column; _ctl: wjcCore.Control; _openDropDown: boolean; _rng: wjcGrid.CellRange; static _isEditing: boolean; // true if any custom editor is active /** * Initializes a new instance of a CustomGridEditor. */ constructor(flex: wjcGrid.FlexGrid, binding: string, edtClass: any, options: any) { // save references this._grid = flex; this._col = flex.columns.getColumn(binding); // create editor this._ctl = new edtClass(document.createElement('div'), options); // connect grid events flex.beginningEdit.addHandler(this._beginningEdit, this); flex.sortingColumn.addHandler(() => { this._commitRowEdits(); }) flex.scrollPositionChanged.addHandler(() => { if (this._ctl.containsFocus()) { flex.focus(); } }); flex.selectionChanging.addHandler((s, e: wjcGrid.CellRangeEventArgs) => { if (e.row != s.selection.row) { this._commitRowEdits(); } }); // connect editor events this._ctl.addEventListener(this._ctl.hostElement, 'keydown', (e: KeyboardEvent) => { 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 let evt = document.createEvent('HTMLEvents') as any; 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(() => { // Chrome/FireFox need a timeOut here... (TFS 138985) 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(() => { // Chrome/FireFox need a timeOut here... (TFS 138985) if (!this._grid.containsFocus() && !CustomGridEditor._isEditing) { this._commitRowEdits(); } }); }); // open drop-down on f4/alt-down this._grid.addEventListener(this._grid.hostElement, 'keydown', (e: KeyboardEvent) => { // 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))) { let 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: wjcGrid.FlexGrid, args: wjcGrid.CellRangeEventArgs) { // 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) let 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; // initialize editor host let 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._ctl.hostElement, { position: 'absolute', left: rcCell.left - 1 + ptOffset.x, top: rcCell.top - 1 + ptOffset.y, width: rcCell.width + 1, height: grid.rows[args.row].renderHeight + 1, borderRadius: '0px', zIndex: zIndex, // TFS 291852 }); // initialize editor content if (!wjcCore.isUndefined(this._ctl['text'])) { this._ctl['text'] = grid.getCellData(this._rng.row, this._rng.col, true); } else { throw 'Can\'t set editor value/text...'; } // start editing item let 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._ctl.hostElement); this._ctl.focus(); setTimeout(() => { // get the key that triggered the editor let key = (evt && evt.charCode > 32) ? String.fromCharCode(evt.charCode) : null; // get input element in the control let input = <HTMLInputElement>this._ctl.hostElement.querySelector('input'); // send key to editor if (input) { if (key) { input.value = key; wjcCore.setSelectionRange(input, key.length, key.length); let 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: boolean) { if (this._rng) { let flexGrid = this._grid, ctl = this._ctl, host = ctl.hostElement; // raise grid's cellEditEnding event let 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; } host.parentElement.removeChild(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() { let flexGrid = this._grid, ecv = flexGrid.editableCollectionView; this._closeEditor(true); if (ecv && ecv.currentEditItem) { let e = new wjcGrid.CellEditEndingEventArgs(flexGrid.cells, flexGrid.selection); ecv.commitEdit(); setTimeout(() => { // let cell edit events fire first flexGrid.onRowEditEnding(e); flexGrid.onRowEditEnded(e); flexGrid.invalidate(); }); } } }
(function (global) { System.config({ transpiler: 'ts', typescriptOptions: { tsconfig: true }, meta: { 'typescript': { "exports": "ts" }, '*.css': { loader: 'css' } }, paths: { // paths serve as alias 'npm:': 'node_modules/' }, // map tells the System loader where to look for things map: { 'core-js': 'npm:core-js/client/shim.min.js', 'zone': 'npm:zone.js/dist/zone.min.js', //'reflect-metadata': 'npm:reflect-metadata/Reflect.js', 'rxjs': 'npm:rxjs/bundles/rxjs.umd.min.js', //'rxjs': 'npm:rxjs', //'rxjs/operators': 'npm:rxjs/operators', '@angular/core': 'npm:@angular/core/bundles/core.umd.min.js', '@angular/common': 'npm:@angular/common/bundles/common.umd.min.js', '@angular/compiler': 'npm:@angular/compiler/bundles/compiler.umd.min.js', '@angular/platform-browser': 'npm:@angular/platform-browser/bundles/platform-browser.umd.min.js', '@angular/platform-browser-dynamic': 'npm:@angular/platform-browser-dynamic/bundles/platform-browser-dynamic.umd.min.js', '@angular/http': 'npm:@angular/http/bundles/http.umd.min.js', '@angular/common/http': 'npm:@angular/common/bundles/common-http.umd.min.js', '@angular/router': 'npm:@angular/router/bundles/router.umd.min.js', '@angular/forms': 'npm:@angular/forms/bundles/forms.umd.min.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.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.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.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": "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": "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", 'bootstrap.css': 'npm:bootstrap/dist/css/bootstrap.min.css', 'jszip': 'npm:jszip/dist/jszip.min.js', 'typescript': 'npm:typescript/lib/typescript.js', //'typescript': 'https://unpkg.com/typescript@3.2.2', 'ts': 'npm:plugin-typescript/lib/plugin.js', 'css': 'npm:systemjs-plugin-css/css.js' }, // packages tells the System loader how to load when no filename and/or no extension packages: { src: { defaultExtension: 'ts' }, rxjs: { defaultExtension: 'js' }, // 'rxjs': {main: 'index.js', defaultExtension: 'js' }, // 'rxjs/ajax': {main: 'index.js', defaultExtension: 'js' }, //'rxjs/operators': {main: 'index.js', defaultExtension: 'js' }, // 'rxjs/testing': {main: 'index.js', defaultExtension: 'js' }, // 'rxjs/webSocket': {main: 'index.js', defaultExtension: 'js' }, "node_modules": { defaultExtension: 'js' }, wijmo: { defaultExtension: 'js', } } }); })(this);