CollectionView 101

This page shows how to get started with Wijmo's CollectionView class.

Wijmo has a solid infrastructure based on a powerful data layer that is familiar to .NET developers. The main data binding interface is the ICollectionView. Wijmo includes several classes that implement ICollectionView. The most basic is the CollectionView class, which uses regular JavaScript arrays as data sources.

The CollectionView class implements the following interfaces:

The CollectionView class can keep track of changes made to the data. This feature is useful for submitting changes to the server.

Getting Started

To use the CollectionView class, start by declaring it and passing in a regular array as the data source. Then access the CollectionView using the view model's collectionView property.

In this example, we show the CollectionView instance in a Wijmo FlexGrid.

Steps to getting started with the CollectionView class in KnockoutJS applications:

  1. Add references to KnockoutJS, Wijmo, and the Wijmo KnockoutJS bindings.
  2. Add table (or FlexGrid) to the page and bind it to the CollectionView data.
  3. Add a view model to provide data and logic.
  4. (Optional) Add some CSS to customize the grid's appearance.
<!DOCTYPE html> <html> <head> <link rel="stylesheet" type="text/css" href="css/wijmo.css"/> <script src="scripts/knockout.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.knockout.js" type="text/javascript"></script> </head> <body> <div id="gettingStarted"> <div data-bind="wjFlexGrid: { itemsSource: collectionView }"></div> </div> </body> </html>
function basicVM(data) { this.collectionView = new wijmo.collections.CollectionView(data); } ko.applyBindings(new basicVM(dataSvc.getData(100)), document.getElementById('gettingStarted'));

Result (live):

Current Record Management

As implementing the interface ICollectionView, CollectionView can manage the current record.

This example shows how you can manage the current record through APIs provided by the CollectionView class.

In this case, we use the properties currentPosition to obtain the current record position in the collection. We also use the methods moveCurrentTo(item), moveCurrentToFirst(), moveCurrentToLast(), moveCurrentToNext(), moveCurrentToPosition(index) and moveCurrentToPrevious() to change the current position. When the current is changed, we use the events currentChanging and currentChanged to track it. We can cancel the current changing in the event currentChanging.

Notes: Click the "Move To Next" button to move the current to the next one. Click the "Move to Previous" to move the current to the previous on. Clicking the "Stop in 4th Row" button will cause the current is forbidden to be changed when it locates in the 4th row. Then clicking the "Clear Stopping" button will let the current be changed freely.

<div class="well btn-group"> <button class="btn btn-default" data-bind="click: function() { collectionView.moveCurrentToNext() }"> Move Next </button> <button class="btn btn-default" data-bind="click: function() { collectionView.moveCurrentToPrevious() }"> Move Previous </button> <button class="btn btn-default" data-bind="click: addStopping">Stop in 4th Row</button> <button class="btn btn-default" data-bind="click: clearStopping">Clear Stopping</button> </div> <div data-bind="wjFlexGrid: { itemsSource: collectionView }"></div>
this.collectionView = new wijmo.collections.CollectionView(data); this.addStopping = function () { this.collectionView.currentChanging.addHandler(stoppingHandler); }; this.clearStopping = function () { this.collectionView.currentChanging.removeHandler(stoppingHandler); }; function stoppingHandler (sender, args) { if (sender.currentPosition === 3) { args.cancel = true; } }

Result (live):

Sorting

The CollectionView class supports sorting through the ICollectionView interface, which is identical to the one in .NET. To enable sorting, add one or more sortDescriptions objects to the CollectionView.sortDescriptions property. Then the sorted result can be obtained from the CollectionView.items property.

SortDescription objects are flexible, allowing you to sort data based on value in ascending or descending order. In the sample below, you can sort the collection based on the corresponding field value choosed in the first list. You can also specify the sorting order in the second list.

<div class="row well"> <div class="col-md-8"> <select class="form-control" data-bind="options: names, value: currentSort, optionsCaption: 'Please choose the field you wish to sort by...'"></select> </div> <div class="col-md-4"> <select class="form-control" data-bind="value: sortOrder"> <option value="true" selected>Ascending</option> <option value="false">Descending</option> </select> </div> </div> <div data-bind="wjFlexGrid: { itemsSource: collectionView }"></div>
this.collectionView = new wijmo.collections.CollectionView(data); this.names = names; this.currentSort = ko.observable(); this.currentSort.subscribe(applySort, this); this.sortOrder = ko.observable(); this.sortOrder.subscribe(applySort, this); // perform CollectionView sorting when dropdown value changes function applySort(newVal) { var currentSort = this.currentSort(), sortOrder = this.sortOrder() === 'true', cv = this.collectionView; if (!currentSort) return; cv.sortDescriptions.clear(); cv.sortDescriptions.push(new wijmo.collections.SortDescription(currentSort, sortOrder)); }

Result (live):

Filtering

The CollectionView class supports filtering through the ICollectionView interface, which is identical to the one in .NET. To enable filtering, set the CollectionView.filter property to a function that determines which objects to be included in the view.

In this example, we create a filter for the country, and get the filter value from the input control. When you input the filter, the grid will be refreshed and render the fitlered data.

<div class="well"> <input type="text" class="form-control" data-bind="textInput: filterText" placeholder="Filter by 'country'" /> </div> <div data-bind="wjFlexGrid: { itemsSource: collectionView }"></div>
this.collectionView = new wijmo.collections.CollectionView(data); this.collectionView.filter = filterFn.bind(this); this.filterText = ko.observable(''); this.filterText.subscribe(function(newVal) { this.collectionView.refresh(); }, this); // function used to filter the CollectionView function filterFn(dataItem) { if (!this.hasOwnProperty('filterText')) return true; var filterText = this.filterText().toLowerCase(); return !filterText || dataItem.country.toLowerCase().indexOf(filterText) > -1; }

Result (live):

Grouping

The CollectionView class supports grouping through the ICollectionView interface, which is identical to the one in .NET. To enable grouping, add one or more GroupDescription objects to the CollectionView.groupDescriptions property, and ensure that the grid's showGroups property is set to true when creating the grid instance(the default value is false.).

GroupDescription objects are flexible, allowing you to group data based on value or on grouping functions.

The example below groups the collection by the field which you select from the list. The grid shows not only the items content, but also the group information: the group name and the average value of amount in the group.

Notes: Selecting one item in the list will add a new instance of GroupDescription. If the GroupDescription already exists, nothing happens. In order to clear the group setting, select the first item in the list.

<div class="well"> <select class="form-control" data-bind="options: names, value: currentGroup, optionsCaption: 'Please choose the field you wish to group by...'"></select> </div> <div data-bind="wjFlexGrid: { itemsSource: collectionView }"></div>
this.collectionView = new wijmo.collections.CollectionView(data); this.names = names; this.currentGroup = ko.observable(); this.currentGroup.subscribe(function (newVal) { var currentGroup = newVal, cv = this.collectionView; // clear groups when the "placeholder" is selected if (!currentGroup) { cv.groupDescriptions.clear(); return; } // prevent double grouping var exists = cv.groupDescriptions.some(function (item) { return item.propertyName === currentGroup; }); if (exists) return; // perform grouping cv.groupDescriptions.push(new wijmo.collections.PropertyGroupDescription(currentGroup)); }, this);

Result (live):

Editing

As implementing the interface IEditableCollectionView, the CollectionView class supports editing.

This sample shows how you can update, add, and remove the specified item in the collection.

In this sample, you can select the row in the grid and press the Edit Detail button to start editing. After finishing editing in the popup dialog, press the OK button to commit your updating. If you want to add a new record to the collection, press the Add button and customize the item content in the popup dialog. Then press the OK button to commit your adding. If you don't want to update/add the record, just press the Cancel button in the dialog. Select the row and press the Delete button will let you remove the record from the collection.

After updating, adding, and removing records, the grid will be refreshed accordingly.

<div data-bind="wjFlexGrid: { itemsSource: collectionView() }"></div> <div class="well"> <button class="btn btn-default" data-toggle="modal" data-target="#editingModal" data-bind="disable: !currentItem()">Edit Details</button> <button class="btn btn-default" data-toggle="modal" data-target="#editingModal" data-bind="click: addItem">Add</button> <button class="btn btn-default" data-bind="click: collectionView().remove(currentItem()), disable: !currentItem()">Delete</button> </div> <div class="modal" id="editingModal" data-backdrop="static" data-keyboard="false"> <div class="modal-dialog"> <div class="modal-content"> <div class="modal-header"> <button class="close" data-dismiss="modal">&times;</button> <h4>Edit Item</h4> </div> <div class="modal-body"> <div class="form-horizontal"> <div class="form-group"> <label class="col-sm-3">ID</label> <div class="col-sm-9"> <input class="form-control" data-bind="value: currentItem().id" disabled /> </div> </div> <div class="form-group"> <label class="col-sm-3">Start Date</label> <div class="col-sm-9"> <input class="form-control" data-bind="value: currentItem().start" placeholder="Start Date" /> </div> </div> <div class="form-group"> <label class="col-sm-3">End Date</label> <div class="col-sm-9"> <input class="form-control" data-bind="value: currentItem().end" placeholder="End Date" /> </div> </div> <div class="form-group"> <label class="col-sm-3">Country</label> <div class="col-sm-9"> <input class="form-control" data-bind="value: currentItem().country" placeholder="Country" /> </div> </div> <div class="form-group"> <label class="col-sm-3">Product</label> <div class="col-sm-9"> <input class="form-control" data-bind="value: currentItem().product" placeholder="Product" /> </div> </div> <div class="form-group"> <label class="col-sm-3">Color</label> <div class="col-sm-9"> <input class="form-control" data-bind="value: currentItem().color" placeholder="Color" /> </div> </div> <div class="form-group"> <label class="col-sm-3">Amount</label> <div class="col-sm-9"> <input class="form-control" data-bind="value: currentItem().amount" placeholder="Amount" /> </div> </div> <div class="form-group"> <label class="col-sm-3">Active</label> <div class="col-sm-9"> <div class="checkbox"> <label> <input type="checkbox" data-bind="checked: currentItem().active" /> </label> </div> </div> </div> </div> </div> <div class="modal-footer"> <button class="btn btn-primary" data-dismiss="modal" data-bind="click: commitUpdate">OK</button> <button class="btn btn-warning" data-dismiss="modal" data-bind="click: cancelUpdate">Cancel</button> </div> </div> </div> </div>
var collectionView = new wijmo.collections.CollectionView(data); collectionView.newItemCreator = function () { // return object with an ID property return { id: wijmo.getAggregate(wijmo.Aggregate.Max, collectionView.sourceCollection, 'id') + 1 }; }; this.collectionView = ko.observable(collectionView); this.currentItem = ko.computed(function () { return this.collectionView().currentItem; }, this); this.addItem = function () { collectionView.addNew(); }; // commit CollectionView's changes this.commitUpdate = function () { collectionView.commitEdit(); collectionView.commitNew(); collectionView.refresh(); }; // cancel CollectionView's changes this.cancelUpdate = function () { collectionView.cancelEdit(); collectionView.cancelNew(); collectionView.refresh(); }; collectionView.collectionChanged.addHandler(notifyChange.bind(this)); collectionView.currentChanged.addHandler(notifyChange.bind(this)); function notifyChange() { this.collectionView.valueHasMutated(); }

Result (live):

Paging

The CollectionView class supports paging through the IPagedCollectionView interface, which is nearly identical to the one in .NET. To enable paging, set the IPagedCollectionView.pageSize property to the number of items you want on each page, and provide a UI for navigating the pages.

In this example, we use JavaScript to show 10 items per page. You can customize it in the text box. We add navigation buttons, and call IPagedCollectionView methods in the button click. Note that we use the pageIndex and pageCount properties to show the current page and total number of pages. You can customize the page size in the first text box. Let it be empty or 0 to make CollectionView no paging. Then the navigation buttons will be invisible.

<div class="row well"> <div class="col-md-5"> <input type="text" class="form-control" data-bind="textInput: pageSize" placeholder="Please enter the page size..." /> </div> <div class="col-md-7"> <div class="btn-group" data-bind="visible: pageSize() > 0"> <button class="btn btn-default" data-bind="click: collectionView().moveToFirstPage.bind(collectionView()), disable: isBeginning"> <span class="glyphicon glyphicon-fast-backward"></span> </button> <button class="btn btn-default" data-bind="click: collectionView().moveToPreviousPage.bind(collectionView()), disable: isBeginning"> <span class="glyphicon glyphicon-step-backward"></span> </button> <button class="btn btn-default" disabled> <span data-bind="text: collectionView().pageIndex + 1"></span> / <span data-bind="text: collectionView().pageCount"></span> </button> <button class="btn btn-default" data-bind="click: collectionView().moveToNextPage.bind(collectionView()), disable: isEnd"> <span class="glyphicon glyphicon-step-forward"></span> </button> <button class="btn btn-default" data-bind="click: collectionView().moveToLastPage.bind(collectionView()), disable: isEnd"> <span class="glyphicon glyphicon-fast-forward"></span> </button> </div> </div> </div> <div data-bind="wjFlexGrid: { itemsSource: collectionView() }"></div>
var collectionView = new wijmo.collections.CollectionView(data); collectionView.pageSize = 10; this.collectionView = ko.observable(collectionView); // computed property to help get/set the CollectionView's pageSize property this.pageSize = ko.computed({ read: function () { return this.collectionView().pageSize; }, write: function (newVal) { this.collectionView().pageSize = Math.abs(parseInt(newVal)) || 0; } }, this); // determine if previous/first page buttons should be disabled/enabled this.isBeginning = ko.computed(function () { return this.collectionView().pageIndex === 0; }, this); // determine if next/last page buttons should be disabled/enabled this.isEnd = ko.computed(function () { return (this.collectionView().pageIndex + 1) === this.collectionView().pageCount; }, this); collectionView.collectionChanged.addHandler(notifyChange.bind(this)); collectionView.currentChanged.addHandler(notifyChange.bind(this)); collectionView.pageChanged.addHandler(notifyChange.bind(this)); function notifyChange() { this.collectionView.valueHasMutated(); }

Result (live):

Tracking Changes

The CollectionView class can keep track of changes made to the data. It is useful in situations where you must submit changes to the server. To turn on change tracking, set the trackChanges property to true. Once you do that, the CollectionView keeps track of any changes made to the data and exposes them in three arrays:

This feature is demonstrated below using a FlexGrid. The grid is bound to a CollectionView with trackChanges set to true.

<h5>Change the data here</h5> <div data-bind="wjFlexGrid: { itemsSource: collectionView, allowAddNew: true, allowDelete: true }" style="height: 300px;"> <div data-bind="wjFlexGridColumn: { header: 'id', binding: 'id' }"></div> <div data-bind="wjFlexGridColumn: { header: 'start', binding: 'start' }"></div> <div data-bind="wjFlexGridColumn: { header: 'end', binding: 'end' }"></div> <div data-bind="wjFlexGridColumn: { header: 'country', binding: 'country' }"></div> <div data-bind="wjFlexGridColumn: { header: 'product', binding: 'product' }"></div> </div> <h6>Items added:</h6> <div data-bind="wjFlexGrid: { itemsSource: collectionView.itemsAdded }" style="height: 150px;"> <div data-bind="wjFlexGridColumn: { header: 'id', binding: 'id' }"></div> <div data-bind="wjFlexGridColumn: { header: 'start', binding: 'start' }"></div> <div data-bind="wjFlexGridColumn: { header: 'end', binding: 'end' }"></div> <div data-bind="wjFlexGridColumn: { header: 'country', binding: 'country' }"></div> <div data-bind="wjFlexGridColumn: { header: 'product', binding: 'product' }"></div> </div> <h6>Items edited:</h6> <div data-bind="wjFlexGrid: { itemsSource: collectionView.itemsEdited }" style="height: 150px;"> <div data-bind="wjFlexGridColumn: { header: 'id', binding: 'id' }"></div> <div data-bind="wjFlexGridColumn: { header: 'start', binding: 'start' }"></div> <div data-bind="wjFlexGridColumn: { header: 'end', binding: 'end' }"></div> <div data-bind="wjFlexGridColumn: { header: 'country', binding: 'country' }"></div> <div data-bind="wjFlexGridColumn: { header: 'product', binding: 'product' }"></div> </div> <h6>Items removed:</h6> <div data-bind="wjFlexGrid: { itemsSource: collectionView.itemsRemoved }" style="height: 150px;"> <div data-bind="wjFlexGridColumn: { header: 'id', binding: 'id' }"></div> <div data-bind="wjFlexGridColumn: { header: 'start', binding: 'start' }"></div> <div data-bind="wjFlexGridColumn: { header: 'end', binding: 'end' }"></div> <div data-bind="wjFlexGridColumn: { header: 'country', binding: 'country' }"></div> <div data-bind="wjFlexGridColumn: { header: 'product', binding: 'product' }"></div> </div>
var collectionView = new wijmo.collections.CollectionView(data); collectionView.trackChanges = true; this.collectionView = collectionView;

Result (live):

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