Skip to content

Commit edf1c53

Browse files
Treelist filter template (#113)
* docs(treelist): filter cell example * docs(treelist): filter menu example * chore(treelist): commit image; likn from templates overview
1 parent ded068f commit edf1c53

File tree

5 files changed

+367
-2
lines changed

5 files changed

+367
-2
lines changed

components/grid/templates/overview.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ The Grid component can use templates for:
3131

3232

3333

34-
Like other Blazor content, most of them can receive a `context` argument that is the type of the model. To use templates, you must bind the grid to a named model. The filter templates are the exception as they are not related to rows and models.
34+
Like other Blazor content, most of them can receive a `context` argument that is the type of the model. To use templates, you must bind the grid to a named model. The filter and header templates are the exception as they are not related to rows and models.
3535

3636
You must make sure to provide valid HTML in the templates.
3737

Lines changed: 362 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,362 @@
1+
---
2+
title: Filter
3+
page_title: TreeList - Filter Template
4+
description: Use custom filter templates in treelist for Blazor.
5+
slug: treelist-templates-filter
6+
tags: telerik,blazor,treelist,templates,filter
7+
published: True
8+
position: 35
9+
---
10+
11+
12+
# Filter Template
13+
14+
The Filter Template lets you customize the appearance and logic of the built-in filters. It lets you step on the built-in filtering logic of the treelist and implement your own design and logic for setting their values.
15+
16+
There are two different templates you can use depending on the [Filter Mode]({%slug treelist-filtering%}) that you chose:
17+
18+
* [Filter Row Template](#filter-row-template)
19+
* [Filter Menu Template](#filter-menu-template)
20+
21+
22+
## Filter Row Template
23+
24+
By default, the filter row puts an appropriate editor (like a numeric textbox for numbers) and its `ValueChanged` handler triggers treelist filtering on every keystroke. There is also a button for the user to choose a filter operator, and a clear filter button when there is a value in the editor.
25+
26+
To customize the filter cell, use the `<FilterCellTemplate>` tag of the `<TreeListColumn>`. It receives a `context` of type `FilterCellTemplateContext` that provides the following members:
27+
28+
* `FilterDescriptor` - the object that describes the column filter. By default it has a first filter with the type and name of the field, and you can add more to its `FilterDescriptors` collection, or change its `LogicalOperator` from the default `AND`.
29+
30+
* `FilterAsync()` - an `async` method that invokes the built-in treelist filtering logic so you can call upon it easily from your template (e.g., when a value changes or a button is clicked).
31+
32+
* `ClearFilterAsync()` - an `async` method that invokes the built-in treelist clear filtering logic so you can call upon it easily from your template (e.g., when a value is cleared or a button is clicked).
33+
34+
You can store a reference to each column's context in a field in the view-model, so you can write the handlers in the standard C# code, instead of using lambdas in the markup. You can also pass the context as a Parameter to your own separate filter component to reduce clutter in the main treelist markup and code.
35+
36+
### Examples
37+
38+
The example below shows a custom filter that:
39+
40+
* Implements a min-max filter in the filter cell through two numeric textboxes.
41+
* Filters in the `OnChange` event (only when the user presses Enter or blurs the input).
42+
* Shows how you can store a reference to the context or use it inline in the template.
43+
* Showcases building a filter descriptor with two filters and sample logic that always filters the data even if one of the inputs is empty.
44+
45+
46+
>caption Custom Filter Row Template - Min and Max filters on OnChange
47+
48+
````CSHTML
49+
@using Telerik.DataSource
50+
51+
The custom filter textboxes invoke filtering on Enter or blur through the OnChange event.
52+
Note that a treelist keeps parent items when filtering should show child items.
53+
For example, try filtering with a Min value of 50+ to leave only root-level items in this sample.
54+
55+
<TelerikTreeList Data="@Data" FilterMode="@TreeListFilterMode.FilterRow"
56+
Pageable="true" IdField="Id" ParentIdField="ParentId" Width="850px">
57+
<TreeListColumns>
58+
<TreeListColumn Field="Name" Expandable="true" Width="320px" Filterable="false" />
59+
<TreeListColumn Field="Id" Filterable="false" Width="100px" />
60+
<TreeListColumn Field="Age" Width="350px">
61+
<FilterCellTemplate>
62+
@{
63+
// we store a reference to the filter context to use in the business logic
64+
// you can also use it inline in the template, like with the Clear button below
65+
theFilterContext = context;
66+
}
67+
68+
<label for="min">Min:&nbsp;</label>
69+
<TelerikNumericTextBox Id="min"
70+
@bind-Value="@MinValue"
71+
OnChange="@SetupFilterRule">
72+
</TelerikNumericTextBox>
73+
<label for="min">Max:&nbsp;</label>
74+
<TelerikNumericTextBox Id="max"
75+
@bind-Value="@MaxValue"
76+
OnChange="@SetupFilterRule">
77+
</TelerikNumericTextBox>
78+
<TelerikButton ButtonType="ButtonType.Button"
79+
Class="k-clear-button-visible ml-2"
80+
Icon="filter-clear"
81+
Enabled="@( MinValue != null || MaxValue != null )"
82+
OnClick="@(async () =>
83+
{
84+
MinValue = MaxValue = null;
85+
86+
// clear filter through the method the context provides
87+
await context.ClearFilterAsync();
88+
})">
89+
</TelerikButton>
90+
</FilterCellTemplate>
91+
</TreeListColumn>
92+
</TreeListColumns>
93+
</TelerikTreeList>
94+
95+
@code {
96+
FilterCellTemplateContext theFilterContext { get; set; }
97+
public decimal? MinValue { get; set; }
98+
public decimal? MaxValue { get; set; }
99+
100+
async Task SetupFilterRule()
101+
{
102+
// set up min value filter - there is one default filter descriptor
103+
// that alredy has the field set up, so we use that for the MIN filter
104+
// and set up a value and operator
105+
var filter1 = theFilterContext.FilterDescriptor.FilterDescriptors[0] as FilterDescriptor;
106+
filter1.Value = MinValue == null ? int.MinValue : MinValue;
107+
filter1.Operator = FilterOperator.IsGreaterThan;
108+
109+
// set up max value filter - we may have to crete a new filter descriptor
110+
// if there wasn't one already so we prepare it first and check whether we have the second filter
111+
var filter2Val = MaxValue == null ? int.MaxValue : MaxValue;
112+
var filter2 = new FilterDescriptor("Age", FilterOperator.IsLessThan, filter2Val);
113+
filter2.MemberType = typeof(decimal);
114+
115+
if (theFilterContext.FilterDescriptor.FilterDescriptors.Count > 1)
116+
{
117+
theFilterContext.FilterDescriptor.FilterDescriptors[1] = filter2;
118+
}
119+
else
120+
{
121+
theFilterContext.FilterDescriptor.FilterDescriptors.Add(filter2);
122+
}
123+
124+
// ensure logical operator between the two filters is AND (it is the default, but we showcase the option)
125+
theFilterContext.FilterDescriptor.LogicalOperator = FilterCompositionLogicalOperator.And;
126+
127+
// invoke filtering through the method the context provides
128+
await theFilterContext.FilterAsync();
129+
}
130+
131+
132+
// sample treelist data follows
133+
134+
public List<Employee> Data { get; set; }
135+
136+
protected override async Task OnInitializedAsync()
137+
{
138+
Data = await GetTreeListData();
139+
}
140+
141+
// sample models and data generation
142+
143+
public class Employee
144+
{
145+
public int Id { get; set; }
146+
public int? ParentId { get; set; }
147+
public string Name { get; set; }
148+
public int Age { get; set; }
149+
}
150+
151+
async Task<List<Employee>> GetTreeListData()
152+
{
153+
List<Employee> data = new List<Employee>();
154+
Random rand = new Random();
155+
156+
for (int i = 1; i < 15; i++)
157+
{
158+
data.Add(new Employee
159+
{
160+
Id = i,
161+
ParentId = null,
162+
Name = $"root: {i}",
163+
Age = rand.Next(50, 67)
164+
});
165+
166+
for (int j = 2; j < 5; j++)
167+
{
168+
int currId = i * 100 + j;
169+
data.Add(new Employee
170+
{
171+
Id = currId,
172+
ParentId = i,
173+
Name = $" child {j} of {i}",
174+
Age = rand.Next(18, 50)
175+
});
176+
}
177+
}
178+
179+
return await Task.FromResult(data);
180+
}
181+
}
182+
183+
@* sample CSS rule to align the custom label elements in the filter cell *@
184+
<style>
185+
.k-filtercell-wrapper {
186+
align-items: center;
187+
}
188+
.k-filtercell-wrapper label {
189+
margin: unset;
190+
}
191+
</style>
192+
````
193+
194+
>caption The result from the code snippet above after filtering
195+
196+
![Custom Filter Cell Template - Min and Max](images/custom-filter-cell-min-max.png)
197+
198+
199+
## Filter Menu Template
200+
201+
By default, the filter menu contains two filter values that are tied with a logical operator - OR or AND, with filgering being triggered through a dedicated Filter button and a Clear button removes the filter.
202+
203+
To customize the filter menu, use the `<FilterMenuTemplate>` tag of the `<TreeListColumn>`. The `Filter` and `Clear` buttons are still available below the template.
204+
205+
The template receives a `context` of type `FilterMenuTemplateContext` that provides the following members:
206+
207+
* `FilterDescriptor` - the object that describes the column filter. By default it has two filters with the type and name of the field, and you can add more to its `FilterDescriptors` collection, or change its `LogicalOperator` from the default `AND`.
208+
209+
You can store a reference to each column's context in a field in the view-model, so you can reference it from event handlers in the standard C# code, instead of passing it as a nargument to lambdas in the markup only. You can also pass the context as a Parameter to your own separate filter component to reduce clutter in the main treelist markup and code.
210+
211+
### Examples
212+
213+
The example below shows a custom filter that:
214+
215+
* Implements a multi checkbox filter that lets the user choose several values from the data source.
216+
* Shows how you can store a reference to the context or use it inline in the template.
217+
* Showcases building multiple filter descriptors for each value the user chooses.
218+
219+
220+
>caption Custom Filter Menu Template - Multiple Checkboxes
221+
222+
````CSHTML
223+
@using Telerik.DataSource
224+
225+
This custom filter menu lets you choose more than one option to match against the data source
226+
Note that a treelist keeps parent items when filtering should show child items.
227+
For example, try filtering just for a "Manager" to leave only root-level items in this sample.
228+
229+
<TelerikTreeList Data="@Data" FilterMode="@TreeListFilterMode.FilterMenu"
230+
Pageable="true" IdField="Id" ParentIdField="ParentId" Width="850px">
231+
<TreeListColumns>
232+
<TreeListColumn Field="Name" Expandable="true" Width="320px" Filterable="false" />
233+
<TreeListColumn Field="Id" Filterable="false" Width="100px" />
234+
<TreeListColumn Field="Role" Width="350px">
235+
<FilterMenuTemplate>
236+
@{
237+
// we store a reference to the filter context to use in the business logic to show we can
238+
// we could, alternatively pass it as an argument to the event handler in the lambda expression
239+
// which can be useful if you want to use the same filter for several columns
240+
// you could then pass more arguments to the business logic such as field name and so on
241+
theFilterContext = context;
242+
}
243+
244+
@foreach (var role in Roles)
245+
{
246+
<div>
247+
<TelerikCheckBox Value="@(IsCheckboxInCurrentFilter(context.FilterDescriptor, role))"
248+
TValue="bool"
249+
ValueChanged="@((value) => UpdateCheckedRoles(value, role))"
250+
Id="@($"role_{role}")">
251+
</TelerikCheckBox>
252+
<label for="@($"role_{role}")">
253+
@role
254+
</label>
255+
</div>
256+
}
257+
</FilterMenuTemplate>
258+
</TreeListColumn>
259+
</TreeListColumns>
260+
</TelerikTreeList>
261+
262+
@code {
263+
FilterMenuTemplateContext theFilterContext { get; set; }
264+
public List<string> CheckedRoles { get; set; } = new List<string>();
265+
266+
public bool IsCheckboxInCurrentFilter(CompositeFilterDescriptor filterDescriptor, string size)
267+
{
268+
// get all current filter descriptors and evaluate whether to select the current checkbox
269+
return filterDescriptor.FilterDescriptors.Select(f => (f as FilterDescriptor).Value?.ToString()).ToList().Contains(size);
270+
}
271+
272+
public void UpdateCheckedRoles(bool value, string itemValue)
273+
{
274+
// update the list of items we want to filter by
275+
var isSizeChecked = CheckedRoles.Contains(itemValue);
276+
if (value && !isSizeChecked)
277+
{
278+
CheckedRoles.Add(itemValue);
279+
}
280+
281+
if (!value && isSizeChecked)
282+
{
283+
CheckedRoles.Remove(itemValue);
284+
}
285+
286+
// prepare filter descriptor
287+
var filterDescriptor = theFilterContext.FilterDescriptor;
288+
289+
filterDescriptor.FilterDescriptors.Clear();
290+
// use the OR logical operator so we include all possible values
291+
filterDescriptor.LogicalOperator = FilterCompositionLogicalOperator.Or;
292+
CheckedRoles.ForEach(s =>
293+
// instantiate a filter descriptor for the desired field, and with the desired operator and value
294+
filterDescriptor.FilterDescriptors.Add(new FilterDescriptor("Role", FilterOperator.IsEqualTo, s))
295+
);
296+
297+
//ensure there is at least one blank filter to avoid null reference exceptions
298+
if (!filterDescriptor.FilterDescriptors.Any())
299+
{
300+
filterDescriptor.FilterDescriptors.Add(new FilterDescriptor());
301+
}
302+
}
303+
304+
305+
// sample treelist data
306+
307+
public List<Employee> Data { get; set; }
308+
public static List<string> Roles = new List<string> { "Manager", "Employee", "Contractor" };
309+
310+
protected override async Task OnInitializedAsync()
311+
{
312+
Data = await GetTreeListData();
313+
}
314+
315+
// sample models and data generation
316+
317+
public class Employee
318+
{
319+
public int Id { get; set; }
320+
public int? ParentId { get; set; }
321+
public string Name { get; set; }
322+
public string Role { get; set; }
323+
}
324+
325+
async Task<List<Employee>> GetTreeListData()
326+
{
327+
List<Employee> data = new List<Employee>();
328+
329+
for (int i = 1; i < 15; i++)
330+
{
331+
data.Add(new Employee
332+
{
333+
Id = i,
334+
ParentId = null,
335+
Name = $"root: {i}",
336+
Role = Roles[0] // manager at root level
337+
});
338+
339+
for (int j = 2; j < 5; j++)
340+
{
341+
int currId = i * 100 + j;
342+
data.Add(new Employee
343+
{
344+
Id = currId,
345+
ParentId = i,
346+
Name = $" child {j} of {i}",
347+
Role = Roles[j % 2 == 0 ? 1 : 2] // the employee and contractor roles
348+
});
349+
}
350+
}
351+
352+
return await Task.FromResult(data);
353+
}
354+
}
355+
````
356+
357+
>caption The result from the code snippet above, after filtering
358+
359+
![Custom Filter Menu Template with Checkboxes](images/custom-filter-menu-checkboxes.png)
360+
361+
362+
Loading
Loading

components/treelist/templates/overview.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,11 @@ The TreeList component can use templates for:
2222

2323
* [column header]({%slug treelist-templates-column-header%}) - the title portion of the column.
2424

25+
* [filter]({%slug treelist-templates-filter%}) - the content of the filter cell or filter menu where you can implement custom rendering and logic for the filters.
2526

26-
Like other Blazor content, most of them can receive a `context` argument that is the type of the model. To use templates, you must bind the treelist to a named model. The header templates are the exception as they are not related to rows and models.
27+
28+
29+
Like other Blazor content, most of them can receive a `context` argument that is the type of the model. To use templates, you must bind the treelist to a named model. The filter and header templates are the exception as they are not related to rows and models.
2730

2831
You must make sure to provide valid HTML in the templates.
2932

0 commit comments

Comments
 (0)