Magic with Events, Properties & HTML from AJAX

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.

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.

... lines 1 - 2
export default class extends Controller {
connect() {
... line 5
this.count = 0;
... 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.

... lines 1 - 3
connect() {
... lines 5 - 7
this.element.addEventListener('click', () => {
this.count++;
this.element.innerHTML = this.count;
});
}
... 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.

Leave a comment!

What PHP libraries does this tutorial use?

// composer.json
{
    "require": {
        "php": ">=7.2.5",
        "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.1.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.28.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
        "@symfony/stimulus-bridge": "^2.0.0", // 2.0.0
        "@symfony/ux-chartjs": "file:vendor/symfony/ux-chartjs/Resources/assets", // 1.1.0
        "@symfony/webpack-encore": "^1.0.0", // 1.0.4
        "bootstrap": "^4.6.0", // 4.6.0
        "core-js": "^3.0.0", // 3.8.3
        "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-use": "^0.22.1", // 0.22.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
    }
}