For a preview, there's a surprising amount of functionality in the KendoGrid and KendoButton included in the Telerik UI for Blazor Early Preview. Here's how you can use them together to display data, support user editing and (almost) handle updating.
In a previous blog post I showed how to set up a Visual Studio 2019 project to use the KendoGrid control that comes with the Telerik UI for Blazor Early Preview. That post also demonstrated how easy it is to have the grid display objects retrieved from a web service. And, thanks to Blazor, it was all driven by C# code running in the client.
Note that the full project referenced in this article can be found on GitHub.
However, I simplified things a little in that post. For example, I fetched the data from the web service as soon as my Blazor-enabled page reached the browser. That's not always realistic. At the very least, the user will want to refresh the grid to get the latest data from the web service. Alternatively, the grid may contain data that shouldn't be displayed as soon as the page is loaded. For example, the user might need to enter some criteria to send to the web service to control what data is retrieved or the grid might be displaying “optional” data that should only be fetched when requested.
The Telerik UI for Blazor preview includes the KendoButton, which allows you to implement all those scenarios: When users are ready to see the grid populated with data, all they have to do is click the button. All that's required is to add the KendoButton
to the .cshtml
file along with the KendoGrid and specify the name of the method in the page that will fetch the data from the web service.
Assuming that method is called GetCustomersAsync
, this markup would do the trick:
<KendoButton OnButtonClick='@GetCustomersAsync'>
Get Customers
</KendoButton>
The declaration I used in my last post for the grid isn't affected at all. For example, I still bind the grid's Data
property to a property in my Blazor code (I called the property, customers
), and I still bind columns in the grid to properties on the Customer
object I'm displaying. Here's that markup:
<KendoGrid Data="@customers">
<KendoGridColumns>
<KendoGridColumn Field="Id" Title="Customer Id" />
<KendoGridColumn Field="FirstName" Title="First Name" />
<KendoGridColumn Field="LastName" Title="Last Name" />
</KendoGridColumns>
</KendoGrid>
The next step, of course, is to add the GetCustomers
method to the functions block in the Blazor page. In that method, I need to do two things: fetch the data from the web service and update the property that the grid's Data
property is bound to. Really, all I do is move the code I had in my startup method in my last blog post into the method that I bound to the button's click
event. That code (along with the customers
property) looks like this:
@functions
{
private IEnumerable<Customer> customers = null;
public async void GetCustomersAsync()
{
HttpClient hc = new HttpClient();
HttpResponseMessage rm =
await hc.GetAsync("https://localhost:5001/customers");
customers =
await rm.Content.ReadAsAsync<IEnumerable<Customer>>();
StateHasChanged();
}
}
What I like about Blazor is that this is just “plain old C# code.” The only thing that signals that this is client-side Blazor code is the call to the Blazor component's StateHasChanged
method (that method notifies Blazor that the UI has been updated so that Blazor will redraw the affected areas of the page).
In fact, the preview version of the KendoGrid is almost ready to handle simple updates. The grid already lets users change values in the grid just by double-clicking on a cell. What's missing is the ability to tell which rows in the grid have been changed. Ignoring that problem (for now), it's relatively easy to build some initial code that sends changed objects back to the web service for updating.
The first step is to add another button to let the user trigger updates. The markup for that would look something like this (this time I've tied the method to the button click's event using a lambda expression just to show that you have that option):
<KendoButton OnButtonClick='@(() => { SaveCustomers(); })'>
Save Customers
</KendoButton>
Because I don't have a way to check to see what rows have changed (and am too lazy to compare the current state of the objects to their original state), in the SaveCustomers
method I'll always assume that it's the first object in my collection that's changed. Here's the method that sends the first Customer
object in the collection back to the web service's PUT
method for updating:
public async void SaveCustomers()
{
HttpClient hc = new HttpClient();
HttpResponseMessage rm =
await hc.PutAsJsonAsync("https://localhost:5001/customers",
customers.First());
}
Again, however, my code is a little too simple: I shouldn't allow the user to click the update button until some objects have been retrieved (and, really, not until the user has made a change, but I can't check for that with the preview grid). What I need to do is manage the update button based on my page's state.
The page's initial state is “no data retrieved.” After the user clicks the Get Customers button, the state changes to “data is retrieved.” I'll manage that state by adding a field to my page that holds the state (I'll call that field “status”). To represent my page's two states, I'll include an enumerated value (called pageState) that holds two values: NoDataRetrieved
and DataRetrieved
. I'll also initialize my state property to my first state of NoDataRetrieved
. Finally, primarily to support debugging, I'll display the value of the state field at the bottom of my page, just above the functions block.
Putting all that together, the code following my grid and button markup looks like this:
Status: @status.ToString()
@functions {
private enum pageState
{
NoDataRetrieved,
DataRetrieved
}
private KendoGrid<Customer> grid { get; set; }
private pageState status = pageState.NoDataRetrieved;
}
Because, currently, only the GetCustomers
method changes the state of my page, it's the only method I'll enhance it to update the status field (I'll also have it deal with errors to make it a little more realistic):
public async void GetCustomers()
{
HttpClient hc = new HttpClient();
HttpResponseMessage rm =
await hc.GetAsync("https://localhost:5001/customers");
customers =
await rm.Content.ReadAsAsync<IEnumerable<Customer>>();
status = rm.IsSuccessStatusCode ? pageState.DataRetrieved
: pageState.NoDataRetrieved;
StateHasChanged();
}
All that's left to do is disable the UpdateCustomers
button when the page isn't displaying data. I can do that by adding some code to the Enable
attribute on the KendoButton and have that code check the value in my status field. My new, enhanced button looks like this:
<KendoButton Enable='@(status==pageState.DataRetrieved)'
OnButtonClick='@(() => { SaveCustomers(); })'>
Save Customers
</KendoButton>
And there you have it: A relatively functional page that, like any good AJAX application, offloads all the UI processing to the user's computer. The page only demands CPU cycles from your servers (a shared resource) when it's absolutely necessary. Unlike other AJAX pages, though, this page is written in C#.
To learn more about Telerik UI for Blazor and download the preview, don't forget to check out this introductory blog post or visit the product page here.
Peter Vogel is a system architect and principal in PH&V Information Services. PH&V provides full-stack consulting from UX design through object modeling to database design. Peter also writes courses and teaches for Learning Tree International.