Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

The Values and Classes APIs #202

Merged
merged 44 commits into from Dec 3, 2020
Merged

The Values and Classes APIs #202

merged 44 commits into from Dec 3, 2020

Conversation

@sstephenson
Copy link
Contributor

@sstephenson sstephenson commented Nov 2, 2018

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:

Screenshot showing error message: "Missing attribute 'data-clipboard-supported-class'"

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"
sstephenson added 28 commits Sep 27, 2018
To be useful, date values need to be wrapped in a time zone-aware object.

This reverts commit 2da572d.
@sstephenson sstephenson added this to the 1.2 milestone Nov 2, 2018
@PFWhite

This comment has been hidden.

@leastbad
Copy link

@leastbad leastbad commented May 1, 2020

@MatheusRich this is really not a great place to ask for this kind of support. Please consider opening an issue or posting on the Stimulus Discourse.

For what it's worth, I checked out your pen and I'm getting Uncaught ReferenceError: Stimulus is not defined regardless of which version of the library is installed.

@panda-madness
Copy link

@panda-madness panda-madness commented May 18, 2020

I legitimately had a dream the other day that Stimulus 2.0 was released. It was glorious, DHH was spraying champagne over a raving crowd and rapping a song about something involving email and pixels.

P.S. Pls no ban.

@dhh
Copy link
Collaborator

@dhh dhh commented May 18, 2020

Haha, lol. We really aren't that far off. It's just that there's a lot of CRUNCH on the plate with the release of HEY.

@jaredcwhite
Copy link

@jaredcwhite jaredcwhite commented May 18, 2020

Wait a minute…why is CRUNCH in all caps. Could it be… a new code name?? 😆

Just teasing. Good luck on the impending launch!

@panda-madness
Copy link

@panda-madness panda-madness commented Jun 16, 2020

It looks like this API is all but confirmed, if HEY source code is of any indication.

P.S. Gratz on the launch.

@leastbad
Copy link

@leastbad leastbad commented Jun 16, 2020

@panda-madness This API was confirmed when the guy who makes the library made the PR. 🤷

If you felt there was any ambiguity on the subject, were you not convinced by repeated comments from Basecamp folks that this is coming?

I'm just very much hoping that there's still time and opportunity for default values to make it in before v2 officially launches.

@panda-madness
Copy link

@panda-madness panda-madness commented Jun 16, 2020

@leastbad there is no need to be toxic about it.

Scrolling through this thread I cannot see a solid confirmation from any of the Basecamp guys, I asked a direct question some time ago that was left unanswered. Hence my perceived ambiguity about this PR's future.

@panda-madness
Copy link

@panda-madness panda-madness commented Jun 23, 2020

@sstephenson In the current alpha build the lifecycle method invokation order seems to be initialize -> {valueChanged callbacks} -> connect. Is that intentional? Intuitively I would have thought that valueChanged callbacks would run after connect.

@msp-matt
Copy link

@msp-matt msp-matt commented Sep 18, 2020

Is there any timeline on when this might get released?

@rupasix
Copy link

@rupasix rupasix commented Oct 9, 2020

https://twitter.com/dhh/status/1291114354482524160
Maybe we're actually waiting for something different than this PR...

@sstephenson sstephenson merged commit 23de125 into master Dec 3, 2020
1 check passed
1 check passed
ci/circleci: test Your tests passed on CircleCI!
Details
@sstephenson sstephenson deleted the values branch Dec 3, 2020
@ssaunier
Copy link

@ssaunier ssaunier commented Dec 3, 2020

@nfilzi
Copy link

@nfilzi nfilzi commented Dec 3, 2020

OMG

@jankeesvw

This comment has been minimized.

Copy link

@jankeesvw jankeesvw commented on 2235047 Dec 22, 2020

In case anyone needs it, I made this regular expression to migrate:

data-target="((.+?)\.(.+?))"
data-$2-target="$3"
dlupu added a commit to kundigo/abyme that referenced this pull request Jan 22, 2021
target attributes are now scope by the controller name
see hotwired/stimulus#202
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Linked issues

Successfully merging this pull request may close these issues.

None yet