Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / web / HTML

AngularJS Grid with Multi-filter Feature

4.67/5 (2 votes)
11 Dec 2014CPOL5 min read 12.8K  
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.
Image 1

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 strings 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.

JavaScript
$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:

JavaScript
$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

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)