FlexGrid 101

入门

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

  1. 为AngularJS,Wijmo和Wijmo的AngularJS指令添加引用。
  2. 在应用模块中包括Wijmo 5指令:
    var app = angular.module('app', ['wj']);
  3. 添加一个控制器提供数字和逻辑。
  4. 向当前页面添加一个FlexGrid,并且将它与数据绑定。
  5. 添加一些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 href="css/app.css" rel="stylesheet" type="text/css" /> <script src="scripts/angular.js" type="text/javascript"></script> <script src="scripts/wijmo.js" type="text/javascript"></script> <script src="scripts/wijmo.grid.js" type="text/javascript"></script> <script src="scripts/wijmo.angular.js" type="text/javascript"></script> <script src="scripts/app.js" type="text/javascript"></script> </head> <body ng-app="app" ng-controller="appCtrl"> <!-- this is the grid --> <wj-flex-grid items-source="data"> </wj-flex-grid> </body> </html>
// declare app module var app = angular.module('app', ['wj']); // app controller provides data app.controller('appCtrl', function appCtrl($scope) { // generate some random data var countries = 'US,Germany,UK,Japan,Italy,Greece'.split(','), data = []; for (var i = 0; i < 100; 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 }); } // add data array to scope $scope.data = data; });
/* 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”列的宽度。这告诉列通过拉伸来填充表格的可用宽度,因此没有多余的空间。 在”Revenue”列,我们设置格式属性为”n0”,导致数字是千分位分隔符,并且没有小数位数字。

<wj-flex-grid items-source="data"> <wj-flex-grid-column header="Country" binding="country" width="*"> </wj-flex-grid-column> <wj-flex-grid-column header="Date" binding="date"> </wj-flex-grid-column> <wj-flex-grid-column header="Revenue" binding="amount" format="n0" > </wj-flex-grid-column> <wj-flex-grid-column header="Active" binding="active"> </wj-flex-grid-column> </wj-flex-grid>

结果:

选择模式

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

这个示例允许你从一个Wijmo菜单控件中选择selectionMode

<wj-flex-grid items-source="data" selection-mode="{​{selectionMode}}"> </wj-flex-grid> <wj-menu value="selectionMode" header="选择模式" > <wj-menu-item value="'None'">None</wj-menu-item> <wj-menu-item value="'Cell'">Cell</wj-menu-item> <wj-menu-item value="'CellRange'">CellRange</wj-menu-item> <wj-menu-item value="'Row'">Row</wj-menu-item> <wj-menu-item value="'RowRange'">RowRange</wj-menu-item> <wj-menu-item value="'ListBox'">ListBox</wj-menu-item> </wj-menu>
// initialize selection mode $scope.selectionMode = 'CellRange';

结果:

None Cell CellRange Row RowRange ListBox

冻结单元格

FlexGrid允许你冻结行和列,因此当用户在表格进行滚动时,它们仍然可以被看到。 冻结的单元格可以被编辑,也可以像普通单元格一样被选中,就像在Excel中一样。

这个示例可以让你切换是否头两行和头两列应该被冻结。

<wj-flex-grid control="frozenFlex" items-source="data" frozen-rows="2" frozen-columns="2"> </wj-flex-grid> <button class="btn btn-default" ng-click="toggleFreeze()"> {​{ frozenFlex.frozenRows == 0 ? '冻结' : '解冻结'}} </button>
// toggle frozen rows/columns $scope.toggleFreeze = function () { var flex = $scope.frozenFlex; if (flex) { var frozenCount = flex.frozenRows == 0 ? 2 : 0; flex.frozenRows = frozenCount; flex.frozenColumns = frozenCount; } }
/* frozen cells */ .wj-frozen:not(.wj-state-selected):not(.wj-state-multi-selected) { background-color: #f8ffd6; } .wj-frozen.wj-alt:not(.wj-state-selected):not(.wj-state-multi-selected) { background-color: #f8ffd6; } /* frozen area edges */ .wj-frozen-row { border-bottom: 1px solid rgba(0,0,0,.5); } .wj-frozen-col { border-right: 1px solid rgba(0,0,0,.5); }

结果:

编辑

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

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

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

当完成编辑后,数据会自动转换为合适的类型。如果用户输入了无效的数据,编辑会被取消,原始数据会保留在原来的位置。

你可以通过设置表格、列或者行对象的isReadOnly属性来禁止对它们的编辑。在这个示例中,我们将ID列置为只读。

<wj-flex-grid items-source="data"> <wj-flex-grid-column header="ID" binding="id" is-read-only="true"> </wj-flex-grid-column> <wj-flex-grid-column header="Country" binding="country" width="*"> </wj-flex-grid-column> <wj-flex-grid-column header="Date" binding="date"> </wj-flex-grid-column> <wj-flex-grid-column header="Revenue" binding="amount" format="n0" > </wj-flex-grid-column> <wj-flex-grid-column header="Active" binding="active"> </wj-flex-grid-column> </wj-flex-grid>

结果:

分组

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

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

观察”Revenue”列展示了行组的总和。我们是通过设置列的aggregate属性为"Sum"做到的。数据聚合会在你编辑列的值时自动更新。

<wj-flex-grid items-source="cvGroup"> <wj-flex-grid-column header="Country" binding="country" width="*"> </wj-flex-grid-column> <wj-flex-grid-column header="Date" binding="date"> </wj-flex-grid-column> <wj-flex-grid-column header="Revenue" binding="amount" format="n0" aggregate="Sum"> </wj-flex-grid-column> </wj-flex-grid> <wj-menu value="groupBy" header="分组" > <wj-menu-item value="''">(no grouping)</wj-menu-item> <wj-menu-item value="'country'">Country</wj-menu-item> <wj-menu-item value="'amount'">Revenue</wj-menu-item> <wj-menu-item value="'date'">Date</wj-menu-item> <wj-menu-item value="'country,date'">Country and Date</wj-menu-item> <wj-menu-item value="'country,amount'">Country and Revenue</wj-menu-item> <wj-menu-item value="'country,date,amount'">Country, Date, and Revenue</wj-menu-item> </wj-menu>
// expose the data as a CollectionView to show grouping $scope.cvGroup = new wijmo.collections.CollectionView(data); $scope.groupBy = ''; // update CollectionView group descriptions when groupBy changes $scope.$watch('groupBy', function () { var cv = $scope.cvGroup; cv.groupDescriptions.clear(); // clear current groups if ($scope.groupBy) { var groupNames = $scope.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); } } } });

结果:

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

过滤

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

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

<wj-flex-grid items-source="cvFilter"> </wj-flex-grid> <div class="input-group"> <span class="input-group-addon"> <span class="glyphicon glyphicon-filter"></span> </span> <input type="text" ng-model="filter" class="form-control" placeholder="filter"/> </div>
// expose the data as a CollectionView to show filtering $scope.filter = ''; var toFilter, lcFilter; $scope.cvFilter = new wijmo.collections.CollectionView(data); $scope.cvFilter.filter = function (item) { // ** filter function if (!$scope.filter) { return true; } return item.country.toLowerCase().indexOf(lcFilter) > -1; }; $scope.$watch('filter', function () { // ** refresh view when filter changes if (toFilter) { clearTimeout(toFilter); } toFilter = setTimeout(function () { lcFilter = $scope.filter.toLowerCase(); $scope.cvFilter.refresh(); }, 500); });

结果:

分页

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

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

<wj-flex-grid items-source="cvPaging" style="height:auto"> </wj-flex-grid> <div class="btn-group"> <button type="button" class="btn" ng-click="cvPaging.moveToFirstPage()" <span class="glyphicon glyphicon-fast-backward"></span> </button> <button type="button" class="btn" ng-click="cvPaging.moveToPreviousPage()" <span class="glyphicon glyphicon-step-backward"></span> </button> <button type="button" class="btn" disabled style="width:100px"> {​{cvPaging.pageIndex + 1 | number}} / {​{cvPaging.pageCount | number}} </button> <button type="button" class="btn" ng-click="cvPaging.moveToNextPage()" <span class="glyphicon glyphicon-step-forward"></span> </button> <button type="button" class="btn" ng-click="cvPaging.moveToLastPage()" <span class="glyphicon glyphicon-fast-forward"></span> </button> </div>
// expose the data as a CollectionView to show paging $scope.cvPaging = new wijmo.collections.CollectionView(data); $scope.cvPaging.pageSize = 10;

结果:

主-细节

ICollectionView接口对当前值有内置的支持,可以让你在FlexGrid中实现主-细节的场景。 你可以引用currentItem并且将它作为页面上任意元素的绑定源。

注意,当当前项更改时,你必须告知AngularJS。 要做到这个,向ICollectionView.currentChanged事件添加一个处理器,并且调用这个示例中显示为JS 选项卡的$scope.$apply

<wj-flex-grid items-source="cvFilter" is-read-only="true"> <wj-flex-grid-column header="Country" binding="country" width="*"> </wj-flex-grid-column> <wj-flex-grid-column header="Date" binding="date"> </wj-flex-grid-column> </wj-flex-grid> <dl class="dl-horizontal"> <dt>ID</dt> <dd>{​{cvFilter.currentItem.id}}</dd> <dt>Country</dt> <dd>{​{cvFilter.currentItem.country}}</dd> <dt>Date</dt> <dd>{​{cvFilter.currentItem.date | date}}</dd> <dt>Revenue</dt> <dd>{​{cvFilter.currentItem.amount | number:2}}</dd> </dl>
// tell scope when current item changes $scope.cvFilter.currentChanged.addHandler(function () { $scope.$apply('cvFilter.currentItem'); });

结果:

ID
{{cvFilter.currentItem.id}}
Country
{{cvFilter.currentItem.country}}
Date
{{cvFilter.currentItem.date | date}}
Revenue
{{cvFilter.currentItem.amount | number:2}}

单元格模板

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

要为一个列定义一个单元格模板,向列定义的每个单元格添加HTML来显示。使用$item变量访问模板内的数据项。

<wj-flex-grid items-source="data"> <wj-flex-grid-column header="Country" binding="country" width="*" is-read-only="true"> <img ng-src="resources/{​{$item.country}}.png" /> {​{$item.country}} </wj-flex-grid-column> <wj-flex-grid-column header="Date" binding="date"> </wj-flex-grid-column> <wj-flex-grid-column header="Revenue" binding="amount" format="n0" > </wj-flex-grid-column> <wj-flex-grid-column header="Active" binding="active"> </wj-flex-grid-column> </wj-flex-grid>

结果:

{{$item.country}}

条件样式

wj-flex-flex-grid-column指令支持ng-style指令。这允许你在每个单元格中根据它的值来自定义样式来显示数据。

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

<wj-flex-grid items-source="data"> <wj-flex-grid-column header="Country" binding="country" width="*"> </wj-flex-grid-column> <wj-flex-grid-column header="Date" binding="date"> </wj-flex-grid-column> <wj-flex-grid-column header="Revenue" binding="amount" format="n0" ng-style="{color:getAmountColor($item.amount)}"> </wj-flex-grid-column> <wj-flex-grid-column header="Active" binding="active"> </wj-flex-grid-column> </wj-flex-grid>
// get the color to use to display the amount $scope.getAmountColor = function (amount) { if (amount < 500) return 'darkred'; if (amount < 2500) return 'black'; return 'darkgreen'; }

结果:

主题

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

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

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

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

<wj-flex-grid items-source="data" class="custom-flex-grid"> </wj-flex-grid>
/* 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属性为包含子元素的数据元素的名字。表格会自动扫描数据,为你构建树。

<wj-flex-grid class="custom-flex-grid" items-source="treeData" child-items-path="items" allow-resizing="None" selection-mode="ListBox" headers-visibility="None"> <wj-flex-grid-column binding="name" width="*"> </wj-flex-grid-column> <wj-flex-grid-column binding="length" width="80" align="center"> </wj-flex-grid-column> </wj-flex-grid>
// hierarchical data $scope.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...
/* 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; }

结果:

树排序

默认情况下,排序一个包含分层数据的表格,只排序顶层项目。 这是因为CollectionView不知道数据的层次结构,由于childItemsPath属性属于表格但是不在CollectionView下。

如果你想要排序一些或所有表格的子项目,你应该处理表格的sortedColumn事件来列举项目并且自己来展现附加的对子项目的排序。

这个示例演示如何做到这一假设,假设你希望对子项目的排序与顶级项目排序顺序相同。在这种情况下,你可以调用sort方法来对子项目数组排序, 使用CollectionView_sortItem方法来比较它们。这与CollectionView内部使用的方法是一样的。

<wj-flex-grid class="custom-flex-grid" items-source="treeData" child-items-path="items" selection-mode="ListBox" headers-visibility="Column" sorted-column="sortedColumn(s,e)"> <wj-flex-grid-column binding="name" width="*"></wj-flex-grid-column> <wj-flex-grid-column binding="length" width="80" align="center"></wj-flex-grid-column> </wj-flex-grid>
$scope.sortedColumn = function (s, e) { var view = s.collectionView; if (view && s.childItemsPath) { for (var i = 0; i < view.items.length; i++) { sortItem(view.items[i], view, s.childItemsPath); } view.refresh(); } } function sortItem(item, view, childItemsPath) { var children = item[childItemsPath]; if (children && wijmo.isArray(children)) { children.sort(view._compareItems()); for (var i = 0; i < children.length; i++) { sortItem(children[i], view, childItemsPath); } } }

结果:

处理null值

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

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

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

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

<wj-flex-grid items-source="data"> <wj-flex-grid-column header="Country" binding="country" width="*" required="true"> </wj-flex-grid-column> <wj-flex-grid-column header="Date" binding="date" required="false"> </wj-flex-grid-column> <wj-flex-grid-column header="Revenue" binding="amount" format="n0" required="false"> </wj-flex-grid-column> <wj-flex-grid-column header="Active" binding="active" required="false"> </wj-flex-grid-column> </wj-flex-grid>

结果: