© The Author(s), under exclusive license to APress Media, LLC, part of Springer Nature 2023
T. LitvinaviciusExploring Blazorhttps://doi.org/10.1007/978-1-4842-8768-2_7

7. Practice Tasks for Client-Side Blazor

Taurius Litvinavicius1  
(1)
Kaunas, Lithuania
 

You have already learned a lot from this book, but to truly learn, you have to practice. In this chapter, you will build a couple of projects.

Specifically, in this chapter, you will be able to practice the following:
  • Blazor UI bindings

  • Local storage in Blazor

  • Event handling in Blazor

Task 1

Your first task will include several simple exercises to practice the general syntax of Blazor, as well as a more complex exercise where you will need to use components and local storage.

Description

You will create a Blazor client-side application that will allow you to make calculations according to the instructions provided. The application will include the following attributes:

  • Age calculator: An age calculator simply allows you to enter two dates and return the difference in years.

  • Cylinder surface area: Use the following formula:

  • A = 2πrh + 2πr^2

where
  • A = area

  • r = radius

  • h = height

This will allow you to calculate all the variables from the rest of them.

  • Rectangular area: Use the following formula:

  • A = a * b

where
  • A = area

  • a = side a

  • b = side b

This will allow you to calculate all the variables from the rest of them.

This app will allow the calculations to be saved locally for later use.

  • Trapezoid area calculator: Use the following formula:

  • A = (a + b) / 2 * h

where
  • A = area

  • a = base 1

  • b = base 2

  • h = height

This will allow you to calculate all the variables from the rest of them.

  • Area of triangle calculator: Use the following formula:

  • A = (h * b) / 2

where
  • A = area

  • h = height

  • b = base length

This will allow you to calculate all the variables from the rest of them.

  • Rectangular area calculator: Use the following formula:

  • A = a * b

where
  • A = area

  • a = side a

  • b = side b

This calculation is quite basic, but there is an additional task to go along with it. You will need to locally save each calculation if the user wants them to be saved. Then, the calculation history will be displayed in the page, and the user will be able to select one of them and insert the variable values from the record . Here are the extra steps:
  1. 1.

    Create calculator pages based on the formulas provided.

     
  2. 2.

    Add a local save feature for the calculation made for the rectangular area.

     

Solution

Our solution will be separated into several parts, for each part of description. As always, this is just one of many possible solutions rather than the only one.

Every calculation has its own page (see Figure 7-1), except for the rectangular calculation, where we also have a component for the history.

A screenshot lists a menu of a task 1 client and pages. It has connected services, dependencies, properties, index, main layout, and wwwroot.

Figure 7-1

Project structure for the solution

Age Calculator Solution

Let’s create the age calculator in this section.

As you can see, the age calculator is quite straightforward (see Listing 7-1); we simply have two variables of type datetime, and we bind them with the appropriate input fields.
@page "/agecalculator"
<p>Birthdate: <input  @onchange="@((args) => { birthdate = Convert.ToDateTime((string)args.Value); Calculate(); })"  type="date"/></p>
<p>To: <input @bind="@To"  type="date"/></p>
<p><button @onclick="@InsertToday">Insert today</button></p>
<p>Age: @age</p>
@code {
    DateTime birthdate = new DateTime(1965,12,15);
    DateTime To = DateTime.Now;
    double age;
    void InsertToday()
    {
        To = DateTime.Now;
    }
    void Calculate()
    {
        age = birthdate.Subtract(To).TotalDays / 365;
    }
}
Listing 7-1

Age Calculator Page

The more interesting part of this is how we execute the Calculate method . The task does not have any specific requirements, but you can either create a simple button and execute it on a click/tap or do something more exciting like we have here. On a change of the value, we assign the new value to the variable, and with that, we also execute calculations. This is a good quick way to handle more than one operation in a single event.

Cylinder Surface Area Calculator

Let’s look into calculating the surface area of cylinder in this section. For the cylinder area calculator (Listing 7-2), we mostly have some basic calculations being made.
@page "/cylindersurfaceareacalculator"
<h3>Cylinder Surface Area Calculator</h3>
<p><input class="inputstyle"  @bind="@A" placeholder="A"/> = 2π * <input @bind="@r" class="inputstyle" placeholder="r"> ∗ <input @bind="@h" class="inputstyle" placeholder="h">
     + 2π * <input @bind="@r" class="inputstyle" placeholder="r" /><sup>2</sup></p>
<p>A = 2πrh + 2πr<sup>2</sup></p>
<p>A - area <button @onclick="@Calculate_A">calculate A</button></p>
<p>r - radius <button @onclick="@Calculate_r">calculate r</button></p>
<p>π - @Math.PI</p>
<p>h - height <button @onclick="@Calculate_h">calculate h</button></p>
@code {
    double r = 0;
    double h = 0;
    double A = 0;
    void Calculate_A()
    {
        A = 2 * Math.PI * r * h + 2 * Math.PI * Math.Pow(h,2);
    }
    void Calculate_r()
    {
        r = 0.5 * Math.Sqrt(Math.Pow(h, 2) + 2 * (A / Math.PI)) - (h / 2);
    }
    void Calculate_h()
    {
        h = (A / (2 * Math.PI * r)) - r;
    }
}
Listing 7-2

Cylinder Surface Area Calculator Page

The trick in this task is to put things in proper places and not make a mess of things, such as having the same formula for two different outputs. First, we display our formula in basic text format, and then we also have our formula with input fields in it. For each variable, we have different methods that calculate a value, and for each of them, we have different buttons that execute those methods.

Trapezoid Area Calculator

Let’s look into calculating the area of trapezoid in this section. The trapezoid calculator will be similar to the one created in Listing 7-2. Again, the difficulty is not in finding something new, but rather in properly assigning all the variables where they fit (Listing 7-3).
@page "/trapezoidareacalculator"
<p>@A = (<input @bind="@a"   class="inputstyle" placeholder="a"/> +
    <input @bind="@b"   class="inputstyle" placeholder="b">) / 2 * <input @bind="@h"   class="inputstyle" placeholder="h"></p>
<p>A = (a + b) / 2 * h</p>
<p>A - area</p>
<p>a - base 1</p>
<p>b - base 2</p>
<p>h - height</p>
<p><button @onclick="@Calculate"></button></p>
@code {
    double A;
    double a;
    double b;
    double h;
    void Calculate()
    {
        A = (a + b) / 2 * h;
    }
}
Listing 7-3

Trapezoid Area Calculator Page

Triangle Area Calculator

Let’s look into calculating the area of triangle in this section. Our triangle calculation (Listing 7-4) is once again quite basic, and this is just one way to do it. You can, of course, do it on different events , or you may want to just display the output in a way where you would not have an interactive formula.
@page "/triangleareacalculator"
    <p>
        <input class="inputstyle" @bind="@A"   placeholder="A"> =
        (<input class="inputstyle" @bind="@h" placeholder="h"> * <input class="inputstyle" @bind="@b" placeholder="b">) / 2
    </p>
<p>A = (h ∗ b) / 2</p>
<p>A - area <button @onclick="@Calculate_A">Calculate</button></p>
<p>h - height <button @onclick="@Calculate_h">Calculate</button></p>
<p>b - base length <button @onclick="@Calculate_b">Calculate</button></p>
@code {
    double A;
    double h;
    double b;
    void Calculate_A()
    {
        A = (h * b) / 2;
    }
     void Calculate_h()
    {
        h = A * 2 / b;
    }
     void Calculate_b()
    {
         b = A * 2 / h;
    }
}
Listing 7-4

Triangle Area Calculator Page

Rectangle Area Calculator

Let’s look at calculating the area of a rectangle in this section (see Listing 7-5, 7-6). In this case, we can begin with setting up a data model for the results, as this will be used for both calculating and saving results. The result set contains data for all the variables in the calculation and the ID.
public class RectangularAreaHistoryModel
    {
        public string id { get; set; }
        public double A { get; set; }
        public double a { get; set; }
        public double b { get; set; }
    }
Listing 7-5

Calculation History Data Model

<p>A: @item.A</p>
<p>side a: @item.a</p>
<p>side b: @item.b</p>
<p><button @onclick="@(async () => await OnSelect.InvokeAsync(item.id))">Pick</button></p>
@code {
    [Parameter]
    public RectangularAreaHistoryModel item { get; set; }
    [Parameter]
    public EventCallback<string> OnSelect  { get; set; }
}
Listing 7-6

 Calculation History Item Component

For the results, we have a component that will be displayed in a list. This simply takes a parameter for a record that will be displayed and a pick button that will invoke the OnSelect event, which will then trigger the display of those variables in the inputs for the calculator Listing 7-7.
@page "/rectangularareacalculator"
@inject IJSRuntime js
<p>
    <input @bind="@currentcalculation.A" class="inputstyle" placeholder="A"> =
    <input @bind="@currentcalculation.a" class="inputstyle" placeholder="a">
    *
    <input @bind="@currentcalculation.b" class="inputstyle" placeholder="b">
</p>
<p>A = a * b</p>
<p>A - area <button @onclick="@Calculate_A">Calculate</button></p>
<p>a - side a <button @onclick="@Calculate_a">Calculate</button></p>
<p>b - side b <button @onclick="@Calculate_b">Calculate</button></p>
<p style="color:red;">@error</p>
<p>Save calculations <input type="checkbox" @bind="@savecalculation" /></p>
<p>History: </p>
@foreach (var item in calculationhistory)
{
    <RectangularAreaHistoryItemComponent  item="@item" OnSelect="Selected"></RectangularAreaHistoryItemComponent>
}
@code {
    string error;
    List<RectangularAreaHistoryModel> calculationhistory = new List<RectangularAreaHistoryModel>();
    bool savecalculation;
    RectangularAreaHistoryModel currentcalculation = new RectangularAreaHistoryModel();
    async Task Calculate_A()
    {
        try
        {
            currentcalculation.A = currentcalculation.a * currentcalculation.b;
            if (savecalculation)
            {
                await SaveCalculation();
            }
        }
        catch (Exception e)
        {
            error = "Something went wrong, try again";
        }
    }
    async Task Calculate_a()
    {
        try
        {
            currentcalculation.a = currentcalculation.A / currentcalculation.b;
            if (savecalculation)
            {
                await  SaveCalculation();
            }
        }
        catch (Exception e)
        {
            error = "Something went wrong, try again";
        }
    }
    async Task Calculate_b()
    {
        try
        {
            currentcalculation.b = currentcalculation.A / currentcalculation.a;
            if (savecalculation)
            {
                await SaveCalculation();
            }
        }
        catch (Exception e)
        {
            error = "Something went wrong, try again";
        }
    }
    async Task SaveCalculation()
    {
        try
        {
            currentcalculation.id = DateTime.UtcNow.Ticks.ToString();
            calculationhistory.Add(currentcalculation);
            string json = System.Text.Json.JsonSerializer.Serialize(calculationhistory);
            await js.InvokeAsync<object>("localStorage.removeItem", "rectareacalculationhistory");
            await js.InvokeAsync<string>("localStorage.setItem", "rectareacalculationhistory", json);
        }
        catch (Exception e)
        {
            error = "Something went wrong, try again" + e.Message;
        }
    }
    protected override async Task OnInitializedAsync()
    {
        try
        {
            string json = await js.InvokeAsync<string>("localStorage.getItem", "rectareacalculationhistory");
            calculationhistory = System.Text.Json.JsonSerializer.Deserialize<List<RectangularAreaHistoryModel>>(json);
        }
        catch (Exception e)
        {
        }
    }
    Task Selected(string id)
    {
        currentcalculation = calculationhistory.Find(x => x.id == id);
        return Task.CompletedTask;
    }
}
Listing 7-7

 Calculator Page

The calculation for rectangular variables is the same as others; we have variables set through input boxes, and then on submit the calculation is made based on formulas, in the methods Calculate_A, Calculate_b, and Calculate_a.

The difference here is that those methods also contain a check for a Boolean: savecalculation. It can be set to true or false using the checkbox, and if it is true, the calculation is saved. To save the calculation, the result data is serialized into a JSON string, which then is inserted into local storage. Notice that for the ID ticks of the current UTC date are used. Also, the ID can be reused as a date stamp for when the result was saved. Alternatively, the GUID could be used. Then, for reading the records, the system simply retrieves the JSON string from local storage and deserializes that to the result list.

Finally, since it suits us well, for the navigation we do not clear most of the defaults (see Listing 7-8); we just set up our navlinks according to the pages that we have. In real-world projects , it is a good idea to change the designs if you take this approach.
<div class="top-row pl-4 navbar navbar-dark">
    <a class="navbar-brand" href="">BlazorApp1</a>
    <button class="navbar-toggler" @onclick="ToggleNavMenu">
        <span class="navbar-toggler-icon"></span>
    </button>
</div>
<div class="@NavMenuCssClass" @onclick="ToggleNavMenu">
    <ul class="nav flex-column">
        <li class="nav-item px-3">
            <NavLink class="nav-link" href="" Match="NavLinkMatch.All">
                <span class="oi oi-home" aria-hidden="true"></span> Home
            </NavLink>
        </li>
        <li class="nav-item px-3">
            <NavLink class="nav-link" href="cylindersurfaceareacalculator">
                Cylinder surface area calculator
            </NavLink>
        </li>
        <li class="nav-item px-3">
            <NavLink class="nav-link" href="triangleareacalculator">
                triangle area calculator
            </NavLink>
        </li>
        <li class="nav-item px-3">
            <NavLink class="nav-link" href="agecalculator">
                age calculator
            </NavLink>
        </li>
        <li class="nav-item px-3">
            <NavLink class="nav-link" href="rectangularareacalculator">
                Rectangle area calculator
            </NavLink>
        </li>
        <li class="nav-item px-3">
            <NavLink class="nav-link" href="trapezoidareacalculator">
                trapezoid area calculator
            </NavLink>
        </li>
    </ul>
</div>
@code {
    bool collapseNavMenu = true;
    string NavMenuCssClass => collapseNavMenu ? "collapse" : null;
    void ToggleNavMenu()
    {
        collapseNavMenu = !collapseNavMenu;
    }
}
Listing 7-8

Navigation Page

Task 2

For this task, we’ll build an invoice generator. The invoice is basically a written request from one business to another for a payment. The invoice generator should state the company details, items, values, and total values. Our version will be simplified.

Description

Since our invoice is simplified, we will have only a couple of company details, and the biggest part of the development will be a sales item. The user should be able to add as many items as they want, and the total each time should be added to the total of the invoice.

Here are the inputs:
  • ID

  • Description

  • Total (generated from items)

Here are the sales items:
  • Description

  • Price

  • Tax

  • Total

In the sales items, you also need to provide the total for each item. Use a component for a sales item. Note that you are not required to do any actual PDF, PNG, or other visual outputs of the invoice.

Solution

Just like the previous task , this is not the only solution possible, but your project will be similar.

Since we need only one page for this application, we will not have too many files; rather, we will work on the index page and create a single component for a sales item, which we will explore later. The layout is also basic (Listing 7-9 and Figure 7-2); we have removed all the default stuff and left just a skeleton layout.

A screenshot lists a menu of web applications, datamodelspages, and shared. It has invoice models, salesitemmodel, components, and imports.

Figure 7-2

Project structure for the solution

@inherits LayoutComponentBase
    <div style="width:100%;float:left;">
        @Body
    </div>
Listing 7-9

Main Layout

For our invoice, we need to create a couple of data models (see Listings 7-10 and 7-11). While the task does not require us to generate the files, we still want to prepare for that. The invoice simply contains an ID, description, total, and then the sales items added to it. The sales item is quite basic as well; we have an item ID, description, price, tax, and total. Everything here is very generic, and the interesting part will begin in the index page.
using System.Collections.Generic;
namespace WebApplication1.DataModels
{
    public class InvoiceModel
    {
        public string id { get; set; }
        public string description { get; set; }
        public double total { get; set; }
        public List<SalesItemModel> salesitems
        {
            get;  set;
        }
}
}
Listing 7-10

Invoice Model (InvoiceModel.cs)

namespace WebApplication1.DataModels
{
    public class SalesItemModel
    {
        public string itemid { get; set; }
        public string description { get; set; }
        public double price { get; set; } = 0;
        public double tax { get; set; } = 0;
        public double total { get; set; } = 0;
    }
}
Listing 7-11

Sales Item Model (SalesItemModel.cs)

@page "/"
@inject IJSRuntime js
    <div style="float:left;width:100%;">
        <p>Total: @total</p>
        <p>Total tax: @totaltax</p>
        <p>Description</p>
        <p><textarea @bind="@currentinvoice.description"></textarea></p>
        <p>Sales items</p>
        <p><button @onclick="@AddNewItem">Add</button></p>
    </div>
@foreach (var item in currentinvoice.salesitems)
{
    <WebApplication1.Pages.Components.SalesItem OnDescription Change="ChangeForItemDescription" OnValueChange="ChangeForItemValue" OnTotalChange="ChangeForItemTotal" OnTaxChange="ChangeForItemTax" OnRemove="RemoveItem" @key="item.itemid" _itemid="@item.itemid"></WebApplication1.Pages.Components.SalesItem>
}
@code {
    DataModels.InvoiceModel currentinvoice = new DataModels.InvoiceModel() { id = Guid.NewGuid().ToString(), salesitems = new List<DataModels.SalesItemModel>() };
    double total = 0;
    double totaltax = 0;
    void AddNewItem()
    {
        currentinvoice.salesitems.Add(new DataModels.SalesItemModel() { itemid = Guid.NewGuid().ToString() });
    }
    void RemoveItem( string id)
    {
        currentinvoice.salesitems.Remove(currentinvoice.salesitems.Where(x => x.itemid == id).ToArray()[0]);
    }
    void ChangeForItemDescription(KeyValuePair<string,string> args)
    {
        currentinvoice.salesitems.Find(x => x.itemid == args.Key).description = args.Value;
    }
    void ChangeForItemValue(KeyValuePair<string,double> args)
    {
        currentinvoice.salesitems.Find(x => x.itemid == args.Key).price = args.Value;
    }
    void ChangeForItemTax(KeyValuePair<string,double> args)
    {
        currentinvoice.salesitems.Find(x => x.itemid == args.Key).tax = args.Value;
        totaltax = 0;
        foreach (var item in currentinvoice.salesitems)
        {
            totaltax += item.tax;
        }
    }
    void ChangeForItemTotal(KeyValuePair<string,double> args)
    {
        currentinvoice.salesitems.Find(x => x.itemid == args.Key).total = args.Value;
        total = 0;
        foreach (var item in currentinvoice.salesitems)
        {
            total += item.total;
        }
    }
}
Listing 7-12

 Main Page (Index.razor)

<div style="float:left;width:100%;">
    <p><button @onclick="@(async () => await OnRemove.InvokeAsync(_itemid))">Remove</button></p>
    <p>description:</p>
    <p><input @onchange="@(async (args) => await OnDescriptionChange.InvokeAsync(new KeyValuePair<string, string>(_itemid, (string)args.Value)))"></p>
    <p>value:</p>
    <p><input  @onchange="@((args) =>  ReeveluateAfterValueChange(Convert.ToDouble(args.Value)))" ></p>
    <p>tax:</p>
    <p><input @onchange="@((args) =>  ReeveluateAfterTaxChange(Convert.ToDouble(args.Value)))" ></p>
    <p>total:</p>
    <p>@total</p>
    <p>@_itemid</p>
</div>
@code  {
    [Parameter]
    public string _itemid { get; set; }
    [Parameter]
    public EventCallback<string> OnRemove { get; set; }
    [Parameter]
    public EventCallback<KeyValuePair<string,string>> OnDescriptionChange { get; set; }
    [Parameter]
    public EventCallback<KeyValuePair<string,double>> OnValueChange { get; set; }
    [Parameter]
    public EventCallback<KeyValuePair<string,double>> OnTaxChange { get; set; }
    [Parameter]
    public EventCallback<KeyValuePair<string,double>> OnTotalChange { get; set; }
    double total;
    double value;
    double tax;
    async void ReeveluateAfterValueChange(double newvalue)
    {
        value = newvalue;
        await OnValueChange.InvokeAsync(new KeyValuePair<string, double>(_itemid,value));
        total = value + (tax / 100) * value;
        await OnTotalChange.InvokeAsync(new KeyValuePair<string, double>(_itemid, total));
    }
     async void ReeveluateAfterTaxChange(double newvalue)
    {
        tax = newvalue;
        await OnValueChange.InvokeAsync(new KeyValuePair<string, double>(_itemid,value));
        total = value + (tax / 100) * value;
        await OnTotalChange.InvokeAsync(new KeyValuePair<string, double>(_itemid, total));
    }
}
Listing 7-13

Sales Item Component (SalesItem.razor)

While the invoice page and items component may seem complex, when you look closely, they use only the most basic features of Blazor. The most difficult part here is dealing with the components and attempting to calculate the total when there are changes. The component (Listing 7-13) simply takes an ID, because when it gets generated, all the values are empty. The most important parts here are the callbacks; as you can see, all the input fields have one, and they all act differently. The description is the simplest one, as it only returns the ID and the new description value. The tax and value are more complex; we first need to establish methods, which will calculate the values in the component and display them in the component directly. Then, these methods invoke our callbacks; to get further, we need to switch to the page. Our page (shown in Listing 7-12) handles the callbacks differently, but for the most part, the idea is to assign the values to the list of items, because that is what would be generated for some kind of visual format.

Summary

Both of these tasks not only give you an opportunity to practice your skills but also showed you how client-side Blazor can make your business more efficient. For any of these tasks, there is absolutely no need to go to the server side, which saves you a lot of money. Using components simplifies development and keeps your code cleaner.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.138.135.80