diff --git a/components/grid/aggregates.md b/components/grid/aggregates.md index 2dc803f3b7..1373255d96 100644 --- a/components/grid/aggregates.md +++ b/components/grid/aggregates.md @@ -10,7 +10,7 @@ position: 23 # Grid Aggregates -The Grid component provides built-in aggregates for column values based on [grouping]({%slug components/grid/features/grouping%}). +The Grid component provides built-in aggregates for column values based on [grouping]({%slug components/grid/features/grouping%}) and also a grand total row. There are several available aggregate functions under the `Telerik.Blazor.GridAggregateType` enum: @@ -24,15 +24,17 @@ The `Count` aggregate can be applied to any type of field. The other aggregates You can use aggregates in the following templates: -* `GroupFooterTemplate` of a `GridColumn` - a footer in the respective column that renders when the grid is grouped. -* `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. +* [`GroupFooterTemplate`]({%slug grid-templates-column-group-footer%}) of a `GridColumn` - a footer in the respective column that renders when the grid is grouped. +* [`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. +* [`FooterTemplate`]({%slug grid-templates-column-footer%}) of a `GridColumn` - a grand total row of footers for the entire grid. To enable aggregates: -1. Set the grid's `Groupable` property to `true`. 1. Under the `GridAggregates` tag, define the `GridAggregate` entries to enable the aggregations per field you want to use. 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. -1. Group the grid to see the effect +1. Set the grid's `Groupable` property to `true`. + * If you will be using only `FooterTemplate`s - grouping is not required. +1. Group the grid to see the effect on group-specific templates You should define only aggregates that you will use to avoid unnecessary calculations that may be noticeable on large data sets. @@ -42,16 +44,30 @@ If you try to use an aggregate that is not defined, or an aggregate over an unsu >caption Use Aggregates in the Telerik Blazor Grid ````CSHTML -@* Enable and use aggregates. To see the effect, group by a column - "Team" and then "Active Projects" *@ +@* Enable and use aggregates. To see the full effect, group by a column - "Team" and then "Active Projects" *@ - + + - + + + Total: @context.Count employees. +
+ @{ + // you can use aggregates for other fields/columns by extracting the desired one by its + // field name and aggregate function from the AggregateResults collection + // The type of its Value is determined by the type of its field - decimal for the Salary field here + decimal salaries = (decimal)context.AggregateResults + .FirstOrDefault(r => r.AggregateMethodName == "Sum" && r.Member == "Salary")?.Value; + } + Total salaries: @salaries.ToString("C0") +
+
Team Members: @context.Count @@ -64,9 +80,9 @@ If you try to use an aggregate that is not defined, or an aggregate over an unsu @* you can use a group footer for non-groupable columns as well *@ - Total montly salary: @context.Sum + Total salaries: @context.Sum
- Top paid employee: @context.Max + Highest: @context.Max
@@ -75,7 +91,7 @@ If you try to use an aggregate that is not defined, or an aggregate over an unsu Currently active projects: @context.Value   //sample of conditional logic in the group header - if ( (int)context.Value > 3) // in a real case, you may want to ensure type safety and add defensive checks + if ((int)context.Value > 3) // in a real case, you may want to ensure type safety and add defensive checks { These people work on too many projects } diff --git a/components/grid/images/grid-aggregates-overview.png b/components/grid/images/grid-aggregates-overview.png index 4fd733005b..be57b6bffd 100644 Binary files a/components/grid/images/grid-aggregates-overview.png and b/components/grid/images/grid-aggregates-overview.png differ diff --git a/components/grid/templates/column-footer.md b/components/grid/templates/column-footer.md new file mode 100644 index 0000000000..55048ed754 --- /dev/null +++ b/components/grid/templates/column-footer.md @@ -0,0 +1,222 @@ +--- +title: Column Footer +page_title: Grid - Column Footer Template +description: Use custom column footer templates for grand total data in Grid for Blazor. +slug: grid-templates-column-footer +tags: telerik,blazor,grid,templates,column,footer,grand,total +published: True +position: 20 +--- + +# Column Footer Template + +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. + +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. + + +>caption Footer Template with grand total data + +````CSHTML +@* grand total footer that is always visible *@ + + + + + + + + + + + Total salaries: @context.Sum.Value.ToString("C0") +
+ Highest salary: @context.Max.Value.ToString("C0") +
+
+ + + @{ + // you can use aggregates for other fields/columns by extracting the desired one by its + // field name and aggregate function from the AggregateResults collection + // The type of its Value is determined by the type of its field - decimal for the Salary field here + int headCount = (int)context.AggregateResults + .FirstOrDefault(r => r.AggregateMethodName == "Count" && r.Member == nameof(Employee.EmployeeId))?.Value; + } + Total employees: @headCount + + +
+
+ +@code { + public List GridData { get; set; } + + protected override void OnInitialized() + { + GridData = new List(); + var rand = new Random(); + for (int i = 0; i < 15; i++) + { + Random rnd = new Random(); + GridData.Add(new Employee() + { + EmployeeId = i, + Name = "Employee " + i.ToString(), + Salary = rnd.Next(1000, 5000), + }); + } + } + + public class Employee + { + public int EmployeeId { get; set; } + public string Name { get; set; } + public decimal Salary { get; set; } + } +} +```` + +>caption The result from the code snippet above + +![](images/footer-template.png) + + +## Notes + +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. + +* Aggregate results are based on all the data across all pages. + +* Aggregate results are calculated over filtered data only. + +* 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. + + **Razor** + + @using Telerik.DataSource.Extensions + @using Telerik.DataSource + + The current data aggregates will differ from the aggregates on all the data + + + + + + Total employees: @totalEmployees +
+ Total employees (from current data): @context.Count +
+
+ + + Top salary: @highestSalary +
+ Top salary (from current data): @context.Max +
+
+ + +
+ + + + +
+ + @code { + public List SourceData { get; set; } + public List GridData { get; set; } + public int Total { get; set; } = 0; + + // values for the "real" aggregations + int totalEmployees { get; set; } + decimal highestSalary { get; set; } + + protected override void OnInitialized() + { + SourceData = GenerateData(); + } + + protected async Task ReadItems(GridReadEventArgs args) + { + var datasourceResult = SourceData.ToDataSourceResult(args.Request); + + GridData = (datasourceResult.Data as IEnumerable).ToList(); + Total = datasourceResult.Total; + + // use Telerik Extension methods to aggregate the entire data source per the aggregates defined in the grid + // in a real case, this code should be in a controller that can query the database directly. We cast here for simplicity + IQueryable allDataAsQueriable = SourceData.AsQueryable(); + + // get the aggregate functions from the grid data source request + IEnumerable gridAggregates = Enumerable.Empty(); + if (args.Request.Aggregates.Count == 0) + { + // aggregate descriptors - the ones from the markup will not be present in the first call to OnRead + // because the framework initializes child components too late and the GridAggregates component is not available yet + gridAggregates = new List() + { + new MaxFunction() + { + SourceField = nameof(Employee.Salary) + }, + new CountFunction() + { + SourceField = nameof(Employee.ID) + } + }; + } + else + { + gridAggregates = args.Request.Aggregates.SelectMany(a => a.Aggregates); + } + + // use the Telerik Aggregate() extension method to perform aggregation on the IQueryable collection + AggregateResultCollection aggregatedResults = allDataAsQueriable.Aggregate(gridAggregates); + + // extract the aggregate data like you would within the footer template - by the function and field name + // and put it in the view-model. In a real case that would be extra data returned in the response + totalEmployees = (int)aggregatedResults.FirstOrDefault( + r => r.AggregateMethodName == "Count" && r.Member == nameof(Employee.ID))?.Value; + + highestSalary = (decimal)aggregatedResults.FirstOrDefault( + r => r.AggregateMethodName == "Max" && r.Member == nameof(Employee.Salary))?.Value; + + + // for the grid data update itself + StateHasChanged(); + } + + private List GenerateData() + { + var result = new List(); + var rand = new Random(); + for (int i = 0; i < 100; i++) + { + result.Add(new Employee() + { + ID = i, + Name = "Name " + i, + Salary = rand.Next(1000, 5000), + }); + } + + return result; + } + + public class Employee + { + public int ID { get; set; } + public string Name { get; set; } + public decimal Salary { get; set; } + } + } + +* Footer Templates are not available for the `GridCheckboxColumn` and the `GridCommandColumn`. + + +## See Also + + * [Live Demo: Grid Footer Template](https://demos.telerik.com/blazor-ui/grid/footer-template) + diff --git a/components/grid/templates/images/footer-template.png b/components/grid/templates/images/footer-template.png new file mode 100644 index 0000000000..b084a1d364 Binary files /dev/null and b/components/grid/templates/images/footer-template.png differ