This pull request introduces two new APIs to Stimulus: Values and Classes. These APIs are designed to improve upon, and ultimately obviate, the current Data Map API. We plan to ship them together in the upcoming Stimulus 2.0 release.
Values
Most uses of the Data Map API in Basecamp fall under the following categories:
- Storing small strings, such as URLs, dates, or color values
- Keeping track of a numeric index into a collection
- Bootstrapping a controller with a JSON object or array
- Conditioning behavior on a per-controller basis
However, the Data Map API only works with string values. That means we must manually convert to and from other types as needed. The Values API handles this type conversion work automatically.
Value properties
The Values API adds support for a static values
object on controllers. The keys of this object are Data Map keys, and the values declare their data type:
export default class extends Controller {
static values = {
url: String,
refreshInterval: Number,
loadOnConnect: Boolean
}
connect() {
if (this.loadOnConnectValue) {
this.load()
}
}
async load() {
const response = await fetch(this.urlValue)
// ...
setTimeout(() => this.load(), this.refreshIntervalValue)
}
}
Supported types and defaults
This pull request implements support for five built-in types:
Type | Serialized attribute value | Default value
---- | -------------------------- | -------------
Array | JSON.stringify(array)
| []
Boolean | boolean.toString()
| false
Number | number.toString()
| 0
Object | JSON.stringify(object)
| {}
String | Itself | ""
Each type has a default value. If a value is declared in a controller but its associated data attribute is missing, the getter property will return its type's default.
Controller properties
Stimulus automatically generates three properties for each entry in the object:
Type | Kind | Property name | Effect
---- | ---- | ------------- | -----------
Boolean, Number, Object, String | Getter | this.[name]Value
| Reads data-[identifier]-[name]-value
Array | Getter | this.[name]Values
| Reads data-[identifier]-[name]-values
Boolean, Number, Object, String | Setter | this.[name]Value=
| Writes data-[identifier]-[name]-value
Array | Setter | this.[name]Values=
| Writes data-[identifier]-[name]-values
Boolean, Number, Object, String | Existential | this.has[Name]Value
| Tests for presence of data-[identifier]-[name]-value
Array | Existential | this.has[Name]Values
| Tests for presence of data-[identifier]-[name]-values
Note that array values are always pluralized, both as properties and as attributes.
Value changed callbacks
In addition to value properties, the Values API introduces value changed callbacks. A value changed callback is a specially named method called by Stimulus whenever a value's data attribute is modified.
To observe changes to a value, define a method named [name]ValueChanged()
. For example, a slideshow controller with a numeric index
property might define an indexValueChanged()
method to display the specified slide:
export default class extends Controller {
static values = { index: Number }
indexValueChanged() {
this.showSlide(this.indexValue)
}
// ...
}
Stimulus invokes each value changed callback once when the controller is initialized, and again any time the value's data attribute changes.
Even if a value's data attribute is missing when the controller is initialized, Stimulus will still invoke its value changed callback. Use the existential property to determine whether the data attribute is present.
Classes
Another common use of the Data Map API is to store CSS class names.
For example, Basecamp's copy-to-clipboard controller applies a CSS class to its element after a successful copy. To avoid inlining a long BEM string in our controller, and to keep things loosely coupled, we declare the class in a data-clipboard-success-class
attribute:
<div data-controller="clipboard"
data-clipboard-success-class="copy-to-clipboard--success">
and access it using this.data.get("successClass")
in the controller:
this.element.classList.add(this.data.get("successClass"))
The Classes API formalizes and refines this pattern.
Class properties
The Classes API adds a static classes
array on controllers. As with targets, Stimulus automatically adds properties for each class listed in the array:
// clipboard_controller.js
export default class extends Controller {
static classes = [ "success", "supported" ]
initialize() {
if (/* ... */) {
this.element.classList.add(this.supportedClass)
}
}
copy() {
// ...
this.element.classList.add(this.successClass)
}
}
Kind | Property name | Effect
---- | ------------- | -----------
Getter | this.[name]Class
| Reads the data-[identifier]-[name]-class
attribute
Existential | this.has[Name]Class
| Tests whether the data-[identifier]-[name]-class
attribute is present
Declarations are assumed to be present
When you access a class property in a controller, such as this.supportedClass
, you assert that the corresponding data attribute is present on the controller element. If the declaration is missing, Stimulus throws a descriptive error:
If a class is optional, you must first use the existential property (e.g. this.hasSupportedClass
) to determine whether its declaration is present.
Unifying target attributes
We've made a change to the target attribute syntax to align them with values and classes, and also to make the controller identifier more prominent by moving it into the attribute name.
The original syntax is:
<div data-target="[identifier].[name]">
and the updated syntax is:
<div data-[identifier]-target="[name]">
The original syntax is supported but deprecated
Stimulus 2.0 will support both syntaxes, but using the original syntax will display a deprecation message in the developer console. We intend to remove the original syntax in Stimulus 3.0.
Try it out in your application
Update the Stimulus entry in package.json
to point to the latest development build:
"stimulus": "https://github.com/stimulusjs/dev-builds/archive/b8cc8c4/stimulus.tar.gz"