Stimulus JS - Accessing an action in another controller
Tags: rails, stimulus
Published: 2023-06-19 15:44:13

If you've used Stimulus before, you should know that controllers are scoped. This can be useful, but you might come across a case where we want to trigger an action in a controller that is out of scope.

We can do this with a custom event. 

I'll start off with a tl;dr version and will follow with an explanation 

## TL;DR
```javascript
// The javascript
const trigger = new CustomEvent("event-name");
window.dispatchEvent(trigger);
```

```HTML
<!-- The HTML attribute -->
data-action="event-name@window->controller#action"
```

## Explanation
Here's a contrived example, to give you an idea of how to get this working. Here are some circles we want to turn red when we click a button.

```hmtl
<div data-controller="controller1">
  <button data-action="click->contoller1#turnCircleRed">
    Button 1
  </button>
  <div 
    class="round-square" 
    data-controller1-target="circle">
  </div>	
</div>

<div data-controller="controller2">
  <button data-action="click->contoller2#turnCircleRed">
    Button 2
  </button>
  <div 
    class="round-square" 
    data-controller2-target="circle">
  </div>
</div>
```

Now let's create the controllers
```javascript
// app/javascript/controllers/controller1
import { Controller } from "@hotwired/stimulus"

// Connects to data-controller="controller1"
export default class extends Controller {
  static targets = [ "circle" ]
	
  turnCircleRed() {		 
    this.circleTarget.style.backgroundColor = "red";
  }
}
```

and here's the second one.
```javascript
// app/javascript/controllers/controller2
import { Controller } from "@hotwired/stimulus"

// Connects to data-controller="controller2"
export default class extends Controller {
  static targets = [ "circle" ]
	
  turnCircleRed() {		 
    this.circleTarget.style.backgroundColor = "red";
  }
}
```

Now when we click on either of the buttons, the background of the circle next to the button will turn red. Even though both targets are called "circle", because each stimulus controller only has access to elements inside of the div (or element) where it was declared, it cannot see or interact with the other circle.

Now, what we want to happen is that when we click on the second button, we want BOTH circles to turn red.

To do this, we add a custom event to controller2's turnRed action.

```javascript
// app/javascript/controllers/controller2
import { Controller } from "@hotwired/stimulus"

// Connects to data-controller="controller2"
export default class extends Controller {
  static targets = [ "circle" ]
	
  turnRed() {		 
    this.circleTarget.style.backgroundColor = "red";

    // Custom event we will use as our trigger
    const trigger = new CustomEvent("trigger-red");
    window.dispatchEvent(trigger);
  }
}
```

and to our view, we want to add an action that will listen for this event.

```hmtl
<div data-controller="controller1">
  <button data-action="click->contoller1#turnRed">
    Button 1
  </button>
  <div id="circle1" class="round-square" 
    data-controller1-target="circle" 
    data-action="trigger-red@window->contoller1#turnRed"> 
  </div>
</div>

<div data-controller="controller2">
  <button 
    id=circle2" 
    class="round-square" 
    data-action="click->contoller2#turnRed"
  >
    Button 2
  </button>
  <div class="round-square" data-controller1-target="circle">
  </div>
</div>
```

Now when we click on Button 2 the following will happen
1. controller2#turnRed action will run and \#circle2 will turn red.
2. Then an event called "trigger-red" will be attached to the "window" element
3. "trigger-red@window" will see this, and run controller1#turnRed

You can take this further and have an action that only adds the custom event with listeners that will trigger turnRed in both controllers. This would allow you to add another button to turn the circles blue, using another event you could call "trigger-blue" that would trigger "turnBlue" actions, fairly easily.

## A mild word of warning
Now we know how to do this, I think it's a good idea to use this sparingly as it does add some complexity to our code and makes us use a little more mental gymnastics to read and update our code in the future.

Let me know if this was helpful, if you have any questions, or spotted some mistakes/errors or got something completely wrong.  

Thanks for reading, and happy coding!

<hr>

### References
https://stimulus.hotwired.dev/handbook/introduction