Toast Notifications
Keep on Learning!
If you liked what you've learned so far, dive in! Subscribe to get access to this tutorial plus video, code and script downloads.
With a Subscription, click any sentence in the script to jump to that part of the video!
Login SubscribeAn important part of any functional beautiful site is a notification system. In Symfony, we often think of flash messages: messages that we render near the top of the page, for example, after the user submits a form. And yes, that is what I'm talking about. But just rendering them at the top of the page isn't good enough for us. Instead, I want to render them as rich, toast-style notifications in the upper right that disappear automatically with CSS transitions and can tie my shoes for me.
Rendering Flash Messages
On our CRUD controllers, I'm already setting a success
flash message... but I'm not rendering it anywhere. In the templates/
directory, create a new _flashes.html.twig
. To start, just loop over the success messages with for message in
app.flashes('success')
... and endfor
:
{% for message in app.flashes('success') %} | |
// ... lines 2 - 4 | |
{% endfor %} |
Inside, I'll paste a very simple flash message, which will start fixed to the bottom of the page:
{% for message in app.flashes('success') %} | |
<div class="fixed bottom-0 right-0 m-4 p-4 bg-green-500 text-white rounded shadow"> | |
{{ message }} | |
</div> | |
{% endfor %} |
Next, in base.html.twig
, instead of rendering the flashes somewhere near the top of the body, put them at the bottom. Say <div id="flash-container">
then {{ include('_flashes.html.twig') }}
:
<html> | |
// ... lines 3 - 15 | |
<body class="bg-black text-white font-mono"> | |
// ... lines 17 - 51 | |
<div id="flash-container"> | |
{{ include('_flashes.html.twig') }} | |
</div> | |
</body> | |
</html> |
The id="flash-container"
isn't important yet, but it will be useful later when we talk about Turbo streams.
So let's see if this works! Hit "Save" and... there we go! It's in a weird spot, but it shows up.
Making the Notification Pretty!
To make this look nicer, let's take a trip to Flowbite. Search for "toast". Ah, this has some great examples for different styles of toast notifications. This has me feeling dangerous!
Tip
I also recommend adding a data-turbo-temporary
attribute to the root <div>
.
This will remove the flash message before Turbo takes its "snapshot" for caching,
This means that if the user clicks "Back" to a page, the toast won't still be visible.
Back in _flashes.html.twig
, I'll paste in some content:
{% for message in app.flashes('success') %} | |
<div | |
class="fixed top-5 right-5 flex items-center w-full max-w-xs p-4 mb-4 text-gray-500 bg-white rounded-lg shadow dark:text-gray-400 dark:bg-gray-800" | |
role="alert" | |
> | |
<div class="inline-flex items-center justify-center flex-shrink-0 w-8 h-8 text-green-500 bg-green-100 rounded-lg dark:bg-green-800 dark:text-green-200"> | |
<svg class="w-5 h-5" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 20 20"> | |
<path d="M10 .5a9.5 9.5 0 1 0 9.5 9.5A9.51 9.51 0 0 0 10 .5Zm3.707 8.207-4 4a1 1 0 0 1-1.414 0l-2-2a1 1 0 0 1 1.414-1.414L9 10.586l3.293-3.293a1 1 0 0 1 1.414 1.414Z"/> | |
</svg> | |
<span class="sr-only">Check icon</span> | |
</div> | |
<div class="ms-3 text-sm font-normal">{{ message }}</div> | |
<button | |
type="button" | |
class="ms-auto -mx-1.5 -my-1.5 bg-white text-gray-400 hover:text-gray-900 rounded-lg focus:ring-2 focus:ring-gray-300 p-1.5 hover:bg-gray-100 inline-flex items-center justify-center h-8 w-8 dark:text-gray-500 dark:hover:text-white dark:bg-gray-800 dark:hover:bg-gray-700" | |
aria-label="Close" | |
> | |
<span class="sr-only">Close</span> | |
<svg class="w-3 h-3" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 14 14"> | |
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m1 1 6 6m0 0 6 6M7 7l6-6M7 7l-6 6"/> | |
</svg> | |
</button> | |
</div> | |
{% endfor %} |
This is heavily inspired by the Flowbite examples. But nothing really changed: we're still looping over the same collection and still dumping out the message. We've just got nice markup around this.
And I can't want to see it! I'll go to edit and "Save". Oh, that is wonderful! In the upper right where I want it and all done with CSS.
Making the Toast Closeable
Though, it doesn't auto close yet. Heck, it doesn't close at all! Since "closing" things will be a common problem, let's create a reusable Stimulus controller that can do that.
In assets/controller/
, add a new closeable_controller.js
. I'll cheat and grab the code from another controller... clear it out... then add a close()
method. When this is called, it'll remove the entire element that the controller is attached to:
import { Controller } from '@hotwired/stimulus'; | |
export default class extends Controller { | |
close() { | |
this.element.remove(); | |
} | |
} |
To use this, in _flashes.html.twig
, attach the controller to the top level element because that's what should be removed on close. Then, down on the button, say data-action="closeable#close"
:
{% for message in app.flashes('success') %} | |
<div | |
// ... lines 3 - 4 | |
data-controller="closeable" | |
> | |
// ... lines 7 - 13 | |
<button | |
// ... lines 15 - 17 | |
data-action="closeable#close" | |
> | |
// ... lines 20 - 23 | |
</button> | |
</div> | |
{% endfor %} |
We don't need the click
because this is a button
, so Stimulus already knows that we want this to trigger on the click
event.
Let's try it! Hit edit and Save. It's there... it's gone.
In just a few minutes of work, we created a beautiful and functional toast notification system! But, darn it, this is not cool enough for our 30 Days of LAST Stack mission! So tomorrow, we'll fancy-ify this with auto-close, CSS transitions and an animated timer bar.
The problem with "code blocks" (and with this comment I refer to Flowbite Toast component) is that it is difficult to find.
I expect to find separately, maybe in a comment (as done by ToG for first version of _flashes.html.twig ), but there is not.
So my second idea is to search in "Course code"... but where? There are 2 folders: "start" and "finish". I naturally go inside "start" because I think that "start" is the "actual-state-of-code-at-the-start-of-the-lesson".
Moreover I am not sure that in "finish" directory contains the current version of the script I need. I am not sure, for example, that content of
_flashes.html.twig
is the same as in the video.Let's say the
_flashes.htm.twig
will be also modified in lesson 23, the question is: which version of that file will I find in "finish" directory? Version in lesson 16 or the modified one in lesson 23?Cheers
Danilo