Skip to content

Commit

Permalink
Docs
Browse files Browse the repository at this point in the history
  • Loading branch information
chrismccord committed May 29, 2023
1 parent 76cadff commit 46cc020
Showing 1 changed file with 73 additions and 0 deletions.
73 changes: 73 additions & 0 deletions lib/phoenix_component.ex
Original file line number Diff line number Diff line change
Expand Up @@ -2224,6 +2224,79 @@ defmodule Phoenix.Component do
</.inputs_for>
</.form>
```
## Dynamically adding and removing inputs
Dynamicaly adding and removing inputs is supported by rendering
checkboxes for inserts and removals. Libraries such as Ecto, or custom param
filtering can then inspect the paramters and handle the added or removed fields.
This can be combined with `Ecto.Changeset.cast/3`'s `:sort_param` and `:drop_param`
options. For example, imagine a parent with an `:emails` `has_many` or `embeds_many`
association. To cast the user input from a nested form, one simply needs to configure
the options:
schema "lists" do
field :title, :string
embeds_many :emails, EmailNotification, on_replace: :delete do
field :email, :string
field :name, :string
end
end
def changeset(list, attrs) do
list
|> cast(attrs, [:title])
|> cast_embed(:emails,
with: &email_changeset/2,
sort_param: :emails_order,
drop_param: :emails_drop
)
end
```
Here we see the `:sort_param` and `:drop_param` options in action.
*Note: `on_replace: :delete` on the `has_many` and `embeds_many` is required when using
these options.
When Ecto sees the specified sort or drop parameter from the form, it will sort
the children based on the order they appear in the form, add new children it hasn't
seen, or drop children if the parameter intructs it to do so.
The markup for such a schema and association would look like this:
```heex
<.inputs_for :let={ef} field={@form[:emails]}>
<input type="hidden" name="list[emails_sort][]" value={ef.index} />
<.input type="text" field={ef[:email]} placeholder="email" />
<.input type="text" field={ef[:name]} placeholder="name" />
<label>
<input type="checkbox" name="list[emails_drop][]" value={ef.index} class="hidden" />
<.icon name="hero-x-mark" class="w-6 h-6 relative top-2" />
</label>
</.inputs_for>
<label class="block cursor-pointer">
<input type="checkbox" name="list[emails_sort][]" class="hidden" />
add more
</label>
```
We used `inputs_for` to render inputs for the `:emails` association, which
containes an email address and name input for each child. Within the nested inputs,
we render a hidden `list[emails_sort][]` input, which is set to the index of the
given child. This tells Ecto's cast operation how to sort existing children, or
where to insert new children. Next, we render the email and name inputs as usual.
Then we render a label containing the "delete" text and a hidden checkbox input
with the name `list[emails_drop][]`, containing the index of the child as its value.
Like before, this tells Ecto to delete the child at this index when the checkbox is
checked. Wrapping the checkbox and textual content in a label makes any clicked content
within the label check and uncheck the checbox.
Finally, outside the `inputs_for`, we render another label with a value-less
`list[emails_sort][]` checkbox witih accompanied "add more" text. Ecto will
treat unknown sort params as new children and build a new child.
"""
@doc type: :component
attr.(:field, Phoenix.HTML.FormField,
Expand Down

0 comments on commit 46cc020

Please sign in to comment.