sajad torkamani

What is Stimulus?

Stimulus is a lightweight JavaScript framework for enhancing static or server-rendered HTML – the “HTML you already have” – by connecting your HTML elements with JS objects using simple data- attributes.

Just as a class attribute connects HTML to CSS, Stimulus’s data- attributes connects HTML to JavaScript.

It’s not intended to power a single-page application in the way that frameworks like React, Angular or Vue are. Stimulus is used mostly in conjuction with server-rendered HTML templates.

Stimulus vs mainstream JS frameworks

Manipulates existing HTML

Stimulus focuses on manipulating existing HTML that comes from the server. This contrasts with most frameworks like React or Angular that typically fetch JSON and use that to dynamically create the DOM.

State is managed in HTML instead of JS

Stimulus stores state in the HTML instead of JS objects.

The main concepts

Stimulus revolves around three main concepts:

  • Controllers – the JavaScript objects that are connected to HTML and used to manipulate it.
  • Actions – connect DOM events to controller methods using data-action attributes.
  • Targets – locates HTML elements within a controller.

Here’s an example HTML that uses Stimulus:

<div data-controller="clipboard">
  PIN: <input data-clipboard-target="source" type="text" value="1234" readonly>
  <button data-action="clipboard#copy">Copy to Clipboard</button>
</div>

Lifecycle methods

MethodDescription
initialize()Invoked once when the controller is first instantiated.
connect()Invoked anytime the controller is connected to the DOM.
disconnected()Invoked anytime the controller is disconnected from the DOM.

Recipes

Connect HTML to controller

Create controller:

// src/controllers/hello_controller.js
import { Controller } from "@hotwired/stimulus"

// Connects to data-controller="hello-converter"
export default class extends Controller {
  connect() {
    console.log('Hello Stimulus', this.element)
  }
}

Link HTML to controller:

<div data-controller="hello">
  <input type="text">
  <button>Greet</button>
</div>

Invoke action on DOM event.

<div data-controller="hello">
  <input type="text">
  <button data-action="click->hello#greet">Greet</button>
</div>

Clicking on the button will invoke the method greet from the controller in hello_controller.js.

Target DOM elements within a controller

Add the target annotation to HTML element:

<div data-controller="hello">
  <input data-hello-target="name" type="text">
  <button data-action="click->hello#greet">Greet</button>
</div>

Target the HTML element:

// src/controllers/hello_controller.js
import { Controller } from "@hotwired/stimulus"

export default class extends Controller {
  static targets = [ "name" ]

  greet() {
    const element = this.nameTarget
    const name = element.value
    console.log(`Hello, ${name}!`)
  }
}

Read initial state from the DOM

Add data-<controller>-value attribute:

<div data-controller="slideshow" data-slideshow-index-value="1">

Add a static values definition to the controller:

export default class extends Controller {
  static values = { index: Number }

  initialize() {
    console.log(this.indexValue)
    console.log(typeof this.indexValue)
  }

  // …
}

this.indexValue will return "1".

Invoke callback when value changes

Given this HTML:

<div data-controller="slideshow" data-slideshow-index-value="1">
  <button data-action="slideshow#previous"> ← </button>
  <button data-action="slideshow#next"> → </button>

  <div data-slideshow-target="slide">🐵</div>
  <div data-slideshow-target="slide">🙈</div>
  <div data-slideshow-target="slide">🙉</div>
  <div data-slideshow-target="slide">🙊</div>
</div>

and this controller:

import { Controller } from "@hotwired/stimulus"

export default class extends Controller {
  static targets = [ "slide" ]
  static values = { index: Number }

  next() {
    this.indexValue++
  }

  previous() {
    this.indexValue--
  }

  indexValueChanged() {
    this.showCurrentSlide()
  }

  showCurrentSlide() {
    this.slideTargets.forEach((element, index) => {
      element.hidden = index != this.indexValue
    })
  }
}

Everytime the user clicks the next or previous buttons, the indexValue is changed and the indexValueChanged callback is invoked.

Sources

Tagged: JavaScript