Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine

Stimulus: Sensible, Beautiful JavaScript

Video not working?

It looks like your browser may not support the H264 codec. If you're using Linux, try a different browser or try installing the gstreamer0.10-ffmpeg gstreamer0.10-plugins-good packages.

Thanks! This saves us from needing to use Flash or encode videos in multiple formats. And that let's us get back to making more videos :). But as always, please feel free to message us.

I want to talk about Stimulus. Stimulus is a small, but delightful JavaScript library that I love. And Symfony has first-class support for it. It's also heavily used by the Ruby on Rails community.

SPA vs "Traditional" Apps

So there are kind of two philosophies in web development. The first is where you return HTML from your site like we've been doing on our homepage and browse page. And then you add JavaScript behavior to that HTML. The second philosophy is to use a front-end JavaScript framework to build all of your HTML and JavaScript. That's a single page application.

The right solution depends on your app, but I strongly like the first approach. And by using Stimulus - as well as another tool we'll talk about in a few minutes called Turbo - we can create highly-interactive apps that look and feel as responsive as a single page app.

We have an entire tutorial on Stimulus, but let's get a taste. You can already see how it works in the example on their docs. You create a small JavaScript class called a controller... and then attach that controller to one or more elements on the page. And that's it! Stimulus allows you to attach event listeners - like click events - and has other goodies.

Stimulus Controllers in our App

In our app, when we installed Encore, it gave us a controllers/ directory. This is where our Stimulus controllers will live. And in app.js, we import bootstrap.js. This is not a file that you'll need to look at much, but it's super useful. This starts up Stimulus - yes, it's already installed - and registers everything in the controllers/ directory as a Stimulus controller. This means that if you want to create a new Stimulus controller, all you need to do is add a file to this controllers/ directory!

And we get one Stimulus controller out-of-the box called hello_controller.js. All Stimulus controllers follow the naming practice of something "underscore" controller.js or something dash controller.js. The part before _controller - so hello - becomes the controller's name.

Attaching a Controller to an Element

Let's attach this to an element. Open up templates/vinyl/homepage.html.twig. Let's see... on the main part of the page, I'm going to add a div... and then to attach the controller to this element, add data-controller="hello".

... lines 1 - 35
<div data-controller="hello"></div>
... lines 37 - 59

Let's try it! Refresh and... yes! It worked! Stimulus saw this element, instantiated the controller... and then our code changed the content of the element. The element that this controller is attached to is available as this.element.

Stimulus Dynamically sees New Elements!

So... this is already really nice... because we get to work inside of a neat JavaScript object... which is tied to a specific element.

But let me show you the coolest part of Stimulus: what makes it such a game changer. Inspect element in your browser tools near the element. I'm going to modify the parent element's HTML. Right above this - though it doesn't matter where - add another element with data-controller="hello".

And... boom! We see the message! This is the killer feature of Stimulus: you can add these data-controller elements onto the page whenever you want. For example, if you make an Ajax call... which adds fresh HTML to your page, Stimulus will notice that and execute any controllers that the new HTML should be attached to. If you've ever had problems where you add HTML to your page via Ajax... but that new HTML's JavaScript is broken because it's missing some event listeners, well, Stimulus just solved that.

The stimulus_controller () Function

When you use Stimulus inside of Symfony, we get a few helper functions to make life easier. So instead of writing data-controller="hello" by hand, we can say {{ stimulus_controller('hello') }}.

... lines 1 - 35
<div {{ stimulus_controller('hello') }}></div>
... lines 37 - 59

But that's just a shortcut to render that attribute exactly like it was before.

Ok, now that we have the basics of Stimulus, let's use it to do something real, like make an Ajax request when we click this play icon. That's next.

Leave a comment!

3
Login or Register to join the conversation

For those coming with recent versions of Symfony (mine is 6.3), Stimulus doesn't seem to be installed by default anymore with the symfony/webpack-encore-bundle package.

Instead you have to install it using:

composer require symfony/stimulus-bundle

And then run:

// or `yarn install`
npm install

to install the npm dependencies added to package.json

Reference: Stimulus & Symfony UX

Kind regards!

11 Reply
Tac-Tacelosky Avatar
Tac-Tacelosky Avatar Tac-Tacelosky | posted 11 months ago | edited

Is there a way to use stimulus_controller twig function to render multiple controllers on an element?

I was looking at the ChartUX example, and there's support there for multiple controllers, internally renderChart checks for a data-controller attribute and renders both, but I'm wondering if stimulus_controller also support that.

In particular, I'm trying to follow the pattern of listening for events rather than extending a base controller.

Reply

Hey

Yeah you can pass an array of controllers to {{ stimulus_controller() }} however it's deprecated from symfony/webpack-encore-bundle:1.15.0

New way was introduced |stimulus_controller() filter... so new code will look:

<div
    {{ stimulus_controller('first_controller')|stimulus_controller('second_controller') }}
>
</div>

Cheers!

3 Reply
Cat in space

"Houston: no signs of life"
Start the conversation!

What PHP libraries does this tutorial use?

// composer.json
{
    "require": {
        "php": ">=8.0.2",
        "ext-ctype": "*",
        "ext-iconv": "*",
        "symfony/asset": "6.0.*", // v6.0.3
        "symfony/console": "6.0.*", // v6.0.3
        "symfony/dotenv": "6.0.*", // v6.0.3
        "symfony/flex": "^2", // v2.1.5
        "symfony/framework-bundle": "6.0.*", // v6.0.4
        "symfony/monolog-bundle": "^3.0", // v3.7.1
        "symfony/runtime": "6.0.*", // v6.0.3
        "symfony/twig-bundle": "6.0.*", // v6.0.3
        "symfony/ux-turbo": "^2.0", // v2.0.1
        "symfony/webpack-encore-bundle": "^1.13", // v1.13.2
        "symfony/yaml": "6.0.*", // v6.0.3
        "twig/extra-bundle": "^2.12|^3.0", // v3.3.8
        "twig/twig": "^2.12|^3.0" // v3.3.8
    },
    "require-dev": {
        "symfony/debug-bundle": "6.0.*", // v6.0.3
        "symfony/stopwatch": "6.0.*", // v6.0.3
        "symfony/web-profiler-bundle": "6.0.*" // v6.0.3
    }
}
userVoice