Problem: accidentally pressing 'Esc' when editing a form in a "standard" LiveView modal dialog will discard all edits.
Proposed solution: Make the modal show a confirmation window on close when there have been changes on the form.
LiveView v0.20 was used.
Table of content
This is how we'll be passing the confirmation message to the modal when a confirmation is needed.
In core_components.ex, add:
attr :close_confirm, :string, default: nil
Let's pass close_confirm
down to custom JS with JS.dispatch
at the attempt to close the modal.
In core_components.ex, replace this line:
data-cancel={JS.exec(@on_cancel, "phx-remove")}
with these:
data-do-cancel={JS.exec(@on_cancel, "phx-remove")}
data-cancel={JS.dispatch("modal:maybe_close", detail: %{close_confirm: @close_confirm})}
In app.js add the handler for our dispatched event:
window.addEventListener("modal:maybe_close", (e) => {
let el = e.detail.dispatcher
let doClose = () => {
// Call the former default action thet we previously renamed to "data-do-cancel"
liveSocket.execJS(el, el.getAttribute("data-do-cancel"))
}
if (e.detail.close_confirm) {
if (confirm(e.detail.close_confirm)) {
doClose()
}
} else {
doClose()
}
})
Let's say we used mix phx.gen.live
to generate some CRUD code for managing posts.
Add a new boolean assign into MyApp.PostLive.Index
to mark the form as "dirty":
In live/post_live/index.ex add a new handle_info clause to react on the form being changed:
def handle_info(:changed, socket) do
{:noreply, assign(socket, :dirty_form, true)}
end
We need to make sure this gets called from live/post_live/form_component.ex. Right below handle_event for the "validate" event, add this line:
send(self(), :changed)
We also need to reset the dirty_form
flag each time we re-open the modal. Back in live/post_live/index.ex, reset the assign in apply_action
for both :edit and :new actions, e.g., for :edit:
defp apply_action(socket, :edit, %{"id" => id}) do
socket
# ...
|> assign(:dirty_form, false)
end
In live/post_live/index.html.heex, when defining the modal, add our new config option that we added in the beginning:
<.modal
:if={@live_action in [:new, :edit]}
id="post-modal"
show
on_cancel={JS.patch(~p"/posts")}
close_confirm={@dirty_form && "Are you sure?"}
>
That's it. For any questions, contact me on X, or better yet, hire me.