FlexGrid入門

はじめに

FlexGridをKnockoutJSアプリケーションで使用する手順は以下のとおりです。

  1. Add references to KnockoutJS, Wijmo, and Wijmo's KnockoutJS bindings.
  2. Add a view model to provide data and logic.
  3. Add a Wijmo FlexGrid control to the page and bind it to your data.
  4. (Optional) Add some CSS to customize the input control's appearance.

これはFlexGridの自動列生成、列ソート、編集、クリップボードのサポートが含まれるデフォルト動作を作成します。

<html> <head> <link rel="stylesheet" type="text/css" href="css/bootstrap.css"/> <link rel="stylesheet" type="text/css" href="css/wijmo.css" /> <link rel="stylesheet" href="styles/app.css" /> <script src="scripts/knockout.js" type="text/javascript"></script> <script src="scripts/wijmo.js" type="text/javascript"></script> <script src="scripts/wijmo.input.js" type="text/javascript"></script> <script src="scripts/wijmo.grid.js" type="text/javascript"></script> <script src="scripts/wijmo.knockout.js" type="text/javascript"></script> <script src="scripts/bindings/appBindings.js"></script> <script src="scripts/app.js"></script> <script src="scripts/viewmodels/appVM.js"></script> </head> <body> <!-- this is the grid --> <div data-bind="wjFlexGrid: { itemsSource: data }"></div> </body> </html>
// create and apply application view model function viewModel1() { // generate some random data function getData(count) { var countries = 'US,Germany,UK,Japan,Italy,Greece'.split(','), data = new wijmo.collections.ObservableArray(); for (var i = 0; i < count; i++) { data.push({ id: i, country: countries[i % countries.length], date: new Date(2014, i % 12, i % 28), amount: Math.random() * 10000, active: i % 4 == 0 }); } return data; } // expose data as a CollectionView (to get updates on changes) this.data = new wijmo.collections.CollectionView(getData(100)); ............. }; (function () { ko.applyBindings(new viewModel1()); })();
/* set default grid style */ .wj-flexgrid { height: 300px; background-color: white; box-shadow: 4px 4px 10px 0px rgba(50, 50, 50, 0.75); margin-bottom: 12px; }

結果(ライブ):

列定義

「はじめに」の例では列を定義していなかったため、FlexGridが自動的に列を生成しました。

この例では、HTMLマークアップを使用して列を定義する方法を示します。これをコードで実行することもできますが、マークアップを使用した方がコントローラーとビューの分離が向上します。

列を指定すると、表示する列とその順序を設定できます。 また、各列の幅、見出し、書式設定、配置などのプロパティも制御できます。

このケースでは、スターサイズ指定を使用して[Country]列の幅を設定しています。これにより、[Country]列が拡張されてグリッドの使用可能な幅が満たされるため、空スペースがなくなります。[Revenue]列ではformatプロパティが"n0"に設定されており、この列の数値が桁区切り文字付き、小数点なしになっています。

<div data-bind="wjFlexGrid: { itemsSource: data }"> <div data-bind="wjFlexGridColumn: { header: 'Country', binding: 'country', width: '*' }"></div> <div data-bind="wjFlexGridColumn: { header: 'Date', binding: 'date' }"></div> <div data-bind="wjFlexGridColumn: { header: 'Revenue', binding: 'amount', format: 'n0' }"></div> <div data-bind="wjFlexGridColumn: { header: 'Active', binding: 'active' }"></div> </div>

結果(ライブ):

選択モード

デフォルトでは、FlexGridではExcelと同様にマウスまたはキーボードを使用してセルの範囲を選択できます。selectionModeプロパティを使用してこのデフォルトの動作を変更し、行、行の範囲、隣接していない行(リストボックスと同様)、または単一セルを選択できるようにするか、あるいはまったく選択できないようにするかを指定できます。

この例では、Wijmo MenuコントロールからselectionModeを選択できます。

<div data-bind="wjFlexGrid: { itemsSource: data, selectionMode: selectionMode }"></div> <div data-bind="wjMenu: { value: selectionMode, header: 'Selection Mode' }"> <span data-bind="wjMenuItem: { value: 'None' }">None</span> <span data-bind="wjMenuItem: { value: 'Cell' }">Cell</span> <span data-bind="wjMenuItem: { value: 'CellRange' }">CellRange</span> <span data-bind="wjMenuItem: { value: 'Row' }">Row</span> <span data-bind="wjMenuItem: { value: 'RowRange' }">RowRange</span> <span data-bind="wjMenuItem: { value: 'ListBox' }">ListBox</span> </div>
// initialize selection mode this.selectionMode = ko.observable('CellRange');

結果(ライブ):

なし セル 隣接するセル範囲 隣接する行 隣接しない行

編集

FlexGridは、Excelに似た迅速なセル内編集を組み込みでサポートしています。表示モードと編集モードを切り替える[編集]ボタンを含む列を追加する必要はありません。

ユーザーはセルに文字を入力することによって編集を開始できます。この場合、セルはクイック編集モードになります。このモードでは、方向キーを押すと編集が終了し、選択が別のセルに移動します。

また、[F2]を押すかセルをダブルクリックすることによって編集を開始することもできます。この場合、セルはフル編集モードになります。このモードでは、方向キーを押すとセルテキスト内でキャレットが移動します。編集を終了して別のセルに移動するためには、[Enter]、[Tab]、[Esc]のいずれかのキーを押す必要があります。

編集を終了するとき、データは適切な型に自動的に型変換されます。ユーザーが無効なデータを入力した場合、編集はキャンセルされ、元のデータに戻ります。

グリッド、列、または行レベルで編集を無効にするには、グリッド、列、または行オブジェクトのisReadOnlyプロパティを使用します。この例では、[ID]列が読み取り専用になっています。

<div data-bind="wjFlexGrid: { itemsSource: data }"> <div data-bind="wjFlexGridColumn: { header: 'ID', binding: 'id', isReadOnly: true, width: 50 }"></div> <div data-bind="wjFlexGridColumn: { header: 'Country', binding: 'country', width: '*' }"></div> <div data-bind="wjFlexGridColumn: { header: 'Date', binding: 'date' }"></div> <div data-bind="wjFlexGridColumn: { header: 'Revenue', binding: 'amount', format: 'n0' }"></div> <div data-bind="wjFlexGridColumn: { header: 'Active', binding: 'active' }"></div> </div>

結果(ライブ):

グループ化

FlexGridは、ICollectionViewインタフェース(これは.NETのICollectionViewインタフェースと同一です)によってグループ化をサポートします。グループ化を有効にするには、1つ以上のGroupDescriptionオブジェクトをCollectionView.groupDescriptionsプロパティに追加し、グリッドのshowGroupsプロパティをtrue(デフォルト値)に設定します。

GroupDescriptionオブジェクトは柔軟性が高く、値またはグループ化関数に基づいてデータをグループ化できます。この例では、日付(年別)、金額(5,000超、500~5,000、500未満の3つの範囲別)、およびその他の項目(値別)でグループ化できます。メニューを使用して各グループ化の効果を確認してください。

[Revenue]列にはグループ行の合計が表示されます。このようにするには、列のaggregateプロパティを"Sum"に設定します。列の値を編集すると、集計は自動的に更新されます。

<div data-bind="wjFlexGrid: { itemsSource: cvGroup }"> <div data-bind="wjFlexGridColumn: { header: 'Country', binding: 'country', width: '*' }"></div> <div data-bind="wjFlexGridColumn: { header: 'Date', binding: 'date' }"></div> <div data-bind="wjFlexGridColumn: { header: 'Revenue', binding: 'amount', format: 'n0', aggregate: 'Sum' }"></div> </div> <div data-bind="wjMenu: { value: groupBy, header: 'グループ化' }"> <span data-bind="wjMenuItem: { value: '' }">(グループ化なし)</span> <span data-bind="wjMenuItem: { value: 'country' }">Country</span> <span data-bind="wjMenuItem: { value: 'amount' }">Revenue</span> <span data-bind="wjMenuItem: { value: 'date' }">Date</span> <span data-bind="wjMenuItem: { value: 'country,date' }">Country、Date</span> <span data-bind="wjMenuItem: { value: 'country,amount' }">Country、Revenue</span> <span data-bind="wjMenuItem: { value: 'country,date,amount' }">Country、Date、Revenue</span> </div>
// expose the data as a CollectionView to show grouping this.cvGroup = new wijmo.collections.CollectionView(getData(100)); this.groupBy = ko.observable(''); // update CollectionView group descriptions when groupBy changes this.groupBy.subscribe(function(oldValue) { var cv = self.cvGroup; cv.groupDescriptions.clear(); if (self.groupBy()) { var groupNames = self.groupBy().split(','); for (var i = 0; i < groupNames.length; i++) { var groupName = groupNames[i]; if (groupName == 'date') { // group dates by year var groupDesc = new wijmo.collections.PropertyGroupDescription(groupName, function (item, prop) { return item.date.getFullYear(); }); cv.groupDescriptions.push(groupDesc); } else if (groupName == 'amount') { // group amounts in ranges var groupDesc = new wijmo.collections.PropertyGroupDescription(groupName, function (item, prop) { return item.amount >= 5000 ? '> 5,000' : item.amount >= 500 ? '500 to 5,000' : '< 500'; }); cv.groupDescriptions.push(groupDesc); } else { // group everything else by value var groupDesc = new wijmo.collections.PropertyGroupDescription(groupName); cv.groupDescriptions.push(groupDesc); } } } });

結果(ライブ):

(グループ化なし) Country Revenue Date Country、Date Country、Revenue Country、Date、Revenue

フィルタリング

FlexGridは、ICollectionViewインタフェース(これは.NETのICollectionViewインタフェースと同一です)によってフィルタリングをサポートします。フィルタリングを有効にするには、ビューに含めるオブジェクトを決定する関数をCollectionView.filterプロパティに設定します。

この例では、国のフィルタを作成し、入力コントロールからフィルタ値を取得します。

<div data-bind="wjFlexGrid: { itemsSource: cvFilter }"></div> <div class="input-group"> <span class="input-group-addon"><span class="glyphicon glyphicon-filter"></span></span> <input type="text" data-bind="value: filter, valueUpdate: 'input'"class="form-control" placeholder="フィルタリング"/> </div>
// expose the data as a CollectionView to show filtering this.filter = ko.observable(''); var toFilter, lcFilter; this.cvFilter = new wijmo.collections.CollectionView(getData(100)); // holds the cvFilter.currentItem this.cvFilterCurrentItem = ko.observable(this.cvFilter.currentItem); // updates the cvFilterCurrentItem observable this.cvFilter.currentChanged.addHandler(function () { self.cvFilterCurrentItem(self.cvFilter.currentItem) }); this.cvFilter.filter = function (item) { // ** filter function if (self.filter()) { return item.country.toLowerCase().indexOf(lcFilter) > -1; } return true; }; this.filter.subscribe(function (oldValue) { // ** refresh view when filter changes if (toFilter) { clearTimeout(toFilter); } toFilter = setTimeout(function () { lcFilter = self.filter().toLowerCase(); self.cvFilter.refresh(); }, 500); });

結果(ライブ):

ページング

FlexGridは、IPagedCollectionViewインタフェース(これは.NETのIPagedCollectionViewインタフェースと同一です)によってページングをサポートします。ページングを有効にするには、各ページに表示する項目数をIPagedCollectionView.pageSizeプロパティに設定し、ページを移動するためのUIを提供します。

この例では、JavaScriptを使用して1ページに10項目を表示しています。ナビゲーションボタンが用意されており、ボタンをクリックするとIPagedCollectionViewメソッドが呼び出されます。また、pageIndexプロパティとpageCountプロパティを使用して現在のページと総ページ数を表示しています。

<div data-bind="wjFlexGrid: { itemsSource: cvPaging }" style="height:auto"></div> <div class="btn-group"> <button type="button" class="btn btn-default" data-bind="click: function () { cvPaging().moveToFirstPage() }, disable: cvPaging().pageIndex <= 0"> <span class="glyphicon glyphicon-fast-backward"></span> </button> <button type="button" class="btn btn-default" data-bind="click: function () { cvPaging().moveToPreviousPage() }, disable: cvPaging().pageIndex <= 0"> <span class="glyphicon glyphicon-step-backward"></span> </button> <button type="button" class="btn btn-default" disabled style="width:100px"> <span data-bind="text: cvPaging().pageIndex + 1"></span> / <span data-bind=" text: cvPaging().pageCount"></span> </button> <button type="button" class="btn btn-default" data-bind="click: function () { cvPaging().moveToNextPage() }, disable: cvPaging().pageIndex >= cvPaging().pageCount - 1"> <span class="glyphicon glyphicon-step-forward"></span> </button> <button type="button" class="btn btn-default" data-bind="click: function () { cvPaging().moveToLastPage() }, disable: cvPaging().pageIndex >= cvPaging().pageCount - 1"> <span class="glyphicon glyphicon-fast-forward"></span> </button> </div>
// expose the data as a CollectionView to show paging var cvPaging = new wijmo.collections.CollectionView(getData(100)); // expose it as an observable to allow chnge notificasions forcing re-read of the child properties by consumers this.cvPaging = ko.observable(cvPaging); // set page size cvPaging.pageSize = 10; // on any collection change, send a change notification for the cvPaging observable to force re-reading // of the child properties by consumers function notifyCvPagingUpdated () { self.cvPaging.valueHasMutated(); } cvPaging.collectionChanged.addHandler(notifyCvPagingUpdated); cvPaging.currentChanged.addHandler(notifyCvPagingUpdated); cvPaging.pageChanged.addHandler(notifyCvPagingUpdated);

結果(ライブ):

マスター/詳細

The ICollectionView interface has built-in support for currency with the currentItem property and currentChanged event, which enables you to implement master-detail scenarios with FlexGrid.

The simplest way to organize binding to the current item is to add an observable property holding the item and update its value in the ICollectionView.currentChanged event handler.

<div data-bind="wjFlexGrid: { itemsSource: cvFilter, isReadOnly: true, }"> <div data-bind="wjFlexGridColumn: { header: 'Country', binding: 'country', width: '*' }"></div> <div data-bind="wjFlexGridColumn: { header: 'Date', binding: 'date' }"></div> </div> <dl class="dl-horizontal"> <dt>ID</dt> <dd><span data-bind="text: cvFilterCurrentItem().id"></span></dd> <dt>Country</dt> <dd><span data-bind="text: cvFilterCurrentItem().country"></span></dd> <dt>Date</dt> <dd><span data-bind="text: format(cvFilterCurrentItem().date, 'd')"></span></dd> <dt>Revenue</dt> <dd><span data-bind="text: format(cvFilterCurrentItem().amount, 'n2')"></span></dd> </dl>
this.cvFilter = new wijmo.collections.CollectionView(getData(100)); // holds the cvFilter.currentItem this.cvFilterCurrentItem = ko.observable(this.cvFilter.currentItem); // updates the cvFilterCurrentItem observable this.cvFilter.currentChanged.addHandler(function () { self.cvFilterCurrentItem(self.cvFilter.currentItem) });

結果(ライブ):

ID
Country
Date
Revenue

セルテンプレート

FlexGridでは、itemFormatterプロパティを使用してセルの内容を完全に制御できます。 The KnockoutJS binding we provide for the grid uses this to support in-line cell templates, so you can define the appearance of the cells using plain HTML.

To define a cell template for a column, add the HTML to display in each cell to the column definition. Use the $item observable variable to access the data item from within the template, and the $row and $col variables to retrieve the cell's row and column indexes.

<div data-bind="wjFlexGrid: { itemsSource: data }"> <div data-bind="wjFlexGridColumn: { header: 'Country', binding: 'country', width: '*', isReadOnly: true }"> <img data-bind="attr: { src: 'resources/' + $item().country + '.png' }" /> <span data-bind="text: $item().country"></span> </div> <div data-bind="wjFlexGridColumn: { header: 'Date', binding: 'date' }"></div> <div data-bind="wjFlexGridColumn: { header: 'Revenue', binding: 'amount', format: 'n0' }"></div> <div data-bind="wjFlexGridColumn: { header: 'Active', binding: 'active' }"></div> </div>

結果(ライブ):

条件付きスタイル設定

The wjFlexGridColumn binding supports the wjStyle binding. This allows you to customize the style used to display the data in each cell based on its value. The wjStyle binding conforms to the same rules used by the native KnockoutJS style binding. In the same way as for the custom cell templates, the $item, $row and $col observable variables are available for binding, in addition to the properties defined in the view model.

この例では、JavaScript関数を使用して、名前付きの色を返す値範囲を作成しています。 We then call this function in the Revenue column inside the wjStyle binding and use the $item variable to pass in the data and set the color.

<div data-bind="wjFlexGrid: { itemsSource: data }"> <div data-bind="wjFlexGridColumn: { header: 'Country', binding: 'country', width: '*' }"></div> <div data-bind="wjFlexGridColumn: { header: 'Date', binding: 'date' }"></div> <div data-bind="wjFlexGridColumn: { header: 'Revenue', binding: 'amount', format: 'n0' }, wjStyle: { color: getAmountColor($item().amount) }"></div> <div data-bind="wjFlexGridColumn: { header: 'Active', binding: 'active' }"></div> </div>
// get the color to be used for displaying an amount this.getAmountColor = function (amount) { if (amount < 4000) return 'darkred'; if (amount < 6000) return 'black'; return 'darkgreen'; }

結果(ライブ):

テーマ

FlexGridの外観はCSSで定義されます。デフォルトテーマに加えてプロフェッショナルなデザインのテーマが多数用意されており、すべてのWijmoコントロールの外観をカスタマイズして一貫性のある魅力的な見た目を提供できます。

グリッドの外観はCSSを使用してカスタマイズできます。そのためには、デフォルトテーマから新しいCSSファイルにCSSルールをコピーし、必要なスタイル属性を変更します。

この例では、"custom-flex-grid"クラスをグリッド要素に追加し、"custom-flex-grid"クラスを持つグリッド用に白黒および罫線なしのシンプルなテーマを作成するCSSルールを定義しています。

また、列のソート方向、およびグループ化されたグリッドのアウトラインノードを示すために使用されるグリフの外観もカスタマイズしています。カスタムグリフを表示するには、列見出しセルをクリックします。

<div data-bind="wjFlexGrid: { itemsSource: data }" class="custom-flex-grid"></div>
/* create a 'custom-flex-grid' theme for the FlexGrid */ .custom-flex-grid .wj-header.wj-cell { background-color: #000; color: #fff; font-weight: bold; border-right: solid 1px #404040; border-bottom: solid 1px #404040; } .custom-flex-grid .wj-cell { border: none; background-color: #fff; } .custom-flex-grid .wj-alt:not(.wj-state-selected):not(.wj-state-multi-selected) { background-color: #fff; } .custom-flex-grid .wj-state-selected { background: #000; color: #fff; } .custom-flex-grid .wj-state-multi-selected { background: #222222; color: #fff; } /* override the glyphs used to show sorting and grouping */ .custom-flex-grid .wj-glyph-up { background-image:url('../resources/ascending.png'); background-repeat: no-repeat; background-position: bottom right; width: 1em; height: 1em; border-top: 0px; border-bottom: 0px; border-left: 0px; border-right: 0px; opacity: 1; } .custom-flex-grid .wj-glyph-down { background-image:url('../resources/descending.png'); background-repeat: no-repeat; background-position: bottom right; width: 1em; height: 1em; border-top: 0px; border-bottom: 0px; border-left: 0px; border-right: 0px; opacity: 1; } .custom-flex-grid .wj-glyph-right { background-image:url('../resources/collapsed.png'); background-repeat: no-repeat; background-position: bottom right; width: 1em; height: 1em; border-top: 0px; border-bottom: 0px; border-left: 0px; border-right: 0px; } .custom-flex-grid .wj-glyph-down-right { background-image:url('../resources/expanded.png'); background-repeat: no-repeat; background-position: bottom right; width: 1em; height: 1em; border-top: 0px; border-bottom: 0px; border-left: 0px; border-right: 0px; }

結果(ライブ):

ツリーと階層データ

グループ化に加えて、FlexGridは階層データ(サブ項目のリストを持つ項目で構成されたデータ)もサポートします。この種の階層構造は非常に一般的で、通常はツリー表示コントロールで表示されます。

FlexGridで階層データソースを使用するには、子要素を含むデータ要素の名前をchildItemsPathプロパティに設定します。グリッドがデータを自動的にスキャンしてツリーを構築します。

<div class="custom-flex-grid" data-bind="wjFlexGrid: { itemsSource: treeData, childItemsPath: 'items', allowResizing: 'None', selectionMode: 'ListBox', headersVisibility: 'None' }"> <div data-bind="wjFlexGridColumn: { binding: 'name', width: '*' }"></div> <div data-bind="wjFlexGridColumn: { binding: '', width: 80, align: 'center' }"></div> </div>
// hierarchical data this.treeData = [ { name: '\u266B Adriane Simione', items: [ { name: '\u266A Intelligible Sky', items: [ { name: 'Theories', length: '2:02' }, { name: 'Giant Eyes', length: '3:29' }, { name: 'Jovian Moons', length: '1:02' }, { name: 'Open Minds', length: '2:41' }, { name: 'Spacetronic Eyes', length: '3:41' }] } ]}, { name: '\u266B Amy Winehouse', items: [ { name: '\u266A Back to Black', items: [ { name: 'Addicted', length: '1:34' }, { name: 'He Can Only Hold Her', length: '2:22' }, { name: 'Some Unholy War', length: '2:21' }, { name: 'Wake Up Alone', length: '3:43' }, { name: 'Tears Dry On Their Own', length: '1:25' }] }, // more hierarchical data...

結果(ライブ):

null値の扱い

デフォルトでは、FlexGridは文字列型の列に空文字を入力することができ、他の列型では空文字やnull値を許容しません。

isRequiredプロパティをグリッドの列に設定することでこの動作を変更することができます。isRequiredプロパティをfalseに設定すると、列型に関わらず空文字の入力を許容することができます。逆にtrueに設定すると、文字列型であっても空文字の入力を許容しません。

isRequiredにnullを設定すると、デフォルト動作(文字列型のみ空文字の入力を許容する)に戻ります。

グリッドは以下のデフォルト動作に戻します。 It sets isRequired to false for the first column, and to true for all others. 空文字を入力する、またはDeleteキーによってコンテンツを削除することができます。

<div data-bind="wjFlexGrid: { itemsSource: data }"> <div data-bind="wjFlexGridColumn: { header: 'Country', binding: 'country', width: '*', isRequired: true }"></div> <div data-bind="wjFlexGridColumn: { header: 'Date', binding: 'date', isRequired: false }"></div> <div data-bind="wjFlexGridColumn: { header: 'Revenue', binding: 'amount', format: 'n0', isRequired: false }"></div> <div data-bind="wjFlexGridColumn: { header: 'Active', binding: 'active', isRequired: false }"></div> </div>

結果(ライブ):