Making external call to stimulus controller action

I have a stimulus controller that handles the behavior of a slide out element on a given page. In many cases, it contains a form, and after the form is successfully submitted, I usually want to close the slide out.

Additionally, the the form submission usually results in the update of a <turbo-frame> somewhere else on the page.

My question is, what is the best way to listen for the update to the turbo frame, and then trigger my stimulus controller to close the slide out?

My first thought was that it would be nice to do something like this:

<%= turbo_frame_tag(
  program,
  data:  { action: 'turbo:frame-render->slide-out#close' },
  class: 'program',
) %>

But to my understanding, this won’t work because it’s not wrapped within the <div data-controller='slide-out'> element.

As an aside, it’s not totally clear to me whether or not turbo:frame-render is even a valid type of event for a stimulus action attribute. And it turns out that this event doesn’t get triggered from a turbo stream. The only event I could discover was turbo:before-stream-render.

At any rate, here is the best solution I’ve been able to come up with so far:

  1. Make the controller easily accessible from the element with this:
connect() {
  this.element[this.identifier.camelCase()] = this
}
  1. Listen globally for the turbo:before-stream-render event, then call the close() method if the right elements exist on the page.
$(document).on 'turbo:before-stream-render', (e) ->
  if $('.program').length
    $('[data-controller="slide-out"]')[0].slideOut.close()

I have a feeling that this deviates from the intended design pattern of the Hotwire framework. I could probably encapsulate the extra javascript in a more generic slide_out_stream_listener_controller or something to better conform, but I’m wondering if there’s an existing “Hotwire” way to do enable this flow.

One option is you could look for the global event within the slide-out controller:

<div controller="slide-out" 
     data-action="turbo:frame-render@window->slide-out#close">

This is a simple solution if you only have one turbo:frame on the page. If this doesn’t work for your scenario, there are other options.

Almost a year later, I’m wondering if anyone has figured out an elegant way of doing this.

I have a similar use case. In my case, the HTML connected to my SlideoutController is hidden by default (e.g., display: none).

Within the SlideoutController, I have an action named #show which, as you probably already guessed, shows the HTML by removing the CSS display attribute.

I want to be able to call slideout#show from outside of the SlideoutController HTML. Is that even possible?

If you really need it the docs suggest using events for cross-controller communication. dispatch an event from a controller and listen for that event using a data-action on your slideoutController.

Stimulus has a built in dispatch method to make this easier

https://stimulus.hotwired.dev/reference/controllers#cross-controller-coordination-with-events

1 Like

Thanks! I actually found that doc this morning. RTFM :joy: