WEBVTT

NOTE Created by CaptionSync from Automatic Sync Technologies www.automaticsync.com

00:00:01.066 --> 00:00:03.436 align:middle
We know how to write HTML in our templates.

00:00:03.576 --> 00:00:05.696 align:middle
And we're handling CSS with Tailwind.

00:00:06.126 --> 00:00:07.226 align:middle
What about JavaScript?

00:00:07.686 --> 00:00:13.066 align:middle
Well, like with CSS, there's an app.js
file, and it's already included on the page.

00:00:13.066 --> 00:00:16.446 align:middle
So you can put whatever JavaScript
you want right here.

00:00:17.036 --> 00:00:23.186 align:middle
But I highly recommend using a small, but
mean, JavaScript library called Stimulus.

00:00:23.766 --> 00:00:26.116 align:middle
It is one of my absolute
favorite things on the Internet.

00:00:26.646 --> 00:00:30.176 align:middle
You take a part of your existing
HTML and connect it

00:00:30.176 --> 00:00:32.926 align:middle
to a small JavaScript file, called a controller.

00:00:33.506 --> 00:00:36.896 align:middle
This allows you to add behavior:
like when you click this button,

00:00:37.016 --> 00:00:39.406 align:middle
the greet method on the controller
will be called.

00:00:39.846 --> 00:00:40.746 align:middle
And that's really it!

00:00:41.316 --> 00:00:46.126 align:middle
Sure, Stimulus has more features, but you
already understand the core of how it works.

00:00:46.686 --> 00:00:50.656 align:middle
Despite its simplicity, this
will let us build any JavaScript

00:00:50.656 --> 00:00:55.156 align:middle
and user interface functionality we
need, in a reliable and predictable way.

00:00:55.156 --> 00:00:57.616 align:middle
So let's get it installed.

00:00:57.616 --> 00:01:02.476 align:middle
Stimulus is a JavaScript library, but
Symfony has a bundle that helps integrate it.

00:01:02.476 --> 00:01:06.906 align:middle
Over at your terminal, if you want to see
what the recipe does, commit your changes.

00:01:07.226 --> 00:01:08.116 align:middle
I already have.

00:01:08.616 --> 00:01:15.576 align:middle
Then run: composer require
symfony/stimulus-bundle When this finishes...

00:01:16.926 --> 00:01:18.936 align:middle
the recipe did make some changes.

00:01:19.236 --> 00:01:20.856 align:middle
Let's walk through the important ones.

00:01:21.366 --> 00:01:24.996 align:middle
The first is in app.js: our
main JavaScript file.

00:01:25.426 --> 00:01:27.256 align:middle
Open that up, there we go.

00:01:27.656 --> 00:01:29.816 align:middle
It added an import on top - .

00:01:30.026 --> 00:01:34.766 align:middle
/bootstrap.js - to a new file
that lives right next to this.

00:01:34.766 --> 00:01:37.936 align:middle
The purpose of this file is
to start the Stimulus engine.

00:01:38.626 --> 00:01:45.716 align:middle
Also, in importmap.php, the recipe added the
@hotwired/stimulus JavaScript package along

00:01:45.716 --> 00:01:48.916 align:middle
with another file that helps
boot up Stimulus inside Symfony.

00:01:48.916 --> 00:01:52.996 align:middle
Finally, the recipe created an
assets/controllers/ directory.

00:01:53.456 --> 00:01:55.916 align:middle
This is where our custom controllers will live.

00:01:56.356 --> 00:01:59.246 align:middle
And it included a demo controller
to get us started!

00:01:59.246 --> 00:02:03.696 align:middle
Thanks! These controller files do
have an important naming convention.

00:02:04.066 --> 00:02:09.226 align:middle
Because this is called hello_controller.js,
to connect this with an element on the page,

00:02:09.416 --> 00:02:12.616 align:middle
we'll use data-controller="hello".

00:02:12.616 --> 00:02:14.416 align:middle
So here's how this works.

00:02:14.776 --> 00:02:19.386 align:middle
As soon as Stimulus sees an element on
the page with data-controller="hello",

00:02:19.626 --> 00:02:24.406 align:middle
it will instantiate a new instance of this
controller and call the connect() method.

00:02:24.406 --> 00:02:27.796 align:middle
So, this hello controller should automatically

00:02:27.796 --> 00:02:31.526 align:middle
and instantly change the content
of the element it's attached to.

00:02:32.036 --> 00:02:34.766 align:middle
And we can already see this.

00:02:34.766 --> 00:02:35.746 align:middle
Refresh the page.

00:02:35.746 --> 00:02:38.546 align:middle
Stimulus is now active on our site.

00:02:38.856 --> 00:02:42.406 align:middle
This means it's watching for
elements with data-controller.

00:02:43.096 --> 00:02:47.876 align:middle
Let's do something wild: inspect
element on the page, find any element -

00:02:48.056 --> 00:02:52.056 align:middle
like this anchor tag - and
add data-controller="hello".

00:02:52.686 --> 00:02:56.146 align:middle
Watch what happens when I click
off to activate this change.

00:02:56.716 --> 00:03:02.226 align:middle
Boom! Stimulus saw that element, instantiated
our controller and called the connect() method.

00:03:02.226 --> 00:03:06.076 align:middle
And you can do this as many
times as you want on the page.

00:03:06.526 --> 00:03:12.316 align:middle
The point is: no matter how a data-controller
element get on your page, Stimulus sees it.

00:03:12.316 --> 00:03:17.296 align:middle
So if we make an Ajax call that returns
HTML and put that onto the page...

00:03:17.626 --> 00:03:21.756 align:middle
yeah, Stimulus is going to see that
and our JavaScript is going to work.

00:03:22.256 --> 00:03:27.576 align:middle
That's the key: when you write JavaScript with
Stimulus, your JavaScript will always work,

00:03:27.756 --> 00:03:31.446 align:middle
no matter how and when that
HTML is added to the page.

00:03:31.446 --> 00:03:34.436 align:middle
So let's use Stimulus to power our close button.

00:03:35.116 --> 00:03:38.726 align:middle
Over in the assets/controller/
directory, duplicate hello_controller.js

00:03:38.726 --> 00:03:41.856 align:middle
and make a new one called
closeable_controller.js.

00:03:41.856 --> 00:03:46.576 align:middle
I'll clear out almost everything
and get down to the absolute basics:

00:03:48.216 --> 00:03:49.856 align:middle
import Controller from stimulus...

00:03:50.056 --> 00:03:51.976 align:middle
then create a class that extends it.

00:03:52.536 --> 00:03:56.496 align:middle
This doesn't do anything, but we can
already attach it to an element on the page.

00:03:57.166 --> 00:04:01.976 align:middle
Here's the plan: we're going to attach the
controller to the entire aside element.

00:04:02.446 --> 00:04:05.676 align:middle
Then, when we click this
button, we'll remove the aside.

00:04:06.466 --> 00:04:11.456 align:middle
That element lives over in
templates/main/_shipStatusAside.html.twig.

00:04:12.266 --> 00:04:16.296 align:middle
To attach the controller, add
data-controller="closeable".

00:04:16.806 --> 00:04:18.296 align:middle
Oh, see that autocompletion?

00:04:18.486 --> 00:04:21.176 align:middle
That comes from a Stimulus plugin for PhpStorm.

00:04:21.736 --> 00:04:26.516 align:middle
If we move over and refresh, nothing will
happen yet: the close button doesn't work.

00:04:26.976 --> 00:04:28.736 align:middle
But open your browser's console.

00:04:29.286 --> 00:04:35.026 align:middle
Nice! Stimulus adds helpful debugging
messages: that it's starting and then -

00:04:35.096 --> 00:04:38.806 align:middle
importantly closeable initialize,
closeable connect.

00:04:39.286 --> 00:04:43.896 align:middle
This means that it did see the data-controller
element and initialized that controller.

00:04:43.896 --> 00:04:47.306 align:middle
So back to our goal: when we click this button,

00:04:47.396 --> 00:04:52.476 align:middle
we want to call code inside the closeable
controller that will remove the aside.

00:04:53.166 --> 00:04:58.296 align:middle
In closeable_controller.js, add a new
method called, how about, close().

00:04:58.296 --> 00:05:01.556 align:middle
Inside, say this.element.remove().

00:05:02.596 --> 00:05:08.416 align:middle
In Stimulus, this.element will always be
whatever element your controller is attached to.

00:05:08.416 --> 00:05:10.916 align:middle
So, this aside element.

00:05:11.566 --> 00:05:16.446 align:middle
But otherwise, this code is standard
JavaScript: every Element has a remove() method.

00:05:17.086 --> 00:05:23.696 align:middle
To call the close() method, on the button, add
data-action="" then the name of our controller -

00:05:23.886 --> 00:05:29.156 align:middle
closeable - a # sign, and the
name of the method: close.

00:05:29.156 --> 00:05:29.616 align:middle
That's it!

00:05:29.916 --> 00:05:30.756 align:middle
Testing time.

00:05:31.496 --> 00:05:33.226 align:middle
Click! Gone!

00:05:33.746 --> 00:05:35.406 align:middle
But I want it be fancier!

00:05:35.736 --> 00:05:38.746 align:middle
I want it to animate when
closing instead of being instant.

00:05:39.156 --> 00:05:40.176 align:middle
Can we do that?

00:05:40.586 --> 00:05:42.746 align:middle
Sure! And we don't need much JavaScript...

00:05:42.816 --> 00:05:45.166 align:middle
because modern CSS is amazing.

00:05:45.756 --> 00:05:52.366 align:middle
Over on the aside element, add a new CSS class
- it could go anywhere - called transition-all.

00:05:52.996 --> 00:05:56.326 align:middle
That's a Tailwind class that
activates CSS transitions.

00:05:56.786 --> 00:06:01.766 align:middle
This means that if certain style properties
change - like the width suddenly being set

00:06:01.766 --> 00:06:06.076 align:middle
to 0 - it will transition that change,
instead of instantly changing it.

00:06:06.716 --> 00:06:10.606 align:middle
Also add overflow-hidden so
that, as the width gets smaller,

00:06:10.796 --> 00:06:12.536 align:middle
it doesn't create a weird scroll bar.

00:06:13.236 --> 00:06:17.226 align:middle
If we try this now, it still closes instantly.

00:06:17.656 --> 00:06:21.866 align:middle
That's because there's nothing to
transition: we're not changing the width...

00:06:21.866 --> 00:06:23.216 align:middle
just removing the element.

00:06:23.216 --> 00:06:25.336 align:middle
But watch this.

00:06:25.336 --> 00:06:28.416 align:middle
Inspect Element and find the aside: here it is.

00:06:28.986 --> 00:06:31.196 align:middle
Manually change the width to 0.

00:06:31.936 --> 00:06:35.916 align:middle
Cool! You go tiny, big, tiny, big, tiny!

00:06:36.356 --> 00:06:38.726 align:middle
The CSS side of things is working.

00:06:39.456 --> 00:06:44.366 align:middle
Back in our controller, instead of removing the
element, we need to change the width to zero,

00:06:44.686 --> 00:06:48.366 align:middle
wait for the CSS transition to
finish, then remove the element.

00:06:48.826 --> 00:06:52.706 align:middle
We can do the first with
this.element.style.width = 0.

00:06:53.626 --> 00:06:56.536 align:middle
The tricky part is waiting
for the CSS transition

00:06:56.536 --> 00:06:58.856 align:middle
to finish before removing the element.

00:06:59.606 --> 00:07:02.966 align:middle
To help with that, I'm going to paste a
method at the bottom of our controller.

00:07:02.966 --> 00:07:09.386 align:middle
If you're not familiar, the # sign makes this
a private method in JavaScript: a small detail.

00:07:09.386 --> 00:07:15.436 align:middle
This code looks fancy, but it has a simple
job: to ask the element to tell us when all

00:07:15.436 --> 00:07:17.276 align:middle
of its CSS animations are finished.

00:07:17.816 --> 00:07:22.096 align:middle
Thanks to that, up here, we can say await this.

00:07:22.226 --> 00:07:23.436 align:middle
#waitForAnimation().

00:07:24.066 --> 00:07:28.666 align:middle
And whenever you use await, you need to
put async on the function around this.

00:07:28.766 --> 00:07:33.956 align:middle
I won't go into details about async, but
that won't change how our code works.

00:07:33.956 --> 00:07:34.906 align:middle
Let's check the result!

00:07:35.226 --> 00:07:36.896 align:middle
Refresh. And...

00:07:37.476 --> 00:07:39.876 align:middle
I absolutely love that.

00:07:40.566 --> 00:07:44.016 align:middle
Next up, everyone wants a
single page application, right?

00:07:44.386 --> 00:07:47.086 align:middle
A site where there are zero full page refreshes.

00:07:47.546 --> 00:07:51.776 align:middle
But to build one, don't we need to
use a JavaScript framework like React?

00:07:52.146 --> 00:07:56.686 align:middle
No! We're going to transform our app
into a single page application in...

00:07:56.926 --> 00:07:59.166 align:middle
about 3 minutes with Turbo.

