Trying out Stimulus
DDH shared a tweet recently talking about a new version for a tool called Stimulus.
As a teaser for the multi-punch combo that is NEW MAGIC, β¦@sstephensonβ© and β¦@javanβ© have finished and released Stimulus 2.0. New APIs for dealing with CSS classes and values. Such power in a 9kb gzipped punch! π₯π https://t.co/FDjcnYCJrv
β DHH (@dhh) December 4, 2020
https://stimulusjs.org/handbook/hello-stimulus
I was looking for a leaner stack to build an interactive guide on tickingfocus.com.
You can access it here
Creating Interactive Page
I got this static page, markdown compiled and templated to a nice static website, thanks for Hugo. Now I wanted to make one of these pages interactive, add buttons, forms, etc.
Using a full-featured react or nextjs app made no sense, I’d have to rebuild the whole website, make sure there where no outdated links, the i18n was working, etc. I like Hugo and I’m productive with it now. Why change and make my stack complex?
I could build the interactive part in a fancy tool like create react app and include it as a piece of javascript. But that’s something I still need to learn to do. I haven’t looked into packaging typescript+react app to embed on a webpage. And I can tell this might not be fun either: how to share the CSS between projects? re-release two projects on every layout change? Nah.
I could go old school and use some javascript to make my tutorial interactive.
That’s where Stimulus looked useful. It’s the missing link between HTML and SPAs.
Why not go “raw javascript” completely? Still an open question. I think having a framework makes my life easy, and stops me from inventing mine.
Stimulus
Basecamp built this. successful company, with good values. I drink the kool aid, read all of their books.
Installing the library is rather involved: https://stimulusjs.org/handbook/installing
Not sure this is useful for now.
You can include the script and load your controller manually. That’s good enough.
That’s what I did in my Hugo website, I just write the whole interactivity page INSIDE the markdown file. Five years ago I might have judged myself for being a script-kiddy, but it looks like a nice balance of value over setup time now.
I think you could summarize this tool with “we love HTML, js, and CSS, and we designed the perfect way to weave them”.
This is VERY different from my “regular” stack that I could summarize with “we hate HTML, js, and CSS, so we designed the perfect way to rewrite them all.”
Quickstart
Controller connects HTML.
data-controller="controllerName"
will create an instance of the controller on a given piece of HTML.
data-controllerName-target="fieldName"
will connect the controller with this HTML element.
- use
static targets = ["fieldName"]
in the controller class - use
this.{fieldName}Target
to access the element.
data-action="controllerName#copy
will connect the click action (implicit) with the controller method. data-action="action->controllerName#copy
is the full definition.
Default events: https://stimulusjs.org/handbook/building-something-real#common-events-have-a-shorthand-action-notation
// src/controllers/hello_controller.js
import { Controller } from "stimulus"
export default class extends Controller {
connect() { // triggered when the controller is bound to the document.
this.element // element connected
}
}
data-controllerName-fieldName-value="1"
will connect the html value with the controller’s field. You have to type it: static values = {fieldName: Number}
then you can set/get using this.{fieldName}Value
.
You can define observers on values: fieldNameValueChanged()
You can use multiple data-action
for a single element, use a space-separated list.
global actions: resize@window
.
You can add options: open:once
You can define multiple controllers with space-separated lists too: <div data-controller="clipboard list-item"></div>
Resilient Design
That sounds amazing. In practice, they hide the element until we add “feature-xx-supported” classes. Why not add these to the body directly?
State
Big topic, here everything is in HTML. That’s weird when you come from React where state libraries are an unending work in progress. Their example is a slideshow, similar to what I want to build, perfect.
The example is stateful, you have to call an old school “showCurrentSlide” to update the rendering after state change. That looks antic.
They use the DOM directly
I like that. Having worked in so high levels framework, touching low level JS and DOM operations is the sign that it’s really a sane middle ground between approaches.
After trying out with my interactive guide, I used patterns like this one:
.first-session--success, .first-session--fail {
display: none;
}
[data-guide-first-session-value="C"] .first-session--fail {
display: inline-block;
}
[data-guide-first-session-value="X"] .first-session--success {
display: inline-block;
}
That data-guide-first-session-value="X"
is just a regular HTML field, I could load this from pretty much anywhere. Here, it’s a default coming from the static page that gets changed by Stimulus as we go through the guide.
We can use regular css selectors like these (ugly but efficient) to create interactivity quickly.
Lifecycle
Lifecycle: https://stimulusjs.org/handbook/managing-state#lifecycle-callbacks-explained
That part I don’t get the difference between controller / instance / class. Can you have the same controller twice in the page? Is the initialization code called twice? What about connect?
This might get the job done, but itβs clunky, requires us to make a decision about what to name the attribute, and doesnβt help us if we want to access the index again or increment it and persist the result in the DOM.
This needs to be discussed. So many questions look trivial to me, I’m missing something.
Reference
https://stimulusjs.org/reference/controllers
Registering controllers by hand: (no build step)
https://stimulusjs.org/reference/controllers#registering-controllers-manually
https://stimulusjs.org/reference/lifecycle-callbacks#reconnection
That one is magic, how do you reconnect a controller? How do you tell that it’s a controller to reconnect or a new controller to connect? There is some internal state? Is it just like a memory pool?
Tricks
<div data-controller="search">
<div data-controller="search">
<div data-search-target="results"></div>
</div>
</div>
what happens with the target?
https://stimulusjs.org/reference/targets#properties
beautiful.
CSS
https://stimulusjs.org/reference/css-classes#properties
That one I don’t get why not make it easier. There is a this.hasNAMEClass
, why not have a setter to toggle the class? The example forces you to go this.element.classList.add(this.NAMEClass)
.
OK, it makes sense if you want to apply the class to a child element. Toggling there would be weird. this.childElement.NAMEClass = true
, lots of implication there.
Laurent Senta
I wrote software for large distributed systems, web applications, and even robots.
These days I focus on making developers, creators, and humans more productive through IPDX.co.