To answer your question, I’ve handled this in two different ways in my application:
do a Changeset.put_change(changeset, :time, time) in my event handler, so the data gets persisted into my form state. I have a hidden input like you do to ensure the value persists when other changes happen to the form.
@impl true
def handle_event("select_hour", %{"hour" => hour_str}, socket) do
time = Time.new!(hour_str |> String.to_integer(), 0, 0)
changeset =
socket.assigns.form.source
|> Ecto.Changeset.put_change(:time, time})
{:noreply, assign_form(socket, changeset)}
end
if I’m in a live component that I want to be more reusable (not requiring the parent to implement a handle_event), I call a assign(socket, :value, hour), and use a small hook I wrote that will trigger the form change event so Phoenix’s standard form handling will detect it and populate it.
But as your current example is, I think both of these are overkill. I would instead improve the select’s data so it already has the times as values. Then there’s no necessary conversion step and you can put the time select in the same form as the name.
defp time_options() do
0..23
|> Enum.map(fn hour ->
label = "#{hour}:00"
value = Time.new!(hour, 0, 0)
[key: label, value: value]
end)
end
This returns this data which you can use in your select:
I had a little trouble because I was expecting the form to behave like a live view component instead of a liveview . The default form that phoenix creates is helpful bc it shows how to call out to the relevant context to make changes without having a nested module essentially doing the same thing.
disclaimer: I’m pretty new so I’m more book smart than experienced.
You can give that form some id let’s say filters-form.
And then you can submit the form from some other element outside the form by dispatching submit event.
on_confirm={JS.dispatch("submit", to: "#filters-form" ) }
in a similar approach, I have a helper function that takes params, and returns updated params.
Because, I have 3 external fields. (uploads, timers etc) outside the form
Hi everyone, I chanced upon a similar issue and I wish to check if my implementation makes sense.
For some context, I have a single modal that shows up whenever I want to add a customer or edit the details of a customer. In order to update the customer details, I need to input the customer object into my Customers query. This means that the customer’s id information is stored in a form struct. The PROBLEM is that everytime I edit my form, it changes into a new form struct (without the id information).
My approach is to just simply have 2 handle events to deal with the 2 separate situations.
def handle_event("validate-new-customer", %{"customer" => params}, socket) do
form =
%Customer{}
|> Customers.change_customer(params)
|> Map.put(:action, :validate)
|> to_form()
{:noreply, assign(socket, :customer_form, form)}
end
def handle_event("validate-edit-customer", %{"customer" => params}, socket) do
params = Map.put_new(params, "id", socket.assigns.customer_form.data.id)
form =
%Customer{}
|> Customers.change_customer(params)
|> Map.put(:action, :validate)
|> to_form()
{:noreply, assign(socket, :customer_form, form)}
end
On top of this, I need to add :id to my cast() for my Customer struct. I also dont know if this affects anything (sorry a newbie here).
By default .inputs_for will add hidden inputs (id, not sure what else?). I’ve disabled this in one place of my app, because I wanted precise control over the location of these inputs so they wouldn’t interfere with drag-and-drop functionality. You can disable them with skip_hidden. Eg:
// Hook that will dispatch change event when it's updated. Useful for inputs that are
// updated by a component so they won't fire a change event.
Hooks.PublishInput = {
updated() {
this.el.dispatchEvent(new Event("input", { bubbles: true }));
},
};
Note: this triggers the input event, which by itself won’t get back to LiveView. It has to be inside a managed form which will then trigger whatever change handling you have.