ppff01

ppff01

DaisyUI modal in phoenix 1.8 - code example

Hi there,

I’ve been testing Phoenix 1.8 a lot lately and noticed the modal component from 1.7 had disappeared. Since we now have DaisyUI which comes with a Modal component, I figured I’d try to use it. It’s not that easy because if you want to do it the cleanest way, you need to use the browser’s showModal() function. I won’t get into too much detail but after several hours of tests here is what I came up with. I hope it can be useful to some of you here!

The component:

  @doc """
  Modal dialog.
  """
  attr :id, :string, required: true
  attr :on_cancel, JS, default: %JS{}
  slot :inner_block, required: true

  def modal(assigns) do
    ~H"""
    <dialog
      id={@id}
      phx-hook="Modal"
      phx-remove={
        JS.remove_attribute("open")
        |> JS.transition({"ease-out duration-200", "opacity-100", "opacity-0"}, time: 0)
      }
      data-cancel={JS.exec(@on_cancel, "phx-remove")}
      class="modal"
    >
      <.focus_wrap
        id={"#{@id}-container"}
        phx-window-keydown={JS.exec("data-cancel", to: "##{@id}")}
        phx-key="escape"
        phx-click-away={JS.exec("data-cancel", to: "##{@id}")}
        class="modal-box"
      >
        <.button phx-click={JS.exec("data-cancel", to: "##{@id}")} class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2 tx-lg">✕</.button>
        <%= render_slot(@inner_block) %>
      </.focus_wrap>
    </dialog>
    """
  end

Add a new hook which will trigger showModal():

Hooks.Modal = {
    mounted() {
        this.el.showModal();
    },
}

In your liveview, just add the modal:

  <.modal
    id="delete_modal"
    :if={@live_action in [:delete]}
    on_cancel={JS.patch(~p"/brands")}
  >
    <h3 class="text-xl text-bold">Warning</h3>
    <p class="text-md mt-3">Are you sure you want to delete brand {@brand.name}?</p>
    <div class="modal-action">
      <.button phx-click="delete" phx-value-id={@brand.id}>Delete</.button>
    </div>
  </.modal>

It’s basically a hot-swap solution for Phoenix modal 1.7. Please note that the modal should be opened only with a specific live_action which is defined in the router:

live "/brands/:id/delete", BrandsLive, :delete

This is necessary because we don’t have an easy way to trigger the close javascript command and retrieve data while letting the browser-operated fade out operate properly (or at least I didn’t find one). But it’s also very useful because it allows us to pass parameters to the modal easily using the url.

Anyway, just my two cents! Feel free to suggest improvements or ask questions.

Most Liked

ppff01

ppff01

Update: the issue with this implementation is that if you use it with a form for example, the liveview updates will rewrite the html without the open tag on the dialog HTML element, therefore closing the modal.

So here is my final solution: ditch the hook and just include the open tag from the start.

    <dialog
      id={@id}
      phx-remove={
        JS.remove_attribute("open")
        |> JS.transition({"ease-out duration-200", "opacity-100", "opacity-0"}, time: 0)
      }
      data-cancel={JS.exec(@on_cancel, "phx-remove")}
      class="modal"
      open
    >
LostKobrakai

LostKobrakai

core_components only include what phoenix generators use. Modals in generated code eventually got replaced with separate pages, so there was no use for the modal anymore.

RodolfoSilva

RodolfoSilva

This is my implementation:

  @doc """
  Renders a modal.

  ## Examples

      <.modal id="confirm-modal">
        This is a modal.
      </.modal>

  JS commands may be passed to the `:on_cancel` to configure
  the closing/cancel event, for example:

      <.modal id="confirm" on_cancel={JS.navigate(~p"/posts")}>
        This is another modal.
      </.modal>

  """
  attr :id, :string, required: true
  attr :on_cancel, JS, default: nil
  slot :inner_block, required: true

  def modal(assigns) do
    ~H"""
    <.portal id={@id} target="body">
      <dialog
        open
        closedby={if @on_cancel, do: "any", else: "none"}
        phx-mounted={
          JS.ignore_attributes("open")
          |> JS.transition({"ease-in duration-200", "opacity-0", "opacity-100"}, time: 0)
        }
        phx-remove={
          JS.remove_attribute("open")
          |> JS.transition({"ease-out duration-200", "opacity-100", "opacity-0"}, time: 0)
        }
        class="modal"
      >
        <.focus_wrap
          class="modal-box w-11/12 max-w-2xl"
          id={"#{@id}-container"}
          tabindex="0"
          phx-key="escape"
          phx-window-keydown={@on_cancel}
        >
          <form :if={@on_cancel} method="dialog">
            <button
              phx-click={@on_cancel}
              class="btn btn-sm btn-circle btn-ghost absolute top-2 right-2"
            >
              ✕
            </button>
          </form>
          {render_slot(@inner_block)}
        </.focus_wrap>
        <form :if={@on_cancel} method="dialog" class="modal-backdrop">
          <button phx-click={@on_cancel}>close</button>
        </form>
      </dialog>
    </.portal>
    """
  end

Requires the LiveView > v1.1.5.

Where Next?

Popular in Discussions Top

vans163
So useless benchmarks aside, Its possible to write a webserver that can serve 300k requests per second (perhaps more with optimizations)....
New
100phlecs
Are there any downsides, like perf issues, to putting all functional components in CoreComponents as long as you prefix it with the conte...
New
jesse
Hi everyone, I hesitated to post this here because I don’t want you to think I’m spamming, but I’ve been working on a Platform-as-a-Serv...
New
Nvim
Elixir appears to be a superior language to Python. I don’t see any advantage of Python over Elixir. Are there any?
New
crispinb
On reading dhh’s latest The One Person Framework it strikes me that Phoenix with LiveView is already pretty much this. However, never hav...
New
Fl4m3Ph03n1x
Background This question comes mainly from my ignorance. Today is Black Friday, one of my favorite days of the year to buy books. One boo...
New
arpan
Hello everyone :wave: Today I am very excited to announce a project that I have been working on for almost 3 months now. The project is...
New
lucaong
Hello Elixir and Nerves community, I have been working for a while on an open-source embedded key-value database for Elixir, that I call...
230 14049 124
New
AstonJ
Can you believe the first professionally published Elixir book was published just 8 years ago? Since then I think we’ve seen more books f...
New
Fl4m3Ph03n1x
Background A few days ago I was listening to The future of Elixir from Elixir Talks, with Dave Thomas (@pragdave ) and Brian Mitchell. I...
New

Other popular topics Top

TunkShif
This post is an instruction guide to help you setup your Neovim for Elixir development from scratch. It includes general information on h...
274 42158 114
New
albydarned
Hello all! I am typing this post from my new MacBook Pro with the M1 chip. I’m loving it so far, and will probably use it as my daily dr...
New
shahryarjb
Hello, I have map which I want to convert it to string like this: the map: %{last_name: "tavakkoli", name: "shahryar"} the string I ne...
New
stefanluptak
Hello everybody, usually, I use a 29" ultra-wide monitor for VSCode which can easily accomodate explorer (files panel) + file with code ...
New
vrod
I am using the Starship cross-shell prompt – it seems pretty nice, but I get some errors: [WARN] - (starship::utils): Executing command ...
New
alice
Hey, Just curious what are the main benefits of Elixir compared to Clojure? When is Elixir more useful than Clojure and vice versa? Th...
New
vonH
When I run the Plug and I recompile I wind up having to use Ctrl C to quit iex and start again. Witht the help of rlwrap I can use the cu...
New
komlanvi
Hi everyone, I was playing with phoenix liveView but I run into an issue. I have a form and want to validate each input text when the te...
New
JorisKok
I have a server on AWS, and was running a load test using artillery. When looking at the Phoenix dashboard I see the Ports going to 100% ...
New
vegabook
I’m brand new to Phoenix and I have stripped one of the demo applications to the bone. I just want to get an svg up on the screen. Here i...
New

We're in Beta

About us Mission Statement