Skip to content

Commit edeca55

Browse files
Grid footer template (#130)
* docs(grid): grand total in aggregates * docs(grid): footer template article * docs(grid): footer docs; onread sample for aggregates * chore(grid): workaround for late aggregates component initialization * chore(grid): fixed typo in column footer article * chore(grid): added slug to data bound columns Co-authored-by: Svetoslav Dimitrov <svdimitr@progress.com>
1 parent fd6bbde commit edeca55

File tree

4 files changed

+249
-11
lines changed

4 files changed

+249
-11
lines changed

components/grid/aggregates.md

Lines changed: 27 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ position: 23
1010

1111
# Grid Aggregates
1212

13-
The Grid component provides built-in aggregates for column values based on [grouping]({%slug components/grid/features/grouping%}).
13+
The Grid component provides built-in aggregates for column values based on [grouping]({%slug components/grid/features/grouping%}) and also a grand total row.
1414

1515
There are several available aggregate functions under the `Telerik.Blazor.GridAggregateType` enum:
1616

@@ -24,15 +24,17 @@ The `Count` aggregate can be applied to any type of field. The other aggregates
2424

2525
You can use aggregates in the following templates:
2626

27-
* `GroupFooterTemplate` of a `GridColumn` - a footer in the respective column that renders when the grid is grouped.
28-
* `GroupHeaderTemplate` of a `GridColumn` - a header in the respective column that renders when the grid is grouped by that column. The `Value` field in the context carries the current group value.
27+
* [`GroupFooterTemplate`]({%slug grid-templates-column-group-footer%}) of a `GridColumn` - a footer in the respective column that renders when the grid is grouped.
28+
* [`GroupHeaderTemplate`]({%slug grid-templates-group-header%}) of a `GridColumn` - a header in the respective column that renders when the grid is grouped by that column. The `Value` field in the context carries the current group value.
29+
* [`FooterTemplate`]({%slug grid-templates-column-footer%}) of a `GridColumn` - a grand total row of footers for the entire grid.
2930

3031
To enable aggregates:
3132

32-
1. Set the grid's `Groupable` property to `true`.
3333
1. Under the `GridAggregates` tag, define the `GridAggregate` entries to enable the aggregations per field you want to use.
3434
1. Use the aggregate result in the templates that support it - their `context` is strongly typed and carries the aggregate values in the respective fields.
35-
1. Group the grid to see the effect
35+
1. Set the grid's `Groupable` property to `true`.
36+
* If you will be using only `FooterTemplate`s - grouping is not required.
37+
1. Group the grid to see the effect on group-specific templates
3638

3739
You should define only aggregates that you will use to avoid unnecessary calculations that may be noticeable on large data sets.
3840

@@ -42,16 +44,30 @@ If you try to use an aggregate that is not defined, or an aggregate over an unsu
4244
>caption Use Aggregates in the Telerik Blazor Grid
4345
4446
````CSHTML
45-
@* Enable and use aggregates. To see the effect, group by a column - "Team" and then "Active Projects" *@
47+
@* Enable and use aggregates. To see the full effect, group by a column - "Team" and then "Active Projects" *@
4648
47-
<TelerikGrid Data=@GridData Groupable="true" Pageable="true" Height="650px">
49+
<TelerikGrid Data=@GridData Groupable="true" Pageable="true" Height="700px">
4850
<GridAggregates>
51+
<GridAggregate Field=@nameof(Employee.Name) Aggregate="@GridAggregateType.Count" />
4952
<GridAggregate Field=@nameof(Employee.Team) Aggregate="@GridAggregateType.Count" />
5053
<GridAggregate Field=@nameof(Employee.Salary) Aggregate="@GridAggregateType.Max" />
5154
<GridAggregate Field=@nameof(Employee.Salary) Aggregate="@GridAggregateType.Sum" />
5255
</GridAggregates>
5356
<GridColumns>
54-
<GridColumn Field=@nameof(Employee.Name) Groupable="false" />
57+
<GridColumn Field=@nameof(Employee.Name) Groupable="false">
58+
<FooterTemplate>
59+
Total: @context.Count employees.
60+
<br />
61+
@{
62+
// you can use aggregates for other fields/columns by extracting the desired one by its
63+
// field name and aggregate function from the AggregateResults collection
64+
// The type of its Value is determined by the type of its field - decimal for the Salary field here
65+
decimal salaries = (decimal)context.AggregateResults
66+
.FirstOrDefault(r => r.AggregateMethodName == "Sum" && r.Member == "Salary")?.Value;
67+
}
68+
Total salaries: @salaries.ToString("C0")
69+
</FooterTemplate>
70+
</GridColumn>
5571
<GridColumn Field=@nameof(Employee.Team) Title="Team">
5672
<GroupFooterTemplate>
5773
Team Members: <strong>@context.Count</strong>
@@ -64,9 +80,9 @@ If you try to use an aggregate that is not defined, or an aggregate over an unsu
6480
<GridColumn Field=@nameof(Employee.Salary) Title="Salary" Groupable="false">
6581
<GroupFooterTemplate>
6682
@* you can use a group footer for non-groupable columns as well *@
67-
Total montly salary: @context.Sum
83+
Total salaries: @context.Sum
6884
<br />
69-
<span style="color: red;">Top paid employee: @context.Max</span>
85+
<span style="color: red;">Highest: @context.Max</span>
7086
</GroupFooterTemplate>
7187
</GridColumn>
7288
<GridColumn Field=@nameof(Employee.ActiveProjects) Title="Active Projects">
@@ -75,7 +91,7 @@ If you try to use an aggregate that is not defined, or an aggregate over an unsu
7591
<span>Currently active projects: @context.Value &nbsp;</span>
7692
7793
//sample of conditional logic in the group header
78-
if ( (int)context.Value > 3) // in a real case, you may want to ensure type safety and add defensive checks
94+
if ((int)context.Value > 3) // in a real case, you may want to ensure type safety and add defensive checks
7995
{
8096
<strong style="color: red;">These people work on too many projects</strong>
8197
}
1.19 KB
Loading
Lines changed: 222 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,222 @@
1+
---
2+
title: Column Footer
3+
page_title: Grid - Column Footer Template
4+
description: Use custom column footer templates for grand total data in Grid for Blazor.
5+
slug: grid-templates-column-footer
6+
tags: telerik,blazor,grid,templates,column,footer,grand,total
7+
published: True
8+
position: 20
9+
---
10+
11+
# Column Footer Template
12+
13+
You can display a grand total row at the bottom of the grid through the `FooterTemplate` of each [bound]({%slug components/grid/columns/bound%}) column.
14+
15+
You can use [aggregates]({%slug grid-aggregates%}) for the current field directly from the `context`, and its `AggregateResults` field lets you get aggregates for other fields that you have defined through their field name and aggregate function.
16+
17+
18+
>caption Footer Template with grand total data
19+
20+
````CSHTML
21+
@* grand total footer that is always visible *@
22+
23+
<TelerikGrid Data=@GridData Pageable="true" Height="300px">
24+
<GridAggregates>
25+
<GridAggregate Field=@nameof(Employee.Salary) Aggregate="@GridAggregateType.Max" />
26+
<GridAggregate Field=@nameof(Employee.Salary) Aggregate="@GridAggregateType.Sum" />
27+
<GridAggregate Field=@nameof(Employee.EmployeeId) Aggregate="@GridAggregateType.Count" />
28+
</GridAggregates>
29+
<GridColumns>
30+
<GridColumn Field=@nameof(Employee.Salary) Title="Salary">
31+
<FooterTemplate>
32+
Total salaries: @context.Sum.Value.ToString("C0")
33+
<br />
34+
Highest salary: @context.Max.Value.ToString("C0")
35+
</FooterTemplate>
36+
</GridColumn>
37+
<GridColumn Field=@nameof(Employee.Name)>
38+
<FooterTemplate>
39+
@{
40+
// you can use aggregates for other fields/columns by extracting the desired one by its
41+
// field name and aggregate function from the AggregateResults collection
42+
// The type of its Value is determined by the type of its field - decimal for the Salary field here
43+
int headCount = (int)context.AggregateResults
44+
.FirstOrDefault(r => r.AggregateMethodName == "Count" && r.Member == nameof(Employee.EmployeeId))?.Value;
45+
}
46+
Total employees: @headCount
47+
</FooterTemplate>
48+
</GridColumn>
49+
</GridColumns>
50+
</TelerikGrid>
51+
52+
@code {
53+
public List<Employee> GridData { get; set; }
54+
55+
protected override void OnInitialized()
56+
{
57+
GridData = new List<Employee>();
58+
var rand = new Random();
59+
for (int i = 0; i < 15; i++)
60+
{
61+
Random rnd = new Random();
62+
GridData.Add(new Employee()
63+
{
64+
EmployeeId = i,
65+
Name = "Employee " + i.ToString(),
66+
Salary = rnd.Next(1000, 5000),
67+
});
68+
}
69+
}
70+
71+
public class Employee
72+
{
73+
public int EmployeeId { get; set; }
74+
public string Name { get; set; }
75+
public decimal Salary { get; set; }
76+
}
77+
}
78+
````
79+
80+
>caption The result from the code snippet above
81+
82+
![](images/footer-template.png)
83+
84+
85+
## Notes
86+
87+
Since the purpose of the footer template is to display aggregates, you need to be aware of their behavior. The following list expands on that and other things to keep in mind.
88+
89+
* Aggregate results are based on all the data across all pages.
90+
91+
* Aggregate results are calculated over filtered data only.
92+
93+
* When using the [`OnRead event`]({%slug components/grid/manual-operations%}), the aggregates are based on the available data - for the current page only. If you want to aggregate over the entire data source, you should get the desired information into the view model with the appropriate code in the application, instead of using the built-in grid Aggregates. The `Aggregate` extension method from our `Telerik.DataSource.Extensions` namespace can help you calculate them.
94+
95+
**Razor**
96+
97+
@using Telerik.DataSource.Extensions
98+
@using Telerik.DataSource
99+
100+
The current data aggregates will differ from the aggregates on all the data
101+
102+
<TelerikGrid Data=@GridData TotalCount=@Total OnRead=@ReadItems Pageable="true">
103+
<GridColumns>
104+
<GridColumn Field=@nameof(Employee.ID)>
105+
<FooterTemplate>
106+
Total employees: @totalEmployees
107+
<hr />
108+
Total employees (from current data): @context.Count
109+
</FooterTemplate>
110+
</GridColumn>
111+
<GridColumn Field="@nameof(Employee.Salary)">
112+
<FooterTemplate>
113+
Top salary: @highestSalary
114+
<hr />
115+
Top salary (from current data): @context.Max
116+
</FooterTemplate>
117+
</GridColumn>
118+
<GridColumn Field=@nameof(Employee.Name) Title="Name">
119+
</GridColumn>
120+
</GridColumns>
121+
<GridAggregates>
122+
<GridAggregate Field=@nameof(Employee.Salary) Aggregate="@GridAggregateType.Max" />
123+
<GridAggregate Field=@nameof(Employee.ID) Aggregate="@GridAggregateType.Count" />
124+
</GridAggregates>
125+
</TelerikGrid>
126+
127+
@code {
128+
public List<Employee> SourceData { get; set; }
129+
public List<Employee> GridData { get; set; }
130+
public int Total { get; set; } = 0;
131+
132+
// values for the "real" aggregations
133+
int totalEmployees { get; set; }
134+
decimal highestSalary { get; set; }
135+
136+
protected override void OnInitialized()
137+
{
138+
SourceData = GenerateData();
139+
}
140+
141+
protected async Task ReadItems(GridReadEventArgs args)
142+
{
143+
var datasourceResult = SourceData.ToDataSourceResult(args.Request);
144+
145+
GridData = (datasourceResult.Data as IEnumerable<Employee>).ToList();
146+
Total = datasourceResult.Total;
147+
148+
// use Telerik Extension methods to aggregate the entire data source per the aggregates defined in the grid
149+
// in a real case, this code should be in a controller that can query the database directly. We cast here for simplicity
150+
IQueryable allDataAsQueriable = SourceData.AsQueryable();
151+
152+
// get the aggregate functions from the grid data source request
153+
IEnumerable<AggregateFunction> gridAggregates = Enumerable.Empty<AggregateFunction>();
154+
if (args.Request.Aggregates.Count == 0)
155+
{
156+
// aggregate descriptors - the ones from the markup will not be present in the first call to OnRead
157+
// because the framework initializes child components too late and the GridAggregates component is not available yet
158+
gridAggregates = new List<AggregateFunction>()
159+
{
160+
new MaxFunction()
161+
{
162+
SourceField = nameof(Employee.Salary)
163+
},
164+
new CountFunction()
165+
{
166+
SourceField = nameof(Employee.ID)
167+
}
168+
};
169+
}
170+
else
171+
{
172+
gridAggregates = args.Request.Aggregates.SelectMany(a => a.Aggregates);
173+
}
174+
175+
// use the Telerik Aggregate() extension method to perform aggregation on the IQueryable collection
176+
AggregateResultCollection aggregatedResults = allDataAsQueriable.Aggregate(gridAggregates);
177+
178+
// extract the aggregate data like you would within the footer template - by the function and field name
179+
// and put it in the view-model. In a real case that would be extra data returned in the response
180+
totalEmployees = (int)aggregatedResults.FirstOrDefault(
181+
r => r.AggregateMethodName == "Count" && r.Member == nameof(Employee.ID))?.Value;
182+
183+
highestSalary = (decimal)aggregatedResults.FirstOrDefault(
184+
r => r.AggregateMethodName == "Max" && r.Member == nameof(Employee.Salary))?.Value;
185+
186+
187+
// for the grid data update itself
188+
StateHasChanged();
189+
}
190+
191+
private List<Employee> GenerateData()
192+
{
193+
var result = new List<Employee>();
194+
var rand = new Random();
195+
for (int i = 0; i < 100; i++)
196+
{
197+
result.Add(new Employee()
198+
{
199+
ID = i,
200+
Name = "Name " + i,
201+
Salary = rand.Next(1000, 5000),
202+
});
203+
}
204+
205+
return result;
206+
}
207+
208+
public class Employee
209+
{
210+
public int ID { get; set; }
211+
public string Name { get; set; }
212+
public decimal Salary { get; set; }
213+
}
214+
}
215+
216+
* Footer Templates are not available for the `GridCheckboxColumn` and the `GridCommandColumn`.
217+
218+
219+
## See Also
220+
221+
* [Live Demo: Grid Footer Template](https://demos.telerik.com/blazor-ui/grid/footer-template)
222+
12.1 KB
Loading

0 commit comments

Comments
 (0)