loading...
Cover image for A few sneak peeks into Hey.com technology (V - Stimulus enhancements)

A few sneak peeks into Hey.com technology (V - Stimulus enhancements)

borama profile image Matouš Borák Updated on ・6 min read

A few sneak peeks into Hey.com technology (7 Part Series)

1) A few sneak peeks into Hey.com technology (I - Intro) 2) A few sneak peeks into Hey.com technology (II - Keyboard shortcuts) 3 ... 5 3) A few sneak peeks into Hey.com technology (III - Turbolinks frames) 4) A few sneak peeks into Hey.com technology (IV - Turbolinks frames continued) 5) A few sneak peeks into Hey.com technology (V - Stimulus enhancements) 6) A few sneak peeks into Hey.com technology (VI - Template page updates) 7) A few sneak peeks into Hey.com technology (VII - Template page updates continued)

Today, I’ll show you some nice little syntactic sugar that should be released with the next Stimulus version − the new values and classes APIs. This may not be entirely news for those who watch the Stimulus forum, but I still think this stuff is very well worth mentioning as it makes the Stimulus controllers more concise and flexible.

Both new APIs originate from this great pull request by Sam Stephenson and both of them are also used on the Hey website so I really expect them to be included in the new Stimulus release, soon!

Let’s have a look at what goodness they bring. But before that we should talk a bit about the data API that has been with Stimulus since long ago.

A small refresh on Stimulus data API

In case you need to pass some data to your Stimulus controller, you normally do that via the data API.

Suppose we want to build an online currency conversion page that will accept an amount in US dollars and convert it to Euros. I created a very basic version of such component in a JS fiddle, feel free to have a look and play with it…

Note that the targets definition in the fiddle has to be slightly different from the syntax you’ll normally write if you use the webpack bundler (this is explained here).
I’m also aware that Float is not a very good data type when working with money but let’s ignore such details for now 😏.

The Stimulus data API is used here for passing the conversion rate to the controller − it’s the data-conversion-rate attribute:

<div data-controller="conversion" data-conversion-rate="0.881709">
  ...
</div>

The Stimulus controller then gets the data value via the data API get method:

// conversion_controller.js
export default class extends ApplicationController {
  ...

  calculate() {
    ...
    const convertedValue = parseFloat(this.data.get("rate")) * parseFloat(value)
    ...
  }
}

There are other methods for working with such data besides get, similar to the ruby hash syntax a bit − see the docs for more info on that.

Now, the data API has a few limitations:

  • It only works with Strings. That’s why we had to explicitly parse the data value into Float using parseFloat(). You cannot work with numbers, arrays, objects,... only strings in the data API.

  • If we wanted to add more currencies to the converter, we would have to either:

    • add more separate data attributes to the HTML element, one for each currency (and the controller would have to “know” about them), or
    • we would have to construct our own custom serialization technique to “encode” all currency rates into a single data attribute (and, again, the controller would have to “know” about this technique).

    Both options seem quite cumbersome and that’s, I think, exactly why the values API was born.

The values API

I created another JS fiddle that shows the same converter, only a bit extended in its capabilities, but using the new APIs (play with it!).

The values API has several distinct new features and syntax enhancements:

  • It works not only with Strings but also with Numbers (Floats), Booleans, Arrays and even Objects (Hashes). Stimulus also brings in a convention to serialize all these types to and from the HTML attributes so you don’t have to bother with that (details in the pull request).

    You can clearly see a non-String type in the values definition in the controller. So, our rates value is an Object, not String (again, the syntax has to be a bit different in the fiddle but it is equivalent to this):

static values = {
  rates: Object
}
  • Stimulus defaults to blank / empty / zero values when the corresponding data attributes are missing in the HTML element.

  • The API provides a new convention for naming the HTML attributes and accessing the values in controllers: e.g., for a data-{controller-name}-{value-name}-value attribute, you can get it’s value using this.{valueName}Value controller method. In our example, as we said, we store the multiple currency rates as an Object in the value attribute and we access it like the following in the controller:

We define the value object like this in HTML:

<div data-controller="conversion" 
     data-conversion-rates-value="{&quot;EUR&quot;: 0.881709, &quot;GBP&quot;: 0.798940, &quot;AUS&quot;: 1.435456, &quot;IND&quot;: 75.499926}">
...
</div>
// The value object is accessed like this in the controller:
console.log(this.ratesValue)

// outputs:
// {
//   AUS: 1.435456,
//   EUR: 0.881709,
//   GBP: 0.79894,
//   IND: 75.499926
// }
  • Note that the conversion rates is a hash (an object) of Floats, not Strings − Stimulus did the de-serialization and type conversion for us.

  • Also note that we must properly HTML-encode the value in the attribute but this is what most frameworks (such as Rails) usually do automatically. It’s a bit of shame that this makes the HTML source much less readable but luckily modern browser dev tools display these encoded strings nicely:

Encoded value in dev tools

We see that once we have the conversion rates available in a single object, we can quite easily enumerate and show them in our sample converter. It is also trivial to add a new currency, now. This is all possible thanks to the new values API!

There are a few other niceties in the values API planned, see the pull request for details, if you’re too curious.

The classes API

Another common task in Stimulus controllers, is updating the CSS classes of the corresponding elements and their various children elements. New Stimulus should provide a new API for handling this, as is also shown in the JS fiddle.

  • Similar to targets or values, in the Stimulus controller you define the classes that you expect to be set in the HTML element.

  • There is again a consistent convention on naming the class attributes in the HTML element, as well as the methods for accessing them in the JS controller. Just try to fill in a price higher than $1000 in the converter and see what happens…

One thing that I personally dislike about the new classes API is that it does not support atomic styling (you know what I’m talking about if you used the Tailwind or Tachyons CSS frameworks).
I really like atomic styling but adding more than a single class to the Stimulus class attribute leads to a nasty exception! There is a proposal in the pull request to address this issue but I couldn’t find any attempt to use this in Hey code… 😔

More consistent naming convention

The new API adds two proposals for naming the HTML attributes for classes and values − they should, not surprisingly, end with "...-value" or "...-class". Nice, but now it turns out that the current convention for naming targets (a feature of Stimulus that we did not talk about today, see the docs) goes against it.

That’s why new Stimulus will encourage you to rewrite your target attribute names from data-target="{controller-name}.{target-name}" to data-{controller-name}-target="{target-name}" and the old syntax is going to be slowly deprecated. In the JS fiddle, you’ll see the new syntax in the two target HTML attributes (input and converted).

Conclusion and a note about data API

So, this is what we can expect in the new Stimulus release! A more convenient API for passing values between your HTML and JS controllers and a nice little syntactic sugar for handling CSS classes.

How are these new APIs used in Hey? Let’s just show a few examples, without much further commentary.

In the search_controller.js (which manages… emmm… the main site search!), you’ll find the following values and classes definitions:

search_controller.js

This is e.g. how one of the values is actually used in the controller:

Value usage in the search controller

And this is what the corresponding HTML element looks like. Marked are the value attributes (red) and class attributes (green) in the element:

Main search element

And you know what is really interesting? The current Stimulus data API seems not to be used in Hey at all! Why is that? As is also noted in the pull request, the new values API may actually serve as a better successor for the data API − the values API can do everything the same and more… and the syntax is more consistent with other features. Thus, after the new Stimulus release there will be no big reason to continue using the data API. The data API is dead, long live the values API!

Next time, we’ll get back to analyzing live Hey pages updates, this time via the <template> element. There’s some exciting stuff, there, stay tuned! 😉

A few sneak peeks into Hey.com technology (7 Part Series)

1) A few sneak peeks into Hey.com technology (I - Intro) 2) A few sneak peeks into Hey.com technology (II - Keyboard shortcuts) 3 ... 5 3) A few sneak peeks into Hey.com technology (III - Turbolinks frames) 4) A few sneak peeks into Hey.com technology (IV - Turbolinks frames continued) 5) A few sneak peeks into Hey.com technology (V - Stimulus enhancements) 6) A few sneak peeks into Hey.com technology (VI - Template page updates) 7) A few sneak peeks into Hey.com technology (VII - Template page updates continued)

Posted on by:

borama profile

Matouš Borák

@borama

CTO at NejRemeslnici, Ruby on Rails developer. Dad of two.

Discussion

markdown guide
 

Cool!
But does it mean that you can't access same target from multiple controllers (now controller is in name of data attribute)?

 

I guess not but could you really do that now either? It never occurred to me to try such thing, I always understood the target to be controller-specific.