カスタムエディタ

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

import 'bootstrap.css'; import '@grapecity/wijmo.styles/wijmo.css'; import './styles.css'; import * as wjGrid from '@grapecity/wijmo.grid'; import * as wjInput from '@grapecity/wijmo.input'; import * as wjCore from '@grapecity/wijmo'; // // *** CustomGridEditor class (transpiled from TypeScript) *** // export class CustomGridEditor { /** * Initializes a new instance of a CustomGridEditor. */ constructor(flex, binding, edtClass, options) { // 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) => { if (e.row != s.selection.row) { this._commitRowEdits(); } }); // connect editor events this._ctl.addEventListener(this._ctl.hostElement, 'keydown', (e) => { switch (e.keyCode) { case wjCore.Key.Tab: case wjCore.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 wjCore.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 == wjCore.Key.F4 || (e.altKey && (e.keyCode == wjCore.Key.Down || e.keyCode == wjCore.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 == wjCore.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 == wjCore.Key.Delete) { return; } // cancel built-in editor args.cancel = true; // save cell being edited this._rng = args.range; CustomGridEditor._isEditing = true; // initialize editor host var rcCell = grid.getCellBoundingRect(args.row, args.col), rcBody = document.body.getBoundingClientRect(), ptOffset = new wjCore.Point(-rcBody.left, -rcBody.top), zIndex = (args.row < grid.frozenRows || args.col < grid.frozenColumns) ? '3' : ''; wjCore.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, }); // initialize editor content if (!wjCore.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 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._ctl.hostElement); 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; wjCore.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 wjInput.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, host = ctl.hostElement; // raise grid's cellEditEnding event var e = new wjGrid.CellEditEndingEventArgs(flexGrid.cells, this._rng); flexGrid.onCellEditEnding(e); // save editor value into grid if (saveEdits) { if (!wjCore.isUndefined(ctl['value'])) { this._grid.setCellData(this._rng.row, this._rng.col, ctl['value']); } else if (!wjCore.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 wjInput.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() { var flexGrid = this._grid, ecv = flexGrid.editableCollectionView; this._closeEditor(true); if (ecv && ecv.currentEditItem) { var e = new wjGrid.CellEditEndingEventArgs(flexGrid.cells, flexGrid.selection); ecv.commitEdit(); setTimeout(() => { flexGrid.onRowEditEnding(e); flexGrid.onRowEditEnded(e); flexGrid.invalidate(); }); } } } // document.readyState === 'complete' ? init() : window.onload = init; // function init() { // // create some random data var countries = 'アメリカ,ドイツ,イギリス,日本,イタリア,ギリシャ'.split(','); var products = [ { id: 0, name: 'ウィジェット', unitPrice: 23.43 }, { id: 1, name: 'ガジェット', unitPrice: 12.33 }, { id: 2, name: 'ツール', unitPrice: 53.07 } ]; var data = []; var dt = new Date(); for (var 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: countries[Math.floor(Math.random() * countries.length)], product: products[Math.floor(Math.random() * products.length)].name, amount: Math.random() * 10000 - 5000, discount: Math.random() / 4 }); } // // grid with custom editors var theGrid = new wjGrid.FlexGrid('#theGrid', { keyActionTab: 'CycleOut', autoGenerateColumns: false, itemsSource: data, columns: [ { header: 'ID', binding: 'id', width: 40, isReadOnly: true }, { header: '日付', binding: 'date', format: 'd' }, { header: '時刻', binding: 'time', format: 't' }, { header: '国', binding: 'country' }, { header: '商品', binding: 'product' }, { header: '金額', binding: 'amount', format: 'n2' } ], }); // // add custom editors to the grid new CustomGridEditor(theGrid, 'date', wjInput.InputDate, { format: 'd' }); new CustomGridEditor(theGrid, 'time', wjInput.InputTime, { format: 't', min: new Date(2000, 1, 1, 7, 0), max: new Date(2000, 1, 1, 22, 0), step: 30 }); new CustomGridEditor(theGrid, 'country', wjInput.ComboBox, { itemsSource: countries }); new CustomGridEditor(theGrid, 'amount', wjInput.InputNumber, { format: 'n2', step: 10 }); // // create an editor based on a ComboBox var multiColumnEditor = new CustomGridEditor(theGrid, 'product', wjInput.ComboBox, { headerPath: 'name', displayMemberPath: 'name', itemsSource: products }); // // customize the ComboBox to show multiple columns var combo = multiColumnEditor.control; combo.listBox.formatItem.addHandler(function (s, e) { 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">' + wjCore.Globalize.format(e.data.unitPrice, 'c') + '</td>' + '</tr></table>'; }); } //# sourceMappingURL=CustomGridEditor.js.map <!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"> <!-- 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"> <div id="theGrid"> </div> </div> </body> </html> .wj-flexgrid { height: 300px; margin-bottom: 12px; } .wj-flexgrid .wj-cell { padding: 6px 3px; } body { margin-bottom: 24px; } 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(); }); } } } import 'bootstrap.css'; import '@grapecity/wijmo.styles/wijmo.css'; import './app.css'; import * as React from 'react'; import * as ReactDOM from 'react-dom'; import * as wjFlexGrid from '@grapecity/wijmo.react.grid'; import * as wjcCore from '@grapecity/wijmo'; import * as wjcInput from '@grapecity/wijmo.input'; import { CustomGridEditor } from './custom-grid-editor'; class App extends React.Component { constructor(props) { super(props); this._countries = 'アメリカ,ドイツ,イギリス,日本,イタリア,ギリシャ'.split(','); this._products = [ { id: 0, name: 'ウィジェット', unitPrice: 23.43 }, { id: 1, name: 'ガジェット', unitPrice: 12.33 }, { id: 2, name: 'ツール', unitPrice: 53.07 } ]; this.data = this._getData(200); this.initializeGrid = this.initializeGrid.bind(this); } render() { return <div className="container-fluid"> <wjFlexGrid.FlexGrid keyActionTab="CycleOut" initialized={this.initializeGrid} itemsSource={this.data}> <wjFlexGrid.FlexGridColumn binding="id" header="ID" width={40} isReadOnly={true}></wjFlexGrid.FlexGridColumn> <wjFlexGrid.FlexGridColumn binding="date" header="日付" format="d"></wjFlexGrid.FlexGridColumn> <wjFlexGrid.FlexGridColumn binding="time" header="時刻" format="t"></wjFlexGrid.FlexGridColumn> <wjFlexGrid.FlexGridColumn binding="country" header="国"></wjFlexGrid.FlexGridColumn> <wjFlexGrid.FlexGridColumn binding="product" header="商品"></wjFlexGrid.FlexGridColumn> <wjFlexGrid.FlexGridColumn binding="amount" header="金額" format="n2"></wjFlexGrid.FlexGridColumn> </wjFlexGrid.FlexGrid> </div>; } initializeGrid(flex) { // 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 = multiColumnEditor.control; if (combo instanceof wjcInput.ComboBox) { combo.listBox.formatItem.addHandler((s, e) => { 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>'; }); } } _getData(cnt) { // generate some random data 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; } } ReactDOM.render(<App />, document.getElementById('app')); <!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"> <!-- 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 id="app"></div> </body> </html> .wj-flexgrid { height: 300px; margin-bottom: 12px; } .wj-flexgrid .wj-cell { padding: 6px 3px; } body { margin-bottom: 24px; } <template> <div class="container-fluid"> <wj-flex-grid :keyActionTab="'CycleOut'" :initialized="initializeGrid" :itemsSource="itemSourceData"> <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> </template> <script> import "@grapecity/wijmo.styles/wijmo.css"; import "bootstrap.css"; import Vue from "vue"; 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.vue2.input"; import { WjGridModule } from "@grapecity/wijmo.vue2.grid"; import { CustomGridEditor } from "./custom-grid-editor"; let App = Vue.extend({ name: "app", data: function() { return { countries: "アメリカ,ドイツ,イギリス,日本,イタリア,ギリシャ".split(","), products: [ { id: 0, name: "ウィジェット", unitPrice: 23.43 }, { id: 1, name: "ガジェット", unitPrice: 12.33 }, { id: 2, name: "ツール", unitPrice: 53.07 } ], itemSourceData: null, }; }, methods: { initializeGrid: function(flex) { // 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 = multiColumnEditor.control; combo.listBox.formatItem.addHandler((s, e) => { 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>"; }); }, getData: function() { 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; } }, mounted:function(){ this.itemSourceData = this.getData(); } }); new Vue({ render: h => h(App) }).$mount("#app"); </script> <style> .wj-flexgrid { height: 300px; margin-bottom: 12px; } .wj-flexgrid .wj-cell { padding: 6px 3px; } body { margin-bottom: 24px; } </style> <!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"> <!-- SystemJS --> <script src="node_modules/systemjs/dist/system.src.js"></script> <script src="systemjs.config.js"></script> <script> System.import('./src/app.vue'); </script> </head> <body> <div id="app"> </div> </body> </html> import * as wjcCore from '@grapecity/wijmo'; import * as wjcInput from '@grapecity/wijmo.input'; import * as wjcGrid from '@grapecity/wijmo.grid'; export class CustomGridEditor { /** * Initializes a new instance of a CustomGridEditor. */ constructor(flex, binding, edtClass, options) { // 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) => { 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 let 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))) { 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, 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) 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, }); // 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 = 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) { 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(() => { flexGrid.onRowEditEnding(e); flexGrid.onRowEditEnded(e); flexGrid.invalidate(); }); } } }