How to create global modal window forms?

Hey guys I im coming from rails world and kinda like LiveView. I’ve created a modal window which opens when I redirect to specific LiveView action my code looks like

<%= if @live_action in [:new, :edit] do %>
  <.modal title={@page_title}>
    <.live_component
      module={MyAppWeb.EntityLive.FormComponent}
      id={@entity_id || :new}
      action={@live_action}
      project={@project}
      current_user={@current_user}
      return_to={current_index_path(nil)}
    />
  </.modal>
<% end %>

However I need a way to have this modal be opened not from specific page(say /dashboard) but from any page(simply made this modal as part of layout). The modal contents has to be dynamic as I might have few different forms rendered inside the modal. As result, once form submitted I need to hide it and possibly update some pieces of page.
In rails I used *turbo_stream approach where I just had #replace actions(which didnt fire if there wasnt specific element present on page).
What are the ways achieving this with LiveView?

I’m not sure I understand the question, sorry if I take a tangent. Did you look at the code generated by mix phx.gen.live? It does have a modal and functionality to show/hide it.

Well, if what you are looking for are ways to reuse the modal component in multiple LiveViews, then you have basically 3 options:

  • Function component (stateless)
  • LiveComponent (stateful)
  • Nested LiveView (also stateful)

I suggest you start with a function component.
The .modal you’re using is itself a function component automatically generated and lives in core_components.ex.

Links to relevant docs:

Hope that helps! Cheers!

1 Like

You could probably use an on_mount with an attach_hook then hook either handle_info or handle_async. Then to use it all you would need to do is to call send or a start_async with special arguments that you’re unlikely to ever use elsewhere.

You would need to pass through things that you don’t match in your modal module’s callbacks just like typical attach_hook usage.

But if as part of the arguments that you pass, you send everything you would need to call live_component from the modal as well as maybe an anonymous function that contains a send_update function from the caller, you could probably do what you are trying to do.

From an accessibility perspective you might not want to do this though as you might have a hard time setting all of the ARIA things to make it so that the modal is focused by something like a screen reader properly.

I agree with the other commenter though. You might be better off just using the modal from core_components or modifying one to suit your needs. You don’t get a whole lot of benefit from having a single modal component on your HTML. There’s other ways of composability and reusability that don’t rely on forcing that composability to result in a single modal component being rendered.

1 Like

For example, you could just take this modal component with the on_mount and instead of handle_info you use handle_params you could use this component wherever you want it, and it just becomes a reusable behavior that you use in any live view you want and you don’t have to worry about the weird callback nature of using send or start_async.

I do something similar with photo uploads, for example where you might be uploading an image like an avatar or a cover, but where the behaviors are slightly different based on the caller such as resizing, or other things, but I don’t want to have to re-implement all of this stuff around displaying and deciding what to do with photo uploads in all of the different places

1 Like

Thanks for replies. I spent whole night trying to find proper way and easiest and most straightforward way I came up with - have LiveComponent added to my layout, which contains UI part, but also modal logic parts for the form. This way I can control when and how modal opens(yeah Im using core components modal, I didnt want to implement that myself too). Since my form inside modal involves some updates, I added a hook to have current_path inside socket assigns, so after form submit I do basically redirect to same page. That also enabled me to have flash notices to work without any hacks.
For now seems to be working as I expected

That sounds reasonable :+1:

The explicit passing of the URL into the component is also a good idea, I feel it pops up relatively often here in the forum.

1 Like

Yeah, exactly. I didnt want to implement complex callback system so redirecting to same page is the easiest and most straightforward way. And its only a matter of 1 hook within on_mount fn