CollectionView 101

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

Wijmo has a solid infrastructure based on a powerful and familiar data layer. The main data binding interface is ICollectionView. Wijmo includes several classes that implement ICollectionView. The most important is CollectionView, 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 in situations where you must submit changes to the server.

Getting Started

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

In this example, we used a FlexGrid control to show the collection.

Steps for getting started with CollectionView class in applications:

  1. Add references to Wijmo.
  2. Add markup to serve as the FlexGrid's host.
  3. Initialize the CollectionView instance and the FlexGrid instance(s) via Javascript.
  4. (Optional) Add some CSS to customize the grid's appearance.
HTML
<div id="gsGrid"></div>
JS
var cvGettingStarted = new wijmo.collections.CollectionView(getData(10)); var gsGrid = new wijmo.grid.FlexGrid('#gsGrid', { itemsSource: cvGettingStarted }); // create some random data function getData(cnt) { var data = [], dt = new Date(), countries = ['US', 'Germany', 'UK', 'Japan', 'Italy', 'Greece'], products = ['Widget', 'Gadget', 'Doohickey'], colors = ['Black', 'White', 'Red', 'Green', 'Blue']; for (var i = 0; i < cnt; i++) { var date = new Date(dt.getFullYear(), i % 12, 25, i % 24, i % 60, i % 60), countryId = Math.floor(Math.random() * countries.length), productId = Math.floor(Math.random() * products.length), colorId = Math.floor(Math.random() * colors.length); data.push({ id: i, start: date, end: date, country: countries[countryId], product: products[productId], color: colors[colorId], amount: Math.random() * 10000 - 5000, active: i % 4 === 0, }); } return data; }
CSS
/* 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):

Current Record Management

You can use the CollectionView class to manage the current record.

This example shows how you can use the API provided in the CollectionView class to change the current record by clicking a row in the grid or by clicking buttons.

We use the currentPosition property to get the position of the current record in the collection, and use the following methods to change the current position:

When the position changes, we use the currentChanging and currentChanged events to track it. We can cancel the change of position in the currentChanging event.

Click the Next button to set the next record as the current one. Click the Previous button to set the previous record as the current one. Click the Stop at 4th Row button to prevent the current record from being changed once it reaches the 4th row. Click the Clear button to remove the stop and allow the current records to be changed freely.

HTML
<div class="row-fluid well btn-group"> <button class="btn btn-default" id="btnCRMMoveNext">Move To Next</button> <button class="btn btn-default" id="btnCRMMovePrev">Move To Previous</button> <button class="btn btn-default" id="btnCRMStop4">Stop at the 4th Row</button> <button class="btn btn-default" id="btnCRMReset">Clear Stopping</button> </div> <div id="crmGrid"></div>
JS
var cvCRM = new wijmo.collections.CollectionView(getData(10)); var crmGrid = new wijmo.grid.FlexGrid('#crmGrid', { isReadOnly: true, selectionMode: 'Row', itemsSource: cvCRM }); // handle prev/next buttons document.getElementById('btnCRMMoveNext').addEventListener('click', function () { cvCRM.moveCurrentToNext(); }); document.getElementById('btnCRMMovePrev').addEventListener('click', function () { cvCRM.moveCurrentToPrevious(); }); // handle the currentChanging event to restrict navigation document.getElementById('btnCRMStop4').addEventListener('click', function () { cvCRM.currentChanging.addHandler(stopCurrentIn4th); }); // remove navigation restriction document.getElementById('btnCRMReset').addEventListener('click', function () { cvCRM.currentChanging.removeHandler(stopCurrentIn4th); }); // restrict navigation at the fourth item function stopCurrentIn4th(sender, e) { e.cancel = sender.currentPosition == 3; }

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 chosen in the first list. You can also specify the sorting order in the second list.

HTML
<div class="row-fluid well row"> <div class="col-md-8"> <div id="sortingFieldNameList"></div> </div> <div class="col-md-4"> <div id="sortingOrderList"></div> </div> </div> <div id="sortingGrid"></div>
JS
var cvSorting = new wijmo.collections.CollectionView(getData(10)); var sortingGrid = new wijmo.grid.FlexGrid('#sortingGrid', { isReadOnly: true, allowSorting: false, itemsSource: cvSorting }); // select sort field and direction var sortingFieldNameList = new wijmo.input.ComboBox('#sortingFieldNameList', { itemsSource: getNames(), placeholder: 'Choose Field to Sort On', isRequired: false, selectedIndex: -1, textChanged: applySort }); var sortingOrderList = new wijmo.input.ComboBox('#sortingOrderList', { itemsSource: ['Ascending', 'Descending'], textChanged: applySort }); // apply the sort function applySort() { var sds = cvSorting.sortDescriptions, fieldName = sortingFieldNameList.text, ascending = sortingOrderList.text == 'Ascending'; // remove old sort sds.splice(0, sds.length); // add new sort if (fieldName) { sds.push(new wijmo.collections.SortDescription(fieldName, ascending)); } }

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 should 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 change the filter text, the grid is updated to show the filtered data.

HTML
<div class="row-fluid well"> <input id="filteringInput" class="form-control app-pad" placeholder="Country Filter (case-insensitive)" /> </div> <div id="filteringGrid"></div>
JS
var cvFiltering = new wijmo.collections.CollectionView(getData(20)); var filteringGrid = new wijmo.grid.FlexGrid('#filteringGrid', { isReadOnly: true, itemsSource: cvFiltering }); // apply filter when input changes var toFilter = null; document.getElementById('filteringInput').addEventListener('input', function () { if (toFilter) { clearTimeout(toFilter); } toFilter = setTimeout(function () { toFilter = null; if (cvFiltering.filter == filterFunction) { cvFiltering.refresh(); } else { cvFiltering.filter = filterFunction; } }, 500); }); // filter function for the collection view. function filterFunction(item) { var filter = filteringInput.value.toLowerCase(); return filter ? item.country.toLowerCase().indexOf(filter) > -1 : true; }

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. You can find the rendering codes for these in the method initTBody. The corresponding code snippet locates in line 116.

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.

HTML
<div class="row-fluid well"> <label for="groupingFieldNameList">Add Group</label> <div id="groupingFieldNameList"></div> <button id="btnClearGroups" class="btn btn-default">Clear Groups</button> </div> <div id="groupingGrid"></div>
JS
var cvGrouping = new wijmo.collections.CollectionView(getData(20)); var groupingGrid = new wijmo.grid.FlexGrid('#groupingGrid', { isReadOnly: true, itemsSource: cvGrouping }); // initialize the field combo and listen to changes var groupingFieldNameList = new wijmo.input.ComboBox('#groupingFieldNameList', { itemsSource: getNames(), placeholder: 'Select field', isRequired: false, selectedIndex: -1, textChanged: applyGrouping }); // clear groups document.getElementById('btnClearGroups').addEventListener('click', function () { groupingFieldNameList.text = ''; }); // apply the selected grouping function applyGrouping() { var gd = cvGrouping.groupDescriptions, fieldName = groupingFieldNameList.text; // no field? clear grouping if (!fieldName) { gd.splice(0, gd.length); return; } // add group description if not already defined if (!groupDefined(fieldName)) { 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 groupDefined(propName) { var gd = cvGrouping.groupDescriptions; for (var i = 0; i < gd.length; i++) { if (gd[i].propertyName === propName) { return true; } } return false; }

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, the grid will be refreshed according to the tracked item array.

HTML
<div id="editingGrid"></div> <div class="row-fluid well"> <button class="btn btn-default" id="btnEdit"> Edit Detail... </button> <button class="btn btn-default" id="btnAdd"> Add... </button> <button class="btn btn-default" id="btnDelete"> Delete </button> </div> <!-- dialog for editing item details --> <div id="dlgDetail" class="modal-dialog"> <div class="modal-content"> <div class="modal-header"> <button type="button" class="close wj-hide"> × </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" /> </dd> <dt>Start Date</dt> <dd> <input class="form-control" id="edtStart" /> </dd> <dt>End Start</dt> <dd> <input class="form-control" id="edtEnd" /> </dd> <dt>Country</dt> <dd> <input class="form-control" id="edtCountry" /> </dd> <dt>Product</dt> <dd> <input class="form-control" id="edtProduct" /> </dd> <dt>Color</dt> <dd> <input class="form-control" id="edtColor" /> </dd> <dt>Amount</dt> <dd> <input class="form-control" id="edtAmount" /> </dd> <dt>Active</dt> <dd> <input id="edtActive" type="checkbox" /> </dd> </dl> </div> <div class="modal-footer"> <button type="button" class="btn btn-primary wj-hide-ok"> OK </button> <button type="button" class="btn btn-warning wj-hide-cancel"> Cancel </button> </div> </div> </div>
JS
var cvEditing = new wijmo.collections.CollectionView(getData(10), { // define newItemCreator with proper unique id newItemCreator: function () { var item = getData(1)[0]; item.id = wijmo.getAggregate(wijmo.Aggregate.Max, cvEditing.sourceCollection, 'id') + 1; return item; } }); var editingGrid = new wijmo.grid.FlexGrid('#editingGrid', { selectionMode: wijmo.grid.SelectionMode.Row, itemsSource: cvEditing }); // create dialog used to edit items var dlgDetail = new wijmo.input.Popup('#dlgDetail', { removeOnHide: false }); // start editing item document.getElementById('btnEdit').addEventListener('click', function () { var editItem = cvEditing.currentItem; cvEditing.editItem(editItem); showDialog(editItem, 'Edit Item'); }); // start adding item document.getElementById('btnAdd').addEventListener('click', function () { var editedItem = cvEditing.addNew(); showDialog(editedItem, 'Add Item'); }); // delete current item document.getElementById('btnDelete').addEventListener('click', function () { cvEditing.remove(cvEditing.currentItem); }); // populate the dialog with the current item's values function showDialog(item, title) { // update dialog inputs setInputValue('edtID', item.id != null ? wijmo.Globalize.format(item.id) : ''); setInputValue('edtStart', item.start != null ? wijmo.Globalize.format(item.start) : ''); setInputValue('edtEnd', item.end != null ? wijmo.Globalize.format(item.end) : ''); setInputValue('edtCountry', item.country != null ? item.country : ''); setInputValue('edtProduct', item.product != null ? item.product : ''); setInputValue('edtColor', item.color != null ? item.color : ''); setInputValue('edtAmount', item.amount != null ? wijmo.Globalize.format(item.amount) : ''); setInputValue('edtActive', item.active); title.innerHTML = title; // show dialog dlgDetail.show(true, function (s) { if (s.dialogResult == 'wj-hide-ok') { // commit changes var item = cvEditing.currentEditItem || cvEditing.currentAddItem; if (item) { updateItem(item); } cvEditing.commitEdit(); cvEditing.commitNew(); } else { // cancel changes cvEditing.cancelEdit(); cvEditing.cancelNew(); } }); } // update item with values from the dialog function updateItem(item) { setItemValue(item, 'id', 'edtID', wijmo.DataType.Number); setItemValue(item, 'start', 'edtStart', wijmo.DataType.Date); setItemValue(item, 'end', 'edtEnd', wijmo.DataType.Date); setItemValue(item, 'country', 'edtCountry', wijmo.DataType.String); setItemValue(item, 'product', 'edtProduct', wijmo.DataType.String); setItemValue(item, 'color', 'edtColor', wijmo.DataType.String); setItemValue(item, 'amount', 'edtAmount', wijmo.DataType.Number); setItemValue(item, 'active', 'edtActive', wijmo.DataType.Boolean); } // set the value of an input element function setInputValue(id, value) { var input = document.getElementById(id); if (input.type == 'checkbox') { input.checked = value; } else { input.value = value; } } // set the value of an input element function setItemValue(item, prop, id, dataType) { var input = document.getElementById(id); item[prop] = input.type == 'checkbox' ? input.checked : wijmo.changeType(input.value, dataType) }

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.

HTML
<div class="row-fluid well"> <label for="pagingInput"> Page Size </label> <input id="pagingInput"> <br/> <div id="currentPagePanel"> <label for="navigationPage"> Current Page </label> <div id="navigationPage" class="btn-group"> <button type="button" class="btn btn-default" id="btnFirstPage"> <span class="glyphicon glyphicon-fast-backward"></span> </button> <button type="button" class="btn btn-default" id="btnPreviousPage"> <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="btnNextPage"> <span class="glyphicon glyphicon-step-forward"></span> </button> <button type="button" class="btn btn-default" id="btnLastPage"> <span class="glyphicon glyphicon-fast-forward"></span> </button> </div> </div> </div> <div id="pagingGrid"></div>
JS
var cvPaging = new wijmo.collections.CollectionView(getData(55), { pageSize: 10, pageChanged: function () { updatePagingButtons(); } }); var pagingGrid = new wijmo.grid.FlexGrid('#pagingGrid', { isReadOnly: true, itemsSource: cvPaging }); // edit page size var pagingInput = new wijmo.input.InputNumber('#pagingInput', { min: 0, max: 20, step: 5, valueChanged: function (s, e) { cvPaging.pageSize = s.value; updatePagingButtons(); }, value: cvPaging.pageSize }); // page navigation document.getElementById('btnFirstPage').addEventListener('click', function () { cvPaging.moveToFirstPage(); });; document.getElementById('btnPreviousPage').addEventListener('click', function () { cvPaging.moveToPreviousPage(); }); document.getElementById('btnNextPage').addEventListener('click', function () { cvPaging.moveToNextPage(); });; document.getElementById('btnLastPage').addEventListener('click', function () { cvPaging.moveToLastPage(); });; // update the navigation buttons function updatePagingButtons() { // show/hide navigation bar var nav = document.getElementById('currentPagePanel'); if (cvPaging.pageSize <= 0) { nav.style.display = 'none'; return; } nav.style.display = ''; // show current page document.getElementById('btnCurrentPage').textContent = (cvPaging.pageIndex + 1) + ' / ' + cvPaging.pageCount; // first/prev var disabled = cvPaging.pageIndex == 0 ? 'disabled' : null; wijmo.setAttribute(document.getElementById('btnFirstPage'), 'disabled', disabled); wijmo.setAttribute(document.getElementById('btnPreviousPage'), 'disabled', disabled); // next/last disabled = cvPaging.pageIndex >= cvPaging.pageCount - 1 ? 'disabled' : null; wijmo.setAttribute(document.getElementById('btnNextPage'), 'disabled', disabled); wijmo.setAttribute(document.getElementById('btnLastPage'), 'disabled', disabled); }

Result (live)


Change Tracking

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.

HTML
<h5>Change the data here</h5> <div id="tcMainGrid"></div> <h5>See the changes here</h5> <h6>Items edited:</h6> <div id="tcEditedGrid" class="tcGrid" style="height:100px"></div> <h6>Items added:</h6> <div id="tcAddedGrid" class="tcGrid" style="height:100px"></div> <h6>Items removed:</h6> <div id="tcRemovedGrid" class="tcGrid" style="height:100px"></div>
JS
// create CollectionView with change tracking enabled var cvTrackingChanges = new wijmo.collections.CollectionView(getData(6), { trackChanges: true }); // create a grid to show and edit the data var tcMainGrid = new wijmo.grid.FlexGrid('#tcMainGrid', { allowAddNew: true, allowDelete: true, itemsSource: cvTrackingChanges }); // create gridS to show the changes (edits, additions, removals) var colDefs = [ { 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' } ]; var tcEditedGrid = new wijmo.grid.FlexGrid('#tcEditedGrid', { isReadOnly: true, autoGenerateColumns: false, columns: colDefs, itemsSource: cvTrackingChanges.itemsEdited }); var tcAddedGrid = new wijmo.grid.FlexGrid('#tcAddedGrid', { isReadOnly: true, autoGenerateColumns: false, columns: colDefs, itemsSource: cvTrackingChanges.itemsAdded }); var tcRemovedGrid = new wijmo.grid.FlexGrid('#tcRemovedGrid', { isReadOnly: true, autoGenerateColumns: false, columns: colDefs, itemsSource: cvTrackingChanges.itemsRemoved });

Result (live):

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