Last active
May 3, 2024 14:30
-
-
Save srcrip/a73f4134e29c261acaa86df1444cbac3 to your computer and use it in GitHub Desktop.
Some helper functions for making nested forms in LiveView
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<.form for={@form} class="max-w-xs"> | |
<NestedFormComponents.inputs_for_embeds | |
:let={embed_form} | |
parent_form={@form} | |
field={@form[:items]} | |
id={"#{@form[:items].id}_embeds"} | |
sort_param={:items_sort} | |
drop_param={:items_drop} | |
> | |
<div class="grow"> | |
<.input field={embed_form[:text]} aria-label="text" type="text" placeholder="List item" /> | |
</div> | |
<:drop_button> | |
Remove | |
</:drop_button> | |
<:add_button class="w-full mt-2"> | |
Add | |
</:add_button> | |
</NestedFormComponents.inputs_for_embeds> | |
</.form> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
defmodule ExampleWeb.NestedFormComponents do | |
@moduledoc """ | |
Helper components for driving forms from embedded schemas. | |
""" | |
use ExampleWeb, :html | |
attr :parent_form, Phoenix.HTML.Form, required: true | |
attr :field, Phoenix.HTML.FormField, required: true | |
attr :id, :string, required: true | |
attr :sort_param, :any, required: true | |
attr :drop_param, :any, required: true | |
attr :rest, :global, include: ~w(as default prepend append skip_hidden options) | |
slot :inner_block, required: true | |
slot :add_button do | |
attr :class, :any | |
end | |
slot :drop_button do | |
attr :class, :any | |
end | |
def inputs_for_embeds(assigns) do | |
# slots can be multiple, but these two are only ever singletons | |
add_button_class = | |
case assigns.add_button do | |
[%{class: add_button_class}] -> | |
add_button_class | |
_ -> | |
nil | |
end | |
drop_button_class = | |
case assigns.drop_button do | |
[%{class: drop_button_class}] -> | |
drop_button_class | |
_ -> | |
nil | |
end | |
assigns = | |
assigns | |
|> assign(:add_button_class, add_button_class) | |
|> assign(:drop_button_class, drop_button_class) | |
~H""" | |
<.inputs_for :let={embed_form} field={@field}> | |
<.sort_field form={@parent_form} index={embed_form.index} sort_param={@sort_param} /> | |
<div class="flex gap-2"> | |
<%= render_slot(@inner_block, embed_form) %> | |
<.drop_button | |
form={@parent_form} | |
index={embed_form.index} | |
drop_param={@drop_param} | |
class={[ | |
"place-self-end", | |
@drop_button_class | |
]} | |
> | |
<%= render_slot(@drop_button) %> | |
</.drop_button> | |
</div> | |
</.inputs_for> | |
<.drop_field form={@parent_form} drop_param={@drop_param} /> | |
<.add_button form={@parent_form} sort_param={@sort_param} class={@add_button_class}> | |
<%= render_slot(@add_button) %> | |
</.add_button> | |
""" | |
end | |
attr :form, Phoenix.HTML.Form, required: true | |
attr :sort_param, :any, required: true | |
attr :index, :integer, required: true | |
def sort_field(assigns) do | |
~H""" | |
<input type="hidden" name={"#{@form.name}[#{@sort_param}][]"} value={@index} /> | |
""" | |
end | |
attr :form, Phoenix.HTML.Form, required: true | |
attr :drop_param, :any, required: true | |
def drop_field(assigns) do | |
~H""" | |
<input type="hidden" name={"#{@form.name}[#{@drop_param}][]"} /> | |
""" | |
end | |
attr :class, :any, default: nil | |
attr :form, Phoenix.HTML.Form, required: true | |
attr :sort_param, :any, required: true | |
attr :on_click, Phoenix.LiveView.JS, default: JS.dispatch("change") | |
slot :inner_block, required: true | |
def add_button(assigns) do | |
~H""" | |
<.button | |
class={@class} | |
type="button" | |
name={"#{@form.name}[#{@sort_param}][]"} | |
value="new" | |
phx-click={@on_click} | |
> | |
<%= render_slot(@inner_block) %> | |
</.button> | |
""" | |
end | |
attr :class, :any, default: nil | |
attr :form, Phoenix.HTML.Form, required: true | |
attr :index, :integer, required: true | |
attr :drop_param, :any, required: true | |
attr :on_click, Phoenix.LiveView.JS, default: JS.dispatch("change") | |
slot :inner_block, required: false | |
def drop_button(assigns) do | |
~H""" | |
<.button | |
class={@class} | |
type="button" | |
name={"#{@form.name}[#{@drop_param}][]"} | |
value={@index} | |
phx-click={@on_click} | |
> | |
<%= if @inner_block == [] do %> | |
<.icon name="hero-x-mark" class="w-6 h-6 relative top-2" /> | |
<% else %> | |
<%= render_slot(@inner_block) %> | |
<% end %> | |
</.button> | |
""" | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment