Toasts with Thymeleaf, HTMX and Spring Boot
Published: 2023-02-20 08:36:57

In this example, I will show you a way to return Toast Notifications from your server and render them interactively.

## The HTML:

We will start with an HTMX Element that creates a request to our spring server:

```html
<form class="" hx-target="#toast" hx-put="/api/someRoute"></form>
```

As you can see we target an element with the id toast. To make this work I created a div with the id that is inside my
footer. The footer is shown on every page.

```html
<footer>
    <div id="toast"></div>
</footer>
```

But what if you don't want to target the toast element, and instead you want to target another element?
I will show you later how you can [change the HTMX target on the server](#change-the-htmx-target-on-the-server).

Next is our toast element, to make them generic we use parameterized Thymeleaf fragments.
In this example I use the awesome [Alpine.js](https://alpinejs.dev/) library, but this is also easily doable with
normal javascript.

We create a toast.html file in our root directory to use it later as a return view.

```html

<div th:fragment="info(message,duration)"
     th:attr="x-init='setTimeout(() => $el.style.display = \'none\',' + ${duration} + ')'"
     class="fixed bottom-10 z-40 left-1/2 -translate-x-1/2"
     x-data="{}"
     x-ref="info">
    <div class="flex">
        <div class="alert alert-info shadow-lg my-6">
            <div>
                <span th:text="${message}"></span>
            </div>
            <button @click="$refs.info.style.display = 'none'">
                <img src="/svg/x-circle.svg">
            </button>
        </div>
    </div>
</div>
```

To break it down:

- `th:fragment="info(message,duration)` attribute declares that this is a parameterized Thymeleaf fragment we can later
  use in our spring backend.
- `th:attr="x-init='setTimeout(() => $el.style.display = \'none\',' + ${duration} + ')'"` creates a timeout function on
  initialization that hides the toast after the duration we specified in the fragment parameter
- Next, we position the element with the tailwind classes `fixed bottom-10 z-40 left-1/2 -translate-x-1/2`
- The `<span th:text="${message}"></span>` element renders our text
- With the `<button @click="$refs.info.style.display = 'none'">` our user can click the toast away before it expires.

I also duplicated this element and changed the info to error and changed the styling to return info or error toasts.

## The backend code:

To specify our toast I used an enum class:

```kotlin
enum class Toast {
    INFO {
        override fun ModelAndView(message: String, durationInMs: Int) =
            ModelAndView("toast :: info(message = '${message}', duration = '$durationInMs')")
    },
    ERROR {
        override fun ModelAndView(message: String, durationInMs: Int) =
            ModelAndView("toast :: error(message = '${message}', duration = '$durationInMs')")
    };

    abstract fun ModelAndView(message: String, durationInMs: Int): ModelAndView
}
```

We can return this enum in our Controller if we want to show either an info message or an error message.

As you can see we return the parameterized fragment with a message we can extract from our business context and give our
user valuable information.

```kotlin
@Controller
ExampleController() {
    @RequestMapping("/api/someRoute")
    fun someRoute(): ModelAndView {
        try {
            // do Something
        } catch (e: Exception) {
            return Toast.ERROR.ModelAndView("An Error occurred: ${e.message}", 5000)
        }

    }
}
```

## Change the HTMX target on the server

But what do you do when you want to swap the element itself and only show a toast if an error occurs?

```html

<form hx-put="/api/someRoute"></form>
```

We can change the behavior of HTMX easily with two Response Headers:

```kotlin
@RequestMapping("/api/someRoute")
fun someRoute(
    httpServletResponse: HttpServletResponse
): ModelAndView {
    try {
        // do Something
    } catch (e: Exception) {
        httpServletResponse.addHeader("HX-Retarget", "#errors");
        httpServletResponse.addHeader("HX-Reswap", "innerHTML");
        return Toast.ERROR.ModelAndView("An Error occurred: ${e.message}", 5000)
    }

}
```

We just include the httpServletResponse: HttpServletResponse in the constructor of our function, and then

- With `HX-Retarget` we can change the [hx-target](https://htmx.org/attributes/hx-target/) property
- And with `HX-Reswap` we can change the [hx-swap](https://htmx.org/attributes/hx-swap/) property

There are many more HTMX [Response Headers](https://htmx.org/reference/#response_headers) you can change to influence the behavior of your application.