Skip to content

Instantly share code, notes, and snippets.

@BLaurenB
Created June 29, 2021 16:39
Show Gist options
  • Save BLaurenB/ce5eed62d89234fe9c70b8b7e0f73d2f to your computer and use it in GitHub Desktop.
Save BLaurenB/ce5eed62d89234fe9c70b8b7e0f73d2f to your computer and use it in GitHub Desktop.

StimulusJS flow

If you boil it all down, here's the basic flow through the app (with some gotcha's)

When there is a request for a page in a Rails-only app, the controller handles and routes the request, and renders a View. When the DOM is being written, some special Stimulus tags cue the instantiation of an associated Stimulus controller. The DOM also listens for user actions that are attached to some page elements via some other special Stimulus tags. When an event occurs, the associated function is called and runs its code to update the DOM without reloading the page.

So... what are the special tags? Here's a longer breakdown:

  1. A section of html is wrapped in a div with a data-controller tag. (The section that is wrapped should contain all the page elements that you plan to listen to or act upon using that controller). The value corresponds to the name of a StimulusJS controller:
  • <div data-controller="dropdown">
  • file app/javascripts/controllers/dropdown_controller.js with a controller class named DropdownController
  1. Any page element that will be 'listened to' or 'affected by' the controller needs to be 'registered' (my wording) with the controller. This is done by adding a data tag & target attribute to each. Then a corresponding string is added to a special targets array in the Stimulus controller: HTML data tags & attributes

    • for non-Rails elements: data-controllername-target="targetName"

    • for Rails forms/elements: data: { 'controllername-target': 'targetName' }

    • Note that there are a few ways that the data target tag can be set up. But when you use data-target='controllername.targetName', you get a notice in the console that this is a deprecated set up and that Stimulus prefers that you specify the controller in the data tag instead of the dot notation in the value:

      Please replace data-target="dropdown.sessionsIds" with data-dropdown-target="sessionsIds". The data-target attribute is deprecated and will be removed in a future version of Stimulus.

    Stimulus controller targets array

    • var targets = ["targetName'], e.g. var targets = ["sessionsIds", "originIds", 'commIds']

    Once the Target elements are registered, their properties and attributes are available to the controller in various ways (after you have created some controller functions and 'registered' them to some page elements). You can access and manipulate them using Javascript API methods:

    • this.targetNameTarget will let you do things like this.sessionsIdsTarget.selectedOptions or this.sessionsIdsTarget.selectedOptions[0].value or this.commIdsTarget.appendChild(someChildVar)

    The data-targets do not stimulate any events or functions. They aren't like 'listeners', they are more like finding an element using $('#some-id) in a console to access the attrs and props.

  2. Elements that have a data-target may or may not need an associated listener and action. For elements that do need to stimlulate an event or action, add a data-action tag. There are specific StimulusJS 'listeners' (or "DOM events to listen for") for different elements (https://stimulus.hotwire.dev/reference/actions#event-shorthand).

    A data-action tag can look like this:

    • "listener->controller#functionInMyController", e.g. "click->dropdown#updateCommIdList"

    ... and the corresponding function would just look like this:

         updateCommIdList() {
           // do stuff
         }
    

    At this point you can write javascript and use the javascript API for working with DOM elements in order to make things happen (like updating a dropdown, showing/hiding elements, adding new classes to change the css)

    It's important to note a few gotcha's at this point.

    • In the controller, this is the object of the controller itself. Even if you are working with some inner html, or have accessed an element in some way, this will always be the controller object. That's why you call this.targetNameTarget when you want to access the page element associated with the target name.

    • If your function isn't simple enough to span a few lines and you want to break out some code into other functions, you'll need to think of them like Ruby Class Methods. The function that you registered to a DOM element will be called when the listening-event is stimulated. But that function can only call other functions by calling this.otherFunction.

      Ex:

        import { Controller } from "stimulus"
      
        export default class FancyThingsController extends Controller {
      
        static targets = ["thing1", "thing2"]
      
        makeItFancy() {
          var foo = true
          var bar = true
          this.otherFunction(foo, bar)
        }
      
        otherFunction(foo, bar) {
          foo = !bar
          return foo
        }
      
  3. Once you have a function and associated data-action tag in your page, you have all the fundamental pieces in place.

Summary

When the DOM is being written, the data-controller tags cue the instantiation of the associated Stimulus controller, and the DOM listens for the user actions that are attached to some page elements via data-target tags and data-action tags. When a data-action's event occurs, the data-action's function is called and runs its code to update the DOM without reloading the page. The function can access and manipulate attrs and props of any page elements that have a data-target tag registered to that controller.

Thoughts:

Could all stimulus controllers just be loaded at the top of the body by putting all the data-controller tags in one place?

  • I suppose yes, but if you have a lot, you'd be instantiating a ton of unused javascript classes for each new request and that might slow page loads. Probably better to just place the data controller tags at the smallest scope possible in a view or partial.
  • So maybe you could have a single div for all controllers at the top of the parent page being loaded... so if your data-targets and data-actions are in a partial on an Index View, and you know all the partials will be loaded and all the Stimulus Controllers will be instantiated, you could throw the data-controller tags all in one div at the top of the Index View (and be sure to have a closing div in the view too)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment