While the Kendo UI for Angular Grid comes with built-in filtering functionality, sometimes you need to allow users to control what the filters show. We can do that too. Let’s take a look.
In an earlier post, I walked through the mechanics of filtering with the Angular Data Grid from Progress Kendo UI. In that post, however, I focused on the Grid’s support for letting the user filter the rows in the grid and I only outlined the mechanics of integrating filtering with the rest of the application’s UI.
In this post, I’m going to look at a more realistic example how you can integrate filtering into an application so that, as the user interacts with the application’s UI, your code controls what rows the Grid displays.
My case study app is relatively simple: I have a Grid displaying a list of customers and dropdown list of countries. As the user selects a country from the dropdown list, my code will update the Grid to show only the customers in that country. To make
matters more interesting, I’ll also throw in a checkbox that lets the user select customers based on whether the customer is in the company’s loyalty program.
Here’s what the UI looks like:
The Customer
class that this grid is showing has six properties. However, for this case study, I’m only interested in the country
and inLoyaltyProgram
properties:
export class Customer
{
constructor(
public id: number,
public country: string,
public inLoyaltyProgram: Boolean,
public custName: string,
public signUpDate: Date | null,
public amountPurchased: number
) { }
}
The markup to define the dropdown list and checkbox is going to look like this:
Select Country:
<kendo-dropdownlist
[data]="countries"
defaultItem=”Pick a Country”
(selectionChange)="CountryChange($event)">
</kendo-dropdownlist><br/><br/>
Loyalty Program Only:
<input type="checkbox" #loyalty kendoCheckBox
(change)="LoyaltyChange($event)"/>
Each of these components calls a function and passes the related event object when the user changes the component’s value. I’ll call these two functions (CountryChange
for the dropdown list and LoyaltyChange
for the
checkbox) my “filter functions.”
I also need two customer arrays. The first array is a “complete/unfiltered” array of customers (probably fetched from some Web service). I bind my Grid, however, to a second array, called selectCustomers
, which displays the currently
selected customers. Initially, I want both arrays to be the same, so I initialize the arrays like this:
public customers: Customer[];
public selectCustomers: Customer[] = [];
this.customers = [new Customer(1, "Peter Vogel", "USA", new Date(), 3250, true),
…more Customers…
];
this.selectCustomers = this.customers;
Finally, I define my Grid with the kendo-Grid
component and bind the Grid to the selectCustomers
list. I also define each column in the Grid so that I can bind each column to one of my company’s properties, control how
the data in the column is formatted, set the heading at the top of the column, and specify the column’s width.
All that Grid markup looks like this:
<kendo-Grid
[kendoGridBinding]="selectCustomers"
[height]="250"
(filterChange)= "this.filterCustomers($event)">
<kendo-Grid-column title="Id" field="id" [width]="50"></kendo-Grid-column>
<kendo-Grid-column title="Name" field="custName" [width]="150"></kendo-Grid-column>
<kendo-Grid-column title="Purchase" field="amountPurchased" [width]="125" format="99,999"></kendo-Grid-column>
<kendo-Grid-column title="Signed Up" field="signUpDate" [width]="200" format="MMM-dd-yyyy"></kendo-Grid-column>
<kendo-Grid-column title="In Program" field="inLoyaltyProgram" [width]="100"></kendo-Grid-column>
</kendo-Grid>
To filter a Grid, I need a CompositeFilterDescriptor
that holds zero or more FilterDescriptors
(if the CompositeFilterDescriptor has no FilterDescriptors, then no rows are “filtered out”). I’ll need to import
those two classes and, since I’m also going to need the filterBy
function later on, I’ll import it too:
import { CompositeFilterDescriptor, FilterDescriptor, filterBy }
from "@progress/kendo-data-query";
A FilterDescriptor
has three properties that matter for this case study:
field
: Name of the property to filter onoperator
: The comparison operator to use (e.g., eq, neq, gt, etc.)value
: the value to compareIt makes sense to me to set up two FilterDescriptors—one for filtering by customer and one for filtering by loyalty program—as properties. This lets me set all the properties on each FilterDescriptor to some default values. This simplifies
my later code: As the user makes changes in the dropdown list and checkbox, the only property I have to change in my two filter functions is each FilterDescriptor’s value
property.
Declaring those two FilterDescriptor properties looks like this:
countryFilter: FilterDescriptor = {field:"country", operator:"eq", value:"Pick a country"};
loyaltyFilter: FilterDescriptor = {field:"inLoyaltyProgram", operator:"eq", value:false};
Now, in my filter functions, I just retrieve the current value from the event passed to the function and set the value
property in the appropriate FilterDescriptor
. That’s pretty straightforward for the dropdown list because
the dropdown list’s selection event just passes the value currently selected in the dropdown:
CountryChange(e:string): void
{
this.countryFilter.value = e;
this.CreateFilter();
}
It’s a little more complicated for the checkbox because that function is passed an Event
object. However, I can cast that Event object’s target
property as an HtmlInput
object and then retrieve the element’s
checked
property to set the FieldDescriptor’s value
property:
LoyaltyChange(e:Event): void
{
const cbx = e.target as HTMLInputElement;
this.loyaltyFilter.value = cbx.checked;
this.CreateFilter();
}
The CreateFilter
function I’m calling at the end of each of these functions assembles the two FilterDescriptors into a CompositeFilter
and actually filters my data (you’ll see that function after the following short
interruption).
I’ve only given the user the ability to filter on whether the customer is in the loyalty program—the user can’t filter for customers not in the program. That’s because I used a checkbox in my user interface to control this filtering option.
While the Kendo UI Dropdown list has an “indeterminate” state that allows the checkbox to indicate that it hasn’t been touched yet, once the user checks the checkbox, the checkbox only alternates between true and false states. If I used false to filter the list to those customers not in the loyalty program then, once a user checks the checkbox, the user would be restricted to switching between using true to see those customers in the program and false to see those customers not in the program … but never all the customers.
If I wanted to let the user have the choice of three states for the loyalty program filter (in the loyalty program, not in the loyalty program, and “don’t care about the loyalty program”), I would have had to use some other combination of components (perhaps two radio buttons or another dropdown list).
I avoided this problem with the countries dropdown list by setting the dropdown list’s defaultItem
property to “Pick a Country.” If the user doesn’t change that default entry or returns to that default entry after
filtering on some country, I’ll go back to showing all the customers, regardless of country.
Back to the code.
Now that I’ve built my FilterDescriptors, I combine them in my CreateFilter
method.
In that CreateFilter
function, I check for the four possible combinations of values in the two FilterConditions and use them to load the CompositeFilterDescriptor’s Filters
array:
LoyaltyFilter
is set to false
and the CustomerFilter
is set to the dropdown list’s default value, I don’t set any filters.LoyaltyFilter
is set to true
and CustomerFilter
to some country (i.e., not the default value), I load both filters.When you do have multiple items in the Filters
condition, you must set the CompositeFilterDescriptor’s logic
property to “and” or “or” to indicate how the filters are to be combined. When I do use
both filters, I want them to be “and”ed together so, in my CompositeFilterDescriptor, I’ve set the logic property to “and.”
Where these is only one (or no) filter, it doesn’t matter what the logic property is set to—having the property set to “and” is just fine. That means (lucky me!) that I never have to change the logic property: I either want the filters “and”ed together or I don’t care.
Here’s the code that builds my ComponsiteFilterDescriptor
(there are probably more sophisticated ways of doing this, but this version has the advantage of being obvious):
GridFilter:CompositeFilterDescriptor = {filters:[],logic:"and"};
CreateFilter()
{
if (!this.loyaltyFilter.value && this.countryFilter.value == "Pick a country")
{
this.GridFilter.filters = [];
}
if (!this.loyaltyFilter.value && this.countryFilter.value != "Pick a country")
{
this.GridFilter.filters = [this.countryFilter];
}
if (this.loyaltyFilter.value && this.countryFilter.value == "Pick a country")
{
this.GridFilter.filters = [this.loyaltyFilter];
}
if (this.loyaltyFilter.value && this.countryFilter.value != "Pick a country")
{
this.GridFilter.filters = [this.loyaltyFilter, this.countryFilter];
}
At the end of my CreateFilter
function, I can finally filter my complete array of customers into the selectCustomers
array that my Grid is bound to. To do that, I use the filterBy
function I imported earlier, passing
the complete customers
array and my CompositeFilterDescriptor
. The code that uses the result of calling filterBy
to set the array my Grid is bound to looks like this:
…
{
this.GridFilter.filters = [this.loyaltyFilter, this.countryFilter];
}
this.selectCustomers = filterBy(this.customers, this.GridFilter);
}
Now, as the user interacts with the other parts of my application’s UI, my code is in control of what rows the Grid displays. And, I have to admit, while the Grid’s built-in filtering functionality is great, I do like to be in control.
Next up: You can combine the Kendo UI for Angular Grid’s filtering with your own custom code in any combination that you want … and then call a web service to retrieve the data you want as you need it. Read more: Server-Side Filtering with the Kendo Angular for UI Grid.
Peter Vogel is a system architect and principal in PH&V Information Services. PH&V provides full-stack consulting from UX design through object modeling to database design. Peter also writes courses and teaches for Learning Tree International.