DEV Community

Cover image for The best one-line Stimulus power move
leastbad
leastbad

Posted on • Updated on

The best one-line Stimulus power move

Stimulus is a tiny and absurdly productive JavaScript framework for developers who are looking for just the right amount of structure (lifecycle events and standard HTML) without attempting to re-invent how the web works (no template rendering or routing). It is criminally underappreciated in the JavaScript community.

When using Stimulus, you write controllers in JavaScript and attach instances of those controllers to DOM elements by setting data-controller="controller-name".

Unfortunately, there's no easy way to access methods in a controller from another controller, external scripts, jQuery plugins or the console... or is there?


Before I do the big reveal, there is technically a way to access another controller instance from inside of a controller. It's an undocumented method so there's no guarantee that it won't disapper someday, but the real clue that this isn't intended to be used is the laughably long name: this.application.getControllerForElementAndIdentifier(element, controller).

Controllers have access to the global Stimulus application scope, which has getControllerForElementAndIdentifier as a member function. If you have a reference to the element with the controller attached and the name of the controller, you can get a reference to any controller on your page. Still, this doesn't offer any solutions to developers working outside of a Stimulus controller.


Here's what we should all do instead.

In your controller's connect() method, add this line:

this.element[this.identifier] = this
Enter fullscreen mode Exit fullscreen mode

Boom! This hangs a reference to the Stimulus controller instance off the DOM element that has the same name as the controller itself. Now, if you can get a reference to the element, you can access element.controllerName anywhere you need it.

What's cool about this trick is that since Stimulus calls connect() every time an instance is created, you can be confident that your elements will always have a direct reference to their parent, even if they are attached to elements that are dynamically inserted by something like morphdom.

this.identifier can be replaced with any camelCase string as you desire.


I'll provide a basic example.

// test_controller.js
import { Controller } from 'stimulus'

export default class extends Controller {
  connect () {
    this.element[this.identifier] = this
  }

  name () {
    this.element.innerHTML = `I am ${this.element.dataset.name}.`
  }
}

// index.html
<div id="person" data-controller="test" data-name="Steve"></div>

// run this in your console
document.querySelector('#person').test.name()
Enter fullscreen mode Exit fullscreen mode

If everything goes according to plan, the div should now say: I am Steve.


If you want to automatically camelCase the name of your Stimulus controller, "one-line" becomes dubious, but it can still be done:

    this.element[
      (str => {
        return str
          .split('--')
          .slice(-1)[0]
          .split(/[-_]/)
          .map(w => w.replace(/./, m => m.toUpperCase()))
          .join('')
          .replace(/^\w/, c => c.toLowerCase())
      })(this.identifier)
    ] = this
Enter fullscreen mode Exit fullscreen mode

I broke the statement up into multiple lines to help illustrate the acrobatics required to pull this off. It can still be expressed on a single line if you choose. However, the devil hides in clever code.


The only caveat I can think of is that you should exercise common sense and not expose any controller instances that you wouldn't want people to access. Even though there's no visible proof that an element has a variable on it in the inspector, you shouldn't assume that it's locked down.

If you're working in FinTech, you might need to skip this technique. Everyone else should be doing this by default.

Top comments (7)

Collapse
 
ordinz profile image
Ordin

Yo this is great! I was able to extend Modal from "tailwindcss-stimulus-components" and open it from the outside with this little power move. Thanks!

gist.github.com/lukelove/16994990e...

Collapse
 
schmijos profile image
Josua Schmid

If you're working in FinTech, you might need to skip this technique. Everyone else should be doing this by default.

I don't understand this statement. Stimulus is JS. Why should the suggested controller exposure make any difference in FinTech compared to other areas?

Collapse
 
leastbad profile image
leastbad

Fair question, but you're reading into it too deeply.

Exposing the internal state of a controller theoretically exposes logic that might offer an attacker insight and therefore subtly increase the attack surface.

Yes, someone could go to the trouble of accessing the same data structures indirectly, but this makes easy and direct.

It was ultimately a wink, not an actual warning.

Collapse
 
rebuilt profile image
rebuilt

Thanks. I really needed this.

Collapse
 
tosbourn profile image
Toby Osbourn

This just saved me a lot of time, thank you so much!

Collapse
 
leastbad profile image
leastbad

If you're doing cool stuff with Stimulus, you should come visit the #stimulus channel on the StimulusReflex Discord (even if you're not a Rails developer).

discord.gg/XveN625

Collapse
 
drews256 profile image
Andrew Stuntz

Cool!