Actions: Listening to Events
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 ofthis.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.
Even after removing the addEventListener from the connect() function, it still increments if I click on the text, not just the button.
This is my controller
This is my div:
The listener is still attached to the data-controller div, not the button.
I'm in the dev environment, I cleared the cache, I have yarn watch running and it shows that it did compile successfully, and I also deleted the temporary files in my browser.