Current section
Files
Jump to
Current section
Files
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
`caravela_svelte`. 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(:actions, <%= @context_short %>.action_access(<%= inspect @entity_name %>, entity, 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"""
<CaravelaSvelte.svelte
name="<%= @component_ref %>"
props={%{
<%= @singular %>: @attrs,
errors: @errors,
saving: @saving,
flash_message: @flash_message,
field_access: @field_access,
actions: @actions
}}
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
# Structured error shape shared across `:live` and `:rest` — see
# `Caravela.ChangesetTranslator`. Each error is a
# `%{code, params, message}` map; frontends key on `:code` for
# localization and fall back to `:message` when no translation
# exists. Configure a Gettext backend via
# `config :caravela, :changeset_translator, MyAppWeb.Gettext`.
defp changeset_errors(%Ecto.Changeset{} = cs),
do: Caravela.ChangesetTranslator.translate(cs)
defp build_context(socket) do
%{
current_user: socket.assigns[:current_user]<%= if @multi_tenant do %>,
tenant: socket.assigns[:tenant]<% end %>
}
end
<%= @custom_marker %>