@carlgleisner I found the entire process quite frustrating to be honest. In order to get the behaviour I wanted I ended up just handling everything myself. I used AshPhoenix.Form to do the actual form field validation but otherwise I threw the errors and all of that myself because I couldn’t get the defaults to work the way I expected. Below is the code I used to get the behaviour I wanted. Take this with a very large grain of salt as this is my first time using phoenix forms and I could just be doing something wrong. The main things you will notice here is that I’m not using for={@form} or any field={@form[:name]} on the inputs. I’m just handling everything myself and not using any helpers because I just couldn’t get what I wanted otherwise. Below are my two relevant files, the form and the ash resource.
defmodule MyAppWeb.ContactLive do
use MyAppWeb, :live_view
@impl true
def render(assigns) do
~H"""
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 my-8">
<div class="text-gray-700">
<form id="contact-form" phx-change="validate" phx-submit="submit">
<div>
<.input
name="name"
type="text"
class={@disabled_loading_state}
placeholder="John Doe"
label="Name"
value=""
errors={@name_errors}
/>
<.input
name="email"
type="text"
class={@disabled_loading_state}
placeholder="john@example.com"
value=""
label="Email"
errors={@email_errors}
/>
</div>
<div>
<.input
name="message"
class={@disabled_loading_state}
placeholder="Enter your message here..."
label="Message"
value=""
type="textarea"
errors={@message_errors}
/>
</div>
<.button
type="submit"
phx-disable-with="Sending..."
class="phx-submit-loading:cursor-not-allowed"
>
Submit
</.button>
</form>
</div>
"""
end
@impl true
def mount(_params, _session, socket) do
form =
MyApp.Public.Message
|> AshPhoenix.Form.for_create(:send_message, as: "contact")
|> to_form()
disabled_loading_state =
"phx-submit-loading:cursor-not-allowed phx-submit-loading:bg-slate-50 phx-submit-loading:text-slate-500 phx-submit-loading:border-slate-200"
{:ok,
socket
|> assign(:disabled_loading_state, disabled_loading_state)
|> assign(:form, form)
|> assign(:name_errors, [])
|> assign(:email_errors, [])
|> assign(:message_errors, [])}
end
def handle_event("validate", params, socket) do
validate = AshPhoenix.Form.validate(socket.assigns.form, params)
target = validate.params["_target"] |> List.first()
case target do
"name" ->
{:noreply, socket |> validate_name(validate.errors[:name])}
"email" ->
{:noreply, socket |> validate_email(validate.errors[:email])}
"message" ->
{:noreply, socket |> validate_message(validate.errors[:message])}
_ ->
{:noreply,
socket
|> assign(name_errors: [])
|> assign(email_errors: [])
|> assign(message_errors: [])}
end
end
@impl true
def handle_event("submit", params, socket) do
validate = AshPhoenix.Form.validate(socket.assigns.form, params)
socket =
socket
|> assign(form: validate)
|> validate_name(validate.errors[:name])
|> validate_email(validate.errors[:email])
|> validate_message(validate.errors[:message])
case AshPhoenix.Form.submit(socket.assigns.form) do
{:ok, form} ->
{:noreply,
socket
|> put_flash(:info, "Thank you for your message #{form.email}!")
|> push_navigate(to: ~p"/contact")}
{:error, form} ->
{:noreply, socket |> assign(form: form)}
end
end
defp validate_message(socket, errors) when is_nil(errors) do
socket |> assign(message_errors: [])
end
defp validate_message(socket, errors) do
{message, _} = errors
socket |> assign(message_errors: [message])
end
defp validate_email(socket, errors) when is_nil(errors) do
socket |> assign(email_errors: [])
end
defp validate_email(socket, errors) do
{message, _} = errors
socket |> assign(email_errors: [message])
end
defp validate_name(socket, errors) when is_nil(errors) do
socket |> assign(name_errors: [])
end
defp validate_name(socket, errors) do
{message, _} = errors
socket |> assign(name_errors: [message])
end
end
Ash Resource
defmodule MyApp.Public.Message do
@moduledoc """
Resource for creating messages from the contact form that is publicly available
"""
use Ash.Resource,
domain: MyApp.Public,
data_layer: AshPostgres.DataLayer
postgres do
table "public_contacts"
repo MyApp.Repo
end
actions do
defaults [:read, :destroy, update: :*]
create :send_message do
accept [:email, :message, :name]
validate match(
:email,
~r<^[a-zA-Z2-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$>
) do
message "invalid email"
end
end
end
attributes do
uuid_primary_key :id
attribute :email, :ci_string, allow_nil?: false, public?: true
attribute :message, :string, allow_nil?: false, public?: true
attribute :name, :string, allow_nil?: false, public?: true
attribute :archived_at, :utc_datetime_usec, public?: true
attribute :deleted_at, :utc_datetime_usec, public?: true
create_timestamp :created_at, public?: true
update_timestamp :updated_at, public?: true
end
end






















