Trigger modal show in handle_info in a LiveView (using CoreComponents)

Problem
My LiveView subscribes to a PubSub topic to get events for a game running in a GenServer. One event I am currently working on implementing is a “game round ended” event where it will show the results of the round in the game to players using a modal. Basically, I want to show the modal on a handle_info call.

What I’ve Tried
First, I tried using an HTML dialog and updating a flag in assigns that controls whether the open attribute is added to the dialog. This technically works as the dialog opens and closes, but MDN web docs clearly state that the dialog will be non-modal if opened this way, making it so there is no backdrop and focus is not placed on the dialog.

Second, I tried sending an event to the JavaScript using push_event on the socket that would call showModal() on an element with the given ID, and I simply provide the ID in the event. This works too, however, any update to the DOM in a render call will close the modal. I suspect this is because the render doesn’t have the open attribute on it, so it gets overwritten in the DOM patch (although I am kind of surprised by this behavior, if that’s what’s happening, as I though the DOM patches would only update what is changing. An explanation of this would be a bonus for me to getting an answer to my question!)

Perhaps there is a way to get it working this way, but I am assuming that since the modal given in CoreComponents doesn’t do it that way, it’s probably not worth the effort of trying to get the client and server synced up on whether a modal is showing.

Lastly, I tried using the CoreComponents that are generated in Phoenix. This doesn’t use the dialog element, but that’s fine, I just want to get it working at this point. I see there is a show_modal function but it returns a JS struct, which from my research in the Phoenix docs and code examples, this can only be pushed to the client if it’s returned to a “phx-” event on a component. The show assigns also doesn’t allow me to do this because it only affects whether the modal is shown on phx-mounted, and does not change whether the modal is shown on updates. Perhaps I can trigger a mount? This is where I have a knowledge gap and I need help to understand some things.

Question
Is there a way to still use this the show_modal function and somehow push the JS struct to the socket in the handle_info function? I realize I could do another push_event to the client and port the show_modal code to regular JS, but I’m trying to find the best way to do this without duplicating code, avoid JS as much as possible, and also to learn best practices!.

Thanks! :smiley:

I figured out a way to execute JS structs from the server. I got it working with the following code:

app.js

window.addEventListener("phx:exec-js", ({detail}) => {
    liveSocket.execJS(document.getElementById(detail.to), detail.encodedJS)
})

my_liveview.ex

def handle_info(..., socket) do
   ...
   {:noreply, socket |> push_js("my-modal", CoreComponents.show_modal("my-modal"))
end

def push_js(socket, to, js) do
    event_details = %{
      to: to,
      encodedJS: Phoenix.json_library().encode!(js.ops)
    }

    socket
    |> Phoenix.LiveView.push_event("exec-js", event_details);
end

Very simple, but is this a good idea? I am wondering if there is an easier or better way, as I am surprised that this functionality isn’t included with Phoenix (as far as I can tell from reading the docs). Thoughts?