# LiveView Form Page Template Copy-pasteable template for a new create/edit form page following TowerOps conventions. Replace all `__RESOURCE__` with PascalCase resource (e.g., `Schedule`). Replace `__resource__` with snake_case (e.g., `schedule`). Replace `__resources__` with plural snake_case (e.g., `schedules`). Replace `__CONTEXT__` with context module name (e.g., `Schedules`). Replace `__SCHEMA__` with schema module name (e.g., `Schedule`). --- ## `lib/towerops_web/live/__resource___live/form.ex` ```elixir defmodule ToweropsWeb.__RESOURCE__Live.Form do @moduledoc false use ToweropsWeb, :live_view alias Towerops.__CONTEXT__ alias Towerops.__CONTEXT__.__SCHEMA__ @impl true def mount(_params, _session, socket) do {:ok, socket |> assign(:timezone, socket.assigns.current_scope.timezone)} end @impl true def handle_params(params, _url, socket) do case socket.assigns.live_action do :new -> __resource__ = %__SCHEMA__{} changeset = __SCHEMA__.changeset(__resource__, %{}) {:noreply, socket |> assign(:page_title, t("New __RESOURCE_TITLE__")) |> assign(:__resource__, __resource__) |> assign(:form, to_form(changeset))} :edit -> __resource__ = __CONTEXT__.get___resource__!(params["id"]) changeset = __SCHEMA__.changeset(__resource__, %{}) {:noreply, socket |> assign(:page_title, t("Edit __RESOURCE_TITLE__")) |> assign(:__resource__, __resource__) |> assign(:form, to_form(changeset))} end end @impl true def handle_event("validate", %{"__resource__" => params}, socket) do changeset = socket.assigns.__resource__ |> __SCHEMA__.changeset(params) |> Map.put(:action, :validate) {:noreply, assign(socket, :form, to_form(changeset))} end def handle_event("save", %{"__resource__" => params}, socket) do scope = socket.assigns.current_scope params = params |> Map.put("organization_id", scope.organization.id) case socket.assigns.live_action do :new -> save___resource__(socket, :create, params) :edit -> save___resource__(socket, :update, params) end end def handle_event("delete", _params, socket) do case __CONTEXT__.delete___resource__(socket.assigns.__resource__) do {:ok, _} -> {:noreply, socket |> put_flash(:info, t("__RESOURCE_TITLE__ deleted")) |> push_navigate(to: ~p"/__resources__")} {:error, _changeset} -> {:noreply, put_flash(socket, :error, t("Unable to delete __resource__"))} end end defp save___resource__(socket, :create, params) do case __CONTEXT__.create___resource__(params) do {:ok, __resource__} -> {:noreply, socket |> put_flash(:info, t("__RESOURCE_TITLE__ created")) |> push_navigate(to: ~p"/__resources__/#{__resource__.id}")} {:error, changeset} -> {:noreply, assign(socket, :form, to_form(changeset))} end end defp save___resource__(socket, :update, params) do case __CONTEXT__.update___resource__(socket.assigns.__resource__, params) do {:ok, __resource__} -> {:noreply, socket |> put_flash(:info, t("__RESOURCE_TITLE__ updated")) |> push_navigate(to: ~p"/__resources__/#{__resource__.id}")} {:error, changeset} -> {:noreply, assign(socket, :form, to_form(changeset))} end end end ``` --- ## `lib/towerops_web/live/__resource___live/form.html.heex` ```heex <%!-- Back link / breadcrumb --%>
<.link navigate={~p"/__resources__"} class="inline-flex items-center gap-1 text-sm text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200" > <.icon name="hero-arrow-left" class="h-4 w-4" /> {t("Back to __RESOURCES_TITLE__")}

{@page_title}

<.form for={@form} phx-change="validate" phx-submit="save" id="__resource__-form" >
<%!-- Name field --%> <.input field={@form[:name]} type="text" label={t("Name") <> " *"} required placeholder={t("e.g., My __RESOURCE_TITLE__")} /> <%!-- Description / reason field --%> <.input field={@form[:description]} type="textarea" label={t("Description")} rows="3" placeholder={t("Optional description")} /> <%!-- Grid layout for related fields --%>
<.input field={@form[:starts_at]} type="datetime-local" label={t("Starts At") <> " *"} required /> <.input field={@form[:ends_at]} type="datetime-local" label={t("Ends At") <> " *"} required />
<%!-- Select field example --%> <%# <.input field={@form[:site_id]} type="select" label={t("Site")} prompt={t("Select a site...")} options={Enum.map(@sites, &{&1.name, &1.id})} /> #%> <%!-- Checkbox example --%> <.input field={@form[:enabled]} type="checkbox" label={t("Enabled")} /> <%!-- Submit / Cancel / Delete --%>
<%!-- Delete button (edit mode only) --%>
<%= if @live_action == :edit do %> <% end %>
<.link navigate={~p"/__resources__"} class="rounded-lg px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-100 dark:text-gray-300 dark:hover:bg-gray-800 transition-colors" > {t("Cancel")}
``` --- ## Router entries ```elixir # In lib/towerops_web/router.ex, inside the authenticated scope: live "/__resources__/new", __RESOURCE__Live.Form, :new live "/__resources__/:id/edit", __RESOURCE__Live.Form, :edit ```