Search code examples
c#razorblazor

Cascading values and EventCallback in Blazor with layout component


I have three components that I want to pass values and eventcallbacks through: First I have MainLayout that is a LayoutComponent

@inherits LayoutComponentBase

<div class="cms-container">
    <div class="header">
        <div class="header-logo">
            CMS
        </div>
        <div class="header-settings">
            <div class="header-settings__alert">
                <i class="fa fa-bell"></i>
                <div class="header-settings__alert--no">
                    <div class="header-settings__alert--no__text">
                        2
                    </div>
                </div>
                <ul class="cms-dropdown">
                    <li class="cms-dropdown__alert">
                        <span class="cms-dropdown__alert--status"><i class="fa fa-info-circle font-danger"></i></span>
                        <span class="cms-dropdown__alert--left">Följ upp händelse</span><span class="cms-dropdown__alert--right">1 dgr</span>
                    </li>
                    <li class="cms-dropdown__alert">
                        <span class="cms-dropdown__alert--status"><i class="fa fa-info-circle font-success"></i></span>
                        <span class="cms-dropdown__alert--left">Tidrapport behöver lämnas in</span><span class="cms-dropdown__alert--right">3 dgr</span>
                    </li>
                </ul>
            </div>
            <div class="header-settings__org">
                <span class="header-settings__org--text">
                    Kanal10.se
                </span>

                <ul class="cms-dropdown">
                    <li class="cms-dropdown__normal">
                        <div class="cms-dropdown__normal--icon">
                            <i class="fa fa-tasks"></i>
                        </div>
                        <div class="cms-dropdown__normal--text">Uppgifter</div>

                    </li>
                    <li class="cms-dropdown__normal">
                        <div class="cms-dropdown__normal--icon">
                            <i class="fa fa-users"></i>
                        </div>
                        <div class="cms-dropdown__normal--text">Användare</div>
                    </li>
                </ul>
            </div>

            <div class="header-settings__account">
                <i class="fa fa-user"></i>
                <ul class="cms-dropdown">
                    <li class="cms-dropdown__normal">
                        <div class="cms-dropdown__normal--icon">
                            <i class="fa fa-user"></i>
                        </div>
                        <div class="cms-dropdown__normal--text">Min profil</div>
                    </li>
                    <li class="cms-dropdown__normal">
                        <div class="cms-dropdown__normal--icon">
                            <i class="fa fa-money-check"></i>
                        </div>
                        <div class="cms-dropdown__normal--text">Lönebesked</div>
                    </li>
                </ul>
            </div>
            <div class="header-settings__system">
                <div class="header-settings__system--icon">
                    <i class="fa fa-cog"></i>
                </div>
                <ul class="cms-dropdown">
                    <li class="cms-dropdown__normal">
                        <div class="cms-dropdown__normal--icon">
                            <i class="fa fa-bars"></i>
                        </div>
                        <div class="cms-dropdown__normal--text">Menyer</div>
                    </li>
                    <li class="cms-dropdown__normal">
                        <div class="cms-dropdown__normal--icon">
                            <i class="fa fa-users"></i>
                        </div>
                        <div class="cms-dropdown__normal--text">Användare</div>
                    </li>
                    <li class="cms-dropdown__normal">
                        <div class="cms-dropdown__normal--icon">
                            <i class="fa fa-shield-alt"></i>
                        </div>
                        <div class="cms-dropdown__normal--text">Behörighetsgrupper</div>
                    </li>

                </ul>
            </div>
        </div>
    </div>
    <div class="LeftNav">
        <NavMenu />
    </div>
    <TelerikRootComponent>
        <div class="cms-content">

            @Body

        </div>
    </TelerikRootComponent>
    <div class="footer">
        <div class="footer-body">
            <div class="footer-body__left">

            </div>
            <div class="footer-body__right">
                <CascadingValue Value="visible">
                    <SaveAndCancelBtns OnBtnAction="?" />
                </CascadingValue>
            </div>
        </div>
    </div>
</div>

<BlazoredToasts Position="Blazored.Toast.Configuration.ToastPosition.TopCenter"
                Timeout="3"
                IconType="IconType.FontAwesome"
                SuccessClass="success-toast-override"
                SuccessIcon="fa fa-thumbs-up" />



@code {
    private bool visible = false;
}

Then I have a child component called Buttons.razor

@if (visible == true)
{
    <div class="footer-body__right--item">
        <button class="btn btn-primary" @onclick="Save" type="submit">Save</button>
    </div>
    <div class="footer-body__right--item">
        <button class="btn btn-cancel" @onclick="Cancel">Cancel</button>
    </div>
}
       [Parameter] public EventCallback<string> OnBtnAction { get; set; }
        [Parameter] public bool visible { get; set; }

        private void Save()
        {
            OnBtnAction.InvokeAsync("Save");
        }

        private void Cancel()
        {
            OnBtnAction.InvokeAsync("Cancel");
        }
    }

And then I have the component razor page called pageDetail.razor

@page "/pages/detail/{Id}"
@page "/pages/detail"

<div class="wrapper">
    <div class="cms-content__title">
        <div class="cms-content__title-left">
            Page Detail
        </div>
    </div>
    <div class="cms-content__page">
        <div class="cms-content__page-all">
            @if (cmsPage == null)
            {
                <ErrorMessage Message="Loading..." />
            }
            else
            {
        <div class="col-6">
            <EditForm Model="cmsPage" OnValidSubmit="SavePage">
                <DataAnnotationsValidator />
                <ValidationSummary />

                <div class="form-group">
                    <label for="PageTitle">Page Title</label>
                    <InputText @bind-Value="@cmsPage.PageTitle" class="form-control" id="PageTitle"></InputText>
                </div>
                <div class="form-group">
                    <label for="PageTitle">Page Descr</label>
                    <InputTextArea @bind-Value="@cmsPage.PageDescr" class="form-control" id="PageTitle"></InputTextArea>
                </div>
                <div class="form-group">
                    <label for="PageTitle">Page Url</label>
                    <InputText @bind-Value="@cmsPage.UrlName" class="form-control" id="PageTitle"></InputText>
                </div>
                <div class="form-group">
                    <label for="PageTitle">Page Type</label><br />
                    <TelerikComboBox Data="@cmsPageTypes" TextField="PageType" Width="100%" ValueField="PageTypeId" @bind-Value="selectedValue">
                    </TelerikComboBox>
                    <ValidationMessage For="@(() => cmsPage.PageTypeId)" />
                </div>
                <div class="form-group">
                    <label for="PageTitle">Parent Page</label><br />
                    <TelerikTreeView Data="@cmsPages" OnItemClick="@OnItemClickHandler">
                        <TreeViewBindings>
                            <TreeViewBinding IdField="PageId" TextField="PageTitle" ParentIdField="ParentId" HasChildrenField="false"></TreeViewBinding>
                        </TreeViewBindings>
                    </TelerikTreeView>
                </div>

                <button class="btn btn-primary" type="submit">Save</button>
                <button class="btn btn-danger" @onclick="DeletePage">Delete</button>
                <button class="btn btn-info" @onclick="BackToList">Back To List</button>

            </EditForm>
        </div>
               
            }
        </div>
    </div>
</div>

@code{
    "not sure how to implement the event button action"
}

What I want is to:

  1. On PageDetail set a parameter for visible and if it is true show the buttons in Buttons.razor or hide them
  2. When clicking on a buttton in buttons.razor handle the event on PageDetail.razor for the EditForm there

The thing is how to handle the component that is a LayoutComponent. Not sure how it fits in the logic.

As you can see I have started implementing it but am not sure of how to connect the different parts.

Grateful for help! Peter


Solution

  • I'd recommend looking at this article: https://chrissainty.com/3-ways-to-communicate-between-components-in-blazor/ but you're part of the way there by using the <CascadingParameter> in your layout.

    In your case I'm not keen on using a boolean visible as a cascading parameter, it's a bit simplistic. The cascading value is also in the wrong place for this as it needs to wrap both the @Body render and the buttons.

    I'd create a state class and cascade that class to any child that wants to use it, by wrapping the @Body and the <SaveAndCancelBtns ..>

    <CascadingValue Value="buttonState" >
        <TelerikRootComponent>
            <div class="cms-content">
    
                @Body
    
            </div>
        </TelerikRootComponent>
        <div class="footer">
            <div class="footer-body">
                <div class="footer-body__left">
    
                </div>
                <div class="footer-body__right">
                    <SaveAndCancelBtns />
                </div>
            </div>
        </div>
    </CascadingValue>
    
    

    You also need to add a value in the layout code:

      ButtonStates buttonState = new ButtonStates();
    

    The ButtonStates class should provide the values and events you need, e.g.

    public class ButtonStates 
    {
        public bool Visible { get; private set; }
    
        // etc
    }
    

    Rather than put all the code here I've created a simple sample repo on github for you: https://github.com/conficient/CascadingStateExample

    Note that I've just used a simple state container and event to pass events from it. EventCallback<T> is specifically used to wire up events between Razor components.