Parametrizing my first modal dialog

Here is a simple component to show a modal dialog:

def modal_access(assigns) do
  ~H"""
  <.modal id="modal-access" class="phx-modal"}>
    <p>Request access to this tool?</p>
  </.modal>
  """
end

The modal is displayed after clicking a button.

  <button
    phx-click={show_modal("modal-access")}
    phx-data-tool-tag={@tool_tag}
  >

There are many tool buttons on the page and the user can click any of them.

This all works fine.

Now, inside the modal I would like to change

  <p>Request access to this tool?</p>

to

  <p>Request access to {@tool_name}?</p>

and this is where I get lost.

I did not think it was good idea to create multiple modals - one for each tool. (If I did that I could inject a different tool_name in the same loop that creates buttons.)

I was hoping I could let the server know which tool/button was clicked, then translate tool_tag to tool_name on the server side, and put it in the assigns (and then display (or update(?) the modal window).

How should I do it?

I have tried a few things, for example adding JS.push:

  phx-click={JS.push("did-select-modal") |> show_modal("modal-access")}

However, I don’t see any data reaching the server. (Plus I am not really sure how that should work - would I have to embed a live-view in the modal window?)

I am probably misunderstanding/missing some core concepts. Any guidance would be much appreciated!

K.

I have stitched something together after a fair amount of head scratching.

  1. First, there is live_render, which allows instantiating a live view inside a functional component.

    <%= live_render(@conn, MyApp.ToolAccessModalDialogLive %>

    (Not really sure why the syntax is <%= ... %> rather than a usual component instantiation.)

  2. Now that I have a live view to send events to, I can use JS.push(...) However, I need to specify the target: JS.push(... target: ...) because the button that triggers showing of the modal dialog is outside that dialog.

  3. Using target: "modal-access" (see the snippet in my original question) does not work because "modal-access" is the id of the modal dialog, the actual live-view is nested inside.

  4. I specified the id in <%= live_render(@conn, .., id: ...) but that does not work either because the id gets overwritten with the auto-generated one.

  5. In the end I created a dummy <div id="receiver"> inside the live view and now I can JS.push events to that div. That works!

  6. The last problem was getting rid of the layout injected to the live_render-ed element. I ended up creating a new helper in MyAppWeb and then in my module I replaced

use MyAppWeb, :live_view

with

use MyAppWeb, :live_render

where live_render is like live_view but without the layout:

  def live_render do
    quote do
      use Phoenix.LiveView,
        layout: false

      unquote(html_helpers())
    end
  end

K.

Hey @Koszal!

Just to confirm, I take it you aren’t able to just make the whole page a LiveView? I feels a little heavy handed to have a LiveView just for managing modal data, but maybe not. Is there going to be more stuff than just the “Request access” message in them?

If you really need to get the data from the server, it may be better to sett up a JSON endpoint (again, assuming you must use with classic controllers).

There are ways to do this clientside as well and I’m happy to give you some ideas there if that’s an option.

A couple of other things:

  • The <%= ... %> syntax is used because live_render isn’t a component, it’s just a regular function call. A compont is a function that takes a single argument of assigns which you can see live_render is not.
  • Another way to remove the layout is to return a 3-tuple from mount/3 with the layout: false option:
    def mount(_params, _session, socket) do
      {:ok, socket, layout: false}
    end
    

just make the whole page a LiveView

Yes, I had come to the same conclusion as I was thinking this over!

The way I implemented it ended up particularly bad - the live view process starts running when the page loads even though it is possible that the user will never use the modal. There are already some elements on the page that could be live so turning the whole thing into a live view makes perfect sense.

(OTOH - since this is purely an exploration project - I would be curious to know how I could start the live view on demand - only after the ‘Show dialog’ button is clicked. That probably requires some JS trickery?)

live_render isn’t a component

Yes - I get it now. Both are functions but the signatures are different. To be a componet, the function must take assigns.

return a 3-tuple from mount/3 with the layout: false option:

Ah - indeed - mount! I knew I saw layout: false somewhere in the documentation…

Thank you very much for the pointers!

K.