From 20203300263ddc0cdb667a8ba7078707b0ff42c9 Mon Sep 17 00:00:00 2001 From: Marin Bratanov Date: Fri, 6 Nov 2020 17:04:51 +0200 Subject: [PATCH 01/22] chore(grid): better CRUD service mimic on editign overview page --- components/grid/editing/overview.md | 132 ++++++++++++++-------------- 1 file changed, 68 insertions(+), 64 deletions(-) diff --git a/components/grid/editing/overview.md b/components/grid/editing/overview.md index c675bacd41..c3030379a4 100644 --- a/components/grid/editing/overview.md +++ b/components/grid/editing/overview.md @@ -74,8 +74,6 @@ Editing is cancelled for the first two records. @code { async Task EditHandler(GridCommandEventArgs args) { - AppendToLog("Edit", args); - SampleData item = (SampleData)args.Item; //prevent opening for edit based on condition @@ -83,6 +81,8 @@ Editing is cancelled for the first two records. { args.IsCancelled = true;//the general approach for cancelling an event } + + AppendToLog("Edit", args); } async Task UpdateHandler(GridCommandEventArgs args) @@ -92,14 +92,10 @@ Editing is cancelled for the first two records. SampleData item = (SampleData)args.Item; // perform actual data source operations here through your service - SampleData updatedItem = await ServiceMimicUpdate(item); - + await MyService.Update(item); + // update the local view-model data with the service data - var index = MyData.FindIndex(i => i.ID == updatedItem.ID); - if (index != -1) - { - MyData[index] = updatedItem; - } + await GetGridData(); } async Task DeleteHandler(GridCommandEventArgs args) @@ -109,13 +105,11 @@ Editing is cancelled for the first two records. SampleData item = (SampleData)args.Item; // perform actual data source operation here through your service - bool isDeleted = await ServiceMimicDelete(item); - - if (isDeleted) - { - // update the local view-model data - MyData.Remove(item); - } + await MyService.Delete(item); + + // update the local view-model data with the service data + await GetGridData(); + } async Task CreateHandler(GridCommandEventArgs args) @@ -125,10 +119,10 @@ Editing is cancelled for the first two records. SampleData item = (SampleData)args.Item; // perform actual data source operation here through your service - SampleData insertedItem = await ServiceMimicInsert(item); - + await MyService.Create(item); + // update the local view-model data with the service data - MyData.Insert(0, insertedItem); + await GetGridData(); } async Task CancelHandler(GridCommandEventArgs args) @@ -142,44 +136,6 @@ Editing is cancelled for the first two records. await Task.Delay(1000); //simulate actual long running async operation } - // the following three methods mimic an actual data service that handles the actual data source - // you can see about implement error and exception handling, determining suitable return types as per your needs - // an example is available here: https://github.com/telerik/blazor-ui/tree/master/grid/remote-validation - - async Task ServiceMimicInsert(SampleData itemToInsert) - { - await Task.Delay(2000); // simulate actual long running async operation - // in this example, we just populate the fields, you project may use - // something else or generate the updated item differently, we use "new" here - SampleData updatedItem = new SampleData() - { - // the service assigns an ID, in this sample we use only the view-model data for simplicity, - // you should use the actual data and set the properties as necessary (e.g., generate nested fields data and so on) - ID = MyData.Count + 1, - Name = itemToInsert.Name - }; - return await Task.FromResult(updatedItem); - } - - async Task ServiceMimicUpdate(SampleData itemToUpdate) - { - await Task.Delay(2000); // simulate actual long running async operation - // in this example, we just populate the fields, you project may use - // something else or generate the updated item differently - SampleData updatedItem = new SampleData() - { - ID = itemToUpdate.ID, - Name = itemToUpdate.Name - }; - return await Task.FromResult(updatedItem); - } - - async Task ServiceMimicDelete(SampleData itemToDelete) - { - await Task.Delay(2000); // simulate actual long running async operation - return await Task.FromResult(true);//always successful - } - // this method and field just display what happened for visual cues in this example MarkupString logger; @@ -204,17 +160,65 @@ Editing is cancelled for the first two records. List MyData { get; set; } - protected override void OnInitialized() + async Task GetGridData() + { + MyData = await MyService.Read(); + } + + protected override async Task OnInitializedAsync() + { + await GetGridData(); + } + + + // the following static class mimics an actual data service that handles the actual data source + // replace it with your actual service through the DI, this only mimics how the API will look like and works for this standalone page + public static class MyService { - MyData = new List(); + private static List _data { get; set; } = new List(); - for (int i = 0; i < 50; i++) + public static async Task Create(SampleData itemToInsert) { - MyData.Add(new SampleData() + await Task.Delay(1000); // simulate actual long running async operation + + _data.Insert(0, itemToInsert); + } + + public static async Task> Read() + { + await Task.Delay(1000); // simulate actual long running async operation + + if (_data.Count < 1) { - ID = i, - Name = "Name " + i.ToString() - }); + for (int i = 1; i < 50; i++) + { + _data.Add(new SampleData() + { + ID = i, + Name = "Name " + i.ToString() + }); + } + } + + return await Task.FromResult(_data); + } + + public static async Task Update(SampleData itemToUpdate) + { + await Task.Delay(1000); // simulate actual long running async operation + + var index = _data.FindIndex(i => i.ID == itemToUpdate.ID); + if (index != -1) + { + _data[index] = itemToUpdate; + } + } + + public static async Task Delete(SampleData itemToDelete) + { + await Task.Delay(1000); // simulate actual long running async operation + + _data.Remove(itemToDelete); } } } From b715484e462ae5dd8a6261cbf54830ab8a9f8b4e Mon Sep 17 00:00:00 2001 From: Marin Bratanov Date: Sat, 7 Nov 2020 16:45:54 +0200 Subject: [PATCH 02/22] chore(grid): inline editing better crud mimic --- components/grid/editing/inline.md | 109 +++++++++++++++--------------- 1 file changed, 53 insertions(+), 56 deletions(-) diff --git a/components/grid/editing/inline.md b/components/grid/editing/inline.md index 1e92589bd0..0b21ecc9b2 100644 --- a/components/grid/editing/inline.md +++ b/components/grid/editing/inline.md @@ -62,14 +62,10 @@ Use the command buttons to control the CUD operations. SampleData item = (SampleData)args.Item; // perform actual data source operations here through your service - SampleData updatedItem = await ServiceMimicUpdate(item); + await MyService.Update(item); - // update the local view-model data - var index = MyData.FindIndex(i => i.ID == updatedItem.ID); - if (index != -1) - { - MyData[index] = updatedItem; - } + // update the local view-model data with the service data + await GetGridData(); Console.WriteLine("Update event is fired."); } @@ -79,13 +75,10 @@ Use the command buttons to control the CUD operations. SampleData item = (SampleData)args.Item; // perform actual data source operation here through your service - bool isDeleted = await ServiceMimicDelete(item); + await MyService.Delete(item); - if (isDeleted) - { - // update the local view-model data - MyData.Remove(item); - } + // update the local view-model data with the service data + await GetGridData(); Console.WriteLine("Delete event is fired."); } @@ -95,10 +88,10 @@ Use the command buttons to control the CUD operations. SampleData item = (SampleData)args.Item; // perform actual data source operation here through your service - SampleData insertedItem = await ServiceMimicInsert(item); + await MyService.Create(item); - // update the local view-model data - MyData.Insert(0, insertedItem); + // update the local view-model data with the service data + await GetGridData(); Console.WriteLine("Create event is fired."); } @@ -112,62 +105,66 @@ Use the command buttons to control the CUD operations. Console.WriteLine("Cancel event is fired."); } - // the following three methods mimic an actual data service that handles the actual data source - // you can see about implement error and exception handling, determining suitable return types as per your needs - // an example is available here: https://github.com/telerik/blazor-ui/tree/master/grid/remote-validation - async Task ServiceMimicInsert(SampleData itemToInsert) + // in a real case, keep the models in dedicated locations, this is just an easy to copy and see example + public class SampleData { - // in this example, we just populate the fields, you project may use - // something else or generate the inserted item differently - SampleData updatedItem = new SampleData() - { - // the service assigns an ID, in this sample we use only the view-model data for simplicity, - // you should use the actual data and set the properties as necessary (e.g., generate nested fields data and so on) - ID = MyData.Count + 1, - Name = itemToInsert.Name - }; - return await Task.FromResult(updatedItem); + public int ID { get; set; } + public string Name { get; set; } } - async Task ServiceMimicUpdate(SampleData itemToUpdate) + public List MyData { get; set; } + + async Task GetGridData() { - // in this example, we just populate the fields, you project may use - // something else or generate the updated item differently - SampleData updatedItem = new SampleData() - { - ID = itemToUpdate.ID, - Name = itemToUpdate.Name - }; - return await Task.FromResult(updatedItem); + MyData = await MyService.Read(); } - async Task ServiceMimicDelete(SampleData itemToDelete) + protected override async Task OnInitializedAsync() { - return await Task.FromResult(true);//always successful + await GetGridData(); } - - // in a real case, keep the models in dedicated locations, this is just an easy to copy and see example - public class SampleData + // the following static class mimics an actual data service that handles the actual data source + // replace it with your actual service through the DI, this only mimics how the API can look like and works for this standalone page + public static class MyService { - public int ID { get; set; } - public string Name { get; set; } - } + private static List _data { get; set; } = new List(); - public List MyData { get; set; } + public static async Task Create(SampleData itemToInsert) + { + _data.Insert(0, itemToInsert); + } - protected override void OnInitialized() - { - MyData = new List(); + public static async Task> Read() + { + if (_data.Count < 1) + { + for (int i = 1; i < 50; i++) + { + _data.Add(new SampleData() + { + ID = i, + Name = "Name " + i.ToString() + }); + } + } + + return await Task.FromResult(_data); + } - for (int i = 1; i < 50; i++) + public static async Task Update(SampleData itemToUpdate) { - MyData.Add(new SampleData() + var index = _data.FindIndex(i => i.ID == itemToUpdate.ID); + if (index != -1) { - ID = i, - Name = "Name " + i.ToString() - }); + _data[index] = itemToUpdate; + } + } + + public static async Task Delete(SampleData itemToDelete) + { + _data.Remove(itemToDelete); } } } From f0d76e5f6f0f730b7f2fce1c719dd867a05473f6 Mon Sep 17 00:00:00 2001 From: Marin Bratanov Date: Sat, 7 Nov 2020 16:48:44 +0200 Subject: [PATCH 03/22] chore(grid): popup editing better crud mimic --- components/grid/editing/popup.md | 114 +++++++++++++++---------------- 1 file changed, 55 insertions(+), 59 deletions(-) diff --git a/components/grid/editing/popup.md b/components/grid/editing/popup.md index c06ffcc1cb..432482201e 100644 --- a/components/grid/editing/popup.md +++ b/components/grid/editing/popup.md @@ -65,14 +65,10 @@ The PopUp editing mode supports [validation]({%slug common-features/input-valida SampleData item = (SampleData)args.Item; // perform actual data source operations here through your service - SampleData updatedItem = await ServiceMimicUpdate(item); + await MyService.Update(item); - // update the local view-model data - var index = MyData.FindIndex(i => i.ID == updatedItem.ID); - if (index != -1) - { - MyData[index] = updatedItem; - } + // update the local view-model data with the service data + await GetGridData(); Console.WriteLine("Update event is fired."); } @@ -82,13 +78,10 @@ The PopUp editing mode supports [validation]({%slug common-features/input-valida SampleData item = (SampleData)args.Item; // perform actual data source operation here through your service - bool isDeleted = await ServiceMimicDelete(item); + await MyService.Delete(item); - if (isDeleted) - { - // update the local view-model data - MyData.Remove(item); - } + // update the local view-model data with the service data + await GetGridData(); Console.WriteLine("Delete event is fired."); } @@ -98,10 +91,10 @@ The PopUp editing mode supports [validation]({%slug common-features/input-valida SampleData item = (SampleData)args.Item; // perform actual data source operation here through your service - SampleData insertedItem = await ServiceMimicInsert(item); + await MyService.Create(item); - // update the local view-model data - MyData.Insert(0, insertedItem); + // update the local view-model data with the service data + await GetGridData(); Console.WriteLine("Create event is fired."); } @@ -115,42 +108,6 @@ The PopUp editing mode supports [validation]({%slug common-features/input-valida Console.WriteLine("Cancel event is fired."); } - // the following three methods mimic an actual data service that handles the actual data source - // you can see about implement error and exception handling, determining suitable return types as per your needs - // an example is available here: https://github.com/telerik/blazor-ui/tree/master/grid/remote-validation - - async Task ServiceMimicInsert(SampleData itemToInsert) - { - // in this example, we just populate the fields, you project may use - // something else or generate the inserted item differently - SampleData updatedItem = new SampleData() - { - // the service assigns an ID, in this sample we use only the view-model data for simplicity, - // you should use the actual data and set the properties as necessary (e.g., generate nested fields data and so on) - ID = MyData.Count + 1, - Name = itemToInsert.Name - }; - return await Task.FromResult(updatedItem); - } - - async Task ServiceMimicUpdate(SampleData itemToUpdate) - { - // in this example, we just populate the fields, you project may use - // something else or generate the updated item differently - SampleData updatedItem = new SampleData() - { - ID = itemToUpdate.ID, - Name = itemToUpdate.Name - }; - return await Task.FromResult(updatedItem); - } - - async Task ServiceMimicDelete(SampleData itemToDelete) - { - return await Task.FromResult(true);//always successful - } - - // in a real case, keep the models in dedicated locations, this is just an easy to copy and see example public class SampleData { @@ -162,17 +119,56 @@ The PopUp editing mode supports [validation]({%slug common-features/input-valida public List MyData { get; set; } - protected override void OnInitialized() + async Task GetGridData() { - MyData = new List(); + MyData = await MyService.Read(); + } + + protected override async Task OnInitializedAsync() + { + await GetGridData(); + } + + // the following static class mimics an actual data service that handles the actual data source + // replace it with your actual service through the DI, this only mimics how the API can look like and works for this standalone page + public static class MyService + { + private static List _data { get; set; } = new List(); + + public static async Task Create(SampleData itemToInsert) + { + _data.Insert(0, itemToInsert); + } + + public static async Task> Read() + { + if (_data.Count < 1) + { + for (int i = 1; i < 50; i++) + { + _data.Add(new SampleData() + { + ID = i, + Name = "Name " + i.ToString() + }); + } + } + + return await Task.FromResult(_data); + } - for (int i = 1; i < 50; i++) + public static async Task Update(SampleData itemToUpdate) { - MyData.Add(new SampleData() + var index = _data.FindIndex(i => i.ID == itemToUpdate.ID); + if (index != -1) { - ID = i, - Name = "Name " + i.ToString() - }); + _data[index] = itemToUpdate; + } + } + + public static async Task Delete(SampleData itemToDelete) + { + _data.Remove(itemToDelete); } } } From 093c9219a2cb5c50c36b49707d3d89e7a65d95a1 Mon Sep 17 00:00:00 2001 From: Marin Bratanov Date: Sat, 7 Nov 2020 16:53:59 +0200 Subject: [PATCH 04/22] chore(grid): incell editing better crud mimic --- components/grid/editing/incell.md | 110 ++++++++++++++---------------- 1 file changed, 53 insertions(+), 57 deletions(-) diff --git a/components/grid/editing/incell.md b/components/grid/editing/incell.md index b40f5a1185..b48a3a4bc7 100644 --- a/components/grid/editing/incell.md +++ b/components/grid/editing/incell.md @@ -66,14 +66,10 @@ Click a cell, edit it and click outside of the cell to see the change. SampleData item = (SampleData)args.Item; // perform actual data source operations here through your service - SampleData updatedItem = await ServiceMimicUpdate(item); + await MyService.Update(item); - // update the local view-model data - var index = MyData.FindIndex(i => i.ID == updatedItem.ID); - if (index != -1) - { - MyData[index] = updatedItem; - } + // update the local view-model data with the service data + await GetGridData(); Console.WriteLine("Update event is fired."); } @@ -83,13 +79,10 @@ Click a cell, edit it and click outside of the cell to see the change. SampleData item = (SampleData)args.Item; // perform actual data source operation here through your service - bool isDeleted = await ServiceMimicDelete(item); + await MyService.Delete(item); - if (isDeleted) - { - // update the local view-model data - MyData.Remove(item); - } + // update the local view-model data with the service data + await GetGridData(); Console.WriteLine("Delete event is fired."); } @@ -99,71 +92,74 @@ Click a cell, edit it and click outside of the cell to see the change. SampleData item = (SampleData)args.Item; // perform actual data source operation here through your service - SampleData insertedItem = await ServiceMimicInsert(item); + await MyService.Create(item); - // update the local view-model data - MyData.Insert(0, insertedItem); + // update the local view-model data with the service data + await GetGridData(); Console.WriteLine("Create event is fired."); } - // the following three methods mimic an actual data service that handles the actual data source - // you can see about implement error and exception handling, determining suitable return types as per your needs - // an example is available here: https://github.com/telerik/blazor-ui/tree/master/grid/remote-validation - - async Task ServiceMimicInsert(SampleData itemToInsert) + // in a real case, keep the models in dedicated locations, this is just an easy to copy and see example + public class SampleData { - // in this example, we just populate the fields, you project may use - // something else or generate the updated item differently - SampleData updatedItem = new SampleData() - { - // the service assigns an ID, in this sample we use only the view-model data for simplicity, - // you should use the actual data and set the properties as necessary (e.g., generate nested fields data and so on) - ID = MyData.Count + 1, - Name = itemToInsert.Name - }; - return await Task.FromResult(updatedItem); + public int ID { get; set; } + public string Name { get; set; } } - async Task ServiceMimicUpdate(SampleData itemToUpdate) + public List MyData { get; set; } + + async Task GetGridData() { - // in this example, we just populate the fields, you project may use - // something else or generate the updated item differently - SampleData updatedItem = new SampleData() - { - ID = itemToUpdate.ID, - Name = itemToUpdate.Name - }; - return await Task.FromResult(updatedItem); + MyData = await MyService.Read(); } - async Task ServiceMimicDelete(SampleData itemToDelete) + protected override async Task OnInitializedAsync() { - return await Task.FromResult(true);//always successful + await GetGridData(); } - - // in a real case, keep the models in dedicated locations, this is just an easy to copy and see example - public class SampleData + // the following static class mimics an actual data service that handles the actual data source + // replace it with your actual service through the DI, this only mimics how the API can look like and works for this standalone page + public static class MyService { - public int ID { get; set; } - public string Name { get; set; } - } + private static List _data { get; set; } = new List(); - public List MyData { get; set; } + public static async Task Create(SampleData itemToInsert) + { + _data.Insert(0, itemToInsert); + } - protected override void OnInitialized() - { - MyData = new List(); + public static async Task> Read() + { + if (_data.Count < 1) + { + for (int i = 1; i < 50; i++) + { + _data.Add(new SampleData() + { + ID = i, + Name = "Name " + i.ToString() + }); + } + } + + return await Task.FromResult(_data); + } - for (int i = 1; i < 50; i++) + public static async Task Update(SampleData itemToUpdate) { - MyData.Add(new SampleData() + var index = _data.FindIndex(i => i.ID == itemToUpdate.ID); + if (index != -1) { - ID = i, - Name = "Name " + i.ToString() - }); + _data[index] = itemToUpdate; + } + } + + public static async Task Delete(SampleData itemToDelete) + { + _data.Remove(itemToDelete); } } } From 8021b688adaecf173bf4f8d0d6637f12bd761a38 Mon Sep 17 00:00:00 2001 From: Marin Bratanov Date: Sat, 7 Nov 2020 17:02:12 +0200 Subject: [PATCH 05/22] chore(grid): autogetn columns editing better crud mimic --- components/grid/columns/auto-generated.md | 170 ++++++++++------------ 1 file changed, 76 insertions(+), 94 deletions(-) diff --git a/components/grid/columns/auto-generated.md b/components/grid/columns/auto-generated.md index 3f4be1449e..5231922f1f 100644 --- a/components/grid/columns/auto-generated.md +++ b/components/grid/columns/auto-generated.md @@ -199,33 +199,30 @@ This example shows how to: @using System.Collections.ObjectModel
- - - - - - Add - - - - - - - Edit - Delete - - - - + + + + Add + + + + + + + Edit + Delete + + + @if (SelectedUsers.Any()) @@ -253,7 +250,6 @@ This example shows how to: public List GridData { get; set; } public IEnumerable SelectedUsers { get; set; } = new ObservableCollection(); public int PageSize { get; set; } = 3; - GridDataModel DataModel = new GridDataModel(); #region data model with annotations public class GridDataModel @@ -290,14 +286,10 @@ This example shows how to: GridDataModel item = (GridDataModel)args.Item; // perform actual data source operations here through your service - GridDataModel updatedItem = await ServiceMimicUpdate(item); + await MyService.Update(item); - // update the local view-model data - var index = GridData.FindIndex(i => i.Id == updatedItem.Id); - if (index != -1) - { - GridData[index] = updatedItem; - } + // update the local view-model data with the service data + await GetGridData(); } async Task DeleteItem(GridCommandEventArgs args) @@ -305,13 +297,10 @@ This example shows how to: GridDataModel item = (GridDataModel)args.Item; // perform actual data source operation here through your service - bool isDeleted = await ServiceMimicDelete(item); + await MyService.Delete(item); - if (isDeleted) - { - // update the local view-model data - GridData.Remove(item); - } + // update the local view-model data with the service data + await GetGridData(); } async Task CreateItem(GridCommandEventArgs args) @@ -319,77 +308,70 @@ This example shows how to: GridDataModel item = (GridDataModel)args.Item; // perform actual data source operation here through your service - GridDataModel insertedItem = await ServiceMimicInsert(item); + await MyService.Create(item); - // update the local view-model data - GridData.Insert(0, insertedItem); + // update the local view-model data with the service data + await GetGridData(); } + #endregion - // the following three methods mimic an actual data service that handles the actual data source - // you can see about implement error and exception handling, determining suitable return types as per your needs - // an example is available here: https://github.com/telerik/blazor-ui/tree/master/grid/remote-validation - - async Task ServiceMimicInsert(GridDataModel itemToInsert) + async Task GetGridData() { - // in this example, we just populate the fields, you project may use - // something else or generate the updated item differently - GridDataModel updatedItem = new GridDataModel() - { - // the service assigns an ID, in this sample we use only the view-model data for simplicity, - // you should use the actual data and set the properties as necessary (e.g., generate nested fields data and so on) - Id = GridData.Count + 1, - Username = itemToInsert.Username, - EmailAddress = itemToInsert.EmailAddress, - RegistrationDate = itemToInsert.RegistrationDate, - LocalTime = itemToInsert.LocalTime, - BoughtBooks = itemToInsert.BoughtBooks - }; - return await Task.FromResult(updatedItem); + GridData = await MyService.Read(); } - async Task ServiceMimicUpdate(GridDataModel itemToUpdate) + protected override async Task OnInitializedAsync() { - // in this example, we just populate the fields, you project may use - // something else or generate the updated item differently - GridDataModel updatedItem = new GridDataModel() - { - Id = itemToUpdate.Id, - Username = itemToUpdate.Username, - EmailAddress = itemToUpdate.EmailAddress, - RegistrationDate = itemToUpdate.RegistrationDate, - LocalTime = itemToUpdate.LocalTime, - BoughtBooks = itemToUpdate.BoughtBooks - }; - return await Task.FromResult(updatedItem); + await GetGridData(); } - async Task ServiceMimicDelete(GridDataModel itemToDelete) + // the following static class mimics an actual data service that handles the actual data source + // replace it with your actual service through the DI, this only mimics how the API can look like and works for this standalone page + public static class MyService { - return await Task.FromResult(true);//always successful - } + private static List _data { get; set; } = new List(); - #endregion + public static async Task Create(GridDataModel itemToInsert) + { + itemToInsert.Id = _data.Count + 1; + _data.Insert(0, itemToInsert); + } - #region data generation - protected override void OnInitialized() - { - GridData = new List(); - for (int i = 0; i < 45; i++) + public static async Task> Read() + { + if (_data.Count < 1) + { + for (int i = 0; i < 45; i++) + { + _data.Add(new GridDataModel() + { + Id = i, + Username = $"Username {i}", + EmailAddress = $"user{i}@mail.com", + RegistrationDate = DateTime.Now.AddDays(-2), + LocalTime = DateTime.Now + }); + } + } + + return await Task.FromResult(_data); + } + + public static async Task Update(GridDataModel itemToUpdate) { - GridData.Add(new GridDataModel() + var index = _data.FindIndex(i => i.Id == itemToUpdate.Id); + if (index != -1) { - Id = i, - Username = $"Username {i}", - EmailAddress = $"user{i}@mail.com", - RegistrationDate = DateTime.Now.AddDays(-2), - LocalTime = DateTime.Now - }); + _data[index] = itemToUpdate; + } } - base.OnInitialized(); + public static async Task Delete(GridDataModel itemToDelete) + { + _data.Remove(itemToDelete); + } } - #endregion } ```` >caption The result from the code snippet above From e4aa22fa18c28ef4c351ab35a4dfd7ebcac45755 Mon Sep 17 00:00:00 2001 From: Marin Bratanov Date: Sat, 7 Nov 2020 17:03:54 +0200 Subject: [PATCH 06/22] chore(grid): insertion to generate ID --- components/grid/editing/incell.md | 1 + components/grid/editing/inline.md | 1 + components/grid/editing/overview.md | 1 + components/grid/editing/popup.md | 1 + 4 files changed, 4 insertions(+) diff --git a/components/grid/editing/incell.md b/components/grid/editing/incell.md index b48a3a4bc7..7e5bbc8cc8 100644 --- a/components/grid/editing/incell.md +++ b/components/grid/editing/incell.md @@ -128,6 +128,7 @@ Click a cell, edit it and click outside of the cell to see the change. public static async Task Create(SampleData itemToInsert) { + itemToInsert.Id = _data.Count + 1; _data.Insert(0, itemToInsert); } diff --git a/components/grid/editing/inline.md b/components/grid/editing/inline.md index 0b21ecc9b2..a1d46244a1 100644 --- a/components/grid/editing/inline.md +++ b/components/grid/editing/inline.md @@ -133,6 +133,7 @@ Use the command buttons to control the CUD operations. public static async Task Create(SampleData itemToInsert) { + itemToInsert.Id = _data.Count + 1; _data.Insert(0, itemToInsert); } diff --git a/components/grid/editing/overview.md b/components/grid/editing/overview.md index c3030379a4..b9b9ea37da 100644 --- a/components/grid/editing/overview.md +++ b/components/grid/editing/overview.md @@ -181,6 +181,7 @@ Editing is cancelled for the first two records. { await Task.Delay(1000); // simulate actual long running async operation + itemToInsert.Id = _data.Count + 1; _data.Insert(0, itemToInsert); } diff --git a/components/grid/editing/popup.md b/components/grid/editing/popup.md index 432482201e..3a7a068eeb 100644 --- a/components/grid/editing/popup.md +++ b/components/grid/editing/popup.md @@ -137,6 +137,7 @@ The PopUp editing mode supports [validation]({%slug common-features/input-valida public static async Task Create(SampleData itemToInsert) { + itemToInsert.Id = _data.Count + 1; _data.Insert(0, itemToInsert); } From b8975cbf469e666515aea63259a77d320790979d Mon Sep 17 00:00:00 2001 From: Marin Bratanov Date: Sat, 7 Nov 2020 17:08:19 +0200 Subject: [PATCH 07/22] chore(grid): command column editing better crud mimic --- components/grid/columns/command.md | 72 +++++++++++++++++++----------- 1 file changed, 46 insertions(+), 26 deletions(-) diff --git a/components/grid/columns/command.md b/components/grid/columns/command.md index f1504318ae..77540934b8 100644 --- a/components/grid/columns/command.md +++ b/components/grid/columns/command.md @@ -99,12 +99,7 @@ The following code example demonstrates declarations and handling. public DateTime HireDate { get; set; } } - List GridData = Enumerable.Range(1, 50).Select(x => new SampleData - { - ID = x, - Name = "name " + x, - HireDate = DateTime.Now.AddDays(-x) - }).ToList(); + List GridData { get; set; } // sample custom commands handling @@ -129,36 +124,61 @@ The following code example demonstrates declarations and handling. } - // sample CUD operations + // sample CRUD operations private async Task MyUpdateHandler(GridCommandEventArgs args) { SampleData theUpdatedItem = args.Item as SampleData; - SampleData updatedItem = await ServiceMimicUpdate(theUpdatedItem); - // update the local view-model data - var index = GridData.FindIndex(i => i.ID == updatedItem.ID); - if (index != -1) - { - GridData[index] = updatedItem; - } + // perform actual data source operations here through your service + await MyService.Update(theUpdatedItem); + + // update the local view-model data with the service data + await GetGridData(); + } + + async Task GetGridData() + { + GridData = await MyService.Read(); } - // the following method mimics an actual data service that handles the actual data source - // you can see about implement error and exception handling, determining suitable return types as per your needs - // an example is available here: https://github.com/telerik/blazor-ui/tree/master/grid/remote-validation + protected override async Task OnInitializedAsync() + { + await GetGridData(); + } - async Task ServiceMimicUpdate(SampleData itemToUpdate) + // the following static class mimics an actual data service that handles the actual data source + // replace it with your actual service through the DI, this only mimics how the API can look like and works for this standalone page + public static class MyService { - // in this example, we just populate the fields, you project may use - // something else or generate the updated item differently - SampleData updatedItem = new SampleData() + private static List _data { get; set; } = new List(); + + public static async Task> Read() { - ID = itemToUpdate.ID, - HireDate = itemToUpdate.HireDate, - Name = itemToUpdate.Name - }; - return await Task.FromResult(updatedItem); + if (_data.Count < 1) + { + for (int i = 1; i < 50; i++) + { + _data.Add(new SampleData() + { + ID = i, + Name = "name " + i, + HireDate = DateTime.Now.AddDays(-i) + }); + } + } + + return await Task.FromResult(_data); + } + + public static async Task Update(SampleData itemToUpdate) + { + var index = _data.FindIndex(i => i.ID == itemToUpdate.ID); + if (index != -1) + { + _data[index] = itemToUpdate; + } + } } } ```` From a47dc45a193b2176a65186cb8107a46f920e5766 Mon Sep 17 00:00:00 2001 From: Marin Bratanov Date: Sat, 7 Nov 2020 17:17:14 +0200 Subject: [PATCH 08/22] chore(grid): state dics editing better crud mimic --- components/grid/state.md | 259 ++++++++++++++++++++------------------- 1 file changed, 132 insertions(+), 127 deletions(-) diff --git a/components/grid/state.md b/components/grid/state.md index d33937b072..2f98e5f411 100644 --- a/components/grid/state.md +++ b/components/grid/state.md @@ -119,7 +119,6 @@ The following example shows one way you can store the grid state - through a cus ````Component @inject LocalStorage LocalStorage @inject IJSRuntime JsInterop - Change something in the grid (like sort, filter, select, page, resize columns, etc.), then reload the page to see the grid state fetched from the browser local storage.
@@ -169,6 +168,9 @@ Change something in the grid (like sort, filter, select, page, resize columns, e // Load and Save the state through the grid events string UniqueStorageKey = "SampleGridStateStorageThatShouldBeUnique"; + TelerikGrid Grid { get; set; } + IEnumerable SelectedItems { get; set; } = Enumerable.Empty(); + List GridData { get; set; } async Task OnStateInitHandler(GridStateEventArgs args) { @@ -193,7 +195,6 @@ Change something in the grid (like sort, filter, select, page, resize columns, e await LocalStorage.SetItem(UniqueStorageKey, args.GridState); } - TelerikGrid Grid { get; set; } async Task ResetState() { // clean up the storage @@ -214,14 +215,12 @@ Change something in the grid (like sort, filter, select, page, resize columns, e SampleData item = (SampleData)args.Item; // perform actual data source operations here through your service - SampleData updatedItem = await ServiceMimicUpdate(item); + await MyService.Update(item); - // update the local view-model data - var index = GridData.FindIndex(i => i.Id == updatedItem.Id); - if (index != -1) - { - GridData[index] = updatedItem; - } + // update the local view-model data with the service data + await GetGridData(); + + Console.WriteLine("Update event is fired."); } async Task DeleteItem(GridCommandEventArgs args) @@ -229,13 +228,12 @@ Change something in the grid (like sort, filter, select, page, resize columns, e SampleData item = (SampleData)args.Item; // perform actual data source operation here through your service - bool isDeleted = await ServiceMimicDelete(item); + await MyService.Delete(item); - if (isDeleted) - { - // update the local view-model data - GridData.Remove(item); - } + // update the local view-model data with the service data + await GetGridData(); + + Console.WriteLine("Delete event is fired."); } async Task CreateItem(GridCommandEventArgs args) @@ -243,79 +241,89 @@ Change something in the grid (like sort, filter, select, page, resize columns, e SampleData item = (SampleData)args.Item; // perform actual data source operation here through your service - SampleData insertedItem = await ServiceMimicInsert(item); + await MyService.Create(item); - // update the local view-model data - GridData.Insert(0, insertedItem); - } + // update the local view-model data with the service data + await GetGridData(); + Console.WriteLine("Create event is fired."); + } - // the following three methods mimic an actual data service that handles the actual data source - // you can see about implement error and exception handling, determining suitable return types as per your needs - // an example is available here: https://github.com/telerik/blazor-ui/tree/master/grid/remote-validation + // Note the Equals override for restoring selection and editing - async Task ServiceMimicInsert(SampleData itemToInsert) + public class SampleData { - // in this example, we just populate the fields, you project may use - // something else or generate the updated item differently - SampleData updatedItem = new SampleData() + public int Id { get; set; } + public string Name { get; set; } + public string Team { get; set; } + + // example of comparing stored items (from editing or selection) + // with items from the current data source - IDs are used instead of the default references + public override bool Equals(object obj) { - // the service assigns an ID, in this sample we use only the view-model data for simplicity, - // you should use the actual data and set the properties as necessary (e.g., generate nested fields data and so on) - Id = GridData.Count + 1, - Name = itemToInsert.Name, - Team = itemToInsert.Team - }; - return await Task.FromResult(updatedItem); + if (obj is SampleData) + { + return this.Id == (obj as SampleData).Id; + } + return false; + } } - async Task ServiceMimicUpdate(SampleData itemToUpdate) + async Task GetGridData() { - // in this example, we just populate the fields, you project may use - // something else or generate the updated item differently - SampleData updatedItem = new SampleData() - { - Id = itemToUpdate.Id, - Name = itemToUpdate.Name, - Team = itemToUpdate.Team - }; - return await Task.FromResult(updatedItem); + GridData = await MyService.Read(); } - async Task ServiceMimicDelete(SampleData itemToDelete) + protected override async Task OnInitializedAsync() { - return await Task.FromResult(true);//always successful + await GetGridData(); } - // Sample data follows below + // the following static class mimics an actual data service that handles the actual data source + // replace it with your actual service through the DI, this only mimics how the API can look like and works for this standalone page + public static class MyService + { + private static List _data { get; set; } = new List(); - public IEnumerable SelectedItems { get; set; } = Enumerable.Empty(); + public static async Task Create(SampleData itemToInsert) + { + itemToInsert.Id = _data.Count + 1; + _data.Insert(0, itemToInsert); + } - public List GridData { get; set; } = Enumerable.Range(1, 30).Select(x => new SampleData - { - Id = x, - Name = "name " + x, - Team = "team " + x % 5 - }).ToList(); + public static async Task> Read() + { + if (_data.Count < 1) + { + for (int i = 1; i < 50; i++) + { + _data.Add(new SampleData() + { + Id = i, + Name = "name " + i, + Team = "team " + i % 5 + }); + } + } - public class SampleData - { - public int Id { get; set; } - public string Name { get; set; } - public string Team { get; set; } + return await Task.FromResult(_data); + } - // example of comparing stored items (from editing or selection) - // with items from the current data source - IDs are used instead of the default references - public override bool Equals(object obj) + public static async Task Update(SampleData itemToUpdate) { - if (obj is SampleData) + var index = _data.FindIndex(i => i.Id == itemToUpdate.Id); + if (index != -1) { - return this.Id == (obj as SampleData).Id; + _data[index] = itemToUpdate; } - return false; + } + + public static async Task Delete(SampleData itemToDelete) + { + _data.Remove(itemToDelete); } } -} +}**** ```` ````Service using Microsoft.JSInterop; @@ -532,7 +540,7 @@ In addition to that, you can also use the `EditItem`, `OriginalEditItem` and `In ````CSHTML @* This example shows how to make the grid edit a certain item or start insert operation through your own code, without requiring the user to click the Command buttons. - The buttons that initiate these operations can be anywhere on the page, inlcuding inside the grid. + The buttons that initiate these operations can be anywhere on the page, including inside the grid. Note the model constructors and static method that show how to get a new instance for the edit item *@ @@ -567,8 +575,8 @@ In addition to that, you can also use the `EditItem`, `OriginalEditItem` and `In // you can predefine values here as well (not mandatory) currState.InsertedItem = new SampleData() { Name = "some predefined value" }; await GridRef.SetState(currState); - - // note: possible only for Inline and Popup edit modes, with InCell there is never an inserted item, only edited items + + // note: possible only for Inline and Popup edit modes, with InCell there is never an inserted item, only edited items } async Task EditItemFour() @@ -594,14 +602,10 @@ In addition to that, you can also use the `EditItem`, `OriginalEditItem` and `In SampleData item = (SampleData)args.Item; // perform actual data source operations here through your service - SampleData updatedItem = await ServiceMimicUpdate(item); + await MyService.Update(item); - // update the local view-model data - var index = MyData.FindIndex(i => i.ID == updatedItem.ID); - if (index != -1) - { - MyData[index] = updatedItem; - } + // update the local view-model data with the service data + await GetGridData(); } async Task DeleteHandler(GridCommandEventArgs args) @@ -609,13 +613,10 @@ In addition to that, you can also use the `EditItem`, `OriginalEditItem` and `In SampleData item = (SampleData)args.Item; // perform actual data source operation here through your service - bool isDeleted = await ServiceMimicDelete(item); + await MyService.Delete(item); - if (isDeleted) - { - // update the local view-model data - MyData.Remove(item); - } + // update the local view-model data with the service data + await GetGridData(); } async Task CreateHandler(GridCommandEventArgs args) @@ -623,46 +624,10 @@ In addition to that, you can also use the `EditItem`, `OriginalEditItem` and `In SampleData item = (SampleData)args.Item; // perform actual data source operation here through your service - SampleData insertedItem = await ServiceMimicInsert(item); + await MyService.Create(item); - // update the local view-model data - MyData.Insert(0, insertedItem); - } - - - // the following three methods mimic an actual data service that handles the actual data source - // you can see about implement error and exception handling, determining suitable return types as per your needs - // an example is available here: https://github.com/telerik/blazor-ui/tree/master/grid/remote-validation - - async Task ServiceMimicInsert(SampleData itemToInsert) - { - // in this example, we just populate the fields, you project may use - // something else or generate the updated item differently - SampleData updatedItem = new SampleData() - { - // the service assigns an ID, in this sample we use only the view-model data for simplicity, - // you should use the actual data and set the properties as necessary (e.g., generate nested fields data and so on) - ID = MyData.Count + 1, - Name = itemToInsert.Name - }; - return await Task.FromResult(updatedItem); - } - - async Task ServiceMimicUpdate(SampleData itemToUpdate) - { - // in this example, we just populate the fields, you project may use - // something else or generate the updated item differently - SampleData updatedItem = new SampleData() - { - ID = itemToUpdate.ID, - Name = itemToUpdate.Name - }; - return await Task.FromResult(updatedItem); - } - - async Task ServiceMimicDelete(SampleData itemToDelete) - { - return await Task.FromResult(true);//always successful + // update the local view-model data with the service data + await GetGridData(); } // Sample class definition - note the constructors, overrides and comments @@ -708,17 +673,57 @@ In addition to that, you can also use the `EditItem`, `OriginalEditItem` and `In public List MyData { get; set; } - protected override void OnInitialized() + async Task GetGridData() + { + MyData = await MyService.Read(); + } + + protected override async Task OnInitializedAsync() + { + await GetGridData(); + } + + // the following static class mimics an actual data service that handles the actual data source + // replace it with your actual service through the DI, this only mimics how the API can look like and works for this standalone page + public static class MyService { - MyData = new List(); + private static List _data { get; set; } = new List(); + + public static async Task Create(SampleData itemToInsert) + { + itemToInsert.ID = _data.Count + 1; + _data.Insert(0, itemToInsert); + } - for (int i = 0; i < 50; i++) + public static async Task> Read() { - MyData.Add(new SampleData() + if (_data.Count < 1) { - ID = i, - Name = "Name " + i.ToString() - }); + for (int i = 1; i < 50; i++) + { + _data.Add(new SampleData() + { + ID = i, + Name = "Name " + i.ToString() + }); + } + } + + return await Task.FromResult(_data); + } + + public static async Task Update(SampleData itemToUpdate) + { + var index = _data.FindIndex(i => i.ID == itemToUpdate.ID); + if (index != -1) + { + _data[index] = itemToUpdate; + } + } + + public static async Task Delete(SampleData itemToDelete) + { + _data.Remove(itemToDelete); } } } From 23e056f6379249aee42cb0390e0bc7fc0f56b061 Mon Sep 17 00:00:00 2001 From: Marin Bratanov Date: Sat, 7 Nov 2020 17:37:32 +0200 Subject: [PATCH 09/22] chore(grid): editor template better crud mimic --- components/grid/templates/editor.md | 206 +++++++++++++++------------- 1 file changed, 111 insertions(+), 95 deletions(-) diff --git a/components/grid/templates/editor.md b/components/grid/templates/editor.md index 8f2f42d283..101aba643e 100644 --- a/components/grid/templates/editor.md +++ b/components/grid/templates/editor.md @@ -54,66 +54,79 @@ You can find the following examples below: @code { - public SampleData CurrentlyEditedEmployee { get; set; } + List MyData { get; set; } + List Roles { get; set; } + SampleData CurrentlyEditedEmployee { get; set; } public async Task UpdateHandler(GridCommandEventArgs args) { SampleData item = (SampleData)args.Item; // perform actual data source operations here through your service - SampleData updatedItem = await ServiceMimicUpdate(item); + await MyService.Update(item); - // update the local view-model data - var index = MyData.FindIndex(i => i.ID == updatedItem.ID); - if (index != -1) - { - MyData[index] = updatedItem; - } + // update the local view-model data with the service data + await GetGridData(); } - // the following method mimics an actual data service that handles the actual data source - // you can see about implement error and exception handling, determining suitable return types as per your needs - // an example is available here: https://github.com/telerik/blazor-ui/tree/master/grid/remote-validation + //in a real case, keep the models in dedicated locations, this is just an easy to copy and see example + public class SampleData + { + public int ID { get; set; } + public string Name { get; set; } + public string Role { get; set; } + } - async Task ServiceMimicUpdate(SampleData itemToUpdate) + async Task GetGridData() { - // in this example, we just populate the fields, you project may use - // something else or generate the updated item differently - SampleData updatedItem = new SampleData() - { - ID = itemToUpdate.ID, - Name = itemToUpdate.Name, - Role = itemToUpdate.Role - }; - return await Task.FromResult(updatedItem); + MyData = await MyService.Read(); + Roles = await MyService.GetRoles(); + } + + protected override async Task OnInitializedAsync() + { + await GetGridData(); } - protected override void OnInitialized() + // the following static class mimics an actual data service that handles the actual data source + // replace it with your actual service through the DI, this only mimics how the API can look like and works for this standalone page + public static class MyService { - MyData = new List(); + private static List _data { get; set; } = new List(); + private static List Roles = new List { "Manager", "Employee", "Contractor" }; - for (int i = 0; i < 50; i++) + public static async Task> Read() { - MyData.Add(new SampleData() + if (_data.Count < 1) { - ID = i, - Name = "name " + i, - Role = Roles[i % Roles.Count] - }); - } - } + for (int i = 1; i < 50; i++) + { + _data.Add(new SampleData() + { + ID = i, + Name = "Name " + i.ToString(), + Role = Roles[i % Roles.Count] + }); + } + } - //in a real case, keep the models in dedicated locations, this is just an easy to copy and see example - public class SampleData - { - public int ID { get; set; } - public string Name { get; set; } - public string Role { get; set; } - } + return await Task.FromResult(_data); + } - public List MyData { get; set; } + public static async Task> GetRoles() + { + return await Task.FromResult(Roles); + } - public static List Roles = new List { "Manager", "Employee", "Contractor" }; + public static async Task Update(SampleData itemToUpdate) + { + var index = _data.FindIndex(i => i.ID == itemToUpdate.ID); + if (index != -1) + { + _data[index] = itemToUpdate; + } + } + } } ```` @@ -168,81 +181,84 @@ You can find the following examples below: Employee item = (Employee)args.Item; // perform actual data source operations here through your service - Employee updatedItem = await ServiceMimicUpdate(item); + await MyService.Update(item); - // update the local view-model data - var index = MyData.FindIndex(i => i.ID == updatedItem.ID); - if (index != -1) - { - MyData[index] = updatedItem; - } + // update the local view-model data with the service data + await GetGridData(); } - // the following method mimics an actual data service that handles the actual data source - // you can see about implement error and exception handling, determining suitable return types as per your needs - // an example is available here: https://github.com/telerik/blazor-ui/tree/master/grid/remote-validation - - async Task ServiceMimicUpdate(Employee itemToUpdate) + public class Employee { - // in this example, we just populate the fields, you project may use - // something else or generate the updated item differently - Employee updatedItem = new Employee() - { - ID = itemToUpdate.ID, - Name = itemToUpdate.Name, - RoleId = itemToUpdate.RoleId - }; - return await Task.FromResult(updatedItem); + public int ID { get; set; } + public string Name { get; set; } + public int RoleId { get; set; } } - protected override async Task OnInitializedAsync() + public class Role { - Roles = await GetRoles(); - MyData = await GetGridData(); + public int RoleId { get; set; } + public string RoleName { get; set; } } - async Task> GetRoles() + async Task GetGridData() { - var data = new List - { - new Role { RoleId = 1, RoleName = "Manager" }, - new Role { RoleId = 2, RoleName = "Employee" }, - new Role { RoleId = 3, RoleName = "Contractor" }, - }; + MyData = await MyService.Read(); + Roles = await MyService.GetRoles(); + } - return await Task.FromResult(data); + protected override async Task OnInitializedAsync() + { + await GetGridData(); } - async Task> GetGridData() + // the following static class mimics an actual data service that handles the actual data source + // replace it with your actual service through the DI, this only mimics how the API can look like and works for this standalone page + public static class MyService { - var data = new List(); - for (int i = 0; i < 50; i++) + private static List _data { get; set; } = new List(); + private static List Roles = new List { "Manager", "Employee", "Contractor" }; + + public static async Task> Read() { - data.Add(new Employee() + if (_data.Count < 1) { - ID = i, - Name = "name " + i, - RoleId = i % 4 // every one in four is an unknown one that will not be present in the roles list - // and will have an ID of 0 to match the DefaultText of the dropdownlist - // you can perform more complicated checks as necessary in your app and/or in the templates - // and/or in the view-model data to present it with suitable values and avoid exceptions - }); + for (int i = 0; i < 50; i++) + { + _data.Add(new Employee() + { + ID = i, + Name = "name " + i, + RoleId = i % 4 // every one in four is an unknown one that will not be present in the roles list + // and will have an ID of 0 to match the DefaultText of the dropdownlist + // you can perform more complicated checks as necessary in your app and/or in the templates + // and/or in the view-model data to present it with suitable values and avoid exceptions + }); + } + } + + return await Task.FromResult(_data); } - return await Task.FromResult(data); - } + public static async Task> GetRoles() + { + var data = new List + { + new Role { RoleId = 1, RoleName = "Manager" }, + new Role { RoleId = 2, RoleName = "Employee" }, + new Role { RoleId = 3, RoleName = "Contractor" }, + }; - public class Employee - { - public int ID { get; set; } - public string Name { get; set; } - public int RoleId { get; set; } - } + return await Task.FromResult(data); + } - public class Role - { - public int RoleId { get; set; } - public string RoleName { get; set; } + public static async Task Update(Employee itemToUpdate) + { + var index = _data.FindIndex(i => i.ID == itemToUpdate.ID); + if (index != -1) + { + _data[index] = itemToUpdate; + } + } } } ```` From 340242dd0716bfb947060823ae3a8eda87e15140 Mon Sep 17 00:00:00 2001 From: Marin Bratanov Date: Sat, 7 Nov 2020 17:48:55 +0200 Subject: [PATCH 10/22] chore(grid): toolbar better CRUD mimic --- .../grid/images/create-toolbar-button.jpg | Bin 88746 -> 0 bytes .../grid/images/create-toolbar-button.png | Bin 0 -> 28957 bytes components/grid/toolbar.md | 112 ++++++++++-------- 3 files changed, 63 insertions(+), 49 deletions(-) delete mode 100644 components/grid/images/create-toolbar-button.jpg create mode 100644 components/grid/images/create-toolbar-button.png diff --git a/components/grid/images/create-toolbar-button.jpg b/components/grid/images/create-toolbar-button.jpg deleted file mode 100644 index 7f841cd08a40e22a1c571d3586c49ce937d58b3a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 88746 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 z z2p!9pWN$~plSlcylerTc1q1u_$(yJ~qCl342S5TBwkogW@wl6(50yCQ8uYU>G`* zn?RP;5VZ0fNIbyw$;MVQ>65&sZPi5efyF89PDP;jDgk!^@(`aU~s zwM>j|TsfPUu@VMPTgRU6??EVvV!kz@nMLc5Hk8|1oumIc6`vfS@Zyb9lra#W9_VNs z(l6f-Ad6G_S&TV8?$q*GM;=fsQzk>ic3%-JEL;bJSZixI*45XOXZVKv8vRIFf};7} z#f$}5tY2e!kn&`8d`^CXH#`ApcAFuP%;N6`|3A_jM-y^Bl@Y^l)cZz$3lQ;hI~ zJF~G`K^t48$Q=^ue-CRJ?~4_T_ft8d9TXZ9JGjK$m9{) z4=c9}Z~)13A!03Yrj8N= zhrjqzUhkNaTyVT7(Iwz>lPK!cLUoPLWyw#q7Jz_*=UhK0*NcH&ZHHuFZ%emtgtsF` zEK6K^{@EY$+6VxN;#X7R4J4w0ztN*m?HX1-1Vu> zqe4;Jx`h`|!tL(zV)-(swue7VMhXZ?s8%BaCCqRMq_aJ#RY|(Gy5h}6Gw|(0m_M%4 z;k1vH$tX2Ooyh6tmL1rrU^s=!a-H(qB1Y?EFzuUyI_y{*cD-|aaHZR0zI_pcz3@T; z8!_%M^ASVb%pzY-ej<9xU4<2s;Ymim)mTQ4E%-~WG{u%KwF<(@#yLiHlIQbRkN^Cy z*nJ<07h|J()u&mOdTA#Y+v(WR2^LA@Ua&-$ga~z2oV?`ATpDLwX3Evv8Qh%extw3m z)+E5>MhVJ&MQ()MYgC=Vva+jXSP1ukAFQIF8)SNluu^~(OjFhRA-`hc{f9g z!ZgDA1-GY{ED_ui%(AFUY>|GEhE%O(9})h~67=*7ipt*t42sYH`@WpB8}xTc&p$;~ zt7@`!w`C_gQhxdG(ak|;d~$(Sje3f^FlrbtqGnQYS5hEN(HiP7mIPVUDib5=lJ+;K zaY&v9-Ce6AHNio@e_Jo>xn8prw<^d{ zM!w&E99ny~w!3${7ANQEf!xiQ7H5J z1~9<>#24dFL}PHj_z+#^r@V5_lamW zM2gx|n^=myYrx2F&>!Zy?zN4s?8k1FP5)JfrAHjwy*{XV01?DXS*b2$nO)Mr+gvKB zJ0q&c8hH{c4cj{8;TeN}S%Wc$uDgibEy8LGnhBtxCuQm>Fi*RL(+1=fW^>je$(7cf zio`HCRnaf%Q&bERNBYh~sCm%O!PKpBB{EV-3=4%IRuF>8U%d zwdo+0vD=Af))sR`?Z>KvFEtK-iJ>u4&=qdb>0O?yU)N3c6L@@hVV35DBsfI6&q{j0 zqTE|2emLGU2fZokIeiYDjwn(Eh~Xi54yXTz%*%JOv+L6FL^5ljq3kebXv}wc!#hd^ z>medHXQ+-*?@yyfrN~I*h-rjuSl|+bKHD_ZQegyguNHqzUDT`siM|TMjofm6n~&UO z+W@5;sX@-Kor|24G^Gdq2qnd;kmyR)ec4j%oHK(K7LB@Ff{y zgth;XL;u9X=b}72#G!`V!!qsL`TH(uCaAgS!{CISr&gb1daqiwi+B+rAEB9&T;P}h z3oLncVktI-HitFmOkU?)*0xdNxPZ4mvc$kKNaoXeklV(jjlmvL5~mWWy5NsblUhh! zqg#Y{@WfH>&%5j($65+0EXLiW%^}2I_yu6#f+@L(U$BSTtXg0s5q{ zMj3?fVYlIBW?r4--2y07D)$iedkA22$==(M?>n`Y-sg50k)u|z%T^(0(*P)2 zxz3u)xWvA;TGbU+*N3P+E^AEE*0Nw*)2r3r5Un0J%m!z*U6u_5KbA*^P34-Rs60c3 z<9^>D@P*khcZ65ZqcxCJ*&zlFjt^?r7+1A88WVT(r`4L6^x*oC-O2K6p>FFK>iW9Z z3f!!VW8D_%%$&tC_v>bL#F|4xA$Ds1DU?lCRxo)_f|y7u{w1$2^O%Hi4#9cW#)KR> zmog&S^+=G=9X__A2*t71)2w4+&)mr|XYgrmF?Dp@@H%r5#bWXY@xjeUBT`P$Saa^x zX1Hdk<$qM7=WkH&w>v$%d*>hNtMvfrF7snF))(L%9qZp&ACSHE%te074!Cj#H|wXE zIUiSbJKz;p;_gA7G@kZ zvrN0z$KA`X+l6>WTk7#iDMoiAJuK=ePI^+J)&?X@XE{VEIhdPGIDs;L3{F3kXr~#M zPbTFp$I(7xHPQ9H^2cp;N-br%T9sr4-i%I5UPWjQ65!po-E~gY&BhrIRAeuS0!^Z_ zis2p304Xnd2?NWN_w`xF1*COEd+*kIx8k19+J0{uYw@uz*s$WLqPXpHvLbck>Wq|j zXn?%kv0QNomuSz4P&XdJ$BY!qN=4I>>H0Z^Hkwh@u^K#`G;zx1>|>ODwxn*N4iT;A z&au#0XmC@6#ObKJaa#^l=%R>3XS0`s);0yr2xW*CHg&Cyx9AH)-hLEqk=BBml@1Rc z4wa)9E^Fbb%@uXlXhsd-lO|Ke)YIf7JdGpFJhh}@v_s3)B9RIq7R#NSpKag#{OY?0 z?N^467mn@T9)8WAohfaK?(}moU`ii-wp;GJR&GiR-=PWwkzch~GRx&t&(7zAqQ)70G0%bVy3bhJRTFW#bLwo4uyX@9$g}*4Q zAO2X#H^v9y7QWqHh*@J(s}VBa>aBvj4w0EDL=09&8vnLbezi!Hs!*tIu8QE}3CLCqU*`bSj1)JU7(){O3{A zE3x|2pRvqk?M9#q!7|J7A{8U|M3_{X-IQ?`|FW$+-xOq-er~TZ34EYAspm0ukOZz^ z6VG6}E*qFsZ7I2Rjf|mT_`BD*eCU0WaDrxLs+Wa4<8(Qn^wgF@YRXaKmed2Y{MpI5pjER;6Yg{q;1A>*TUet$08kE@nLdc$bC- zT0)822n9VK0OG9_cOz*h;tv4aVc&v)$4xQ6)(IZ~i;o?{C_K&ChK%ly`LOFl<6Axz zCb#8HCqscZ>&pd11GO6`t^UsTVOrq=;#nuASHF0+nwK+9=sZUl22R|pk1cBgh_G!R z_eA+`xQAPVB7n!mY)_DdM$`+3IfNf%b1`L zSiU7_th&4r0$a(qggQWAg*n*~cmL6jt=RWIr^}~$J$p9BYO-li`?;6aE4g|t)~anF zEj{|;>EG7Jc9WN`+S)leYJa~AEcS0;em~jNj?VcDETs34tJ7?8a8Vc}!OcXVvM`DWI0{XK-N!VK$j zcHv-c;Lx}UhIylaC13HJSKlLQk61k-$pRW{+<|HbJ)-*^3dszM{vknVVz6~cayH#K zc*~-#?XK#FuH&%iI|pQ86i+F!{VdHEfKldr_uJz>FeSx8+=<7^&XNmoTs9>#mG_;D zeei7QQ{#@We&(B0+4ND%&`%qY>H=1sM8Eywro>wiJ=&SQL6UTdM!^Yvgr&AJ#!}1c z-F2;kG;+4{+t*(5vP_{6N9OW6Ngmk(oxR%xmq3`rrX9h;R>sC`Ly!;(OxcO-#V;TN zM=@=f{Ry_EXG%jXn!Chwl%WO}XMJ9%cn}jDxlBqpXQvipT}af z(Y37$eEF{Ht!jD*dW-WkD&6M8^oCizTlPE9=+t2I{bHzvsjMk&<=T9W;#30_vSqdBaCAlbQ08iJO+D!k0FnN%bx|qU#Zs6dule zA4q#X^$QY_^$8ciQ}8Fr!7KEUZyU#D&g#kjwj?y zMy3#}PWVA?wE=q}<09p|+JgF8lEZ%QI8DN(MJ?ZZq-bJM`{I=>$Tc}fH?MB1N><3x z8^cb^l2Lr(;;Q2k#H8dKx9XflUwdF$*r+vIAafe`(s8D&RY`yONYb$%N1ttAe3BvX zK0{e|I4Ixse4(l)FJXjw^~db6a*r^j#fG0?x>WwPNR=4vfD^F_f3ke#2~Lp#YUaLqz>Fko^R=TsAQhHPJzAz)IqdXS*6;xYKz0 zV@!La{c^8*-UND!XARCE);3Zyu$Eg*WOTPG;YjCQ;q3@g< z)gg29(n5~i#-gKPQ+iNNzdHuKN zp8e_(AKw3T_aX|@YR{>vdl?cI()?P~+q0cS&YUc2CyJYFlPk#qB4Qi_@AeqBjMvH# z>597I-uS93?4iLk8w<{}Y^dwUg<<2-}~U7Xph?{^i_ z6&mGv)=48^-0aBK2aN7d&f(*E7jGiOy{%`m=flUGkLbpwK{TZH-eUAojaXsCE{%C zxlwexb^3ix-n{Ab$gfOEqBCI%VuhJDnCdBn7t z17kiQqL2#c`MO&nn?3Vuxc$K57brtQX8x#^*CSH6`2hf8-bR5A@{ZLyzkBs8_3E{7 zu=486FC~4#{T`ka+9n0Kdf}?4raOt^a^*k=ifYeqR1?{QSS6ia6Ylljr;x3FvK8P z+=KdFgNtYRAZ-DL@op6z#f>fak3RPA${_w0H_$!R^Y34NlfbYGap*jxv`{wp=Yq;y<+B`FD5dza-H3g$w2g&TVLPQGijj4!t7O_CwR54hquH%z-m_W zoi7YcskOUj-44}ON74%GYoJ@~R)3}BRFZFPhA#_*?CeJCTV6`+1HhVEi+JL$Y=jXN z@ae@*@+Y)96o{SKgn12jU441h$Qrn<>ld<$H?>7h$VgH#sG{aAmZPe&MK>kZCzz2Q z&ti{ay#&nPCq@WG&Fe$;NhhFX$nhp&j5{o^1SvIvAoWs^BM7I6F*lUI1Fd#fens!h zjE$Y8;sLOiuJHgkszSaN^yR%JdlRh+=zQG%dw^ZYX_c;YO9^v(+jcwpxR44Zc{OCeo7F)$|jlIHLP67{Eq zp0KYu2X(;A3VGH}{*0Q~=l#L0TamL?I6AH?qW)4yab59oNIskLy;Rz91B=leu9`(^ zf*SKdh2LlaXsNFsLK9K5hohhLWFGXJG|Ss zs9o@nG;J^fTE{3HYuKb>dcI1uq^|9T$91pbE}U;d8Py9oWAxT`nNz1_4VwRU;(_aR zmqaqJy69OC1J3mN@M~jbHM3vdE&T_h1SalYe%97Y^N9;TARDfJZdy&D5_+LGRfCKW zD`O2DIW)W-#_@RE+=?}RVz00a4vI_np-T>mZS>L-M8`nVVpUS4@I_{^vZ3mGsE~;l zM;C?N%`teen7{4;@XEtpsMTUyR_x^xleUWUj&2Z7!2nNWvZwvC9%oi8Qg`E_kBwfFD-IGCl!u=H;Svk2drD1UJq?`2bX-lr*+`tBny#@2FP z>|E$<^eRTI+}C0)Gyc-d5o<8@Qka#N1Y1$qt5?MpGOEw$%(t;&Pj68wG}CBQV3m<( z_@8OkSk8s|ehGFPVp|1n;9+s=j?(@+2xeUzgPFA5JwsFuy<@gBs z^Se`CAz(mF4yH06%b`%k{Mz%Vs6pG3<(S;AZ|;b_H48Z7QV@xA&9x{AyhYg_;gxy=T+*_6gmh5bu{)M@7qz8 z9-rY*Gu-9H>B?$6bNDD06S&1c;#OB%>`$a&c%QtsSFt{MCFp@NLXsbLU|*&=UEb=c zjK#HCJZRT{2OMCH&SQfm#JkuzmcEfgVARbSZYK=A!O6kg^bm6qCWKoB4n1wV2Q8ZMd zY3Qnyhut;8y%YId&-9GJ{4Q3*jGh0Dy|;jBbL;kgDRm08xI=M*yHkooaECy!U?BvG zJ1w-hyVD{;0)b+|p@M4(?xnbt7WeL(ea7DJ-tT$Ox#x^=zj5z($9D#UgvnUX8n7~- zCG+{u-$bEGD_l)NP|hlqFCi%BQ9ht&{5ZAxU9(sd&s6tr?@tAT3C(i74G>4QNb6|RXARl3TJQW&=OVaeNMCdwNEoMCj`93M?YU0FEogg=WHg84KJPP92wR?A*r zCq@@tuxAB&*&A&btsinP{F1K-?5PM`PFWi_nnX;^!nx6NX7G9hdLAyF14q=Oc+jZ` zfI1YYhke7oR3Y_Wc@E?ePEtJ&dQ-ni%5!|F0E1b2`0kwX=<>Ags`a3#Hjzu0Mbi+c zvtS_{5(^<4*1Ms)%Ntkj{dQ5#Lt3T5voC2-62fysn z|45c->aGn061%hLLIoX+mDJjn-(g8T7rrm~=AEt<7|t%SWT}^YkJqo@mI1GS{f48{ zZ4jkw7j@|i+v$ooU_)2Nw~?MOE@7rjx;ZQ-u9#XMxrBahZK4f;!sbttPdjVM zj9Y4p%bn2OPZ$fQLr`$&Eygwb7?S~~#l&M5FrepxtiH+it@e7Amc+e#Q3WH?RUS!& z>ff}qIg6aj-f31PZyokZ&1cB(D4Kq4=_roMgn{e;{i7c$iE}x2!vzVId;J8@xwxg& z7CdLh=(?M6U4FxfXTiXk%Pgaf#0!k!K5^P@iH{JRde( zs{Q?DUSGNHgjf7{@p5?wOJcfilg;?v5VZgJ>PCLK!BhUOWx@39)N=#jMbVW`l+8Gu z@%MK_t;EpR9qW7d(k3NkXHZspXmxwXeQ}E<7Gceo_>M z|8_i}wwgUedpmJW%%5-wWt@AmNret*Xm1F1FX>D<6X?apQhvjEKD%mAW4ZpUVf@F% ziLue!mmv*7TwqWW0Jx|b+*+=!I`=8-pTIj*n--vuVmOm0jsh2S+XrqccE+qhX0c+~o^`7->;((iFR!2Kty=}h#he7k_Cn^NN znU?y~_{Y34^Fd_fPvg&iAfo&P9)B8t>y&B!Pv44m`Qw^z0{`kn`1Ug>fEO2be#4^J zFuO3|?~!t3J&Fq(KF%dW_;Sfn{pDNw|LWK7OQIGIu)X8_2mhGU{X0CQ&dXcGq0hMv4Gzy<2_W6L2?F){kiUgpJ@tuE>Xg z`R~>}ev!e9Q9^7&3fu22TI(wF4qu<^Z7ePPV{G@W>t!!)^zLr8VLBG@;qPSzoN8&< zDQP$Oy7F#~+pNTv3&7ixP88>XFDRQuS?p?ph6q_llJ^Sh62ofNe?1Fn;gU$x$gvm~ zP2WMaBvV#MDW(aLBxhFCeS?4qJzvQQ(}wKG0fp$xq6h8uCR~WrtJ((uE~7ia?@lD7 z>2KlWn>@vPBw+umudNv;u=OtI>wWj4P08!~?5vo{#ByeqU~q&-x;UFPd7t3SqqQ9= zd{-qAxwtvC#12NSfk|1voKMSXDq774s1(kwgR79J6fVWK^t0j~5lmHo$u4Q)xf40< z-D35W63x(YR-36o6E}b6LsEbM!GNVj4q5YhDvv-6!Z=9l{fK}WYB8x=CesF{5*%%z%Y8xM{1+$_VLIh?Yg7}+{`T7fF50YCT>EPlQgW&Jb!T%L+Wi+Bs# zmn5z})F;%(4R8LaKA<8>sylT-QW-TloOW6{ti>}_r9SS`G@=x*={E186=~R`MC%u~ z$O_d8!E!2$iEQ*q&BtM!b1Ww5il%GX69q{@<>ZBa98=v>YzAsm6h8NChCYPc_Zxp@ z7OqmC%VD93J8fpeT0$3ec^3I>R+qG>bpUp?M^;k$-Gz=(iICkuhEB zp@jA59e2a%WQp5JeKIEQmEv}+sw_CTf;0cs(?={;($?m$w1mGBNuKR0Ya)Q2jXef@B048<%&X>$R zZxH}IXH~@I{aIUD0?81o7V+7J#PI0*rqyhTyTk8?UczV-*V2oY?J*P7JO@E6bQ_XJ zlGgS{qRa*O=b+dVcd0@S5$@cf8ar-l#} zLCWK={1?C?SwLs8X*{(K8t}CX%14aVT|&wClT&YnWJ=Hr%B$23yDv)U+xK2k!zHWt zT3o*qXaZI8AMsRjlJXk^P_;Lb-6LLK2=U?_u9q{4@xNS~xd=w_`9+Pm9|-n$37QGo zl<<})`-HCagw9YNa#O(%%XcBbsF>Z0WL%n}xHZsBI>#Q$Zh+=sT~gj|la9``J~2W~ zAeXA+JPSZ45L-kzbeM{m=bL3SeJJ{vG)csPTRL~wg58KEqBqKvtnFxGDgloIlm;gf zTU|b+fK+5PuizFqHIvOv7LIVuq5WK;&RcDT^)<6Ip|JXQf1DYOeO%J z8k$jbl8c$k(OF1y93-h68dWUbW%==iHgPr0V*%Pqu9)_TQNL|%J8Cmah)Kc5X*5>F zs7cW@o94^s0#{5FQ&**!l&xBV~_FM%h+4GfyM!3YKWH%LgcwZN>`PQlbzed}&f ze5zk4$Mbms$G6U>BheSsaNazUwC31rqbni6!#P};UxjmrRnWoW<ubbqJiino4-dx2N~^;7)e=C9k(%}NPbL|ov58vKN^$K;sHkQ$4@gSd z6C;gpI5nKPI&m=PYGn(KmS08|#N#;vhW_j+Wm7B+x{pq9OF@YZ2vvw|eqj#iG8zo_ z4MN>J%-;W64MhkWnEMzOCR-hWqnv@y-r#IFTc>WkwVO4%Gr;Hj%UBH3TT@vUt{%7Q zs)Np1O=fS*9IJGFa&c#sSsdLW+Ehp0OfpNT-{>1`!g{ zl}-+Q<#UJg`{FMB=g~drHmGE5Lvo(VEJ!@m?3t&Dfo6k$n9g)@&4re{xo1Jt(D}&^ z9v>U;(E^_d+Gd(sEv53c6=HYx;`cgEBk;|?LfDve31kGen%S1-XZ zTTbc@9=WkYe)upEn?>HUyu2r_j(Pc|w>~OvGpB24S=b5S|6KVov_UK06sqqyuX zQ)U0hD-G=S$_F@|e`j>u723${e|73msCtN-UCwtZ`l)fZGS#2#Duk&0#EGO@W-fqw zp2SNBs}-BsR9bJ)YW`1_!M~O}mac!Sf{d}3lG)Y-E-GnW&cdmW2skgDrme$D1+REZ z+C8VSrx<8eS}D#ip8`5S#Ypp1{nu&Z#yGFzpHu182yrjHhDo4)L})4kDtFx;IQ0G4 z<~N*4O{xYg=>I}I_-x+)M)+6!V;zWq4}#(GB5f;t^V~94#^Bee=52r7pIF2edg*1@ z`R7UPz2h%_>m~Ntu}xtGJ0y=m-<;#*>l2&t(fs~Ou0P9Jnqr6r_#x=K+@s}(T8>)! z*=la$yF=(kv7V$c+ZJ77flF48k&Q1MkBje#e;iHDFX?)>b>o z3=b+kM1}NMyZu7V3L{#C~u|5B4QsFaCZ{i2I_?yblmbK;XxkZz{S0L z^SA>xR^fHHfX(?Mg8JiT;(0FhiQ73bMYS%CnjP z0Fr4ls>iC+CP8`?yTF}E;7?3_H8z+BBz%wq4G@WMsf5MIrH@XdH@R=e($7;DjiIGL zH12I|?A$L{+c$J`=G2p=o4%9vuWI!QchAjaESZ^yA-bE}`XL$bgasQ)+6{ll#<4Xt zf<-Xjy{G$I&=*G7+ySgD&IQspnHdgk&6h0c9VZ#R)bc&|mvBzrG1Wqj{m`06+@Cac<{$!H%w8Z%?x|pTw(Igi?F`%ujQ8BiV;v7@4Gq&I?uuOe1@F9!=AMsL0?oRBX zJD`9Ktisnbem`>KAuL77dX|?j@ldIm2C%l*nnhpgn}xww9MDZH^}WFl6Y{^G1Bz3M zOK(=AhQvL}z({duH%Lby5?DSlB;fCJ z?~y?rON~MB0Hm~28r5^!NaIvq(dQ&IT7jf*y;W{AtStT5M$697kVwxzAEK$J|Gb3Bzr1!hi`cq;1IH&MUJ(O}Jg2k+O+I`o?xETeg8 zXw)f16KUJSD?NX*@NO>MdYa5`3rFCgTEe`#OcT~mF;`~7tMO9zJ2tu>>CYk2I!M0wBfkz!)_k+FXHA6)-u^Q0cQhmSjhf7~^i&mIk zpMJKq1if5PHzJu%WWTVMcnbM-u=X2{gzWP5Z02t`OGDU)Bj+jqO@4n;UjAVcs842NEk|#`DzqCM}sR)9sct=PK5w4ZVzY&i^7ZbOVIzw>iuF zCaFD@X)B7#jg1)k>{y6y;!Wu`w$*A>a=41hSJEcJjr6a{o<4aQDipFpEoLu8WHsa} z{W4Mv0;k4wkloFdTPG%K7IbeHruz+t0+Nw2Gq^z4B82N6qhPzQ6`#SDvQ}R$I1z~t z5WJT&$=XqDAJ*(b!>@V~>lu>T+@O5y*w^Gloxw;JlQSjOSG<$$8t)o4bA`)aZb}B% zNq~Wu$~GHE1gFv~w7d+yV3(a%j1I-ip(3Ns9AI2^xl_=xq2{p6B0&uJ85>*^eAU&4 zd6|I;^q($x^Q^%z)3h?Y1%^Z(pm@OTL0j@Hp-WlcVOj&JPA}FMo~#KTGM|$7nH<6% zy>&cazU9*%@u>JC?+c9>?xc@J6LG!f4}IULff!c#1EbMZNt1?NaPv=9vNk^G-WGa; zkSu-e9PenCv1s{m!ZB^Sc_xQ|*p;@ZQ;h-S^uk7z`J7f#y3O%YLsZq9RthND;Gt~_ zjaiVf)7NU3EX6ihU`1FtRVK zUGfqWZ{tXmq+(c-vo(RM-3UHr{`R3rz@A5z;i1!T2%WC_PdISwV*T)bD$&4Eyn1-f zU1RCQ)vaO*eAaaFXK6I{XyI(t1NZ)4JBGb9dVwuLSp*-mtImR5Roi(Zs`s~I4X zFH7p%2UApUyCHLWY)ZjGzCJ_EKx3&0w8j$|HAc=XOGcf(N#-n~;zH9jgAB9Pkw`iq zOk$l$`RvYM+x()sPM?qAtD3J7IGp##7sfct+7$yMk`prK87T$5s}mzT@k{iLeSy#_ zc2eXk#DhJ5_>K!7pL|fZB-ufB#e#ULspCXf<4ZUd`ANlIw(0qq3YH z?5rXxW$Y_QQ6UUlL`r8n&-k2=WR*te{8L+KWrvJsarP0iEeq6ZCrV#=;mtUQcxCO@ z#2t}bZ^@FT&GvA!<)!ZF={&SWO1NkT%Ka%{0M<>dD8||6=hl8ir&1?;7;wwq@|N-? zmR%k*y(;bBzI3&?G7FceNqX;YB}N*lN5-!`GjvQfX#6oX$-jxt}w|aCo^+aDynO4g-5^VzRz;_ zsL}ek!{E9t`{@`duO?Lp2@q9W5%)2U4yc{H{C(815jydin7@G$rGRvR+<*JbUP zsh{;5JK)wNCAzPy_gs(piK5>X&t-89&V1VAO`HdBD?krq6`(1~#-D^#I}#-rd-!k$ z{AgH#S@sWquJ7L~&#zTg_W*Akc1qOHu!23-xQBVX3e6E{R7e9&hqcjBi? zYSIR4=z#pEjZApx#%%YyVPtk9u(r=6S$65;7D!mj4mR!x(to4iawQ>z5E&n9CtP5- zSPr3sR)^n_Dqh@dNt|y=W4??lICwjF$1!w!YXDlTT2t zY(t(D-Tb*_*Nx||QKj2Hv!Aa_$7>cl!>+Zbu_s0yPfSQC)6-O2ydF-|(0exfL;LDe z4ero@FAc6nPP$n+5}SgE#hIx|MwC4sVxY|MmcgCel216Xsyd0+e}IF95EfV=>pR6b$Ioz?YP1L4X& zVCG$*Fwpc^RmCWdl}aFC#!C>+#l*3n!61}5v?oM065kUgP+1`Cm>%oWl7%aHIWRP$ z52-*#(xkfetp5n-mn{{J>*4rXJ ztQ&jJYoo6opyne^r@jI<QzNVK*((9sC)DWz?wFx$Xi zsE57#Vp1QHtb7?x!emIVvsEj)RgeX<4K@JxSGyu7wuTLg>n&b^>?YA^9qIEyaD)qb zem4lcjQqhl?}|bjT>-$J=$%=+X}x+B9NlP?8ohY>;@6bto{#IwOOliED;ZL}>N-O& zR!@|du_4EoUEuB&9Mn*Z0DyK&OX?9a28eq-vAT%|lgLI{hG*SlUk(;t-^4e@P3XyL zZHKgf%`j%hXSuA zbl-@e6X6Z-F*?i~^IR3+aPr(%K6FdNIJi--z6v>&ds`YSdH%=U`A-R8|73{2RZmEj zdpY-VM>&Efz7Mmy{>1Y1?PXFy&r0m+qdUQe%$++LG&`@A_o-F>ZsA+OPWn@6_kOss z(8BAD^5+&zjhErPMfz|U_Um_vi&5HSwFMH>8T{JnZ?c({*phv+wm4&Bu_l@@S7yV> z;eCSGTd~tpe;D^x%=#w1JOW-ra3CThe;6T6Mu#F`^QEsdA1H@C{xn?v=tq?Qo{;(f z*`4Y?m2dQt`895Mlx2D7;g?l$4!Hcw&4y)NA+YMAmicnvyw~rV zHIxYUTbv6x9-dZu*HKpC{~UL6$N^bU_&L2hrLtRJb{Y3GvV=RgWDmv}Wq)r;{kLHe z!43){h>X@z%0rVczhL?MN;XN6k*fry z#p|JVXUAfmHK9*Og+AKTnTd!FOro9-w|Nc&R(@TlI|QA!oeK<<#<3#(bu^kAkWqG!A9QS3HWwD)1c zMYBV(l(~0YOrpT2CjbC-K^lq;Zp8`k3LRZn5HiYy-%#GElpeu&*>X30ulO36Huh~Y z&H?aXDxv01u>jsJZgHnMu2IhJ0!tdabY;B5ZC9QmQFZQ}@c17hxlX&=A07izwx^Ry zQ0MK*Y;((xv)aCzlvw4rv5i%z@o*HKwh!d3351`gkQ(34pF1IU(%F5S)N09{)gRj~ z1t0oO;I_>bk^q}&jb>-=Yc5hrKRZ3%Kfevo`pQ|!RO{=%>A-C#Xq2Sx1$GTrkBkhiR@TTTOqz0YaHd$$L5RzC`I z!DWpOk|_X%&~NobU`}$ketlR@Op7o1l>LTq_*ZpnwVoB)vUz>9!!X(P1uaR@oPPuK zzD(q|{!_ianc!OqW1kPZQdrmePmP>~RO|V=DD^eiR1z(0m5UQ=yIuXNEUm+ui^Bz^ zBo$0!?VOVvr3%%SKB-!obH>ezvQ56_G>q;3PBmQ-cqxieZ!J>jojCuVC(}`Pc8MuS z@GmejO3^e+F7_^F_Rg|((DrVwnOIPCFmY9YnL>4mjB#+P%>HTb@t+v~VYZdqwJL3_ zy;YzOM=PO7c|m_)tp;j>d;CeEgkjjSS@2xCrJDhIXZ~~ojiu`;n^EM~^VH}mHms|E zd1VsSPaQ9xkSt&R^l@_VJt&pTcMgr)>bk7_syOYGOt{)OxI_0@e#+`6f0eH6eq7rW z!24RFdm1pplF*`4R%kvo59qIL9P)2#iwQ6B%aV!}#k=0=Ac;?bOg9yhr%S!VuaU1H zlYn@fjQAj%t-TilG98k?3+Ss`+VRF_@~Rb)Yh*$C)CNf3*|e~e^f%qqRPf0b^3UVx zok_rrzB5Afnk6_X-HuX&TFMh8k;!w_1z&pY8eIB0VU4NjN9pu&>gR8%8rNNA-M;F= zP?h)$mBVC}E0R0T#~&oyC~UyxQiDBv&@!Vl=iLigR#^%>DlBo4cS7_|i$*2eG13xuz_U{+$w?)i>%0u#5wgGfv8rR1m`!7y+X7437c297{*{*L&$msO%%)>CH zrIqJ%e(;w}+zTYk)eS4?;;;$L%^;bWaU+$$^Y6ZTS9q};*~{vS8wvsUlU#`dVO`$1 z6?Kn=${lXqBm?S5^R`-6Ve>EOgGJOW>q0W)SwrfI+rp>5)7s9`XZg#IvEfu93(=Y%_68=%ZWr5iTwI`F*aLQgz?qh9)kyw$ZtfqW7YrHi5P-f~mR}rhZK6>dZtEcig4nH52x|*;y3SDBa6o!QJAy z%G)JZK<*5oekyO~Vvkfemh7hkVQ63|zG47@_z37{J!^medX@Oj{3DOd#W;v}izN3P zzJk$dCcbcSz+|^&;34o#&$i;)*$VcR}2Lwlf`J!>FLy~ zY~a8EtP@fA9B)*p^BZ8pY7e;}6lZ&k@o>+(CwvI6I-uvI8v~T1$>MmN%bzSm#1cq z>mz8Ir?(~i;e?e$*)>^=u9A9_90s&p>J?RMo6Qf5f2MB19kDy9DeQU&F|Bv{Bv{l| zzWnA8|AWuLoYz9-#3^sxf&LLLaN#c5rMiZJz#}dm`w|4n{F0S)!9X%qU9P_7Fw;Q4 zM+v|_R)1PKR+A>dHY(7EPPF%cmW@c#Vob1?C%*V2JD0nD&T-P6un`ZMWQoLqElji| zBC|&s0k~9Gqu2{qAQXwB5YeW09`RRL9rE$$;*TiP=n#kn&rtNO)f^`uvh2rHW%Ut8 zw+b@l_C$tNIXNen0HPYu%3l=oj2tydRLXGk70+Lm7_n$qzm>e*)!>L62`>s19jLV2 z4zK!fZ^$bHqVf%1vyh)%v7w0<@2I`9R^gWbA|`r+zmdpeAf;Kugp6$TJ+*o)Im0AW znq`rADb`zis-^Uxr}kP%O1TkUoPyu6lE5zDAQt69;xy>q97?s|c(zSz2rS9kO`DRN4|!yhNv&7b-033bm{$y!AV{`- zb3xEg8mfHlzg}B5G?O)D?UbNwuJf>x97J`8ibi`KAKKs z8ow+cf{`3cReo~(1!zM+hcNID!KcN(Sv(&}mMRnQ%LX{I)dmgiSb}Z<(PO`wJp)qj zr>L!yI6>+!&2o|*l(jU*3E$tgxx1DXl$n2RV4p&1K5A`Bq5ec|WCDD_zA7L#IA&N@}5~7^CGiyNI4rNeTdQ|Vp6N1(nT_B1l^d+J$BIWoJ!>1#=~3Qqe!h_nPw1-cI- zWq>IIT3Hgx^aXMoCc1ZsH8V1yz0`J7>87k1@ae3eGc&t%y+*JX&ooGmQO=&xP>#Fn z70&HsHCdcnhid1yS#VWxh}Cd#Bv{_Q!eL-Z`H6Gm!6RQtoamp%o|+}-9w&WrKg*is ziJXyzbtx@017xa13U0Kp&RB$pVM8xqkQv;G36dPS&>h#JO_W#*gt)S}pri2JW^b?3 z6k2j)8r41OY{FD2h&CU!bxNnnAujBe9n#iKL62#K5g0Ti7r~t^>v;S{acU?G-Va(C zc=AqfI(-GGSrLeKOQGOZMk$ggV`d566+azL1_=L#!zN9iuKB>vDpt6*RkNL7OkLU6 zC~5||V13W%7ox!+>>CI@y?qNqUFa25E>%!dpcV;I4}G$x2wx6q@L-%R5iJWo_ci(k1z#vmd>SS)EU{p0FPFlT;+At$`#QYsQtpo_1A*a^ zyIRQ-FqvnN8~)j!Ic-`*v}5B8IrQ$HFN?8NDEb*DzSY;AoteBj$$l}YN)jt7d_C^L zRg}Yh<5{AY2iCUukpFFA_wD;2Zf{lT`CgcxEcP*JSa}Vq;yu)5(TQBMy|ij1sZa(! zG+WX>?->;|J+3FAd6Pi5aD63e-Es+bn8vCp_y?vQ0f(pbY<4OeNRIda_TQ1UV3Q0i zsExr~z#Xi};=%yMrRiVs(So7<<5pF{TIxLeh6+^bh?d$VnljJ>7(~RxX}%cTKmO?L zJUeACeLG6Ey?U>c-oe>kW^z#r(_8ew1jP10x}~)gt=Qb7Q9VU%3ATiTjJ}(TAVl;U zom#F@zJ0lt?7rUb&D(JRrNLk+!E)~lIV8Uyp5@A z{KDWR!e}!kmnFA%qtD{Y#6A#EW#y;Z+XuXG+EW*KkdPSSV58>XcVWr;-CcbuXWpnn zrt(Lo?o5BXp=3vhHohrCTW=TUHp-(_^|;z|9lUv z872)1lKk=lTfi|{en#a=+}AgEfNM^NWrA-iiB-10tqE((PFf{ zwUHYMLrL~bcoG7|;su>#Ul-uaB42XUbifojb64p1q0`?Dk9LlS0grg4i3Xc zw94ZDijLlSLP=(Ymne4!_W^V01KB%7KjqGuS%@DzO_W>1{>)Ht|D0Juo~-cRvj`~_ zK7GJj&39i|NXXm3k?eSkE}7F*q9epesGl5z>f!^3G*vw!$`_vDHKqM}v$M`b?8fBE zIeP56pn3hkn*>cVbCwbJrYYX2)%z;ahGJcf`s8v4_55#mJgy!p-vD4p4mo6^HVD<`pdA!&q&~CStz`v0g)i*Xn*IoWJy`&a|v|t~Xyk zac_Yi|AP<$x16&KQwbMh)gxzwJrp;2W1)Y%l1XnXf! zhZy|VGlw1bemapwuT=FN($X+0Pu3@I%`EI=&^^Q~+=PHQk*M$W8DgQdP6PE?5opGU z(r*2>oX~}WnFB%&HibA^#*xRw_<>VygJzU=qIRmv8V){2b9<=n^%moHKuH zkRRHma)0GzAA-SL`dC?7Zk+^izXr)jgi90#OTYe5OZcp}7C!_>PWFw;!3o2vAgR&d zN=?%|-iGq7b?yCdV=n;s`f4a%H8AY7|MvP}7T4#GE_W@B4s%wms9E2C7TDPnze;s~ zruk{`9ijO0o|<5Sl`^Hj>Q$CfiH^GxWx|8ihj#Jx>Mi|+ZudCi8y8*T!&nW4efv#i`828`8dj&WMD@(@BtA*phnYhV%)<}A z^7}qgYhBxRBYcdxkDku*xC}q70IzY8G!#6IEvCffpXkq?&1R)~m^id4=G&)t|Ma2q zs9NEnu|4JdUDA(BGl`9yP{yXR3eW^}Vdu;y%$P^xz}UzxLd4krRl2=RSIymqA4p(w z#8_Pe&-c{3c!}O(MKXh5+<@Q1XNnriReTK%@yK-U)_arr1LVL#PSD-NH)PuclA^_S zy+}~I$VLp+Foo*3HrmNi^fQflJOeVZ92DuatQj~>ObFTCT#c_Ne73z{7X=>WPARlyiONe@*Ep1TVRIOF@xGGA z<~%Gjw+(51CJE-NooDo3^Vf&*b@(}hn#te8?57QX&9oCvfcbOlX~w|zD!mfAbq`lL za;s^8xf_m;BO%V7T?Wa|zs}U)*^S@U@2Yb5m5BNXPR=|`HrL^Sr8Bw)@EC3)5E)*X z53~nOxqp4c#rMA{vDLQV{1~w~Yh7AeRjI~RU0PmMtYSox+qpqQaWy=dzSyBemc73c zaxSym(CBl%ekyv3Hp|&tO8uw(+h4}t7Av{Gs<;S5jAmIpi~0R%wwaziQz5aD3!KOM zfKes6y;7G>q2?}~S+JMsj3%6m`#Sv~Wme#n3hzwxOr;`|z{6HV(!RH+9yXW%SZ4}l zW)@L6IBsY|Ru1suMixiw>s^k6xy#p<40%z2&mc3ihN;x!9a4hSfZuS&Vbx10A5aVI z_~!eMs%q`KBGbnA3A|oxszoC!$Zx@@Lt}ML!;r^Gg%Kv!VH1}(qm@X9(unch-*DzujxS#l1R0V9aGZTU9S)T3 zCBxE{DX<)GrQ3G&=fShp5$E4JEx-M++l8>O^t#+|K41IJ{oT7DdE#!5=lC z)U40nowV*7qo~b~2$%RkH8>DnBS^#GlG^X8AHR^`km72mU*sW*>nV`@d?DmEX;|Ep zWMYZGcl?0N&zm~S#|PcPo!v{FW6x(*IvCKfm0#jm@^0(&C5OO+LXQj_EZ&;P><8|X znqQ^RY1_d-P?7EpJ#5G ziFTDt6iVjGm3aGzrpr^0$ZT_1xCfc;ODG{Sy=7=)yQX@HRT--*@3Bj?@WyYZ>UotDsM?E@y>gqZhjFm=Apw9FLOzrqu+Qj_G&EQ)FYrHu2tbuh)Hf*iaa1AtoYZ5A0Je1(Ij?Paq}f5o`o z=p=CnJYho$b0iz>SQ?a*u%V7ikd}H6-y{DG&d`G1MR09R33j`m|0h`^e~+Fx}sFr=>|1YwNxyU6;<-Z=QP0@Kxi$AgB1f#K7-pcPr zm^4(6DcFCndTFx4_;E>UU7Y*+(dW)hwGN~UzFm=Hk{ht)uJ1~{Y7+vC+vcW;(_@Z~ zq9Cn!XBxYR((>vWqJV1n9kd^Wd-Yk^(st|K3eib^M@_GHMMdJT0||9FSfE>JjH) zY)Ad?k_R*F0Zc!9f<8}DDC8OT%qS zYB8q~TaVMBj?SK6NbItp;9a?)BI`W3d+oYYeT=>0@&EcORMw5T5L}>Ii5=|gj!1UH z*Xe3Qj_;tUHnOk3|7H?sR1iCp^T%yV@P|`H%X9M}PI{s0(#WYagp(;6WS55G z`V5?!F9%`TgpVJWL3qvn{dJD_1lRwV7he47wI#3qf7?(fPPk9krcoRex*de3st?xW zLjb&_-y8&RRbgohGNM!UlU}|e{#+l#%e<8{7&t5(N5=V1qR@tF{H^ac1MSY0cT3Y>STmDq1=S%cM~$KpWK~I zcb?s3Km;@f!#zV%*tj^im3LFzm4UXJQNUc~Lxp_`M}Z$iE8syE4Nzb9o&>9FgP@2L zWqMDcEtMynysJycc=q*Dw5IE9wQ5?NUxK1>EGjj5K3_1Dd7Z>$*7rp?yK=8`zaWJp z7D3uKGLTCI@fu^h;OV38Ph$ySnmpzGf+8x553>+3u60-7A?*AnQ<7l(P|5b~3aYtw zkyggxe-aK|Cmww~2ELwU8PG2=Va%I2wr6KH$L@*t=ks;TWYfr=S7JB9wMv@#W(j z5eVpt*89j6O+L}iyt~W1_b@A3@yPxBB%{pJGxEJ)Za~-wrzt3VeBZNJQ$^KzmOcog z8P$*M(SJ8RcSx-X62P|;s9ANLO>>H2Q&?DZttKE+n~ksF$`<@EqZBkMIx{skpS$+1 zw0Vx^2rvGNcl}m@44ZMu&TBhqNXme>6!n@Ty#i|(95q_<5pi$<)u^JF4?2D7EfDhY zQ_p(ijA;fa%}(vvu6oA~1<2G38bC{dqmj;6LIV`u*EJQ=7*~EQ5C`lfQ0_6U8DZ)@ zN*}k0tGidnu->SikOFVe?CEz~Cgv;DZki2|)+9!6m^x0fGkd?s==$`qsPF{?<9G_W9$~ zIeSwzMRm>0af?onQW;&X^Ed-m=0sd_CZ~DQR>N@W7(a5vgtSJ z(^2(~#)MB!)sL~d$4xSuOR2Jjyq5F!;Wg{1r)Y)2#oY+Lb3|}!843z#-mAZEgZ@q9 zAJt6pf)vraI@ao0S4ei7T3h&3?|19MP1$|ljqiegpsb!f4uo%CO5Mtw9bATcSwDm+ z(k6z<^?aW>JV^F9mLHp2NO~lQ;Cl?N{H;z|>vYyqy3T%DyMI#rjkqiJZ2wkyAIY3R z^=YIZy=^t+PLpTq;CjMVpwy;RU&bzo#{j{Z=v@9tvT_$}{{_p1iS_uy}=|mR0U$@>5ZEcxGTPGjX9*2{O=f z!^~*b0#BCePvshWXc_k8!KKK)7F;iZTadw5vBJDVl}2N-C4E{AO|Tg`C#!(1@2hF?9l<4Euu zMG{AA;>_V!>PA|xG;tw~Uy8>Q@(Wqv-*pHhMvm9#*}NOKoG0gN$`hJa!;D{V8{YY? zbY~S9-fohA+F|ujt8^6++tlsD7r`_+QLZ`*GirTq)p-~+(eFMrWNe22rF-ZPl#KVO zwE@oSj=YtbH%9WR$Bj_s^e7J?UFGN{kUC1tYkr&8PQp!Wnm7J?OI;9IN~?#4M}^fk z)%w|4Wz7XpB?^1}A|7ZW69(fk+g5}oV)?EqFatDxaiQ?W15u-DVSRr!VX z-7+>t{f>gNr}AA$e3(hYJ}(l}vJgsr$|(ZhX?DH$BQppi&T?#;xfif+m5e@foVC)6 zwf}KSQK2nxO;#y_3HNR{Qv{Q&Kg;FFw9ly>BB-W`;wFk__DfY=$=`jR&YoIl$!X{5 z$0=QBFEYzEXjFJJI6i9RKb#aXAa%o2l6az(c>AoIQ{0AD`eY%|^tO7_^~g!J9@?dj zk*~bknH+ne@y(-h^o!O^F}<*)yH-ZN$I$CU)<6hna7l^_p0^^YuNPRwSZp$pV@V@Q zd=IAP$VxX^uArW7WY5(dkLe`9WO8eCiow$|mRmus&^4s-(oynomAaRfnVom)5>n!LP*DBN7LXZycJ%H2{@8 zxLlaZu+Ynto1~cCVmPlKSSDQtY7n^8GnB71^b!lkka?ZvyflmPvZJRxN9mld6~yJwZC8V(a(mRiiQRGJK3)N6*!q#<7sJoIA@h_|paYdwy$&a>Z>O2>i{oEZQt&Fu zl8wCl5IXgA#;qF`BD%i1(k|fq+{?M)$&hy+>guIwwIUeJJjA;Yv{07wV{>!3 zLfX$d^sL4JL+!nK3i}*9RkhK|GJdR&1{5cK(tx?z_F27-LOO46Uer>Jrb{gC>aAp< zF}~h0ZVwogy%wyj$)r}Yh&hZDA_xwB z3=CR%0NtzZfRCkK+u3AlYuNyf%mAD%XmR8(1$e1U?jn6>xvj}6p-+K)`Zw4qzI!&f zr_fA68@~U!kSd99o|N~{KpkvYT2~>X^kZJ)1iM0;-90Sva|v|CfX6U)m|d=e|6Qfw z&|Hi|naEBDPUPDD@H@)YEpC&&Nl!2AO*&#Vi+LKi{JeURvS&VP&vJej?zAp;kIh&Q zg|ja{NZYA1;uu1x{S?A#)(QnxoD)yeizoi`O4HEOauz9U(|Diyt&;+tCcTFGqd zIWY4QWEED8%?j}K;#GaDqCDsFj>a(eXaI(I2(eA;9)GR9#kdu zO)NenfEsE9188ao4iNM<;v@yDwOce|+vyvA!(-3J&CVa(uQi`0QS3I>+Hr;zG>Xg7 z3g&h8BlqFFAu)EPS@4t;ju6AbgxdsW6XqIo{Kmm$>2HC|1GDd`hDIjyn0AGe)7mu* z5Bcd5i)%8AAMJysK35Rg@8YT12@+d~nWo)9PPA$^HuOczexLZ*5<>$dJGfXlYd?z0 zs6A#}NZ;rAp=TYFEMM|&k$vVcVjFboV6pA(@rdDZpF4I1+keR5kqpDdO&<74T3BR7 z6>g1Kx=^QKyw$JkH_3wOKXFvv}#R;Pof#%a7HCptSiA3*tk6lm#n_w zaN`iUMB4QUAXW`n&M@%Y(8T)yL1ei|QE=?u@?wO-wzdwWuDGh?DK!+Ym@)V37f37s z_--e59-)nwQpe7&un{i^T(gY@OtD01K)eLTJRq(zjqv>CYwE`g+?&fyd!43b>ftVN zx<637hL?U3Ay;qkx#v`;XIR|^H0~n&TT95NkO;pU=Yw^2lj#=mZ|I);pJ=JmTpVJH zgp^Z2 z4@5OTOc^%g@Dl~|r?U8U=<&J)eMm2JXQP_mfeeGC>>0U)?8xG>8+>y}#- zj*Q%)eFU1u1M}QsArL)>oe$oQ=Kd`WO1tNr$4^7+a5+-Ow=puscEJ@Ug$jyinq{NNJ4u{SYo5`y`R7b_60WIEdDgs*w08L2cU9ULNmuc~ z>79EZ`;ZE^Ldd9dbs6TA8365ez8}diArRsPtJ)jrhgcbTLl^euZy?u1v^<$KbB(Nc{d7-;a}-(yW^6N&fQzOjSqZgEUY~k`8*Lt zxU1xB2tF#>q{%xQML2eS|Cl+PgxiH6LGEapSGKC{E75&oi9J`ggEdQGMKiPnUQnY2 z`d(>8rFr+qnaf}6du`87r}%vzr%e|}OsEQSmJ8`AS1or&$(WO2{Ju)bqc5QyPtH;> zO{OblVA`Vut!(;R4Ke5(qFiYEwQ{S(nKs}%V77NeMv1lPe)!ntoiYxhQyc>kmi}pU zWPNVGO?4ef@mLa{45Bk$mmB)+H2TB>M^hrwQ=PB5WE(~(cq%gL+Shqon)gx}ZS*Q#V*3rCZeqgHb8Lg9AHCtyegIB;ZP zXt80ZwQ}L(h2##eb{Ll2%<^jV&xpP?>-&Oi+zYfKv@-z@PsE#Rmli5BVEBwKRa$GcNhhGmLgcF^ zwAM4iMGR;q%S9DM+5qf5qgqXezif_88-4wc7=OUtmG^3}yZZhoajY_Svi934C5Wb)0U9)|rPV5zBG@ zLb=##f^NDVT9JC@1uh>lr-IwN&5Dcs&Lc!q7t9wxtT5|{}+gPR++gY(IKnFejS(aLjZp`Bz(z)dq456gki1yJpIX=cKQ# zf)|&mECB|<&Laq?Hg%1^zkarU78KFh2JB4^1R@csb;ws`fJoG2=1x&pw594z?vD-p zQC{i?KJzh|QPe)5M!z6yDw?}%p2s|X)p_vhncN$Ibs*d|m;W})SlPPq>&5q9ds)G%4CB=2)jRzGHIt+6_8_mjhMVw%?_zDmO#n6_03d@Bbc+b`(ZasV6-*#Rf zul~9^dklJ%7N-f2`SknwA1FsQ{rJD)Zkg}spNU+OrBa~sm0WojP`@v&PBLp88RRjU zB$+O`A;+j-V?F*Z*^@4r&1*4oaW>g#v}kz6t6itHPZ_K{8ZUOcj{RPQ>T`&uw#ZT_ zP|gp)=j%&s#%$+pBztS3P(?AqA`Q%?2!h zWG+9BId(uimk)?lB?mMD$mK*|QOC@v;9A9Em1H${Q6{nkBT>Gq4&d1p{mc;*20~@# ztQw5!53@-mA6@ZhmKeFPeH1|5=jYKEh95u)dYE?{N$a_WzpEi{ZA;&p_^7^+tp6yH zL26sFu0D7>MsMfyK}Q?%qY_6*5}$V$prHd}dT-7=0bOV>0wcIQv|T%CY{)9*Vl6#z zg|giVp0H#LvlO9L?p%JU#HyBjM6JXs{fUAifns;Ze&^_D_W%yy8IR}^CK}g)+p4o5 z0W*Y2|2Qm0dGc=>LjP^eJ&{e)N7Zu`-uTfS;`Tbxw<=CV>oYI|jt4QR3GNI}m_xl$ z3lZDi&g6G#h_^DYUN1Mv-L3;!*%ZeyZ!f6+wTV)e>G96%gPQwYO>En0V9#(Ktx;uS z2N$4YNGv#OWJ+QW{WzW31XeJ1=4n{PWb~{;U}x9bp%Nt&Gx7%ugyq@iJfTjZar`BS zi!xU?YTc14&gWFPPq>zeDCV8WtC7Bf4;y{uN!UJ$Oawu?%48vljYUSDN;8>)^W-cd zt#zgOz82Ta^f}@TKE)W2(kK>vvKtb(v7X?5MM)DD±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 z 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 Employee + @@ -52,20 +53,17 @@ The grid offers built-in commands that you can invoke through its toolbar. To us @code { string result; + public List MyData { get; set; } private async Task UpdateHandler(GridCommandEventArgs args) { SampleData item = args.Item as SampleData; // perform actual data source operations here through your service - SampleData updatedItem = await ServiceMimicUpdate(item); + SampleData updatedItem = await MyService.Update(item); - // update the local view-model data - var index = MyData.FindIndex(i => i.ID == updatedItem.ID); - if (index != -1) - { - MyData[index] = updatedItem; - } + // update the local view-model data with the service data + await GetGridData(); result = string.Format("Employee with ID {0} now has name {1} and hire date {2}", updatedItem.ID, updatedItem.Name, updatedItem.HireDate); } @@ -74,67 +72,83 @@ The grid offers built-in commands that you can invoke through its toolbar. To us { SampleData item = args.Item as SampleData; - // perform actual data source operation here through your service - SampleData insertedItem = await ServiceMimicInsert(item); + // perform actual data source operations here through your service + SampleData insertedItem = await MyService.Create(item); - // update the local view-model data - MyData.Insert(0, insertedItem); + // update the local view-model data with the service data + await GetGridData(); result = string.Format("On {2} you added the employee {0} who was hired on {1}.", insertedItem.Name, insertedItem.HireDate, DateTime.Now); } - // the following two methods mimic an actual data service that handles the actual data source - // you can see about implement error and exception handling, determining suitable return types as per your needs - // an example is available here: https://github.com/telerik/blazor-ui/tree/master/grid/remote-validation - - async Task ServiceMimicInsert(SampleData itemToInsert) + //in a real case, keep the models in dedicated locations, this is just an easy to copy and see example + public class SampleData { - // in this example, we just populate the fields, you project may use - // something else or generate the updated item differently - SampleData updatedItem = new SampleData() - { - // the service assigns an ID, in this sample we use only the view-model data for simplicity, - // you should use the actual data and set the properties as necessary (e.g., generate nested fields data and so on) - ID = MyData.Count + 1, - Name = itemToInsert.Name, - HireDate = itemToInsert.HireDate - }; - return await Task.FromResult(updatedItem); + public int ID { get; set; } + public string Name { get; set; } + public DateTime HireDate { get; set; } } - async Task ServiceMimicUpdate(SampleData itemToUpdate) + async Task GetGridData() { - // in this example, we just populate the fields, you project may use - // something else or generate the updated item differently - SampleData updatedItem = new SampleData() - { - ID = itemToUpdate.ID, - Name = itemToUpdate.Name, - HireDate = itemToUpdate.HireDate - }; - return await Task.FromResult(updatedItem); + MyData = await MyService.Read(); } - //in a real case, keep the models in dedicated locations, this is just an easy to copy and see example - public class SampleData + protected override async Task OnInitializedAsync() { - public int ID { get; set; } - public string Name { get; set; } - public DateTime HireDate { get; set; } + await GetGridData(); } - public List MyData = Enumerable.Range(1, 50).Select(x => new SampleData + // the following static class mimics an actual data service that handles the actual data source + // replace it with your actual service through the DI, this only mimics how the API can look like and works for this standalone page + public static class MyService { - ID = x, - Name = "name " + x, - HireDate = DateTime.Now.AddDays(-x) - }).ToList(); + private static List _data { get; set; } = new List(); + + public static async Task Create(SampleData itemToInsert) + { + itemToInsert.ID = _data.Count + 1; + _data.Insert(0, itemToInsert); + + return await Task.FromResult(itemToInsert); + } + + public static async Task> Read() + { + if (_data.Count < 1) + { + for (int i = 1; i < 50; i++) + { + _data.Add(new SampleData() + { + ID = i, + Name = "Name " + i.ToString(), + HireDate = DateTime.Now.AddDays(-i) + }); + } + } + + return await Task.FromResult(_data); + } + + public static async Task Update(SampleData itemToUpdate) + { + var index = _data.FindIndex(i => i.ID == itemToUpdate.ID); + if (index != -1) + { + _data[index] = itemToUpdate; + return await Task.FromResult(_data[index]); + } + + throw new Exception("no item to update"); + } + } } ```` ->caption The result from the code snippet above, after built-in Create button in the toolbar was clicked +>caption The result from the code snippet above, after the built-in Create button in the toolbar was clicked -![](images/create-toolbar-button.jpg) +![](images/create-toolbar-button.png) ## Custom Commands From 2033d2b594918edfdc653a2e9126c8161157157f Mon Sep 17 00:00:00 2001 From: Marin Bratanov Date: Sat, 7 Nov 2020 17:55:26 +0200 Subject: [PATCH 11/22] chore(listview): better CRUD mimic --- components/listview/editing.md | 123 +++++++++++++++++---------------- 1 file changed, 65 insertions(+), 58 deletions(-) diff --git a/components/listview/editing.md b/components/listview/editing.md index 8b8b61611d..bb672f85d8 100644 --- a/components/listview/editing.md +++ b/components/listview/editing.md @@ -62,19 +62,18 @@ The CUD operations are implemented through dedicated events that let you alter t @code{ + List ListViewData { get; set; } + List Teams { get; set; } + async Task UpdateHandler(ListViewCommandEventArgs args) { Employee item = (Employee)args.Item; - // perform actual data source operations here through your service - Employee updatedItem = await ServiceMimicUpdate(item); + // perform actual data source operation here through your service + await MyService.Update(item); // update the local view-model data with the service data - var index = ListViewData.FindIndex(i => i.Id == updatedItem.Id); - if (index != -1) - { - ListViewData[index] = updatedItem; - } + await GetListViewData(); } async Task DeleteHandler(ListViewCommandEventArgs args) @@ -82,13 +81,10 @@ The CUD operations are implemented through dedicated events that let you alter t Employee item = (Employee)args.Item; // perform actual data source operation here through your service - bool isDeleted = await ServiceMimicDelete(item); + await MyService.Delete(item); - if (isDeleted) - { - // update the local view-model data - ListViewData.Remove(item); - } + // update the local view-model data with the service data + await GetListViewData(); } async Task CreateHandler(ListViewCommandEventArgs args) @@ -96,10 +92,10 @@ The CUD operations are implemented through dedicated events that let you alter t Employee item = (Employee)args.Item; // perform actual data source operation here through your service - Employee insertedItem = await ServiceMimicInsert(item); + await MyService.Create(item); // update the local view-model data with the service data - ListViewData.Insert(0, insertedItem); + await GetListViewData(); } async Task EditHandler(ListViewCommandEventArgs e) @@ -120,64 +116,75 @@ The CUD operations are implemented through dedicated events that let you alter t Console.WriteLine($"user changed item {changedItem.Id} to have Name: {changedItem.Name} and Team: {changedItem.Team}"); } - // the following three methods mimic an actual data service that handles the actual data source - // you can see about implement error and exception handling, determining suitable return types as per your needs - // an example is available here: https://github.com/telerik/blazor-ui/tree/master/grid/remote-validation + // data and models follow - async Task ServiceMimicInsert(Employee itemToInsert) + async Task GetListViewData() { - // in this example, we just populate the fields, you project may use - // something else or generate the updated item differently, we use "new" here - Employee updatedItem = new Employee() - { - // the service assigns an ID, in this sample we use only the view-model data for simplicity, - // you should use the actual data and set the properties as necessary (e.g., generate nested fields data and so on) - Id = ListViewData.Count + 1, - Name = itemToInsert.Name, - Team = itemToInsert.Team - }; - return await Task.FromResult(updatedItem); + ListViewData = await MyService.Read(); + Teams = await MyService.GetTeams(); } - async Task ServiceMimicUpdate(Employee itemToUpdate) + protected override async Task OnInitializedAsync() { - // in this example, we just populate the fields, you project may use - // something else or generate the updated item differently - Employee updatedItem = new Employee() - { - Id = itemToUpdate.Id, - Name = itemToUpdate.Name, - Team = itemToUpdate.Team - }; - return await Task.FromResult(updatedItem); + await GetListViewData(); } - async Task ServiceMimicDelete(Employee itemToDelete) + public class Employee { - return await Task.FromResult(true);//always successful + public int Id { get; set; } + public string Name { get; set; } + public string Team { get; set; } } - // data and models follow + // the following static class mimics an actual data service that handles the actual data source + // replace it with your actual service through the DI, this only mimics how the API can look like and works for this standalone page + public static class MyService + { + private static List _data { get; set; } = new List(); + private static List _teams = new List { "Sales", "Dev", "Support" }; - List ListViewData { get; set; } + public static async Task Create(Employee itemToInsert) + { + itemToInsert.Id = _data.Count + 1; + _data.Insert(0, itemToInsert); + } - protected override void OnInitialized() - { - ListViewData = Enumerable.Range(1, 250).Select(x => new Employee + public static async Task> Read() { - Id = x, - Name = $"Name {x}", - Team = Teams[x % Teams.Count] - }).ToList(); - } + if (_data.Count < 1) + { + for (int i = 1; i < 50; i++) + { + _data.Add(new Employee() + { + Id = i, + Name = $"Name {i}", + Team = _teams[i % _teams.Count] + }); + } + } + + return await Task.FromResult(_data); + } - List Teams = new List { "Sales", "Dev", "Support" }; + public static async Task> GetTeams() + { + return await Task.FromResult(_teams); + } - public class Employee - { - public int Id { get; set; } - public string Name { get; set; } - public string Team { get; set; } + public static async Task Update(Employee itemToUpdate) + { + var index = _data.FindIndex(i => i.Id == itemToUpdate.Id); + if (index != -1) + { + _data[index] = itemToUpdate; + } + } + + public static async Task Delete(Employee itemToDelete) + { + _data.Remove(itemToDelete); + } } } ```` From 3827aef943b16a406c2848af47b70cd01aeab1ce Mon Sep 17 00:00:00 2001 From: Marin Bratanov Date: Sat, 7 Nov 2020 18:04:09 +0200 Subject: [PATCH 12/22] chore(scheduler): better CRUD service mimic --- components/scheduler/edit-appointments.md | 175 ++++++++++------------ 1 file changed, 75 insertions(+), 100 deletions(-) diff --git a/components/scheduler/edit-appointments.md b/components/scheduler/edit-appointments.md index 1e5d85c527..0ce9c802a7 100644 --- a/components/scheduler/edit-appointments.md +++ b/components/scheduler/edit-appointments.md @@ -96,8 +96,6 @@ The example below shows the signature of the event handlers so you can copy the @* This sample implements only updates to the view model. Your app must also update the database in the CUD events. This example uses the default field names for data binding *@ -There is a deliberate delay in the CUD operations in this sample to showcase their async nature - @code { + // sample data and scheduler settings + public SchedulerView CurrView { get; set; } = SchedulerView.Week; + public DateTime StartDate { get; set; } = new DateTime(2019, 12, 2); + public DateTime DayStart { get; set; } = new DateTime(2000, 1, 1, 8, 0, 0); //the time portion is important + + List Appointments { get; set; } + async Task UpdateAppointment(SchedulerUpdateEventArgs args) { SchedulerAppointment item = (SchedulerAppointment)args.Item; // perform actual data source operations here through your service - SchedulerAppointment updatedItem = await ServiceMimicUpdate(item); + await MyService.Update(item); // update the local view-model data with the service data - var index = Appointments.FindIndex(i => i.Id == updatedItem.Id); - if (index != -1) - { - Appointments[index] = updatedItem; - } + await GetSchedulerData(); } async Task AddAppointment(SchedulerCreateEventArgs args) { SchedulerAppointment item = args.Item as SchedulerAppointment; - // perform actual data source operation here through your service - SchedulerAppointment insertedItem = await ServiceMimicInsert(item); + // perform actual data source operations here through your service + await MyService.Create(item); // update the local view-model data with the service data - Appointments.Add(insertedItem); + await GetSchedulerData(); } async Task DeleteAppointment(SchedulerDeleteEventArgs args) { SchedulerAppointment item = (SchedulerAppointment)args.Item; - // perform actual data source operation here through your service - bool isDeleted = await ServiceMimicDelete(item); + // perform actual data source operations here through your service + await MyService.Delete(item); - if (isDeleted) - { - // update the local view-model data - Appointments.Remove(item); - } + // update the local view-model data with the service data + await GetSchedulerData(); // see the comments in the service mimic method below. - // if you do perform additional data source updates, you may want to - // also fetch the entire scheduler data anew to ensure correct and fresh data - // this also applies to the other CUD methods above - // something like the following can refresh the data - // Appointments = await AppointmentService.GetData(); } //Handlers for application logic flexibility @@ -189,80 +182,40 @@ There is a deliberate delay in the CUD operations in this sample to showcase the } - // the following three methods mimic an actual data service that handles the actual data source - // you can see about implement error and exception handling, determining suitable return types as per your needs - - async Task ServiceMimicInsert(SchedulerAppointment itemToInsert) + public class SchedulerAppointment { - await Task.Delay(1000); // simulate actual long running async operation - // in this example, we just populate the fields, you project may use - // something else or generate the updated item differently, we use "new" here - SchedulerAppointment updatedItem = new SchedulerAppointment() + public Guid Id { get; set; } + public string Title { get; set; } + public string Description { get; set; } + public DateTime Start { get; set; } + public DateTime End { get; set; } + public bool IsAllDay { get; set; } + public string RecurrenceRule { get; set; } + public List RecurrenceExceptions { get; set; } + public Guid? RecurrenceId { get; set; } + + public SchedulerAppointment() { - Id = Guid.NewGuid(), - Title = itemToInsert.Title, - Description = itemToInsert.Description, - Start = itemToInsert.Start, - End = itemToInsert.End, - IsAllDay = itemToInsert.IsAllDay, - RecurrenceExceptions = itemToInsert.RecurrenceExceptions, - RecurrenceRule = itemToInsert.RecurrenceRule, - RecurrenceId = itemToInsert.RecurrenceId - }; - return await Task.FromResult(updatedItem); + Id = Guid.NewGuid(); + } } - async Task ServiceMimicUpdate(SchedulerAppointment itemToUpdate) + async Task GetSchedulerData() { - await Task.Delay(1000); // simulate actual long running async operation - // in this example, we just populate the fields, you project may use - // something else or generate the updated item differently - SchedulerAppointment updatedItem = new SchedulerAppointment() - { - Id = itemToUpdate.Id, - Title = itemToUpdate.Title, - Description = itemToUpdate.Description, - Start = itemToUpdate.Start, - End = itemToUpdate.End, - IsAllDay = itemToUpdate.IsAllDay, - RecurrenceExceptions = itemToUpdate.RecurrenceExceptions, - RecurrenceRule = itemToUpdate.RecurrenceRule, - RecurrenceId = itemToUpdate.RecurrenceId - }; - - return await Task.FromResult(updatedItem); + Appointments = await MyService.Read(); } - async Task ServiceMimicDelete(SchedulerAppointment itemToDelete) + protected override async Task OnInitializedAsync() { - await Task.Delay(1000); // simulate actual long running async operation - - - if (itemToDelete.RecurrenceId != null) - { - // a recurrence exception was deleted, you may want to update - // the actual data source - an item where theItem.Id == item.RecurrenceId - // and remove the current exception date from the list of its RecurrenceExceptions - } - - if (!string.IsNullOrEmpty(itemToDelete.RecurrenceRule) && itemToDelete.RecurrenceExceptions?.Count > 0) - { - // a recurring appointment was deleted that had exceptions, you may want to - // delete or update any exceptions from the data source - look for - // items where theItem.RecurrenceId == item.Id - } - - return await Task.FromResult(true);//always successful + await GetSchedulerData(); } - - // sample data and scheduler settings - public SchedulerView CurrView { get; set; } = SchedulerView.Week; - public DateTime StartDate { get; set; } = new DateTime(2019, 12, 2); - public DateTime DayStart { get; set; } = new DateTime(2000, 1, 1, 8, 0, 0); //the time portion is important - - List Appointments = new List() + // the following static class mimics an actual data service that handles the actual data source + // replace it with your actual service through the DI, this only mimics how the API can look like and works for this standalone page + public static class MyService { + private static List _data { get; set; } = new List() + { new SchedulerAppointment { Title = "Board meeting", @@ -304,23 +257,45 @@ There is a deliberate delay in the CUD operations in this sample to showcase the End = new DateTime(2019, 11, 27, 9, 30, 0), RecurrenceRule = "FREQ=WEEKLY;BYDAY=MO,TU,WE,TH,FR" } - }; + }; - public class SchedulerAppointment - { - public Guid Id { get; set; } - public string Title { get; set; } - public string Description { get; set; } - public DateTime Start { get; set; } - public DateTime End { get; set; } - public bool IsAllDay { get; set; } - public string RecurrenceRule { get; set; } - public List RecurrenceExceptions { get; set; } - public Guid? RecurrenceId { get; set; } + public static async Task Create(SchedulerAppointment itemToInsert) + { + itemToInsert.Id = Guid.NewGuid(); + _data.Insert(0, itemToInsert); + } - public SchedulerAppointment() + public static async Task> Read() { - Id = Guid.NewGuid(); + return await Task.FromResult(_data); + } + + public static async Task Update(SchedulerAppointment itemToUpdate) + { + var index = _data.FindIndex(i => i.Id == itemToUpdate.Id); + if (index != -1) + { + _data[index] = itemToUpdate; + } + } + + public static async Task Delete(SchedulerAppointment itemToDelete) + { + if (itemToDelete.RecurrenceId != null) + { + // a recurrence exception was deleted, you may want to update + // the rest of the data source - find an item where theItem.Id == itemToDelete.RecurrenceId + // and remove the current exception date from the list of its RecurrenceExceptions + } + + if (!string.IsNullOrEmpty(itemToDelete.RecurrenceRule) && itemToDelete.RecurrenceExceptions?.Count > 0) + { + // a recurring appointment was deleted that had exceptions, you may want to + // delete or update any exceptions from the data source - look for + // items where theItem.RecurrenceId == itemToDelete.Id + } + + _data.Remove(itemToDelete); } } } From 3e323a8cf61c0715a48e75daaf8a0a6490053d4d Mon Sep 17 00:00:00 2001 From: Marin Bratanov Date: Sat, 7 Nov 2020 18:07:07 +0200 Subject: [PATCH 13/22] docs(grid): clarify why async Task and not async void --- components/grid/editing/overview.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/components/grid/editing/overview.md b/components/grid/editing/overview.md index b9b9ea37da..f051bc8dda 100644 --- a/components/grid/editing/overview.md +++ b/components/grid/editing/overview.md @@ -235,7 +235,9 @@ There are a few considerations to keep in mind with the CUD operations of the gr * For example, you may want to update the view-model only on success of the data service with the model returned from the server. Another thing you may want to do is to inform the user for server (async, remote) validation errors such as duplicates. You can find examples of both in the [Remote Validation sample project](https://github.com/telerik/blazor-ui/tree/master/grid/remote-validation). -* The CRUD event handlers must be `async Task` and **not** `async void`. A Task can be properly awaited and allows working with services and contexts. When the method returns `void`, the execution of the context operations is not actually awaited, and you may get errors from the context (such as "Cannot access a disposed object. A common cause of this error is disposing a context that was resolved from dependency injection and then later trying to use the same context instance elsewhere in your application" or "A second operation started on this context before a previous operation completed. This is usually caused by different threads using the same instance of DbContext"). +* The CRUD event handlers must be `async Task` and **not** `async void`. A Task can be properly awaited and allows working with services and contexts, and lets the grid update after the actual data source operations complete. + + * When the method returns `void`, the execution of the context operations is not actually awaited, and you may get errors from the context (such as "Cannot access a disposed object. A common cause of this error is disposing a context that was resolved from dependency injection and then later trying to use the same context instance elsewhere in your application" or "A second operation started on this context before a previous operation completed. This is usually caused by different threads using the same instance of DbContext"). The grid may also re-render before the actual data update happens and you may not see the result. * The Grid uses `Activator.CreateInstance();` to generate a new item when an Insert action is invoked, so the Model should have a Parameterless constructor defined. A workaround might be [invoking Insert through the grid state]({%slug grid-state%}#initiate-editing-or-inserting-of-an-item) and creating the object with your own code. From 11300762752d9d4e8f04f88245d24cb8b7895eb3 Mon Sep 17 00:00:00 2001 From: Marin Bratanov Date: Sat, 7 Nov 2020 18:16:27 +0200 Subject: [PATCH 14/22] chore(treelist): autogen columns better CRUD mimic --- components/treelist/columns/auto-generated.md | 112 +++++++++--------- 1 file changed, 55 insertions(+), 57 deletions(-) diff --git a/components/treelist/columns/auto-generated.md b/components/treelist/columns/auto-generated.md index cb89c99dff..c66e38777e 100644 --- a/components/treelist/columns/auto-generated.md +++ b/components/treelist/columns/auto-generated.md @@ -333,11 +333,6 @@ This example shows how to: @code { public List Data { get; set; } - protected override async Task OnInitializedAsync() - { - Data = await GetTreeListData(); - } - // sample models with annotations public class Employee @@ -380,78 +375,81 @@ This example shows how to: var item = e.Item as Employee; // perform actual data source operations here through your service - Employee updatedItem = await ServiceMimicUpdate(item); + await MyService.Update(item); // update the local view-model data with the service data - var index = Data.FindIndex(x => x.Id == updatedItem.Id); - if (index != -1) - { - // see the Equals override in the model - it ensures this is the same - // object from the treelist point of view and its state - Data[index] = updatedItem; - } + await GetTreeListData(); } - // the following method mimics an actual data service that handles the actual data source - // you can see about implement error and exception handling, determining suitable return types as per your needs + // data generation - async Task ServiceMimicUpdate(Employee itemToUpdate) + async Task GetTreeListData() { - // in this example, we just populate the fields, you project may use - // something else or generate the updated item differently - Employee updatedItem = new Employee() - { - Id = itemToUpdate.Id, - ParentId = itemToUpdate.ParentId, - Name = itemToUpdate.Name, - EmailAddress = itemToUpdate.EmailAddress, - HireDate = itemToUpdate.HireDate - }; - return await Task.FromResult(updatedItem); + Data = await MyService.Read(); } - // data generation + protected override async Task OnInitializedAsync() + { + await GetTreeListData(); + } - async Task> GetTreeListData() + // the following static class mimics an actual data service that handles the actual data source + // replace it with your actual service through the DI, this only mimics how the API can look like and works for this standalone page + public static class MyService { - List data = new List(); + private static List _data { get; set; } = new List(); - for (int i = 1; i < 15; i++) + public static async Task> Read() { - data.Add(new Employee - { - Id = i, - ParentId = null, - Name = $"root: {i}", - HireDate = DateTime.Now.AddYears(-i) - }); ; - - for (int j = 1; j < 4; j++) + if (_data.Count < 1) { - int currId = i * 100 + j; - data.Add(new Employee - { - Id = currId, - ParentId = i, - Name = $"first level child {j} of {i}", - HireDate = DateTime.Now.AddDays(-currId) - }); - - for (int k = 1; k < 3; k++) + for (int i = 1; i < 15; i++) { - int nestedId = currId * 1000 + k; - data.Add(new Employee + _data.Add(new Employee { - Id = nestedId, - ParentId = currId, - Name = $"second level child {k} of {i} and {currId}", - HireDate = DateTime.Now.AddMinutes(-nestedId) + Id = i, + ParentId = null, + Name = $"root: {i}", + HireDate = DateTime.Now.AddYears(-i) }); ; + + for (int j = 1; j < 4; j++) + { + int currId = i * 100 + j; + _data.Add(new Employee + { + Id = currId, + ParentId = i, + Name = $"first level child {j} of {i}", + HireDate = DateTime.Now.AddDays(-currId) + }); + + for (int k = 1; k < 3; k++) + { + int nestedId = currId * 1000 + k; + _data.Add(new Employee + { + Id = nestedId, + ParentId = currId, + Name = $"second level child {k} of {i} and {currId}", + HireDate = DateTime.Now.AddMinutes(-nestedId) + }); ; + } + } } } + + return await Task.FromResult(_data); } - return await Task.FromResult(data); + public static async Task Update(Employee itemToUpdate) + { + var index = _data.FindIndex(i => i.Id == itemToUpdate.Id); + if (index != -1) + { + _data[index] = itemToUpdate; + } + } } } ```` From 49fb0751e2db95eeaba4449c5f7e494180103c0f Mon Sep 17 00:00:00 2001 From: Marin Bratanov Date: Sat, 7 Nov 2020 19:34:03 +0200 Subject: [PATCH 15/22] chore(treeList): command column better CRUD mimic --- components/treelist/columns/command.md | 168 ++++++++++++------------- 1 file changed, 81 insertions(+), 87 deletions(-) diff --git a/components/treelist/columns/command.md b/components/treelist/columns/command.md index a887788181..d35e24802d 100644 --- a/components/treelist/columns/command.md +++ b/components/treelist/columns/command.md @@ -76,8 +76,6 @@ The `OnClick` handler of the commands receives an argument of type `TreeListComm @code { public List Data { get; set; } - public static List Roles = new List { "Manager", "Employee", "Contractor" }; - public Employee CurrentlyEditedEmployee { get; set; } // Sample CUD operations for the local data async Task UpdateItem(TreeListCommandEventArgs args) @@ -85,10 +83,10 @@ The `OnClick` handler of the commands receives an argument of type `TreeListComm var item = args.Item as Employee; // you can also use the entire model // perform actual data source operations here through your service - Employee updatedItem = await ServiceMimicUpdate(item); + await MyService.Update(item); // update the local view-model data with the service data - UpdateItemRecursive(Data, updatedItem); + await GetTreeListData(); } // sample custom command handling @@ -115,45 +113,6 @@ The `OnClick` handler of the commands receives an argument of type `TreeListComm await Task.Delay(2000); //simulate actual long running async operation } - // sample helper methods for handling the view-model data hierarchy - void UpdateItemRecursive(List items, Employee itemToUpdate) - { - for (int i = 0; i < items.Count; i++) - { - if (items[i].Id.Equals(itemToUpdate.Id)) - { - items[i] = itemToUpdate; - return; - } - - if (items[i].DirectReports?.Count > 0) - { - UpdateItemRecursive(items[i].DirectReports, itemToUpdate); - } - } - } - - - // the following method mimics an actual data service that handles the actual data source - // you can see about implement error and exception handling, determining suitable return types as per your needs - - async Task ServiceMimicUpdate(Employee itemToUpdate) - { - // in this example, we just populate the fields, you project may use - // something else or generate the updated item differently - Employee updatedItem = new Employee() - { - Id = itemToUpdate.Id, - Name = itemToUpdate.Name, - EmailAddress = itemToUpdate.EmailAddress, - HireDate = itemToUpdate.HireDate, - HasChildren = itemToUpdate.HasChildren, - DirectReports = itemToUpdate.DirectReports - }; - return await Task.FromResult(updatedItem); - } - - // sample model public class Employee @@ -180,69 +139,104 @@ The `OnClick` handler of the commands receives an argument of type `TreeListComm } } - // data generation - // used in this example for data generation and retrieval for CUD operations on the current view-model data - public int LastId { get; set; } = 1; + async Task GetTreeListData() + { + Data = await MyService.Read(); + } protected override async Task OnInitializedAsync() { - Data = await GetTreeListData(); + await GetTreeListData(); } - async Task> GetTreeListData() + // the following static class mimics an actual data service that handles the actual data source + // replace it with your actual service through the DI, this only mimics how the API can look like and works for this standalone page + public static class MyService { - List data = new List(); + private static List _data { get; set; } = new List(); + // used in this example for data generation and retrieval for CUD operations on the current view-model data + private static int LastId { get; set; } = 1; + private static List Roles = new List { "Manager", "Employee", "Contractor" }; - for (int i = 1; i < 15; i++) + public static async Task> Read() { - Employee root = new Employee - { - Id = LastId, - Name = $"root: {i}", - Role = Roles[i % Roles.Count], - EmailAddress = $"{i}@example.com", - HireDate = DateTime.Now.AddYears(-i), - DirectReports = new List(), - HasChildren = true - }; - data.Add(root); - LastId++; - - for (int j = 1; j < 4; j++) + if (_data.Count < 1) { - int currId = LastId; - Employee firstLevelChild = new Employee - { - Id = currId, - Name = $"first level child {j} of {i}", - Role = Roles[j % Roles.Count], - EmailAddress = $"{currId}@example.com", - HireDate = DateTime.Now.AddDays(-currId), - DirectReports = new List(), - HasChildren = true - }; - root.DirectReports.Add(firstLevelChild); - LastId++; - - for (int k = 1; k < 3; k++) + for (int i = 1; i < 15; i++) { - int nestedId = LastId; - firstLevelChild.DirectReports.Add(new Employee + Employee root = new Employee { Id = LastId, - Name = $"second level child {k} of {j} and {i}", - Role = Roles[k % Roles.Count], - EmailAddress = $"{nestedId}@example.com", - HireDate = DateTime.Now.AddMinutes(-nestedId) - }); ; + Name = $"root: {i}", + Role = Roles[i % Roles.Count], + EmailAddress = $"{i}@example.com", + HireDate = DateTime.Now.AddYears(-i), + DirectReports = new List(), + HasChildren = true + }; + _data.Add(root); LastId++; + + for (int j = 1; j < 4; j++) + { + int currId = LastId; + Employee firstLevelChild = new Employee + { + Id = currId, + Name = $"first level child {j} of {i}", + Role = Roles[j % Roles.Count], + EmailAddress = $"{currId}@example.com", + HireDate = DateTime.Now.AddDays(-currId), + DirectReports = new List(), + HasChildren = true + }; + root.DirectReports.Add(firstLevelChild); + LastId++; + + for (int k = 1; k < 3; k++) + { + int nestedId = LastId; + firstLevelChild.DirectReports.Add(new Employee + { + Id = LastId, + Name = $"second level child {k} of {j} and {i}", + Role = Roles[k % Roles.Count], + EmailAddress = $"{nestedId}@example.com", + HireDate = DateTime.Now.AddMinutes(-nestedId) + }); ; + LastId++; + } + } } } + + return await Task.FromResult(_data); + } + + public static async Task Update(Employee itemToUpdate) + { + UpdateItemRecursive(_data, itemToUpdate); } - return await Task.FromResult(data); + // sample helper methods for handling the view-model data hierarchy + private static void UpdateItemRecursive(List items, Employee itemToUpdate) + { + for (int i = 0; i < items.Count; i++) + { + if (items[i].Id.Equals(itemToUpdate.Id)) + { + items[i] = itemToUpdate; + return; + } + + if (items[i].DirectReports?.Count > 0) + { + UpdateItemRecursive(items[i].DirectReports, itemToUpdate); + } + } + } } } ```` From 4ff3d3ec3a5b8437fae680b7e956568bfdfdc219 Mon Sep 17 00:00:00 2001 From: Marin Bratanov Date: Sat, 7 Nov 2020 19:50:15 +0200 Subject: [PATCH 16/22] chore(treeList): incell edit better CRUD mimic --- components/treelist/editing/incell.md | 297 ++++++++++++-------------- 1 file changed, 139 insertions(+), 158 deletions(-) diff --git a/components/treelist/editing/incell.md b/components/treelist/editing/incell.md index bddfedb79f..feca579a89 100644 --- a/components/treelist/editing/incell.md +++ b/components/treelist/editing/incell.md @@ -44,9 +44,8 @@ Editing is cancelled for the first record. Add - + Add Child - Edit Delete Update Cancel @@ -71,21 +70,22 @@ Editing is cancelled for the first record. var item = args.Item as Employee; // you can also use the entire model // perform actual data source operations here through your service - Employee updatedItem = await ServiceMimicUpdate(item); + await MyService.Update(item); // update the local view-model data with the service data - UpdateItemRecursive(Data, updatedItem); + await GetTreeListData(); } async Task CreateItem(TreeListCommandEventArgs args) { - var argsItem = args.Item as Employee; + var item = args.Item as Employee; + var parentItem = args.ParentItem as Employee; - // perform actual data source operation here through your service - Employee insertedItem = await ServiceMimicInsert(argsItem); + // perform actual data source operations here through your service + await MyService.Create(item, parentItem); // update the local view-model data with the service data - InsertItemRecursive(Data, insertedItem, args); + await GetTreeListData(); } async Task DeleteItem(TreeListCommandEventArgs args) @@ -93,73 +93,10 @@ Editing is cancelled for the first record. var item = args.Item as Employee; // perform actual data source operations here through your service - bool isDeleted = await ServiceMimicDelete(item); - - if (isDeleted) - { - // update the local view-model data - RemoveChildRecursive(Data, item); - } - } - - // sample helper methods for handling the view-model data hierarchy - void InsertItemRecursive(List Data, Employee insertedItem, TreeListCommandEventArgs args) - { - if (args.ParentItem != null) - { - var parent = (Employee)args.ParentItem; - - parent.HasChildren = true; - if (parent.DirectReports == null) - { - parent.DirectReports = new List(); - } - - parent.DirectReports.Insert(0, insertedItem); - } - else - { - Data.Insert(0, insertedItem); - } - } - - void UpdateItemRecursive(List items, Employee itemToUpdate) - { - for (int i = 0; i < items.Count; i++) - { - if (items[i].Id.Equals(itemToUpdate.Id)) - { - items[i] = itemToUpdate; - return; - } - - if (items[i].DirectReports?.Count > 0) - { - UpdateItemRecursive(items[i].DirectReports, itemToUpdate); - } - } - } + await MyService.Delete(item); - void RemoveChildRecursive(List items, Employee item) - { - for (int i = 0; i < items.Count(); i++) - { - if (item.Equals(items[i])) - { - items.Remove(item); - - return; - } - else if (items[i].DirectReports?.Count > 0) - { - RemoveChildRecursive(items[i].DirectReports, item); - - if (items[i].DirectReports.Count == 0) - { - items[i].HasChildren = false; - } - } - } + // update the local view-model data with the service data + await GetTreeListData(); } // OnEdit handler @@ -184,49 +121,6 @@ Editing is cancelled for the first record. } - // the following three methods mimic an actual data service that handles the actual data source - // you can see about implement error and exception handling, determining suitable return types as per your needs - - async Task ServiceMimicInsert(Employee itemToInsert) - { - // in this example, we just populate the fields, you project may use - // something else or generate the updated item differently, we use "new" here - Employee insertedItem = new Employee() - { - // the service assigns an ID, in this sample we use only the view-model data for simplicity, - // you should use the actual data and set the properties as necessary (e.g., generate nested fields data and so on) - Id = LastId++, - Name = itemToInsert.Name, - EmailAddress = itemToInsert.EmailAddress, - HireDate = itemToInsert.HireDate, - HasChildren = itemToInsert.HasChildren, - DirectReports = itemToInsert.DirectReports - }; - return await Task.FromResult(insertedItem); - } - - async Task ServiceMimicUpdate(Employee itemToUpdate) - { - // in this example, we just populate the fields, you project may use - // something else or generate the updated item differently - Employee updatedItem = new Employee() - { - Id = itemToUpdate.Id, - Name = itemToUpdate.Name, - EmailAddress = itemToUpdate.EmailAddress, - HireDate = itemToUpdate.HireDate, - HasChildren = itemToUpdate.HasChildren, - DirectReports = itemToUpdate.DirectReports - }; - return await Task.FromResult(updatedItem); - } - - async Task ServiceMimicDelete(Employee itemToDelete) - { - return await Task.FromResult(true);//always successful - } - - // sample model public class Employee @@ -253,65 +147,152 @@ Editing is cancelled for the first record. } // data generation - // used in this example for data generation and assigning an ID to newly inserted items - public int LastId { get; set; } = 1; + + async Task GetTreeListData() + { + Data = await MyService.Read(); + } protected override async Task OnInitializedAsync() { - Data = await GetTreeListData(); + await GetTreeListData(); } - async Task> GetTreeListData() + // the following static class mimics an actual data service that handles the actual data source + // replace it with your actual service through the DI, this only mimics how the API can look like and works for this standalone page + public static class MyService { - List data = new List(); + private static List _data { get; set; } = new List(); + // used in this example for data generation and retrieval for CUD operations on the current view-model data + private static int LastId { get; set; } = 1; - for (int i = 1; i < 15; i++) + public static async Task Create(Employee itemToInsert, Employee parentItem) { - Employee root = new Employee - { - Id = LastId, - Name = $"root: {i}", - EmailAddress = $"{i}@example.com", - HireDate = DateTime.Now.AddYears(-i), - DirectReports = new List(), - HasChildren = true - }; - data.Add(root); - LastId++; - - for (int j = 1; j < 4; j++) + InsertItemRecursive(_data, itemToInsert, parentItem); + } + + public static async Task> Read() + { + if (_data.Count < 1) { - int currId = LastId; - Employee firstLevelChild = new Employee + for (int i = 1; i < 15; i++) { - Id = currId, - Name = $"first level child {j} of {i}", - EmailAddress = $"{currId}@example.com", - HireDate = DateTime.Now.AddDays(-currId), - DirectReports = new List(), - HasChildren = true - }; - root.DirectReports.Add(firstLevelChild); - LastId++; - - for (int k = 1; k < 3; k++) - { - int nestedId = LastId; - firstLevelChild.DirectReports.Add(new Employee + Employee root = new Employee { Id = LastId, - Name = $"second level child {k} of {j} and {i}", - EmailAddress = $"{nestedId}@example.com", - HireDate = DateTime.Now.AddMinutes(-nestedId) - }); ; + Name = $"root: {i}", + EmailAddress = $"{i}@example.com", + HireDate = DateTime.Now.AddYears(-i), + DirectReports = new List(), + HasChildren = true + }; + _data.Add(root); LastId++; + + for (int j = 1; j < 4; j++) + { + int currId = LastId; + Employee firstLevelChild = new Employee + { + Id = currId, + Name = $"first level child {j} of {i}", + EmailAddress = $"{currId}@example.com", + HireDate = DateTime.Now.AddDays(-currId), + DirectReports = new List(), + HasChildren = true + }; + root.DirectReports.Add(firstLevelChild); + LastId++; + + for (int k = 1; k < 3; k++) + { + int nestedId = LastId; + firstLevelChild.DirectReports.Add(new Employee + { + Id = LastId, + Name = $"second level child {k} of {j} and {i}", + EmailAddress = $"{nestedId}@example.com", + HireDate = DateTime.Now.AddMinutes(-nestedId) + }); ; + LastId++; + } + } } + + _data[0].Name += " (non-editable, see OnEdit)"; } + + return await Task.FromResult(_data); } - data[0].Name += " (non-editable, see OnEdit)"; + public static async Task Update(Employee itemToUpdate) + { + UpdateItemRecursive(_data, itemToUpdate); + } - return await Task.FromResult(data); + public static async Task Delete(Employee itemToDelete) + { + RemoveChildRecursive(_data, itemToDelete); + } + + // sample helper methods for handling the view-model data hierarchy + static void UpdateItemRecursive(List items, Employee itemToUpdate) + { + for (int i = 0; i < items.Count; i++) + { + if (items[i].Id.Equals(itemToUpdate.Id)) + { + items[i] = itemToUpdate; + return; + } + + if (items[i].DirectReports?.Count > 0) + { + UpdateItemRecursive(items[i].DirectReports, itemToUpdate); + } + } + } + + static void RemoveChildRecursive(List items, Employee item) + { + for (int i = 0; i < items.Count(); i++) + { + if (item.Equals(items[i])) + { + items.Remove(item); + + return; + } + else if (items[i].DirectReports?.Count > 0) + { + RemoveChildRecursive(items[i].DirectReports, item); + + if (items[i].DirectReports.Count == 0) + { + items[i].HasChildren = false; + } + } + } + } + + static void InsertItemRecursive(List Data, Employee insertedItem, Employee parentItem) + { + insertedItem.Id = LastId++; + if (parentItem != null) + { + parentItem.HasChildren = true; + if (parentItem.DirectReports == null) + { + parentItem.DirectReports = new List(); + } + + parentItem.DirectReports.Insert(0, insertedItem); + } + else + { + Data.Insert(0, insertedItem); + } + } } } ```` From 27590f748d82bd353e0e097a8b3e08c1d2f39519 Mon Sep 17 00:00:00 2001 From: Marin Bratanov Date: Sat, 7 Nov 2020 19:57:08 +0200 Subject: [PATCH 17/22] chore(treeList): inline edit better CRUD mimic --- components/treelist/editing/inline.md | 294 ++++++++++++-------------- 1 file changed, 139 insertions(+), 155 deletions(-) diff --git a/components/treelist/editing/inline.md b/components/treelist/editing/inline.md index 2e9e31e431..7eeb45245d 100644 --- a/components/treelist/editing/inline.md +++ b/components/treelist/editing/inline.md @@ -53,6 +53,7 @@ Editing is cancelled for the first record. + @code { public List Data { get; set; } @@ -62,21 +63,22 @@ Editing is cancelled for the first record. var item = args.Item as Employee; // perform actual data source operations here through your service - Employee updatedItem = await ServiceMimicUpdate(item); + await MyService.Update(item); // update the local view-model data with the service data - UpdateItemRecursive(Data, updatedItem); + await GetTreeListData(); } async Task CreateItem(TreeListCommandEventArgs args) { - var argsItem = args.Item as Employee; + var item = args.Item as Employee; + var parentItem = args.ParentItem as Employee; - // perform actual data source operation here through your service - Employee insertedItem = await ServiceMimicInsert(argsItem); + // perform actual data source operations here through your service + await MyService.Create(item, parentItem); // update the local view-model data with the service data - InsertItemRecursive(Data, insertedItem, args); + await GetTreeListData(); } async Task DeleteItem(TreeListCommandEventArgs args) @@ -84,73 +86,10 @@ Editing is cancelled for the first record. var item = args.Item as Employee; // perform actual data source operations here through your service - bool isDeleted = await ServiceMimicDelete(item); - - if (isDeleted) - { - // update the local view-model data - RemoveChildRecursive(Data, item); - } - } - - // sample helper methods for handling the view-model data hierarchy - void InsertItemRecursive(List Data, Employee insertedItem, TreeListCommandEventArgs args) - { - if (args.ParentItem != null) - { - var parent = (Employee)args.ParentItem; - - parent.HasChildren = true; - if (parent.DirectReports == null) - { - parent.DirectReports = new List(); - } - - parent.DirectReports.Insert(0, insertedItem); - } - else - { - Data.Insert(0, insertedItem); - } - } - - void UpdateItemRecursive(List items, Employee itemToUpdate) - { - for (int i = 0; i < items.Count; i++) - { - if (items[i].Id.Equals(itemToUpdate.Id)) - { - items[i] = itemToUpdate; - return; - } - - if (items[i].DirectReports?.Count > 0) - { - UpdateItemRecursive(items[i].DirectReports, itemToUpdate); - } - } - } - - void RemoveChildRecursive(List items, Employee item) - { - for (int i = 0; i < items.Count(); i++) - { - if (item.Equals(items[i])) - { - items.Remove(item); + await MyService.Delete(item); - return; - } - else if (items[i].DirectReports?.Count > 0) - { - RemoveChildRecursive(items[i].DirectReports, item); - - if (items[i].DirectReports.Count == 0) - { - items[i].HasChildren = false; - } - } - } + // update the local view-model data with the service data + await GetTreeListData(); } // OnEdit handler @@ -174,48 +113,6 @@ Editing is cancelled for the first record. // if necessary, perform actual data source operation here through your service } - // the following three methods mimic an actual data service that handles the actual data source - // you can see about implement error and exception handling, determining suitable return types as per your needs - - async Task ServiceMimicInsert(Employee itemToInsert) - { - // in this example, we just populate the fields, you project may use - // something else or generate the updated item differently, we use "new" here - Employee insertedItem = new Employee() - { - // the service assigns an ID, in this sample we use only the view-model data for simplicity, - // you should use the actual data and set the properties as necessary (e.g., generate nested fields data and so on) - Id = LastId++, - Name = itemToInsert.Name, - EmailAddress = itemToInsert.EmailAddress, - HireDate = itemToInsert.HireDate, - HasChildren = itemToInsert.HasChildren, - DirectReports = itemToInsert.DirectReports - }; - return await Task.FromResult(insertedItem); - } - - async Task ServiceMimicUpdate(Employee itemToUpdate) - { - // in this example, we just populate the fields, you project may use - // something else or generate the updated item differently - Employee updatedItem = new Employee() - { - Id = itemToUpdate.Id, - Name = itemToUpdate.Name, - EmailAddress = itemToUpdate.EmailAddress, - HireDate = itemToUpdate.HireDate, - HasChildren = itemToUpdate.HasChildren, - DirectReports = itemToUpdate.DirectReports - }; - return await Task.FromResult(updatedItem); - } - - async Task ServiceMimicDelete(Employee itemToDelete) - { - return await Task.FromResult(true);//always successful - } - // sample model @@ -243,65 +140,152 @@ Editing is cancelled for the first record. } // data generation - // used in this example for data generation and assigning an ID to newly inserted items - public int LastId { get; set; } = 1; + + async Task GetTreeListData() + { + Data = await MyService.Read(); + } protected override async Task OnInitializedAsync() { - Data = await GetTreeListData(); + await GetTreeListData(); } - async Task> GetTreeListData() + // the following static class mimics an actual data service that handles the actual data source + // replace it with your actual service through the DI, this only mimics how the API can look like and works for this standalone page + public static class MyService { - List data = new List(); + private static List _data { get; set; } = new List(); + // used in this example for data generation and retrieval for CUD operations on the current view-model data + private static int LastId { get; set; } = 1; - for (int i = 1; i < 15; i++) + public static async Task Create(Employee itemToInsert, Employee parentItem) { - Employee root = new Employee - { - Id = LastId, - Name = $"root: {i}", - EmailAddress = $"{i}@example.com", - HireDate = DateTime.Now.AddYears(-i), - DirectReports = new List(), - HasChildren = true - }; - data.Add(root); - LastId++; - - for (int j = 1; j < 4; j++) + InsertItemRecursive(_data, itemToInsert, parentItem); + } + + public static async Task> Read() + { + if (_data.Count < 1) { - int currId = LastId; - Employee firstLevelChild = new Employee - { - Id = currId, - Name = $"first level child {j} of {i}", - EmailAddress = $"{currId}@example.com", - HireDate = DateTime.Now.AddDays(-currId), - DirectReports = new List(), - HasChildren = true - }; - root.DirectReports.Add(firstLevelChild); - LastId++; - - for (int k = 1; k < 3; k++) + for (int i = 1; i < 15; i++) { - int nestedId = LastId; - firstLevelChild.DirectReports.Add(new Employee + Employee root = new Employee { Id = LastId, - Name = $"second level child {k} of {j} and {i}", - EmailAddress = $"{nestedId}@example.com", - HireDate = DateTime.Now.AddMinutes(-nestedId) - }); ; + Name = $"root: {i}", + EmailAddress = $"{i}@example.com", + HireDate = DateTime.Now.AddYears(-i), + DirectReports = new List(), + HasChildren = true + }; + _data.Add(root); LastId++; + + for (int j = 1; j < 4; j++) + { + int currId = LastId; + Employee firstLevelChild = new Employee + { + Id = currId, + Name = $"first level child {j} of {i}", + EmailAddress = $"{currId}@example.com", + HireDate = DateTime.Now.AddDays(-currId), + DirectReports = new List(), + HasChildren = true + }; + root.DirectReports.Add(firstLevelChild); + LastId++; + + for (int k = 1; k < 3; k++) + { + int nestedId = LastId; + firstLevelChild.DirectReports.Add(new Employee + { + Id = LastId, + Name = $"second level child {k} of {j} and {i}", + EmailAddress = $"{nestedId}@example.com", + HireDate = DateTime.Now.AddMinutes(-nestedId) + }); ; + LastId++; + } + } + } + + _data[0].Name += " (non-editable, see OnEdit)"; + } + + return await Task.FromResult(_data); + } + + public static async Task Update(Employee itemToUpdate) + { + UpdateItemRecursive(_data, itemToUpdate); + } + + public static async Task Delete(Employee itemToDelete) + { + RemoveChildRecursive(_data, itemToDelete); + } + + // sample helper methods for handling the view-model data hierarchy + static void UpdateItemRecursive(List items, Employee itemToUpdate) + { + for (int i = 0; i < items.Count; i++) + { + if (items[i].Id.Equals(itemToUpdate.Id)) + { + items[i] = itemToUpdate; + return; + } + + if (items[i].DirectReports?.Count > 0) + { + UpdateItemRecursive(items[i].DirectReports, itemToUpdate); } } } - data[0].Name += " (non-editable, see OnEdit)"; + static void RemoveChildRecursive(List items, Employee item) + { + for (int i = 0; i < items.Count(); i++) + { + if (item.Equals(items[i])) + { + items.Remove(item); + + return; + } + else if (items[i].DirectReports?.Count > 0) + { + RemoveChildRecursive(items[i].DirectReports, item); - return await Task.FromResult(data); + if (items[i].DirectReports.Count == 0) + { + items[i].HasChildren = false; + } + } + } + } + + static void InsertItemRecursive(List Data, Employee insertedItem, Employee parentItem) + { + insertedItem.Id = LastId++; + if (parentItem != null) + { + parentItem.HasChildren = true; + if (parentItem.DirectReports == null) + { + parentItem.DirectReports = new List(); + } + + parentItem.DirectReports.Insert(0, insertedItem); + } + else + { + Data.Insert(0, insertedItem); + } + } } } ```` From 93b9c049b5d514f677a7b8b29a1aca089eed04ae Mon Sep 17 00:00:00 2001 From: Marin Bratanov Date: Sat, 7 Nov 2020 20:08:10 +0200 Subject: [PATCH 18/22] chore(treeList): popup editing better CRUD mimic --- components/treelist/editing/popup.md | 295 +++++++++++++-------------- 1 file changed, 139 insertions(+), 156 deletions(-) diff --git a/components/treelist/editing/popup.md b/components/treelist/editing/popup.md index 391e188c25..15bab0fe9c 100644 --- a/components/treelist/editing/popup.md +++ b/components/treelist/editing/popup.md @@ -58,6 +58,7 @@ Editing is cancelled for the first record. + @code { public List Data { get; set; } @@ -67,21 +68,22 @@ Editing is cancelled for the first record. var item = args.Item as Employee; // perform actual data source operations here through your service - Employee updatedItem = await ServiceMimicUpdate(item); + await MyService.Update(item); // update the local view-model data with the service data - UpdateItemRecursive(Data, updatedItem); + await GetTreeListData(); } async Task CreateItem(TreeListCommandEventArgs args) { - var argsItem = args.Item as Employee; + var item = args.Item as Employee; + var parentItem = args.ParentItem as Employee; - // perform actual data source operation here through your service - Employee insertedItem = await ServiceMimicInsert(argsItem); + // perform actual data source operations here through your service + await MyService.Create(item, parentItem); // update the local view-model data with the service data - InsertItemRecursive(Data, insertedItem, args); + await GetTreeListData(); } async Task DeleteItem(TreeListCommandEventArgs args) @@ -89,73 +91,10 @@ Editing is cancelled for the first record. var item = args.Item as Employee; // perform actual data source operations here through your service - bool isDeleted = await ServiceMimicDelete(item); - - if (isDeleted) - { - // update the local view-model data - RemoveChildRecursive(Data, item); - } - } - - // sample helper methods for handling the view-model data hierarchy - void InsertItemRecursive(List Data, Employee insertedItem, TreeListCommandEventArgs args) - { - if (args.ParentItem != null) - { - var parent = (Employee)args.ParentItem; - - parent.HasChildren = true; - if (parent.DirectReports == null) - { - parent.DirectReports = new List(); - } + await MyService.Delete(item); - parent.DirectReports.Insert(0, insertedItem); - } - else - { - Data.Insert(0, insertedItem); - } - } - - void UpdateItemRecursive(List items, Employee itemToUpdate) - { - for (int i = 0; i < items.Count; i++) - { - if (items[i].Id.Equals(itemToUpdate.Id)) - { - items[i] = itemToUpdate; - return; - } - - if (items[i].DirectReports?.Count > 0) - { - UpdateItemRecursive(items[i].DirectReports, itemToUpdate); - } - } - } - - void RemoveChildRecursive(List items, Employee item) - { - for (int i = 0; i < items.Count(); i++) - { - if (item.Equals(items[i])) - { - items.Remove(item); - - return; - } - else if (items[i].DirectReports?.Count > 0) - { - RemoveChildRecursive(items[i].DirectReports, item); - - if (items[i].DirectReports.Count == 0) - { - items[i].HasChildren = false; - } - } - } + // update the local view-model data with the service data + await GetTreeListData(); } // OnEdit handler @@ -180,49 +119,6 @@ Editing is cancelled for the first record. } - // the following three methods mimic an actual data service that handles the actual data source - // you can see about implement error and exception handling, determining suitable return types as per your needs - - async Task ServiceMimicInsert(Employee itemToInsert) - { - // in this example, we just populate the fields, you project may use - // something else or generate the updated item differently, we use "new" here - Employee insertedItem = new Employee() - { - // the service assigns an ID, in this sample we use only the view-model data for simplicity, - // you should use the actual data and set the properties as necessary (e.g., generate nested fields data and so on) - Id = LastId++, - Name = itemToInsert.Name, - EmailAddress = itemToInsert.EmailAddress, - HireDate = itemToInsert.HireDate, - HasChildren = itemToInsert.HasChildren, - DirectReports = itemToInsert.DirectReports - }; - return await Task.FromResult(insertedItem); - } - - async Task ServiceMimicUpdate(Employee itemToUpdate) - { - // in this example, we just populate the fields, you project may use - // something else or generate the updated item differently - Employee updatedItem = new Employee() - { - Id = itemToUpdate.Id, - Name = itemToUpdate.Name, - EmailAddress = itemToUpdate.EmailAddress, - HireDate = itemToUpdate.HireDate, - HasChildren = itemToUpdate.HasChildren, - DirectReports = itemToUpdate.DirectReports - }; - return await Task.FromResult(updatedItem); - } - - async Task ServiceMimicDelete(Employee itemToDelete) - { - return await Task.FromResult(true);//always successful - } - - // sample model public class Employee @@ -252,65 +148,152 @@ Editing is cancelled for the first record. } // data generation - // used in this example for data generation and assigning an ID to newly inserted items - public int LastId { get; set; } = 1; + + async Task GetTreeListData() + { + Data = await MyService.Read(); + } protected override async Task OnInitializedAsync() { - Data = await GetTreeListData(); + await GetTreeListData(); } - async Task> GetTreeListData() + // the following static class mimics an actual data service that handles the actual data source + // replace it with your actual service through the DI, this only mimics how the API can look like and works for this standalone page + public static class MyService { - List data = new List(); + private static List _data { get; set; } = new List(); + // used in this example for data generation and retrieval for CUD operations on the current view-model data + private static int LastId { get; set; } = 1; - for (int i = 1; i < 15; i++) + public static async Task Create(Employee itemToInsert, Employee parentItem) { - Employee root = new Employee - { - Id = LastId, - Name = $"root: {i}", - EmailAddress = $"{i}@example.com", - HireDate = DateTime.Now.AddYears(-i), - DirectReports = new List(), - HasChildren = true - }; - data.Add(root); - LastId++; - - for (int j = 1; j < 4; j++) + InsertItemRecursive(_data, itemToInsert, parentItem); + } + + public static async Task> Read() + { + if (_data.Count < 1) { - int currId = LastId; - Employee firstLevelChild = new Employee - { - Id = currId, - Name = $"first level child {j} of {i}", - EmailAddress = $"{currId}@example.com", - HireDate = DateTime.Now.AddDays(-currId), - DirectReports = new List(), - HasChildren = true - }; - root.DirectReports.Add(firstLevelChild); - LastId++; - - for (int k = 1; k < 3; k++) + for (int i = 1; i < 15; i++) { - int nestedId = LastId; - firstLevelChild.DirectReports.Add(new Employee + Employee root = new Employee { Id = LastId, - Name = $"second level child {k} of {j} and {i}", - EmailAddress = $"{nestedId}@example.com", - HireDate = DateTime.Now.AddMinutes(-nestedId) - }); ; + Name = $"root: {i}", + EmailAddress = $"{i}@example.com", + HireDate = DateTime.Now.AddYears(-i), + DirectReports = new List(), + HasChildren = true + }; + _data.Add(root); LastId++; + + for (int j = 1; j < 4; j++) + { + int currId = LastId; + Employee firstLevelChild = new Employee + { + Id = currId, + Name = $"first level child {j} of {i}", + EmailAddress = $"{currId}@example.com", + HireDate = DateTime.Now.AddDays(-currId), + DirectReports = new List(), + HasChildren = true + }; + root.DirectReports.Add(firstLevelChild); + LastId++; + + for (int k = 1; k < 3; k++) + { + int nestedId = LastId; + firstLevelChild.DirectReports.Add(new Employee + { + Id = LastId, + Name = $"second level child {k} of {j} and {i}", + EmailAddress = $"{nestedId}@example.com", + HireDate = DateTime.Now.AddMinutes(-nestedId) + }); ; + LastId++; + } + } + } + + _data[0].Name += " (non-editable, see OnEdit)"; + } + + return await Task.FromResult(_data); + } + + public static async Task Update(Employee itemToUpdate) + { + UpdateItemRecursive(_data, itemToUpdate); + } + + public static async Task Delete(Employee itemToDelete) + { + RemoveChildRecursive(_data, itemToDelete); + } + + // sample helper methods for handling the view-model data hierarchy + static void UpdateItemRecursive(List items, Employee itemToUpdate) + { + for (int i = 0; i < items.Count; i++) + { + if (items[i].Id.Equals(itemToUpdate.Id)) + { + items[i] = itemToUpdate; + return; + } + + if (items[i].DirectReports?.Count > 0) + { + UpdateItemRecursive(items[i].DirectReports, itemToUpdate); } } } - data[0].Name += " (non-editable, see OnEdit)"; + static void RemoveChildRecursive(List items, Employee item) + { + for (int i = 0; i < items.Count(); i++) + { + if (item.Equals(items[i])) + { + items.Remove(item); + + return; + } + else if (items[i].DirectReports?.Count > 0) + { + RemoveChildRecursive(items[i].DirectReports, item); - return await Task.FromResult(data); + if (items[i].DirectReports.Count == 0) + { + items[i].HasChildren = false; + } + } + } + } + + static void InsertItemRecursive(List Data, Employee insertedItem, Employee parentItem) + { + insertedItem.Id = LastId++; + if (parentItem != null) + { + parentItem.HasChildren = true; + if (parentItem.DirectReports == null) + { + parentItem.DirectReports = new List(); + } + + parentItem.DirectReports.Insert(0, insertedItem); + } + else + { + Data.Insert(0, insertedItem); + } + } } } ```` From 01dbe5c5dbc1b0d4c74b56e3bcbfab92ba03a460 Mon Sep 17 00:00:00 2001 From: Marin Bratanov Date: Sat, 7 Nov 2020 20:11:50 +0200 Subject: [PATCH 19/22] chore(treeList): editing overview better CRUD mimic --- components/treelist/editing/overview.md | 300 +++++++++++------------- 1 file changed, 143 insertions(+), 157 deletions(-) diff --git a/components/treelist/editing/overview.md b/components/treelist/editing/overview.md index 76f6b25098..ed703465f6 100644 --- a/components/treelist/editing/overview.md +++ b/components/treelist/editing/overview.md @@ -53,6 +53,9 @@ The example below shows how you can handle the events the treelist exposes, so y >caption Handling the CRUD events of the treelist to save data to the actual data source ````CSHTML +@using System.ComponentModel.DataAnnotations +@* Used for the model annotations only *@ + Editing is cancelled for the first record.
@@ -96,23 +99,24 @@ Editing is cancelled for the first record. var item = args.Item as Employee; // perform actual data source operations here through your service - Employee updatedItem = await ServiceMimicUpdate(item); + await MyService.Update(item); // update the local view-model data with the service data - UpdateItemRecursive(Data, updatedItem); + await GetTreeListData(); AppendToLog("Update", args); } async Task CreateItem(TreeListCommandEventArgs args) { - var argsItem = args.Item as Employee; + var item = args.Item as Employee; + var parentItem = args.ParentItem as Employee; - // perform actual data source operation here through your service - Employee insertedItem = await ServiceMimicInsert(argsItem); + // perform actual data source operations here through your service + await MyService.Create(item, parentItem); // update the local view-model data with the service data - InsertItemRecursive(Data, insertedItem, args); + await GetTreeListData(); AppendToLog("Create", args); } @@ -122,78 +126,14 @@ Editing is cancelled for the first record. var item = args.Item as Employee; // perform actual data source operations here through your service - bool isDeleted = await ServiceMimicDelete(item); + await MyService.Delete(item); - if (isDeleted) - { - // update the local view-model data - RemoveChildRecursive(Data, item); - } + // update the local view-model data with the service data + await GetTreeListData(); AppendToLog("Delete", args); } - - // sample helper methods for handling the view-model data hierarchy - void InsertItemRecursive(List Data, Employee insertedItem, TreeListCommandEventArgs args) - { - if (args.ParentItem != null) - { - var parent = (Employee)args.ParentItem; - - parent.HasChildren = true; - if (parent.DirectReports == null) - { - parent.DirectReports = new List(); - } - - parent.DirectReports.Insert(0, insertedItem); - } - else - { - Data.Insert(0, insertedItem); - } - } - - void UpdateItemRecursive(List items, Employee itemToUpdate) - { - for (int i = 0; i < items.Count; i++) - { - if (items[i].Id.Equals(itemToUpdate.Id)) - { - items[i] = itemToUpdate; - return; - } - - if (items[i].DirectReports?.Count > 0) - { - UpdateItemRecursive(items[i].DirectReports, itemToUpdate); - } - } - } - - void RemoveChildRecursive(List items, Employee item) - { - for (int i = 0; i < items.Count(); i++) - { - if (item.Equals(items[i])) - { - items.Remove(item); - - return; - } - else if (items[i].DirectReports?.Count > 0) - { - RemoveChildRecursive(items[i].DirectReports, item); - - if (items[i].DirectReports.Count == 0) - { - items[i].HasChildren = false; - } - } - } - } - // OnEdit handler async Task OnEditHandler(TreeListCommandEventArgs args) @@ -219,49 +159,6 @@ Editing is cancelled for the first record. AppendToLog("Cancel", args); } - // the following three methods mimic an actual data service that handles the actual data source - // you can see about implement error and exception handling, determining suitable return types as per your needs - - async Task ServiceMimicInsert(Employee itemToInsert) - { - // in this example, we just populate the fields, you project may use - // something else or generate the updated item differently, we use "new" here - Employee insertedItem = new Employee() - { - // the service assigns an ID, in this sample we use only the view-model data for simplicity, - // you should use the actual data and set the properties as necessary (e.g., generate nested fields data and so on) - Id = LastId++, - Name = itemToInsert.Name, - EmailAddress = itemToInsert.EmailAddress, - HireDate = itemToInsert.HireDate, - HasChildren = itemToInsert.HasChildren, - DirectReports = itemToInsert.DirectReports - }; - return await Task.FromResult(insertedItem); - } - - async Task ServiceMimicUpdate(Employee itemToUpdate) - { - // in this example, we just populate the fields, you project may use - // something else or generate the updated item differently - Employee updatedItem = new Employee() - { - Id = itemToUpdate.Id, - Name = itemToUpdate.Name, - EmailAddress = itemToUpdate.EmailAddress, - HireDate = itemToUpdate.HireDate, - HasChildren = itemToUpdate.HasChildren, - DirectReports = itemToUpdate.DirectReports - }; - return await Task.FromResult(updatedItem); - } - - async Task ServiceMimicDelete(Employee itemToDelete) - { - return await Task.FromResult(true);//always successful - } - - // sample visualization of the results MarkupString logger; void AppendToLog(string commandName, TreeListCommandEventArgs args) @@ -275,11 +172,13 @@ Editing is cancelled for the first record. logger = new MarkupString(logger + currAction); } + // sample model public class Employee { public int Id { get; set; } + public string Name { get; set; } public string EmailAddress { get; set; } public DateTime HireDate { get; set; } @@ -301,65 +200,152 @@ Editing is cancelled for the first record. } // data generation - // used in this example for data generation and assigning an ID to newly inserted items - public int LastId { get; set; } = 1; + + async Task GetTreeListData() + { + Data = await MyService.Read(); + } protected override async Task OnInitializedAsync() { - Data = await GetTreeListData(); + await GetTreeListData(); } - async Task> GetTreeListData() + // the following static class mimics an actual data service that handles the actual data source + // replace it with your actual service through the DI, this only mimics how the API can look like and works for this standalone page + public static class MyService { - List data = new List(); + private static List _data { get; set; } = new List(); + // used in this example for data generation and retrieval for CUD operations on the current view-model data + private static int LastId { get; set; } = 1; - for (int i = 1; i < 15; i++) + public static async Task Create(Employee itemToInsert, Employee parentItem) { - Employee root = new Employee - { - Id = LastId, - Name = $"root: {i}", - EmailAddress = $"{i}@example.com", - HireDate = DateTime.Now.AddYears(-i), - DirectReports = new List(), - HasChildren = true - }; - data.Add(root); - LastId++; - - for (int j = 1; j < 4; j++) + InsertItemRecursive(_data, itemToInsert, parentItem); + } + + public static async Task> Read() + { + if (_data.Count < 1) { - int currId = LastId; - Employee firstLevelChild = new Employee - { - Id = currId, - Name = $"first level child {j} of {i}", - EmailAddress = $"{currId}@example.com", - HireDate = DateTime.Now.AddDays(-currId), - DirectReports = new List(), - HasChildren = true - }; - root.DirectReports.Add(firstLevelChild); - LastId++; - - for (int k = 1; k < 3; k++) + for (int i = 1; i < 15; i++) { - int nestedId = LastId; - firstLevelChild.DirectReports.Add(new Employee + Employee root = new Employee { Id = LastId, - Name = $"second level child {k} of {j} and {i}", - EmailAddress = $"{nestedId}@example.com", - HireDate = DateTime.Now.AddMinutes(-nestedId) - }); ; + Name = $"root: {i}", + EmailAddress = $"{i}@example.com", + HireDate = DateTime.Now.AddYears(-i), + DirectReports = new List(), + HasChildren = true + }; + _data.Add(root); LastId++; + + for (int j = 1; j < 4; j++) + { + int currId = LastId; + Employee firstLevelChild = new Employee + { + Id = currId, + Name = $"first level child {j} of {i}", + EmailAddress = $"{currId}@example.com", + HireDate = DateTime.Now.AddDays(-currId), + DirectReports = new List(), + HasChildren = true + }; + root.DirectReports.Add(firstLevelChild); + LastId++; + + for (int k = 1; k < 3; k++) + { + int nestedId = LastId; + firstLevelChild.DirectReports.Add(new Employee + { + Id = LastId, + Name = $"second level child {k} of {j} and {i}", + EmailAddress = $"{nestedId}@example.com", + HireDate = DateTime.Now.AddMinutes(-nestedId) + }); ; + LastId++; + } + } + } + + _data[0].Name += " (non-editable, see OnEdit)"; + } + + return await Task.FromResult(_data); + } + + public static async Task Update(Employee itemToUpdate) + { + UpdateItemRecursive(_data, itemToUpdate); + } + + public static async Task Delete(Employee itemToDelete) + { + RemoveChildRecursive(_data, itemToDelete); + } + + // sample helper methods for handling the view-model data hierarchy + static void UpdateItemRecursive(List items, Employee itemToUpdate) + { + for (int i = 0; i < items.Count; i++) + { + if (items[i].Id.Equals(itemToUpdate.Id)) + { + items[i] = itemToUpdate; + return; + } + + if (items[i].DirectReports?.Count > 0) + { + UpdateItemRecursive(items[i].DirectReports, itemToUpdate); + } + } + } + + static void RemoveChildRecursive(List items, Employee item) + { + for (int i = 0; i < items.Count(); i++) + { + if (item.Equals(items[i])) + { + items.Remove(item); + + return; + } + else if (items[i].DirectReports?.Count > 0) + { + RemoveChildRecursive(items[i].DirectReports, item); + + if (items[i].DirectReports.Count == 0) + { + items[i].HasChildren = false; + } } } } - data[0].Name += " (non-editable, see OnEdit)"; + static void InsertItemRecursive(List Data, Employee insertedItem, Employee parentItem) + { + insertedItem.Id = LastId++; + if (parentItem != null) + { + parentItem.HasChildren = true; + if (parentItem.DirectReports == null) + { + parentItem.DirectReports = new List(); + } - return await Task.FromResult(data); + parentItem.DirectReports.Insert(0, insertedItem); + } + else + { + Data.Insert(0, insertedItem); + } + } } } ```` From fffabf22e398de0c6881eda36daf8eb9fa159d9f Mon Sep 17 00:00:00 2001 From: Marin Bratanov Date: Sat, 7 Nov 2020 20:18:09 +0200 Subject: [PATCH 20/22] docs(treelist): improvements on editing notes --- components/treelist/editing/overview.md | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/components/treelist/editing/overview.md b/components/treelist/editing/overview.md index ed703465f6..75854b52a2 100644 --- a/components/treelist/editing/overview.md +++ b/components/treelist/editing/overview.md @@ -356,12 +356,11 @@ There are a few considerations to keep in mind with the CUD operations of the tr * It is up to the data access logic to save the data once it is changed in the data collection. The example above showcases when that happens and adds some code to provide a visual indication of the change. In a real application, the code for handling data updates may be entirely different. -* The CRUD event handlers must be `async Task` and **not** `async void`. A Task can be properly awaited and allows working with services and contexts. When the method returns `void`, the execution of the context operations is not actually awaited, and you may get errors from the context (such as "Cannot access a disposed object. A common cause of this error is disposing a context that was resolved from dependency injection and then later trying to use the same context instance elsewhere in your application" or "A second operation started on this context before a previous operation completed. This is usually caused by different threads using the same instance of DbContext"). +* The CRUD event handlers must be `async Task` and **not** `async void`. A Task can be properly awaited and allows working with services and contexts, and lets the treelist update after the actual data source operations complete. -* The treelist uses `Activator.CreateInstance();` to generate a new item when an Insert action is invoked, so the Model should have a Parameterless constructor defined. + * When the method returns `void`, the execution of the context operations is not actually awaited, and you may get errors from the context (such as "Cannot access a disposed object. A common cause of this error is disposing a context that was resolved from dependency injection and then later trying to use the same context instance elsewhere in your application" or "A second operation started on this context before a previous operation completed. This is usually caused by different threads using the same instance of DbContext"). The treelist may also re-render before the actual data update happens and you may not see the result. - +* The treelist uses `Activator.CreateInstance();` to generate a new item when an Insert action is invoked, so the Model should have a Parameterless constructor defined. A workaround might be [invoking Insert through the treelist state]({%slug treelist-state%}#initiate-editing-or-inserting-of-an-item) and creating the object with your own code. ## See Also From 04afa4cc5f6d4dbe6edcb9dbd8f634f630a7254d Mon Sep 17 00:00:00 2001 From: Marin Bratanov Date: Sat, 7 Nov 2020 20:26:11 +0200 Subject: [PATCH 21/22] chore(treeList): state sampel better CRUD mimic --- components/treelist/state.md | 316 +++++++++++++++++------------------ 1 file changed, 149 insertions(+), 167 deletions(-) diff --git a/components/treelist/state.md b/components/treelist/state.md index e509d86ae9..9b87620b6e 100644 --- a/components/treelist/state.md +++ b/components/treelist/state.md @@ -662,6 +662,7 @@ In addition to that, you can also use the `EditItem`, `OriginalEditItem`, `Inser @code { + public List Data { get; set; } TelerikTreeList TreeListRef { get; set; } = new TelerikTreeList(); async Task EnterEditMode() @@ -688,48 +689,8 @@ In addition to that, you can also use the `EditItem`, `OriginalEditItem`, `Inser await TreeListRef.SetState(state); } - public List Data { get; set; } - - // Sample CUD operations for the local data - async Task UpdateItem(TreeListCommandEventArgs args) - { - var item = args.Item as Employee; - - // perform actual data source operations here through your service - Employee updatedItem = await ServiceMimicUpdate(item); - - // update the local view-model data with the service data - UpdateItemRecursive(Data, updatedItem); - } - - async Task CreateItem(TreeListCommandEventArgs args) - { - var argsItem = args.Item as Employee; - - // perform actual data source operation here through your service - Employee insertedItem = await ServiceMimicInsert(argsItem); - - // update the local view-model data with the service data - InsertItemRecursive(Data, insertedItem, args); - } - - async Task DeleteItem(TreeListCommandEventArgs args) - { - var item = args.Item as Employee; - - // perform actual data source operations here through your service - bool isDeleted = await ServiceMimicDelete(item); - - if (isDeleted) - { - // update the local view-model data - RemoveChildRecursive(Data, item); - } - } - - - // sample helper methods for handling the view-model data hierarchy - private Employee FindItemRecursive(List items, int id) + // sample helper method for handling the view-model data hierarchy + Employee FindItemRecursive(List items, int id) { foreach (var item in items) { @@ -752,108 +713,42 @@ In addition to that, you can also use the `EditItem`, `OriginalEditItem`, `Inser return null; } - void InsertItemRecursive(List Data, Employee insertedItem, TreeListCommandEventArgs args) + // Sample CUD operations for the local data + async Task UpdateItem(TreeListCommandEventArgs args) { - if (args.ParentItem != null) - { - var parent = (Employee)args.ParentItem; - - parent.HasChildren = true; - if (parent.DirectReports == null) - { - parent.DirectReports = new List(); - } - - parent.DirectReports.Insert(0, insertedItem); - } - else - { - Data.Insert(0, insertedItem); - } - } + var item = args.Item as Employee; - void UpdateItemRecursive(List items, Employee itemToUpdate) - { - for (int i = 0; i < items.Count; i++) - { - if (items[i].Id.Equals(itemToUpdate.Id)) - { - items[i] = itemToUpdate; - return; - } + // perform actual data source operations here through your service + await MyService.Update(item); - if (items[i].DirectReports?.Count > 0) - { - UpdateItemRecursive(items[i].DirectReports, itemToUpdate); - } - } + // update the local view-model data with the service data + await GetTreeListData(); } - void RemoveChildRecursive(List items, Employee item) + async Task CreateItem(TreeListCommandEventArgs args) { - for (int i = 0; i < items.Count(); i++) - { - if (item.Equals(items[i])) - { - items.Remove(item); + var item = args.Item as Employee; + var parentItem = args.ParentItem as Employee; - return; - } - else if (items[i].DirectReports?.Count > 0) - { - RemoveChildRecursive(items[i].DirectReports, item); + // perform actual data source operations here through your service + await MyService.Create(item, parentItem); - if (items[i].DirectReports.Count == 0) - { - items[i].HasChildren = false; - } - } - } + // update the local view-model data with the service data + await GetTreeListData(); } - - // the following three methods mimic an actual data service that handles the actual data source - // you can see about implement error and exception handling, determining suitable return types as per your needs - - async Task ServiceMimicInsert(Employee itemToInsert) + async Task DeleteItem(TreeListCommandEventArgs args) { - // in this example, we just populate the fields, you project may use - // something else or generate the updated item differently, we use "new" here - Employee insertedItem = new Employee() - { - // the service assigns an ID, in this sample we use only the view-model data for simplicity, - // you should use the actual data and set the properties as necessary (e.g., generate nested fields data and so on) - Id = LastId++, - Name = itemToInsert.Name, - EmailAddress = itemToInsert.EmailAddress, - HireDate = itemToInsert.HireDate, - HasChildren = itemToInsert.HasChildren, - DirectReports = itemToInsert.DirectReports - }; - return await Task.FromResult(insertedItem); - } + var item = args.Item as Employee; - async Task ServiceMimicUpdate(Employee itemToUpdate) - { - // in this example, we just populate the fields, you project may use - // something else or generate the updated item differently - Employee updatedItem = new Employee() - { - Id = itemToUpdate.Id, - Name = itemToUpdate.Name, - EmailAddress = itemToUpdate.EmailAddress, - HireDate = itemToUpdate.HireDate, - HasChildren = itemToUpdate.HasChildren, - DirectReports = itemToUpdate.DirectReports - }; - return await Task.FromResult(updatedItem); - } + // perform actual data source operations here through your service + await MyService.Delete(item); - async Task ServiceMimicDelete(Employee itemToDelete) - { - return await Task.FromResult(true);//always successful + // update the local view-model data with the service data + await GetTreeListData(); } + // sample model public class Employee @@ -908,63 +803,150 @@ In addition to that, you can also use the `EditItem`, `OriginalEditItem`, `Inser } // data generation - // used in this example for data generation and assigning an ID to newly inserted items - public int LastId { get; set; } = 1; + + async Task GetTreeListData() + { + Data = await MyService.Read(); + } protected override async Task OnInitializedAsync() { - Data = await GetTreeListData(); + await GetTreeListData(); } - async Task> GetTreeListData() + // the following static class mimics an actual data service that handles the actual data source + // replace it with your actual service through the DI, this only mimics how the API can look like and works for this standalone page + public static class MyService { - List data = new List(); + private static List _data { get; set; } = new List(); + // used in this example for data generation and retrieval for CUD operations on the current view-model data + private static int LastId { get; set; } = 1; - for (int i = 1; i < 15; i++) + public static async Task Create(Employee itemToInsert, Employee parentItem) { - Employee root = new Employee + InsertItemRecursive(_data, itemToInsert, parentItem); + } + + public static async Task> Read() + { + if (_data.Count < 1) { - Id = LastId, - Name = $"root: {i}", - EmailAddress = $"{i}@example.com", - HireDate = DateTime.Now.AddYears(-i), - DirectReports = new List(), - HasChildren = true - }; - data.Add(root); - LastId++; + for (int i = 1; i < 15; i++) + { + Employee root = new Employee + { + Id = LastId, + Name = $"root: {i}", + EmailAddress = $"{i}@example.com", + HireDate = DateTime.Now.AddYears(-i), + DirectReports = new List(), + HasChildren = true + }; + _data.Add(root); + LastId++; - for (int j = 1; j < 4; j++) + for (int j = 1; j < 4; j++) + { + int currId = LastId; + Employee firstLevelChild = new Employee + { + Id = currId, + Name = $"first level child {j} of {i}", + EmailAddress = $"{currId}@example.com", + HireDate = DateTime.Now.AddDays(-currId), + DirectReports = new List(), + HasChildren = true + }; + root.DirectReports.Add(firstLevelChild); + LastId++; + + for (int k = 1; k < 3; k++) + { + int nestedId = LastId; + firstLevelChild.DirectReports.Add(new Employee + { + Id = LastId, + Name = $"second level child {k} of {j} and {i}", + EmailAddress = $"{nestedId}@example.com", + HireDate = DateTime.Now.AddMinutes(-nestedId) + }); ; + LastId++; + } + } + } + } + + return await Task.FromResult(_data); + } + + public static async Task Update(Employee itemToUpdate) + { + UpdateItemRecursive(_data, itemToUpdate); + } + + public static async Task Delete(Employee itemToDelete) + { + RemoveChildRecursive(_data, itemToDelete); + } + + // sample helper methods for handling the view-model data hierarchy + static void UpdateItemRecursive(List items, Employee itemToUpdate) + { + for (int i = 0; i < items.Count; i++) { - int currId = LastId; - Employee firstLevelChild = new Employee + if (items[i].Id.Equals(itemToUpdate.Id)) { - Id = currId, - Name = $"first level child {j} of {i}", - EmailAddress = $"{currId}@example.com", - HireDate = DateTime.Now.AddDays(-currId), - DirectReports = new List(), - HasChildren = true - }; - root.DirectReports.Add(firstLevelChild); - LastId++; + items[i] = itemToUpdate; + return; + } - for (int k = 1; k < 3; k++) + if (items[i].DirectReports?.Count > 0) { - int nestedId = LastId; - firstLevelChild.DirectReports.Add(new Employee + UpdateItemRecursive(items[i].DirectReports, itemToUpdate); + } + } + } + + static void RemoveChildRecursive(List items, Employee item) + { + for (int i = 0; i < items.Count(); i++) + { + if (item.Equals(items[i])) + { + items.Remove(item); + + return; + } + else if (items[i].DirectReports?.Count > 0) + { + RemoveChildRecursive(items[i].DirectReports, item); + + if (items[i].DirectReports.Count == 0) { - Id = LastId, - Name = $"second level child {k} of {j} and {i}", - EmailAddress = $"{nestedId}@example.com", - HireDate = DateTime.Now.AddMinutes(-nestedId) - }); ; - LastId++; + items[i].HasChildren = false; + } } } } - return await Task.FromResult(data); + static void InsertItemRecursive(List Data, Employee insertedItem, Employee parentItem) + { + insertedItem.Id = LastId++; + if (parentItem != null) + { + parentItem.HasChildren = true; + if (parentItem.DirectReports == null) + { + parentItem.DirectReports = new List(); + } + + parentItem.DirectReports.Insert(0, insertedItem); + } + else + { + Data.Insert(0, insertedItem); + } + } } } ```` From 335473f3eb9cfbe22a612e519a76deaf97ab0af6 Mon Sep 17 00:00:00 2001 From: Marin Bratanov Date: Sat, 7 Nov 2020 20:33:18 +0200 Subject: [PATCH 22/22] chore(treeList): editor template better CRUD mimic --- components/treelist/templates/editor.md | 178 ++++++++++++------------ 1 file changed, 92 insertions(+), 86 deletions(-) diff --git a/components/treelist/templates/editor.md b/components/treelist/templates/editor.md index a1194af66b..1a7a66aeaf 100644 --- a/components/treelist/templates/editor.md +++ b/components/treelist/templates/editor.md @@ -53,58 +53,20 @@ If you need to perform logic more complex than simple data binding, use the chan @code { - public List Data { get; set; } - public static List Roles = new List { "Manager", "Employee", "Contractor" }; - public Employee CurrentlyEditedEmployee { get; set; } + List Data { get; set; } + static List Roles { get; set; } + Employee CurrentlyEditedEmployee { get; set; } // Sample CUD operations for the local data async Task UpdateItem(TreeListCommandEventArgs args) { - var item = args.Item as Employee; // you can also use the entire model + var item = args.Item as Employee; // perform actual data source operations here through your service - Employee updatedItem = await ServiceMimicUpdate(item); + await MyService.Update(item); // update the local view-model data with the service data - UpdateItemRecursive(Data, updatedItem); - } - - - // sample helper method for handling the view-model data hierarchy - void UpdateItemRecursive(List items, Employee itemToUpdate) - { - for (int i = 0; i < items.Count; i++) - { - if (items[i].Id.Equals(itemToUpdate.Id)) - { - items[i] = itemToUpdate; - return; - } - - if (items[i].DirectReports?.Count > 0) - { - UpdateItemRecursive(items[i].DirectReports, itemToUpdate); - } - } - } - - // the following method mimics an actual data service that handles the actual data source - // you can see about implement error and exception handling, determining suitable return types as per your needs - async Task ServiceMimicUpdate(Employee itemToUpdate) - { - // in this example, we just populate the fields, you project may use - // something else or generate the updated item differently - Employee updatedItem = new Employee() - { - Id = itemToUpdate.Id, - Name = itemToUpdate.Name, - Role = itemToUpdate.Role, - EmailAddress = itemToUpdate.EmailAddress, - HireDate = itemToUpdate.HireDate, - HasChildren = itemToUpdate.HasChildren, - DirectReports = itemToUpdate.DirectReports - }; - return await Task.FromResult(updatedItem); + await GetTreeListData(); } @@ -136,64 +98,108 @@ If you need to perform logic more complex than simple data binding, use the chan // data generation + async Task GetTreeListData() + { + Data = await MyService.Read(); + Roles = await MyService.GetRoles(); + } + protected override async Task OnInitializedAsync() { - Data = await GetTreeListData(); + await GetTreeListData(); } - async Task> GetTreeListData() + // the following static class mimics an actual data service that handles the actual data source + // replace it with your actual service through the DI, this only mimics how the API can look like and works for this standalone page + public static class MyService { - List data = new List(); - int LastId = 1; + private static List _data { get; set; } = new List(); + private static List Roles = new List { "Manager", "Employee", "Contractor" }; + // used in this example for data generation and retrieval for CUD operations on the current view-model data + private static int LastId { get; set; } = 1; - for (int i = 1; i < 15; i++) + public static async Task> GetRoles() { - Employee root = new Employee - { - Id = LastId, - Name = $"root: {i}", - Role = Roles[i % Roles.Count], - EmailAddress = $"{i}@example.com", - HireDate = DateTime.Now.AddYears(-i), - DirectReports = new List(), - HasChildren = true - }; - data.Add(root); - LastId++; - - for (int j = 1; j < 4; j++) + return await Task.FromResult(Roles); + } + + public static async Task> Read() + { + if (_data.Count < 1) { - int currId = LastId; - Employee firstLevelChild = new Employee + for (int i = 1; i < 15; i++) { - Id = currId, - Name = $"first level child {j} of {i}", - Role = Roles[j % Roles.Count], - EmailAddress = $"{currId}@example.com", - HireDate = DateTime.Now.AddDays(-currId), - DirectReports = new List(), - HasChildren = true - }; - root.DirectReports.Add(firstLevelChild); - LastId++; - - for (int k = 1; k < 3; k++) - { - int nestedId = LastId; - firstLevelChild.DirectReports.Add(new Employee + Employee root = new Employee { Id = LastId, - Name = $"second level child {k} of {j} and {i}", - Role = Roles[k % Roles.Count], - EmailAddress = $"{nestedId}@example.com", - HireDate = DateTime.Now.AddMinutes(-nestedId) - }); ; + Name = $"root: {i}", + Role = Roles[i % Roles.Count], + EmailAddress = $"{i}@example.com", + HireDate = DateTime.Now.AddYears(-i), + DirectReports = new List(), + HasChildren = true + }; + _data.Add(root); LastId++; + + for (int j = 1; j < 4; j++) + { + int currId = LastId; + Employee firstLevelChild = new Employee + { + Id = currId, + Name = $"first level child {j} of {i}", + Role = Roles[j % Roles.Count], + EmailAddress = $"{currId}@example.com", + HireDate = DateTime.Now.AddDays(-currId), + DirectReports = new List(), + HasChildren = true + }; + root.DirectReports.Add(firstLevelChild); + LastId++; + + for (int k = 1; k < 3; k++) + { + int nestedId = LastId; + firstLevelChild.DirectReports.Add(new Employee + { + Id = LastId, + Name = $"second level child {k} of {j} and {i}", + Role = Roles[k % Roles.Count], + EmailAddress = $"{nestedId}@example.com", + HireDate = DateTime.Now.AddMinutes(-nestedId) + }); ; + LastId++; + } + } } } + + return await Task.FromResult(_data); + } + + public static async Task Update(Employee itemToUpdate) + { + UpdateItemRecursive(_data, itemToUpdate); } - return await Task.FromResult(data); + // sample helper methods for handling the view-model data hierarchy + static void UpdateItemRecursive(List items, Employee itemToUpdate) + { + for (int i = 0; i < items.Count; i++) + { + if (items[i].Id.Equals(itemToUpdate.Id)) + { + items[i] = itemToUpdate; + return; + } + + if (items[i].DirectReports?.Count > 0) + { + UpdateItemRecursive(items[i].DirectReports, itemToUpdate); + } + } + } } } ````