Dialog Component with RiotJS (Material Design)
Tags: riotjs, frontend, javascript, beginners
Published: 2024-04-02 08:22:28

This article covers creating a Riot Dialog component, using the Material Design CSS BeerCSS, and reacting to click events.

Before starting, make sure you have a base application running, or read my previous article Setup [Riot + BeerCSS + Vite](https://dev.to/steeve/riotjs-material-design-how-to-setup-beercss-269c).

> These articles form a series focusing on [RiotJS](https://riot.js.org/) paired with [BeerCSS](https://www.beercss.com/), designed to guide you through creating components and mastering best practices for building production-ready applications. I assume you have a foundational understanding of Riot; however, feel free to refer to the documentation if needed: https://riot.js.org/documentation/

The Dialog, informs users about a specific task and may contain critical information, require decisions, or involve multiple tasks. In a modal dialog, the user is interrupted and interacts with the dialog before they can continue interacting with the rest of the application, for instance choosing a phone ringtone:

![Modal example to select a phone ringtone](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wofakckkhlyodp12olvq.png)

## Dialog Component Base

The goal is to create a Riot app with a Dialog and execute an action if the modal is cancelled or confirmed.

![Display a modal when a button is clicked and close the modal when the confirm or cancel button is clicked](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jc14achpep7ww5p41s2o.gif)

First, create a new file named `c-dialog.riot` under the components folder. The `c-` stands for "component", a helpful naming convention and a good practice.

Write the following HTML code (found on the [BeerCSS documentation](https://www.beercss.com/#dialogs)) in `./components/c-dialog.riot`:
```html
<c-dialog>
    <dialog class="
                {props?.active ? 'active ' : null}
                {props?.blur ? 'overlay blur ' : null}
                {props?.left ? 'left ' : null}
                {props?.right ? 'right ' : null}
                {props?.top ? 'top ' : null}
                {props?.bottom ? 'bottom ' : null}
                {props?.max ? 'max ' : null}
            "
        >
        <h5 if={ props?.title }>{ props?.title }</h5>
        <div>
            { props?.message }
            <slot name="body"></slot>
        </div>
        <nav class="right-align no-space">
            <button onclick={ (ev) => clicked(ev, "cancel") } class="transparent link" if={ !props?.disableCancel }>{ props?.cancelText ?? "cancel" }</button>
            <button onclick={ (ev) => clicked(ev, "confirm") } class="transparent link" if={ !props?.disableConfirm }>{ props?.confirmText ?? "confirm" }</button>
            <slot name="button"></slot>
        </nav>
    </dialog>
       <script>
        export default {
            clicked (e, name) {
                e.preventDefault();
                e.stopPropagation();
                this.root.dispatchEvent(new Event(name));
            }
        }
    </script>
</c-dialog>
```
> Source Code: https://github.com/steevepay/riot-beercss/blob/main/components/c-dialog.riot

Let's break down the code:
1. The `<c-dialog>` and `</c-dialog>` define a custom root tag, with the same name as the file. You must write it; otherwise, it may create unexpected results. Using the `<dialog></dialog>` as a root tag or redefining native HTML tags is a bad practice, so starting `c-` is a good naming.
2. The Dialog has three main sections:
   * **A title as header** filled thanks to the `{props.title}`.
   * **A message as body** printed with `{props.message}`.
   * **A call to action** with two buttons: cancel and confirm.
3. When a button is clicked, the `click` Event is caught into the **clicked** function, and then a **Custom Event** is emitted to the parent HTML with `this.root.dispatchEvent(new Event(name));`: it will emit either `confirm` or `cancel`.
4. The modal is displayed only if the **props.active** exists and is positive; then, the class `active` is applied.
5. The Dialog component has different styles, and it is printed conditionally if their corresponding `props` exist, for making the Dialog fullscreen: `{props?.max ? 'max ' : null}`: if the property `max` exists, the class max is applied.

Finally, load and instantiate the **c-dialog.riot** in a front page named **index.riot**:
```html
<index-riot>
    <div style="width:800px;padding:20px;">
        <c-button onclick={ (ev) => update({ active: !state.active, max: false })}>Dialog</c-button> 
        <c-button onclick={ (ev) => update({ active: !state.active, max: true })}>Max</c-button>

        <c-dialog 
            title="Dialog" 
            message="Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."
            active={ state.active }
            oncancel={ cancel } 
            onconfirm={ confirm }
            max={ state.max }
        />
    </div>
    <script>
        import cButton from "../components/c-button.riot"
        import cDialog from "../components/c-dialog.riot"

        export default {
            components: {
                cDialog,
                cButton
            },
            state: {
                active: false,
                left: false,
                max: false
            },
            cancel() {
                console.log("dialog cancel")
                this.update({ active: false })
            },
            confirm() {
                console.log("dialog confirm")
                this.update({ active: false })
            }
        }
    </script>
</index-riot>
```
> Source Code: https://github.com/steevepay/riot-beercss/blob/main/examples/index.dialog.riot

Code details:
1. Components are imported with `import cDialog from "./components/c-dialog.riot";` then loaded in the **components:{}** Riot object.
2. The `dialog` component is instantiated with `<c-dialog/>` on the HTML.
3. The state of the Dialog is stored in the **state:{}** Riot object under the `state.active` Boolean property. The property is passed as an attribute, such as: `<c-dialog active={ state.active }/>`. When the button clicks, it sets the **active** property to `true` and displays the modal.
4. The dialog's title and message are passed as attributes.
5. If the cancel or confirm button is clicked, it will emit two custom events:
   * **oncancel**: it will execute the `this.cancel()` function, and close the modal.
   * **onconfirm**: it will execute the `this.confirm()` function, and close the modal.
6. Both calls to action can be bound to a custom function: make an API call, add an HTML element, send an email, confirm a deletion and more!

## Dialog Component Testing

It exists two methods for testing the Dialog component, and it is covered in two different articles:
* [Test with Vitest and Riot-SSR in a Node Environment](https://dev.to/steeve/riot-component-unit-test-with-vitest-node-env-hk0)
* [Test with Vitest in a JsDom Environment](https://dev.to/steeve/riot-component-unit-test-with-vitest-jsdom-env-182l)

## Conclusion

Voilà 🎉 We made a Dialog Riot Component using Material Design elements with BeerCSS.

The source code of the modal is available on Github:
https://github.com/steevepay/riot-beercss/blob/main/components/c-dialog.riot

Have a great day! Cheers 🍻