Beter data-confirm modals in Phoenix LiveView

RMAG news

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

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

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>

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” dataref=“title”>
Are you sure?
</h1>
<div class=“flex gap-4 items-center justify-end”>
<.button dataref=“cancel” type=“submit” value=“cancel”>
Cancel
</.button>
<.button dataref=“confirm” type=“submit” value=“confirm” class=“bg-red-500”>
Confirm
</.button>
</div>
</form>
</dialog>
</body>

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; }

// This is to enable re-triggering, which doesn’t show the modal just executes the click event. without showing the dialog
if (targetButton.hasAttribute(CONFIRM_ATTRIBUTE)) {
targetButton.removeAttribute(CONFIRM_ATTRIBUTE)
return true;
}

// We do this since window.confirm prevents all execution by default, so to recreate this behaviour we prevent the default and add an attribute which will allow us to re-trigger the click event without calling the dialog
event.preventDefault();
targetButton.setAttribute(CONFIRM_ATTRIBUTE, “”)

// Reset the `returnValue` as otherwise on keyboard `Esc` it will simply take the most recent `returnValue`
DANGER_DIALOG.returnValue = cancel;

// We change the title, which is nice we can have translated titles and so forth
// You can expand this and add `data-confirm-message` for a message `data-confirm-accept` to change the button text and so forth, imagination is your limit
// you could event have different dialogs data-success, data-prompt and so forth.
DANGER_DIALOG.querySelector([data-ref=’title’]).innerText = title;

// we show the modal, dialog is a very cool element and provides a lot of cool things out of the box.
DANGER_DIALOG.showModal();

// Here we add the re-triggering logic
DANGER_DIALOG.addEventListener(close, ({ target }) => {
if (target.returnValue === confirm) {
// we re-trigger the click event, since we have the attribute set it will simply execute the click event without the dialog logic
targetButton.click();
} else {
// we need to remove the attribute as otherwise the next click would just execute the action
targetButton.removeAttribute(CONFIRM_ATTRIBUTE);
}
// once: true, automatically remove the listener after first execution
}, { once: true })

}, false)

And Voila

You now have a customised modal for data-confirm, you can add any other thing you need, allow customising button texts, add a message, translate, change icons, animate the appearance/hiding of the modal, 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

Thank you and take care!

Good luck with your adventures!

Leave a Reply

Your email address will not be published. Required fields are marked *