Logo

dev-resources.site

for different kinds of informations.

Beter data-confirm modals in Phoenix LiveView

Published at
4/22/2024
Categories
webdev
elixir
phoenix
liveview
Author
neophen
Categories
4 categories in total
webdev
open
elixir
open
phoenix
open
liveview
open
Author
7 person written this
neophen
open
Beter data-confirm modals in Phoenix LiveView

TLDR; here's the result

The default experience

I really loved that Phoenix provided a nice plugin for having quick confirm behaviours without needing to do a lot of juggling things. While the behaviour works for testing it's really lacking UX for production use.

And as it turns out it's not really clear how to customise the behaviour, as out of the box it uses window.confirm which awaits the action before continuing on.

So if you want this behaviour you need to implement some sort of preventDefault, but execute after method.

Well you're in luck as today i've finally got around to writing this post.

The setup

First I've generated a phoenix application

mix phx.new better_data_confirm --databse sqlite3
Enter fullscreen mode Exit fullscreen mode

Next I've generated a Posts resource so that i could show this behaviour without too much hassle:

 mix phx.gen.live Blog Post posts title:string content:string
Enter fullscreen mode Exit fullscreen mode

That will get you to the default behaviour video above

Adding the danger_dialog

Next i've added a dialog element to the lib/better_data_confirm_web/components/layouts/root.html.heex root layout

Before:

  <body class="bg-white antialiased">
    <%= @inner_content %>
  </body>
Enter fullscreen mode Exit fullscreen mode

After:

  <body class="bg-white antialiased">
    <%= @inner_content %>
    <dialog
      id="danger_dialog"
      class="backdrop:bg-slate-800/75 shadow-xl rounded-md bg-white p-6 border"
    >
      <form method="dialog" class="grid gap-6 place-items-center">
        <h1 class="text-2xl" data-ref="title">
          Are you sure?
        </h1>
        <div class="flex gap-4 items-center justify-end">
          <.button data-ref="cancel" type="submit" value="cancel">
            Cancel
          </.button>
          <.button data-ref="confirm" type="submit" value="confirm" class="bg-red-500">
            Confirm
          </.button>
        </div>
      </form>
    </dialog>
  </body>
Enter fullscreen mode Exit fullscreen mode

Now this is up to your design choices and so on, you can have translated string, aria attributes, animations all that jazz, i'll link to a few good posts about that in the end.

But for the basic example it's a simple dialog with some styling and that's all.

Overriding the default behaviour

next in the assets/js/app.js we need to add the overriding behaviour, pop this code at the end of the file.

// Attribute which we use to re-trigger the click event
const CONFIRM_ATTRIBUTE = "data-confirm-fired"

// our dialog from the `root.html.heex`
const DANGER_DIALOG = document.getElementById("danger_dialog");

// Here we override the behaviour
document.body.addEventListener('phoenix.link.click', function (event) {
  // we prevent the default handling of this by phoenix html
  event.stopPropagation();

  // grab the target
  const { target: targetButton } = event;
  const title = targetButton.getAttribute("data-confirm");

  // if the target does not have `data-confirm` we simply ignore and continue
  if (!title) { return true; }

  // For re-triggering the click event
  if (targetButton.hasAttribute(CONFIRM_ATTRIBUTE)) {
    targetButton.removeAttribute(CONFIRM_ATTRIBUTE)
    return true;
  }

  // We do this since `window.confirm` prevents all execution by default.
  // To recreate this behaviour we `preventDefault` 
  // Then add an attribute which will allow us to re-trigger the click event while skipping the dialog
  event.preventDefault();
  targetButton.setAttribute(CONFIRM_ATTRIBUTE, "")

  // Reset the `returnValue` as otherwise on keyboard `Esc` it will simply take the most recent `returnValue`, causing all sorts of issues :D
  DANGER_DIALOG.returnValue = "cancel";

  // We use the title, which is nice we can have translated titles
  DANGER_DIALOG.querySelector("[data-ref='title']").innerText = title;


  // <dialog> is a very cool element and provides a lot of cool things out of the box, like showing the modal in the #top-layer
  DANGER_DIALOG.showModal();

  // Re-triggering logic
  DANGER_DIALOG.addEventListener('close', ({ target }) => {
    if (target.returnValue === "confirm") {
      // we re-trigger the click event
      // since we have the attribute set. This will just execute the click event
      targetButton.click();
    } else {
      // Remove the attribute on cancel as otherwise the next click would execute the click event without the dialog
      targetButton.removeAttribute(CONFIRM_ATTRIBUTE);
    }
  // once: true, automatically remove the listener after first execution
  }, { once: true })

}, false)
Enter fullscreen mode Exit fullscreen mode

And Voila

You now have a customised modal for data-confirm!
You can add any other things you need!

  • allow customising button texts
  • add a message
  • translate
  • change icons
  • animate the appearance/hiding of the modal
  • have different modal types danger/success/prompt

The sky is the limit...

Read more to create even better ux:

https://www.scelto.no/blog/promise-based-dialog
https://developer.chrome.com/blog/entry-exit-animations

Don't forget to leave a like!

Good luck with your adventures! If you enjoyed the post, likes are very appreciated, even better if you share this :D

liveview Article's
30 articles in total
Favicon
Phoenix LiveView, hooks and push_event: json_view
Favicon
Phoenix LiveView is slot empty?
Favicon
Bridging the Gap: Simplifying Live Component Invocation in Phoenix LiveView
Favicon
Optimize LiveView Performance with Temporary Assigns
Favicon
Phoenix LiveView Optimization Guide
Favicon
Better LiveView Hooks with Typescript
Favicon
Automatically clearing flash messages in Phoenix LiveView
Favicon
Debug and visualise TailwindCSS document structure
Favicon
Build a small chat service using Elixir and deploy it on Amazon ec2 using AWS (Part 1)
Favicon
Build a small chat service using Elixir and deploy it on Amazon ec2 using AWS (Part 2)
Favicon
Weather API: A GenServer and LiveView Implementation Part I
Favicon
Build a small chat service using Elixir and deploy it on Amazon ec2 using AWS (Last part)
Favicon
Backpex - a highly customizable admin panel for Phoenix LiveView applications
Favicon
AI powered app (with open-source LLMs like Llama) with Elixir, Phoenix, LiveView, and TogetherAI
Favicon
Using Ecto (without Db) for validating Phoenix form
Favicon
Adding stream_async() to Phoenix LiveView
Favicon
SaladUI - Implement avatar component for Phoenix LiveView
Favicon
Connectivity status with Phoenix LiveView
Favicon
Phoenix Liveview components for Shadcn UI
Favicon
Ash - Create with relationship
Favicon
My Failed Student Housing App
Favicon
Beter data-confirm modals in Phoenix LiveView
Favicon
Free Beginner Friendly LiveView Course in English and Portuguese
Favicon
LiveView + WebComponents = 🚀
Favicon
Ash AshSqlite - Aggregates not supported
Favicon
Phoenix Liveview - open editor for element
Favicon
Ash Calculations - Cond
Favicon
How does Ecto.Schema's `has_one/3` works?
Favicon
Creating a Date Range Picker with Phoenix LiveView
Favicon
Listing matches for users

Featured ones: