Blazor - Right sidebar

Durante lo sviluppo del BackOffice di BadgeApp, un software per lo scambio di feedback,  ho avuto l'esigenza di aggiungere una barra laterale a destra che contenesse del testo e più in generale dei Blazor components.

 

In particolare il mio obbiettivo è stato quello di:

  1. Aggiungere un tasto nella MainLayout quando un determinato component è presente in pagina
  2. Visualizzare il contenuto del component nella right sidebar ma non nella pagina visualizzata

Questo mi consente di aggiungere ad ogni pagina una sezione di "Help" e mostrarla (a destra se presente) quando si clicca il tasto "?". Il contenuto dell' "help" non è solo testuale. In alcuni casi è possibile scaricare un template Excel, in altri la compilazione di un form o altri componenti più o meno complessi.

Funzionamento

Il primo problema è aggiornare la MainLayout poichè il tasto con il punto interrogativo è nella topbar, ma il codice che causa l'aggiornamento della MainLayout è in un componente interno. 

Il secondo problema è inserire il componente da visualizzare nella barra a destra, non visualizzarlo nella pagina interna ma includerlo come child nel componente contenitore della sidebar.

Per ovviare al primo problema ho scritto il servizio "PageLayoutService" che viene iniettato nella MainLayout e passato come CascadingValue al @Body

Nel metodo OnInitializedAsync() viene registrato un handler per l'evento PageLayoutService.OnChange che causa un StateHasChanged() della layout stessa.

NOTA: non mi fa impazzire avere a che fare con gli eventi in Blazor ma per questo articolo va bene così :P

L'evento PageLayoutService.OnChange viene scatenato quando si aggiorna la proprietà "BarContent" di PageLayoutService.

Per il secondo problema, ho risolto creando un componente con il corpo "html" vuoto ma una proprietà di tipo "RenderFragment"

Il codice

Il codice è disponibile sul mio repository di GitHub al seguente indirizzo:

giemma/blazor-right-sidebar (github.com)

La MainLayout diventa:

MainLayout.razor
@using BlazorRightSidebar.Infrastructure
@inherits LayoutComponentBase

<div class="page">
    <div class="sidebar">
        <NavMenu />
    </div>

    <main>
        <div class="top-row px-4">

            @if (PageLayoutService.BarContent != null)
            {
                <div class="main-topbar-button d-inline-block me-2 ms-2" @onclick="ToggleRightbar">
                    <span>Click me!</span>
                </div>
            }

            <a href="https://learn.microsoft.com/aspnet/core/" target="_blank">About</a>
        </div>

        <article class="content px-4">
            <CascadingValue Value="PageLayoutService">
            @Body
            </CascadingValue>
        </article>
    </main>

    @if (PageLayoutService.BarContent != null && RightbarVisible)
    {
        <div class="sidebar-right">
            <RightSiderbar BarFragment="@PageLayoutService.BarContent" />
        </div>
    }


</div>

<div id="blazor-error-ui">
    An unhandled error has occurred.
    <a href="" class="reload">Reload</a>
    <a class="dismiss">🗙</a>
</div>
MainLayout.razor.cs
using BlazorRightSidebar.Infrastructure;
using Microsoft.AspNetCore.Components;

namespace BlazorRightSidebar.Components.Layout
{
    public partial class MainLayout : IDisposable
    {
        [Inject]
        protected PageLayoutService PageLayoutService { get; set; }

        public bool RightbarVisible { get; set; }

        protected override async Task OnInitializedAsync()
        {
            PageLayoutService.OnChange += PageLayoutService_OnChange;
        }

        public void ToggleRightbar()
        {
            RightbarVisible = !RightbarVisible;
            StateHasChanged();
        }

        private void PageLayoutService_OnChange()
        {
            StateHasChanged();
        }

        public void Dispose()
        {
            PageLayoutService.OnChange += PageLayoutService_OnChange;
        }
    }
}

Il componente "RightSiderbar" è:

RightSiderbar.razor


<div class="nav-scrollable">
    <nav class="flex-column ms-2 me-1">

        <h1>Help</h1>

        <div class="help-container">
            @BarFragment
        </div>

    </nav>
</div>

@code{

    [Parameter]
    public RenderFragment BarFragment { get; set; }
}

Il componente "RightSiderbarContent" è:

using BlazorRightSidebar.Infrastructure;
using Microsoft.AspNetCore.Components;

namespace BlazorRightSidebar.Components.Layout
{
    public partial class RightSiderbarContent
    {
        [CascadingParameter]
        public PageLayoutService PageLayoutService { get; set; }

        [Parameter]
        public RenderFragment BarContent { get; set; }

        protected override void OnAfterRender(bool firstRender)
        {
            base.OnAfterRender(firstRender);
        }
        protected override async Task OnInitializedAsync()
        {
            PageLayoutService.BarContent = BarContent;
            StateHasChanged();
        }
    }
}

Il suo corpo è vuoto!

Supponiamo di voler visualizzare il contenuto nella barra a destra solo quando si visualizza il componente "Counter" questo diventa:

@page "/counter"
@rendermode InteractiveServer

<PageTitle>Counter</PageTitle>

<h1>Counter</h1>

<p role="status">Current count: @currentCount</p>

<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>

<BlazorRightSidebar.Components.Layout.RightSiderbarContent>
    <BarContent>
        I'm the counter page!
    </BarContent>
</BlazorRightSidebar.Components.Layout.RightSiderbarContent>

@code {
    private int currentCount = 0;

    private void IncrementCount()
    {
        currentCount++;
    }
}

Buon lavoro!

Se hai voglia di lasciarmi un commento mi trovi su Linkedin.