The Values API

This Chapter isn't
quite ready...

Rest assured, the gnomes are hard at work
completing this video!

Browse Tutorials

What if we want a color to be automatically selected on page load? Like maybe there's a most popular color that we want to suggest.

The best approach would probably be to pre-select an option in the select element. Then we could read that from inside our Stimulus controller - probably in connect() - and set that color square as "selected".

But for the purposes of learning, we're going to do something different to show a solution to a really common problem: the problem of how to pass options and server-side data into your Stimulus controller. In this case, our Symfony code will decide which color should be selected by default and we need to pass that info into Stimulus.

Passing info Via a Data Attribute

We already know one approach to do this: data attributes! We invented a data attribute on the button to add more information to it. So... if we want to pass something to our controller, why not add a data attribute to the top level controller element?

A fantastic suggestion! Break the element onto multiple lines and then add data-color-id=. To keep things simple, let's pre-select whatever the second color is. To do that, copy this whole addToCartForm.vars thing from down here, paste, and at the very end, add [1].id to use the id of the second color.

Done! Inside the controller... inside connect(), let's see if we can log this: console.log(), get the top-level element - this.element - and read its data: .dataset.colorId.

Let's give it a go! When we refresh... we can see the data-color-id in the HTML and... over in the log... there's the id!

Hello Values API

So this works great. But this is such a common thing to do - passing info from your server into your Stimulus controller - that stimulus gives us a special system for handling this... with a couple of really nice advantages. It's called the values API.

Here's how it works. Step 1, add a static values property set to an object. Each "value" that we want to allow to be passed into our controller will be a key in this object. So we want a colorId value. Set this to the type that this will be. In our case, colorId will be a Number.

As soon as we define this, we can magically access a colorIdValue property. Let's log it: console.log(this.colorIdValue).

Finally, to pass this value into the controller, we will use a data attribute, but with a special syntax. It's data- the name of the controller - color-square - the name of our value - color-id - the name is colorId in our controller, but it will be color-id in HTML - then -value= the actual value.

I know what you're thinking: that is ugly! Don't worry, we're going to learn an awesome shortcut in a minute.

Type Coercion

But let's try it. Move over and refresh. Woohoo! It works! And something subtle just changed. If you dug deeper, you'd find out that the 2 in the log is a Number type. But before we started using the values API, the 2 was a string!

That second part makes sense. Technically, in HTML, data attributes are strings! If you read something from the dataset property, it will always be a string.

But the values API allows us to set the type for each value. It then handles converting the string into that type. We can use things like Object, Array, Boolean, or any other JavaScript type.

The stimulus_controller() Method

I love the values API and it has one sweet trick up its sleeve that we'll learn about in the next video. But the syntax, woof.

So far, we've created all of our data-controller elements by hand... because... it's pretty simple to write data-controller="color-square".

But WebpackEncoreBundle gives us a shortcut method. Check it out: inside the element where we want the data-controller to be, add {{ stimulus_controller() }} and pass the name of the controller: color-square.

Then, for the second optional argument, we can pass an array of values. We have one: color-id set to the long addToCartForm.vars line.

Celebrate by deleting data-controller and the value.

This will give us the exact same result as before. We can see it: inspect element, find data-controller and... refresh! Sweet! It does look the same as before and we still get the log.

I love this feature because the values API is incredibly powerful and this removes all the pain of using it.

Values are HTML Escaped Automatically

Oh, and this function also automatically escapes each value so it's safe to use in an HTML attribute. So if a value contains a double-quote, it won't break your page. We'll see this later when we use the values API to pass props to a React app.

Values Names are Normalized

The function also normalizes the value names automatically. If you want, you can use colorId here... and we don't even need the quotes anymore. This is nice because it now exactly matches the name of the value inside the controller.

When this renders - I'll refresh and go back to Elements - it still outputs the same attribute name... and it all still works.

Next: let's use this new colorIdValue property to pre-select the color. Thanks to the organization of our controller, that's going to be pretty easy. And thanks to a feature of the values API that we have not seen yet, we're going to end up with less code after adding the new feature. Cool.

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