Introduction
In this tip, I am going to show how to build a grid that can filter your data by joining multiple conditions that can be applied on each grid column.
You can use a filter and a text to narrow down the number of items shown in your grid. The filter, however, will use the searched text against all fields of the objects bound to your grid. This may not be desirable if you just want to filter the items by field and additionally if you want to join filters on different columns. This is where this guide comes to help.
Background
The reader should be familiar with the main concepts behind AngularJS: the MVC architectural pattern, scopes, expressions, filters and a good understanding of the Javascript language.
Using the Code
I've included all my code into a partial view, so the HTML file that you'll download will contain a single table that, if
needed, you'll be able to include in your SPA application. This partial view is based on the classic app-seed project
template provided by Angular (https://github.com/angular/angular-seed).
The controller in this sample has only 2 dependencies, which are $scope
(of course) and $http
. The $http
service is used to query data from http://www.json-generator.com/ Web Service, which returns a sample data set with a good amount of JSON serialized random entities.
The controller has few scope variables (you will find some additional variables if you download the attached files, but in this tip, I'm just going to explain the relevant ones for this feature):
$scope.headerFields
: an array populated with the name of the columns in our table $scope.filters
: an array to store the string
s that you are using as filters on each table column $scope.showFilters
: a true
or false
flag, used to show or hide the filters in our table's header
The initialization of the controller sets these scope variables with their default values and then it makes a GET
request to receive data. When any data are returned, it iterates through all the object's properties to store the properties names into the $scope.headeFields
variable to show them in the table header.
$scope.onSuccess = function (response) {
for (var responseField in response[0]) {
$scope.headerFields.push(responseField);
}
$scope.results = response;
[...]
}
The table header is produced with the very simple ng-repeater
directive that iterates a td
element over the items of $scope.headerFields
.
The table header also contains a checkbox input control labelled Show Filters with a two-way data-binding to the scope variable showFilters
. showFilters
, in turn, controls the visibility of another table header row containing as many text input controls as the items in the headerFields
array. Each one of these text input controls is bound to the filters
scope variable array. More precisely, it makes use of the ng-repeater
directive $index
special property to make sure that each text input control binds its value to the n-th (or $index
-th, as we should say) item of the filters
array.
So, for example, as we're typing 'something' into the 2-nd text input control, filters[1]
should be automatically updated with its value:
filters[0]=undefined
filters[1]='something'
filters[2]=undefined
- ...
Now ... the body of our table is produced again using an ng-repeater
directive, and we will apply a filter to it.
The filter will be calling a function called combineFilers
that will act as a predicate on each row or item, returning true
if the item will be included in the results, or false
otherwise. So we just need to setup a function that must be able to take into account the text contained in the filters
array, and apply each filter to the related properties of the items bound to the table. Here's our combineFilters
function:
$scope.combineFilters = function (item) {
var returnValue = true;
if ($scope.showFilters == true) {
var regExpressions = [];
for (var i = 0; i < $scope.filters.length; i++) {
if ($scope.filters[i] != undefined && $scope.filters[i].length > 0) {
var regExp = new RegExp($scope.filters[i], 'i');
regExpressions.push({ index: i, regExpression: regExp });
}
}
for (var i = 0; i < regExpressions.length; i++) {
var regularExpression = regExpressions[i];
var index = regularExpression.index;
var regExpression = regularExpression.regExpression;
var fieldName = $scope.headerFields[index];
var fieldValue = item[fieldName];
if (!regExpression.test(fieldValue)) {
returnValue = false;
break;
}
}
}
return returnValue;
}
The first for
statement iterates through all non-null
filters, and stores the filters (and their ordinal position) into the regularExpressions
array. Each item in this array will contain a regular expression built with the filter text, and the index of the filter, that is the position of that column into the table.
Subsequently, the second for
statement iterates through all the items in the regularExpressions
previously populated, using the index of the filter to retrieve the correct field name (remember we stored them in $scope.headerFields
when data were retrieved), and then the value of that field in current item (also remember that this is a function called for each item in the table). The regular expression associated with each filter text input control was also stored along with the index, so now we can just test the regular expression against the field value in the current item.
As soon as the regular expression test fails, we can just stop and return false
and the item will be excluded by the filter. This means, indeed, that in one of the text input controls we have specified some text that the corresponding field of the current item does not contain, so it's correct to hide this item from the filtered results.
Note that the combineFilters
functions starts with a check on the $scope.showFilters
variable. This totally enables\disables the filtering functionality of the grid, so if you're filtering your data and then
uncheck the Show Filters checkbox, then all the rows will be displayed again.
The table contained in the attached downloadable files also contains other features (like sorting, ability to add a new item to the table, and posting data back to the server) that are still in development, and I'll surely complete and explain in a follow-up to this post.
History
- 11th December, 2014 - Initial release