From 777c5dc63152b9ed18e5c2fa1a5d5234626ba93e Mon Sep 17 00:00:00 2001 From: Dimo Dimov Date: Tue, 18 Jan 2022 14:28:35 +0200 Subject: [PATCH 1/4] docs: Document OnRead breaking changes --- .../common/dropdowns-virtualization.md | 11 +- common-features/loading-sign.md | 20 +- components/autocomplete/events.md | 99 +++--- components/autocomplete/virtualization.md | 41 +-- components/combobox/events.md | 98 +++--- components/combobox/virtualization.md | 25 +- components/dropdownlist/events.md | 97 +++--- components/dropdownlist/virtualization.md | 26 +- components/grid/filter/checkboxlist.md | 23 +- components/grid/grouping/load-on-demand.md | 14 +- components/grid/manual-operations.md | 321 +++++------------- components/grid/refresh-data.md | 23 +- components/grid/templates/column-footer.md | 127 +------ components/grid/virtual-scrolling.md | 13 +- components/listview/manual-operations.md | 27 +- components/multiselect/events.md | 91 ++--- components/multiselect/virtualization.md | 21 +- components/scheduler/manual-operations.md | 2 +- components/scheduler/templates/dateheader.md | 2 +- knowledge-base/combo-debounce-onread.md | 36 +- .../grid-aggregates-and-datatable.md | 95 ++---- .../grid-combobox-in-filtermenu-empty.md | 7 +- knowledge-base/grid-debounce-operations.md | 28 +- knowledge-base/grid-expand-button-tooltip.md | 27 +- knowledge-base/grid-expand-only-current.md | 63 +--- knowledge-base/grid-force-refresh.md | 101 +----- knowledge-base/grid-get-filtered-data.md | 55 +-- knowledge-base/grid-get-index-of-grid-row.md | 143 ++------ .../grid-large-skip-breaks-virtualization.md | 39 +-- .../grid-one-expanded-detail-template.md | 37 +- knowledge-base/grid-row-numbers.md | 30 +- .../grid-search-in-hidden-fields.md | 30 +- upgrade/breaking-changes/3-0-0.md | 107 +++++- 33 files changed, 693 insertions(+), 1186 deletions(-) diff --git a/_contentTemplates/common/dropdowns-virtualization.md b/_contentTemplates/common/dropdowns-virtualization.md index 0b6e20c8c4..fabbb1dfe3 100644 --- a/_contentTemplates/common/dropdowns-virtualization.md +++ b/_contentTemplates/common/dropdowns-virtualization.md @@ -13,11 +13,11 @@ This section will explain the parameters and behaviors that are related to the v * `ScrollMode` - `Telerik.Blazor.DropDownScrollMode` - set it to `DropDownScrollMode.Virtual`. It defaults to the "regular" scrolling. -* `PopupHeight` - `string` - set the height of the popup element to a valid CSS unit. It must **not** be a `null/empty` string. +* `Height` - `string` - [set the height]({%slug common-features/dimensions%}) in the nested **popup settings** tag of the component. It must **not** be a `null/empty` string. * `ItemHeight` - `decimal` - set it to the height each individual item will have in the dropdown. Make sure to accommodate the content your items will have and any item template. -* `PageSize` - `int` - defines how many items will actually be rendered and reused. The value determines how many items are loaded on each scroll. The number of items must be large enough according to the `ItemHeight` and `PopupHeight` so that there are more items than the dropdown so there is a scrollbar. +* `PageSize` - `int` - defines how many items will actually be rendered and reused. The value determines how many items are loaded on each scroll. The number of items must be large enough according to the `ItemHeight` and popup `Height`, so that there are more items than the dropdown so there is a scrollbar. You can find a basic example in the [Local Data](#local-data-example) section below. @@ -27,16 +27,13 @@ You can find a basic example in the [Local Data](#local-data-example) section be #value-mapper-text -the component will call this method to request the model that matches the `Value` it has set. This is required because with remote data the `Value` may not be in the initial collection of `Data` that the component has, and so there would otherwise be no way to extract the `DataTextField` from it to render it. Usually, this method will be called on the initial render only to fetch the data item for the current selection. +the component will call this method to request the model that matches the `Value` it has set. This is required because with remote data the `Value` may not be in the initial collection of data that the component has, and so there would otherwise be no way to extract the `DataTextField` from it to render it. Usually, this method will be called on the initial render only to fetch the data item for the current selection. #end #remote-data-specifics -* `OnRead` - `EventCallback` - the component will call this event when the user scrolls with the corresponding offset (`Skip`), `PageSize` and any filters. This lets you optimize the data queries and return only what is needed at the moment, when it is needed. - -* `TotalCount` - `int` - the total number of items that the dropdown can have. Needs to take into account any current filtering. -#end +* `OnRead` - `EventCallback` - the component will call this event when the user scrolls with the corresponding offset (`Skip`), `PageSize` and any filters. This lets you optimize the data queries and return only what is needed at the moment, when it is needed. Set the `args.Data` and `args.Total` properties of the event argument object. diff --git a/common-features/loading-sign.md b/common-features/loading-sign.md index 809294a582..061ca60554 100644 --- a/common-features/loading-sign.md +++ b/common-features/loading-sign.md @@ -358,16 +358,15 @@ We understand, however, that you might want to disable this feature in some case @* The data operations (such as filtering, sorting, paging) are slow in this example, but there is no loading sign *@ + TItem="@Employee" OnRead="@ReadItems" + FilterMode="@GridFilterMode.FilterRow" + Sortable="true" + Pageable="true"> - - Add Employee - @* Everything else here is sample data binding *@ @@ -376,8 +375,6 @@ We understand, however, that you might want to disable this feature in some case @code { public List SourceData { get; set; } - public List GridData { get; set; } - public int Total { get; set; } = 0; protected override void OnInitialized() { @@ -390,15 +387,10 @@ We understand, however, that you might want to disable this feature in some case var datasourceResult = SourceData.ToDataSourceResult(args.Request); - GridData = (datasourceResult.Data as IEnumerable).ToList(); - Total = datasourceResult.Total; - - StateHasChanged(); + args.Data = (datasourceResult.Data as IEnumerable).ToList(); + args.Total = datasourceResult.Total; } - //This sample implements only reading of the data. To add the rest of the CRUD operations see - //https://docs.telerik.com/blazor-ui/components/grid/editing/overview - private List GenerateData() { var result = new List(); diff --git a/components/autocomplete/events.md b/components/autocomplete/events.md index a2200cb066..548ab6f6c9 100644 --- a/components/autocomplete/events.md +++ b/components/autocomplete/events.md @@ -77,7 +77,6 @@ from model: @Role } ```` - ## OnChange The `OnChange` event represents a user action - confirmation of the current value/item. The key differences with `ValueChanged` are: @@ -121,32 +120,34 @@ You can use the he `OnRead` event to provide data to the component according to You can also call remote data through async operations. +When using `OnRead`, make sure to set `TItem` and `TValue`. + >caption Custom Data according to the user input in the AutoComplete ````CSHTML -@SelectedValue -
-@AutoCompleteValue

+ + + Placeholder="Type anything"> -@code{ - public string SelectedValue { get; set; } +@code { + public string AutoCompleteValue { get; set; } List Suggestions { get; set; } = new List(); async Task ReadItems(AutoCompleteReadEventArgs args) { - if (args.Request.Filters.Count > 0) // there is user filter input, skips providing data on initialization + if (args.Request.Filters.Count > 0) // wait for user filter input { Telerik.DataSource.FilterDescriptor filter = args.Request.Filters[0] as Telerik.DataSource.FilterDescriptor; string userInput = filter.Value.ToString(); string method = filter.Operator.ToString(); //new data collection comes down from the service - Suggestions = await GetSuggestionsData(userInput, method); + args.Data = await GetSuggestionsData(userInput, method); } } @@ -157,7 +158,7 @@ You can also call remote data through async operations. //sample logic for getting suggestions - here they are generated, you can call a remote service //for brevity, this example does not use the filter operator, but your actual service can List suggestionsData = new List(); - for (int i = 0; i < 5; i++) + for (int i = 1; i <= 5; i++) { suggestionsData.Add($"suggestion {i} for input {userInput}"); } @@ -172,53 +173,51 @@ You can also call remote data through async operations. ````CSHTML @using Telerik.DataSource.Extensions -@SelectedValue -
- +

@AutoCompleteValue

+ + @code { - public string SelectedValue { get; set; } + public string AutoCompleteValue { get; set; } List AllSuggestions { get; set; } - List CurrentSuggestions { get; set; } - protected async Task ReadItems(AutoCompleteReadEventArgs args) { - //generate the big data source that we want to narrow down for the user - //in a real case you would probably have fetched it in OnInitializedAsync - if (AllSuggestions == null) - { - AllSuggestions = new List - { - new Car { Id = 1, Make = "Honda" }, - new Car { Id = 2, Make = "Opel" }, - new Car { Id = 3, Make = "Audi" }, - new Car { Id = 4, Make = "Lancia" }, - new Car { Id = 5, Make = "BMW" }, - new Car { Id = 6, Make = "Mercedes" }, - new Car { Id = 7, Make = "Tesla" }, - new Car { Id = 8, Make = "Vw" }, - new Car { Id = 9, Make = "Alpha Romeo" }, - new Car { Id = 10, Make = "Chevrolet" }, - new Car { Id = 11, Make = "Ford" }, - new Car { Id = 12, Make = "Cadillac" }, - new Car { Id = 13, Make = "Dodge" }, - new Car { Id = 14, Make = "Jeep" }, - new Car { Id = 15, Make = "Chrysler" }, - new Car { Id = 16, Make = "Lincoln" } - }; - } - - //use Telerik extension methods to filter the data source based on the request from the component + //using Telerik extension methods to filter the data var datasourceResult = AllSuggestions.ToDataSourceResult(args.Request); - CurrentSuggestions = (datasourceResult.Data as IEnumerable).ToList(); + args.Data = datasourceResult.Data; + } + + protected override void OnInitialized() + { + AllSuggestions = new List { + new Car { Id = 1, Make = "Honda" }, + new Car { Id = 2, Make = "Opel" }, + new Car { Id = 3, Make = "Audi" }, + new Car { Id = 4, Make = "Lancia" }, + new Car { Id = 5, Make = "BMW" }, + new Car { Id = 6, Make = "Mercedes" }, + new Car { Id = 7, Make = "Tesla" }, + new Car { Id = 8, Make = "Vw" }, + new Car { Id = 9, Make = "Alpha Romeo" }, + new Car { Id = 10, Make = "Chevrolet" }, + new Car { Id = 11, Make = "Ford" }, + new Car { Id = 12, Make = "Cadillac" }, + new Car { Id = 13, Make = "Dodge" }, + new Car { Id = 14, Make = "Jeep" }, + new Car { Id = 15, Make = "Chrysler" }, + new Car { Id = 16, Make = "Lincoln" } + }; + + base.OnInitialized(); } - + public class Car { public int Id { get; set; } @@ -227,8 +226,6 @@ You can also call remote data through async operations. } ```` - - ## OnBlur The `OnBlur` event fires when the component loses focus. diff --git a/components/autocomplete/virtualization.md b/components/autocomplete/virtualization.md index 8043c3763e..90d62fe3e2 100644 --- a/components/autocomplete/virtualization.md +++ b/components/autocomplete/virtualization.md @@ -24,7 +24,6 @@ The AutoComplete @[template](/_contentTemplates/common/dropdowns-virtualization. ![Virtual Scrolling of large local data](images/autocomplete-virtual-scrolling-local.gif) - ## Basics @[template](/_contentTemplates/common/dropdowns-virtualization.md#basics-core) @@ -37,7 +36,6 @@ The AutoComplete @[template](/_contentTemplates/common/dropdowns-virtualization. @[template](/_contentTemplates/common/dropdowns-virtualization.md#limitations) - ## Local Data Example @@ -73,8 +71,6 @@ The AutoComplete @[template](/_contentTemplates/common/dropdowns-virtualization. } ```` - - ## Remote Data Example @[template](/_contentTemplates/common/dropdowns-virtualization.md#remote-data-sample-intro) @@ -85,31 +81,30 @@ Run this and see how you can display, scroll and filter over 10k records in the @using Telerik.DataSource @using Telerik.DataSource.Extensions -@SelectedValue -
-@AutoCompleteValue

+ + + OnRead="@GetRemoteData" + @bind-Value="@AutoCompleteValue" + Filterable="true" + FilterOperator="@StringFilterOperator.Contains"> + + + -@code{ - string SelectedValue { get; set; } = "Name 1234"; // pre-select an item to showcase it works like in a regular textbox - List CurentPageOfData { get; set; } - int TotalItems { get; set; } +@code { + string AutoCompleteValue { get; set; } = "Name 1234"; // pre-select an item to showcase it works like in a regular textbox - async Task GetRemoteData(AutoCompleteReadEventArgs e) + async Task GetRemoteData(AutoCompleteReadEventArgs args) { - DataEnvelope result = await MyService.GetItems(e.Request); + DataEnvelope result = await MyService.GetItems(args.Request); - CurentPageOfData = result.Data; - TotalItems = result.Total; + args.Data = result.Data; + args.Total = result.Total; } // mimics a real service in terms of API appearance, refactor as necessary for your app @@ -149,6 +144,4 @@ Run this and see how you can display, scroll and filter over 10k records in the ## See Also - * [Live Demo: AutoComplete Virtualization](https://demos.telerik.com/blazor-ui/autocomplete/virtualization) - - +* [Live Demo: AutoComplete Virtualization](https://demos.telerik.com/blazor-ui/autocomplete/virtualization) diff --git a/components/combobox/events.md b/components/combobox/events.md index ebfbbdc328..7ba295ac11 100644 --- a/components/combobox/events.md +++ b/components/combobox/events.md @@ -212,43 +212,44 @@ You can use the `OnRead` event to provide data to the component according to som You can also call remote data through `async` operations. +When using `OnRead`, make sure to set `TItem` and `TValue`. + >caption Custom Data according to the user input in the ComboBox >tip You can also [debounce the service calls and implement minimum filter length]({%slug combo-kb-debounce-onread%}). ->important You should **change** **only** the `Data` of the ComboBox in the `OnRead` handler. You should **not** change other parameters such as `Value`, because this can lead to issues with the asynchronous nature of the event - the ComboBox cannot know whether the change of those parameters comes from somewhere external, and race conditions can occur with the arrival of the new data. Moreover, such a change is likely to be unwanted and unexpected for the end user and cause bad UX. +>important You should **change** **only** the data of the ComboBox in the `OnRead` handler. You should **not** change other parameters such as `Value`, because this can lead to issues with the asynchronous nature of the event - the ComboBox cannot know whether the change of those parameters comes from somewhere external, and race conditions can occur with the arrival of the new data. Moreover, such a change is likely to be unwanted and unexpected for the end user and cause bad UX. ````CSHTML -@SelectedValue -
-@SelectedValue

+ + + Placeholder="Type anything"> -@code{ +@code { public string SelectedValue { get; set; } - List Options { get; set; } = new List(); async Task ReadItems(ComboBoxReadEventArgs args) { - if (args.Request.Filters.Count > 0) // there is user filter input, skips providing data on initialization + if (args.Request.Filters.Count > 0) // wait for user input to load data { Telerik.DataSource.FilterDescriptor filter = args.Request.Filters[0] as Telerik.DataSource.FilterDescriptor; string userInput = filter.Value.ToString(); string method = filter.Operator.ToString(); //new data collection comes down from the service - Options = await GetOptions(userInput, method); + args.Data = await GetOptions(userInput, method); } else { // when there is no user input you may still want to provide data // in this example we just hardcode a few items, you can either fetch all the data // or you can provide some subset of most common items, or something based on the business logic - Options = new List() { "one", "two", "three" }; + args.Data = new List() { "one", "two", "three" }; } } @@ -256,10 +257,10 @@ You can also call remote data through `async` operations. { await Task.Delay(500); // simulate network delay, remove it for a real app - //sample logic for getting suggestions - here they are generated, you can call a remote service + //dummy suggestions //for brevity, this example does not use the filter operator, but your actual service can List optionsData = new List(); - for (int i = 0; i < 5; i++) + for (int i = 1; i <= 5; i++) { optionsData.Add($"option {i} for input {userInput}"); } @@ -277,51 +278,50 @@ You can also call remote data through `async` operations. ````CSHTML @using Telerik.DataSource.Extensions -@SelectedValue -
- +

Selected Id: @SelectedValue

+ + @code { public int? SelectedValue { get; set; } List AllOptions { get; set; } - List CurrentOptions { get; set; } - protected async Task ReadItems(ComboBoxReadEventArgs args) { - //generate the big data source that we want to narrow down for the user - //in a real case you would probably have fetched it in OnInitializedAsync - if (AllOptions == null) - { - AllOptions = new List - { - new Car { Id = 1, Make = "Honda" }, - new Car { Id = 2, Make = "Opel" }, - new Car { Id = 3, Make = "Audi" }, - new Car { Id = 4, Make = "Lancia" }, - new Car { Id = 5, Make = "BMW" }, - new Car { Id = 6, Make = "Mercedes" }, - new Car { Id = 7, Make = "Tesla" }, - new Car { Id = 8, Make = "Vw" }, - new Car { Id = 9, Make = "Alpha Romeo" }, - new Car { Id = 10, Make = "Chevrolet" }, - new Car { Id = 11, Make = "Ford" }, - new Car { Id = 12, Make = "Cadillac" }, - new Car { Id = 13, Make = "Dodge" }, - new Car { Id = 14, Make = "Jeep" }, - new Car { Id = 15, Make = "Chrysler" }, - new Car { Id = 16, Make = "Lincoln" } - }; - } - - //use Telerik extension methods to filter the data source based on the request from the component + //using Telerik extension methods to filter the data var datasourceResult = AllOptions.ToDataSourceResult(args.Request); - CurrentOptions = (datasourceResult.Data as IEnumerable).ToList(); + args.Data = (datasourceResult.Data as IEnumerable).ToList(); + } + + protected override void OnInitialized() + { + AllOptions = new List { + new Car { Id = 1, Make = "Honda" }, + new Car { Id = 2, Make = "Opel" }, + new Car { Id = 3, Make = "Audi" }, + new Car { Id = 4, Make = "Lancia" }, + new Car { Id = 5, Make = "BMW" }, + new Car { Id = 6, Make = "Mercedes" }, + new Car { Id = 7, Make = "Tesla" }, + new Car { Id = 8, Make = "Vw" }, + new Car { Id = 9, Make = "Alpha Romeo" }, + new Car { Id = 10, Make = "Chevrolet" }, + new Car { Id = 11, Make = "Ford" }, + new Car { Id = 12, Make = "Cadillac" }, + new Car { Id = 13, Make = "Dodge" }, + new Car { Id = 14, Make = "Jeep" }, + new Car { Id = 15, Make = "Chrysler" }, + new Car { Id = 16, Make = "Lincoln" } + }; + + base.OnInitialized(); } public class Car diff --git a/components/combobox/virtualization.md b/components/combobox/virtualization.md index 7a5e529b8f..20fcf258c8 100644 --- a/components/combobox/virtualization.md +++ b/components/combobox/virtualization.md @@ -37,8 +37,6 @@ The ComboBox @[template](/_contentTemplates/common/dropdowns-virtualization.md#v ## Local Data Example - - ````CSHTML @SelectedValue
@@ -86,35 +84,33 @@ Run this and see how you can display, scroll and filter over 10k records in the @using Telerik.DataSource @using Telerik.DataSource.Extensions -@SelectedValue -
-@SelectedValue

+ + + + + @code{ int SelectedValue { get; set; } = 1234; // pre-select an item to showcase the value mapper - List CurentPageOfData { get; set; } - int TotalItems { get; set; } - async Task GetRemoteData(ComboBoxReadEventArgs e) + async Task GetRemoteData(ComboBoxReadEventArgs args) { - DataEnvelope result = await MyService.GetItems(e.Request); + DataEnvelope result = await MyService.GetItems(args.Request); // set the Data and the TotalItems to the current page of data and total number of items - CurentPageOfData = result.Data; - TotalItems = result.Total; + args.Data = result.Data; + args.Total = result.Total; } async Task GetModelFromValue(int selectedValue) @@ -123,7 +119,6 @@ Run this and see how you can display, scroll and filter over 10k records in the return await MyService.GetItemFromValue(selectedValue); } - // mimics a real service in terms of API appearance, refactor as necessary for your app public static class MyService { diff --git a/components/dropdownlist/events.md b/components/dropdownlist/events.md index 62bc0270bf..7f276a7aa6 100644 --- a/components/dropdownlist/events.md +++ b/components/dropdownlist/events.md @@ -127,16 +127,16 @@ You can also call remote data through `async` operations. >tip You can also debounce the service calls and implement minimum filter length. An example of such approach is available in [this knowledge base article for the ComboBox]({%slug combo-kb-debounce-onread%}). The same approach is applicable for the DropDownList. ->important You should **change** **only** the `Data` of the DropDownList in the `OnRead` handler. You should **not** change other parameters such as `Value`, because this can lead to issues with the asynchronous nature of the event - the DropDownList cannot know whether the change of those parameters comes from somewhere external, and race conditions can occur with the arrival of the new data. Moreover, such a change is likely to be unwanted and unexpected for the end user and cause bad UX. +>important You should **change** **only** the data of the DropDownList in the `OnRead` handler. You should **not** change other parameters such as `Value`, because this can lead to issues with the asynchronous nature of the event - the DropDownList cannot know whether the change of those parameters comes from somewhere external, and race conditions can occur with the arrival of the new data. Moreover, such a change is likely to be unwanted and unexpected for the end user and cause bad UX. ````CSHTML -@SelectedValue -
- +

@SelectedValue

+ + @code{ @@ -145,21 +145,21 @@ You can also call remote data through `async` operations. async Task ReadItems(DropDownListReadEventArgs args) { - if (args.Request.Filters.Count > 0) // there is user filter input, skips providing data on initialization + if (args.Request.Filters.Count > 0) // wait for user input to load data { Telerik.DataSource.FilterDescriptor filter = args.Request.Filters[0] as Telerik.DataSource.FilterDescriptor; string userInput = filter.Value.ToString(); string method = filter.Operator.ToString(); //new data collection comes down from the service - Options = await GetOptions(userInput, method); + args.Data = await GetOptions(userInput, method); } else { // when there is no user input you may still want to provide data // in this example we just hardcode a few items, you can either fetch all the data // or you can provide some subset of most common items, or something based on the business logic - Options = new List() { "one", "two", "three" }; + args.Data = new List() { "one", "two", "three" }; } } @@ -167,10 +167,10 @@ You can also call remote data through `async` operations. { await Task.Delay(500); // simulate network delay, remove it for a real app - //sample logic for getting suggestions - here they are generated, you can call a remote service + //dummy suggestions //for brevity, this example does not use the filter operator, but your actual service can List optionsData = new List(); - for (int i = 0; i < 5; i++) + for (int i = 1; i <= 5; i++) { optionsData.Add($"option {i} for input {userInput}"); } @@ -188,51 +188,50 @@ You can also call remote data through `async` operations. ````CSHTML @using Telerik.DataSource.Extensions -@SelectedValue -
- +

Selected Id: @SelectedValue

+ + @code { public int? SelectedValue { get; set; } List AllOptions { get; set; } - List CurrentOptions { get; set; } - protected async Task ReadItems(DropDownListReadEventArgs args) { - //generate the big data source that we want to narrow down for the user - //in a real case you would probably have fetched it in OnInitializedAsync - if (AllOptions == null) - { - AllOptions = new List - { - new Car { Id = 1, Make = "Honda" }, - new Car { Id = 2, Make = "Opel" }, - new Car { Id = 3, Make = "Audi" }, - new Car { Id = 4, Make = "Lancia" }, - new Car { Id = 5, Make = "BMW" }, - new Car { Id = 6, Make = "Mercedes" }, - new Car { Id = 7, Make = "Tesla" }, - new Car { Id = 8, Make = "Vw" }, - new Car { Id = 9, Make = "Alpha Romeo" }, - new Car { Id = 10, Make = "Chevrolet" }, - new Car { Id = 11, Make = "Ford" }, - new Car { Id = 12, Make = "Cadillac" }, - new Car { Id = 13, Make = "Dodge" }, - new Car { Id = 14, Make = "Jeep" }, - new Car { Id = 15, Make = "Chrysler" }, - new Car { Id = 16, Make = "Lincoln" } - }; - } - - //use Telerik extension methods to filter the data source based on the request from the component + //using Telerik extension methods to filter the data var datasourceResult = AllOptions.ToDataSourceResult(args.Request); - CurrentOptions = (datasourceResult.Data as IEnumerable).ToList(); + args.Data = (datasourceResult.Data as IEnumerable).ToList(); + } + + protected override void OnInitialized() + { + AllOptions = new List { + new Car { Id = 1, Make = "Honda" }, + new Car { Id = 2, Make = "Opel" }, + new Car { Id = 3, Make = "Audi" }, + new Car { Id = 4, Make = "Lancia" }, + new Car { Id = 5, Make = "BMW" }, + new Car { Id = 6, Make = "Mercedes" }, + new Car { Id = 7, Make = "Tesla" }, + new Car { Id = 8, Make = "Vw" }, + new Car { Id = 9, Make = "Alpha Romeo" }, + new Car { Id = 10, Make = "Chevrolet" }, + new Car { Id = 11, Make = "Ford" }, + new Car { Id = 12, Make = "Cadillac" }, + new Car { Id = 13, Make = "Dodge" }, + new Car { Id = 14, Make = "Jeep" }, + new Car { Id = 15, Make = "Chrysler" }, + new Car { Id = 16, Make = "Lincoln" } + }; + + base.OnInitialized(); } public class Car diff --git a/components/dropdownlist/virtualization.md b/components/dropdownlist/virtualization.md index 5c905f9cce..3f4c396094 100644 --- a/components/dropdownlist/virtualization.md +++ b/components/dropdownlist/virtualization.md @@ -91,34 +91,33 @@ Run this and see how you can display, scroll and filter over 10k records in the @using Telerik.DataSource @using Telerik.DataSource.Extensions -@SelectedValue +

Selected Id: @SelectedValue


- + Filterable="true" + FilterOperator="@StringFilterOperator.Contains"> + + + @code{ int SelectedValue { get; set; } = 1234; // pre-select an item to showcase the value mapper - List CurentPageOfData { get; set; } - int TotalItems { get; set; } - async Task GetRemoteData(DropDownListReadEventArgs e) + async Task GetRemoteData(DropDownListReadEventArgs args) { - DataEnvelope result = await MyService.GetItems(e.Request); + DataEnvelope result = await MyService.GetItems(args.Request); - CurentPageOfData = result.Data; - TotalItems = result.Total; + args.Data = result.Data; + args.Total = result.Total; } async Task GetModelFromValue(int selectedValue) @@ -127,7 +126,6 @@ Run this and see how you can display, scroll and filter over 10k records in the return await MyService.GetItemFromValue(selectedValue); } - // mimics a real service in terms of API appearance, refactor as necessary for your app public static class MyService { diff --git a/components/grid/filter/checkboxlist.md b/components/grid/filter/checkboxlist.md index 901e07a1e2..63045a31ec 100644 --- a/components/grid/filter/checkboxlist.md +++ b/components/grid/filter/checkboxlist.md @@ -94,9 +94,12 @@ Now try to filter by the On Vacation column - it will use only the current grid depending on how you filter the data so you may never be able to get back all values. - + @@ -122,9 +125,6 @@ depending on how you filter the data so you may never be able to get back all va @code { List AllGridData { get; set; } - IEnumerable CurrentGridData { get; set; } - int Total { get; set; } - #region custom-filter-data List TeamsList { get; set; } List NameOptions { get; set; } @@ -171,12 +171,12 @@ depending on how you filter the data so you may never be able to get back all va } #endregion custom-filter-data - async Task OnReadHandler(GridReadEventArgs e) + async Task OnReadHandler(GridReadEventArgs args) { //typical data retrieval for the grid - var filteredData = await AllGridData.ToDataSourceResultAsync(e.Request); - CurrentGridData = filteredData.Data as IEnumerable; - Total = filteredData.Total; + var filteredData = await AllGridData.ToDataSourceResultAsync(args.Request); + args.Data = filteredData.Data as IEnumerable; + args.Total = filteredData.Total; } protected override async Task OnInitializedAsync() @@ -185,7 +185,7 @@ depending on how you filter the data so you may never be able to get back all va // the actual grid data is retrieve in its OnRead handler AllGridData = new List(); var rand = new Random(); - for (int i = 0; i < 15; i++) + for (int i = 1; i <= 15; i++) { AllGridData.Add(new Employee() { @@ -227,7 +227,6 @@ depending on how you filter the data so you may never be able to get back all va ```` - ## See Also * [Grid Filtering Overview]({%slug components/grid/filtering%}) diff --git a/components/grid/grouping/load-on-demand.md b/components/grid/grouping/load-on-demand.md index 2c684a364a..76ae68e51c 100644 --- a/components/grid/grouping/load-on-demand.md +++ b/components/grid/grouping/load-on-demand.md @@ -135,12 +135,11 @@ This example shows how you can combine the virtual row scrolling feature with lo Scroll through the groups or expand them to load their data on demand - @@ -153,7 +152,6 @@ Scroll through the groups or expand them to load their data on demand @code { List GridData { get; set; } - int Total { get; set; } = 0; protected async Task ReadItems(GridReadEventArgs args) { @@ -162,16 +160,14 @@ Scroll through the groups or expand them to load their data on demand if (args.Request.Groups.Count > 0) { - GridData = result.GroupedData.Cast().ToList(); + args.Data = result.GroupedData.Cast().ToList(); } else { - GridData = result.CurrentPageData.Cast().ToList(); + args.Data = result.CurrentPageData.Cast().ToList(); } - Total = result.TotalItemCount; - - StateHasChanged(); + args.Total = result.TotalItemCount; } void OnStateInitHandler(GridStateEventArgs args) @@ -222,7 +218,7 @@ Scroll through the groups or expand them to load their data on demand { SourceData = new List(); var rand = new Random(); - for (int i = 0; i < 2500; i++) + for (int i = 1; i <= 2500; i++) { SourceData.Add(new Employee() { diff --git a/components/grid/manual-operations.md b/components/grid/manual-operations.md index 86ea412ff8..9fc445cae7 100644 --- a/components/grid/manual-operations.md +++ b/components/grid/manual-operations.md @@ -20,11 +20,13 @@ By default, the grid will receive the entire collection of data, and it will per The parameter of type `DataSourceRequest` exposes information about the desired paging, filtering and sorting so you can, for example, call your remote endpoint with appropriate parameters so its performance is optimized and it fetches only the relevant data. -When the `OnRead` event is used, the internal operations are disabled and you must perform them all in the `OnRead` event. You must also set the `TotalCount` property of the grid to the total number of items in the data source. +When the `OnRead` event is used, the internal operations are disabled and you must perform them all in the `OnRead` event. You must set: -If you are using an `ObservableCollection`, make sure to create a `new` one, because using `.Add()`, `.Remove()` or `.Clear()` on it will cause an infinite loop - the [grid monitors the ObservableCollection events]({%slug common-features-observable-data%}) and updates its data, which will fire `OnRead`. +* the data to `args.Data` +* the total number of items (if there is no paging) to `args.Total` +* the Grid itself should set the `TItem` attribute to the model type -The event arguments to `OnRead` will receive the `Page` that comes from the application. For example, if you load the grid [state]({%slug grid-state%}), or you set the parameter value yourself, that value will be passed to the event handler. If it is out of the range of the available data, it will be up to the application to handle that discrepancy. +The event arguments of `OnRead` will receive the `Page` that comes from the application. For example, if you load the grid [state]({%slug grid-state%}), or you set the parameter value yourself, that value will be passed to the event handler. If it is out of the range of the available data, it will be up to the application to handle that discrepancy. ## Examples @@ -32,24 +34,14 @@ Below you can find a few examples of using the `OnRead` event to perform custom The comments in the code provide explanations on what is done and why. - Examples: - * [Custom paging with a remote service](#custom-paging-with-a-remote-service) - * [Telerik .ToDataSourceResult(request)](#telerik-todatasourceresultrequest) - * [Grouping with OnRead](#grouping-with-onread) - * [Get Information From the DataSourceRequest](#get-information-from-the-datasourcerequest) - -* [Cache Data Request](#cache-data-request) - * [Use OData Service](https://github.com/telerik/blazor-ui/tree/master/grid/odata) - * [Serialize the DataSoureRequest to the server](https://github.com/telerik/blazor-ui/tree/master/grid/datasourcerequest-on-server) - * [Debounce Data Source Operations and Requests]({%slug grid-kb-debounce-operations%}) ## Custom paging with a remote service @@ -59,9 +51,9 @@ Examples: ````CSHTML Custom paging. There is a deliberate delay in the data source operations in this example to mimic real life delays and to showcase the async nature of the calls. - + @@ -69,9 +61,6 @@ Custom paging. There is a deliberate delay in the data source operations in this @code { - public List GridData { get; set; } - public int Total { get; set; } = 0; - protected async Task ReadItems(GridReadEventArgs args) { Console.WriteLine("data requested: " + args.Request); @@ -81,10 +70,8 @@ Custom paging. There is a deliberate delay in the data source operations in this DataEnvelope DataResult = await FetchPagedData(args.Request.Page, args.Request.PageSize); //use the current page of data and the total amount of items in the data source that are returned from the service - GridData = DataResult.CurrentPageData; - Total = DataResult.TotalItemCount; - - StateHasChanged(); + args.Data = DataResult.CurrentPageData; + args.Total = DataResult.TotalItemCount; } //This sample implements only reading pages of the data. To add the rest of the CRUD operations see @@ -102,7 +89,7 @@ Custom paging. There is a deliberate delay in the data source operations in this //generate dummy data for the example int totalCount = 100; - for (int i = 0; i < totalCount; i++) + for (int i = 1; i <= totalCount; i++) { fullList.Add(new Employee() { @@ -120,7 +107,7 @@ Custom paging. There is a deliberate delay in the data source operations in this result.CurrentPageData = fullList.Skip(pageSize * (pageNumber - 1)).Take(pageSize).ToList(); result.TotalItemCount = fullList.Count; - await Task.Delay(2000); //simulate network delay from a real async call + await Task.Delay(1000); //simulate network delay from a real async call return result; } @@ -155,82 +142,66 @@ Using Telerik DataSource extension methods to manipulate all the data into paged @using Telerik.DataSource.Extensions - - - - - - - Update - Edit - Delete - Cancel - - - - Add Employee - + + + + + + @code { - public List SourceData { get; set; } - public List GridData { get; set; } - public int Total { get; set; } = 0; - - protected override void OnInitialized() - { - SourceData = GenerateData(); - } + public List SourceData { get; set; } - protected async Task ReadItems(GridReadEventArgs args) - { - Console.WriteLine("data requested: " + args.Request); + protected async Task ReadItems(GridReadEventArgs args) + { + Console.WriteLine("data requested: " + args.Request); - //you need to update the total and data variables - //the ToDataSourceResult() extension method can be used to perform the operations over the full data collection - //in a real case, you can call data access layer and remote services here instead, to fetch only the necessary data + //update the Data and Total properties + //the ToDataSourceResult() extension method can be used to perform the operations over the full data collection + //in a real case, you can call data access layer and remote services here instead, to fetch only the necessary data - await Task.Delay(2000); //simulate network delay from a real async call + await Task.Delay(1000); //simulate network delay from a real async call - var datasourceResult = SourceData.ToDataSourceResult(args.Request); + var datasourceResult = SourceData.ToDataSourceResult(args.Request); - GridData = (datasourceResult.Data as IEnumerable).ToList(); - Total = datasourceResult.Total; + args.Data = datasourceResult.Data; + args.Total = datasourceResult.Total; + } - StateHasChanged(); - } - - //This sample implements only reading of the data. To add the rest of the CRUD operations see - //https://docs.telerik.com/blazor-ui/components/grid/editing/overview + protected override void OnInitialized() + { + SourceData = GenerateData(); + } - 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, - HireDate = DateTime.Now.Date.AddDays(rand.Next(-20, 20)) - }); - } + 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, + HireDate = DateTime.Now.Date.AddDays(rand.Next(-20, 20)) + }); + } - return result; - } + return result; + } - public class Employee - { - public int ID { get; set; } - public string Name { get; set; } - public DateTime HireDate { get; set; } - } + public class Employee + { + public int ID { get; set; } + public string Name { get; set; } + public DateTime HireDate { get; set; } + } } ```` - ## Grouping with OnRead When the grid needs to be grouped, the shape of the data changes - it is no longer a flat list of models, but a nested list of collections that describe each group and have the group data. At the same time, the grid is designed for a data source that is a collection of models, and the source of this collection is up to the application. @@ -239,7 +210,7 @@ When you let the grid handle the operations internally, it hides that complexity Thus, to use the `OnRead` event with grouping, you must: -1. Use an `IEnumerable` for the grid `Data`. +1. Use an `IEnumerable` for the Grid data. * This is required so the special data structure for grouped data can be used, otherwise you will get compile-time errors. 1. Set the `FieldType` of the columns to match the type of the field you will be showing. * If you also use [filtering]({%slug components/grid/filtering%}), do not use nullable types. For example, if the model field is `int?`, set `FieldType="@(typeof(int))"`. @@ -255,37 +226,22 @@ This sample shows how to set up the grid to use grouping with manual data source @using Telerik.DataSource.Extensions - + - - Update - Edit - Delete - Cancel - - - Add Employee - @code { public List SourceData { get; set; } - // the grid Data needs to be IEnumerable to work with grouping in OnRead - public List GridData { get; set; } - - public int Total { get; set; } = 0; - - protected override void OnInitialized() - { - SourceData = GenerateData(); - } - // Handling grouping happens here - by casting the DataSourceResult.Data to objects protected async Task ReadItems(GridReadEventArgs args) { @@ -294,24 +250,24 @@ This sample shows how to set up the grid to use grouping with manual data source // https://github.com/telerik/blazor-ui/tree/master/grid/datasourcerequest-on-server var datasourceResult = SourceData.ToDataSourceResult(args.Request); - // to work with grouping, the grid Data needs to be an IEnumerable + // to work with grouping, the grid data needs to be an IEnumerable // because grouped data has a different shape than non-grouped data // and this is, generally, hidden from you by the grid, but now it cannot be - GridData = datasourceResult.Data.Cast().ToList(); - - Total = datasourceResult.Total; + args.Data = datasourceResult.Data.Cast().ToList(); - StateHasChanged(); + args.Total = datasourceResult.Total; } - //This sample implements only reading of the data. To add the rest of the CRUD operations see - //https://docs.telerik.com/blazor-ui/components/grid/editing/overview + protected override void OnInitialized() + { + SourceData = GenerateData(); + } private List GenerateData() { var result = new List(); var rand = new Random(); - for (int i = 0; i < 15; i++) + for (int i = 1; i <= 15; i++) { result.Add(new Employee() { @@ -337,7 +293,7 @@ This sample shows how to set up the grid to use grouping with manual data source >important This approach cannot work directly with a [DataTable](https://demos.telerik.com/blazor-ui/grid/data-table) or [OData](https://github.com/telerik/blazor-ui/tree/master/grid/odata) as underlying data sources, because these two external data sources do not return objects that can be converted to the data structure needed for grouping by the grid. We recommend that you consider creating actual models to use the Grid in a native Blazor way. If that's not possible, you can consider [ExpandoObject collections](https://github.com/telerik/blazor-ui/tree/master/grid/binding-to-expando-object) which are a bit more flexible and can be parsed to the needed grouping structure. ->note Since the grid does not have the type of the data models (it is bound to `IEnumerable`), it uses the first item in the available `Data` to infer the type. If there is no data, this type will be unavailable and the grid will be unable to create an item to insert. The filters can get the proper operators list from the `FieldType`, but an entire model cannot be constructed by the grid. +>note Since the grid does not have the type of the data models (it is bound to `IEnumerable`), it uses the first item in the available data to infer the type. If there is no data, this type will be unavailable and the grid will be unable to create an item to insert. The filters can get the proper operators list from the `FieldType`, but an entire model cannot be constructed by the grid. > > Thus, clicking the built-in Add command button on its toolbar when there is no data will produce a `null` item and if you have editor templates, there may be null reference errors (the `context` will be `null`). To avoid that, you can [initiate insertion of items through the grid state]({%slug grid-state%}#initiate-editing-or-inserting-of-an-item) in order to ensure a model reference exists. @@ -350,12 +306,12 @@ With a few simple loops, you can extract information from the DataSourceRequest @using Telerik.DataSource @using Telerik.DataSource.Extensions -@ConsoleSim - -
+

@ConsoleSim

- @@ -370,10 +326,6 @@ With a few simple loops, you can extract information from the DataSourceRequest @functions { MarkupString ConsoleSim { get; set; } // to showcase what you get - // implementation of OnRead - List CurrPageData { get; set; } - int Total { get; set; } - async Task OnReadHandler(GridReadEventArgs args) { string output = string.Empty; @@ -412,10 +364,9 @@ With a few simple loops, you can extract information from the DataSourceRequest // actual data source operation, implement as required in your case (e.g., call a service with parameters you built) var result = PristineData.ToDataSourceResult(args.Request); - CurrPageData = (result.Data as IEnumerable).ToList(); - Total = result.Total; - StateHasChanged(); + args.Data = result.Data; + args.Total = result.Total; } public IEnumerable PristineData = Enumerable.Range(1, 300).Select(x => new SampleData @@ -437,118 +388,6 @@ With a few simple loops, you can extract information from the DataSourceRequest ```` -## Cache Data Request - -If you need to replay the last request for some reason (your data has updated, or you need to await some business logic that determines what data to request), store the `DataSourceRequest` object in a field in your view model, then run the method that will read the data when necessary - a button click, or when some async operation completes. - - -````CSHTML -@* This example awaits some business data in OnInitializedAsync and fetches grid data according to it -You can call the SetGridData() method from button clicks or other events according to your needs *@ - -@using Telerik.DataSource.Extensions -@using Telerik.DataSource - - - - - - - - - -@code { - string something { get; set; } // an object on which the grid data depends - - protected override async Task OnInitializedAsync() - { - // the business logic that determines the global object - such as a user, their role, access rights, settings, etc. - await GetSomething(); - - // Refresh the Grid Data after the business data has arrived - SetGridData(); - - await base.OnInitializedAsync(); - } - - async Task GetSomething() - { - // in a real case - apply the desired business logic here - await Task.Delay(3000); - something = DateTime.Now.Millisecond.ToString(); - } - - public List SourceData { get; set; } - public List GridData { get; set; } - public int Total { get; set; } = 0; - - // cache the last data request so you can "replay" it and update the grid data anytime - public DataSourceRequest CurrentRequest { get; set; } - - - protected async Task ReadItems(GridReadEventArgs args) - { - // cache the last data request so you can update teh grid data with it at any time - CurrentRequest = args.Request; - - if (!string.IsNullOrEmpty(something)) // business logic that dictates when/what to request for the grid - { - SetGridData(); - } - } - - // Update the grid data with the cached request at any time - private void SetGridData() - { - if (CurrentRequest == null) - { - return; - } - - // implement actual reading of the data, for example, use the "something" business object above - // this is merely some generated data to get the grid running - var datasourceResult = SourceData.ToDataSourceResult(CurrentRequest); - - GridData = (datasourceResult.Data as IEnumerable).ToList(); - Total = datasourceResult.Total; - } - - //This sample implements only reading of the data. To add the rest of the CRUD operations see - //https://docs.telerik.com/blazor-ui/components/grid/editing/overview - - protected override void OnInitialized() - { - SourceData = GenerateData(); - } - - 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, - HireDate = DateTime.Now.Date.AddDays(rand.Next(-20, 20)) - }); - } - - return result; - } - - public class Employee - { - public int ID { get; set; } - public string Name { get; set; } - public DateTime HireDate { get; set; } - } -} -```` - - ## See Also * [CRUD Operations Overview]({%slug components/grid/editing/overview%}) diff --git a/components/grid/refresh-data.md b/components/grid/refresh-data.md index f67678b00b..82768d3f62 100644 --- a/components/grid/refresh-data.md +++ b/components/grid/refresh-data.md @@ -185,13 +185,16 @@ For cases when directly modifying the data collection with the new information a ````CSHTML @using Telerik.DataSource.Extensions -Make grid request fresh data and call OnRead -
Monitor the GeneratedAtMilliseconds column values when you click the button +Call OnRead to refresh Grid +

Monitor the GeneratedAtMilliseconds column values when you click the button

- + FilterMode="@GridFilterMode.FilterRow" + Sortable="true" + Pageable="true"> @code { @@ -204,8 +207,6 @@ For cases when directly modifying the data collection with the new information a //basic data generation follows public List SourceData { get; set; } - public List GridData { get; set; } - public int Total { get; set; } = 0; protected override void OnInitialized() { @@ -219,15 +220,13 @@ For cases when directly modifying the data collection with the new information a // this is the standard data retrieval. Replace with your actual service // see more at https://docs.telerik.com/blazor-ui/components/grid/manual-operations - // here we will just generate the data again to clearly showcase the grid got new data + // here we will regenerate the data to clearly show the Grid got new data SourceData = GenerateData(); var datasourceResult = SourceData.ToDataSourceResult(args.Request); - GridData = (datasourceResult.Data as IEnumerable).ToList(); - Total = datasourceResult.Total; - - StateHasChanged(); + args.Data = (datasourceResult.Data as IEnumerable).ToList(); + args.Total = datasourceResult.Total; } //This sample implements only reading of the data. To add the rest of the CRUD operations see @@ -237,7 +236,7 @@ For cases when directly modifying the data collection with the new information a { var result = new List(); var rand = new Random(); - for (int i = 0; i < 100; i++) + for (int i = 1; i <= 100; i++) { result.Add(new Employee() { diff --git a/components/grid/templates/column-footer.md b/components/grid/templates/column-footer.md index 9636c0b84c..adb9efe82a 100644 --- a/components/grid/templates/column-footer.md +++ b/components/grid/templates/column-footer.md @@ -85,135 +85,10 @@ You can use [aggregates]({%slug grid-aggregates%}) for the current field directl ## 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. +Footer templates usually display aggregates. Here are some 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`. diff --git a/components/grid/virtual-scrolling.md b/components/grid/virtual-scrolling.md index 918928318a..9b6e72d322 100644 --- a/components/grid/virtual-scrolling.md +++ b/components/grid/virtual-scrolling.md @@ -127,11 +127,11 @@ List of the known limitations of the virtual scrolling feature: ## See Also diff --git a/components/listview/manual-operations.md b/components/listview/manual-operations.md index 003cb8673b..b334178b93 100644 --- a/components/listview/manual-operations.md +++ b/components/listview/manual-operations.md @@ -21,35 +21,29 @@ In this article you will find examples how to: This is, effectively, loading data on demand only when the user goes to a certain page, as opposed to the default case where you fetch all the data items initially. To implement your own paging in the listview, you need to: -* Handle the `OnRead` event and set the current page of data to the listview's `Data` parameter -* Set the `TotalCount` parameter of the listview to the total number of items from the data source, so it can show the proper pager. +* Handle the `OnRead` event and... +* Set the current page of data to the `args.Data` property of the event argument. +* Set the `args.Total` property to the total number of items on all pages, so that the pager displays correct information. >caption Custom Paging in the ListView ````CSHTML @* This example simulates fetching the page data from a service *@ - + @code{ - int TotalItems { get; set; } - List ListViewCurrPageData { get; set; } int PageSize { get; set; } = 15; - async Task OnReadHandler(ListViewReadEventArgs e) + async Task OnReadHandler(ListViewReadEventArgs args) { - ListViewCurrPageData = await GetListViewPageData(e.Request.Page, e.Request.PageSize); - TotalItems = await GetTotalItemsCount(); - StateHasChanged(); - } - - protected override async Task OnInitializedAsync() - { - ListViewCurrPageData = await GetListViewPageData(1, PageSize); - TotalItems = await GetTotalItemsCount(); + args.Data = await GetListViewPageData(args.Request.Page, args.Request.PageSize); + args.Total = await GetTotalItemsCount(); } async Task> GetListViewPageData(int pageIndex, int pageSize) @@ -81,7 +75,6 @@ To implement your own paging in the listview, you need to: } ```` - ## Filter and Sort While the listview does not have built-in UI for filtering and sorting like a grid does, you can add your own components to invoke such actions and simply update the data source of the component. @@ -165,5 +158,3 @@ The example below shows a relatively simple way to filter and sort over all data * [Live Demo: ListView Filtering](https://demos.telerik.com/blazor-ui/listview/filtering) * [Live Demo: ListView Sorting](https://demos.telerik.com/blazor-ui/listview/sorting) - - diff --git a/components/multiselect/events.md b/components/multiselect/events.md index eb5c13f969..e75ec97965 100644 --- a/components/multiselect/events.md +++ b/components/multiselect/events.md @@ -135,10 +135,13 @@ You can also call remote data through async operations. ````CSHTML @* this sample simulates fetching options based on the user input *@ - -
-selected values + + +

Selected values:

    @foreach (var item in TheValues) { @@ -148,18 +151,17 @@ selected values @code{ List TheValues { get; set; } = new List(); - List Options { get; set; } = new List(); async Task ReadItems(MultiSelectReadEventArgs args) { - if (args.Request.Filters.Count > 0) // there is user filter input, skips providing data on initialization + if (args.Request.Filters.Count > 0) // wait for user input to load data { Telerik.DataSource.FilterDescriptor filter = args.Request.Filters[0] as Telerik.DataSource.FilterDescriptor; string userInput = filter.Value.ToString(); string method = filter.Operator.ToString(); - //new data collection comes down from the service - Options = await GetSuggestionsData(userInput, method); + //new data collection comes from the service + args.Data = await GetSuggestionsData(userInput, method); } } @@ -170,7 +172,7 @@ selected values //sample logic for getting options - here they are generated, you can call a remote service //for brevity, this example does not use the filter operator, but your actual service can List optionssData = new List(); - for (int i = 0; i < 5; i++) + for (int i = 1; i <= 5; i++) { optionssData.Add($"suggestion {i} for input {userInput}"); } @@ -185,10 +187,15 @@ selected values ````CSHTML @using Telerik.DataSource.Extensions - -
    -selected values + + +

    Selected values

      @foreach (var item in TheValues) { @@ -196,7 +203,7 @@ selected values }
    -@code{ +@code { List TheValues { get; set; } = new List(); List AllOptions { get; set; } @@ -204,34 +211,34 @@ selected values async Task ReadItems(MultiSelectReadEventArgs args) { - //generate the big data source that we want to narrow down for the user - //in a real case you would probably have fetched it in OnInitializedAsync - if (AllOptions == null) - { - AllOptions = new List - { - new Car { Id = 1, Make = "Honda" }, - new Car { Id = 2, Make = "Opel" }, - new Car { Id = 3, Make = "Audi" }, - new Car { Id = 4, Make = "Lancia" }, - new Car { Id = 5, Make = "BMW" }, - new Car { Id = 6, Make = "Mercedes" }, - new Car { Id = 7, Make = "Tesla" }, - new Car { Id = 8, Make = "Vw" }, - new Car { Id = 9, Make = "Alpha Romeo" }, - new Car { Id = 10, Make = "Chevrolet" }, - new Car { Id = 11, Make = "Ford" }, - new Car { Id = 12, Make = "Cadillac" }, - new Car { Id = 13, Make = "Dodge" }, - new Car { Id = 14, Make = "Jeep" }, - new Car { Id = 15, Make = "Chrysler" }, - new Car { Id = 16, Make = "Lincoln" } - }; - } - - //use Telerik extension methods to filter the data source based on the request from the component + //using Telerik extension methods to filter the data var datasourceResult = AllOptions.ToDataSourceResult(args.Request); - CurrentOptions = (datasourceResult.Data as IEnumerable).ToList(); + args.Data = datasourceResult.Data; + } + + protected override void OnInitialized() + { + AllOptions = new List() + { + new Car { Id = 1, Make = "Honda" }, + new Car { Id = 2, Make = "Opel" }, + new Car { Id = 3, Make = "Audi" }, + new Car { Id = 4, Make = "Lancia" }, + new Car { Id = 5, Make = "BMW" }, + new Car { Id = 6, Make = "Mercedes" }, + new Car { Id = 7, Make = "Tesla" }, + new Car { Id = 8, Make = "Vw" }, + new Car { Id = 9, Make = "Alpha Romeo" }, + new Car { Id = 10, Make = "Chevrolet" }, + new Car { Id = 11, Make = "Ford" }, + new Car { Id = 12, Make = "Cadillac" }, + new Car { Id = 13, Make = "Dodge" }, + new Car { Id = 14, Make = "Jeep" }, + new Car { Id = 15, Make = "Chrysler" }, + new Car { Id = 16, Make = "Lincoln" } + }; + + base.OnInitialized(); } public class Car @@ -242,8 +249,6 @@ selected values } ```` - - ## OnBlur The `OnBlur` event fires when the component loses focus. diff --git a/components/multiselect/virtualization.md b/components/multiselect/virtualization.md index b7d67bb6d0..8d1ec8fb6b 100644 --- a/components/multiselect/virtualization.md +++ b/components/multiselect/virtualization.md @@ -38,10 +38,8 @@ The MultiSelect @[template](/_contentTemplates/common/dropdowns-virtualization.m @[template](/_contentTemplates/common/dropdowns-virtualization.md#limitations) - ## Local Data Example - ````CSHTML Number of selected items: @SelectedValues?.Count
    @@ -79,7 +77,6 @@ Number of selected items: @SelectedValues?.Count ```` - ## Remote Data Example @[template](/_contentTemplates/common/dropdowns-virtualization.md#remote-data-sample-intro) @@ -94,12 +91,10 @@ Run this and see how you can display, scroll and filter over 10k records in the Number of selected items: @SelectedValues?.Count
    - + + + @code{ List SelectedValues { get; set; } = new List { 4, 1234 }; // pre-select an item to showcase the value mapper - List CurentPageOfData { get; set; } - int TotalItems { get; set; } - async Task GetRemoteData(MultiSelectReadEventArgs e) + async Task GetRemoteData(MultiSelectReadEventArgs args) { - DataEnvelope result = await MyService.GetItems(e.Request); + DataEnvelope result = await MyService.GetItems(args.Request); - CurentPageOfData = result.Data; - TotalItems = result.Total; + args.Data = result.Data; + args.Total = result.Total; } async Task> GetModelFromValue(List selectedValues) @@ -129,7 +125,6 @@ Number of selected items: @SelectedValues?.Count return await MyService.GetItemsFromValue(selectedValues); } - // mimics a real service in terms of API appearance, refactor as necessary for your app public static class MyService { diff --git a/components/scheduler/manual-operations.md b/components/scheduler/manual-operations.md index db9b6fcc7c..b814cb1967 100644 --- a/components/scheduler/manual-operations.md +++ b/components/scheduler/manual-operations.md @@ -14,7 +14,7 @@ By default, the scheduler will receive the entire collection of appointments, an The parameter of type `DataSourceRequest` exposes information about the desired paging, filtering and sorting so you can, for example, call your remote endpoint with appropriate parameters so its performance is optimized and it fetches only the relevant data. -When the `OnRead` event is used, the internal operations are disabled and you must perform them all in the `OnRead` event. You must also set the `TotalCount` property of the grid to the total number of items in the data source. +When the `OnRead` event is used, the internal operations are disabled and you must perform them all in the `OnRead` event. You must set the `args.Data` and `args.Total` properties of the event argument object. Do not set the component `Data` attribute when using `OnRead`. ## Examples diff --git a/components/scheduler/templates/dateheader.md b/components/scheduler/templates/dateheader.md index 06821721b9..6be88abdee 100644 --- a/components/scheduler/templates/dateheader.md +++ b/components/scheduler/templates/dateheader.md @@ -5,7 +5,7 @@ description: Use custom date headers rendering through a template in the schedul slug: scheduler-templates-dateheader tags: telerik,blazor,scheduler,templates,date,header,dateheader published: True -position: 5 +position: 10 --- # DateHeader Templates diff --git a/knowledge-base/combo-debounce-onread.md b/knowledge-base/combo-debounce-onread.md index 727aef5075..1a08836aac 100644 --- a/knowledge-base/combo-debounce-onread.md +++ b/knowledge-base/combo-debounce-onread.md @@ -41,29 +41,28 @@ For min filter length, just add a check in the handler for the desired string le @implements IDisposable @using System.Threading -@SelectedValue -
    -@SelectedValue

    + + + Placeholder="Type anything"> -@code{ +@code { public string SelectedValue { get; set; } - List Options { get; set; } = new List(); CancellationTokenSource tokenSource = new CancellationTokenSource(); // for debouncing the service calls - async Task RequestData(string userInput, string method) + async Task RequestData(string userInput, string method, ComboBoxReadEventArgs args) { // this method calls the actual service (in this case - a local method) - Options = await GetOptions(userInput, method); + args.Data = await GetOptions(userInput, method); } async Task ReadItems(ComboBoxReadEventArgs args) { - if (args.Request.Filters.Count > 0) // there is user filter input, skips providing data on initialization + if (args.Request.Filters.Count > 0) // wait for user input { Telerik.DataSource.FilterDescriptor filter = args.Request.Filters[0] as Telerik.DataSource.FilterDescriptor; string userInput = filter.Value.ToString(); @@ -81,21 +80,18 @@ For min filter length, just add a check in the handler for the desired string le await Task.Delay(300, token); // 300ms timeout for the debouncing //new service request after debouncing - await RequestData(userInput, method); + await RequestData(userInput, method, args); } } else { - if (Options?.Count < 1) - { - // when there is no user input you may still want to provide data - // in this example we just hardcode a few items, you can either fetch all the data - // or you can provide some subset of most common items, or something based on the business logic - Options = new List() { "one", "two", "three" }; - } + // when there is no user input you may still want to provide data + // in this example we just hardcode a few items, you can either fetch all the data + // or you can provide some subset of most common items, or something based on the business logic + args.Data = new List() { "one", "two", "three" }; } } - + public void Dispose() { try @@ -120,5 +116,5 @@ For min filter length, just add a check in the handler for the desired string le return optionsData; } +} ```` - diff --git a/knowledge-base/grid-aggregates-and-datatable.md b/knowledge-base/grid-aggregates-and-datatable.md index c56d5d519e..584f47d572 100644 --- a/knowledge-base/grid-aggregates-and-datatable.md +++ b/knowledge-base/grid-aggregates-and-datatable.md @@ -51,20 +51,16 @@ Attempting to use built-in aggregates with the templates that need to extract th Note that using OnRead makes the grid calculate aggregates on the current page of data only This sample contains a solution for calculating them on the server over all data - + - Total employees: @totalEmployees -
    - Total employees (from current data): @context.Count + Total employees: @context.Count
    - Top salary: @highestSalary -
    - Top salary (from current data): @context.Max + Top salary: @context.Max
    @@ -78,12 +74,6 @@ This sample contains a solution for calculating them on the server over all data @code { public DataTable SourceData { get; set; } - public List> GridData { get; set; } = new List>(); - public int Total { get; set; } = 0; - - // values for the data table aggregations - int totalEmployees { get; set; } - decimal highestSalary { get; set; } protected override void OnInitialized() { @@ -94,7 +84,7 @@ This sample contains a solution for calculating them on the server over all data { DataSourceResult datasourceResult = SourceData.ToDataSourceResult(args.Request); - GridData = (datasourceResult.Data as IEnumerable>) + args.Data = (datasourceResult.Data as IEnumerable>) .Select(x => x.ToDictionary( x => x.Key, x => @@ -110,22 +100,10 @@ This sample contains a solution for calculating them on the server over all data })) .ToList(); - Total = datasourceResult.Total; - - - // 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)datasourceResult.AggregateResults.FirstOrDefault( - r => r.AggregateMethodName == "Count" && r.Member == nameof(Employee.ID))?.Value; - - highestSalary = (decimal)datasourceResult.AggregateResults.FirstOrDefault( - r => r.AggregateMethodName == "Max" && r.Member == nameof(Employee.Salary))?.Value; - - // for the grid data update itself - StateHasChanged(); + args.Total = datasourceResult.Total; + args.AggregateResults = datasourceResult.AggregateResults; } - public DataTable GenerateData() { var rand = new Random(); @@ -140,7 +118,7 @@ This sample contains a solution for calculating them on the server over all data table.Columns["Name"].DefaultValue = default(string); table.Columns["Salary"].DefaultValue = default(decimal); - for (int i = 1; i < 50; i++) + for (int i = 1; i <= 50; i++) { table.Rows.Add(i, $"Name {i}", rand.Next(1000, 5000)); } @@ -157,9 +135,6 @@ This sample contains a solution for calculating them on the server over all data } ```` - - - ## Cause\Possible Cause(s) When using a `DataTable` as the grid data source, aggregates are not supported, they require using a model so they can extract the type of the field - the grid itself is strongly typed. @@ -174,7 +149,7 @@ For such scecnarios you can pass the desired aggregation functions through the ` Since the grid data source is a DataTable, built-in aggregate calculations cannot work, because they need a model. You can, however, add the desired aggregate functions and let .ToDataSourceResult() calculate them so you can use them through view-model fields - + @@ -193,8 +168,6 @@ You can, however, add the desired aggregate functions and let .ToDataSourceResul @code { public DataTable SourceData { get; set; } - public List> GridData { get; set; } = new List>(); - public int Total { get; set; } = 0; // values for the data table aggregations int totalEmployees { get; set; } @@ -211,35 +184,33 @@ You can, however, add the desired aggregate functions and let .ToDataSourceResul //so that the ToDataSourceResult method will calculate them for you args.Request.Aggregates = new List { - new AggregateDescriptor{ + new AggregateDescriptor + { Member = nameof(Employee.Salary), - Aggregates = - new List() + Aggregates = new List() + { + new MaxFunction() { - new MaxFunction() - { - SourceField = nameof(Employee.Salary) - } + SourceField = nameof(Employee.Salary) } + } }, - new AggregateDescriptor - { - Member = nameof(Employee.ID), - Aggregates = - new List() + new AggregateDescriptor + { + Member = nameof(Employee.ID), + Aggregates = new List() { - new CountFunction() - { - SourceField = nameof(Employee.ID) - } + new CountFunction() + { + SourceField = nameof(Employee.ID) } - }, - }; - + } + } + }; DataSourceResult datasourceResult = SourceData.ToDataSourceResult(args.Request); - GridData = (datasourceResult.Data as IEnumerable>) + args.Data = (datasourceResult.Data as IEnumerable>) .Select(x => x.ToDictionary( x => x.Key, x => @@ -255,8 +226,7 @@ You can, however, add the desired aggregate functions and let .ToDataSourceResul })) .ToList(); - Total = datasourceResult.Total; - + args.Total = datasourceResult.Total; // 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 might be extra data returned in the response @@ -265,13 +235,8 @@ You can, however, add the desired aggregate functions and let .ToDataSourceResul highestSalary = (decimal)datasourceResult.AggregateResults.FirstOrDefault( r => r.AggregateMethodName == "Max" && r.Member == nameof(Employee.Salary))?.Value; - - - // for the grid data update itself - StateHasChanged(); } - public DataTable GenerateData() { var rand = new Random(); @@ -286,7 +251,7 @@ You can, however, add the desired aggregate functions and let .ToDataSourceResul table.Columns["Name"].DefaultValue = default(string); table.Columns["Salary"].DefaultValue = default(decimal); - for (int i = 1; i < 50; i++) + for (int i = 1; i <= 50; i++) { table.Rows.Add(i, $"Name {i}", rand.Next(1000, 5000)); } @@ -300,11 +265,11 @@ You can, however, add the desired aggregate functions and let .ToDataSourceResul public string Name { get; set; } public decimal Salary { get; set; } } - +} ```` ## Suggested Workarounds -Consider using a [collection of ExpandoObjects](https://github.com/telerik/blazor-ui/tree/master/grid/binding-to-expando-object) instead of a `DataTable` in general. +Consider using a [collection of ExpandoObjects](https://github.com/telerik/blazor-ui/tree/master/grid/binding-to-expando-object) instead of a `DataTable`. diff --git a/knowledge-base/grid-combobox-in-filtermenu-empty.md b/knowledge-base/grid-combobox-in-filtermenu-empty.md index 3f5b611061..c145264c85 100644 --- a/knowledge-base/grid-combobox-in-filtermenu-empty.md +++ b/knowledge-base/grid-combobox-in-filtermenu-empty.md @@ -17,6 +17,10 @@ res_type: kb Product Grid for Blazor + + Version + 2.30 or older + @@ -28,9 +32,10 @@ I have a ComboBox in a Grid `FilterMenuTemplate`. The ComboBox data is loaded as The behavior is related to the filter menu popup. It is rendered outside the Grid component in the page ``. If the ComboBox data is loaded asynchronously, the popup is not refreshed even by `StateHasChanged`. ## Suggested Workarounds +* Upgrade to UI for Blazor **3.0** or later. It uses different `OnRead` mechanism for loading asynchronous data and updating the ComboBox. The component dropdown will update even after it has been opened. * Load the ComboBox data before the filter menu is opened for the first time. Use Blazor events like `OnInitializedAsync`, or Grid events like [**OnStateInit**]({% slug grid-state %}#events) or [OnRead]({% slug grid-events %}#read-event). * Load the ComboBox data synchronously. -* If needed, it is possible to enable Grid filtering with a delay, after the ComboBox data has been loaded. +* Enable Grid filtering with a delay, after the ComboBox data has been loaded. Here is a test page that loads the `FilterMenuTemplate` data in `OnRead` and enables Grid filtering afterwards: diff --git a/knowledge-base/grid-debounce-operations.md b/knowledge-base/grid-debounce-operations.md index 55f98c3d2f..e22707f454 100644 --- a/knowledge-base/grid-debounce-operations.md +++ b/knowledge-base/grid-debounce-operations.md @@ -52,9 +52,10 @@ There are three ideas on the basic approach how to do this: @using Telerik.DataSource @using Telerik.DataSource.Extensions - + @@ -62,10 +63,6 @@ There are three ideas on the basic approach how to do this: @code { - public List GridData { get; set; } - public int Total { get; set; } = 0; - - DataSourceRequest lastRequest { get; set; } CancellationTokenSource tokenSource = new CancellationTokenSource(); // for debouncing protected async Task ReadItems(GridReadEventArgs args) @@ -77,21 +74,18 @@ There are three ideas on the basic approach how to do this: tokenSource = new CancellationTokenSource(); var token = tokenSource.Token; - await Task.Delay(500, token); // 500ms timeout for the debouncing + await Task.Delay(1500, token); // 500ms timeout for the debouncing //new data collection comes down from the service after debouncing - lastRequest = args.Request; - await RequestData(); + await RequestData(args); } - async Task RequestData() + async Task RequestData(GridReadEventArgs args) { - DataEnvelope DataResult = await FetchPagedData(lastRequest); - - GridData = DataResult.CurrentPageData; - Total = DataResult.TotalItemCount; + DataEnvelope DataResult = await FetchPagedData(args.Request); - StateHasChanged(); + args.Data = DataResult.CurrentPageData; + args.Total = DataResult.TotalItemCount; } public void Dispose() @@ -112,7 +106,7 @@ There are three ideas on the basic approach how to do this: List fullList = new List(); int totalCount = 100; - for (int i = 0; i < totalCount; i++) + for (int i = 1; i <= totalCount; i++) { fullList.Add(new Employee() { diff --git a/knowledge-base/grid-expand-button-tooltip.md b/knowledge-base/grid-expand-button-tooltip.md index ace7a6b5b2..d22f04b13e 100644 --- a/knowledge-base/grid-expand-button-tooltip.md +++ b/knowledge-base/grid-expand-button-tooltip.md @@ -51,9 +51,11 @@ The example below shows one way to do that, and to distinguish a particular grid } - + @{ var employee = context as MainModel; @@ -72,23 +74,24 @@ The example below shows one way to do that, and to distinguish a particular grid @code { - List CurrentGridData { get; set; } List AllData { get; set; } - int Total { get; set; } = 0; protected async Task ReadItems(GridReadEventArgs args) { // typical data retrieval through OnRead var datasourceResult = AllData.ToDataSourceResult(args.Request); - CurrentGridData = (datasourceResult.Data as IEnumerable).ToList(); - Total = datasourceResult.Total; - - StateHasChanged(); + args.Data = datasourceResult.Data; + args.Total = datasourceResult.Total; // end data operations - - // ensure the hierarchy expand icons have the desired tooltip after the grid re-renderes with new data - await _js.InvokeVoidAsync("setGridExpandButtonTitles", ".titles-on-expand-buttons", "Expand Details"); + + try + { + // using try-catch to prevent JSInterop calls at too early stage + // ensure the hierarchy expand icons have the desired tooltip after the grid re-renderes with new data + await _js.InvokeVoidAsync("setGridExpandButtonTitles", ".titles-on-expand-buttons", "Expand Details"); + } + catch (InvalidOperationException e) { } } // only data generation follows diff --git a/knowledge-base/grid-expand-only-current.md b/knowledge-base/grid-expand-only-current.md index 589c80919b..e43bb64ae8 100644 --- a/knowledge-base/grid-expand-only-current.md +++ b/knowledge-base/grid-expand-only-current.md @@ -6,7 +6,7 @@ page_title: Expand only current item of the Grid and programmatically collapse a slug: grid-kb-expand-only-current position: tags: grid, expand, collapse, programmatically -ticketid: 1513997 +ticketid: 1513997, 1520717 res_type: kb --- @@ -20,6 +20,7 @@ res_type: kb + ## Description @@ -27,25 +28,18 @@ I have a Grid with hierarchy enabled. When I expand one row, I want to programma ## Solution -The `OnRowExpand` event provides arguments of type `GridRowExpandEventArgs`. You can check the `Item` field of the arguments to get information which item is currently expanded. - -You can then use the [Grid State]({%slug grid-state%}) to programmatically set its `ExpandedRows` field. In order to achieve the desired behavior, the `ExpandedRows` of the Grid State should only has information for the current expanded item. This will result in collapsing all previously expanded items and keep just the current one expanded. - -The `ExpandedRows` accepts a list of item indexes. In order to [get the correct index of the current expanded item]({%slug grid-kb-index-of-a-grid-row%}) in case of filtering, sorting, paging of the Grid, use the `OnRead` event to work with the current data. - -See code comments in the example below for more details on the spot. +1. Handle the [Grid `OnRowExpand` event]({%slug grid-events%}#onrowexpand). It provides argument of type `GridRowExpandEventArgs` with a `Item` field that refers to the currently expanded Grid item. +1. Set the `ShouldRender` property of the `GridRowExpandEventArgs` argument to `true`. +1. Use the [Grid State]({%slug grid-state%}) to programmatically set its `ExpandedItems` property. Set `ExpandedItems` to a `List` that should only contain the currently expanded item. This will result in collapsing all previously expanded items. ````CSHTML -@using Telerik.DataSource.Extensions - -Expanded item index: @currItemIndex - - + @{ var employee = context as MainModel; - + @@ -61,41 +55,15 @@ Expanded item index: @currItemIndex @code { public TelerikGrid GridRef { get; set; } + List SalesTeamMembers { get; set; } - List salesTeamMembers { get; set; } - - List currentSalesTeamMembers { get; set; } //implementation of OnRead - - MainModel currentItem { get; set; } //need to save the expanded item to determine its new index - - public int currItemIndex { get; set; } //the new index of the current expanded item - - public int Total { get; set; } - - //use the OnRead event to work with the current data in order to get the new index of the current expanded item in case of filtering, sorting, paging of the Grid - void OnReadHandler(GridReadEventArgs args) - { - var dataSourceResult = salesTeamMembers.ToDataSourceResult(args.Request); - - currentSalesTeamMembers = dataSourceResult.Data.Cast().ToList(); // this is the collection with the current items - - Total = dataSourceResult.Total; - - StateHasChanged(); - } - - //in the OnRowExpand handler you can perform the desired logic to collapse the rest of the items and keep just the current one opened async Task OnExpand(GridRowExpandEventArgs args) { - currentItem = args.Item as MainModel; + args.ShouldRender = true; - //get the new index of the current expanded item from the current data - currItemIndex = currentSalesTeamMembers.IndexOf(currentItem); - - //use the Grid state and its ExpandedRows field to set the only expanded item to be the current one GridState desiredState = GridRef.GetState(); - desiredState.ExpandedRows = new List { currItemIndex }; + desiredState.ExpandedItems = new List { args.Item as MainModel }; await GridRef.SetState(desiredState); } @@ -103,16 +71,16 @@ Expanded item index: @currItemIndex //data generation and models protected override void OnInitialized() { - salesTeamMembers = GenerateData(); + SalesTeamMembers = GenerateData(); } private List GenerateData() { List data = new List(); - for (int i = 0; i < 30; i++) + for (int i = 1; i <= 30; i++) { MainModel mdl = new MainModel { Id = i, Name = $"Name {i}" }; - mdl.Orders = Enumerable.Range(1, 15).Select(x => new DetailsModel { OrderId = x, DealSize = x ^ i }).ToList(); + mdl.Orders = Enumerable.Range(1, 3).Select(x => new DetailsModel { OrderId = x, DealSize = x ^ i }).ToList(); data.Add(mdl); } return data; @@ -132,4 +100,3 @@ Expanded item index: @currItemIndex } } ```` - diff --git a/knowledge-base/grid-force-refresh.md b/knowledge-base/grid-force-refresh.md index 637bed5edd..baced14f31 100644 --- a/knowledge-base/grid-force-refresh.md +++ b/knowledge-base/grid-force-refresh.md @@ -98,103 +98,4 @@ If you don't use an `ObservableCollection`, you can create a `new` instance of t ### Manual operations -When using manual operations through the [OnRead event](https://docs.telerik.com/blazor-ui/components/grid/manual-operations), the general pattern is to store the last `DataSourceRequest` so you can repeat it over the new data and create a `new` Data collection. Here is an example: - ->caption Refresh grid data with manual data source operations - - -````CSHTML -@using Telerik.DataSource -@using Telerik.DataSource.Extensions -@using System.Collections.ObjectModel - -Change Data Source - - - - - - - - Update - Edit - Delete - Cancel - - - - Add Employee - - - -@code { - public List SourceData { get; set; } - public int Total { get; set; } = 0; - public ObservableCollection GridData { get; set; } = new ObservableCollection(); //prevent null reference errors by intializing the field - DataSourceRequest LastRequest { get; set; }//store the last request so we can repeat it when changing the data - - async Task ChangeData() - { - SourceData = GenerateData(DateTime.Now.Millisecond.ToString()); - await LoadData();//repeat the last request when changing data - } - - async Task LoadData() - { - await Task.Delay(500); //simulate network delay from a real async call - - //this example uses the Telerik .ToDataSourceResult() method for brevity, you can call an API here instead - var datasourceResult = SourceData.ToDataSourceResult(LastRequest); - - //should not be needed, but if the data does not update, try clearing it in order to fire the observable collection event - //GridData.Clear(); - - //update the grid data as usual - GridData = new ObservableCollection(datasourceResult.Data as IEnumerable); - Total = datasourceResult.Total; - - //tell the UI to update - StateHasChanged(); - } - - protected override void OnInitialized() - { - SourceData = GenerateData(string.Empty); - } - - protected async Task ReadItems(GridReadEventArgs args) - { - LastRequest = args.Request; - - await LoadData(); - } - - //This sample implements only reading of the data. To add the rest of the CRUD operations see - //https://docs.telerik.com/blazor-ui/components/grid/editing/overview - - private List GenerateData(string generationIdentifier) - { - var result = new List(); - var rand = new Random(); - for (int i = 0; i < 100; i++) - { - result.Add(new Employee() - { - ID = i, - Name = $"Name {i} {generationIdentifier}", - HireDate = DateTime.Now.Date.AddDays(rand.Next(-20, 20)) - }); - } - - return result; - } - - public class Employee - { - public int ID { get; set; } - public string Name { get; set; } - public DateTime HireDate { get; set; } - } -} -```` +When using manual operations through the [OnRead event](https://docs.telerik.com/blazor-ui/components/grid/manual-operations), the general pattern is to [store the last `DataSourceRequest`]({%slug components/grid/manual-operations%}#cache-data-request) so you can repeat it over a [new `OnRead` call]({%slug grid-refresh-data%}#call-onread). diff --git a/knowledge-base/grid-get-filtered-data.md b/knowledge-base/grid-get-filtered-data.md index 5e96779254..428335f2e5 100644 --- a/knowledge-base/grid-get-filtered-data.md +++ b/knowledge-base/grid-get-filtered-data.md @@ -1,6 +1,6 @@ --- title: Get Filtered Data from Grid -description: How to get the filtered and sorted data from the blazor grid +description: How to get the filtered and sorted data from the Blazor Grid type: how-to page_title: Get Filtered Data from Grid slug: grid-kb-get-filtered-data @@ -27,19 +27,22 @@ I want to obtain the filtered and sorted rows / items in the grid. This applies ## Solution -Use the `OnRead` event of the grid to get the `DataSourceRequest` object that contains a collection of filter and sort descriptors, as well as information for the current page and its size. +Use the [Grid `OnRead` event]({%slug components/grid/manual-operations%}) to get the `DataSourceRequest` object that contains a collection of filter and sort descriptors, as well as information for the current page and its size. -You can then use that to fetch the desired page of data and you will have that information. This is the concept behind implementing your own data source operations so you can optimize them. You can read more about this [here]({%slug components/grid/manual-operations%}). +You can then fetch the desired page of filtered and sorted data. This is the same concept as when implementing your own data source operations. You can read more about this in the linked [Manual Data Operations article]({%slug components/grid/manual-operations%}). -If you already have all the data in the view-model, the Telerik extension methods can help you apply those operations - see `.ToDataSourceResult()` - an example is available below and in the article linked above. +If you already have all the data in the view-model, the Telerik extension methods can help you get the data in a specific sorted and filtered state at any time - see [`.ToDataSourceResult()`]({%slug components/grid/manual-operations%}#telerik-todatasourceresultrequest). ->caption Example of how to get the filtered and sorted data for the current page of the grid, including from the searchbox +>caption Example of how to get the filtered and sorted data of the Grid, including from the SearchBox ````CSHTML +@using Telerik.DataSource @using Telerik.DataSource.Extensions - + @@ -52,36 +55,50 @@ If you already have all the data in the view-model, the Telerik extension method @if (GridData != null && GridData.Any()) { -

    Search has items starting with item @GridData[0].ID and ending with item @GridData[GridData.Count - 1].ID

    +

    Search has @GridData.Count.ToString() items starting with item @GridData[0].ID + and ending with item @GridData[GridData.Count - 1].ID

    } @code { public List SourceData { get; set; } public List GridData { get; set; } - public int Total { get; set; } = 0; - - 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; + args.Data = datasourceResult.Data; + args.Total = datasourceResult.Total; + + // get the filtered/sorted data on the current page only + //GridData = (datasourceResult.Data as IEnumerable).ToList(); - //the GridData variable now holds the currently filtered/sorted data from the grid + // get the filtered/sorted data on all pages + // use a new DataSourceRequest OR reset args.Request.Page and args.Request.PageSize + var allPagesRequest = new DataSourceRequest(); + allPagesRequest.Sorts = args.Request.Sorts; + allPagesRequest.Filters = args.Request.Filters; + // OR + //args.Request.Page = 1; + //args.Request.PageSize = SourceData.Count; - StateHasChanged(); + var datasourceResultAllPages = SourceData.ToDataSourceResult(allPagesRequest); + // OR + //var datasourceResultAllPages = SourceData.ToDataSourceResult(args.Request); + + GridData = (datasourceResultAllPages.Data as IEnumerable).ToList(); + } + + protected override void OnInitialized() + { + SourceData = GenerateData(); } private List GenerateData() { var result = new List(); var rand = new Random(); - for (int i = 0; i < 100; i++) + for (int i = 1; i <= 100; i++) { result.Add(new Employee() { diff --git a/knowledge-base/grid-get-index-of-grid-row.md b/knowledge-base/grid-get-index-of-grid-row.md index a282b65821..fd1737c4eb 100644 --- a/knowledge-base/grid-get-index-of-grid-row.md +++ b/knowledge-base/grid-get-index-of-grid-row.md @@ -21,30 +21,31 @@ res_type: kb ## Description -I am having a Grid and I would like to know the new index of a row when sorting or filtering the Grid. +I have a Grid and I would like to know the new index of a row when sorting or filtering the Grid. ## Solution -The Grid does not directly alter the collection of items passed to its `Data` parameter so when performing operations like Sorting or Filtering the collection will not be changed, thus the index of the item will remain unchanged. In order to get the sorted/filtered collection you should use the [OnRead]({%slug components/grid/manual-operations%}) event for the Grid. +The Grid does not directly alter the collection of items passed to its `Data` parameter. When performing operations like Sorting or Filtering the collection will not be changed, thus the index of an item will remain unchanged. In order to get the sorted/filtered collection, use the [OnRead event]({%slug components/grid/manual-operations%}) of the Grid. -* [Get the New Index Of a Selected Row](#get-the-new-index-of-a-selected-row) -* [Get the New Index Of a Clicked Row](#get-the-new-index-of-an-clicked-row) +* Use the `OnRead` event to cache the Grid data in its current sort state. +* Use the `SelectedItemsChanged` or `OnRowClick` event to find the item index. +* Optionally, use `OnRead` to update the item index. -### Get the New Index Of a Selected Row +>caption Get the index of a clicked/selected row immediately and after sorting ````CSHTML -@*Get the new index of the selected Grid row when sorting the Grid*@ +@*Get the index of a clicked/selected row immediately and after sorting*@ @using Telerik.DataSource @using Telerik.DataSource.Extensions - @@ -60,109 +61,30 @@ The Grid does not directly alter the collection of items passed to its `Data` pa } @code { - public int Total { get; set; } - List salesTeamMembers { get; set; } - IEnumerable selectedItems { get; set; } = new List(); - List currentSalesTeamMembers { get; set; } //implementation of OnRead - MainModel currentItem { get; set; } //need to save the selected item to determine its new index + List SalesTeamMembers { get; set; } + List CurrentSalesTeamMembers { get; set; } //implementation of OnRead + IEnumerable SelectedItems { get; set; } = new List(); + MainModel ClickedItem { get; set; } public string Result { get; set; } void SelectedItemsChanged(IEnumerable items) { - selectedItems = items; - - var selectedItem = items.FirstOrDefault(); - - currentItem = selectedItem; - } - - async Task OnReadHandler(GridReadEventArgs args) - { - var dataSourceResult = salesTeamMembers.ToDataSourceResult(args.Request); - - currentSalesTeamMembers = dataSourceResult.Data.Cast().ToList(); // this is the collection with the sorted items - - int currentIndexOfItem = currentSalesTeamMembers.IndexOf(currentItem); - - if(currentIndexOfItem > -1) // this check is not mandatory - { - Result = $"The index of the selected item is: {currentIndexOfItem}"; - } - - Total = dataSourceResult.Total; - - StateHasChanged(); - } - - protected override void OnInitialized() - { - salesTeamMembers = GenerateData(); - } - - private List GenerateData() - { - List data = new List(); - for (int i = 0; i < 5; i++) - { - MainModel mdl = new MainModel { Id = i, Name = $"Name {i}" }; - data.Add(mdl); - } - return data; - } - - public class MainModel - { - public int Id { get; set; } - public string Name { get; set; } + // same logic as in OnRowClick, use either event + SelectedItems = items; + ClickedItem = items.FirstOrDefault(); + PrintTheRowIndex(ClickedItem); } -} -```` - -### Get the New Index Of a Clicked Row -````CSHTML -@*Get the new index of a clicked row*@ - -@using Telerik.DataSource -@using Telerik.DataSource.Extensions - - - - - - - - -@if (!String.IsNullOrEmpty(Result)) -{ -
    - @Result -
    -} - -@code { - public int Total { get; set; } - List salesTeamMembers { get; set; } - List currentSalesTeamMembers { get; set; } //implementation of OnRead - MainModel currentItem { get; set; } //need to save the selected item to determine its new index - public string Result { get; set; } - - void OnRowClickHandler(GridRowClickEventArgs args) + void OnRowClick(GridRowClickEventArgs args) { - var clickedRow = args.Item as MainModel; - - currentItem = clickedRow; - - PrintTheRowIndex(clickedRow); + // same logic as in SelectedItemsChanged, use either event + //ClickedItem = args.Item as MainModel; + //PrintTheRowIndex(ClickedItem); } void PrintTheRowIndex(MainModel item) { - int currentIndexOfItem = currentSalesTeamMembers.IndexOf(item); + int currentIndexOfItem = CurrentSalesTeamMembers.IndexOf(item); if (currentIndexOfItem > -1) // this check is not mandatory { @@ -172,26 +94,24 @@ The Grid does not directly alter the collection of items passed to its `Data` pa async Task OnReadHandler(GridReadEventArgs args) { - var dataSourceResult = salesTeamMembers.ToDataSourceResult(args.Request); - - currentSalesTeamMembers = dataSourceResult.Data.Cast().ToList(); // this is the collection with the sorted items + var dataSourceResult = SalesTeamMembers.ToDataSourceResult(args.Request); - Total = dataSourceResult.Total; + args.Data = CurrentSalesTeamMembers = dataSourceResult.Data.Cast().ToList(); // this is the collection with the sorted items + args.Total = dataSourceResult.Total; - PrintTheRowIndex(currentItem); - - StateHasChanged(); + // update the row index if changed + PrintTheRowIndex(ClickedItem); } protected override void OnInitialized() { - salesTeamMembers = GenerateData(); + SalesTeamMembers = GenerateData(); } private List GenerateData() { List data = new List(); - for (int i = 0; i < 5; i++) + for (int i = 1; i <= 5; i++) { MainModel mdl = new MainModel { Id = i, Name = $"Name {i}" }; data.Add(mdl); @@ -206,4 +126,3 @@ The Grid does not directly alter the collection of items passed to its `Data` pa } } ```` - diff --git a/knowledge-base/grid-large-skip-breaks-virtualization.md b/knowledge-base/grid-large-skip-breaks-virtualization.md index c46f624b7c..af169ef1dd 100644 --- a/knowledge-base/grid-large-skip-breaks-virtualization.md +++ b/knowledge-base/grid-large-skip-breaks-virtualization.md @@ -22,7 +22,7 @@ res_type: kb ## Description -We use [virtualization]({%slug components/grid/virtual-scrolling%}) and set the `Skip` of the grid through its [state]({%slug grid-state%}) (for example, to restore state from the user or to scroll the grid programmatically). +We use [virtualization]({%slug components/grid/virtual-scrolling%}) and set the `Skip` of the grid through its [state]({%slug grid-state%}) (for example, to restore state from the user or to scroll the grid programmatically). When the sum of the skip and the page size is bigger then the total count of the items and we try to set the Skip property for the second time, some of the top items are not shown. @@ -31,12 +31,12 @@ When the sum of the skip and the page size is bigger then the total count of the ![Setting invalid Skip value breaks the grid virtualization appearance](images/invalid-skip.gif) ## Cause\Possible Cause(s) -The origin of the problem is that when the Skip value is too large, there may not be enough items to fill the grid viewport with data. +The origin of the problem is that when the `Skip` value is too large, there may not be enough items to fill the Grid viewport. -For example, if the grid viewport could fit 15 items, but according to the `Skip` setting that you set there are only 8 items in the `Data`, those items cannot push the placeholder rows out of view, and you will still see them at the top of the grid. +For example, if the Grid viewport can fit 15 items, but according to the `Skip` setting, there are only 8 items to display, those items cannot push the placeholder rows out of view, and you will still see them at the top of the Grid. ## Solution -Ensure that you set such a `Skip` to the grid so that you don't show placeholders to your user. For example, when the data arrives, check if there are too few items and update the Skip value. +Ensure that you set a suitable `Skip` value, so that row placeholders don't show. For example, when the data arrives, check if there are too few items and update the `Skip` value. >caption Reproducible and a solution for setting an invalid (too large) Skip @@ -44,7 +44,7 @@ Ensure that you set such a `Skip` to the grid so that you don't show placeholder @using Telerik.DataSource.Extensions
      -
    1. Click the button twice or more - placeholders remain open on the second call because the items are too few.
    2. +
    3. Click the button twice or more - placeholders remain visible on the second call because the items are too few.
    4. Check the checkbox
    5. Click the button again
    -Set invalidly large Skip +Set too large Skip - + FilterMode="@GridFilterMode.FilterRow" Sortable="true"> @@ -71,6 +72,7 @@ Ensure that you set such a `Skip` to the grid so that you don't show placeholder bool shouldFixInvalidSkip { get; set; } int PageSize = 20; TelerikGrid GridRef { get; set; } + async Task SetSkip(int skip) { if (GridRef != null) @@ -86,9 +88,10 @@ Ensure that you set such a `Skip` to the grid so that you don't show placeholder //this should actually be happening on the server, but for brevity we do it here //see more at https://github.com/telerik/blazor-ui/tree/master/grid/datasourcerequest-on-server var datasourceResult = SourceData.ToDataSourceResult(args.Request); + List curentData; - GridData = (datasourceResult.Data as IEnumerable).ToList(); - Total = datasourceResult.Total; + args.Data = curentData = (datasourceResult.Data as IEnumerable).ToList(); + args.Total = datasourceResult.Total; if (shouldFixInvalidSkip) { @@ -98,26 +101,22 @@ Ensure that you set such a `Skip` to the grid so that you don't show placeholder // with the current grid settings there can be 6 items in the viewport, calculate this as needed in your app // for example, based on the row size and grid height, or even use JS Interop if needed to get actual DOM elements' sizes int itemsThatFitPerPage = 6; - bool isInvalidSkip = GridData.Count < itemsThatFitPerPage; - //a general rule that could cause this is something like this issue is the following + bool isInvalidSkip = (args.Data as IEnumerable).Count() < itemsThatFitPerPage; + //generally, the issue can occur as a result of the following condition //but using that can prevent the user from scrolling all the way down to the last items //Total < (args.Request.Skip + PageSize); if (isInvalidSkip) { - int matchingSkip = Total - itemsThatFitPerPage; + int matchingSkip = args.Total - itemsThatFitPerPage; await SetSkip(matchingSkip); } } - - await InvokeAsync(StateHasChanged); } // only basic data binding follows public List SourceData { get; set; } - public List GridData { get; set; } - public int Total { get; set; } = 0; protected override void OnInitialized() { @@ -128,7 +127,7 @@ Ensure that you set such a `Skip` to the grid so that you don't show placeholder { var result = new List(); var rand = new Random(); - for (int i = 1; i < 101; i++) + for (int i = 1; i <= 100; i++) { result.Add(new Employee() { @@ -153,6 +152,4 @@ Ensure that you set such a `Skip` to the grid so that you don't show placeholder ## Notes -The grid cannot make this change for you because that would mean altering the state you provide, which would be invalid and unexpected behavior. Moreover, it could result in infinite loops or errors, and is a heuristic (undetermined) task. - - +The Grid cannot apply the fix automatically, because that would mean altering the state you provide. This would be invalid and unexpected behavior. Moreover, it could result in infinite loops or errors, and is a heuristic (undetermined) task. diff --git a/knowledge-base/grid-one-expanded-detail-template.md b/knowledge-base/grid-one-expanded-detail-template.md index 5f6e4c10c1..b94f7850bf 100644 --- a/knowledge-base/grid-one-expanded-detail-template.md +++ b/knowledge-base/grid-one-expanded-detail-template.md @@ -6,7 +6,7 @@ page_title: Expand only one detail template on row click slug: grid-kb-one-expanded-detail-template position: tags: -ticketid: 1520717 +ticketid: 1513997, 1520717 res_type: kb --- @@ -20,6 +20,7 @@ res_type: kb + ## Description I would like functionality where you close any detail page that is open if the customer open another one. So in any given situation, there will be only one detailpage open. @@ -32,13 +33,12 @@ You can use the [grid state]({%slug grid-state%}) to make sure only one item is ````CSHTML @using Telerik.DataSource.Extensions - + Pageable="true" PageSize="5" FilterMode="@GridFilterMode.FilterMenu" Sortable="true"> @{ var employee = context as MainModel; @@ -58,23 +58,17 @@ You can use the [grid state]({%slug grid-state%}) to make sure only one item is @code { List AllSalesTeamMembers { get; set; } - List CurrentSalesTeamMembers { get; set; } - int Total { get; set; } TelerikGrid Grid { get; set; } async Task EnsureOnlyCurrentRowExpanded(MainModel currItem) { - int currentIndexOfItem = CurrentSalesTeamMembers.IndexOf(currItem); - if (currentIndexOfItem > -1) - { - // use the current grid state to keep filters, sorts, paging and so on - var state = Grid.GetState(); - // set only the current row to be expanded - state.ExpandedRows = new List { currentIndexOfItem }; - // Note: SetState() will call OnRead, so you may want to - // consider raising flags and caching data if you want to reduce requests for remote data - await Grid.SetState(state); - } + // use the current grid state to keep filters, sorts, paging and so on + var state = Grid.GetState(); + // set only the current row to be expanded + state.ExpandedItems = new List { currItem }; + // Note: SetState() will call OnRead, so you may want to + // consider raising flags and caching data if you want to reduce requests for remote data + await Grid.SetState(state); } async Task OnRowClickHandler(GridRowClickEventArgs args) @@ -92,9 +86,8 @@ You can use the [grid state]({%slug grid-state%}) to make sure only one item is async Task OnReadHandler(GridReadEventArgs args) { var dataSourceResult = AllSalesTeamMembers.ToDataSourceResult(args.Request); - CurrentSalesTeamMembers = dataSourceResult.Data.Cast().ToList(); - Total = dataSourceResult.Total; - await InvokeAsync(StateHasChanged); + args.Data = dataSourceResult.Data.Cast().ToList(); + args.Total = dataSourceResult.Total; } // only models and data generation follow @@ -107,10 +100,10 @@ You can use the [grid state]({%slug grid-state%}) to make sure only one item is private List GenerateData() { List data = new List(); - for (int i = 0; i < 15; i++) + for (int i = 1; i <= 15; i++) { MainModel mdl = new MainModel { Id = i, Name = $"Name {i}" }; - mdl.Orders = Enumerable.Range(1, 15).Select(x => new DetailsModel { OrderId = x, DealSize = x ^ i }).ToList(); + mdl.Orders = Enumerable.Range(1, 3).Select(x => new DetailsModel { OrderId = x, DealSize = x ^ i }).ToList(); data.Add(mdl); } return data; diff --git a/knowledge-base/grid-row-numbers.md b/knowledge-base/grid-row-numbers.md index 44fcbe23a7..6204f9f3d8 100644 --- a/knowledge-base/grid-row-numbers.md +++ b/knowledge-base/grid-row-numbers.md @@ -27,9 +27,9 @@ Is there a way to add row numbers to the grid? I want them to update every time the grid changes, so whenever I filter it or sort it. So basically if it's sorted by date ascending, the rows start at 1 and increase as you travel down the grid. If you then sort descending, the row numbers will again start at 1 and increase as you travel down the grid. ## Solution -Add a field to the row model that will display the row index, and populate the index in the `OnRead` event by iterating the collection. +Add a property to the row model that will display the row index. Populate the index in the [Grid `OnRead` event]({%slug components/grid/manual-operations%}) by iterating the items collection. -You can then use a regular grid column to show them in the beginning of the grid. This column should have the various data operations disabled (such as filtering, sorting, grouping, editing) because it does not carry actual information about the data. +You can then use a Grid column to show them. This column should have the various data operations disabled (such as filtering, sorting, grouping, editing) because it does not carry actual information about the data. In the general case, that logic would be done by the backend, this sample keeps all the operations in one place for brevity. @@ -38,13 +38,13 @@ In the general case, that logic would be done by the backend, this sample keeps ````CSHTML @using Telerik.DataSource.Extensions - - + - + @@ -52,13 +52,10 @@ In the general case, that logic would be done by the backend, this sample keeps @code { - public List SourceData { get; set; } // in a real case that's a remote data source, it is here for brevity - public List GridData { get; set; } - public int Total { get; set; } = 0; + public List SourceData { get; set; } protected async Task ReadItems(GridReadEventArgs args) { - // in a real case, the remote data source will hande the data shaping, this is here for brevity var datasourceResult = SourceData.ToDataSourceResult(args.Request); // start row index setup @@ -68,14 +65,10 @@ In the general case, that logic would be done by the backend, this sample keeps iteratableData[i].RowIndex = i + 1; // we add one for human readabale 1-based index } datasourceResult.Data = iteratableData; - // end row index setup - // in a real case you receive the data here and use it as-is - GridData = (datasourceResult.Data as IEnumerable).ToList(); - Total = datasourceResult.Total; - - StateHasChanged(); + args.Data = datasourceResult.Data; + args.Total = datasourceResult.Total; } protected override void OnInitialized() @@ -87,7 +80,7 @@ In the general case, that logic would be done by the backend, this sample keeps { var result = new List(); var rand = new Random(); - for (int i = 0; i < 100; i++) + for (int i = 1; i <= 100; i++) { result.Add(new Employee() { @@ -105,9 +98,8 @@ In the general case, that logic would be done by the backend, this sample keeps public Guid ID { get; set; } public string Name { get; set; } public DateTime HireDate { get; set; } - //this field will contain the row index for display purposes + //this property will contain the row index for display purposes public int RowIndex { get; set; } } } ```` - diff --git a/knowledge-base/grid-search-in-hidden-fields.md b/knowledge-base/grid-search-in-hidden-fields.md index 3beab66c94..4f63b0ef31 100644 --- a/knowledge-base/grid-search-in-hidden-fields.md +++ b/knowledge-base/grid-search-in-hidden-fields.md @@ -43,7 +43,7 @@ Here is an example: @using Telerik.DataSource.Extensions @using Telerik.DataSource - @@ -59,21 +59,7 @@ Here is an example: @code { - - private int CurrentGridTotalCount { get; set; } - public List GridData { get; set; } = new List(); - public List CurrentGridData { get; set; } = new List(); - - protected override Task OnInitializedAsync() - { - for (int j = 1; j <= 9; j++) - { - GridData.Add(new GridItem() { ID = j, Name = "Name " + j, Secret = "Secret" + j }); - } - - return base.OnInitializedAsync(); - } private async Task GridReadHandler(GridReadEventArgs args) { @@ -101,8 +87,18 @@ Here is an example: } var result = GridData.ToDataSourceResult(args.Request); - CurrentGridData = (result.Data as IEnumerable).ToList(); - CurrentGridTotalCount = result.Total; + args.Data = result.Data; + args.Total = result.Total; + } + + protected override Task OnInitializedAsync() + { + for (int j = 1; j <= 10; j++) + { + GridData.Add(new GridItem() { ID = j, Name = "Name " + j, Secret = "Secret" + j }); + } + + return base.OnInitializedAsync(); } public class GridItem diff --git a/upgrade/breaking-changes/3-0-0.md b/upgrade/breaking-changes/3-0-0.md index ed9037d636..8973c1e6fb 100644 --- a/upgrade/breaking-changes/3-0-0.md +++ b/upgrade/breaking-changes/3-0-0.md @@ -10,6 +10,108 @@ position: 1 > This article is a heads up for the upcoming changes in Telerik UI for Blazor 3.0.0 release, which will be shipped in mid January 2022. While typically such articles are published at the day of a release, we wanted to make sure we inform our customers in advance and provide time to plan and prepare for what's coming. Additionally with the 3.0.0 release we will update the current article with "how to" guidelines, so that are enabled with everything you need for a smooth upgrade. +## Common Changes + +### OnRead + +The `OnRead` handlers of all components now expect you to pass the data to an **event argument**. This brings the following changes when using manual data operations: + +- Set the `TItem` attribute of the component. This will provide information about the model type, instead of `Data`. +- Set the `TValue` attribute for AutoComplete, ComboBox, DropDownList and MultiSelect. +- Do not set the `Data` attribute. Instead, set `args.Data` (`IEnumerable`) in the `OnRead` handler. +- There is no need to cast the items returned by `ToDataSourceResult()` when setting `args.Data`. +- Do not set `TotalCount`. This attribute is now removed in favor of `args.Total` (`int`) in the `OnRead` handler. +- Aggregates over all the data are now supported via `args.AggregateResults` (`IEnumerable`). The `AggregateResults` event argument is exposed only for components that support aggregates. +- If you have cached the `DataSourceRequest` object in order to set `Data` later, the new approach is to [reset the Grid state, so that `OnRead` is called again]({%slug grid-refresh-data%}#call-onread). + +>caption OnRead Usage in UI for Blazor up to version 2.30 and after version 3.0 + + + + + + + + + + + + +
    UI for Blazor 2.30UI for Blazor 3.0
    +
    
    +<TelerikComboBox Data="@ComboData"
    +                 OnRead="@OnComboRead"
    +                 TotalCount="@ComboTotal" />
    +
    +<TelerikGrid Data="@GridData"
    +             OnRead="@OnGridRead"
    +             TotalCount="@GridTotal" />
    +
    +
    
    +List<Product> SourceData { get; set; } = new();
    +List<Product> ComboData { get; set; }
    +List<Product> GridData { get; set; }
    +int ComboTotal { get; set; } = 0;
    +int GridTotal { get; set; } = 0;
    +
    +protected void OnComboRead(ComboBoxReadEventArgs args)
    +{
    +    var result = SourceData.ToDataSourceResult(args.Request);
    +
    +    ComboData = result.Data.Cast<Product>().ToList();
    +    ComboTotal = result.Total;
    +}
    +
    +protected void OnGridRead(GridReadEventArgs args)
    +{
    +    var result = SourceData.ToDataSourceResult(args.Request);
    +
    +    GridData = result.Data.Cast<Product>().ToList();
    +    GridTotal = result.Total;
    +    // aggregates N/A
    +}
    +
    +
    +
    
    +<TelerikComboBox TItem="@Product"
    +                 TValue="int"
    +                 OnRead="@OnComboRead" />
    +
    +<TelerikGrid TItem="@Product"
    +             OnRead="@OnGridRead" />
    +
    +
    +
    
    +List<Product> SourceData { get; set; } = new();
    +
    +
    +
    +
    +
    +protected void OnComboRead(ComboBoxReadEventArgs args)
    +{
    +    var result = SourceData.ToDataSourceResult(args.Request);
    +
    +    args.Data = result.Data;
    +    args.Total = result.Total;
    +}
    +
    +protected void OnGridRead(GridReadEventArgs args)
    +{
    +    var result = SourceData.ToDataSourceResult(args.Request);
    +
    +    args.Data = result.Data;
    +    args.Total = result.Total;
    +    args.AggregateResults = result.AggregateResults;
    +}
    +
    +
    + +### Other Common Changes + +- [`ToODataString` extension method](https://docs.telerik.com/blazor-ui/api/Telerik.Blazor.Extensions.DataSourceExtensions) is moved to the [`Telerik.Blazor.Extensions` namespace](https://docs.telerik.com/blazor-ui/api/Telerik.Blazor.Extensions). The `Telerik.Blazor.ExtensionMethods` namespace is removed in favor of `Telerik.Blazor.Extensions`. +- [`Telerik.Blazor.IconName` class]({%slug common-kb-migration-from-iconname%}) ([obsolete since version 2.0]({%slug changes-in-2-0-0%})) is removed. Use the icon names from the [Built-in Icons documentation]({%slug general-information/font-icons%}#icons-list). + ## Component Changes - Button - removed `Primary` parameter in favor of [`ThemeColor`]({%slug components/button/overview%}) of type `string`. There is a new static class `Telerik.Blazor.ThemeConstants.Button.ThemeColor` with a predefined set of theme colors. To get the old primary Button styling, set `ThemeColor="@ThemeConstants.Button.ThemeColor.Primary"`. @@ -26,11 +128,6 @@ position: 1 Grid EditorTemplates and Keyboard Navigation are in design stage. So, there might be changes coming from this direction as well. -## Common Changes - -- [`ToODataString` extension method](https://docs.telerik.com/blazor-ui/api/Telerik.Blazor.Extensions.DataSourceExtensions) is moved to the [`Telerik.Blazor.Extensions` namespace](https://docs.telerik.com/blazor-ui/api/Telerik.Blazor.Extensions). The `Telerik.Blazor.ExtensionMethods` namespace is removed in favor of `Telerik.Blazor.Extensions`. -- [`Telerik.Blazor.IconName` class]({%slug common-kb-migration-from-iconname%}) ([obsolete since version 2.0]({%slug changes-in-2-0-0%})) is removed. Use the icon names from the [Built-in Icons documentation]({%slug general-information/font-icons%}#icons-list). - ## Parameter Names We are making our API naming more consistent. From 0a268c6827cba052ac2cb1cf71ec9c07c8b0ffde Mon Sep 17 00:00:00 2001 From: Dimo Dimov Date: Tue, 18 Jan 2022 14:30:46 +0200 Subject: [PATCH 2/4] docs: Document OnRead breaking changes 2 --- components/autocomplete/virtualization.md | 2 +- components/combobox/virtualization.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/components/autocomplete/virtualization.md b/components/autocomplete/virtualization.md index 90d62fe3e2..5ef3f2363d 100644 --- a/components/autocomplete/virtualization.md +++ b/components/autocomplete/virtualization.md @@ -92,7 +92,7 @@ Run this and see how you can display, scroll and filter over 10k records in the Filterable="true" FilterOperator="@StringFilterOperator.Contains"> - + diff --git a/components/combobox/virtualization.md b/components/combobox/virtualization.md index 20fcf258c8..29c1959872 100644 --- a/components/combobox/virtualization.md +++ b/components/combobox/virtualization.md @@ -97,7 +97,7 @@ Run this and see how you can display, scroll and filter over 10k records in the @bind-Value="@SelectedValue" Filterable="true" FilterOperator="@StringFilterOperator.Contains"> - +
    From 42df823a3ae28f18f0ce20159d2506a599827563 Mon Sep 17 00:00:00 2001 From: Dimo Dimov Date: Tue, 18 Jan 2022 14:46:50 +0200 Subject: [PATCH 3/4] docs: Document OnRead breaking changes 3 --- upgrade/breaking-changes/3-0-0.md | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/upgrade/breaking-changes/3-0-0.md b/upgrade/breaking-changes/3-0-0.md index 8973c1e6fb..84de30ae81 100644 --- a/upgrade/breaking-changes/3-0-0.md +++ b/upgrade/breaking-changes/3-0-0.md @@ -14,14 +14,16 @@ position: 1 ### OnRead -The `OnRead` handlers of all components now expect you to pass the data to an **event argument**. This brings the following changes when using manual data operations: +The `OnRead` handlers of all components now expect you to pass the data to an **event argument**. This new data binding mechanism will no longer depend on the component life cycle and will allow new features, which could not be supported in the past. + +Note the following changes when using manual data operations: - Set the `TItem` attribute of the component. This will provide information about the model type, instead of `Data`. -- Set the `TValue` attribute for AutoComplete, ComboBox, DropDownList and MultiSelect. +- Set the `TValue` attribute for ComboBox, DropDownList and MultiSelect. - Do not set the `Data` attribute. Instead, set `args.Data` (`IEnumerable`) in the `OnRead` handler. - There is no need to cast the items returned by `ToDataSourceResult()` when setting `args.Data`. -- Do not set `TotalCount`. This attribute is now removed in favor of `args.Total` (`int`) in the `OnRead` handler. -- Aggregates over all the data are now supported via `args.AggregateResults` (`IEnumerable`). The `AggregateResults` event argument is exposed only for components that support aggregates. +- Do not set `TotalCount`. This attribute is now removed in favor of the `args.Total` event argument (`int`) in the `OnRead` handler. +- Aggregates over all the data are now supported via `args.AggregateResults` (`IEnumerable`). The `AggregateResults` event argument is exposed only for components that support aggregates. [If the Grid is bound to `DataTable`, a workaround is still necessary]({%slug grid-kb-aggregates-and-datatable%}). - If you have cached the `DataSourceRequest` object in order to set `Data` later, the new approach is to [reset the Grid state, so that `OnRead` is called again]({%slug grid-refresh-data%}#call-onread). >caption OnRead Usage in UI for Blazor up to version 2.30 and after version 3.0 @@ -71,7 +73,7 @@ protected void OnGridRead(GridReadEventArgs args)
    
     <TelerikComboBox TItem="@Product"
    -                 TValue="int"
    +                 TValue="@int"
                      OnRead="@OnComboRead" />
     
     <TelerikGrid TItem="@Product"
    
    From 1dcb4440b6604723b201075b90967a04a3498ee0 Mon Sep 17 00:00:00 2001
    From: Dimo Dimov 
    Date: Tue, 18 Jan 2022 17:27:00 +0200
    Subject: [PATCH 4/4] docs: Document OnRead breaking changes 4
    
    ---
     .../common/dropdowns-virtualization.md        | 20 +++++++------------
     components/combobox/events.md                 |  3 ++-
     components/dropdownlist/events.md             |  3 ++-
     components/listview/manual-operations.md      |  4 +++-
     4 files changed, 14 insertions(+), 16 deletions(-)
    
    diff --git a/_contentTemplates/common/dropdowns-virtualization.md b/_contentTemplates/common/dropdowns-virtualization.md
    index fabbb1dfe3..dc892434c5 100644
    --- a/_contentTemplates/common/dropdowns-virtualization.md
    +++ b/_contentTemplates/common/dropdowns-virtualization.md
    @@ -5,18 +5,14 @@ Enabling the UI virtualization feature makes the component reuse a set number of
     #end
     
     
    -
     #basics-core
     This section will explain the parameters and behaviors that are related to the virtualization feature so you can set it up.
     
     >caption To enable UI virtualization, you need to set the following parameters of the component:
     
     * `ScrollMode` - `Telerik.Blazor.DropDownScrollMode` - set it to `DropDownScrollMode.Virtual`. It defaults to the "regular" scrolling.
    -
     * `Height` - `string` - [set the height]({%slug common-features/dimensions%}) in the nested **popup settings** tag of the component. It must **not** be a `null/empty` string.
    -
     * `ItemHeight` - `decimal` - set it to the height each individual item will have in the dropdown. Make sure to accommodate the content your items will have and any item template.
    -
     * `PageSize` - `int` - defines how many items will actually be rendered and reused. The value determines how many items are loaded on each scroll. The number of items must be large enough according to the `ItemHeight` and popup `Height`, so that there are more items than the dropdown so there is a scrollbar.
     
     You can find a basic example in the [Local Data](#local-data-example) section below.
    @@ -25,36 +21,34 @@ You can find a basic example in the [Local Data](#local-data-example) section be
     #end
     
     
    -
     #value-mapper-text
     the component will call this method to request the model that matches the `Value` it has set. This is required because with remote data the `Value` may not be in the initial collection of data that the component has, and so there would otherwise be no way to extract the `DataTextField` from it to render it. Usually, this method will be called on the initial render only to fetch the data item for the current selection.
     #end
     
     
    -
     #remote-data-specifics
     * `OnRead` - `EventCallback` - the component will call this event when the user scrolls with the corresponding offset (`Skip`), `PageSize` and any filters. This lets you optimize the data queries and return only what is needed at the moment, when it is needed. Set the `args.Data` and `args.Total` properties of the event argument object.
    -
    +#end
     
     
     #limitations
    -
     * When the initially selected item/items are on a page different than the first one, opening the dropdown list will NOT scroll the list to the selected item.
    -
     #end
     
     
    -
     #remote-data-sample-intro
     This example showcases sample implementations of:
     
     * An async remote service that returns the data. It is mocked by a static class for this example, you can refactor as needed, and you can find examples of serializing it over the wire in this collection of sample projects for the grid component - the approach is identical.
    -
     * An `OnRead` event handler that calls that service.
    -
     #end
     
     
     #value-mapper-in-remote-example
     * A `ValueMapper` that also calls the service.
    -#end
    \ No newline at end of file
    +#end
    +
    +
    +#value-in-onread
    +>important The `OnRead` handler should change **only the data** of the component, and **not** other parameters such as `Value`. This can lead to issues with the asynchronous nature of the event, and race conditions can occur with the arrival of the new data. Moreover, such a change is likely to be unexpected by the user and cause bad UX.
    +#end
    diff --git a/components/combobox/events.md b/components/combobox/events.md
    index 7ba295ac11..b14c840b44 100644
    --- a/components/combobox/events.md
    +++ b/components/combobox/events.md
    @@ -218,7 +218,8 @@ When using `OnRead`, make sure to set `TItem` and `TValue`.
     
     >tip You can also [debounce the service calls and implement minimum filter length]({%slug combo-kb-debounce-onread%}).
     
    ->important You should **change** **only** the data of the ComboBox in the `OnRead` handler. You should **not** change other parameters such as `Value`, because this can lead to issues with the asynchronous nature of the event - the ComboBox cannot know whether the change of those parameters comes from somewhere external, and race conditions can occur with the arrival of the new data. Moreover, such a change is likely to be unwanted and unexpected for the end user and cause bad UX.
    +@[template](/_contentTemplates/common/dropdowns-virtualization.md#value-in-onread)
    +
     
     ````CSHTML
     

    @SelectedValue

    diff --git a/components/dropdownlist/events.md b/components/dropdownlist/events.md index 7f276a7aa6..de1e7e1b55 100644 --- a/components/dropdownlist/events.md +++ b/components/dropdownlist/events.md @@ -127,7 +127,8 @@ You can also call remote data through `async` operations. >tip You can also debounce the service calls and implement minimum filter length. An example of such approach is available in [this knowledge base article for the ComboBox]({%slug combo-kb-debounce-onread%}). The same approach is applicable for the DropDownList. ->important You should **change** **only** the data of the DropDownList in the `OnRead` handler. You should **not** change other parameters such as `Value`, because this can lead to issues with the asynchronous nature of the event - the DropDownList cannot know whether the change of those parameters comes from somewhere external, and race conditions can occur with the arrival of the new data. Moreover, such a change is likely to be unwanted and unexpected for the end user and cause bad UX. +@[template](/_contentTemplates/common/dropdowns-virtualization.md#value-in-onread) + ````CSHTML

    @SelectedValue

    diff --git a/components/listview/manual-operations.md b/components/listview/manual-operations.md index b334178b93..2e296308a2 100644 --- a/components/listview/manual-operations.md +++ b/components/listview/manual-operations.md @@ -21,9 +21,11 @@ In this article you will find examples how to: This is, effectively, loading data on demand only when the user goes to a certain page, as opposed to the default case where you fetch all the data items initially. To implement your own paging in the listview, you need to: -* Handle the `OnRead` event and... +* Handle the `OnRead` event. * Set the current page of data to the `args.Data` property of the event argument. * Set the `args.Total` property to the total number of items on all pages, so that the pager displays correct information. +* Set the `TItem` attribute of the ListView to the model type. +* Do not set the component `Data` attribute. >caption Custom Paging in the ListView