Hello,
I have a problem (and potential bug) with nested form.
I have a form for a “product” witch has many “languages” witch has many “attributes”.
I want to be able to add some “attributes”.
If I have a create a new product with this changeset :
Product.changeset(
%Product{
languages: [
%Language{name: "FR", attributes: []},
%Language{name: "EN", attributes: []}
]
},
%{}
)
Everything seems to work fine.
But if I want to edit a product from the following seed
Repo.insert!(%Product{
name: "a product",
languages: [
%Language{name: "French",
attributes: [
%Attribute{label: "Couleur",
value: "Noir"}
]
},
%Language{name: "English",
attributes: [
%Attribute{label: "Color",
value: "Black"}
]
}]
})
the button add attribute doesn’t work any more.
The line Ecto.Changeset.put_assoc(changeset, :languages, udpated_languages)
returns
#Ecto.Changeset<action: nil, changes: %{}, errors: [],
data: #NestedInputsFor.Product.Product<>, valid?: true>
Here is my live view component :
defmodule NestedInputsForWeb.ProductForm do
use Phoenix.LiveView
alias NestedInputsFor.Product.{Attribute, Language, Product}
alias NestedInputsFor.Repo
import NestedInputsForWeb.CoreComponents
def render(assigns) do
~H"""
<h1>Product form</h1>
<.form for={@form} as={:product} phx-submit={:save} phx-change={:validate}>
<label>Product name</label>
<.input field={@form[:name]} type="text" />
<div class="flex">
<.inputs_for :let={language_form} field={@form[:languages]}>
<.input field={language_form[:name]} type="hidden" />
<div class="flex-initial">
<%= Ecto.Changeset.get_field(language_form.source, :name) %>
<br/>
<.inputs_for :let={attribute_form} field={language_form[:attributes]}>
<label>Label</label>
<.input field={attribute_form[:label]} type="text" />
<label>Value</label>
<.input field={attribute_form[:value]} type="text" />
</.inputs_for>
</div>
</.inputs_for>
</div>
<div phx-click="add_attribute">Add attribute</div>
<button>Save</button>
</.form>
"""
end
def mount(_params, _, socket) do
changeset = Repo.all(Product)
|> Repo.preload(languages: :attributes)
|> hd()
|> Product.changeset(%{})
{:ok, assign(socket, %{form: to_form(changeset), changeset: changeset})}
end
def handle_event("add_attribute", _params, socket) do
changeset = socket.assigns.changeset
udpated_languages =
Ecto.Changeset.get_field(changeset, :languages)
|> Enum.map(fn language ->
Map.put(language, :attributes, language.attributes ++ [%Attribute{}])
end)
changeset = Ecto.Changeset.put_assoc(changeset, :languages, udpated_languages)
{:noreply, assign(socket, %{form: to_form(changeset), changeset: changeset})}
end
def handle_event("validate", %{"product" => params}, socket) do
changeset = Product.changeset(socket.assigns.changeset, params)
{:noreply, assign(socket, %{form: to_form(changeset), changeset: changeset})}
end
def handle_event("save", params, socket) do
IO.inspect(params)
{:noreply, socket}
end
end
This demo project is available here https://github.com/ChristopheBelpaire/nested_inputs_for
Did I do something wrong or is it a bug ?
Thanks in advance!