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!
Magic with Events, Properties & HTML from Ajax
Scroll down to the script below, click on any sentence (including terminal blocks) to jump to that spot in the video!
To show off the power of this simple, controller-instance-bound-to-HTML-element concept, let's count how many times each element is clicked and print that inside the element.
Adding a Controller Property
Head over to the controller. I'm going to start by inventing a new property called count in connect()
: this.count = 0
.
Show Lines
|
// ... lines 1 - 2 |
export default class extends Controller { | |
connect() { | |
Show Lines
|
// ... line 5 |
this.count = 0; | |
Show Lines
|
// ... lines 7 - 11 |
} | |
} |
That property isn't a stimulus
thing... I'm just creating a property for our own use and initializing it to zero.
Below this, attach a click
listener to our element: this.element.addEventListener()
, click
then a hipster arrow function. Oh, that's mad because I forgot my comma!
Inside, say this.count++
to increment and then this.element.innerHTML = this.count
.
Show Lines
|
// ... lines 1 - 3 |
connect() { | |
Show Lines
|
// ... lines 5 - 7 |
this.element.addEventListener('click', () => { | |
this.count++; | |
this.element.innerHTML = this.count; | |
}); | |
} | |
Show Lines
|
// ... lines 13 - 14 |
What about jQuery?
If you're not super familiar with using the native DOM Element object methods, like addEventListener()
, don't worry! I'm not using jQuery because, in a lot of cases, it's really not needed. But if you're more comfortable with using jQuery, awesome! You can totally still use it! Just install it - yarn add jquery --dev
- then import it into any file that needs it - import $ from 'jquery'
. Oh, and if you're migrating a legacy app where you have jquery already included via a script
tag, that's something we talk about in our Encore tutorial.
Anyways, if you are using jQuery, just use $(this.element)
before you call any methods - like $(this.element).on('click')
. Though, we are going to learn a cooler way to attach event listeners in a few minutes.
Ok: I think this is ready! Move over and refresh. Now... click. Boom! The count increments and prints in the element. And, most importantly, we can see that each element is working independently. This proves that there are two separate objects with two separate count
properties.
When data-controller Elements are Loaded via Ajax
This isn't even my favorite part of Stimulus. Down in your browser's inspector, right click on the div around one of our controllers and go to "Edit as HTML". Copy the <div data-controller>
and paste to hack in a new one right above it.
What I'm doing is imitating what happens when HTML is added to the page after it's done loading, like via an Ajax call. This is a classic problem with JavaScript. Suppose you have some jQuery code that attaches a click
event listener to all elements that have some class. Usually, that code runs when the page finishes loading.
Now, what happens if you load new HTML onto the page later via Ajax and the HTML contains an element with that class? Is the event listener automatically attached to it? Nope! It's not... unless you go to the hassle of manually re-calling your function that attaches the event listener.
So: can stimulus handle this? Can it somehow "notice" that a new element with data-controller
was added to the page? The answer is.... yup!
When I click off to add the new element to the page... it works! Behind the scenes Stimulus actually did notice that a new element was added to the page and instantiated a brand new controller object for it. That's incredible! It's a game-changer! I can now write nice controller classes, return HTML via Ajax and not have to worry about re-initializing behavior on that new HTML.
And, of course, the controller works exactly like the other ones: it increments as I click.
If this were the end of Stimulus's features, I'd absolutely use it. But it's not! Let's learn about "targets" next: an easy way to find elements inside of the controller's main element.
15 Comments
Hey @Ryan-L
Yea, you're right about jQuery, if you attach the event to a parent element, the event will be attached automatically to new elements, but that's something you don't need to know or care anymore with Stimulus :)
Cheers!
Hi, the fact that with each click it increases me by 2 instead of 1 is due to some structure of the html.twig file, right ? Thanks
[SOLVED] I found, it is the layout too full of other js files that caused me the double event, I restarted from a very poor layout and stilus is working again.
Hey @pasquale_pellicani ,
Glad to hear you were able to find the solution. Thanks for sharing the actual problem
Cheers!
I think it's a matter of import and positions of the generated assets, for example now I miss the jQuery but if I include it manually (without the webpack) it goes back to firing the clicks two at a time...
Hm, it sounds like you include something twice... or probably you have 2 Stimulus controllers created? Better to import it correctly, or even better to stop using jQuery :)
Cheers!
Hi @Victor thanks for the suggestion but now that i have only one controller it keeps loading me the connect() event twice ... why ? I loaded the app.js file into the main view :(
I solved with / stimulusFetch: 'lazy' / in controller code...
import { Controller } from '@hotwired/stimulus';
/* stimulusFetch: 'lazy' */
export default class extends Controller {....
Hey @pasquale_pellicani ,
That stimulusFetch: 'lazy'
is a good idea, all my Stimulus controllers have that. But even without that the connect should not be called more than once, unless you assigned that controller to more than 1 element.
Cheers!
...strange because I assigned it only to a one unique div present in the view index. I don't know what could be the cause.
in console I've this output:
application #starting
conteggia #initialize //conteggia is my stimulus controller
conteggia #connect
application #start
conteggia #disconnect
conteggia #connect
...... any problem in my code ?
Hello, I have a question. What is the right way to manipulate a global variable? Let's say I have two "click me" elements and 1 counter variable. I want If I click on element 1 or element 2, both elements counters to increment by 1. In this scenario, where should I put the global variable?
Hey Eddie
As a recommendation, try to avoid using global variables, they just add complexity to your code and there are alternatives. If you're doing it using Stimulus, it comes to my mind that you can emit an event whenever an element is clicked, so you can listen to that event and increase the counter. Does it makes sense to you?
Btw, if you don't know how to work with events in Stimulus controllers, this chapter will be of great help https://symfonycasts.com/sc...
Cheers!
hey guys, we are waiting the tutos....
Hey Alberto,
Thank you for your interest in this tutorial! The video is already published, pleasant viewing ;)
P.S. More will come soon, usually we're trying to release at least one video a day, but sometimes things go busy, thank you for your patience!
Cheers!
"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
}
}
JQuery event listeners actually can be attached to new elements, so long as you approach it from the perspective of a containing element.
For example, instead of $('.class').on('click', function() {});
You do this: $('#container').on('click', '.class', function() {});
http://api.jquery.com/on/
For example, if each row in a table came with a specific cell that was rendered as a link, which you could click to edit, you could do something like:
$('#nameOfTable').on('click', '.editLinkClass', function() { code to launch editing nonsense ));
Even if new rows came in, a page refresh isn't necessary; the function still triggers because it's technically listening to the table, and only firing off when the clicked element has the class in question. Well, either way, I'm sure Stimulus will be much, much easier to work with.