defmodule PentoWeb.Components.SubmitForm do
use PentoWeb, :live_component
use Ecto.Schema
import Ecto.Changeset
embedded_schema do
field(:username, :string)
end
def changeset(survey, attrs \\ %{}) do
survey
|> cast(attrs, [:username])
|> validate_required([
:username
])
end
def mount(socket) do
user_details = %__MODULE__{
username: "abcd"
}
change_set = changeset(user_details)
{:ok,
socket
|> assign(:user_details, user_details)
|> assign(:changeset, change_set)}
end
def update(assigns, socket) do
IO.puts("update being called")
changeset = changeset(socket.assigns.user_details)
{:ok,
socket
|> assign(assigns)
|> assign(:changeset, changeset)}
end
def handle_event("validate", params, socket) do
changeset = changeset(socket.assigns.user_details, Map.get(params, "submit_form"))
IO.inspect(changeset)
{:noreply,
socket
|> assign(:changeset, changeset)}
end
end
i am having trouble understanding how this form works, basically if someone can answer the following questions
in the form markup what exactly does the following for={@changeset} do ? i know it looks at a changeset, but what is it used for ?
what does the following achieve <%= text_input f, :username %>. i know it displays the username in the textbox but how does it get the value, if you look closely at the code i’m using user_details in the mount but i’m not specifying that this is the object the form should refer to, so how does it know which object to refer to ?
yup, i got that, but in the for i’m just simply supplying the changeset and in the initial load this is blank, there is nothing in the changeset, so how does it know to display “abcd” in the field ? even if i remove the line |> assign(:user_details, user_details) it still displays abcd in the text box, how dies it get this value ?
aha, got it, thanks for that, that clarifies quite a bit. also in the validate method if i submit a blank username i see the following changeset which is expected
Read the section “A note on :errors” here (just scroll down a bit).
Combining it with the link that I sent before you’ll see that there’s a field :action in the changeset struct, that field needs to be set manually to show errors, here’s where you’ll have to put the action:
def handle_event("validate", params, socket) do
changeset = changeset(socket.assigns.user_details, Map.get(params, "submit_form")) |> Map.put(:action, :validate)
IO.inspect(changeset)
{:noreply,
socket
|> assign(:changeset, changeset)}
end
You’ll usually see :validate in examples around the web, but that’s a convention and you can put any atom in there, you just need to say to the changeset “Hey, when we’re validating, set an action to yourself so the form knows that he has to show errors”.
thanks for that clarification (appreciate it), one last question considering the line why do we need socket.assigns.user_details when checking the changeset and do we need to store this value in the assigns ?
A changement is meant to track changes before applying them to update the initial data, it is supposed to be ephemeral. So you create a new changeset each time by casting the values in your params to the initial structure. There is a nice blogpost about this, by @LostKobrakai, https://kobrakai.de/kolumne/one-to-many-liveview-form, scroll down to the section “Echo.Changeset in LiveView”.
It does not. There’s a big chance you won’t be seeing any difference in the rendered form, because the form itself only uses a subset of a changeset’s features, but consider the following:
The resulting changeset here won’t have an error for :required_field, because it is already set, even though there are no changes to the field.
I’d strongly suggest learning about changesets outside the contexts of forms, because they’re a really powerful tool in a wide array of places with powering forms being only one of them.
Hmm, it likely appears to be still working because all of the parameters in user_details – in this case just username– are used in the form which means they come back across in Map.get(params, "submit_form"). I suspect if you had a field such as login_count that never gets displayed, but is a required field through validate_required, then things might be different as @LostKobrakai mentions above.