gstreamer0.10-ffmpeg
gstreamer0.10-plugins-good
packages.
Ok: time for Stimulus! First stimulus is... a JavaScript library! If you start a new project and install Encore fresh, like we did, then thanks to the recipe, stimulus
is already inside of your package.json
file.
We also have an @symfony/stimulus-bridge
library. This adds superpowers on top of stimulus. I'll tell you exactly what those are as we go along.
If you don't have these packages, install them with:
yarn add stimulus @symfony/stimulus-bridge --dev
Let me close a few tabs and open up our assets/app.js
file. This imports a bootstrap.js
file that the recipe also gave us. And you will need this: if you don't have it, you can get it from the code block on this page or the stimulus-bridge
docs.
The one line in this file starts stimulus by telling it to look for stimulus controllers in this controllers/
directory, which is literally assets/controllers/
. Symfony gives us one dummy controller to start.
And... yea, the entire point of this file is to say:
Hey Stimulus! I have some controllers in this
controllers/
directory.
We'll learn what all this weird lazy-controller
stuff is a bit later, it's not important yet.
The best way to see how Stimulus works is just to try it. Delete the hello_controller.js
file: let's create our first controller from scratch. Call it counter_controller.js
. To learn the Stimulus basics, we're going to create an element that tracks how many times we click it.
Oh and this naming convention is important. All of our controller files will be something_controller.js
. And you'll see why in a minute.
Tip
If you start a new project today, you'll be using Stimulus 3. You can check by looking in your
package.json
file for @hotwired/stimulus
. The only thing you need to change for Stimulus 3
is the import statement. Use:
import { Controller } from '@hotwired/stimulus';
Inside the file, these always start the same way: import {}
from stimulus
and what we want to import is Controller
. Then, export default
a class that extends Controller
. Inside the class, add a method called connect()
with this.element.innerHTML =
a message:
You have clicked zero times ð¢
import { Controller } from 'stimulus'; | |
export default class extends Controller { | |
connect() { | |
this.element.innerHTML = 'You have clicked me 0 times ?'; | |
} | |
} |
That's all we need for now. To see what this does, we need to add a matching element to one of our pages. Open templates/product/index.html.twig
.
This is the template for the homepage. Down a bit, how about at the top of the main content, add <div data-controller="counter"></div>
.
... lines 1 - 2 | |
{% block body %} | |
... lines 4 - 9 | |
<div class="col-xs-12 col-9"> | |
... line 11 | |
<div data-controller="counter"></div> | |
... lines 13 - 82 | |
</div> | |
... lines 84 - 85 | |
{% endblock %} |
We can put something in the div
, but we don't need to for our example.
The data-controller
connects this element to the controller class that we just created. Because we named the file counter_controller.js
, the controller's name internally in Stimulus is counter
: it strips off the _controller.js
part.
So we connected the element to that controller with data-controller="counter"
. Thanks to that, this should work!
As a reminder, I still have a yarn watch
going over in my terminal, so it's been happily rebuilding each time we make a change.
Spin over to your browser and click to get to the homepage. Yes! It's alive! The empty div has our message! Inspect that element. Yep! We can see data-controller
and the text inside.
This is the magic of stimulus. As soon as it sees an element with data-controller="counter"
, it instantiates an instance of our "counter" controller and calls the connect()
method... named that way because Stimulus is "connecting" this object to a specific element on the page. And, as you can see, the element we just got connected to is available via this.element
.
That allowed us to easily set its inner HTML.
The beauty is that we can have as many of these elements on the page at the same time as we want. I'll copy the div
and, up in the aside
, paste.
... lines 1 - 2 | |
{% block body %} | |
... lines 4 - 5 | |
<aside class="col-xs-12 col-3"> | |
<div data-controller="counter"></div> | |
... line 8 | |
</aside> | |
... lines 11 - 86 | |
{% endblock %} |
Go refresh now. Two messages! And the really cool part is that each element is connected to a separate instance of our controller class. This means we get to write clean JavaScript code in a class and store information specific to its element as properties on that object. We'll do that very soon.
So... with Stimulus, we get objects that are bound to individual HTML elements and are instantiated automatically when those elements appear on the page. I would use Stimulus just for that! It's the simple, object-oriented JavaScript approach I've always tried to create on my own.
But wait there's more! Next: let's add a count
property and a click listener to show how each element is connected to a separate controller object. Then I'll show you the feature of Stimulus that absolutely knocked me over when I first saw it.
// 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.18.5
"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
}
}
// 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
}
}