Problem using Liveview, JS{} class toggle embedded in a modal

I’m sure this is an easy problem to fix, I just don’t know how to do it :slight_smile:

The code below lets a user click on a list item (called a testbed.note) and as a result a modal pops up whereby they can either preview or edit the content based on a edit/preview button

Problem
The user should NEVER be able to open the modal and have it default to the edit state. In other words, each time the modal launches it should always be in preview mode. As it stands it easily gets stuck in the edit state.

I tried placing %JS{} code in handle_event(“send”) to toggle the class when the user clicks the submit button. It didn’t work.
I am not sure how to run code when the Modal closes without putting my hands on the core component

I included a video.



    defmodule AppWeb.PageLive do 
    
      use AppWeb, :live_view  
      alias App.Testbeds 
      def mount(_params, _session, socket)do   
         {:ok, assign(socket, testbeds: Testbeds.list_testbeds())}   
      end
    
        def button_event("invoke_editing") do
        %JS{}
        |> JS.remove_class("active", to: "#editing_button")
        |> JS.add_class("hidden", to: "#editing_button")
        |> JS.remove_class("hidden", to: "#preview_button")
        |> JS.add_class("active", to: "#preview_button")
        |> JS.remove_class("active", to: "#preview")
        |> JS.add_class("hidden", to: "#preview")
        |> JS.remove_class("hidden", to: "#editing")
        |> JS.add_class("active", to: "#editing")
      end
    
      def button_event("invoke_preview") do
        %JS{}
        |> JS.remove_class("active", to: "#preview_button")
        |> JS.add_class("hidden", to: "#preview_button")
        |> JS.remove_class("hidden", to: "#editing_button")
        |> JS.add_class("active", to: "#editing_button")
        |> JS.remove_class("active", to: "#editing")
        |> JS.add_class("hidden", to: "#editing")
        |> JS.remove_class("hidden", to: "#preview")
        |> JS.add_class("active", to: "#preview")
    
      end
    
      def handle_event("send", params, socket) do
    
        {:noreply, socket}
      end
    
    
    
    
    
      def render(assigns) do  
         ~H"""
    
    
    
        <%= for testbed <- @testbeds do %>
    		<.modal id={"notes-modal-#{testbed.id}"}>
    		    <.button phx-click={button_event("invoke_editing")} id="editing_button" class="hidden">
    		      Preview
    		    </.button>
    		    <.button phx-click={button_event("invoke_preview")} id="preview_button">
    		      Edit Me
    		    </.button>
    		    <div id="editing"> PREVIEWING THE WORK</div>
    		    <form phx-submit = "send"   id="preview" class="hidden">
    		      <textarea value = {testbed.note}></textarea>
    		      <.button type="submit" phx-click={hide_modal("notes-modal-#{testbed.id}")}>SAVE WORK</.button>
    		    </form>
    		</.modal>
    
        <%= if testbed.note do %>
          <div phx-click={show_modal("notes-modal-#{testbed.id}")}>READ ME  ...<%= testbed.note %></div>
        <% end %>
    	   
        <% end %>
           
          """
      end
    
    end

I think I have a similar problem…

On mount, I render my page which has a hidden modal where the content is just a skeleton. Upon clicking the “show modal” button, not only do I want to show the modal, but also kick off an async Task that gets the content that will replace the skeleton.

This not only shows the code, but sends an event to server:

<.button phx-click={show_modal("my-modal") |> JS.push("show-my-modal")}>
  ...skeleton...
</.button>

I have yet to discover if simply re-rendering the modal while it is already shown with simply replace the contents or it will reset the modal back to hidden.

If I’m understanding, I think you want your server side handler to reset the contents of your modal to the edit state whenever the show modal button is clicked.

I have yet to discover if simply re-rendering the modal while it is already shown with simply replace the contents or it will reset the modal back to hidden.

It does update the content in place without closing the modal. But hiding/showing the modal again shows the old content instead of skeleton again.

Here’s what I did to reset the modal content everytime…

def render(assigns) do
  ~H"""
  <.modal id="my-modal"  on_cancel={JS.push("hide-my-modal")}>
    <%= if @modal_content %>
      <%= @modal_content %>
    <% else %>
      <.skeleton type="table">
    <% end %>
  <.modal>
  
  <.button phx-click={show_modal("my-modal") |> JS.push("show-my-modal")}>
    Show Modal
  </.button>
  """
end

def mount(_params, _session, socket) do
  {:ok, assign(socket, modal_content: nil)}
end

def handle_event("hide-my-modal", _params, socket) do
  {:noreply, assign(socket, modal_content: nil)}
end

def handle_event("show-my-modal", _params, socket) do
  {:noreply, assign(socket, modal_content: fetch_modal_content())}
end

Now, every time I click on “Show Modal”, it will show a skeleton that will be replaced after the content is fetched.

Edit:

My original approach was the set :modal_content to nil in the "show-my-modal" event handler, then kick off an async Task to fetch the content. This works, but shows the old modal contents for a split second before it re-renders the skeleton again. This is due to the JS showing the modal much faster than Phoenix can react to the server side event.