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 sstephenson added this to the 1.2 milestone Nov 2, 2018
@PFWhite

This comment has been minimized.

@jaredcwhite
Copy link

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

@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

@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

@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

Is there any timeline on when this might get released?

@rupasix
Copy link

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
@sstephenson sstephenson deleted the values branch December 3, 2020 21:31
@ssaunier
Copy link

ssaunier commented Dec 3, 2020

@nfilzi
Copy link

nfilzi commented Dec 3, 2020

OMG

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging this pull request may close these issues.

None yet