I have two forms. One to create and one to delete. The form validation for the create form works and prevents submit until valid is true. For the delete form, I want users to confirm deletion by typing the name. Here, the form validation also works, if the name_confirmation doesn’t match the changeset is invalid, but the form submit is not prevented.
So to make it extra clear, submitting the create form with an invalid changeset blocks. Submitting the delete form with an invalid changeset does not.
The only difference I can find is that the create form is in a live_view, but the delete form is in a live_component.
Below are the schema, component and form for the delete with confirmation feature. I’d appreciate any pointers what I’m doing wrong.
defmodule Cloud.Platform.Stack do
use Ecto.Schema
import Ecto.Changeset
@primary_key {:id, Ecto.UUID, autogenerate: true}
schema "stacks" do
field(:name, :string)
field(:owner, :string)
field(:name_confirmation, :string, virtual: true)
field(:delete, :boolean, virtual: true)
timestamps()
end
def changeset(stack, %{"delete" => "true"} = attrs) do
stack
|> cast(attrs, [:name, :name_confirmation])
|> validate_required([:name, :name_confirmation])
|> validate_confirmation(:name,
required: true,
message: "must match name to confirm"
)
end
def changeset(stack, attrs) do
stack
|> cast(attrs, [:name, :owner])
|> validate_required([:name, :owner])
|> validate_length(:name, min: 3, max: 255)
end
end
defmodule CloudWeb.StackLive.FormComponent do
use CloudWeb, :live_component
alias Cloud.Platform
@impl true
def update(%{stack: stack} = assigns, socket) do
changeset = Platform.change_stack(stack)
{:ok,
socket
|> assign(assigns)
|> assign(:changeset, changeset)}
end
require Logger
@impl true
def handle_event("validate", %{"stack" => stack_params}, socket) do
changeset =
socket.assigns.stack
|> Platform.change_stack(stack_params)
|> Map.put(:action, :validate)
Logger.debug(changeset)
{:noreply, assign(socket, :changeset, changeset)}
end
def handle_event("delete", _stack_params, socket) do
case Platform.delete_stack(socket.assigns.stack) do
{:ok, _stack} ->
{:noreply,
socket
|> put_flash(:info, "Stack deleted successfully")
|> push_redirect(to: Routes.stack_index_path(socket, :index))}
{:error, %Ecto.Changeset{} = changeset} ->
{:noreply, assign(socket, changeset: changeset)}
end
end
end
<div>
<%= if @action == :delete do %>
<.form
let={f}
for={@changeset}
id="delete-stack-form"
phx-target={@myself}
phx-change="validate"
phx-submit="delete">
<%= hidden_input f, :name, value: input_value(f, :name) %>
<%= hidden_input f, :delete, value: true %>
<div class="sm:rounded-md sm:overflow-hidden">
<%= label f, :name_confirmation, class: "block text-sm font-medium text-gray-700" %>
<div class="mt-1 flex rounded-md shadow-sm">
<%= text_input f, :name_confirmation, class: "focus:ring-indigo-500 focus:border-indigo-500 flex-1 block w-full rounded sm:text-sm border-gray-300", placeholder: @stack.name %>
<%= error_tag f, :name_confirmation %>
</div>
</div>
<div class="px-5 py-3 text-right sm:px-6">
<%= submit "Delete", phx_disable_with: "Deleting...", class: "inline-flex justify-center py-2 px-4 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-red-600 hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500" %>
</div>
</.form>
<% end %>
</div>