Right ways to build modal/dialog

Hello,

I wanted to know which is the correct method among below to build modals with Hotwire.

A (data-action : turbo:frame-load->dialog#showModal) or B (lazy-loaded frame) ?

A. With data-action : turbo:frame-load->dialog#showModal

<html>
  <head>
    <script type="module">
      import { Application, Controller } from "https://unpkg.com/@hotwired/stimulus/dist/stimulus.js"
      window.Stimulus = Application.start()

      Stimulus.register("dialog", class extends Controller {
        showModal() {
          if (this.element.open) return
          else this.element.showModal()
        }
      })
    </script>
  </head>

  <body>
    <main>
      <form action="/articles/new" data-turbo-frame="dialog_frame">
        <button aria-expanded="false" aria-controls="dialog">New article</button>
      </form>
    </main>

    <dialog id="dialog" data-controller="dialog" data-action="turbo:frame-load->dialog#showModal">
      <turbo-frame id="dialog_frame"></turbo-frame>
    </dialog>
  </body>
</html>

B. With lazy-loaded frame

<html>
  <head>
    <script>
      const newbutton = document.getElementById('button_new');
      const dialog = document.getElementById('dialog');

      newbutton.addEventListener('click', function onOpen() {
        dialog.showModal();
      });
    </script>
  </head>

  <body>
    <main>
        <button id="button_new">New article</button>
    </main>

    <dialog id="dialog">
      <turbo-frame id="dialog_frame" src="/articles/new" loading="lazy"></turbo-frame>
    </dialog>
  </body>
</html>

For case A, the modal will not be displayed immediately when the button is clicked but only when “/articles/new” is loaded.

For case B, the modal will be displayed immediately on click but the content of the modal will be displayed when “/articles/new” is loaded.

Thank you in advance for your answers :slight_smile:

1 Like

@sebastienjean76: Where did you find documentation around using turbo:frame-load within data-action?

Here is an example:

That’s pretty interesting. We’ve been doing something like this to achieve the same effect with a modal:

const addListener = (eventName, event) => {
  return document.documentElement.addEventListener(eventName, event)
}

/*
 * Creates a listener for any frame-specific event.
 */
export const createFrameListener = (eventName, frameId, action) => {
  return addListener(eventName, (ev) => {
    if (ev.target.id === frameId) {
      return action()
    }
  })
}

It’s been working great, but I think we’ll swap it out for this.

EDIT: I think the strategy we’re using, however, has the advantage of only firing when a frame of a given ID is rendered.