CollectionView 101

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

Wijmo 5 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.

<div class="sGrid"> <table class="table table-condensed table-bordered"> <thead> <tr class="active"> <th class="text-center" *ngFor="let fieldName of fieldNames">{​{fieldName}​}</th> </tr> </thead> <tbody> <tr *ngFor="let item of cvGettingStarted.items"> <td class="text-center" *ngFor="let name of fieldNames">{​{item[name] | globalize}​}</td> </tr> </tbody> </table> </div>
// Angular import * as wjcCore from 'wijmo/wijmo'; import { Component, EventEmitter, Input, Inject, enableProdMode, NgModule } from '@angular/core'; import { FormsModule } from '@angular/forms'; import { CommonModule } from '@angular/common'; import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; import { BrowserModule } from '@angular/platform-browser'; import { TabsModule } from './components/AppTab'; import { GlobalizePipe } from './pipes/appPipes'; import { DataSvc } from './services/DataSvc'; 'use strict'; // The application root component. @Component({ selector: 'app-cmp', templateUrl: 'src/app.html' }) export class AppCmp { cvGettingStarted: wjcCore.CollectionView; fieldNames: string[]; constructor( @Inject(DataSvc) dataSvc: DataSvc) { // initialize the collectionview this.cvGettingStarted = new wjcCore.CollectionView(dataSvc.getData(10)); this.fieldNames = dataSvc.getNames(); } } @NgModule({ imports: [BrowserModule, FormsModule, TabsModule], declarations: [GlobalizePipe, AppCmp], providers: [DataSvc], bootstrap: [AppCmp] }) export class AppModule { } enableProdMode(); // Bootstrap application with hash style navigation and global services. platformBrowserDynamic().bootstrapModule(AppModule);;
/* 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.

<div class="row-fluid well btn-group"> <button class="btn btn-default" (click)="cvCRM.moveCurrentToNext()">Next</button> <button class="btn btn-default" (click)="cvCRM.moveCurrentToPrevious()">Previous</button> <button class="btn btn-default" (click)="stopCurrent()">Stop at 4th Row</button> <button class="btn btn-default" (click)="reset()">Clear</button> </div> <div class="sGrid"> <table class="table table-condensed table-bordered"> <thead> <tr class="active"> <th class="text-center" *ngFor="let fieldName of fieldNames">{​{fieldName}​}</th> </tr> </thead> <tbody> <tr *ngFor="let item of cvCRM.items" [ngClass]="{success: item == cvCRM.currentItem}" (click)="cvCRM.moveCurrentTo(item)"> <td class="text-center" *ngFor="let name of fieldNames">{​{item[name] | globalize}​}</td> </tr> </tbody> </table> </div>
// The application root component. @Component({ selector: 'app-cmp', templateUrl: 'src/app.html' }) export class AppCmp { cvCRM: wjcCore.CollectionView; fieldNames: string[]; constructor( @Inject(DataSvc) dataSvc: DataSvc) { // initialize the collectionview this.cvCRM = new wjcCore.CollectionView(dataSvc.getData(10)); this.fieldNames = dataSvc.getNames(); } // current record management stopCurrent() { this.cvCRM.currentChanging.addHandler(this._stopCurrentIn4th); }; // restore to be able to change current. reset() { this.cvCRM.currentChanging.removeHandler(this._stopCurrentIn4th); }; // forbid changing current when the current item is the 4th one. private _stopCurrentIn4th(sender, e) { // when the current is the 4rd item, stop moving. if (sender.currentPosition === 3) { e.cancel = true; } } }

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.

<div class="sGrid"> <table class="table table-condensed table-bordered"> <thead> <tr class="active"> <th class="text-center" *ngFor="let fieldName of fieldNames" (click)="toggleSort(fieldName)" style="cursor:pointer"> {​{fieldName}​}<span style="color: red">{​{getSort(fieldName)}​}</span> </th> </tr> </thead> <tbody> <tr *ngFor="let item of cvSorting.items"> <td class="text-center" *ngFor="let name of fieldNames">{​{item[name] | globalize}​}</td> </tr> </tbody> </table> </div>
// Angular import { Component, EventEmitter, Input, Inject, enableProdMode, NgModule } from '@angular/core'; import { FormsModule } from '@angular/forms'; import { CommonModule } from '@angular/common'; import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; import { BrowserModule } from '@angular/platform-browser'; import { TabsModule } from './components/AppTab'; import { GlobalizePipe } from './pipes/appPipes'; import { DataSvc } from './services/DataSvc'; 'use strict'; // The application root component. @Component({ selector: 'app-cmp', templateUrl: 'src/app.html' }) export class AppCmp { cvSorting: wjcCore.CollectionView; fieldNames: string[]; constructor( @Inject(DataSvc) dataSvc: DataSvc) { // initialize the collectionview this.cvSorting = new wjcCore.CollectionView(dataSvc.getData(10)); this.fieldNames = dataSvc.getNames(); } // sorting toggleSort(fieldName:string) { // get all the sort descriptions. var sd = this.cvSorting.sortDescriptions; var ascending = true; // try to find whether the field has been sorted. if (sd.length > 0 && sd[0].property === fieldName) { // if finded, toggle the sort order. ascending = !sd[0].ascending; } // create a new SortDescription object. var sdNew = new wjcCore.SortDescription(fieldName, ascending); // remove any old sort descriptors and add the created one. sd.splice(0, sd.length, sdNew); }; // get the sort label getSort(propName:string) { var sd = this.cvSorting.sortDescriptions; if (sd.length > 0 && sd[0].property === propName) { return sd[0].ascending ? '▲' : '▼'; } return ''; }; } @NgModule({ imports: [BrowserModule, FormsModule, TabsModule], declarations: [GlobalizePipe, AppCmp], providers: [DataSvc], bootstrap: [AppCmp] }) export class AppModule { } enableProdMode(); // Bootstrap application with hash style navigation and global services. platformBrowserDynamic().bootstrapModule(AppModule);

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.

<div class="row-fluid well"> <input type="text" class="form-control app-pad" placeholder="Please enter characters for the country by which to filter (case-insensitive)" [(ngModel)]="filter" /> </div> <div class="sGrid"> <table class="table table-condensed table-bordered"> <thead> <tr class="active"> <th class="text-center" *ngFor="let fieldName of fieldNames">{​{fieldName}​}</th> </tr> </thead> <tbody> <tr *ngFor="let item of cvFiltering.items"> <td class="text-center" *ngFor="let name of fieldNames">{​{item[name] | globalize}​}</td> </tr> </tbody> </table> </div>
// The application root component. @Component({ selector: 'app-cmp', templateUrl: 'src/app.html' }) export class AppCmp { cvFiltering: wjcCore.CollectionView; fieldNames: string[]; private _toFilter: any; private _thisFilterFunction: wjcCore.IPredicate; private _filter: string; constructor( @Inject(DataSvc) dataSvc: DataSvc) { // initialize the collectionview this.cvFiltering = new wjcCore.CollectionView(dataSvc.getData(10)); this.fieldNames = dataSvc.getNames(); this._thisFilterFunction = this._filterFunction.bind(this); } // filtering get filter(): string { return this._filter; } set filter(value: string) { if (this._filter != value) { this._filter = value; this._applyFilter(); } } // apply filter (applied on a 500 ms timeOut) private _applyFilter() { if (this._toFilter) { clearTimeout(this._toFilter); } //var self = this; this._toFilter = setTimeout(() => { this._toFilter = null; if (this.cvFiltering) { var cv = this.cvFiltering; if (cv) { if (cv.filter != this._thisFilterFunction) { cv.filter = this._thisFilterFunction; } else { cv.refresh(); } } } }, 500); } // ICollectionView filter function private _filterFunction(item: any): boolean { var filter = this.filter.toLowerCase(); if (!filter) { return true; } return item['country'].toLowerCase().indexOf(filter) > -1; } }

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.

<select class="form-control" [(ngModel)]="selectedGroupOpt"> <option [ngValue]="''" >Please choose the field by which to group.</option> <option *ngFor="let name of fieldNames" [ngValue]="name">{​{name}​}</option> </select> </div> <div class="sGrid"> <table class="table table-condensed table-bordered"> <thead> <tr class="active"> <th class="text-center" *ngFor="let fieldName of fieldNames">{​{fieldName}​}</th> </tr> </thead> <tbody> <tr *ngFor="let item of groupItems"> <td class="active" [ngStyle]="{display:isGroupItem(item)? '':'none'}" colspan="6"> <span [ngStyle]="{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" [ngStyle]="{display:isGroupItem(item)? '':'none'}" > {​{avgAmount(item)}​} </td> <td class="text-center" *ngFor="let name of fieldNames" [ngStyle]="{display:isGroupItem(item)? 'none':''}"> {​{item[name] | globalize}​} </td> </tr> </tbody> </table> </div>
// The application root component. @Component({ selector: 'app-cmp', templateUrl: 'src/app.html' }) export class AppCmp { cvGrouping: wjcCore.CollectionView; fieldNames: string[]; groupItems: any; private _selectedGroupOpt = ''; constructor( @Inject(DataSvc) dataSvc: DataSvc) { // initialize the collectionview this.cvGrouping = new wjcCore.CollectionView(dataSvc.getData(20)); this.fieldNames = dataSvc.getNames(); this.groupItems = this.cvGrouping.items; // update the group list this.cvGrouping.collectionChanged.addHandler(() => { this.groupItems = this.cvGrouping.items; if (this.cvGrouping.groups && this.cvGrouping.groups.length > 0) { this.groupItems = []; for (var i = 0; i < this.cvGrouping.groups.length; i++) { this._addGroup(this.cvGrouping.groups[i]); } } }); } // grouping get selectedGroupOpt(): string { return this._selectedGroupOpt; } set selectedGroupOpt(value: string) { if (this._selectedGroupOpt != value) { this._selectedGroupOpt = value; this._applyGrouping(); } } private _applyGrouping() { var gd, fieldName = this.selectedGroupOpt; gd = this.cvGrouping.groupDescriptions; if (!fieldName) { // clear all the group settings. gd.splice(0, gd.length); return; } if (this._findGroup(fieldName) >= 0) { return; } if (fieldName == 'amount') { // when grouping by amount, use ranges instead of specific values gd.push(new wjcCore.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 wjcCore.PropertyGroupDescription(fieldName)); } } // check whether the group with the specified property name already exists. private _findGroup(propName: string) { var gd = this.cvGrouping.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.

<div class="sGrid"> <table class="table table-condensed table-bordered"> <thead> <tr class="active"> <th class="text-center" *ngFor="let fieldName of fieldNames">{​{fieldName}​}</th> </tr> </thead> <tbody> <tr *ngFor="let item of cvEditing.items" (click)="cvEditing.moveCurrentTo(item)" [ngClass]="{success: item == cvEditing.currentItem}"> <td class="text-center" *ngFor="let name of 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" (click)="cvEditing.editItem(currentItem)" [disabled]="!currentItem"> Edit Detail... </button> <button class="btn btn-default" data-toggle="modal" data-target="#dlgDetail" (click)="cvEditing.addNew()"> Add... </button> <button class="btn btn-default" (click)="cvEditing.remove(currentItem)" [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"> × </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" [(ngModel)]="currentItem.id" /> </dd> <dt>Start Date</dt> <dd> <input formatted-model class="form-control" type="text" [(ngModel)]="currentItem.start" /> </dd> <dt>End Start</dt> <dd> <input formatted-model class="form-control" type="text" [(ngModel)]="currentItem.end" /> </dd> <dt>Country</dt> <dd> <input class="form-control" type="text" [(ngModel)]="currentItem.country" /> </dd> <dt>Product</dt> <dd> <input class="form-control" type="text"[(ngModel)]="currentItem.product" /> </dd> <dt>Color</dt> <dd> <input class="form-control" type="text" [(ngModel)]="currentItem.color" /> </dd> <dt>Amount</dt> <dd> <input class="form-control" type="text" [(ngModel)]="currentItem.amount" /> </dd> <dt>Active</dt> <dd> <input class="form-control" type="checkbox" [(ngModel)]="currentItem.active" /> </dd> </dl> </div> <div class="modal-footer"> <button type="button" class="btn btn-primary" data-dismiss="modal" (click)="confirmUpdate()"> OK </button> <button type="button" class="btn btn-warning" data-dismiss="modal" (click)="cancelUpdate()"> Cancel </button> </div> </div> </div> </div>
// The application root component. @Component({ selector: 'app-cmp', templateUrl: 'src/app.html' }) export class AppCmp { cvEditing: wjcCore.CollectionView; fieldNames: string[]; currentItem: any; constructor( @Inject(DataSvc) dataSvc: DataSvc) { // initialize the collectionview this.cvEditing = new wjcCore.CollectionView(dataSvc.getData(20)); this.fieldNames = dataSvc.getNames(); this.currentItem = this.cvEditing.currentItem; // define the new item value. this.cvEditing.newItemCreator = () => { var item = dataSvc.getData(1)[0]; // aggregate the max value of id in the collection. item.id = wijmo.getAggregate(wijmo.Aggregate.Max, this.cvEditing.sourceCollection, 'id') + 1; return item; } // syn the scope currentItem with the collectionview. this.cvEditing.currentChanged.addHandler(() => { this.currentItem = this.cvEditing.currentItem; }); } // editing confirmUpdate() { // commit editing/adding this.cvEditing.commitEdit(); this.cvEditing.commitNew(); }; cancelUpdate() { // cancel editing or adding this.cvEditing.cancelEdit(); this.cvEditing.cancelNew(); }; }

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.

<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." [(ngModel)]="cvPaging.pageSize" /> </div> <div class="btn-group col-md-7" > <button type="button" class="btn btn-default" [disabled]="cvPaging.pageIndex <= 0" (click)="cvPaging.moveToFirstPage()"> <span class="glyphicon glyphicon-fast-backward"></span> </button> <button type="button" class="btn btn-default" [disabled]="cvPaging.pageIndex <= 0" (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" [disabled]="cvPaging.pageIndex >= cvPaging.pageCount - 1" (click)="cvPaging.moveToNextPage()"> <span class="glyphicon glyphicon-step-forward"></span> </button> <button type="button" class="btn btn-default" [disabled]="cvPaging.pageIndex >= cvPaging.pageCount - 1" (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" *ngFor="let fieldName of fieldNames">{​{fieldName}​}</th> </tr> </thead> <tbody> <tr *ngFor="let item of cvPaging.items"> <td class="text-center" *ngFor="let name of fieldNames">{​{item[name] | globalize}​}</td> </tr> </tbody> </table> </div>
// The application root component. @Component({ selector: 'app-cmp', templateUrl: 'src/app.html' }) export class AppCmp { cvPaging: wjcCore.CollectionView; fieldNames: string[]; currentItem: any; constructor( @Inject(DataSvc) dataSvc: DataSvc) { // initialize the collectionview this.cvPaging = new wjcCore.CollectionView(dataSvc.getData(20)); this.fieldNames = dataSvc.getNames(); this.cvPaging.pageSize = 10; } }

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.

<h5>Change the data here</h5> <wj-flex-grid class="sGrid" [itemsSource]="cvTrackingChanges" [allowAddNew]="true" [allowDelete]="true"> </wj-flex-grid> <h5>See the changes here</h5> <h6>Items edited:</h6> <wj-flex-grid class="tcGrid" style="background:#eeeeff" [itemsSource]="cvTrackingChanges.itemsEdited" [isReadOnly]="true"> <wj-flex-grid-column binding="id" header="id" [dataType]="'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" [dataType]="'Number'"></wj-flex-grid-column> <wj-flex-grid-column binding="active" header="active"[dataType]="'Boolean'"></wj-flex-grid-column> </wj-flex-grid> <h6>Items added:</h6> <wj-flex-grid class="tcGrid" style="background:#eeeeff" [itemsSource]="cvTrackingChanges.itemsAdded" [isReadOnly]="true"> <wj-flex-grid-column binding="id" header="id" [dataType]="'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" [dataType]="'Number'"></wj-flex-grid-column> <wj-flex-grid-column binding="active" header="active" [dataType]="'Boolean'"></wj-flex-grid-column> </wj-flex-grid> <h6>Items removed:</h6> <wj-flex-grid class="tcGrid" style="background:#eeeeff" [itemsSource]="cvTrackingChanges.itemsRemoved" [isReadOnly]="true"> <wj-flex-grid-column binding="id" header="id" [dataType]="'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" [dataType]="'Number'"></wj-flex-grid-column> <wj-flex-grid-column binding="active" header="active" [dataType]="'Boolean'"></wj-flex-grid-column> </wj-flex-grid>
import { Component, EventEmitter, Input, Inject, enableProdMode, NgModule } from '@angular/core'; import { FormsModule } from '@angular/forms'; import { CommonModule } from '@angular/common'; import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; import { BrowserModule } from '@angular/platform-browser'; import { WjCoreModule } from 'wijmo/wijmo.angular2.core'; import { WjGridModule } from 'wijmo/wijmo.angular2.grid'; import { WjInputModule } from 'wijmo/wijmo.angular2.input'; import { TabsModule } from './components/AppTab'; import { GlobalizePipe } from './pipes/appPipes'; import { DataSvc } from './services/DataSvc'; // The application root component. @Component({ selector: 'app-cmp', templateUrl: 'src/app.html', }) export class AppCmp { cvTrackingChanges: wjcCore.CollectionView; fieldNames: string[]; constructor( @Inject(DataSvc) dataSvc: DataSvc) { // initialize the collectionview this.cvTrackingChanges = new wjcCore.CollectionView(dataSvc.getData(6)); this.fieldNames = dataSvc.getNames(); this.cvTrackingChanges.trackChanges = true; } } @NgModule({ imports: [WjCoreModule, WjInputModule, WjGridModule, BrowserModule, FormsModule, TabsModule], declarations: [GlobalizePipe, AppCmp], providers: [DataSvc], bootstrap: [AppCmp] }) export class AppModule { }
/* 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.

<h4>Result (live):</h4> <h5>Change the data here</h5> <wj-flex-grid class="sGrid" [itemsSource]="cvTrackingChangesExtra" [allowAddNew]="true" [allowDelete]="true"> </wj-flex-grid> <h5>See the changes here</h5> <h6>Items edited:</h6> <wj-flex-grid class="tcGrid" style="background:#eeeeff" [itemsSource]="cvTrackingChangesExtra.itemsEdited" [isReadOnly]="true"> <wj-flex-grid-column binding="id" header="id" [dataType]="'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" [dataType]="'Number'"></wj-flex-grid-column> <wj-flex-grid-column binding="active" header="active"[dataType]="'Boolean'"></wj-flex-grid-column> </wj-flex-grid> <h6>Items added:</h6> <wj-flex-grid class="tcGrid" style="background:#eeeeff" [itemsSource]="cvTrackingChangesExtra.itemsAdded" [isReadOnly]="true"> <wj-flex-grid-column binding="id" header="id" [dataType]="'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" [dataType]="'Number'"></wj-flex-grid-column> <wj-flex-grid-column binding="active" header="active" [dataType]="'Boolean'"></wj-flex-grid-column> </wj-flex-grid> <h6>Items removed:</h6> <wj-flex-grid class="tcGrid" style="background:#eeeeff" [itemsSource]="cvTrackingChangesExtra.itemsRemoved" [isReadOnly]="true"> <wj-flex-grid-column binding="id" header="id" [dataType]="'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" [dataType]="'Number'"></wj-flex-grid-column> <wj-flex-grid-column binding="active" header="active" [dataType]="'Boolean'"></wj-flex-grid-column> </wj-flex-grid>
// The application root component. @Component({ selector: 'app-cmp', templateUrl: 'src/app.html' }) export class AppCmp { cvTrackingChangesExtra: wjcCore.CollectionView; fieldNames: string[]; current: any; constructor( @Inject(DataSvc) dataSvc: DataSvc) { // initialize the collectionview this.cvTrackingChangesExtra = new wjcCore.CollectionView(dataSvc.getData(6)); this.fieldNames = dataSvc.getNames(); this.cvTrackingChangesExtra.trackChanges = true; // keep the original state of the current item in tracking Changes this.current = this.cvTrackingChangesExtra.currentItem ? JSON.stringify(this.cvTrackingChangesExtra.currentItem) : null; this.cvTrackingChangesExtra.currentChanged.addHandler((s, e) => { this.current = s.currentItem ? JSON.stringify(s.currentItem) : null; }); // keep track of the original state of edited items var original = []; this.cvTrackingChangesExtra.itemsEdited.collectionChanged.addHandler((s, e: any) => { if (e.action == wjcCore.NotifyCollectionChangedAction.Add || e.action == wjcCore.NotifyCollectionChangedAction.Change) { // check if we have this item's original data var index = this.cvTrackingChangesExtra.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 = this.cvTrackingChangesExtra.itemsEdited.indexOf(e.item); this.cvTrackingChangesExtra.itemsEdited.splice(index, 1); } } else { // if we don't, store it now found = original.length; original.push({ index: index, value: this.current }); } } }); } }
/* 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: