Element.dataset

Keep on Learning!

If you liked what you've learned so far, dive in!
Subscribe to get access to this tutorial plus
video, code and script downloads.

Start your All-Access Pass
Buy just this tutorial for $12.00

With a Subscription, click any sentence in the script to jump to that part of the video!

Login Subscribe

To make our widget fully work, when we click a color, we need to change the real select element's value. That way, when we submit, the form will POST the correct color. Inspect element near the select and look at its options: each value is the id of that color in the database. So somehow, when we click a color square, we need to know the id of that color so we can set it as the select element's value.

Adding our Own Custom data- Attributes

Fortunately, there's a native way to add extra information to DOM elements: data attributes!

Over in the template, find the button and add a new data attribute: data-color-id equals {{ color.id }}.

... lines 1 - 7
{% for color in addToCartForm.vars.data.product.colors %}
<button
... lines 10 - 13
data-color-id="{{ color.id }}"
... line 15
></button>
{% endfor %}
... lines 18 - 37

This is the first time that we've used a data attribute that has nothing to do with Stimulus. We're just inventing this for our own purposes. The only rules about data attribute names is that they must, of course, start with data- and they must be all lowercase. You also usually see dashes between words like color-id.

Over in our controller, we could read the attribute manually... but we don't have to! JavaScript has a built-in way to read data- attributes: it's the dataset property.

At the bottom of selectColor(), console.log(event.currentTarget) - to get the button - then .dataset.colorId.

... lines 1 - 2
export default class extends Controller {
... lines 4 - 5
selectColor(event) {
... lines 7 - 11
console.log(event.currentTarget.dataset.colorId);
}
}

Notice that the color-id from the HTML becomes colorId inside this dataset property. This is... once again, not a Stimulus thing. This is just how data attributes work.

Let's test it out. Open the console and... when I click, yes! We see ids!

Adding the select Target

Now that we've got that, the next step is to find the select element. And, whenever we need to find something, it means that we need a target.

Over in the controller, add a second target called, how about, select.

... lines 1 - 2
export default class extends Controller {
static targets = ['colorSquare', 'select']
... lines 5 - 13
}

Then, in the HTML, add that target. Oh... but this is trickier because the form_widget() function is rendering the select element for us. No problem: we can pass a custom attribute. Add a second argument to form widget, pass an associative array, give this an attr key set to another associative array with data-color-square-target set to select.

... lines 1 - 3
{% if addToCartForm.color is defined %}
<div data-controller="color-square">
{{ form_widget(addToCartForm.color, {
attr: { 'data-color-square-target': 'select' }
}) }}
... lines 9 - 19
</div>
{% endif %}
... lines 22 - 39

Back over in the controller, assuming I haven't messed anything up, we should now be able to reference the select with this.selectTarget. Set its value with .value = and then event.currentTarget.dataset.colorId.

... lines 1 - 2
export default class extends Controller {
... lines 4 - 5
selectColor(event) {
... lines 7 - 11
this.selectTarget.value = event.currentTarget.dataset.colorId;
}
}

Ok! Let's try this thing! Refresh click and... awesome! As we click the colors, the select updates. This is fun!

At this point... we're done! This will work! To celebrate, let's hide the select element. We could do that in Twig or in Stimulus - it's up to you. If you wanted to make your site work with and without JavaScript for some reason, you could hide the select element and show the color boxes at the same time in Stimulus.

Anyways, in the controller, add a connect() method. Then hide it with this.selectTarget.classList.add('d-none'), which will add a display: none since we're using Bootstrap.

... lines 1 - 5
connect() {
this.selectTarget.classList.add('d-none');
}
... lines 9 - 19

Go refresh. Oh, that is lovely. Let's add a red sofa to the cart. When we submit... I think it worked! Go check out the shopping cart. It did!

Now that this is working, go check out our Stimulus controller. Yup, this whole feature required about 15 lines of JavaScript! That's thanks to the fact that all of our markup gets to live in Twig. Then, our JavaScript can stay lean and mean.

Over in the browser, click to go back to the sofa page. Next: I want to allow the user to click a color again to unselect that color. That won't be too hard, but to make it even easier, let's take advantage of the fact that we can store state on our controller.

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
    }
}