How to Build A Reusable Dialog With Blazor

Rasheed K Mozaffar - Aug 28 '23 - - Dev Community

Introduction πŸ‘‹πŸ»

Hello there! I got an interesting tutorial for you in this post. Here, we'll be building a nice looking, fully reusable dialog component using Blazor for interactivity, and plain css for styling, the reason I didn't use something like Tailwind Css is because you'll have to add Tailwind to the projects where you want to use the dialog, so for that, I went with standard Css so you can subsequently put the dialog in a Razor Class Library, just reference the library from your Blazor project, and add the component where ever you like, and it'll work straight out of the box.

🌟Note: If you want to get the source code for the component, you can check out the Github repository for this post on my Github here: https://github.com/rasheed-k-mozaffar/CoolDialog

Beginning The Project βž•

For the sake of simplicity, I created a new Blazor WebAssembly Empty App project to use for this tutorial, now if you want to follow along exactly what I'll be doing, then begin by doing the following:

  • Create an empty Blazor Wasm project
  • Enable Css isolation by uncommenting this statement inside index.html, the root file inside wwwroot:



<link href="CoolDialog.styles.css" rel="stylesheet" />



Enter fullscreen mode Exit fullscreen mode

πŸ’‘ I named my project CoolDialog, hence the reason you're seeing it in the code, this line comes commented out by default, so if you want to enable Css isolation, make sure to uncomment it.

  • Add the CDN link for Bootstrap Icons

πŸ’‘ This step is optional, but I wanted to use an icon for the close dialog button, which you'll see in a minute, but if you want to follow along, head to https://icons.getbootstrap.com/ ,scroll all the way down, copy the CDN Link, and paste it in the head of index.html

The head of index.html should look something like this:




<head>
    <meta charset="utf-8" />
    <title>CoolDialog</title>
    <base href="/" />
    <link href="css/app.css" rel="stylesheet" />

    <!--Add Bootstrap Icons CDN Link-->
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.5/font/bootstrap-icons.css">
    <!--Enable Css Isolation-->
    <link href="CoolDialog.styles.css" rel="stylesheet" />
</head>



Enter fullscreen mode Exit fullscreen mode
  • Because we like clean structure, we'll create a new folder called Components, it'll be the place in which we save Razor Components along with their Component Scoped Css files.

The project structure should look something like this:

Project files structure

We're Ready to Start πŸš€

Inside our Components directory, create two files, one for the dialog, and another for its styles.

We'll begin by adding the Html skeleton of the dialog, add this following markup to the Dialog.razor component:




<div class="overlay">
    <div class="dialog">
        <div class="dialog-header">
                <div> </div>

                <div>
                    <div class="close">
                        <i class="bi bi-x-lg"> </i>
                    </div>
                </div>
        </div>

        <div class="dialog-body">

        </div>

        <div class="dialog-footer">

        </div>
    </div>
</div>



Enter fullscreen mode Exit fullscreen mode

We have couple of sections here:
1: Overlay: Everything surrounding the dialog box.
2: Dialog: The container of the dialog itself.
3: Dialog Header: The top portion of the dialog.
4: Dialog Body: The main content of the dialog.
5: Dialog Footer: Action buttons or just empty.

Let's add the Css for it, the following snippet shows the styling for the dialog:




.overlay {
    position: fixed;
    width: 100%;
    height: 100%;
    top: 0;
    left: 0;
    background-color: rgba(0,0, 0, 0.5);
    display: flex;
    justify-content: center;
    align-items: center;
    backdrop-filter: blur(10px);
    -webkit-backdrop-filter: blur(10px);
}

.dialog {
    width: 750px;
    background-color: white;
    border-radius: 10px;
    padding: 1rem;
}

    .dialog .dialog-header {
        display: flex;
        align-items: center;
        justify-content: space-between;
        padding: 2rem;
    }

    .dialog .dialog-body {
        padding: 2rem;
    }

    .dialog .dialog-footer {
        padding: 2rem;
    }


.close {
    height: 40px;
    width: 40px;
    background-color: transparent;
    transition: 0.25s ease;
    border-radius: 10px;
    display: flex;
    align-items: center;
    justify-content: center;
    cursor: pointer;
}

    .close:hover {
        background-color: #DDD;
    }




Enter fullscreen mode Exit fullscreen mode

πŸ’‘ Feel free to review the Css and change it according to your needs, and also, if you want to also control the css while reusing the component, you can use some inline styles and dynamic values for them using C#.

Pay attention to these lines:




backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);



Enter fullscreen mode Exit fullscreen mode

The reason for the second line is because Safari requires it and without it, the blur won't appear if you're going to use Safari. However, it works just fine on Edge and Chrome, so make sure to add it as it gives the overlay a nice look to it.

Time To Make Things Interactive πŸ‘€

So far all we've done was plain Html and Css, now we'll add some C# to add interactivity to our dialog, and we'll make it reusable by using the RenderFragment type, which will allow us to reuse the dialog, and based on the need, we'll be able to rebuild the inner parts of it, with the footer as well, so let's get going.

To begin with, we'll need a bunch of parameters for our dialog, like the title, is it opened or not, a UI segment for the body and another for the footer, so add a code directive to the Dialog.razor component file, and inside it, add the following parameters:




@code {
    [Parameter] public string? Title { get; set; }
    [Parameter] public RenderFragment? BodyContent { get; set; }
    [Parameter] public RenderFragment? FooterContent { get; set; }
    [Parameter] public bool Opened { get; set; }
    [Parameter] public EventCallback CloseButtonClicked { get; set; }

    private void CloseDialog()
    {
        Opened = false;
        CloseButtonClicked.InvokeAsync();
    }
}



Enter fullscreen mode Exit fullscreen mode

Before we talk about the previous code, create an if statement that wraps all of the dialog markup:




@if(Opened)
{
    // Dialog markup goes here
}



Enter fullscreen mode Exit fullscreen mode

This will check if Opened equals true, then the dialog will be displayed, otherwise, the dialog will not appear.

For the code written in the code directory, let's see what it does. Ignore the CloseDialog method for now, and focus on the parameters, the title is a simple string, the BodyContent and FooterContent are of type RenderFragment, this allows to write some markup inside the dialog component where we're trying to render it, which gives the flexibility to reuse the same component, in different places, while keeping the general functionality present, like opening the dialog, closing it, and its styles as well.
Now we need to plug those parameters into the Html. For brevity, I'll show snippets for each section of the dialog!

The header section




<div class="dialog-header">
    <div>
        <h2>@Title</h2>
    </div>

    <div @onclick="CloseDialog">
        <div class="close">
            <i class="bi bi-x-lg"> </i>
        </div>
    </div>
</div>



Enter fullscreen mode Exit fullscreen mode

The body section




<div class="dialog-body">
    @BodyContent
</div>



Enter fullscreen mode Exit fullscreen mode

And lastly, the footer section:




<div class="dialog-footer">
    @FooterContent
</div>



Enter fullscreen mode Exit fullscreen mode

For the close button, we wired an event for when the button is clicked, what's going to happen is, the dialog will close as Opened will be turned into false, therefore the condition inside the main if statement will result in false hence it won't render the dialog, and the event callback will be fired, but this one is for later use, we'll talk about it at a later time.

And our dialog is now ready to be used, let's first add a way to open it when we want, we'll do that inside Index.razor, the home page in our project.

The following is the content of Index.razor:




@page "/"

<h1>Hello, world!</h1>

<button class="cool-button" @onclick="ShowDialog">Open Dialog</button>

<Dialog Title="Hello World!" Opened="dialogShown">
    <BodyContent>
        <p style="font-size: 13px;">
            Lorem, ipsum dolor sit amet consectetur...
        </p>
    </BodyContent>

    <FooterContent>
        <button class="ok-button">Ok</button>
    </FooterContent>
</Dialog>

@code {
    private bool dialogShown = false;

    private void ShowDialog()
    {
        dialogShown = true;
    }
}



Enter fullscreen mode Exit fullscreen mode

The code adds a button and hooks it to an event, when clicked, the dialog should open, then I'm adding the component itself, providing it with the required parameters like Title. The parameter Opened will be provided according to a a value controlled by a private variable dialogShown, this variable will control wether we want to open the dialog or not.
Inside the dialog, we have this markup:




<BodyContent>
    // paragraph
</BodyContent>

<FooterContent>
    // Ok button
</FooterContent>



Enter fullscreen mode Exit fullscreen mode

These are the Render fragments that will allow us to control the content of the dialog, inside those tags, you can add whatever you want, and that's how we can make a reusable component, I just added a simple paragraph and a button that does nothing, but if you want, you could add an entire form inside the dialog, and add more action buttons like Submit, Cancel, and links to other pages or actions.

How To Handle The Closing of The Dialog 🚫

Remember that event callback we added as a parameter, that event could help us clear things up inside the dialog. Assuming you have a form inside the dialog, and the user typed in some stuff inside the fields, but then decided to cancel, and closed the dialog, for that reason, we should have a backing event that we can use to clear those fields, and make sure our dialog is closed properly, now here I don't have a form inside my dialog, the thing I want to do here, is hook that event, so that the variable dialogShown reverts back to false when the CloseButtonClicked event fires.

I can do that by providing an event for the parameter, like the following:




<Dialog Title="Hello World!" Opened="dialogShown" CloseButtonClicked="@(() => dialogShown = false)">



Enter fullscreen mode Exit fullscreen mode

This will ensure that my variable is restored to its original state, and that is false.

Result 🧠

Final result of the dialog component

Conclusion

In this post, you've learned how create a reusable dialog but not only that, you got a component that you could use in many upcoming projects, all you have to do is tweak it a little to fit the criteria of a project, and it'll work just fine, additionally, you've seen how we can make a reusable component using things like RenderFragment and component parameters, if you have any questions or suggestions, make sure to leave them in the comments down below!

Bye for now πŸ‘‹πŸ»

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .