Thank you for your response
Thats works for for standart select form, it’s first advice which I hear when I show a field like this. Could you explain what field should process with put_assoc
? And where is it should process (form, changeset)?
we can skip complex combobox
mechanics and just use multiselect from core components
form represent changeset, where I prepare data for multiselect
defmodule RunaWeb.Live.Project.Form do
@moduledoc """
Form responsible for creating and updating projects.
"""
use RunaWeb, :live_component
alias Runa.Languages
alias Runa.Projects
alias Runa.Projects.Project
import RunaWeb.Components.Form
import RunaWeb.Components.Input
@impl true
def mount(socket) do
{:ok, {languages, _}} = Languages.index()
{:ok, assign(socket, languages: Enum.map(languages, &{&1.title, &1.id}))}
end
@impl true
def update(%{data: %Project{} = data} = assigns, socket) do
data =
data
|> maybe_preload(:base_language)
|> maybe_preload(:locales)
attrs = %{
"locales" =>
if(is_nil(data.id),
do: [],
else: Enum.map(data.locales, & &1.id)
)
}
socket =
socket
|> assign(assigns)
|> assign(data: data)
|> assign(
action: if(is_nil(data.id), do: :new, else: :edit),
form: to_form(Project.changeset(data, attrs))
)
{:ok, socket}
end
slot(:actions, doc: "the slot for form actions, such as a submit button")
@impl true
def render(assigns) do
~H"""
<div>
<.custom_form
id={@id}
for={@form}
phx-change="validate"
phx-submit="submit"
phx-target={@myself}
aria-label="Project form"
>
<.input type="hidden" field={@form[:team_id]} value={@team_id} />
<.input type="text" aria-label="Project name" field={@form[:name]}>
<:label>Name</:label>
</.input>
<.input
type="textarea"
aria-label="Project description"
field={@form[:description]}
>
<:label>Description</:label>
</.input>
<.input type="select" field={@form[:base_language_id]} options={@languages}>
<:label>Base language</:label>
</.input>
<.input type="select" field={@form[:locales]} options={@languages} multiple>
<:label>Languages</:label>
</.input>
{render_slot(@actions, @form)}
</.custom_form>
</div>
"""
end
@impl true
def handle_event("validate", %{"project" => attrs}, socket) do
dbg(attrs)
socket =
update(socket, :form, fn _, %{data: data} ->
changeset =
Project.changeset(data, attrs)
to_form(changeset, action: :validate)
end)
{:noreply, socket}
end
@impl true
def handle_event("submit", %{"project" => attrs}, socket) do
submit(socket, socket.assigns.action, attrs)
end
@impl true
def handle_event(_, _, socket) do
{:noreply, socket}
end
defp submit(socket, :edit, attrs) do
socket.assigns.data
|> Projects.update(attrs)
|> case do
{:ok, _} ->
{:noreply, socket}
{:error, %Ecto.Changeset{} = changeset} ->
{:noreply, assign(socket, form: to_form(changeset))}
end
end
defp submit(socket, :new, attrs) do
attrs
|> Projects.create()
|> case do
{:ok, _} ->
{:noreply, socket}
{:error, %Ecto.Changeset{} = changeset} ->
{:noreply, assign(socket, form: to_form(changeset))}
end
end
defp put_locales(attrs) do
locales =
attrs["locales"]
|> Enum.map(&%{"language_id" => &1})
Map.put(attrs, "locales", locales)
end
end
project changeset
defmodule Runa.Projects.Project do
@moduledoc """
Schema for the projects entity.
"""
use Runa, :schema
alias Runa.Files.File
alias Runa.Keys.Key
alias Runa.Languages.Language
alias Runa.Languages.Locale
alias Runa.Teams.Team
schema "projects" do
field :name, :string
field :description, :string
has_many :files, File
has_many :keys, Key
has_many :locales, Locale, on_replace: :delete
belongs_to :team, Team
belongs_to :base_language, Language
many_to_many :languages, Language, join_through: Locale, on_replace: :delete
timestamps type: :utc_datetime
end
def changeset(struct, attrs \\ %{}) do
struct
|> cast(attrs, [:name, :description, :team_id, :base_language_id])
|> validate_required([:name, :team_id, :base_language_id])
|> foreign_key_constraint(:team_id)
|> foreign_key_constraint(:base_language_id)
|> cast_assoc(:locales)
end
end
project relates with languages through locales schema
defmodule Runa.Languages.Locale do
@moduledoc """
Schema for the locale entity.
"""
use Runa, :schema
alias Runa.Languages.Language
alias Runa.Projects.Project
schema "locales" do
belongs_to :project, Project
belongs_to :language, Language
timestamps type: :utc_datetime
end
@doc false
def changeset(locale, attrs) do
locale
|> cast(attrs, [:project_id, :language_id])
|> validate_required([:language_id])
|> foreign_key_constraint(:project_id)
|> unique_constraint([:project_id, :language_id])
end
end
After choose another select options I’ve got error, because I work with language id, but projects expect locale as map