Actions: Listening to Events

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 make our controller more realistic: instead of being able to click anywhere on the element to increment the count, let's add a button.

Easy enough! In the template, add <button class="btn btn-primary btn-sm"> and then the excellent call to action: "Click me".

... lines 1 - 2
{% block body %}
... lines 4 - 11
<div data-controller="counter">
<button class="btn btn-primary btn-sm">
Click me!
</button>
<br>
... lines 18 - 21
</div>
... lines 23 - 95
{% endblock %}

If we check it in the browser... amazing! It works! Ok, I'm kidding: of course it works. But that's the cool thing about Stimulus: we get to do so much of our work in Twig where life is quick and easy.

Now: how could we attach a click listener to just this element? You might be thinking:

I know! Let's add a target to the button like we did for the span. Then we can find that button inside of connect() and add the event listener to it instead of this.element.

And yeah! That will totally do it! But it's way too much work.

Adding a Click Action

After targets, the second big feature of Stimulus is actions. Anytime you need to listen to an event, like click, submit, keyup or anything else, you can use an action instead.

Here's how it works: on the button element, I'll break this onto multiple lines for clarity. Now add data-action="click->counter#increment".

... lines 1 - 12
<button
data-action="click->counter#increment"
class="btn btn-primary btn-sm"
>
... lines 17 - 100

This is another special syntax: data-action="", then the name of the event, like click, submit or keyup, arrow, the name of the controller, a pound sign, and then the name of the method to call on our controller when this event happens. We'll create the increment method in a minute.

When I first used Stimulus, I did not love this syntax. It's... a bit weird. But it really does make life a lot nicer. And we'll simplify it a bit in a few minutes.

Over in the controller, add the method - increment() - then copy the logic from the click callback, delete, and paste it here.

And now we can remove the event listener entirely.

... lines 1 - 2
export default class extends Controller {
... lines 4 - 5
connect() {
this.count = 0;
}
increment() {
this.count++;
this.countTarget.innerText = this.count;
}
}

I may not love the data-action syntax in the template, but I do love the result. This is gorgeous.

Let's try it. Refresh the page. Now, if I click somewhere else in the element, nothing happens. If I click on the button, it increments! Woo!

The Default Action Name

But I did promise a simplification in the template. Remove click and the -> after it.

... lines 1 - 12
<button
data-action="counter#increment"
... line 15
>
... lines 17 - 100

Try it again: it's still works just fine! How? Stimulus has a default event name for the most common elements. If you add data-action to an a tag or a button, the default event name is click. If you add one to a <form> element, it defaults to submit. And if you add one to an <input> or <textarea>, it defaults to input, which is the event that happens when the value of the field changes.

So most of the time, you don't need to specify the event name.

Oh, and now, to celebrate, in the controller, we can remove the connect() method entirely! Move this.count = to a normal property: count = 0. Then delete connect().

... lines 1 - 2
export default class extends Controller {
count = 0;
... lines 5 - 10
}

Let's make sure I didn't break anything. Nope! All is good!

So... that is most of Stimulus... seriously! But I already love it. I mean, look how clean this controller is! And I get to render nice, clean HTML inside a template.

There are a bunch of things that I still want to talk about, like the values API, but Stimulus really is a lean and mean library.

Next: as nice as our counter example was, let's do something real. Over in the browser, click the "Furniture" category and then click the "Inflatable Sofa". Some products come in multiple colors and you choose the color with a color select element. Boooooooring. Let's enhance this by turning it into a color square selector widget.

Leave a comment!

What PHP libraries does this tutorial use?

// composer.json
{
    "require": {
        "php": ">=7.4.0",
        "ext-ctype": "*",
        "ext-iconv": "*",
        "composer/package-versions-deprecated": "1.11.99.1", // 1.11.99.1
        "doctrine/annotations": "^1.0", // 1.11.1
        "doctrine/doctrine-bundle": "^2.2", // 2.2.3
        "doctrine/doctrine-migrations-bundle": "^3.0", // 3.0.2
        "doctrine/orm": "^2.8", // 2.8.1
        "phpdocumentor/reflection-docblock": "^5.2", // 5.2.2
        "sensio/framework-extra-bundle": "^5.6", // v5.6.1
        "symfony/asset": "5.2.*", // v5.2.3
        "symfony/console": "5.2.*", // v5.2.3
        "symfony/dotenv": "5.2.*", // v5.2.3
        "symfony/flex": "^1.3.1", // v1.12.1
        "symfony/form": "5.2.*", // v5.2.3
        "symfony/framework-bundle": "5.2.*", // v5.2.3
        "symfony/property-access": "5.2.*", // v5.2.3
        "symfony/property-info": "5.2.*", // v5.2.3
        "symfony/proxy-manager-bridge": "5.2.*", // v5.2.3
        "symfony/security-bundle": "5.2.*", // v5.2.3
        "symfony/serializer": "5.2.*", // v5.2.3
        "symfony/twig-bundle": "5.2.*", // v5.2.3
        "symfony/ux-chartjs": "^1.1", // v1.2.0
        "symfony/validator": "5.2.*", // v5.2.3
        "symfony/webpack-encore-bundle": "^1.9", // v1.11.1
        "symfony/yaml": "5.2.*", // v5.2.3
        "twig/extra-bundle": "^2.12|^3.0", // v3.2.1
        "twig/intl-extra": "^3.2", // v3.2.1
        "twig/twig": "^2.12|^3.0" // v3.2.1
    },
    "require-dev": {
        "doctrine/doctrine-fixtures-bundle": "^3.4", // 3.4.0
        "symfony/debug-bundle": "^5.2", // v5.2.3
        "symfony/maker-bundle": "^1.27", // v1.30.0
        "symfony/monolog-bundle": "^3.0", // v3.6.0
        "symfony/stopwatch": "^5.2", // v5.2.3
        "symfony/var-dumper": "^5.2", // v5.2.3
        "symfony/web-profiler-bundle": "^5.2" // v5.2.3
    }
}

What JavaScript libraries does this tutorial use?

// package.json
{
    "devDependencies": {
        "@babel/preset-react": "^7.0.0", // 7.12.13
        "@popperjs/core": "^2.9.1", // 2.9.1
        "@symfony/stimulus-bridge": "^2.0.0", // 2.1.0
        "@symfony/ux-chartjs": "file:vendor/symfony/ux-chartjs/Resources/assets", // 1.1.0
        "@symfony/webpack-encore": "^1.0.0", // 1.0.4
        "bootstrap": "^5.0.0-beta2", // 5.0.0-beta2
        "core-js": "^3.0.0", // 3.8.3
        "jquery": "^3.6.0", // 3.6.0
        "react": "^17.0.1", // 17.0.1
        "react-dom": "^17.0.1", // 17.0.1
        "regenerator-runtime": "^0.13.2", // 0.13.7
        "stimulus": "^2.0.0", // 2.0.0
        "stimulus-autocomplete": "^2.0.1-phylor-6095f2a9", // 2.0.1-phylor-6095f2a9
        "stimulus-use": "^0.24.0-1", // 0.24.0-1
        "sweetalert2": "^10.13.0", // 10.14.0
        "webpack-bundle-analyzer": "^4.4.0", // 4.4.0
        "webpack-notifier": "^1.6.0" // 1.13.0
    }
}