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
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(:flash_message, nil)}
end
@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
@impl true
def render(assigns) do
~H"""
<LiveSvelte.render
name="<%= @component_ref %>"
props={%{
<%= @singular %>: @attrs,
errors: @errors,
saving: @saving,
flash_message: @flash_message
}}
/>
"""
end
# --- Internal --------------------------------------------------------
defp load_initial(%{"id" => id}, context) do
case <%= @context_short %>.<%= @get_fn %>(id, context) do
nil -> {%<%= @entity_short %>{}, %{}}
entity -> {entity, Map.from_struct(entity) |> Map.drop([:__meta__])}
end
end
defp load_initial(_params, _context), do: {%<%= @entity_short %>{}, %{}}
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 %>