Creating a Dynamic DataGrid Sourced from Tree Component in Blazor

Introduction

In this article, we will create the DataGrid component, which efficiently fetches data from our previously designed Tree component, discussed in my prior article. This combination of components can be exceptionally handy in various scenarios, such as product listings, file exploration, and organizational charts.

Context

Imagine you're developing an online store for selling mobile phones. Your task is to create a user interface that allows users to navigate through phone manufacturers, operating systems, and individual phone details. To accomplish this, we've already crafted a Tree component that visually organizes phone manufacturers, OS versions, and phone models in a hierarchical manner. Now, the next piece of the puzzle is to showcase the data of each phone and also to arrange the data grouped by manufacturer or operating system. Here's a preview of the application we're going to build.

Figure 1: Tree and DataGrid in action

 

Refreshing the Tree Component

The Tree component provides a hierarchical view of available phone data. It groups phones based on their manufacturers and OS versions, allowing users to expand and collapse sections as depicted in Figure 2.

 

Figure 2: Tree component.

Each node in the tree has two events. The first event handles the expansion and collapse of the node when users click the [+] and [-] buttons. We've already covered this functionality in the previous article.

In this article, we'll create another event that triggers when users click a node. This event is responsible for sending data to other components. The following code snippets show the necessary changes to the Tree component.

<ul class="tree">
    @foreach (var manufacturer in GetDistinctManufacturers())
    {
        <li>
            ...
            <span @onclick="() =>NodeClicked(manufacturer)">
                  @manufacturer
            </span>
            <ul>
                @foreach (var os in GetDistinctOS(manufacturer))
                {
                    <li>
                        ...
                        <span @onclick="() =>NodeClicked(manufacturer,os)">
                              @os
                        </span>
                        <ul>
                            <li>
                                @foreach (var phone in GetPhonesByManufacturerAndOS(manufacturer, os))
                                {
                                    <li @onclick="() => NodeClicked(phone: phone)">
                                        @phone.Name
                                    </li>
                                }
                            </li>
                        </ul>
                    </li>
                }
            </ul>
        </li>
    }
</ul>

@code{

        [Parameter] 
        public EventCallback<List<Phone>> SelectedNodeChanged { get; set; }
        List<Phone> phonesToBeSent;

        void NodeClicked(string manufacturer = "", string os ="", Phone phone = null)
        {
            if (!string.IsNullOrEmpty(os))
            {
                phonesToBeSent = phones.Where(p => p.Manufacturer == manufacturer && p.OS == os).ToList();
            }
            else if (!string.IsNullOrEmpty(manufacturer))
            {
                phonesToBeSent = phones.Where(p => p.Manufacturer == manufacturer).ToList();
            }
              
            else if (phone != null)
            {
                phonesToBeSent = phones.Where(p => p.Name == phone.Name).ToList();
            }
            SelectedNodeChanged.InvokeAsync(phonesToBeSent);
        }
}

Code snippet 1: Tree.razor

Explanation of code snippet 1

The NodeClicked() method is crucial. It manages user interactions when a node in the tree is clicked. Based on the clicked node, it prepares a list of phones to be sent to the parent component. In this case, the parent component is Index.razor. The method also identifies the clicked node's type (manufacturer, operating system, or phone), filters the phone data accordingly, and then triggers the SelectedNodeChanged event with the prepared list of phones.

Let's break down the method step by step:

  1. NodeClicked(string manufacturer = "", string os = "", Phone phone = null): The method can accept three types of parameters: manufacturer, os (operating system), and phone. The parameters have default values of empty strings or null.
  2. The method starts with conditional checks to determine the type of node that was clicked.
    • (!string.IsNullOrEmpty(os)): This check indicates that an operating system node was clicked. It filters the phone data to include phones with the specified manufacturer and os values. The filtered phones are assigned to the phonesToBeSent list.
    • (!string.IsNullOrEmpty(manufacturer)): This check indicates that a manufacturer node was clicked. It filters the phone data to include phones with the specified manufacturer value. Again, the filtered phones are assigned to the phonesToBeSent list.
    • (phone != null): This check indicates that a phone node was clicked. It filters the phone data to include only the clicked phone. The filtered phone is added to the phonesToBeSent list.
  3. After preparing the list of phones based on the clicked node, the method is called SelectedNodeChanged.InvokeAsync(phonesToBeSent) to trigger the SelectedNodeChanged event. This event is typically declared in the parent component, and it allows the parent component to receive the list of phones and update the DataGrid component with the selected data.

The DataGrid Component

The DataGrid component complements the Tree component by displaying detailed information about selected phones. It arranges the data in a tabular format, showcasing attributes like name, manufacturer, operating system, and cost. The DataGrid receives the data from its parent component, Index.razor.

The component receives a filtered list of phones triggered by the NodeClicked() method in the Tree component from Code Snippet 1. In the HTML section, a foreach loop iterates through each phone in the Phones list.

For each phone, a table row (<tr>) is generated.

Inside each row, table cells (<td>) display the phone's attributes: Name, Manufacturer, Operating System, and Cost.

@using TreeAndDataGridComponents.Data

<h3>Data Grid</h3>

@if (Phones != null)
{
    <div class="table-container">
        <table class="table">
            <thead>
                <tr>
                    <th>Name</th>
                    <th>Manufacturer</th>
                    <th>Operating System</th>
                    <th>Cost</th>
                </tr>
            </thead>
            <tbody>
                @foreach (var phone in Phones)
                {
                    <tr>
                        <td>@phone.Name</td>
                        <td>@phone.Manufacturer</td>
                        <td>@phone.OS</td>
                        <td>@phone.Cost</td>
                    </tr>
                }
            </tbody>
        </table>
    </div>
}
else
{
    <p>Loading...</p>
}

@code {
    [Parameter]
    public List<Phone> Phones { get; set; }

}

Code snippet 2: DataGrid.razor

This is how DataGrid would look:

Figure 3: DataGrid component

Bridging the Components in the Index

The Index.razor file is the bridge that connects the Tree and DataGrid components. Both components are integrated within this file. The selected phones from the Tree component are passed to the DataGrid component, ensuring that only relevant data is displayed.

@page "/"
@using TreeAndDataGridComponents.Data
@inject IPhoneService phoneService;

<div class="container">
    <div class="tree-container">
        <Tree SelectedNodeChanged="OnSelectedNodeChanged" />
    </div>
    <div class="datagrid-container">
        <DataGrid Phones="filteredPhones" />
    </div>
</div>

@code {

    List<Phone> filteredPhones = new();

    void OnSelectedNodeChanged(List<Phone> parameters)
    {
        filteredPhones = parameters;
    }
}

Code snippet 3: Index.razor

Explanation of Code Snippet 3

Code Snippet 3 orchestrates the interaction between the Tree and DataGrid components. It sets up the layout for the Index.razor component, placing the Tree and DataGrid components side by side. This code establishes communication between the components through the handling of the SelectedNodeChanged event. When phones are selected in the Tree component, the OnSelectedNodeChanged event updates the filteredPhones list. This list is then used to display the selected phone data within the DataGrid component.

Conclusion

As we wrap up this two-part series, we've explored the creation of interconnected components: the Tree component and the DataGrid component. These components provide a dynamic, user-friendly experience for navigating hierarchical data.

From e-commerce platforms to project management tools, these components have a wide range of applications. I hope this mini-series has shed light on crafting dynamic components in Blazor and their potential uses.

Feel free to download the attached source code.

Looking to dive deeper into Blazor and unlock its full potential? Check out my comprehensive guide "[Blazor Simplified]". Whether you're a beginner or an experienced developer, this book offers practical insights, expert tips, and real-world examples to master Blazor. Take your skills to the next level and elevate your web development projects with the power of Blazor! That's not all! You can also explore the [Official GitHub repository] of the book, where you'll find code samples, supplementary materials, and a community of fellow Blazor enthusiasts. Let's embark on a journey to master Blazor together!


Similar Articles