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:
ICollectionView: Provides current record management, custom sorting,
filtering, and grouping.
IEditableCollectionView: Provides methods for editing, adding, and
removing items.
IPagedCollectionView: Provides paging for navigating through large
numbers of items.
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:
Add references to AngularJS, Wijmo, and the Wijmo AngularJS directives.
Optionally add a reference to FlexGrid.
Add references to your app, services, filters, and directives modules.
Add a table (or FlexGrid) to the page and bind it to the CollectionView data.
Add a controller to provide data and logic.
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.
/* 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;
});
/* 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:
moveCurrentTo(item)
moveCurrentToFirst()
moveCurrentToLast()
moveCurrentToNext()
moveCurrentToPosition(index)
moveCurrentToPrevious()
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.
/* 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.
/* 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.
/* 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.
/* 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;
}
});
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.
/* 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}}
Edit Item
ID
Start Date
End Start
Country
Product
Color
Amount
Active
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.
/* 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:
itemsEdited: This list contains items that are edited using
the editItem and commitEdit methods.
itemsAdded: This list contains items that are added using the
addNew and commitNew methods.
itemsRemoved: This list contains items that are removed using
the remove method.
This feature is demonstrated below using a FlexGrid. The grid is bound
to a CollectionView with trackChanges set to true.
/* 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;
});
/* 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.
/* 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 });
}
}
});
});