New tools, and a new guide on nested forms with `AshPhoenix.Form`!

Working with nested forms in Ash was already great, but it’s even better now with a the new features that will be in the next release of AshPhoenix! Use hidden checkboxes to automatically add, remove and reorder nested forms!

Also check out the new guide. Inspired by Phoenix & Ecto’s drop_param and sort_param, but with no need to configure a drop_param or sort_param, all automatically handled by AshPhoenix.Form

Example code:

defmodule MyApp.MyForm do
  use MyAppWeb, :live_view

  def render(assigns) do
    ~H"""
    <.simple_form for={@form} phx-change="validate" phx-submit="submit">
      <.input field={@form[:email]} />

      <!-- Use sortable.js to allow sorting nested input -->
      <div id="location-list" phx-hook="Sortable">
        <.inputs_for :let={location} field={@form[:locations]}>
          <!-- inputs each nested location -->
          <div data-sortable="true">
            <!-- AshPhoenix.Form automatically applies this sort -->
            <input
              type="hidden"
              name={"#{@form.name}[_sort_locations][]"}
              value={location_form.index}
            />

            <.input field={location[:name]} />

            <!-- AshPhoenix.Form automatically removes items when checked -->
            <label>
              <input
                type="checkbox"
                name={"#{@form.name}[_drop_locations][]"}
                value={location_form.index}
                class="hidden"
              />

              <.icon name="hero-x-mark" />
            </label>
          </div>
        </.inputs_for>

        <!-- AshPhoenix.Form automatically appends a new item when checked -->
        <label>
          <input
            type="checkbox"
            name={"#{@form.name}[_add_locations]"}
            value="end"
            class="hidden"
          />
          <.icon name="hero-plus" />
        </label>
      </div>
    </.form>
    """
  end

  def mount(_params, _session, socket) do
    {:ok, assign(socket, form: MyApp.Operations.form_to_create_business())}
  end

  def handle_event(socket, "validate", %{"form" => params}) do
    {:noreply, assign(socket, :form, AshPhoenix.Form.validate(socket.assigns.form, params))}
  end

  def handle_event(socket, "submit", %{"form" => params}) do
    case AshPhoenix.Form.submit(socket.assigns.form, params: params) do
      {:ok, business} ->
        socket =
          socket
          |> put_flash(:success, "Business created successfully")
          |> push_navigate(to: ~p"/businesses/#{business.id}")

        {:noreply, socket}

      {:error, form} ->
        {:noreply, assign(socket, :form, form)}
    end
  end
end

It also comes with very useful tools for manually managing nested forms, when checkboxes just won’t cut it :sunglasses:

def handle_event("add-form", %{"path" => path}, socket) do
  form = AshPhoenix.Form.add_form(socket.assigns.form, path, params: %{
    address: "Put your address here!"
  })

  {:noreply, assign(socket, :form, form)}
end

def handle_event("remove-form", %{"path" => path}, socket) do
  form = AshPhoenix.Form.remove_form(socket.assigns.form, path)

  {:noreply, assign(socket, :form, form)}
end


def handle_event("update-sorting", %{"path" => path, "indices" => indices}, socket) do
  form = AshPhoenix.Form.sort_forms(socket, path, indices)
  {:noreply, assign(socket, form: form)}
end

def handle_event("move-up", %{"path" => form_to_move}, socket) do
  # decrement typically means "move up" visually
  # because forms are rendered down the page ascending
  form = AshPhoenix.Form.sort_forms(socket, form_to_move, :decrement)
  {:noreply, assign(socket, form: form)}
end

def handle_event("move-down", %{"path" => form_to_move}, socket) do
  # increment typically means "move down" visually
  # because forms are rendered down the page ascending
  form = AshPhoenix.Form.sort_forms(socket, form_to_move, :increment)
  {:noreply, assign(socket, form: form)}
end

:partying_face: :hugs: Happy Friday folks! :hugs:

14 Likes