Build Master Detail CRUD in ASP.NET Core 3.1 Blazor II

Source Image

<<  Build Master Detail CRUD in ASP.NET Core 3.1 Blazor Part One

If you have not been following the article, please go back to the first part of this article.

Download Source Code

In MasterDetailCRUD project > Pages folder, modify the Index.razor file in like so:
 @page "/"  
 @inject HttpClient Http  
 <h1>Customer Orders</h1>  
 <div>  
   <a class="btn btn-primary" href="create"><span class="oi oi-plus" aria-hidden="true"></span> Create</a>  
   <p></p>  
 </div>  
 @if (orders == null)  
 {  
   <p><em>Loading...</em></p>  
 }  
 else  
 {  
   <table class="table table-bordered">  
     <thead>  
       <tr>  
         <th>Order No.</th>  
         <th>Customer</th>  
         <th>Payment Method</th>  
         <th>Order Date</th>  
         <th>Total</th>  
       </tr>  
     </thead>  
     <tbody>  
       @foreach (var order in orders)  
       {  
         <tr>  
           <td>  
             <span class="oi @order.DetailIcon" aria-hidden="true" @onclick="@(() => ChangeDetailState(order))"></span>&nbsp;  
             @order.OrderNumber  
           </td>  
           <td>@order.CustomerName</td>  
           <td>@order.PaymentMethod</td>  
           <td>@order.DateCreated.ToString("ddd, MMM dd yyyy")</td>  
           <td>@order.Total.ToString("#,##0.00;(#,##0.00)")</td>  
         </tr>  
         if (order.OrderDetails.Count() > 0)  
         {  
           <tr>  
             <td colspan="5">  
               @if (order.ShowDetail)  
               {  
                   <table class="table table-bordered">  
                     <thead>  
                       <tr>  
                         <th>Item</th>  
                         <th>Quantity</th>  
                         <th>Price</th>  
                       </tr>  
                     </thead>  
                     <tbody>  
                       @foreach (var detail in order.OrderDetails)  
                       {  
                         <tr>  
                           <td>@detail.Item</td>  
                           <td>@detail.Quantity</td>  
                           <td>@detail.Price</td>  
                         </tr>  
                       }  
                     </tbody>  
                   </table>  
               }  
             </td>  
           </tr>  
         }  
       }  
     </tbody>  
   </table>  
 }  
 @code {  
   private List<OrderViewModel> orders;  
   protected override async Task OnInitializedAsync()  
   {  
     orders = await Http.GetJsonAsync<List<OrderViewModel>>("orders");  
   }  
   void ChangeDetailState(OrderViewModel order)  
   {  
     order.ShowDetail = !order.ShowDetail;  
     if (order.ShowDetail) order.DetailIcon = "oi-caret-bottom";  
     else order.DetailIcon = "oi-caret-right";  
   }  
 }  

Modify the _Import.razor file to import the necessary namespaces like so:
  @using System.Net.Http  
  @using Microsoft.AspNetCore.Components.Forms  
  @using Microsoft.AspNetCore.Components.Routing  
  @using Microsoft.AspNetCore.Components.Web  
  @using Microsoft.JSInterop  
  @using MasterDetailCRUD  
  @using MasterDetailCRUD.Shared  
  @using MasterDetailCRUD.Models  

Build the solution, the run the application, you should have:
Source image


Add new class named JSExtension.cs to the project, modify the file like so:
 using Microsoft.JSInterop;  
 using System.Threading.Tasks;  
 namespace MasterDetailCRUD  
 {  
   public static class JSExtension  
   {  
     public static ValueTask<bool> Confirm(this IJSRuntime jSRuntime, string message)  
     {  
       return jSRuntime.InvokeAsync<bool>("confirm", message);  
     }  
     public static ValueTask Alert(this IJSRuntime jSRuntime,string message)  
     {  
       return jSRuntime.InvokeVoidAsync("alert", message);  
     }  
   }  
 }  
The above uses the JSInterop capability to invoke the confirm and alert functions of JavaScript.
These methods will be used later in blazor components to prompt and display alert messages respectively.

Add Components
The first component that will be created is DialogTemplate. This component is used as template for creating modals and dialog.
In the MasterDetailCRUD project > shared folder, create DialogTemplate.razor and modify the file like so:
 @if (Show)  
 {  
   <div class="dialog-container">  
     <div class="dialog">  
       @ChildContent  
       </div>  
   </div>  
 }  
 @code {  
   [Parameter] public bool Show { get; set; }  
   [Parameter] public RenderFragment ChildContent { get; set; }  
 }  
The component takes two parameters, a bool to determine if the dialog is to be displayed and the RenderFragment which act as a placeholder for the contents.
Modify the site.css, add the folowing:
 .dialog-container {  
   position: absolute;  
   top: 0;  
   bottom: 0;  
   left: 0;  
   right: 0;  
   background-color: rgba(0,0,0,0.5);  
   z-index: 2000;  
   display: flex;  
   animation: dialog-container-entry 0.2s;  
 }  
 @keyframes dialog-container-entry {  
   0% {  
     opacity: 0;  
   }  
   100% {  
     opacity: 1;  
   }  
 }  
 .dialog {  
   background-color: white;  
   box-shadow: 0 0 12px rgba(0,0,0,0.6);  
   display: flex;  
   flex-direction: column;  
   z-index: 2000;  
   align-self: center;  
   margin: auto;  
   width: 700px;  
   max-height: calc(100% - 3rem);  
   animation: dialog-entry 0.4s;  
   animation-timing-function: cubic-bezier(0.075, 0.820, 0.165, 1.000);  
   border-radius:6px;  
 }  
 @keyframes dialog-entry {  
   0% {  
     transform: translateY(30px) scale(0.95);  
   }  
   100% {  
     transform: translateX(0px) scale(1.0);  
   }  
 }  
 .dialog-title {  
   background-color: #444;  
   color: #fff2cc;  
   padding: 1.3rem 2rem;  
 }  
   .dialog-title h2 {  
     color: white;  
     font-size: 1.4rem;  
     margin: 0;  
     font-family: 'Bahnschrift', Arial, Helvetica, sans-serif;  
     text-transform: uppercase;  
     line-height: 1.3rem;  
   }  
 .dialog-body {  
   flex-grow: 1;  
   padding: 0.5rem 3rem 1rem 0;  
 }  
 .dialog-buttons {  
   height: 4rem;  
   flex-shrink: 0;  
   display: flex;  
   align-items: center;  
   background-color: #eee;  
   padding: 0 1rem;  
 }  
 .dialog-body > div {  
   display: flex;  
   margin-top: 1rem;  
   align-items: center;  
 }  
 .dialog-body input, .dialog-body select {  
   flex-grow: 1;  
   width: unset;  
 }  
 .dialog-body .size-label {  
   min-width: 110px;  
   text-align: right;  
 }  
next, we add the DetailDialog.razor  component in MasterDetailCRUD > Shared folder and modify like so:
 @inject IJSRuntime JS  
 <div class="dialog-title">  
   <h3>Detail</h3>  
 </div>  
 <div class="dialog-body">  
   <form class="form-horizontal">  
     <div class="col-md-12">  
       <div class="form-group">  
         <label for="Item">Item</label>  
         <select class="form-control col-md-4" @bind="@SelectedValue" name="Item">  
           @foreach (var item in PriceList)  
           {  
             <option value="@item.Key">@item.Key</option>  
           }  
         </select>  
       </div>  
       <div class="form-group">  
         <label for="price">Price</label>  
         <input type="text" name="price" class="form-control col-md-4" @bind="@orderDetail.Price" disabled="disabled" />  
       </div>  
       <div class="form-group">  
         <label for="quantity">Quantity</label>  
         <input type="number" name="quantity" @bind="@orderDetail.Quantity" class="form-control col-md-4" min="0" max="1000" />  
       </div>  
       <div class="form-group">  
         <input type="button" value="@BtnText" class="btn btn-primary" @onclick="@AddToList" />  
         <input type="button" value="close" class="btn btn-danger" @onclick="OnCancel" />  
       </div>  
     </div>  
   </form>  
 </div>  
 @code {  
   Dictionary<string, decimal> PriceList = new Dictionary<string, decimal>()  
   {  
     {"",0.00m },  
     {"Apple Watch",130.00m },  
     {"Google Pixel",290.20m },  
     {"IphoneX Max",783.10m },  
     {"Samsung S10",310.00m },  
     {"Oppo Reno 8",400.30m },  
   };  
   public string SelectedValue  
   {  
     get { return orderDetail.Item; }  
     set  
     {  
       orderDetail.Item = value;  
       SelectChange();  
     }  
   }  
   [Parameter] public OrderDetail orderDetail { get; set; }  
   [Parameter] public List<OrderDetail> orderDetails { get; set; }  
   [Parameter] public EventCallback OnCancel { get; set; }  
   [Parameter] public string BtnText { get; set; }  
   private async void AddToList()  
   {  
     string mesage = string.Empty;  
     if (string.IsNullOrEmpty(orderDetail.Item) || orderDetail.Price <= 0 || orderDetail.Quantity <= 0) return;  
     if (orderDetail.OrderDetailId > 0)  
     {  
       var data = orderDetails.FirstOrDefault(m => m.OrderDetailId == orderDetail.OrderDetailId);  
       data.Price = orderDetail.Price;  
       data.Quantity = orderDetail.Quantity;  
       data.Item = orderDetail.Item;  
       mesage=$"You updated {orderDetail.Item}";  
     }  
     else  
     {  
       var data = orderDetails.FirstOrDefault(m => m.Item == orderDetail.Item);  
       if (data != null)  
       {  
         data.Quantity += orderDetail.Quantity;  
         mesage=$"You updated {orderDetail.Item}";  
       }  
       else  
       {  
         orderDetails.Add(new OrderDetail()  
         {  
           Item = orderDetail.Item,  
           Price = orderDetail.Price,  
           Quantity = orderDetail.Quantity,  
           OrderDetailId = (orderDetails.Count > 0 ? orderDetails.Select(m => m.OrderDetailId).Max() : 0) + 1  
         });  
        mesage=$"You added {orderDetail.Item} to your order";  
       }  
     }  
     Reset();  
     StateHasChanged();  
     await JS.Alert(mesage);  
   }  
   private void Reset()  
   {  
     orderDetail = new OrderDetail();  
   }  
   private void SelectChange()  
   {  
     orderDetail.Price = PriceList[orderDetail.Item];  
   }  
 }  
The component is used to render the detail form. In the code section, we define a dictionary of PriceList, input parameters and methods. The AddToList is use to add new order detail to the collection, the Reset method resets the  form to its initial state.
Create the CreateForm.razor component in the Shared folder, modify like so:
 <div class="row">  
   <form class="form-horizontal">  
     <div class="form-group">  
       <label for="orderNumber">Order Number</label>  
       <input type="text" name="orderNumber" @bind="@order.OrderNumber" class="form-control" disabled="disabled" />  
     </div>  
     <div class="form-group">  
       <label for="customer">Customer</label>  
       <input type="text" name="customer" @bind="@order.CustomerName" class="form-control" />  
     </div>  
     <div class="form-group">  
       <label for="paymentmethod">Payment Method</label>  
       <select name="paymentmethod" @bind="@order.PaymentMethod" class="form-control">  
         <option value=""></option>  
         <option value="CASH">Cash</option>  
         <option value="NIP">NIP</option>  
         <option value="ETF">ETF</option>  
         <option value="DirectDebit">Direct Debit</option>  
       </select>  
     </div>  
     <div class="form-group">  
       <label for="total">Total</label>  
       <input type="text" name="total" value="@OrderTotal" class="form-control" disabled="disabled" />  
     </div>  
   </form>  
 </div>  
 @code {  
   [Parameter] public Order order { get; set; }  
   [Parameter] public decimal OrderTotal { get; set; }  
 }  

The component takes two parameters, the Order and OrderTotal of type decimal.
Lastly,create CreateDetailTable.razor component in the Shared folder and modify like so:
 @inject IJSRuntime JS  
 <table class="table table-bordered">  
   <thead>  
     <tr>  
       <th>Item</th>  
       <th>Price</th>  
       <th>Quantity</th>  
       <th>#</th>  
     </tr>  
   </thead>  
   <tbody>  
     @foreach (var order_Detail in orderDetails)  
     {  
       <tr>  
         <td>@order_Detail.Item</td>  
         <td>@order_Detail.Price</td>  
         <td>@order_Detail.Quantity</td>  
         <td>  
           <button class="btn btn-primary btn-sm" @onclick="@(()=>EditItem(order_Detail))">edit</button>|  
           <button class="btn btn-sm btn-danger" @onclick="@(() => RemoveItem(order_Detail))">delete</button>  
         </td>  
       </tr>  
     }  
   </tbody>  
 </table>  
 @code {  
   [Parameter] public List<OrderDetail> orderDetails { get; set; }  
   [Parameter] public EventCallback<OrderDetail> _EditItem { get; set; }  
   private async void RemoveItem(OrderDetail detail)  
   {  
     if (await JS.Confirm($"Remove {detail.Item} from your order?"))  
     {  
       orderDetails.Remove(detail);  
       await JS.Alert($"You Removed {detail.Item} from your order");  
     }  
   }  
   private void EditItem(OrderDetail detail)  
   {  
     _EditItem.InvokeAsync(detail);  
   }  
 }  
Now, lets put all the components we have created into use.
In the MasterDetailCRUD >Pages folder, create Create.razor file and modify like so:
 @page "/create"  
 @inject HttpClient httpclient  
 @inject NavigationManager navmanager  
   <div class="row">  
     <h3>Create</h3>  
   </div>  
   <div class="row">  
     <button class="btn btn-success" @onclick="SubmitData">Save</button>&nbsp;|&nbsp;  
     <button class="btn btn-warning" @onclick="@(()=>navmanager.NavigateTo(""))">Close</button>  
     <p>&nbsp;</p>  
   </div>  
   <CreateForm order="@order" OrderTotal="@OrderTotal"></CreateForm>  
 <div class="row">  
   <div class="col-md-5 ml-0">  
     <div class="offset-8 mb-2">  
       <button class="btn btn-primary" style="float:right" @onclick="DisplayDetail"><span class="oi oi-plus" aria-hidden="true"></span> Add</button>  
     <p></p>  
     </div>  
     <div>  
       <CreateDetailTable orderDetails="@orderDetails" _EditItem="@EditItem"> </CreateDetailTable>  
     </div>  
   </div>  
 </div>  
 <DialogTemplate Show="@ShowDialog">  
   <DetailDialog BtnText="@BtnText"   
          OnCancel="CloseDetail"   
          orderDetails="@orderDetails"   
          orderDetail="@orderDetail">  
   </DetailDialog>  
 </DialogTemplate>  
 @code {  
   private bool ShowDialog;  
   private Order order = new Order();  
   private OrderDetail orderDetail=new OrderDetail();  
   private List<OrderDetail> orderDetails = new List<OrderDetail>();  
   private decimal OrderTotal { get => orderDetails.Sum(m => m.TotalPerItem); }  
   public string BtnText { get; set; } = "add";  
   protected override Task OnInitializedAsync()  
   {  
     var ran = new Random();  
     order.OrderNumber = $"ITL{ran.Next(9999, 99999)}";  
     return base.OnInitializedAsync();  
   }  
   private void DisplayDetail()  
   {  
     orderDetail=new OrderDetail();  
     BtnText = "add";  
     ShowDialog = true;  
     StateHasChanged();  
   }  
   private void CloseDetail()  
   {  
     ShowDialog = false;  
   }  
   private void EditItem(OrderDetail detail)  
   {  
     BtnText = "update";  
     ShowDialog = true;  
     orderDetail = detail;  
   }  
   private async Task SubmitData()  
   {  
     if (string.IsNullOrEmpty(order.CustomerName) ||  
       string.IsNullOrEmpty(order.PaymentMethod) ||  
       orderDetails.Count() <= 0)  
       return;  
     order.Total = OrderTotal;  
     order.OrderDetails = orderDetails;  
     var res = await httpclient.PostJsonAsync<int>("Orders", order);  
     if (res > 0)  
       navmanager.NavigateTo("");  
   }  
 }  
Build, and run the application, create new customer order and save.

source image


Build-Master-Detail-CRUD-in-ASP.NET-Core-3.1-Blazor image


Build-Master-Detail-CRUD-in-ASP.NET-Core-3.1-Blazor image

Thank you for your time.

I hope the tutorial is helpful. 

Kindly leave your comments


For further reading,



<<  Part One

Build Master Detail CRUD in ASP.NET Core 3.1 Blazor II Build Master Detail CRUD in ASP.NET Core 3.1  Blazor II Reviewed by Akintunde Toba on March 01, 2020 Rating: 5

No comments:

Home Ads

Powered by Blogger.