Countries List - Editing
So far there hasn't been a way to add, edit or delete countries - for development purposes the sample data was entered directly in SQL Management Studio. This next section shows how this can be accomplished. We will be following the basic model described by Alan Simpson in his YouTube videos, but the user will access the functions using the Syncfusion DataGrid Custom Toolbar. (There is a standard toolbar that, as well as Add, Edit and Delete that we cannot use, does include some functions that could be useful, such as Search and Excel Export. The standard toolbar isn't covered here.)
Custom Toolbar
Adding a Custom Toolbar requires the following steps:
- Adding 'Toolbar="Toolbaritems"' to the <SfGrid> section. 'Toolbaritems' is a variable declared later.
- Adding <GridEvents OnToolbarClick="ToolbarClickHandler" TValue="Countries"> to the grid HTML, where "Countries" is the data model.
- Adding "private List<ItemModel>
Toolbaritems = new List<ItemModel> (); at the top of the @Code section to declare the Toolbaritems variable. For each Toolbaritem adding code similar to "Toolbaritems.Add(new ItemModel() { Text = "Add", TooltipText = "Add a new country", PrefixIcon = "e-add" });" to the OnInitializedSync procedure. Adding a procedure to handle the toolbar click event, (ToolbarClickHandler in the code below). At this stage the Add, Edit and Delete don't do anything.
The complete code is shown below:
@page "/"
@using BlazorCountries.Data
@inject ICountriesService CountriesService
<h3>Countries List</h3>
<SfGrid @ref="FirstGrid"
DataSource="@countries"
AllowSorting="true"
AllowResizing="true"
AllowFiltering="true"
AllowPaging="true"
AllowReordering="true"
AllowExcelExport="true"
ContextMenuItems="@(new List<object>() {"AutoFit", "AutoFitAll", "SortAscending", "SortDescending","Copy", "ExcelExport", "CsvExport", "FirstPage", "PrevPage","LastPage", "NextPage"})"
Toolbar="Toolbaritems">
<GridPageSettings PageSize="5"></GridPageSettings>
<GridEvents OnToolbarClick="ToolbarClickHandler" TValue="Countries"></GridEvents>
<GridColumns>
<GridColumn Field="@nameof(Countries.CountryId)"
HeaderText="Country Id"
TextAlign="@TextAlign.Left"
Width="20">
</GridColumn>
<GridColumn Field="@nameof(Countries.CountryName)"
HeaderText="Country Name"
TextAlign="@TextAlign.Left"
Width="90">
</GridColumn>
</GridColumns>
</SfGrid>
@code {
private SfGrid<Countries> FirstGrid;
private List<ItemModel> Toolbaritems = new List<ItemModel>();
// Create an empty list, named countries, of empty Counties objects.
IEnumerable<Countries> countries;
protected override async Task OnInitializedAsync()
{
//Populate the list of countries objects from the Countries table.
countries = await CountriesService.CountriesGetAll();
Toolbaritems.Add(new ItemModel() { Text = "Add", TooltipText = "Add a new country", PrefixIcon = "e-add" });
Toolbaritems.Add(new ItemModel() { Text = "Edit", TooltipText = "Edit selected country", PrefixIcon = "e-edit" });
Toolbaritems.Add(new ItemModel() { Text = "Delete", TooltipText = "Delete selected country", PrefixIcon = "e-delete" });
}
public async Task ExcelExport()
{
await this.FirstGrid.ExcelExport();
}
public async Task ToolbarClickHandler(Syncfusion.Blazor.Navigations.ClickEventArgs args)
{
if (args.Item.Text == "Add")
{
//Code for adding goes here
}
if (args.Item.Text == "Edit")
{
//Code for editing
}
if (args.Item.Text == "Delete")
{
//code for deleting
}
}
}If we run the application now, it should look like this:
Add a Country
Following Alan Simpson's method, the add function will use a separate page. To use a separate page the Index page must be amended with the following changes:
- Inject the NavigationManager to enable navigation to a different page.
- Declare a variable for CountryId, so that it can be assigned a value of 0 to signify that this is a new country
- and in the ToolbarClickHandler procedure add the code to set the CountryId to 0 and navigate to the new CountriesAdd page.
The revised code for Index.razor is shown below.
@page "/"
@using BlazorCountries.Data
@inject ICountriesService CountriesService
@inject NavigationManager NavigationManager
<h3>Countries List</h3>
<SfGrid @ref="FirstGrid"
DataSource="@countries"
AllowSorting="true"
AllowResizing="true"
AllowFiltering="true"
AllowPaging="true"
AllowReordering="true"
AllowExcelExport="true"
ContextMenuItems="@(new List<object>() {"AutoFit", "AutoFitAll", "SortAscending", "SortDescending","Copy", "ExcelExport", "CsvExport", "FirstPage", "PrevPage","LastPage", "NextPage"})"
Toolbar="Toolbaritems">
<GridPageSettings PageSize="5"></GridPageSettings>
<GridEvents OnToolbarClick="ToolbarClickHandler" TValue="Countries"></GridEvents>
<GridColumns>
<GridColumn Field="@nameof(Countries.CountryId)"
HeaderText="Country Id"
TextAlign="@TextAlign.Left"
Width="20">
</GridColumn>
<GridColumn Field="@nameof(Countries.CountryName)"
HeaderText="Country Name"
TextAlign="@TextAlign.Left"
Width="90">
</GridColumn>
</GridColumns>
</SfGrid>
@code {
private SfGrid<Countries> FirstGrid;
private List<ItemModel> Toolbaritems = new List<ItemModel>();
private int? CountryID;
// Create an empty list, named countries, of empty Counties objects.
IEnumerable<Countries> countries;
protected override async Task OnInitializedAsync()
{
//Populate the list of countries objects from the Countries table.
countries = await CountriesService.CountriesGetAll();
Toolbaritems.Add(new ItemModel() { Text = "Add", TooltipText = "Add a new country", PrefixIcon = "e-add" });
Toolbaritems.Add(new ItemModel() { Text = "Edit", TooltipText = "Edit selected country", PrefixIcon = "e-edit" });
Toolbaritems.Add(new ItemModel() { Text = "Delete", TooltipText = "Delete selected country", PrefixIcon = "e-delete" });
}
public async Task ExcelExport()
{
await this.FirstGrid.ExcelExport();
}
public void ToolbarClickHandler(Syncfusion.Blazor.Navigations.ClickEventArgs args)
{
if (args.Item.Text == "Add")
{
CountryID = 0;
NavigationManager.NavigateTo($"/countriesadd/{CountryID}");
}
if (args.Item.Text == "Edit")
{
//Code for editing
}
if (args.Item.Text == "Delete")
{
//code for deleting
}
}
}In Visual Studio open the Pages folder and add a new Blazor component. Call it 'CountriesAdd.razor' and copy and paste the code below.
@using BlazorCountries.Data
@page "/countriesadd/{CountryId:int}"
@inject ICountriesService CountriesService
@inject NavigationManager NavigationManager
<h1>Add a Country</h1>
<SfDialog IsModal="true" Width="500px" ShowCloseIcon="false" Visible="true">
<h5>Add a Country</h5>
<br />
<EditForm Model="@countries" OnValidSubmit="@CountriesSave">
<div>
<SfTextBox Enabled="true" Placeholder="Country"
FloatLabelType="@FloatLabelType.Always"
@bind-Value="countries.CountryName"></SfTextBox>
</div>
<br /><br />
<div class="e-footer-content">
<div class="button-container">
<button type="submit" class="e-btn e-normal e-primary">Save</button>
<button type="button" class="e-btn e-normal" @onclick="@Cancel">Cancel</button>
</div>
</div>
</EditForm>
</SfDialog>
@code {
// Create a new, empty Countries object
Countries countries = new Countries();
[Parameter]
public int CountryId { get; set; }
// Executes OnValidSubmit of EditForm above.
protected async Task CountriesSave()
{
await CountriesService.CountriesInsert(countries);
NavigationManager.NavigateTo("/");
}
//Executes if user clicks the Cancel button.
void Cancel()
{
NavigationManager.NavigateTo("/");
}
}The essential parts of this code are:
- The routing (@page) points to a new page with the extension of the CountryId
- The HTML section uses the Syncfusion dialog as a main control. This is set to be modal to prevent the user from leaving without either saving or cancelling. Within the dialog is an EditForm which calls '@CountriesSave' on a valid submission. Within the EditForm is a Syncfusion 'SfTextBox' bound to the country's name. Lastly, the HTML section has two buttons to control the Save or Cancel events.
- The code section handles the CountriesSave event by calling CountriesInsert service and then navigating back to the main page.
Running the application and clicking the 'Add' button on the Countries toolbar will open a new page, as shown, allowing the user to add a new country and save or cancel; clicking either button will return the user to the Countries List.
Editing a Country
There are two possible approaches to adding the ability to edit a country's name (the user will not be permitted to change the CountryId). Either we could add a separate page following the pattern of the CountriesAdd.razor page, or alternatively, as the page will look almost identical to the Add a Country page we could adapt the CountriesAdd page. In the spirit of not duplicating code, we will take the latter course.
If we are to use the same page, I feel compelled to re-name it as 'CountriesAddEdit.razor'. In Solution Explorer, right-click 'CountriesAdd.razor' and select 'Rename'; if a prompt appears asking if you want to rename references, answer 'No'. In the renamed file, change the @page at the top to '@page "/countriesaddedit/{CountryId:int}"
Having renamed the page, 'Index.razor' must be amended to reflect this change. Fortunately it is just one line, NavigationManager.NavigateTo($"/countriesaddedit/{CountryID}");
To check progress, save all changes and run the application.
In 'Index.razor' a couple more changes need to be made. In order to edit a particular country the system needs to detect which row has been clicked by the user, and then having detected the row needs to pass that to 'countriesaddedit'.
Add 'RowSelected="RowSelectHandler"' to <GridEvents> and the following code at the end of the code section. This sets CountryID to the id of the selected row.
public void RowSelectHandler(RowSelectEventArgs<Countries> args)
{
//{args.Data} returns the current selected records.
CountryID = args.Data.CountryId;
}With CountryID set to the selected row, this can be passed by the click event on the 'Edit' button to the CountriesAddEdit page. Change the Edit section to the code shown below. The 'if (CountryID > 0)' checks that a row has been selected. We will add a message later to tell the user if no row has been selected, but at this stage if no row has been selected clicking Edit will just do nothing.
if (args.Item.Text == "Edit")
{
if (CountryID > 0)
{
NavigationManager.NavigateTo($"/countriesaddedit/{CountryID}");
}
}The CountriesAddEdit page will require more of a change.
To start with, we will change the <h1> header for the page and <h5> header within the dialog to either 'Add a Country' or 'Edit a Country' depending on the page's mode. To do this:
- Declare a string variable 'pagetitle' and set to a default of 'Add a Country'.
- Change the <h1> and <h5> text to '
<h1>@pagetitle<h1>' - Add an 'OnInitalize' procedure to determine if Add or Edit was selected and change pagetitle accordingly.
<h1>@pagetitle</h1>
<SfDialog IsModal="true" Width="500px" ShowCloseIcon="false" Visible="true">
<h5>@pagetitle</h5.
public string pagetitle = "Add a Country";
//Executes on page open, sets headings and gets data in the case of edit
protected override async Task OnInitializedAsync()
{
if (CountryId == 0)
{
pagetitle = "Add a Country";
}
else
{
pagetitle = "Edit a Country";
}
}In the case of Edit we also need to populate the form with the record from the Countries table. This can be incorporated by modifying the above code to this:
public string pagetitle = "Add a Country";
//Executes on page open, sets headings and gets data in the case of edit
protected override async Task OnInitializedAsync()
{
if (CountryId == 0)
{
pagetitle = "Add a Country";
}
else
{
pagetitle = "Edit a Country";
countries = await CountriesService.CountriesGetOne(CountryId);
}
}The next change is to handle the different behaviour of @CountriesSave depending on whether a new record needs to be inserted into the database or the existing one being edited needs to be updated. This can also be achieved by an 'if' statement similar to the one used on page initialisation. The code is shown below; in both cases the page closes and navigation is returned to the home page.
// Executes OnValidSubmit of EditForm above.
protected async Task CountriesSave()
{
if (CountryId == 0)
{
await CountriesService.CountriesInsert(countries);
}
else
{
await CountriesService.CountriesUpdate(countries);
}
NavigationManager.NavigateTo("/");
}Deleting a Country
We could simply (or not so simply) add some code to delete a record in the ToolbarClickHandler procedure, but it would be better practise to warn the user that a record is about to be deleted and ask for confirmation. We can do this by adding a dialog and then reacting to the user's response.
To add the dialog insert the following code in the HTML section after the </SfGrid> tag. We are again using the Syncfusion SfDialog control. Note that this control is given a @ref of 'DialogDelete' and that depending on the user's response either @ConfirmDeleteYes or @ConfirmDeleteNo is called by the OnClick event.
<SfDialog @ref="DialogDelete" IsModal="true" Width="250px" ShowCloseIcon="true" Visible="false">
<DialogTemplates>
<Header> Confirm Delete </Header>
<Content> Please confirm that you want to delete this record </Content>
</DialogTemplates>
<DialogButtons>
<DialogButton Content="Delete" IsPrimary="true" OnClick="@ConfirmDeleteYes" />
<DialogButton Content="Cancel" IsPrimary="false" OnClick="@ConfirmDeleteNo" />
</DialogButtons>
</SfDialog>
Declare the 'DialogDelete' in the code section with SfDialog DialogDelete;
In the ToolbarClickHandler for Delete, insert DialogDelete.Show();
Add the following at the end of the code section to handle the 'ConfirmDeleteNo' and 'ConfirmDeleteYes'
public async void ConfirmDeleteNo()
{
await DialogDelete.Hide();
}
public async void ConfirmDeleteYes()
{
await CountriesService.CountriesDelete(CountryID.GetValueOrDefault()); //This deletes the record
await DialogDelete.Hide();
// Both following lines required to refresh the grid
countries = await CountriesService.CountriesGetAll();
StateHasChanged();
}
No Record Selected?
When either editing or deleting a record the user must select a record from the DataGrid or else nothing happens and the user will wonder what is happening. We can again use a Dialog warning the user to select a record before continuing.
Add the following in the HTML section under the last SfDialog section.
<SfDialog @ref="DialogNoRecordSelected" IsModal="true" Width="250px" ShowCloseIcon="true" Visible="false">
<DialogTemplates>
<Header> Warning! </Header>
<Content> You must select a country </Content>
</DialogTemplates>
<DialogButtons>
<DialogButton Content="OK" IsPrimary="true" OnClick="@CloseDialogNoRecordSelected" />
</DialogButtons>
</SfDialog>Add the declaration of the SfDialog in the code section. SfDialog DialogNoRecordSelected; and insert the following after both the 'Edit' and 'Delete' in the ToolBarHandler
else
{
//No record has been selected
DialogNoRecordSelected.Show();
}And add the following at the end of the code section to close the 'No Record Selected' warning dialog.
private async Task CloseDialogNoRecordSelected()
{
await this.DialogNoRecordSelected.Hide();
}Full Code
Click the above heading for the full code for both Index.razor and CountriesAddEdit.razor (at this stage!)




