CollectionView 101

这个页面演示如何开始使用Wijmo的CollectionView类.

Wijmo5具有.NET开发者熟悉的基于强大数据层的可靠的基础设施。主要的数据绑定接口是 ICollectionView 。 Wijmo包括了几个类来实现 CollectionView 。最基础的是CollectionView类,它使用常规的JavaScript数组作为数据源。

CollectionView类实现了下列的接口:

CollectionView类可以追踪对数据所做的更改。这个功能在必须向服务器提交更改的情景中是非常有用的。

入门

要使用CollectionView类,先声明它并传入一个常规数组作为数据源。然后使用items属性访问视图。

这里集合会展示在FlexGrid中。

在应用中开始使用CollectionView类的步骤:

  1. 添加对Wijmo的引用。
  2. 添加标记作为FlexGrid的宿主。
  3. 使用JavaScript初始化CollectionView实例和FlexGrid实例。
  4. (可选)添加一些CSS来自定义表格的外观。
<!DOCTYPE html> <html> <head> <link rel="stylesheet" type="text/css" href="css/bootstrap.css"/> <script src="scripts/wijmo.js" type="text/javascript"></script> <script src="scripts/wijmo.grid.js" type="text/javascript"></script> </head> <body> <div id="gsGrid"></div> </body> </html>
// create collectionview, grid var cvGettingStarted = new wijmo.collections.CollectionView(getData(10)), gsGrid = new wijmo.grid.FlexGrid('#gsGrid', { itemsSource: cvGettingStarted });
/* 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):

当前记录管理

因为实现了ICollectionView接口,CollectionView可以管理当前记录。

这个样例演示如何使用CollectionView类提供的API来管理当前记录。

在这种情况下,我们使用currentPosition属性获取集合中当前记录的位置。 我们也会使用moveCurrentTo(item), moveCurrentToFirst(), moveCurrentToLast(), moveCurrentToNext(), moveCurrentToPosition(index)moveCurrentToPrevious() 方法更改当前的位置。 当当前位置更改后,我们使用currentChangingcurrentChanged来追踪它。 我们可以在currentChanging事件中取消当前的更改。

注意:单击下一个按钮来移动当前记录到下一个。 单击"上一个" 移动当前记录到上一个。 单击"停在第4行"按钮,当前记录到达第四行后禁止更改。 然后单击"清除"按钮移除允许当前记录自由更改。

<div class="row-fluid well btn-group"> <button class="btn btn-default" id="btnCRMMoveNext">下一个</button> <button class="btn btn-default" id="btnCRMMovePre">上一个s</button> <button class="btn btn-default" id="btnCRMStop4">停在第4行</button> <button class="btn btn-default" id="btnCRMReset">清除</button> </div> <div id="crmGrid"></div>
// create collectionview, grid var cvCRM = new wijmo.collections.CollectionView(getData(10)), crmGrid = new wijmo.grid.FlexGrid('#crmGrid'); // initialize grid crmGrid.initialize({ isReadOnly: true, selectionMode: wijmo.grid.SelectionMode.Row, itemsSource: cvCRM }); // Add the processes for buttons' click // move the current to the next one document.getElementById('btnCRMMoveNext').addEventListener('click', function () { cvCRM.moveCurrentToNext(); }); // move the current to the preivous one document.getElementById('btnCRMMovePre').addEventListener('click', function () { cvCRM.moveCurrentToPrevious(); }); // when the current item is the 4th one, forbid changing current. document.getElementById('btnCRMStop4').addEventListener('click', function () { cvCRM.currentChanging.addHandler(stopCurrentIn4th); }); // restore to be able to change current. document.getElementById('btnCRMReset').addEventListener('click', function () { cvCRM.currentChanging.removeHandler(stopCurrentIn4th); }); // define the funciton to forbid the current moving. function stopCurrentIn4th(sender, e) { // when the current is the 4rd item, stop moving. if (sender.currentPosition === 3) { e.cancel = true; } }

Result (live):

排序

CollectionView类,就像.NET中那样,实现了ICollectionView接口来支持排序。 要实现排序,添加一个或多个sortDescriptions对象到一个数组中, 并把该数组传递给CollectionView.sortDescriptions属性。现在你就可以从CollectionView.items属性获得排序结果了。

SortDescription对象是灵活的,允许你根据值按升序或降序来排列数据。在下面的样例中, 你可以根据在第一个列表选中选中的字段值来对集合进行排序。你也可以在第二个列表指定排序顺序。

<div class="row-fluid well row"> <div class="col-md-8"> <select id="sortingFieldNameList" class="form-control"> </select> </div> <div class="col-md-4"> <select id="sortingOrderList" class="form-control"> <option value="true" selected="selected">Ascending</option> <option value="false">Descending</option> </select> </div> </div> <div id="sortingGrid"></div>
// create collectionview, grid, the jQuery elements, the field name list. var cvSorting = new wijmo.collections.CollectionView(getData(10)), sortingGrid = new wijmo.grid.FlexGrid('#sortingGrid'), sortingFieldNameList = document.getElementById('sortingFieldNameList'), sortingOrderList = document.getElementById('sortingOrderList'), sortingNames = getNames(); // initialize grid sortingGrid.initialize({ isReadOnly: true, allowSorting: false, itemsSource: cvSorting }); // initialize the list items for field names and orders. sortingFieldNameList.innerHTML += '<option value="" selected="selected">Please choose the field you want to sort by...</option>'; for (var i = 0; i < sortingNames.length; i++) { sortingFieldNameList.innerHTML += '<option value="' + sortingNames[i] + '">' + sortingNames[i] + '</option>'; } // track the list change in order to udpate the sortDescriptions property. sortingFieldNameList.addEventListener('change', sortGrid); sortingOrderList.addEventListener('change', sortGrid); function sortGrid() { var fieldName = sortingFieldNameList.value, ascending = sortingOrderList.value, sd, sdNew; if (!fieldName) { return; } ascending = ascending === 'true'; sd = cvSorting.sortDescriptions; sdNew = new wijmo.collections.SortDescription(fieldName, ascending); // remove any old sort descriptors and add the new one sd.splice(0, sd.length, sdNew); }

Result (live):

过滤

CollectionView类,就像.NET中那样,实现了ICollectionView接口来支持过滤。 要实现过滤,设置CollectionView.filter属性为一个函数,它决定视图中包含哪一个对象。

在这个样例中,我们为country创建了一个过滤器,并且可以从输入控件获取过滤的值。 当你输入一个过滤值时,表格会刷新并且只显示过滤后的数据。

<div class="row-fluid well"> <input id="filteringInput" type="text" class="form-control app-pad" placeholder="Please input the character you want filter by country(case-insensitive)" /> </div> <div id="fileringGrid"></div>
// create collectionview, grid, filter with timeout, textbox for inputting filter. var cvFiltering = new wijmo.collections.CollectionView(getData(20)), filteringGrid = new wijmo.grid.FlexGrid('#filteringGrid'), toFilter, filteringInput = document.getElementById('filteringInput'); // initialize grid filteringGrid.initialize({ isReadOnly: true, itemsSource: cvFiltering }); // apply filter when input filteringInput.addEventListener('input', filterGrid); // define the filter function for the collection view. function filterFunction(item) { var filter = filteringInput.value.toLowerCase(); if (!filter) { return true; } return item.country.toLowerCase().indexOf(filter) > -1; } // apply filter (applied on a 500 ms timeOut) function filterGrid() { if (toFilter) { clearTimeout(toFilter); } toFilter = setTimeout(function () { toFilter = null; if (cvFiltering.filter === filterFunction) { cvFiltering.refresh(); } else { cvFiltering.filter = filterFunction; } }, 500); }

Result (live):

分组

CollectionView类,就像.NET中那样,实现了ICollectionView接口来支持分组。 要实现分组,添加一个或多个GroupDescription 对象到一个数组并且将这个数组传递给CollectionView.groupDescriptions属性。 在创建表格实例时,你必须设置表格的showGroups属性为true(默认值是false)。

GroupDescription对象是灵活的,允许你根据值或分组函数来进行分组。

下面的样例根据你从列表中选择的任意一个字段进行分组。表格不仅展示项的内容也会展示分组信息: 组名,组汇总的平均值,你可以在initTBody方法找到表达的代码,相应的代码片段在116行。

注意:选择列表中的一个项会添加一个新的GroupDescription实例。随后的选择会嵌套组。 如果你选择列表中已经存在GroupDescription对象的项,不会有任何事发生。要清除组的设置,选择列表的第一项。

<div class="row-fluid well"> <select id="groupingFieldNameList" class="form-control"></select> </div> <div id="groupingGrid"></div>
// create collectionview, grid, the select element and the names list. var cvGrouping = new wijmo.collections.CollectionView(getData(20)), groupingGrid = new wijmo.grid.FlexGrid('#groupingGrid'), groupingFieldNameList = document.getElementById('groupingFieldNameList'), groupingNames = getNames(); // initialize grid groupingGrid.initialize({ isReadOnly: true, itemsSource: cvGrouping }); // initialize the list and listen to the list's change. groupingFieldNameList.innerHTML += '<option value="" selected="selected">Please choose the field you want to group by...</option>'; for (var i = 0; i < groupingNames.length; i++) { groupingFieldNameList.innerHTML += '<option value="' + groupingNames[i] + '">' + groupingNames[i] + '</option>'; } groupingFieldNameList.addEventListener('change', groupGrid); // update the group settings. function groupGrid() { var gd, fieldName = groupingFieldNameList.value; gd = cvGrouping.groupDescriptions; if (!fieldName) { // clear all the group settings. gd.splice(0, gd.length); return; } if (findGroup(fieldName) >= 0) { return; } if (fieldName === 'amount') { // when grouping by amount, use ranges instead of specific values gd.push(new wijmo.collections.PropertyGroupDescription(fieldName, function (item, propName) { var value = item[propName]; // amount if (value > 1000) return 'Large Amounts'; if (value > 100) return 'Medium Amounts'; if (value > 0) return 'Small Amounts'; return 'Negative Amounts'; })); } else { // group by specific property values gd.push(new wijmo.collections.PropertyGroupDescription(fieldName)); } } // check whether the group with the specified property name already exists. function findGroup(propName) { var gd = cvGrouping.groupDescriptions; for (var i = 0; i < gd.length; i++) { if (gd[i].propertyName === propName) { return i; } } return -1; }

Result (live):

编辑

CollectionView类,实现了IEditableCollectionView接口来支持编辑。

这个样例展示你应该如何更新,添加和删除集合中的项。

在这个表格中,选中一个行然后按下下面编辑详情按钮会开始编辑。 当你在弹出对话框完成编辑后,按下OK按钮来提交你的更新。 如果你想要添加一个新的记录到集合中,按下添加按钮并且在对话框中自定义输入项的内容。 按下OK来提交该新纪录。如果你不想更新/添加记录,按下对话框的取消按钮。 选中一个行然后按下删除按钮来从集合中移除一个记录。

在更新,增加和删除后,表格会根据追踪的项数组来刷新表格。

<div id="editingGrid"></div> <!-- commands --> <div class="row-fluid well grid-sort-group"> <!-- edit details in a popup --> <button class="btn btn-default" data-toggle="modal" data-target="#dlgDetail" id="btnEdit"> Edit Detail... </button> <button class="btn btn-default" data-toggle="modal" data-target="#dlgDetail" id="btnAdd"> Add... </button> <button class="btn btn-default" id="btnDelete"> Delete </button> </div> <!-- a dialog for editing item details --> <div class="modal fade" id="dlgDetail"> <div class="modal-dialog"> <div class="modal-content"> <div class="modal-header"> <button type="button" class="close" data-dismiss="modal" aria-hidden="true"> &times; </button> <h4 class="modal-title">Edit Item</h4> </div> <div class="modal-body"> <dl class="dl-horizontal"> <dt>ID</dt> <dd> <input class="form-control" id="edtID" type="text" /> </dd> <dt>Start Date</dt> <dd> <input class="form-control" id="edtStart" type="text" /> </dd> <dt>End Start</dt> <dd> <input class="form-control" id="edtEnd" type="text" /> </dd> <dt>Country</dt> <dd> <input class="form-control" id="edtCountry" type="text" /> </dd> <dt>Product</dt> <dd> <input class="form-control" id="edtProduct" type="text" /> </dd> <dt>Color</dt> <dd> <input class="form-control" id="edtColor" type="text" /> </dd> <dt>Amount</dt> <dd> <input class="form-control" id="edtAmount" type="text" /> </dd> <dt>Active</dt> <dd> <input class="form-control" id="edtActive" type="checkbox" /> </dd> </dl> </div> <div class="modal-footer"> <button type="button" class="btn btn-primary" data-dismiss="modal" id="btnCRUDOK"> OK </button> <button type="button" class="btn btn-warning" data-dismiss="modal" id="btnCRUDCancel"> Cancel </button> </div> </div> </div> </div>
// create collectionview, grid // create collectionview, grid var cvEditing = new wijmo.collections.CollectionView(getData(10)), editingGrid = new wijmo.grid.FlexGrid('#editingGrid'); // initialize grid editingGrid.initialize({ selectionMode: wijmo.grid.SelectionMode.Row, itemsSource: cvEditing }); // track the collection changes so that updating the grid. cvEditing.trackChanges = true; // define the new item value. cvEditing.newItemCreator = function () { var item = getData(1)[0]; // aggregate the max value of id in the collection. item.id = wijmo.getAggregate(wijmo.Aggregate.Max, cvEditing.sourceCollection, 'id') + 1; return item; }; // Add the processes for buttons' click document.getElementById('btnEdit').addEventListener('click', function () { cvEditing.editItem(cvEditing.currentItem); // update the content in the dialog with the current edited item. updateDialog(cvEditing.currentEditItem, true); }); document.getElementById('btnAdd').addEventListener('click', function () { var newItem = cvEditing.addNew(); // update the content in the dialog with the current added item updateDialog(newItem, false); }); document.getElementById('btnDelete').addEventListener('click', function () { var position = cvEditing.currentPosition; cvEditing.remove(cvEditing.currentItem); }); // commit editing or adding document.getElementById('btnCRUDOK').addEventListener('click', function () { // update the editing/adding item with the returned data from dialog. var updatedItem = getUpdatedData(), cItem = cvEditing.currentEditItem, names = getNames(); if (!cItem) { cItem = cvEditing.currentAddItem; } if (!cItem) { return; } for (var i = 0; i < names.length; i++) { var fName = names[i]; cItem[fName] = updatedItem[fName]; } // commit editing/adding cvEditing.commitEdit(); cvEditing.commitNew(); }); // cancel editing or adding document.getElementById('btnCRUDCancel').addEventListener('click', function () { cvEditing.cancelEdit(); cvEditing.cancelNew(); }); // fill the dialog with the item. function updateDialog(item, isEdit) { document.getElementById('edtID').value = item.id !== null && typeof (item.id) != 'undefined' ? wijmo.Globalize.format(item.id) : ''; document.getElementById('edtStart').value = item.start ? wijmo.Globalize.format(item.start) : ''; document.getElementById('edtEnd').value = item.end ? wijmo.Globalize.format(item.end) : ''; document.getElementById('edtCountry').value = item.country ? item.country : ''; document.getElementById('edtProduct').value = item.product ? item.product : ''; document.getElementById('edtColor').value = item.color ? item.color : ''; document.getElementById('edtAmount').value = item.amount !== null && typeof item.amount != 'undefined' ? wijmo.Globalize.format(item.amount) : ''; document.getElementById('edtActive').checked = item.active; var title = document.getElementById('dlgDetail').querySelector('div.modal-header h4.modal-title'); title.innerHTML = isEdit ? 'Edit Item' : 'Add Item'; } // get the content from the dialog function getUpdatedData() { var item = {}, content = document.getElementById('edtID').value; if (content) { item.id = wijmo.Globalize.parseInt(content); } content = document.getElementById('edtStart').value; if (content) { item.start = wijmo.Globalize.parseDate(content); } content = document.getElementById('edtEnd').value; if (content) { item.end = wijmo.Globalize.parseDate(content); } item.country = document.getElementById('edtCountry').value; item.product = document.getElementById('edtProduct').value; item.color = document.getElementById('edtColor').value; content = document.getElementById('edtAmount').value; if (content) { item.amount = wijmo.Globalize.parseFloat(content); } item.active = document.getElementById('edtActive').checked; return item; }

Result (live):

分页

CollectionView类,与.NET中几乎相同,实现了IPagedCollectionView接口来支持分页。要实现分页, 设置IPagedCollectionView.pageSize 属性为你想要在每个页面展示的项的数量,然后提供一个导航页面的UI。

在这个样例中,CollectionView对象被初始化为每页显示10个项。 你可以在textbox中自定义一个数字。 我们添加了导航按钮,并且在按钮的点击事件中调用IPagedCollectionView方法。 注意我们使用pageIndexpageCount属性来展示当前页面和页面的总数。你可以在第一个文本框自定义页面的大小。 将它设为空或0会禁用分页并且隐藏导航按钮。

<div class="row-fluid well row"> <div class="col-md-5"> <input id="pagingInput" type="text" class="form-control" placeholder="0 or empty is for no paging." /> </div> <div class="btn-group col-md-7" id="naviagtionPage"> <button type="button" class="btn btn-default" id="btnMoveToFirstPage"> <span class="glyphicon glyphicon-fast-backward"></span> </button> <button type="button" class="btn btn-default" id="btnMoveToPreviousPage"> <span class="glyphicon glyphicon-step-backward"></span> </button> <button type="button" class="btn btn-default" disabled style="width:100px" id="btnCurrentPage"> </button> <button type="button" class="btn btn-default" id="btnMoveToNextPage"> <span class="glyphicon glyphicon-step-forward"></span> </button> <button type="button" class="btn btn-default" id="btnMoveToLastPage"> <span class="glyphicon glyphicon-fast-forward"></span> </button> </div> </div> <div id="pagingGrid"></div>
// create collectionview, grid, the navigation buttons' elements var cvPaging = new wijmo.collections.CollectionView(getData(55)), pagingGrid = new wijmo.grid.FlexGrid('#pagingGrid'), btnFirstPage = document.getElementById('btnMoveToFirstPage'), btnPreviousPage = document.getElementById('btnMoveToPreviousPage'), btnNextPage = document.getElementById('btnMoveToNextPage'), btnLastPage = document.getElementById('btnMoveToLastPage'), btnCurrentPage = document.getElementById('btnCurrentPage'); // initialize grid pagingGrid.initialize({ isReadOnly: true, itemsSource: cvPaging }); // initialize the page size with 10. cvPaging.pageSize = 10; // initialize the input value. document.getElementById('pagingInput').value = cvPaging.pageSize; // init the button status. updateNaviagteButtons(); // update the collectionview's pagesize according to the user's input. document.getElementById('pagingInput').addEventListener('blur', function () { var pagesize = this.value; if (!pagesize) { pagesize = 0; } else { pagesize = wijmo.Globalize.parseInt(pagesize); } cvPaging.pageSize = pagesize; updateNaviagteButtons(); }); // update the navigation buttons' status function updateNaviagteButtons() { if (cvPaging.pageSize <= 0) { document.getElementById('naviagtionPage').style.display = 'none'; return; } document.getElementById('naviagtionPage').style.display = 'block'; if (cvPaging.pageIndex === 0) { btnFirstPage.setAttribute('disabled', 'disabled'); btnPreviousPage.setAttribute('disabled', 'disabled'); btnNextPage.removeAttribute('disabled'); btnLastPage.removeAttribute('disabled'); } else if (cvPaging.pageIndex === (cvPaging.pageCount - 1)) { btnFirstPage.removeAttribute('disabled'); btnPreviousPage.removeAttribute('disabled'); btnLastPage.setAttribute('disabled', 'disabled'); btnNextPage.setAttribute('disabled', 'disabled'); } else { btnFirstPage.removeAttribute('disabled'); btnPreviousPage.removeAttribute('disabled'); btnNextPage.removeAttribute('disabled'); btnLastPage.removeAttribute('disabled'); } btnCurrentPage.innerHTML = (cvPaging.pageIndex + 1) + ' / ' + cvPaging.pageCount; } // commands: moving page. btnFirstPage.addEventListener('click', function () { // move to the first page. cvPaging.moveToFirstPage(); updateNaviagteButtons(); }); btnPreviousPage.addEventListener('click', function () { // move to the previous page. cvPaging.moveToPreviousPage(); updateNaviagteButtons(); }); btnNextPage.addEventListener('click', function () { // move to the next page. cvPaging.moveToNextPage(); updateNaviagteButtons(); }); btnLastPage.addEventListener('click', function () { // move to the last page. cvPaging.moveToLastPage(); updateNaviagteButtons(); });

Result (live):

追踪更改

CollectionView类可以追踪对数据的更改。这个功能在一些必须提交更改到服务器的情形是有用的。 要打开更改追踪的功能,把trackChanges属性改为true。 一旦你那样做了,CollectionView会跟踪数据的任何变化并且在三个数组中显示它们:

这个功能在下方通过一个FlexGrid展现。这个表格与CollectionView绑定,并把trackChanges设为真。 当你编辑表格时,这些更改会出现在下方的三个表格中。

<h5>Change the data here</h5> <div id="tcMainGrid"></div> <h5>See the changes here</h5> <h6>Items edited:</h6> <div id="tcEditedGrid" style="height:100px"></div> <h6>Items added:</h6> <div id="tcAddedGrid" style="height:100px"></div> <h6>Items removed:</h6> <div id="tcRemovedGrid" style="height:100px"></div>
// create collectionview, grids, the grid column layout var cvTrackingChanges = new wijmo.collections.CollectionView(getData(6)), tcMainGrid = new wijmo.grid.FlexGrid('#tcMainGrid'), // the flexGrid to edit the data tcEditedGrid = new wijmo.grid.FlexGrid('#tcEditedGrid'), // the flexGrid to record the edited items tcAddedGrid = new wijmo.grid.FlexGrid('#tcAddedGrid'), // the flexGrid to record the added items tcRemovedGrid = new wijmo.grid.FlexGrid('#tcRemovedGrid'), // the flexGrid to record the removed items columnsDefinition = [ { header: 'id', binding: 'id' }, { header: 'start', binding: 'start' }, { header: 'end', binding: 'end' }, { header: 'country', binding: 'country' }, { header: 'product', binding: 'product' }, { header: 'color', binding: 'color' }, { header: 'amount', binding: 'amount' }, { header: 'active', binding: 'active' } ]; // initialize the grids tcMainGrid.initialize({ allowAddNew: true, allowDelete: true, itemsSource: cvTrackingChanges }); tcEditedGrid.initialize({ isReadOnly: true, autoGenerateColumns: false, columns: columnsDefinition, itemsSource: cvTrackingChanges.itemsEdited }); tcAddedGrid.initialize({ isReadOnly: true, autoGenerateColumns: false, columns: columnsDefinition, itemsSource: cvTrackingChanges.itemsAdded }); tcRemovedGrid.initialize({ isReadOnly: true, autoGenerateColumns: false, columns: columnsDefinition, itemsSource: cvTrackingChanges.itemsRemoved }); // track changes of the collectionview cvTrackingChanges.trackChanges = true;

Result (live):

Change the data here
See the changes here
Items edited:
Items added:
Items removed: