FlexGrid 101

入门

在KnockoutJS应用中开始使用FlexGrid的步骤:

  1. 添加对KnockoutJS, Wijmo和Wijmo的KnockoutJS绑定的引用。
  2. 添加一个视图模型提供数据和逻辑。
  3. 向当前页面添加一个Wijmo FlexGrid控件,并将它绑定到你的数据。
  4. (可选)添加一些CSS来自定义输入控件的外观。

以下将会创建一个具有默认行为的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; }

Result (live):

列定义

上面入门的样例没有定义任何列,所以FlexGrid会自动生成它们。

这个样例演示如何使用HTML标记定义列。 你也可以在代码中这样做,但是使用标记可以令控制器和视图更明显的分开。

指定列允许你选择哪些列显示,以及按什么顺序。这也给了你对于每一列的宽度,标题,格式,对齐和其它属性的控制。

在这个样例中,我们使用星形来设置”Country”列的宽度。这告诉列通过拉伸来填充表格的可用宽度,因此没有空余的空间。 在”Revenue”列,我们设置格式属性为”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>

Result (live):

选择模式

默认情况下,FlexGrid允许你使用鼠标或键盘选择一个范围的单元格,就像Excel做的那样,selectionMode属性允许你改变这一行为, 因此,你可以选择一行,一个范围内的行,不连续的行(如在一个列表框),一个单元格或者将选择完全禁用。

这个样例允许你从一个Wijmo菜单控件中选择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');

Result (live):

None Cell CellRange Row RowRange ListBox

编辑

FlexGrid对快速编辑,单元格内部编辑具有内置的支持,正如你在Excel中。使用控制显示和编辑模式切换的Edit按钮来添加额外的列是没有必要的。

用户可以通过键入任何一个单元格来开始编辑。这样会使单元格处于快速编辑模式。在这种模式下,按下光标键会完成编辑并且选中一个不同的单元格。

另一种开始编辑的方法是用过按F2键或者双击一个单元格。这会是单元格处于完全编辑模式。在这种模式下,按下光标键会在单元格文本中移动光标。 为了完成编辑并且移动到下一个单元格,用户必须按下回车,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>

Result (live):

分组

FlexGrid支持分组,通过ICollectionView接口,与.NET中的相同。 为了达到分组的目的,向CollectionView.groupDescriptions属性增加一个或多个GroupDescription对象 并且确保表格的showGroups属性设置为true(默认属性)

GroupDescription对象是灵活的,允许你基于值或者分组函数来对数据进行分组。以下的这个样例按年份对时间分组,按返回的三个范围: 大于5000,500到5000,小于500来对总数分组,其它按值分组。通过菜单可以看到每个分组的效果。

观察”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: 'Group by' }"> <span data-bind="wjMenuItem: { value: '' }">(no grouping)</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 and Date</span> <span data-bind="wjMenuItem: { value: 'country,amount' }">Country and Revenue</span> <span data-bind="wjMenuItem: { value: 'country,date,amount' }">Country, Date, and 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); } } } });

Result (live):

(no grouping) Country Revenue Date Country and Date Country and Revenue Country, Date, and Revenue

过滤

FlexGrid支持过滤,通过ICollectionView接口,与.NET中的相同。为了达到过滤的目的, 设置CollectionView.filter的属性为一个函数,由这个函数决定那个对象要留在视野中。

在这个样例中,我们创建了一个对于country的过滤,可以通过输入控件来获得过滤后得到的值。

<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="filter"/> </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); });

Result (live):

分页

FlexGrid支持分页,通过IPagedCollectionView接口,与.NET中的几乎相同。 为了达到分页的目的,设置IPagedCollectionView.pageSize属性为你想要在每页上展示项目的数量,并且提供一个界面导航的UI。

在这个样例中,我们使用JavaScript在每页展示10个项目。我们增加了导航按钮,并且在按钮单击指令中调用了IPagedCollectionView方法。 注意,我们使用了pageIndexpageCount属性来展示当前页和总的页数。

<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);

Result (live):

主-细节

ICollectionView接口对currentItem属性和currentChanged事件的当前值有内置的支持, 可以让你在FlexGrid中实现主-细节的场景。 property and currentChanged event, which enables you to implement master-detail scenarios with FlexGrid.

组织绑定到当前项最简单的方法是添加一个观测值属性,这个属性拥有项目,并且在ICollectionView.currentChanged事件处理器 更新它的值。

<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) });

Result (live):

ID
Country
Date
Revenue

单元格模板

FlexGrid有一个itemFormatter属性,让你对单元格的内容进行完全的控制。 我们为表格提供的KnockoutJS指令使用这个属性来支持在线单元格模板,因此你可以使用纯HTML来定义单元格的外观。

要为一个列定义一个单元格模板,向列定义的每个单元格添加HTML来显示。 使用$item观测值变量来访问模板的数据项, 使用$row和$col变量获取单元格行和列的索引。

<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>

Result (live):

条件样式

wjFlexGridColumn绑定支持wjStyle绑定。 这允许你自定义每个单元格基于它的值显示数据的样式。 wjStyle绑定符合本地KnockoutJS style绑定使用的同种规则。 同样的方法,对于自定义单元格模板,$item, $row和$col观测值变量对绑定是可用的,除了在视图模型上定义的属性。

这个样例使用了一个JavaScript函数来创建值范围,返回命名的颜色。 然后我们在wjStyle绑定中的Revenue列调用这个函数,使用$item变量传递数据并设置颜色。

<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'; }

Result (live):

主题

FlexGrid的外观是在CSS中定义的。除了默认的主题外,我们有十几个专业设计的主题,它们自定义了所有Wijmo控件的外观来达到一致的,有吸引力的效果。

你可以使用CSS自定义表格的外观。要做到这一点,从默认主题复制CSS规则到一个新的CSS文件并且修改你想要更改的样式属性。

在这个样例中,我们向表格元素添加了一个custom-flex-grid类, 定义了一些CSS规则为任何拥有custom-flex-grid类的表格来创建一个简单的“黑与白,无国界”主题。

我们也可以自定义用来显示分组表格中的列排序方向和轮廓节点字形的外观。要观看自定义的字形,单击一个列头部单元格。

<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; }

Result (live):

树形和分层数据

除了分组,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...

Result (live):

处理null值

默认情况下,FlexGrid允许你在字符串类型的列输入空值,不允许空值或者null在任何其它类型的列中。

你可以使用表格列的required属性来更改此行为。如果你设置required属性为false, 表格会允许你在这一列键入空值,不管这一列是什么数据类型。 相反,如果你设置required属性为true,表格不允许你输入空值,甚至在字符串类型的列中也不允许。

设置required为null恢复默认的行为(只允许在字符串类型列输入空值)。

它把第一类的下面的表格恢复了默认的行为。它把第一列的required属性设为false,其他列设为true。 你可以通过输入一个空字符串或者只要按下删除键来删除不需要的内容。

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

Result (live):