Chapters
-
Course Code
Subscribe to download the code!
Subscribe to download the code!
-
This Video
Subscribe to download the video!
Subscribe to download the video!
-
Subtitles
Subscribe to download the subtitles!
Subscribe to download the subtitles!
-
Course Script
Subscribe to download the script!
Subscribe to download the script!
Stimulus Controllers
Scroll down to the script below, click on any sentence (including terminal blocks) to jump to that spot in the video!
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.
Creating our First Controller
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 ?'; | |
} | |
} |
Adding the data-controller Element
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>
.
Show Lines
|
// ... lines 1 - 2 |
{% block body %} | |
Show Lines
|
// ... lines 4 - 9 |
<div class="col-xs-12 col-9"> | |
Show Lines
|
// ... line 11 |
<div data-controller="counter"></div> | |
Show Lines
|
// ... lines 13 - 82 |
</div> | |
Show Lines
|
// ... 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.
Elements & Controller Objects
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.
Multiple Controller Instances on the Page
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.
Show Lines
|
// ... lines 1 - 2 |
{% block body %} | |
Show Lines
|
// ... lines 4 - 5 |
<aside class="col-xs-12 col-3"> | |
<div data-controller="counter"></div> | |
Show Lines
|
// ... line 8 |
</aside> | |
Show Lines
|
// ... 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.
50 Comments
Hey Thomas,
Good question :) Well, the simple answer is because technically it's really a dev dependency :) You do not use any of your JS dependencies directly on the front end because everything is going through the Webpack Encore, i.e. the Webpack Encore compiles the final files into the public/build/ directory. And as soon as the files are compiled - you can even completely drop the node_modules/ directory, it will still work. From this point of you all your JS dependencies are just dev deps. But it's not that much important, you can put them in the "dependencies" section instead if you want, but that's not the best practice we recommend. That's it :)
Cheers!
Cheers!
Thank you very much for your answer Victor.
I think I am doing a confusion about dev dependencies and thiere good practices.
For example the good practice for jquery is also: npm install jquery --save-dev
But in reality when I go in the production mode I need jquery.
In my mind the good practice would have been to use, a testing module for example, in dev dependencies.
So why stimulus or jquery use the flag --save-dev ?
Do you uderstand what I mean ?
Hey Thomas,
Unfortunately, you do not use the jQuery directly in prod :) What you're actually using in prod is a compiled version of jQuery, i.e. Webpack Encore compiled it, added some internal code around the jQuery code, and put it into the public/build/ directory. So once again, even with jQuery, it's technically a dev dependency for your project because it's needed only for Webpack Encore which in turn will compile the final files for you.
I hope it clarifies thing for you. But as I said, do whatever you like, you can add it to the "dependencies" section and it will still work ;)
Cheers!
Ok thank you Victor.
So I imagine you mean that if I modify my jquery libraries It will became some production libraries ?
I my mind, developpement libraries, are libraries used in the developpement periode only like eslint. My confusion must come from here.
Anyway, thank you very much for your answers and your time.
Loving Stimulus, and using it in places where I had been using jQuery for just a bit of interactivity.
What's the proper way to load stimulus controllers from a Symfony bundle? I'm assuming that in the bundle I can create an assets/controllers directory, but then how do I configure the app using the bundle to look in that directory for controllers? Perhaps in controllers.json? Or in Webpack?
Thanks.
Hiya Tac-Tacelosky !
You have 2 options here - one is the more "automated" way that the actual UX packages work. And the second is a simpler, but more manual process, which is totally fine if you are the one installing the bundles into your app :).
First, in both cases, yea, it's common to have an assets/ or Resources/assets directory in your bundle. And then, a controllers subdirectory is fine - you can organize that part however you want. So that's totally correct. You'll also want to put a package.json file in that directory so that it looks like a real package. The name won't really matter - since it will be an internal JavaScript package. Example: https://github.com/symfony/ux/blob/main/src/Chartjs/Resources/assets/package.json
You'll notice in the above example that they have a src/ and dist/ directory, you shouldn't need to worry about that for your own bundles. Just have the source files.
Anyways, if I remember correctly, as soon as you have this assets/package.json or Resources/assets/package.json file, when you install the bundle into a project, Flex will automatically update the project's package.json to point to this. It will look something like:
"@tacman/your-package": "file:/vendor/tacman/your-package/assets"
Where @tacman/your-package
is whatever you named your package in the bundle's package.json file.
At this point (after running yarn install --force
, you have JavaScript package available! Woo! To expose its controllers to the project's Stimulus, this is where those 2 options come into play:
1) The simpler option
Go into assets/bootstrap.js
and import each controller and register with Stimulus. So
import TacController from '@tacman/your-package/controllers/tac-controller';
// ...
app.register('tac', TacController);
2) The more magic option
To mimic what the core UX packages do, add a section like this to your bundle's package.json file https://github.com/symfony/ux/blob/ddd7953dab49e990486044a6a9827f70c11a01c5/src/Chartjs/Resources/assets/package.json#L6-L15
NOW you will be able to go into the assets/controllers.json file and point to the new controller. Actually, if you NOW installed the bundle, Flex would update the controllers.json file automatically.
Let me know if you hit any bumps! Really happy you're digging Stimulus - me too!
Cheers!
Thanks, Ryan, this is great. Now that Symfony 6.1 has an easy way to configure bundles, do you think you'll do another tutorial, maybe one that includes a stimulus controller?
I still can't figure this out, I've added it as an issue to symfony/flex, can you take a look if you have some time? Bundles with stimulus controllers -- can't wait!
Hey Tac-Tacelosky!
Sorry for the slow reply! It looks like you figured it out. Tbh, I didn't even realize that they keyword was a requirement - that issue will likely help others.
Cheers!
Hello Ryan. I just have a problem. My Bundles package.json contains a working configuration. And this even worked when I wrote the given entries manually into the controllers.json of my test app. Anyway symfony flex don't update automaticly the controllers.json when I install the bundle via composer. Is there something I've been missing? Maybe I have to push a recipe to the Contrib repo? I mean somehow flex has to know where my package.json is...
Hey Michael,
Yeah, unfortunately, Flex won't update those files. You can create a recipe, but Flex just replace files instead of updating them, so it will mean that your recipe will update users custom configuration. The good part is that with git diff users can check the difference and merge configs. Btw, IIRC you can write a post-install message in the recipe, so it might be an another way telling your users to go there and copy the working config into their own configuration. So, it's up to you what way to choose.
Cheers!
Ok, thank you for your reply. Where can I find a working example for that kind of recipe? I searched one for Ux swup but did not found one. And there is something what confuses me - when I install one of the symfony-ux composer packages both the package.json and the controller.json files are updated and NOT just overwritten. But when I install my bundle none of these gets even touched - not overwritten or modified...
Hey Michael,
See symfony/webpack-encore-bundle recipe, it should have all that stuff like package.json, assets/controllers.json, etc. Here's the link to the latest version: https://github.com/symfony/... - but I suppose you're interested in manifest.json file where everything is configured. Also, it has an example of that post-install message I was talking about, see post-install.txt there.
I hope this helps!
Cheers!
Thank you Victor. I found the mistake by looking at ux-collection-bundle.
The problem was symfony-flex don't see the package.json
when its inside src/Resources/assets folder. The Resources folder has to be inside the project root to make this work.
Hey Michael,
Ah, yes, it makes sense, good catch! Thanks for sharing the problem with others
Cheers!
My question is now - Do I have to name my package inside the package.json after the symfony bundle? I guess yes. I tried to rename it (make it shorter) but it was not recognized anymore. Am I right, that renaming is not possible ?
Ok, I found this here the moment I wanted to ask the same :D.
So I did it. What's worth mentioning is you will not be able to use the controller in a "normal" way. You have to write for every thing (controller, target, value ...) for example
data-controller="tacman--your-package--tac"
data-tacman--your-package--tac-target="target"
Hey Michael B.!
It looks like you already answered your own question - nice! I think it would be nice (we actually need it for Symfony Live Components - https://github.com/symfony/... to be able to add an "name" key in your package's package.json file that would override the controller name to make it short. The reason this doesn't exist is... mostly due to potential collisions - i.e. 2 packages both naming their controllers "foo". Unfortunately, we can't simply allow the user to override the name in their controllers.json file because some packages include PHP helpers that output the controller's name (e.g. for data-controller or targets). So if the user can change it dynamically... those helpers would output the wrong controller name.
Anyways, I hope you found a solution that makes you happy - it's on my todo list to at least make it possible to shorten the name in the package.json file.
Cheers!
Oh, and one more thing. For the long, ugly names, using the helpers - like {{ stimulus_controller('tacman/your-package/tag') }}
or {{ stimulus_target() }}
can make them less ugly - the "/" are normalized into the "--".
Yes. I found the {{ stimulus_controller('tacman/your-package/tag') }} function and thats how I found out the right spelling for the data-controller ;). I don't want to use this function for a bundle but it's good to know about it for an app.
Thank you! Cheers!
Yes - I already thought that it is going in that direction. Thank you anyway for your answer.
The video doesn't work with firefox v 123.0 on fedora linux
Hey @red_smeg
Is only this video failing or are there others? It could be a short outage from Vimeo, which is our video-hosting platform
Cheers!
I am trying to use AssetMapper, stimulus and turbo. I have a custom bundle that I use. I am lost as to how to use/register the custom bundles's stimulus controllers to my main project. Could you please advise.
Just realized there a course on this being released https://symfonycasts.com/screencast/last-stack
Hey @hrprakash!
I'm glad you saw that new course! I'm really happy with it. But, I can answer your question here. There are 2 parts:
1) Something needs to add the directory that contains the bundle's "public resources" to AssetMapper. You can actually just do this in your application (and not in the bundle) by editing asset_mapper.yaml
and adding a new path under assets/
: https://symfony.com/doc/current/frontend/asset_mapper.html#framework-asset-mapper-paths
2) Or, you can effectively do the same thing... but have your bundle add this path automatically. See https://github.com/symfony/ux/blob/2.x/src/StimulusBundle/src/DependencyInjection/StimulusExtension.php#L48-L65 and https://symfony.com/doc/current/bundles/prepend_extension.html
3) If you put bundle's assets live in a public/
or Resources/public
directory, they will automatically be in AssetMapper https://symfony.com/doc/current/frontend/asset_mapper.html#third-party-bundles-custom-asset-paths
Either way, once you have done 1, 2 or 3, you can verify by running bin/console debug:asset
to see if your bundle's public files are listed.
Once they are, to register your bundle's Stimulus controllers in your app, you have 2 options this time :).
A) You can simply register them in your app - in assets/bootstrap.js
- something like:
import YourController from '../../vendor/some-vendor/your-bundle/assets/your_controller.js;
// the rest of the code already in this file
app.register('your-controller-name', YourController);
B) If you are building your own bundle and you want this registration to happen automatically, you should add a package.json
file into your bundle. https://symfony.com/doc/current/frontend/create_ux_bundle.html#package-json-file
Let me know if that helps!
Cheers!
Thank you very much, it not only solved my current problem but also helped me adopt some other automations in my custom bundle. Thank you a bunch, you are AWESOME.
Woohoo! Very happy to hear it!
@weaverryan I have a problem with vue_component
. Symfony says this is the way how you render a vue component. Now I tried that in a bundle-related setup. Imagine I have a core
and a ui
bundle. If I create a component inside the core
bundle (it's the "root" bundle) vue_component('Hello')
works but if I create the exact same component in my ui
bundle it doesn't work (with a different name of course). I always get the following error (in the console) "Error connecting controller >
Error: Vue controller "Test" does not exist. Possible values: Hello"
I checked the following:
- Webpack (which I use) compiles without any problems
- the
controllers.json
file is there and holds the corresponding controller - the connection from the
core
to theui
bundle (via node_modules folder) is there and works
do you (or anyone) has an idea what I forget/do wrong?
Hi @Christian!
Apologies for the slow reply - was in deep in a tutorial!
Hmm, so let's see. The key part of getting this working is some code that you have, most likely in assets/app.js
. It should look something like this:
registerVueControllerComponents(require.context('./vue/controllers', true, /\\.vue$/));"
THAT is what tells the vue_component()
UX system where to find your vue controllers: inside assets/vue/controllers
. Anything in there will be available to vue_component()
. Anything NOT in there will not be. Unfortunately, I don't think that line is designed to be called multiple times: e.g. if you called this once to load from dir1 and again to load from dir2, I think that second call would override the first one.
So, not a full answer, but let me know if that helps :)
Cheers!
Update Stimulus to 3.2.x on symfony/webpack-encore-bundle
Hi,
I'm wondering when symfony/webpack-encore-bundle will support @hotwired/stimulus 3.2.x version.
There is a new Outlets function that is very useful.
Thanks!
Hey MAli,
Hm, I see the latest v4.2.0 Webpack Encore release allows "@hotwired/stimulus" ^3.0.0: https://github.com/symfony/webpack-encore/blob/v4.2.0/package.json#L58 - that means you should be able to update to the latest 3.x version without problems and start using those outlets. Did you try to upgrade? What exactly constraints you from upgrading to @hotwired/stimulus 3.2.x ?
Cheers!
Hey Victor,
I'm using this link to install encore(symfony/webpack-encore-bundle)
After running yarn install
I get @hotwired/stimulus version = 3.0.1 on node_modules folder.
I just want to upgrade stimulus to the latest version.
Can I do that manually by running yarn upgrade --scope @hotwired
?
wouldn't that effect symfony/webpack-encore-bundle ?
Hey MAli,
Yep, try to upgrade your Yarn deps manually. The problem is that you may have some dependencies installed and locked to specific versions that prevent you to install the latest "@hotwired/stimulus". I suppose you just need to try to update your Yarn dependencies as you suggested, or if it does not help - maybe try to upgrade ALL your Yarn dependencies - this should probably help. If not - then there might be a package installed that prevents you from upgrading to the latest Stimulus, but I bet the problem is not in webpack encore as you can see from its package.json file.
Cheers!
Thank you Victor,
It was very helpful. I'm going to upgrade all my yarn dependencies and it should work without any problem then.
Cheers!
Hey MAli,
Yeah, I think it should work. Good luck! Just keep in mind that if you upgrade some major versions - it may bring some BC breaks. But with patch/minor versions upgrade it should be OK I think
Cheers!
Hiiiii,
Need help for a specific development.
My app is embed in a website, but when the Stimulus start i have no controller initialize.
I get the debug log in my console, but the only trace i have is the Stimulus starting and start.
Any idea what happen ?
I also try to register the controller manually with app.register('tier-price', TierPriceController);
no more luck.
Hey Quentin!
Hmmm. Just to be sure:
My app is embed in a website
What do you mean by this?
I get the debug log in my console, but the only trace i have is the Stimulus starting and start.
So, obviously, this is a good sign: it means Stimulus is running :). Unless there is something VERY wrong, when a controller is not initialized, it is almost always because of some naming problem. Registering it manually with app.register()
is a very good way to try to debug things. When you load the page, do you see the data-controller="tier-price"
in your HTML? I would also try rebuilding Encore and doing a force refresh. This is one of those situations where the problem is usually some tiny thing.
Cheers!
My app is call by a website via an enoint that render my view. In this view i have my data controller and one action . When i render this view in à custom route in my app , everything work. But when i render in this Web site, via ajax , the controller is not registred. I have debug a litte bit and found when i print the Application class from stimulus bridge my controller is not in the symfonyCrontrollers variable.
The funy thing is that in the Router propertie I found an array with my controller...
Hey Quentin!
Hmm. So your app returns some HTML. And the other website makes an Ajax call and prints that HTML onto its page, right? Does that other page have Stimulus running? And if so, does that other website hold the code for your Stimulus controller? My guess is that there is some misconfiguration between where Stimulus & your controller is initialized.
Cheers!
Hello,
I want to ask how to pass .env variable to stimulus controller.
I am using libphonenumber-js and I am using this methodthis.phoneNumber = parsePhoneNumber(this.inputTarget.value, 'CZ');
.
I want to change 'CZ' with a variable from Symfony's .env...
Thank for reply
Hey Tomáš S.
I believe the way to go is to inject that value as a Stimulus controller value. You can get the env var from a Symfony Controller and pass it through your JS
Here are the docs for using Stimulus values https://stimulus.hotwired.d...
Cheers!
Hi,
yarn watch not working.
It worked with out any problem until yesterday but when I have made some changes and started yran watch it is not working/syncing.
- yarn watch shows no error message and says `webpack compiled successfully`
- yarn build works filne and gets me the latest changes
Any help please!
It's working now but taking long time to copy/sync new changes !!
Hey MAli
What's your local dev environment setup? I used to run under Windows WSL 1 and it's very slow at reading files. You may be facing something similar
Hi @DMolloKhan
Thank you for your reply!
I'm using Vagrant on mac.
Uhmm, I'm not familiar with that environment but you may want to upgrade your tools, for example, upgrade Nodejs to the latest version, also Yarn's version, it may help
Besides that, you could search for tricks to optimize your setup performance
Cheers!
Yes I'll try. Thank you very much :)
hello,
don't understand why ! but controller don't work for me ... ?
i follow exactly the same as the course ?
"Houston: no signs of life"
Start the conversation!
What PHP libraries does this tutorial use?
// composer.json
{
"require": {
"php": ">=8.1",
"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.21.6
"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
}
}
Hi,
Would someone tell me why stimulus is in the dev dependencies ?
yarn add stimulus @symfony/stimulus-bridge --dev
Thanks