CollectionView 101

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 view using the items property.

In this example, we show the CollectionView instance in an HTML table.

Steps for getting started with the CollectionView class in AngularJS applications:

  1. Add references to AngularJS, Wijmo, and the Wijmo AngularJS directives.
  2. Optionally add a reference to FlexGrid.
  3. Add references to your app, services, filters, and directives modules.
  4. Add a table (or FlexGrid) to the page and bind it to the CollectionView data.
  5. Add a controller to provide data and logic.
  6. Optionally add some CSS to customize the table's appearance.

Notes: In the Tracking Changes sample below, we use FlexGrid instead of a table, so you can see the difference in the markup.

HTML
<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"> <div ng-controller="appGSCtrl"> <div class="sGrid"> <table class="table table-condensed table-bordered"> <thead> <tr class="active"> <th class="text-center" ng-repeat="fieldName in fieldNames"> {​{fieldName}} </th> </tr> </thead> <tbody> <tr ng-repeat="item in cvGettingStarted.items"> <td class="text-center" ng-repeat="name in fieldNames"> {​{item[name] | globalize}} </td> </tr> </tbody> </table> </div> </div> </body> </html>
JS
/* define the controller for getting started */ var app = angular.module('app'); app.controller('appGSCtrl', function ($scope, dataSvc) { var cv = new wijmo.collections.CollectionView(dataSvc.getData(10)); $scope.fieldNames = dataSvc.getNames(); $scope.viewItems = cv.items; });
CSS
/* set default grid height and some shadow */ .sGrid { background-color: #fff; box-shadow: 4px 4px 10px 0 rgba(50, 50, 50, 0.75); height: 300px; margin-bottom: 12px; overflow: auto; }

Result (live):

{{fieldName}}
{{item[name] | globalize}}

Current Record Management

Since it implements the ICollectionView interface, 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 ng-controller="appCRMCtrl"> <div class="row-fluid well btn-group"> <button class="btn btn-default" ng-click="cvCRM.moveCurrentToNext()"> Next</button> <button class="btn btn-default" ng-click="cvCRM.moveCurrentToPrevious()"> Previous</button> <button class="btn btn-default" ng-click="stopCurrent()"> Stop at 4th Row</button> <button class="btn btn-default" ng-click="reset()"> Clear</button> </div> <div class="sGrid"> <table class="table table-condensed table-bordered"> <thead> <tr class="active"> <th class="text-center" ng-repeat="fieldName in fieldNames"> {​{fieldName}}</th> </tr> </thead> <tbody> <tr ng-repeat="item in cvCRM.items" ng-class="{success: item == cvCRM.currentItem}" ng-click="cvCRM.moveCurrentTo(item)"> <td class="text-center" ng-repeat="name in fieldNames"> {​{item[name] | globalize}}</td> </tr> </tbody> </table> </div> </div>
JS
/* Define the controller for current record management */ var app = angular.module('app'); app.controller('appCRMCtrl', function ($scope, dataSvc) { var cv = new wijmo.collections.CollectionView(dataSvc.getData(10)); $scope.cvCRM = cv; $scope.fieldNames = dataSvc.getNames(); // Forbid changing the current record when the 4th one is current. var stopCurrentIn4th = function (sender, e) { // When the current record is the 4th record, stop moving. if (sender.currentPosition === 3) { e.cancel = true; } } $scope.stopCurrent = function () { cv.currentChanging.addHandler(stopCurrentIn4th); }; // Restore the ability to change the current record. $scope.reset = function () { cv.currentChanging.removeHandler(stopCurrentIn4th); }; });

Result (live):

{{fieldName}}
{{item[name] | globalize}}

Sorting

The CollectionView class, like the one in .NET, implements the ICollectionView interface to support sorting. To enable sorting, add one or more SortDescription objects to an array and pass it to the CollectionView.sortDescriptions property. Now you can get the sorted results from the CollectionView.items property.

SortDescription objects are flexible, allowing you to sort data based on a value in ascending or descending order. In the sample below, click a property in the grid header to sort by that property in ascending order. Click it again to sort by that property in descending order.

HTML
<div ng-controller="appSortingCtrl"> <div class="sGrid"> <table class="table table-condensed table-bordered"> <thead> <tr class="active"> <th class="text-center" ng-repeat="fieldName in fieldNames" ng-click="toggleSort(fieldName)" style="cursor:pointer"> {​{fieldName}} <span style="color: red"> {​{getSort(fieldName)}}</span> </th> </tr> </thead> <tbody> <tr ng-repeat="item in cvSorting.items"> <td class="text-center" ng-repeat="name in fieldNames"> {​{item[name] | globalize}}</td> </tr> </tbody> </table> </div> </div>
JS
/* Define the controller for sorting. */ var app = angular.module('app'); app.controller('appSortingCtrl', function ($scope, dataSvc) { // Initialize the CollectionView. var cv = new wijmo.collections.CollectionView(dataSvc.getData(10)); // Initialize the scope data. $scope.cvSorting = cv; $scope.fieldNames = dataSvc.getNames(); // Sorting $scope.toggleSort = function (fieldName) { // Get all of the sort descriptions. var sd = cv.sortDescriptions; var ascending = true; // Find whether the field is sorted. if (sd.length > 0 && sd[0].property === fieldName) { // If sorting is found, toggle the sort order. ascending = !sd[0].ascending; } // Create a new SortDescription object. var sdNew = new wijmo.collections.SortDescription(fieldName, ascending); // Remove any old sort descriptions and add the newly created one. sd.splice(0, sd.length, sdNew); }; // Get the sort label. $scope.getSort = function (propName) { var sd = cv.sortDescriptions; if (sd.length > 0 && sd[0].property === propName) { return sd[0].ascending ? '▲' : '▼'; } return ''; }; });

Result (live):

{{fieldName}}{{getSort(fieldName)}}
{{item[name] | globalize}}

Filtering

The CollectionView class, like the one in .NET, implements the ICollectionView interface to support filtering. To enable filtering, set the CollectionView.filter property to a function that determines which objects to include in the view.

In this example, we create a filter for the country, and get the filter value from the input control. When you enter a value by which to filter, the grid refreshes and shows only the filtered data.

HTML
<div ng-controller="appFilteringCtrl"> <div class="sGrid"> <table class="table table-condensed table-bordered"> <thead> <tr class="active"> <th class="text-center" ng-repeat="fieldName in fieldNames"> {​{fieldName}} </th> </tr> </thead> <tbody> <tr ng-repeat="item in cvFiltering.items"> <td class="text-center" ng-repeat="name in fieldNames"> {​{item[name] | globalize}} </td> </tr> </tbody> </table> </div> </div>
JS
/* define the controller for filtering */ var app = angular.module('app'); app.controller('appFilteringCtrl', function ($scope, dataSvc) { // initialize the collectionview var cv = new wijmo.collections.CollectionView(dataSvc.getData(10)); // initialize the scope data. $scope.cvFiltering = cv; $scope.filter = ''; $scope.fieldNames = dataSvc.getNames(); // define the filter function for collectionview function filterFunction(item) { var filter = $scope.filter.toLowerCase(); if (!filter) { return true; } return item['country'].toLowerCase().indexOf(filter) > -1; } // apply filter (applied on a 500 ms timeOut) var toFilter; $scope.$watch('filter', function () { if (toFilter) { clearTimeout(toFilter); } toFilter = setTimeout(function () { toFilter = null; if (cv.filter === filterFunction) { cv.refresh(); } else { cv.filter = filterFunction; } $scope.$apply('cvFiltering.items'); }, 500); }); });

Result (live):

{{fieldName}}
{{item[name] | globalize}}

Grouping

The CollectionView class, like the one in .NET, implements the ICollectionView interface to support grouping. To enable grouping, add one or more GroupDescription objects to an array and pass it to the CollectionView.groupDescriptions property. You must also set the grid's showGroups property to true when creating the grid instance, as 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 whichever field you select from the list. The grid shows not only the item's content but also group information: the group name and the average value of the amount for the group appear in the group header row.

Notes: Selecting one item in the list adds a new instance of the GroupDescription. Subsequent selections nest groups. If you select an item from the list for which a GroupDescription object already exists, nothing happens. In order to clear the group settings, select the first item in the list.

HTML
<div class="appGroupingCtrl"> <div class="row-fluid well"> <select class="form-control" ng-model="selectedGroupOpt" ng-options="opt for opt in fieldNames"> <option value="" selected="selected"> Please choose the field you want to group by...</option> </select> </div> <div class="sGrid"> <table class="table table-condensed table-bordered"> <thead> <tr class="active"> <th class="text-center" ng-repeat="fieldName in fieldNames"> {​{fieldName}}</th> </tr> </thead> <tbody> <tr ng-repeat="item in groupItems"> <td class="active" ng-show="isGroupItem(item)" colspan="6"> <span ng-style="{ display:'inline-block', width: (item.level*25) + 'px'}"></span> <b>{​{item.name | globalize}}</b> ({​{item.items.length}} items) </td> <td class="text-center" colspan="2" ng-show="isGroupItem(item)"> {​{avgAmount(item)}} </td> <td class="text-center" ng-repeat="name in fieldNames" ng-hide="isGroupItem(item)"> {​{item[name] | globalize}} </td> </tr> </tbody> </table> </div> </div>
JS
/* define the controller for grouping */ var app = angular.module('app'); app.controller('appGroupingCtrl', function ($scope, dataSvc) { // initialize the collectionview var cv = new wijmo.collections.CollectionView(dataSvc.getData(20)); // initialize the scope data. $scope.cvGrouping = cv; $scope.fieldNames = dataSvc.getNames(); $scope.groupItems = cv.items; $scope.selectedGroupOpt = ''; $scope.isGroupItem = function (item) { return item instanceof wijmo.collections.CollectionViewGroup; }; $scope.avgAmount = function (item) { // it only works when the item is a group item. if (!$scope.isGroupItem(item)) { return; } // get the average value of group amount values. var avg = item.getAggregate(wijmo.Aggregate.Avg, 'amount'); return wijmo.Globalize.format(avg); }; // update the group list cv.collectionChanged.addHandler(function () { $scope.groupItems = cv.items; if (cv.groups && cv.groups.length > 0) { $scope.groupItems = []; for (var i = 0; i < cv.groups.length; i++) { addGroup(cv.groups[i]); } } }); function addGroup(g) { $scope.groupItems.push(g); if (g.isBottomLevel) { for (var i = 0; i < g.items.length; i++) { $scope.groupItems.push(g.items[i]); } } else { for (var i = 0; i < g.groups.length; i++) { addGroup(g.groups[i]); } } } //apply the group setting $scope.$watch('selectedGroupOpt', function () { var gd, fieldName = $scope.selectedGroupOpt; gd = cv.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 = cv.groupDescriptions; for (var i = 0; i < gd.length; i++) { if (gd[i].propertyName === propName) { return i; } } return -1; } });

Result (live):

{{fieldName}}
{{item.name | globalize}} ({{item.items.length}} items) {{avgAmount(item)}} {{item[name] | globalize}}

Editing

The CollectionView class, like the one in .NET, implements the IEditableCollectionView interface to support editing.

This sample shows how you can update, add, and remove items in a collection.

In the grid, select a row and press the Edit Detail button below to start editing. When you finish editing in the popup dialog, press the OK button to commit your updates. If you want to add a new record to the collection, press the Add button and enter content for the item in the popup dialog. Press OK to commit the new record. Select a row and press the Delete button to remove a record from the collection.

HTML
<div ng-controller="appEditingCtrl"> <div class="sGrid"> <table class="table table-condensed table-bordered"> <thead> <tr class="active"> <th class="text-center" ng-repeat="fieldName in fieldNames"> {​{fieldName}}</th> </tr> </thead> <tbody> <tr ng-repeat="item in cvEditing.items" ng-click="cvEditing.moveCurrentTo(item)" ng-class="{success: item == cvEditing.currentItem}"> <td class="text-center" ng-repeat="name in fieldNames"> {​{item[name] | globalize}}</td> </tr> </tbody> </table> </div> <!-- commands --> <div class="row-fluid well"> <!-- edit details in a popup --> <button class="btn btn-default" data-toggle="modal" data-target="#dlgDetail" ng-click="cvEditing.editItem(currentItem)" ng-disabled="!currentItem"> Edit Detail...</button> <button class="btn btn-default" data-toggle="modal" data-target="#dlgDetail" ng-click="cvEditing.addNew()"> Add...</button> <button class="btn btn-default" ng-click="cvEditing.remove(currentItem)" ng-disabled="!currentItem"> 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" type="text" ng-model="currentItem.id" /> </dd> <dt>Start Date</dt> <dd> <input formatted-model class="form-control" type="text" ng-model="currentItem.start" /> </dd> <dt>End Start</dt> <dd> <input formatted-model class="form-control" type="text" ng-model="currentItem.end" /> </dd> <dt>Country</dt> <dd> <input class="form-control" type="text" ng-model="currentItem.country" /> </dd> <dt>Product</dt> <dd> <input class="form-control" type="text" ng-model="currentItem.product" /> </dd> <dt>Color</dt> <dd> <input class="form-control" type="text" ng-model="currentItem.color" /> </dd> <dt>Amount</dt> <dd> <input class="form-control" type="text" ng-model="currentItem.amount" /> </dd> <dt>Active</dt> <dd> <input class="form-control" type="checkbox" ng-model="currentItem.active" /> </dd> </dl> </div> <div class="modal-footer"> <button type="button" class="btn btn-primary" data-dismiss="modal" ng-click="confirmUpdate()"> OK</button> <button type="button" class="btn btn-warning" data-dismiss="modal" ng-click="cancelUpdate()"> Cancel</button> </div> </div> </div> </div> </div>
JS
/* define the controller for editing */ var app = angular.module('app'); app.controller('appEditingCtrl', function ($scope, dataSvc) { // initialize the collectionview var cv = new wijmo.collections.CollectionView(dataSvc.getData(10)); // define the new item value. cv.newItemCreator = function () { var item = dataSvc.getData(1)[0]; // aggregate the max value of id in the collection. item.id = wijmo.getAggregate(wijmo.Aggregate.Max, cv.sourceCollection, 'id') + 1; return item; } // initialize the scope data. $scope.cvEditing = cv; $scope.fieldNames = dataSvc.getNames(); $scope.currentItem = cv.currentItem; $scope.confirmUpdate = function () { // commit edit or addition cv.commitEdit(); cv.commitNew(); }; $scope.cancelUpdate = function () { // cancel editing or adding cv.cancelEdit(); cv.cancelNew(); }; // Sync the currentItem scope with the CollectionView. cv.currentChanged.addHandler(function () { $scope.currentItem = cv.currentItem; }); });

Result (live):

{{fieldName}}
{{item[name] | globalize}}

Paging

The CollectionView class, like the one in .NET, implements the IPagedCollectionView interface to support paging. 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, the CollectionView object is initialized 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 event. 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. Leave it empty or set it to 0 to disable paging and hide the navigation buttons.

HTML
<div ng-controller="appPagingCtrl"> <div class="row-fluid well row"> <div class="col-md-5"> <input number-input type="text" class="form-control" placeholder="0 or empty is for no paging." ng-model="cvPaging.pageSize" /> </div> <div class="btn-group col-md-7" ng-show="cvPaging.pageSize > 0"> <button type="button" class="btn btn-default" ng-disabled="cvPaging.pageIndex <= 0" ng-click="cvPaging.moveToFirstPage()"> <span class="glyphicon glyphicon-fast-backward"></span> </button> <button type="button" class="btn btn-default" ng-disabled="cvPaging.pageIndex <= 0" ng-click="cvPaging.moveToPreviousPage()"> <span class="glyphicon glyphicon-step-backward"></span> </button> <button type="button" class="btn btn-default" disabled style="width:100px" > {​{cvPaging.pageIndex + 1 | number}} / {​{cvPaging.pageCount | number}} </button> <button type="button" class="btn btn-default" ng-disabled="cvPaging.pageIndex >= cvPaging.pageCount - 1" ng-click="cvPaging.moveToNextPage()"> <span class="glyphicon glyphicon-step-forward"></span> </button> <button type="button" class="btn btn-default" ng-disabled="cvPaging.pageIndex >= cvPaging.pageCount - 1" ng-click="cvPaging.moveToLastPage()"> <span class="glyphicon glyphicon-fast-forward"></span> </button> </div> </div> <div class="sGrid"> <table class="table table-condensed table-bordered"> <thead> <tr class="active"> <th class="text-center" ng-repeat="fieldName in fieldNames"> {​{fieldName}}</th> </tr> </thead> <tbody> <tr ng-repeat="item in cvPaging.items"> <td class="text-center" ng-repeat="name in fieldNames"> {​{item[name] | globalize}}</td> </tr> </tbody> </table> </div> </div>
JS
/* define the controller for paging */ var app = angular.module('app'); app.controller('appPagingCtrl', function ($scope, dataSvc) { // initialize the collectionview var cv = new wijmo.collections.CollectionView(dataSvc.getData(55)); // initialize the page size with 10. cv.pageSize = 10; // initialize the scope data. $scope.cvPaging = cv; $scope.fieldNames = dataSvc.getNames(); });

Result (live):

{{fieldName}}
{{item[name] | globalize}}

Tracking changes

The CollectionView class can keep track of changes made to the data. This 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
<div ng-controller="appTCCtrl"> <h5>Change the data here</h5> <wj-flex-grid class="sGrid" items-source="cvTrackingChanges" allow-add-new="true" allow-delete="true"> </wj-flex-grid> <h5>See the changes here</h5> <h6>Items edited:</h6> <wj-flex-grid class="tcGrid" style="background:#eeeeff" items-source="cvTrackingChanges.itemsEdited" is-read-only="true"> <wj-flex-grid-column binding="id" header="id" data-type="Number"> </wj-flex-grid-column> <wj-flex-grid-column binding="start" header="start"> </wj-flex-grid-column> <wj-flex-grid-column binding="end" header="end"> </wj-flex-grid-column> <wj-flex-grid-column binding="country" header="country"> </wj-flex-grid-column> <wj-flex-grid-column binding="product" header="product"> </wj-flex-grid-column> <wj-flex-grid-column binding="color" header="color"> </wj-flex-grid-column> <wj-flex-grid-column binding="amount" header="amount" data-type="Number"> </wj-flex-grid-column> <wj-flex-grid-column binding="active" header="active" data-type="Boolean"> </wj-flex-grid-column> </wj-flex-grid> <h6>Items added:</h6> <wj-flex-grid class="tcGrid" style="background:#eeeeff" items-source="cvTrackingChanges.itemsAdded" is-read-only="true"> <wj-flex-grid-column binding="id" header="id" data-type="Number"> </wj-flex-grid-column> <wj-flex-grid-column binding="start" header="start"> </wj-flex-grid-column> <wj-flex-grid-column binding="end" header="end"> </wj-flex-grid-column> <wj-flex-grid-column binding="country" header="country"> </wj-flex-grid-column> <wj-flex-grid-column binding="product" header="product"> </wj-flex-grid-column> <wj-flex-grid-column binding="color" header="color"> </wj-flex-grid-column> <wj-flex-grid-column binding="amount" header="amount" data-type="Number"> </wj-flex-grid-column> <wj-flex-grid-column binding="active" header="active" data-type="Boolean"> </wj-flex-grid-column> </wj-flex-grid> <h6>Items removed:</h6> <wj-flex-grid class="tcGrid" style="background:#eeeeff" items-source="cvTrackingChanges.itemsRemoved" is-read-only="true"> <wj-flex-grid-column binding="id" header="id" data-type="Number"> </wj-flex-grid-column> <wj-flex-grid-column binding="start" header="start"> </wj-flex-grid-column> <wj-flex-grid-column binding="end" header="end"> </wj-flex-grid-column> <wj-flex-grid-column binding="country" header="country"> </wj-flex-grid-column> <wj-flex-grid-column binding="product" header="product"> </wj-flex-grid-column> <wj-flex-grid-column binding="color" header="color"> </wj-flex-grid-column> <wj-flex-grid-column binding="amount" header="amount" data-type="Number"> </wj-flex-grid-column> <wj-flex-grid-column binding="active" header="active" data-type="Boolean"> </wj-flex-grid-column> </wj-flex-grid> </div>
JS
/* define the controller for tracking changes */ var app = angular.module('app'); app.controller('appTCCtrl', function ($scope, dataSvc) { // initialize the collectionview var cv = new wijmo.collections.CollectionView(dataSvc.getData(6)); //track the changes cv.trackChanges = true; // initialize the scope data. $scope.cvTrackingChanges = cv; });
CSS
/* set default grid height and some shadow */ .sGrid { background-color: #fff; box-shadow: 4px 4px 10px 0 rgba(50, 50, 50, 0.75); height: 300px; margin-bottom: 12px; overflow: auto; } /* set the record grids height and some shadow */ .tcGrid { background-color: #fff; box-shadow: 4px 4px 10px 0 rgba(50, 50, 50, 0.75); height: 100px; margin-bottom: 12px; overflow: auto; }

Result (live):

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

Tracking changes with Customization

When you edit an item on a CollectionView with change tracking on, the item is added to the itemdEdited collection. However, the CollectionView doesn't keep track of the item's original values, so if you later edit it again and restore the original values, the item will remain in the itemdEdited collection.

But you can change that behavior if you want. This example uses the events exposed by the CollectionView and itemsChanged classes to keep track if the original values for each item, and to remove items from the itemsEdited collection if the user restores the original values.

HTML
<div ng-controller="appTCCtrlX"> <h5>Change the data here</h5> <wj-flex-grid class="sGrid" items-source="cvTrackingChanges" allow-add-new="true" allow-delete="true"> </wj-flex-grid> <h5>See the changes here</h5> <h6>Items edited:</h6> <wj-flex-grid class="tcGrid" style="background:#eeeeff" items-source="cvTrackingChanges.itemsEdited" is-read-only="true"> <wj-flex-grid-column binding="id" header="id" data-type="Number"> </wj-flex-grid-column> <wj-flex-grid-column binding="start" header="start"> </wj-flex-grid-column> <wj-flex-grid-column binding="end" header="end"> </wj-flex-grid-column> <wj-flex-grid-column binding="country" header="country"> </wj-flex-grid-column> <wj-flex-grid-column binding="product" header="product"> </wj-flex-grid-column> <wj-flex-grid-column binding="color" header="color"> </wj-flex-grid-column> <wj-flex-grid-column binding="amount" header="amount" data-type="Number"> </wj-flex-grid-column> <wj-flex-grid-column binding="active" header="active" data-type="Boolean"> </wj-flex-grid-column> </wj-flex-grid> <h6>Items added:</h6> <wj-flex-grid class="tcGrid" style="background:#eeeeff" items-source="cvTrackingChanges.itemsAdded" is-read-only="true"> <wj-flex-grid-column binding="id" header="id" data-type="Number"> </wj-flex-grid-column> <wj-flex-grid-column binding="start" header="start"> </wj-flex-grid-column> <wj-flex-grid-column binding="end" header="end"> </wj-flex-grid-column> <wj-flex-grid-column binding="country" header="country"> </wj-flex-grid-column> <wj-flex-grid-column binding="product" header="product"> </wj-flex-grid-column> <wj-flex-grid-column binding="color" header="color"> </wj-flex-grid-column> <wj-flex-grid-column binding="amount" header="amount" data-type="Number"> </wj-flex-grid-column> <wj-flex-grid-column binding="active" header="active" data-type="Boolean"> </wj-flex-grid-column> </wj-flex-grid> <h6>Items removed:</h6> <wj-flex-grid class="tcGrid" style="background:#eeeeff" items-source="cvTrackingChanges.itemsRemoved" is-read-only="true"> <wj-flex-grid-column binding="id" header="id" data-type="Number"> </wj-flex-grid-column> <wj-flex-grid-column binding="start" header="start"> </wj-flex-grid-column> <wj-flex-grid-column binding="end" header="end"> </wj-flex-grid-column> <wj-flex-grid-column binding="country" header="country"> </wj-flex-grid-column> <wj-flex-grid-column binding="product" header="product"> </wj-flex-grid-column> <wj-flex-grid-column binding="color" header="color"> </wj-flex-grid-column> <wj-flex-grid-column binding="amount" header="amount" data-type="Number"> </wj-flex-grid-column> <wj-flex-grid-column binding="active" header="active" data-type="Boolean"> </wj-flex-grid-column> </wj-flex-grid> </div>
JS
/* define the controller for tracking changes with extra/custom tracking */ var app = angular.module('app'); app.controller('appTCCtrlX', function ($scope, dataSvc) { // initialize the collectionview var cv = new wijmo.collections.CollectionView(dataSvc.getData(6)); //track the changes cv.trackChanges = true; // initialize the scope data. $scope.cvTrackingChanges = cv; // keep the original state of the current item var current = cv.currentItem ? JSON.stringify(cv.currentItem) : null; cv.currentChanged.addHandler(function (s, e) { current = s.currentItem ? JSON.stringify(s.currentItem) : null; }); // keep track of the original state of edited items var original = []; cv.itemsEdited.collectionChanged.addHandler(function (s, e) { if (e.action == wijmo.collections.NotifyCollectionChangedAction.Add || e.action == wijmo.collections.NotifyCollectionChangedAction.Change) { // check if we have this item's original data var index = cv.sourceCollection.indexOf(e.item); var found = -1; for (var i = 0; i < original.length && found < 0; i++) { if (original[i].index == index) { found = i; } } // if we have the item, check original value if (found > -1) { // if the current value is the same as the original, remove var valueNow = JSON.stringify(e.item); if (valueNow == original[found].value) { original.splice(found, 1); index = cv.itemsEdited.indexOf(e.item); cv.itemsEdited.splice(index, 1); } } else { // if we don't, store it now found = original.length; original.push({ index: index, value: current }); } } }); });
CSS
/* set default grid height and some shadow */ .sGrid { background-color: #fff; box-shadow: 4px 4px 10px 0 rgba(50, 50, 50, 0.75); height: 300px; margin-bottom: 12px; overflow: auto; } /* set the record grids height and some shadow */ .tcGrid { background-color: #fff; box-shadow: 4px 4px 10px 0 rgba(50, 50, 50, 0.75); height: 100px; margin-bottom: 12px; overflow: auto; }

Result (live):

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