|
| 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 | + |
| 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 | + |
0 commit comments