Current section

Files

Jump to
caravela priv templates live_form.eex
Raw

priv/templates/live_form.eex

defmodule <%= inspect @module %> do
@moduledoc """
Generated by Caravela from <%= inspect @domain_module %>.
LiveView that mounts the `<%= @component_ref %>` Svelte component via
LiveSvelte. Handles both create (no `id` param) and edit
(`?id=<uuid>`) flows, validation round-trips, and persistence.
Regenerate with `mix caravela.gen.live <%= inspect @domain_module %>`.
Custom code placed below the `# --- CUSTOM ---` marker is preserved.
"""
use <%= @web_module_alias %>, :live_view
alias <%= inspect @context_module %>
alias <%= inspect @entity_module %>
@impl true
def mount(params, _session, socket) do
context = build_context(socket)
{entity, attrs} = load_initial(params, context)
changeset = <%= @context_short %>.<%= @change_fn %>(entity, attrs)
{:ok,
socket
|> assign(:context, context)
|> assign(:<%= @singular %>, entity)
|> assign(:attrs, attrs)
|> assign(:changeset, changeset)
|> assign(:errors, changeset_errors(changeset))
|> assign(:saving, false)
|> assign(:field_access, <%= @context_short %>.field_access(<%= inspect @entity_name %>, context))
|> assign(:flash_message, nil)}
end
# --- CUSTOM :mount_<%= @singular %>_form ---
# --- END :mount_<%= @singular %>_form ---
@impl true
def handle_event("validate", %{"field" => field, "value" => value}, socket) do
key = String.to_existing_atom(field)
attrs = Map.put(socket.assigns.attrs, key, value)
changeset =
socket.assigns.<%= @singular %>
|> <%= @context_short %>.<%= @change_fn %>(attrs)
{:noreply,
socket
|> assign(:attrs, attrs)
|> assign(:changeset, changeset)
|> assign(:errors, changeset_errors(changeset))}
end
def handle_event("save", _params, %{assigns: %{<%= @singular %>: entity}} = socket) do
context = socket.assigns.context
attrs = socket.assigns.attrs
socket = assign(socket, :saving, true)
result =
if new?(entity) do
<%= @context_short %>.<%= @create_fn %>(attrs, context)
else
<%= @context_short %>.<%= @update_fn %>(entity, attrs, context)
end
case result do
{:ok, _saved} ->
{:noreply,
socket
|> assign(:saving, false)
|> push_navigate(to: "<%= @index_path %>")}
{:error, :unauthorized} ->
{:noreply,
socket
|> assign(:saving, false)
|> assign(:flash_message, "Not authorized")}
{:error, %Ecto.Changeset{} = cs} ->
{:noreply,
socket
|> assign(:saving, false)
|> assign(:changeset, cs)
|> assign(:errors, changeset_errors(cs))}
{:error, reason} ->
{:noreply,
socket
|> assign(:saving, false)
|> assign(:flash_message, "Error: " <> inspect(reason))}
end
end
def handle_event("cancel", _params, socket) do
{:noreply, push_navigate(socket, to: "<%= @index_path %>")}
end
# --- CUSTOM :handle_event_<%= @singular %>_form ---
# --- END :handle_event_<%= @singular %>_form ---
@impl true
def render(assigns) do
~H"""
<LiveSvelte.svelte
name="<%= @component_ref %>"
props={%{
<%= @singular %>: @attrs,
errors: @errors,
saving: @saving,
flash_message: @flash_message,
field_access: @field_access
}}
socket={@socket}
/>
"""
end
# --- Internal --------------------------------------------------------
defp load_initial(%{"id" => id}, context) do
case <%= @context_short %>.<%= @get_fn %>(id, context) do
nil -> {%<%= @entity_short %>{}, %{}}
entity -> {entity, entity_attrs(entity)}
end
end
defp load_initial(_params, _context), do: {%<%= @entity_short %>{}, %{}}
# Narrow the attrs map to the declared entity fields and normalise
# struct values to wire-friendly primitives so the Svelte form inputs
# don't receive `%Decimal{}` / `%DateTime{}` dumps.
defp entity_attrs(entity) do
<%= inspect @attrs_fields %>
|> Enum.reduce(%{}, fn field, acc ->
Map.put(acc, field, normalise_attr(Map.get(entity, field)))
end)
end
<%= if @decimal_fields != [] do %>
defp normalise_attr(%Decimal{} = d), do: Decimal.to_string(d, :normal)<% end %>
defp normalise_attr(other), do: other
defp new?(%<%= @entity_short %>{id: nil}), do: true
defp new?(%<%= @entity_short %>{}), do: false
defp changeset_errors(%Ecto.Changeset{} = cs) do
Ecto.Changeset.traverse_errors(cs, fn {msg, opts} ->
[
Enum.reduce(opts, msg, fn {k, v}, acc ->
String.replace(acc, "%{" <> Atom.to_string(k) <> "}", to_string(v))
end)
]
end)
end
defp build_context(socket) do
%{
current_user: socket.assigns[:current_user]<%= if @multi_tenant do %>,
tenant: socket.assigns[:tenant]<% end %>
}
end
<%= @custom_marker %>