Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine

The Values API

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

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.

... lines 1 - 3
{% if addToCartForm.color is defined %}
<div
data-controller="color-square"
data-color-id="{{ addToCartForm.vars.data.product.colors[1].id }}"
>
... lines 9 - 22
</div>
{% endif %}
... lines 25 - 42

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.

... lines 1 - 2
export default class extends Controller {
... lines 4 - 7
connect() {
... lines 9 - 10
console.log(this.element.dataset.colorId);
}
... lines 13 - 43
}

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.

... lines 1 - 2
export default class extends Controller {
... lines 4 - 6
static values = {
colorId: Number
}
... lines 10 - 46
}

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

... lines 1 - 10
connect() {
... lines 12 - 13
console.log(this.colorIdValue);
}
... lines 16 - 48

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.

... lines 1 - 3
{% if addToCartForm.color is defined %}
<div
... line 6
data-color-square-color-id-value="{{ addToCartForm.vars.data.product.colors[1].id }}"
>
... lines 9 - 22
</div>
{% endif %}
... lines 25 - 42

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.

... lines 1 - 3
{% if addToCartForm.color is defined %}
<div
{{ stimulus_controller('color-square', {
'color-id': addToCartForm.vars.data.product.colors[1].id
}) }}
>
... lines 10 - 23
</div>
{% endif %}
... lines 26 - 43

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!

7
Login or Register to join the conversation
Default user avatar
Default user avatar Andromadus | posted 1 year ago | edited

Good day there and the rest of the awesome SymfonyCasts team.

I have been using Symfony to develop web apps and recently decided to give Stimulus a try.

The journey has been smooth... Till I found an issue while attempting to use the Values API.

  • This works:
    static values = { myIndex: Number };

  • However, this doesn't:

     static values = { myIndex: { type: Number } };```
    

According to the official docs at https://stimulus.hotwired.dev/reference/values , this should be possible.

After some investigation, I discovered some things:

  1. The new syntax (the one that doesn't work) wasn't possible until a recent update to Stimulus (version 3.0.0)
  2. The stimulus core package that Symfony UX/Stimulus bridge uses is on version 2.0.0.

In summary, the stimulus dependency needs an urgent update.

I hope this gets resolved soon though. I'm not sure of what other cool features are available in V3.

Thank you. I wish you all the best.

Reply

Hey Andromadus!

Nice to chat with you :). V3 does have some cool stuff - but it's not a giant update. The values API has a new way to define optional values and there's a new debug mode, which I think is really cool. There are a few other things - you can read about them here - https://world.hey.com/hotwi... - but nothing major. The biggest reason they released a version 3 (and not a version 2.1) was that they renamed the package to @hotwired/stimulus. We will add some notes to the tutorial soon - but only once we have updated the Symfony UX tools for Stimulus 3.

> 2. The stimulus core package that Symfony UX/Stimulus bridge uses is on version 2.0.0.

Correct. I have a PR to upgrade to Stimulus 3 - https://github.com/symfony/... - and PR on the UX repository https://github.com/symfony/... - but we're not in a huge rush. I'd like to see the stimulus-use library release their v3 support first, ideally. But all the pieces are in place to support Stimulus 3.

> 1. The new syntax (the one that doesn't work) wasn't possible until a recent update to Stimulus (version 3.0.0)

Totally :). The old syntax works fine. The new syntax only works on Stimulus 3. However, I wouldn't use that syntax (just because it's longer) unless I actually needed to specify a default value for my value.

Let me know if you have any other questions about all of this :). The tl;dr is that Symfony UX will use Stimulus v3 soon & we'll add some notes about it in this tutorial... but other than the package name (and a couple of cool features), nothing much has changed.

Cheers!

Reply
John V. Avatar
John V. Avatar John V. | posted 2 years ago | edited

Hmm, I'm getting this too but I thought it was working earlier - I have WebpackEncoreBundle v1.11.1

there how do I go about debugging why it would not be finding the stimulus_controller function? Thanks!

Reply

Hey John,

> how do I go about debugging why it would not be finding the stimulus_controller function? Thanks!

Not sure if it is possible, sorry. I think the best you can do - write a console.log('hit!') in that file to make sure you're hitting that code that is responsible to finding the stimulus_controller function. If it is hit - you will see that "hit!" message in Console tab of your Chrome browser, and if so - you will need to double check your paths to the files, and the regular expression. If it's not hit - you will know the error now, try yo find the reason why it's not hit.

I hope it helps! Sorry I can't help more

Cheers!

Reply

Woohoo! Thanks for posting back that it worked!

> I stay curious about the upcoming symfony ux

I'm really excited about all this stuff - I'm releasing them as fast as a I can (though, I'm also still coding up the final chapters!)

Cheers!

Reply
weaverryan Avatar weaverryan | SFCASTS | posted 2 years ago | edited

Hey there!

Hi back to you from (currently) sunny Grand Rapids, MI :).

Hmm. This function is a new feature in WebpackEncoreBundle v1.11.0. Check what version you have with composer show symfony/webpack-encore-bundle. If it's too old, update it with composer update symfony/webpack-encore-bundle.

Let me know if that helps!

Cheers!

Reply

Oh, also, I should say, if you started coding along with this tutorial RIGHT when it was released... then you installed WebpackEncoreBundle before that feature even existed - it's very new. So that might explain it :)

Reply
Cat in space

"Houston: no signs of life"
Start the conversation!

This tutorial works perfectly with Stimulus 3!

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.18.5
        "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
    }
}
userVoice