Blazor Walkthrough: Act IV - rochellew/csci_1260_spring2025 GitHub Wiki

Digital Tome of Magical Flora & Fauna

😈 "You've finally made it through the wizards' challenges and met me, the MYSTERIOUS TRAVELER. I have seen many wondrous things, and thank you for helping me to catalogue them in this digital tome.

In this final tutorial, you will craft one more view that uses a Dictionary structure to render all of the compiled data from the previous tutorials into one sortable page.

Best of luck, developer..."

NPC art by William H. Rochelle

Task 1: Updating OrganismService

😈 "Unlike those benevolent wizards, I'm not going to give you as much code for free. It will be up to you to fill in the blanks (denoted by comments in the provided code) to make what I want. Let's get started by updating the OrganismService class in TomeBlazorProject."

Step 1: Adding a new Method

  1. In TomeBlazorProject, navigate to OrganismService.cs.
  2. Copy the following method, and paste it into OrganismService.cs somewhere below the constructor.
public async Task<Dictionary<Rarity, List<IOrganism>>> GetOrganismsGroupedByRarity()
{
    // TODO: asynchronously get the List<Plant> from the appropriate method and store the reference in a variable called 'plants'
    // TODO: asynchronously get the List<Creature> from the appropriate method and store the reference in a variable called 'creatures'

    // TODO: Combine both the List<Plant> and List<Creature> into one List<IOrganism> called 'organisms'

    return organisms
        .GroupBy(o => o.Rarity)
        .ToDictionary(group => group.Key, group => group.ToList());
}

😈 Infernal Insight: Make sure that you're naming your List objects as specified in the instructions to make sure the provided code works as intended.

The provided code converts the organisms List to a Dictionary<Rarity, List<IOrganism>>. Take a moment to look at how LINQ and lambda expressions are being used to do this.

Task 2: Creating a new View

😈 "We now have the means to generate the data, represented with a Dictionary and returned via the method in OrganismService. Let's now add a .razor page to generate a view. I'll give you most of the code here, but will have you complete one line yourself for the hell of it."

Step 1: Creating the Razor Component

  1. In /components/pages, create a new Razor component called Organisms.razor.
  2. Update your navbar component to include a link to /organisms.
  3. Copy and paste the following code, replacing the default contents of Organisms.razor, if necessary.

HTML/Razor Content

@page "/organisms"
@inject TomeBlazorProject.Services.OrganismService OrganismService

<h2>Mysterious Traveler's Field Guide</h2>

@if (GroupedOrganisms == null)
{
    <p>Loading organisms...</p>
}
else
{
    <div class="controls-bar">
        <div class="rarity-buttons">
            @foreach (var rarity in GroupedOrganisms.Keys.OrderBy(r => r))
            {
                <button @onclick="() => SelectRarity(rarity)"
                        class="@(SelectedRarity == rarity ? "active-button" : "")">
                    @rarity
                </button>
            }
        </div>

        <div class="filter-sort-controls">
            <input @bind="SearchText" placeholder="Filter by name..." />
            <select @bind="SortOption">
                <option value="name">Sort by Name</option>
                <option value="type">Sort by Type</option>
            </select>
        </div>
    </div>

    @if (SelectedRarity != null)
    {
        // TODO: Use the method that returns filtered and sorted organisms, and store them in a variable called 'filtered'

        <h3>@SelectedRarity Organisms (@filtered.Count)</h3>

        @if (filtered.Count == 0)
        {
            <p><em>No matches found.</em></p>
        }
        else
        {
            <div class="organism-list">
                @foreach (var organism in filtered)
                {
                    <div class="organism-card">
                        <img src="@organism.ImageUrl" alt="@organism.Name" />
                        <h4>@organism.Name</h4>
                        <p>@organism.Description</p>

                        @if (organism is Plant plant)
                        {
                            <p><strong>Bloom Season:</strong> @plant.BloomSeason</p>
                            <p><strong>Medicinal:</strong> @(plant.IsMedicinal ? "Yes" : "No")</p>
                            <p><strong>Poisonous:</strong> @(plant.IsPoisonous ? "Yes" : "No")</p>
                        }
                        else if (organism is Creature creature)
                        {
                            <p><strong>Habitat:</strong> @creature.Habitat</p>
                            <p><strong>Hostile:</strong> @(creature.IsHostile ? "Yes" : "No")</p>
                        }
                    </div>
                }
            </div>
        }
    }
}

Razor @code Section:

@code {
    private Dictionary<Rarity, List<IOrganism>>? GroupedOrganisms;
    private Rarity? SelectedRarity;
    private string SearchText = "";
    private string SortOption = "name";

    protected override async Task OnInitializedAsync()
    {
        GroupedOrganisms = await OrganismService.GetOrganismsGroupedByRarity();
        SelectedRarity = GroupedOrganisms.Keys.FirstOrDefault();
    }

    private void SelectRarity(Rarity rarity)
    {
        SelectedRarity = rarity;
        SearchText = ""; // Optional: clear search on group change
    }

    private List<IOrganism> GetFilteredAndSortedOrganisms()
    {
        if (GroupedOrganisms is null || SelectedRarity is null)
            return new();

        var organisms = GroupedOrganisms[SelectedRarity];

        if (!string.IsNullOrWhiteSpace(SearchText))
        {
            organisms = organisms
                .Where(o => o.Name.Contains(SearchText, StringComparison.OrdinalIgnoreCase))
                .ToList();
        }

        organisms = SortOption switch
        {
            "type" => organisms.OrderBy(o => o is Creature ? "Creature" : "Plant")
                               .ThenBy(o => o.Name)
                               .ToList(),
            _ => organisms.OrderBy(o => o.Name).ToList()
        };

        return organisms;
    }
}

Step 2: Add Styling

  1. Navigate to app.css in /wwwroot.
  2. Add styling for the following CSS classes:
    • controls-bar
    • filter-sort-controls
      • filter-sort-controls input
      • filter-sort-controls select

Step 3: Add More Data

  1. Navigate to creatures.json and plants.json in /wwwroot/data (or wherever your .json files are located).
  2. Add at least five more creatures to creatures.json.
    • Make sure that each field has a value (e.g., each creature needs a unique image URL)
  3. Add at least five more plants to plants.json.
    • Make sure that each field has a value (e.g., each plant needs a unique image URL)

Conclusion

😈 "Thank you for your help, intrepid developer. Hopefully, this guide has served you well in your journey as a programmer. Keep up the good work, and remember, I'll be seeing you again!

⚠️ **GitHub.com Fallback** ⚠️