dberg
Liveview dynamic form - adding items from list
Say I want to make a form for making custom pizzas (the app is not actually about pizzas, but it is an example that is easier to understand).
I have toppings (say cheese, pepper, anchovies, etc) that are shown in a list.
Customers are allowed to create custom pizzas, by giving the pizza a name, and selecting any amount of toppings from a list of toppings that is displayed next to the form.
I have created the following Ecto schemas:
schema "toppings" do
field :name, :string
end
schema "pizza_toppings" do
belongs_to :pizza, Pizza
belongs_to :topping, Topping
end
schema "pizzas" do
field :name, :string
has_many :pizza_toppings, PizzaTopping
has_many :toppings, through: [:pizza_toppings, :topping]
end
I can not figure out the code that goes in the handle_event("add-topping", %{"topping_id" => topping_id}, socket) function (that is called when clicking on a specific topping) to actually add the topping to the changeset and have it validated by the form. I guess this is due to not quite grokking how changesets work…
I 've tried adapting the guide here One-to-Many LiveView Form | Benjamin Milde but have not succeded.
Most Liked
LostKobrakai
Ecto 3.10 (which I hoped to be out by now, but alas it isn’t) will include Ecto.Changeset.get_assoc, which in combination with Ecto.Changeset.put_assoc should make modification of assocs a bit more comfortable.
Is there a reason to go with nested embeds? These make sense if you have multiple fields per row, but you don’t seem to have that. I’d consider using checkboxes to toggle and transform the resulting list to a nested structure only on submit. I’ve a small POC for a checkboxes component and there’s a fly.io blogpost for something similar:
Scratchpad | Benjamin Milde
Making a CheckboxGroup Input · The Phoenix Files
BartOtten
codeanpeace
Based off of the guide you’re trying to adapt and your join schema, maybe try something like this?
def handle_event("add-topping", %{"topping_id" => topping_id}, socket) do
pizza_topping = %PizzaTopping{topping_id: topping_id}
socket =
update(socket, :form, fn %{source: changeset} ->
existing = get_change_or_field(changeset, :pizza_toppings)
changeset = Ecto.Changeset.put_assoc(changeset, :pizza_toppings, existing ++ [pizza_topping])
to_form(changeset)
end)
{:noreply, socket}
end
defp get_change_or_field(changeset, field) do
with nil <- Ecto.Changeset.get_change(changeset, field) do
Ecto.Changeset.get_field(changeset, field, [])
end
end
See: Adding tags to a post | Ecto.Changeset
There seems to be an understandable disconnect between Ecto and LiveView that likely exists because Ecto predates LiveView and was designed for the stateless paradigm of HTTP requests handled by conventional controllers. Subsequently, there is no default interface/function to incrementally build/add to an existing association within a parent changeset. Basically, something like Ecto.Changeset.add_to_assoc/3 doesn’t exist – but if it did, we could then do something like this:
def handle_event("add-topping", %{"topping_id" => topping_id}, socket) do
{:noreply,
update(socket, :form, fn %{source: changeset} ->
changeset
|> Ecto.Changeset.add_to_assoc(:pizza_toppings, %PizzaTopping{topping_id: topping_id})
|> to_form
end)
}
end
dberg
Yes, I’m starting to think that might be good way of implementing it. Thanks!







