Hotwire Discussion

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.