The Telerik UI for Blazor DataGrid provides a set of lifecycle events that you can use to not only manage updates, adds, and deletes but extend the grid with additional functionality—like an undo button, for example.
The DataGrid in Telerik UI for Blazor provides a set of lifecycle events that you can use to manage updates, adds, and deletes made through the grid. To take advantage of those events, you just have to do just two things: write the code that updates the collection driving your grid and provide the UI controls that allow the user to trigger the events you put the code in. The grid will take care of the UI-related work for you. Once you’ve done that, though, it doesn’t take much code to leverage these events to implement more sophisticated functionality, including an undo button.
Before taking advantage of the update events (OnEdit, OnCreate, etc.) you need to set up the DataGrid to allow the user to trigger the events. The first step is to assign methods to the ends as this markup does (it also binds the grid to a collection in a field called MyData and uses the grid’s @ref attribute to tie the grid to a field called theGrid):
<TelerikGrid Data="@MyData"
Pageable="true" PageSize="10"
@ref="theGrid"
OnCancel="@Canceling"
OnCreate="@Creating"
OnDelete="@Deleting"
OnEdit="@Editing"
OnUpdate="@Updating">
With the events wired up, you next need to add the UI elements that the user will interact with. To trigger adding a new row to the grid (and, eventually, raise the OnCreate event), you’ll need to include a GridToolBar element within your TelerikGrid element.
Within the toolbar, you’ll use a GridCommandButton, with its Command attribute set to Add, to trigger adding new rows to the grid. You can supply, between the GridCommandButton’s open and close tags, whatever text you want to appear in the resulting toolbar button. The icon attribute will let you assign one of the standard Kendo icons to the toolbar item. The markup for a typical toolbar with an add button looks like this:
<GridToolBar>
<GridCommandButton Command="Add" Icon="add">Add Employee</GridCommandButton>
</GridToolBar>
To support editing and deleting individual rows, you’ll need to add a GridCommandColumn within the GridColumns element of the TelerikGrid. Within that GridCommandColumn, you can add buttons to support the edit and delete activities, like this:
<GridColumns>
<GridCommandColumn>
<GridCommandButton Command="Edit" Icon="edit">Edit</GridCommandButton>
<GridCommandButton Command="Delete" Icon="delete">Delete</GridCommandButton>
Clicking the button with its Command attribute set to Edit will put the row in edit mode. At that point, you’ll want the command button column to display an Update button (to begin the process of saving your changes) and a Cancel button (to exit edit mode without making changes). You can do that by adding Save and Cancel buttons to the GridCommandColumn element and setting these buttons’ ShowInEdit attribute to true to have them only appear when the row is in edit mode:
<GridCommandButton Command="Save" Icon="save"
ShowInEdit="true">Update</GridCommandButton>
<GridCommandButton Command="Cancel" Icon="cancel"
ShowInEdit="true">Cancel</GridCommandButton>
</GridCommandColumn>
In your code, to support that markup, you need the two fields that hold the data driving the grid and the field tied to the grid’s @ref attribute:
List<Employee> MyData;
TelerikGrid<Employee> theGrid;
You’re now ready to start putting code in your events. You may need to use all five events but odds are you’ll only need these three:
All of these events are passed GridCommandEventArgs parameter which has an Item property that holds the object the user is updating, adding, or deleting.
Typical code for an update method consists of finding the location of the matching object in the collection and replacing it with the object passed in the GridCommandEventArgs parameter’s Item property. A basic version of the update method might look like this:
async Task Updating(GridCommandEventArgs e)
{
Employee emp = (Employee) e.Item;
int i = MyData.FindIndex(emp => emp.Id == emp.Id);
MyData[i] = emp;
}
In real life, however, you’ll probably want to validate the data the user entered before making any changes. If the values in the Item property fail validation, you can return control to the user and leave the row in edit mode by setting the GridCommandEventArgs’ IsCancelled property to true before exiting your update method. That’s what this example does when the user leaves the FullName blank:
void Creating(GridCommandEventArgs e)
{
Employee emp = (Employee) e.Item;
if (string.IsNullOrWhiteSpace(emp.FullName))
{
e.IsCancelled = true;
return;
}
int i = MyData.FindIndex(empl => empl.Id == emp.Id);
MyData[i] = emp;
}
If you’re updating a backend data source (i.e. local storage or a web service), then you could perform that update in this method. Alternatively, you might just mark the updated object as changed and perform a batch update of all the flagged items when the user clicks a submit button.
The OnUpate event works with the OnEdit event which is raised when the user clicks on the edit button to put the row in edit mode. You could use the OnEdit event to fetch up-to-date data from your data source (you can’t replace the object in the GridCommandEventArags Item property but you can change its properties). This example, instead, updates a flag on the object to indicate that it’s been edited:
void Editing(GridCommandEventArgs e)
{
((Employee) e.Item).Changed = true;
}
Also tied to the OnUpdate event is the OnCancel event. The OnCancel event is fired when the user clicks the Cancel button while in edit mode (which also causes the row to exit edit mode). As an example, this code sets the object’s Changed property back to false since the user isn’t making any changes:
void Canceling(GridCommandEventArgs e)
{
((Employee) e.Item).Changed = false;
}
When the user clicks the Add button in the toolbar, a new row is added to the grid in edit mode. As with updates, the row has both an Update and Cancel mode. However, when the user clicks the Update button during an add, the OnCreate event is fired. In a method tied to the OnCreate event, you’ll want to add an item to the grid’s data collection. That’s what this example does:
void Updating(GridCommandEventArgs e)
{
MyData.Insert(0, ((Employee) e.Item));
}
As with the update event, you’ll probably want to check for problems with the user’s entries and remain in edit mode if you find a problem. Code like this does the trick:
void Updating(GridCommandEventArgs e)
{
Employee emp = (Employee)e.Item;
if (string.IsNullOrWhiteSpace(emp.FullName))
{
e.IsCancelled = true;
return;
}
MyData.Insert(0, emp);
}
If the user clicks the Cancel button while adding a new item, the OnCancel event is still raised, just like the Update event. Inside the Cancel event, if you want to do something different when adding new objects (as opposed to updating existing objects), you can check the GridCommandEventArgs IsNew property which is set to true when the process of adding an item is cancelled. Upgrading my previous cancel method to handle new items would look like this, for example:
async Task Canceling(GridCommandEventArgs e)
{
if (!e.IsNew)
{
((Employee) e.Item).Changed = false;
}
}
When adding an item, you might want to do some processing before the grid is put in edit mode. For example, you might want to provide a new object with some default values for the user to modify rather than giving them a blank row. There isn’t a grid-level event associated with clicking the Add button in a toolbar, but you can replace the button’s Command attribute with an OnClick attribute set to a lambda expression that calls a method of your own.
Here’s an example of some markup that will call a method named Adding when the user clicks the add button:
<GridCommandButton OnClick="e => Adding(e)" Icon="add">
Add Employee</GridCommandButton>
Like the grid’s lifecycle events, the method called from a GridCommandButton’s OnClick event is passed a GridCommandEventArgs parameter. Removing the Command attribute, however, also suppresses the default behavior of the button so you’ll have to duplicate adding a new, editable row yourself. Fortunately, that’s easy to do: just create a GridState object, set its InsertedItem to your default object, and then merge your modified GridState object into the grid’s current state with the grid’s SetState method.
Here’s some sample code that provides a default Employee object for the user to modify before the object is added in the Creating event:
async Task Adding(GridCommandEventArgs e)
{
GridState<Employee> state = new GridState<Employee>();
state.InsertedItem = new Employee
{
Id = MyData.Max(e => e.Id) + 1,
HireDate = DateTime.Now.Date
};
await theGrid.SetState(state);
}
In the delete event, all you need to do is remove the selected object from the collection. To do that, you just have to pass the Item property of the GridCommandEventArgs to your collection’s Remove method:
async void Deleting(GridCommandEventArgs e)
{
MyData.Remove( (Employee) e.Item);
}
Of course, there may be employees you don’t want to delete. As with the other events, when you find that’s the case, you can just set the IsCancelled property to true before exiting the method. However, unlike updates and adds, cancelling a delete does not call the OnCancel method. Here’s some code that checks to see if employees have any unpaid fines before deleting them:
async void Deleting(GridCommandEventArgs e)
{
Employee emp = (Employee) e.Item;
if (emp.OutStandingFines > 0)
{
e.IsCancelled = true;
return;
}
MyData.Remove(emp);
}
Since it isn’t possible to check with the user before deleting an item in the grid, the decent thing to do is provide some simple undo functionality. Here’s some code that, before deleting an Employee object from the grid, pushes the object (and its position in the collection) onto a stack:
Stack<Employee> deletedEmployees = new Stack<Employee>();
Stack<int> deletePositions = new Stack<int>();
async void Deleting(GridCommandEventArgs e)
{
Employee emp = (Employee) e.Item;
int pos = MyData.FindIndex(e => e.Id == emp.Id);
deletedEmployees.Push(emp);
deletePositions.Push(pos);
MyData.Remove(emp);
}
To provide the undo functionality, you just need to two things: Insert the top item on the stack of deleted employees back into its old position and update the grid’s state:
async void UndoDelete()
{
MyData.Insert(deletePositions.Pop(), deletedEmployees.Pop());
GridState<Employee> state = theGrid.GetState();
await theGrid.SetState(state);
}
The last step in supporting an undo is to provide a button for the user to call this UndoDelete method (you should also make sure that the button is only enabled when there’s something to undo). That button belongs on the grid’s toolbar with the Add button. Here’s the required markup for that:
<GridToolBar>
<GridCommandButton OnClick="e => Adding(e)" Icon="add">
Add Employee</GridCommandButton>
<GridCommandButton OnClick="UndoDelete"
Enabled="@(deletedEmployees.Count() != 0)">Undo</GridCommandButton>
</GridToolBar>
By leveraging the DataGrid’s lifecycle update/add/delete events, you can not only provide the user with a complete environment for making changes to their data, you can build in additional functionality to support them.
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.