Context/LiveView generator
This commit is contained in:
parent
a3ec57344a
commit
4498eabedb
13 changed files with 628 additions and 2 deletions
18
CHANGELOG.md
18
CHANGELOG.md
|
@ -2,6 +2,24 @@
|
||||||
|
|
||||||
## Master
|
## Master
|
||||||
|
|
||||||
|
## 0.6.0-alpha.1
|
||||||
|
|
||||||
|
### Generators
|
||||||
|
|
||||||
|
You can now use `mix temple.gen.live Context Schema table_name col:type` in the same way you can with Phoenix.
|
||||||
|
|
||||||
|
### Other
|
||||||
|
|
||||||
|
- Make a note in the README to set the filetype for Live temple templates to `lexs`. You should be able to set this extension to use Elixir for syntax highlighting in your editor. In vim, you can add the following to your `.vimrc`
|
||||||
|
|
||||||
|
```vim
|
||||||
|
augroup elixir
|
||||||
|
autocmd!
|
||||||
|
|
||||||
|
autocmd BufRead,BufNewFile *.lexs set filetype=elixir
|
||||||
|
augroup END
|
||||||
|
```
|
||||||
|
|
||||||
## 0.6.0-alpha.0
|
## 0.6.0-alpha.0
|
||||||
|
|
||||||
### Breaking!
|
### Breaking!
|
||||||
|
|
|
@ -65,7 +65,9 @@ Add the templating engine to your Phoenix configuration.
|
||||||
config :phoenix, :template_engines,
|
config :phoenix, :template_engines,
|
||||||
exs: Temple.Engine
|
exs: Temple.Engine
|
||||||
# or for LiveView support
|
# or for LiveView support
|
||||||
exs: Temple.LiveViewEngine
|
# this will work for files named like `index.html.lexs`
|
||||||
|
# you can enable Elixir syntax highlighting in your editor
|
||||||
|
lexs: Temple.LiveViewEngine
|
||||||
|
|
||||||
# config/dev.exs
|
# config/dev.exs
|
||||||
config :your_app, YourAppWeb.Endpoint,
|
config :your_app, YourAppWeb.Endpoint,
|
||||||
|
|
|
@ -207,7 +207,7 @@ if Code.ensure_loaded?(Mix.Phoenix) do
|
||||||
if context.generate?, do: Gen.Context.print_shell_instructions(context)
|
if context.generate?, do: Gen.Context.print_shell_instructions(context)
|
||||||
end
|
end
|
||||||
|
|
||||||
defp inputs(%Schema{} = schema) do
|
def inputs(%Schema{} = schema) do
|
||||||
Enum.map(schema.attrs, fn
|
Enum.map(schema.attrs, fn
|
||||||
{_, {:references, _}} ->
|
{_, {:references, _}} ->
|
||||||
{nil, nil, nil}
|
{nil, nil, nil}
|
||||||
|
|
254
lib/mix/tasks/temple.gen.live.ex
Normal file
254
lib/mix/tasks/temple.gen.live.ex
Normal file
|
@ -0,0 +1,254 @@
|
||||||
|
if Code.ensure_loaded?(Mix.Phoenix) do
|
||||||
|
defmodule Mix.Tasks.Temple.Gen.Live do
|
||||||
|
@shortdoc "Generates LiveView, templates, and context for a resource"
|
||||||
|
|
||||||
|
@moduledoc """
|
||||||
|
Generates LiveView, templates, and context for a resource.
|
||||||
|
|
||||||
|
mix temple.gen.live Accounts User users name:string age:integer
|
||||||
|
|
||||||
|
The first argument is the context module followed by the schema module
|
||||||
|
and its plural name (used as the schema table name).
|
||||||
|
|
||||||
|
The context is an Elixir module that serves as an API boundary for
|
||||||
|
the given resource. A context often holds many related resources.
|
||||||
|
Therefore, if the context already exists, it will be augmented with
|
||||||
|
functions for the given resource.
|
||||||
|
|
||||||
|
When this command is run for the first time, a `ModalComponent` and
|
||||||
|
`LiveHelpers` module will be created, along with the resource level
|
||||||
|
LiveViews and components, including an `IndexLive`, `ShowLive`, `FormComponent`
|
||||||
|
for the new resource.
|
||||||
|
|
||||||
|
> Note: A resource may also be split
|
||||||
|
> over distinct contexts (such as `Accounts.User` and `Payments.User`).
|
||||||
|
|
||||||
|
The schema is responsible for mapping the database fields into an
|
||||||
|
Elixir struct. It is followed by an optional list of attributes,
|
||||||
|
with their respective names and types. See `mix phx.gen.schema`
|
||||||
|
for more information on attributes.
|
||||||
|
|
||||||
|
Overall, this generator will add the following files to `lib/`:
|
||||||
|
|
||||||
|
* a context module in `lib/app/accounts.ex` for the accounts API
|
||||||
|
* a schema in `lib/app/accounts/user.ex`, with an `users` table
|
||||||
|
* a view in `lib/app_web/views/user_view.ex`
|
||||||
|
* a LiveView in `lib/app_web/live/user_live/show_live.ex`
|
||||||
|
* a LiveView in `lib/app_web/live/user_live/index_live.ex`
|
||||||
|
* a LiveComponent in `lib/app_web/live/user_live/form_component.ex`
|
||||||
|
* a LiveComponent in `lib/app_web/live/modal_component.ex`
|
||||||
|
* a helpers modules in `lib/app_web/live/live_helpers.ex`
|
||||||
|
|
||||||
|
## The context app
|
||||||
|
|
||||||
|
A migration file for the repository and test files for the context and
|
||||||
|
controller features will also be generated.
|
||||||
|
|
||||||
|
The location of the web files (LiveView's, views, templates, etc) in an
|
||||||
|
umbrella application will vary based on the `:context_app` config located
|
||||||
|
in your applications `:generators` configuration. When set, the Phoenix
|
||||||
|
generators will generate web files directly in your lib and test folders
|
||||||
|
since the application is assumed to be isolated to web specific functionality.
|
||||||
|
If `:context_app` is not set, the generators will place web related lib
|
||||||
|
and test files in a `web/` directory since the application is assumed
|
||||||
|
to be handling both web and domain specific functionality.
|
||||||
|
Example configuration:
|
||||||
|
|
||||||
|
config :my_app_web, :generators, context_app: :my_app
|
||||||
|
|
||||||
|
Alternatively, the `--context-app` option may be supplied to the generator:
|
||||||
|
|
||||||
|
mix temple.gen.live Sales User users --context-app warehouse
|
||||||
|
|
||||||
|
## Web namespace
|
||||||
|
|
||||||
|
By default, the controller and view will be namespaced by the schema name.
|
||||||
|
You can customize the web module namespace by passing the `--web` flag with a
|
||||||
|
module name, for example:
|
||||||
|
|
||||||
|
mix temple.gen.live Sales User users --web Sales
|
||||||
|
|
||||||
|
Which would generate a LiveViews inside `lib/app_web/live/sales/user_live/` and a
|
||||||
|
view at `lib/app_web/views/sales/user_view.ex`.
|
||||||
|
|
||||||
|
## Customising the context, schema, tables and migrations
|
||||||
|
|
||||||
|
In some cases, you may wish to bootstrap HTML templates, LiveViews,
|
||||||
|
and tests, but leave internal implementation of the context or schema
|
||||||
|
to yourself. You can use the `--no-context` and `--no-schema` flags
|
||||||
|
for file generation control.
|
||||||
|
|
||||||
|
You can also change the table name or configure the migrations to
|
||||||
|
use binary ids for primary keys, see `mix phx.gen.schema` for more
|
||||||
|
information.
|
||||||
|
"""
|
||||||
|
use Mix.Task
|
||||||
|
|
||||||
|
alias Mix.Phoenix.{Context}
|
||||||
|
alias Mix.Tasks.Phx.Gen
|
||||||
|
|
||||||
|
@doc false
|
||||||
|
def run(args) do
|
||||||
|
if Mix.Project.umbrella?() do
|
||||||
|
Mix.raise("mix temple.gen.live can only be run inside an application directory")
|
||||||
|
end
|
||||||
|
|
||||||
|
{context, schema} = Gen.Context.build(args)
|
||||||
|
Gen.Context.prompt_for_code_injection(context)
|
||||||
|
|
||||||
|
binding = [
|
||||||
|
context: context,
|
||||||
|
schema: schema,
|
||||||
|
inputs: Mix.Tasks.Temple.Gen.Html.inputs(schema)
|
||||||
|
]
|
||||||
|
|
||||||
|
paths = [".", :temple]
|
||||||
|
|
||||||
|
prompt_for_conflicts(context)
|
||||||
|
|
||||||
|
context
|
||||||
|
|> copy_new_files(binding, paths)
|
||||||
|
|> maybe_inject_helpers()
|
||||||
|
|> print_shell_instructions()
|
||||||
|
end
|
||||||
|
|
||||||
|
defp prompt_for_conflicts(context) do
|
||||||
|
context
|
||||||
|
|> files_to_be_generated()
|
||||||
|
|> Kernel.++(context_files(context))
|
||||||
|
|> Mix.Phoenix.prompt_for_conflicts()
|
||||||
|
end
|
||||||
|
|
||||||
|
defp context_files(%Context{generate?: true} = context) do
|
||||||
|
Gen.Context.files_to_be_generated(context)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp context_files(%Context{generate?: false}) do
|
||||||
|
[]
|
||||||
|
end
|
||||||
|
|
||||||
|
defp files_to_be_generated(%Context{schema: schema, context_app: context_app}) do
|
||||||
|
web_prefix = Mix.Phoenix.web_path(context_app)
|
||||||
|
test_prefix = Mix.Phoenix.web_test_path(context_app)
|
||||||
|
web_path = to_string(schema.web_path)
|
||||||
|
live_subdir = "#{schema.singular}_live"
|
||||||
|
|
||||||
|
[
|
||||||
|
{:eex, "show.ex", Path.join([web_prefix, "live", web_path, live_subdir, "show.ex"])},
|
||||||
|
{:eex, "index.ex", Path.join([web_prefix, "live", web_path, live_subdir, "index.ex"])},
|
||||||
|
{:eex, "form_component.ex",
|
||||||
|
Path.join([web_prefix, "live", web_path, live_subdir, "form_component.ex"])},
|
||||||
|
{:eex, "form_component.html.lexs",
|
||||||
|
Path.join([web_prefix, "live", web_path, live_subdir, "form_component.html.lexs"])},
|
||||||
|
{:eex, "index.html.lexs",
|
||||||
|
Path.join([web_prefix, "live", web_path, live_subdir, "index.html.lexs"])},
|
||||||
|
{:eex, "show.html.lexs",
|
||||||
|
Path.join([web_prefix, "live", web_path, live_subdir, "show.html.lexs"])},
|
||||||
|
{:eex, "live_test.exs",
|
||||||
|
Path.join([test_prefix, "live", web_path, "#{schema.singular}_live_test.exs"])},
|
||||||
|
{:new_eex, "modal_component.ex", Path.join([web_prefix, "live", "modal_component.ex"])},
|
||||||
|
{:new_eex, "live_helpers.ex", Path.join([web_prefix, "live", "live_helpers.ex"])}
|
||||||
|
]
|
||||||
|
end
|
||||||
|
|
||||||
|
defp copy_new_files(%Context{} = context, binding, paths) do
|
||||||
|
files = files_to_be_generated(context) |> IO.inspect(label: "FILES")
|
||||||
|
|
||||||
|
Mix.Phoenix.copy_from(
|
||||||
|
paths |> IO.inspect(label: "PATHS"),
|
||||||
|
"priv/templates/temple.gen.live",
|
||||||
|
binding,
|
||||||
|
files
|
||||||
|
)
|
||||||
|
|
||||||
|
if context.generate?,
|
||||||
|
do: Gen.Context.copy_new_files(context, Mix.Phoenix.generator_paths(), binding)
|
||||||
|
|
||||||
|
context
|
||||||
|
end
|
||||||
|
|
||||||
|
defp maybe_inject_helpers(%Context{context_app: ctx_app} = context) do
|
||||||
|
web_prefix = Mix.Phoenix.web_path(ctx_app)
|
||||||
|
[lib_prefix, web_dir] = Path.split(web_prefix)
|
||||||
|
file_path = Path.join(lib_prefix, "#{web_dir}.ex")
|
||||||
|
file = File.read!(file_path)
|
||||||
|
inject = "import #{inspect(context.web_module)}.LiveHelpers"
|
||||||
|
|
||||||
|
if String.contains?(file, inject) do
|
||||||
|
:ok
|
||||||
|
else
|
||||||
|
do_inject_helpers(context, file, file_path, inject)
|
||||||
|
end
|
||||||
|
|
||||||
|
context
|
||||||
|
end
|
||||||
|
|
||||||
|
defp do_inject_helpers(context, file, file_path, inject) do
|
||||||
|
Mix.shell().info([:green, "* injecting ", :reset, Path.relative_to_cwd(file_path)])
|
||||||
|
|
||||||
|
new_file =
|
||||||
|
String.replace(
|
||||||
|
file,
|
||||||
|
"import Phoenix.LiveView.Helpers",
|
||||||
|
"import Phoenix.LiveView.Helpers\n #{inject}"
|
||||||
|
)
|
||||||
|
|
||||||
|
if file != new_file do
|
||||||
|
File.write!(file_path, new_file)
|
||||||
|
else
|
||||||
|
Mix.shell().info("""
|
||||||
|
|
||||||
|
Could not find Phoenix.LiveView.Helpers imported in #{file_path}.
|
||||||
|
|
||||||
|
This typically happens because your application was not generated
|
||||||
|
with the --live flag:
|
||||||
|
|
||||||
|
mix temple.new my_app --live
|
||||||
|
|
||||||
|
Please make sure LiveView is installed and that #{inspect(context.web_module)}
|
||||||
|
defines both `live_view/0` and `live_component/0` functions,
|
||||||
|
and that both functions import #{inspect(context.web_module)}.LiveHelpers.
|
||||||
|
""")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc false
|
||||||
|
def print_shell_instructions(%Context{schema: schema, context_app: ctx_app} = context) do
|
||||||
|
prefix = Module.concat(context.web_module, schema.web_namespace)
|
||||||
|
web_path = Mix.Phoenix.web_path(ctx_app)
|
||||||
|
|
||||||
|
if schema.web_namespace do
|
||||||
|
Mix.shell().info("""
|
||||||
|
|
||||||
|
Add the live routes to your #{schema.web_namespace} :browser scope in #{web_path}/router.ex:
|
||||||
|
|
||||||
|
scope "/#{schema.web_path}", #{inspect(prefix)}, as: :#{schema.web_path} do
|
||||||
|
pipe_through :browser
|
||||||
|
...
|
||||||
|
|
||||||
|
#{for line <- live_route_instructions(schema), do: " #{line}"}
|
||||||
|
end
|
||||||
|
""")
|
||||||
|
else
|
||||||
|
Mix.shell().info("""
|
||||||
|
|
||||||
|
Add the live routes to your browser scope in #{Mix.Phoenix.web_path(ctx_app)}/router.ex:
|
||||||
|
|
||||||
|
#{for line <- live_route_instructions(schema), do: " #{line}"}
|
||||||
|
""")
|
||||||
|
end
|
||||||
|
|
||||||
|
if context.generate?, do: Gen.Context.print_shell_instructions(context)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp live_route_instructions(schema) do
|
||||||
|
[
|
||||||
|
~s|live "/#{schema.plural}", #{inspect(schema.alias)}Live.Index, :index\n|,
|
||||||
|
~s|live "/#{schema.plural}/new", #{inspect(schema.alias)}Live.Index, :new\n|,
|
||||||
|
~s|live "/#{schema.plural}/:id/edit", #{inspect(schema.alias)}Live.Index, :edit\n\n|,
|
||||||
|
~s|live "/#{schema.plural}/:id", #{inspect(schema.alias)}Live.Show, :show\n|,
|
||||||
|
~s|live "/#{schema.plural}/:id/show/edit", #{inspect(schema.alias)}Live.Show, :edit|
|
||||||
|
]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
55
priv/templates/temple.gen.live/form_component.ex
Normal file
55
priv/templates/temple.gen.live/form_component.ex
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
defmodule <%= inspect context.web_module %>.<%= inspect Module.concat(schema.web_namespace, schema.alias) %>Live.FormComponent do
|
||||||
|
use <%= inspect context.web_module %>, :live_component
|
||||||
|
|
||||||
|
alias <%= inspect context.module %>
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def update(%{<%= schema.singular %>: <%= schema.singular %>} = assigns, socket) do
|
||||||
|
changeset = <%= inspect context.alias %>.change_<%= schema.singular %>(<%= schema.singular %>)
|
||||||
|
|
||||||
|
{:ok,
|
||||||
|
socket
|
||||||
|
|> assign(assigns)
|
||||||
|
|> assign(:changeset, changeset)}
|
||||||
|
end
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def handle_event("validate", %{"<%= schema.singular %>" => <%= schema.singular %>_params}, socket) do
|
||||||
|
changeset =
|
||||||
|
socket.assigns.<%= schema.singular %>
|
||||||
|
|> <%= inspect context.alias %>.change_<%= schema.singular %>(<%= schema.singular %>_params)
|
||||||
|
|> Map.put(:action, :validate)
|
||||||
|
|
||||||
|
{:noreply, assign(socket, :changeset, changeset)}
|
||||||
|
end
|
||||||
|
|
||||||
|
def handle_event("save", %{"<%= schema.singular %>" => <%= schema.singular %>_params}, socket) do
|
||||||
|
save_<%= schema.singular %>(socket, socket.assigns.action, <%= schema.singular %>_params)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp save_<%= schema.singular %>(socket, :edit, <%= schema.singular %>_params) do
|
||||||
|
case <%= inspect context.alias %>.update_<%= schema.singular %>(socket.assigns.<%= schema.singular %>, <%= schema.singular %>_params) do
|
||||||
|
{:ok, _<%= schema.singular %>} ->
|
||||||
|
{:noreply,
|
||||||
|
socket
|
||||||
|
|> put_flash(:info, "<%= schema.human_singular %> updated successfully")
|
||||||
|
|> push_redirect(to: socket.assigns.return_to)}
|
||||||
|
|
||||||
|
{:error, %Ecto.Changeset{} = changeset} ->
|
||||||
|
{:noreply, assign(socket, :changeset, changeset)}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp save_<%= schema.singular %>(socket, :new, <%= schema.singular %>_params) do
|
||||||
|
case <%= inspect context.alias %>.create_<%= schema.singular %>(<%= schema.singular %>_params) do
|
||||||
|
{:ok, _<%= schema.singular %>} ->
|
||||||
|
{:noreply,
|
||||||
|
socket
|
||||||
|
|> put_flash(:info, "<%= schema.human_singular %> created successfully")
|
||||||
|
|> push_redirect(to: socket.assigns.return_to)}
|
||||||
|
|
||||||
|
{:error, %Ecto.Changeset{} = changeset} ->
|
||||||
|
{:noreply, assign(socket, changeset: changeset)}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
13
priv/templates/temple.gen.live/form_component.html.lexs
Normal file
13
priv/templates/temple.gen.live/form_component.html.lexs
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
h2 do: @title
|
||||||
|
|
||||||
|
f = form_for @changeset, "#", id: "<%= schema.singular %>-form",
|
||||||
|
phx_target: @myself,
|
||||||
|
phx_change: "validate",
|
||||||
|
phx_submit: "save"
|
||||||
|
<%= for {label, input, error} <- inputs, input do %>
|
||||||
|
<%= label %>
|
||||||
|
<%= input %>
|
||||||
|
<%= error %>
|
||||||
|
<% end %>
|
||||||
|
submit "Save", phx_disable_with: "Saving..."
|
||||||
|
"</form>"
|
46
priv/templates/temple.gen.live/index.ex
Normal file
46
priv/templates/temple.gen.live/index.ex
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
defmodule <%= inspect context.web_module %>.<%= inspect Module.concat(schema.web_namespace, schema.alias) %>Live.Index do
|
||||||
|
use <%= inspect context.web_module %>, :live_view
|
||||||
|
|
||||||
|
alias <%= inspect context.module %>
|
||||||
|
alias <%= inspect schema.module %>
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def mount(_params, _session, socket) do
|
||||||
|
{:ok, assign(socket, :<%= schema.collection %>, list_<%= schema.plural %>())}
|
||||||
|
end
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def handle_params(params, _url, socket) do
|
||||||
|
{:noreply, apply_action(socket, socket.assigns.live_action, params)}
|
||||||
|
end
|
||||||
|
|
||||||
|
defp apply_action(socket, :edit, %{"id" => id}) do
|
||||||
|
socket
|
||||||
|
|> assign(:page_title, "Edit <%= schema.human_singular %>")
|
||||||
|
|> assign(:<%= schema.singular %>, <%= inspect context.alias %>.get_<%= schema.singular %>!(id))
|
||||||
|
end
|
||||||
|
|
||||||
|
defp apply_action(socket, :new, _params) do
|
||||||
|
socket
|
||||||
|
|> assign(:page_title, "New <%= schema.human_singular %>")
|
||||||
|
|> assign(:<%= schema.singular %>, %<%= inspect schema.alias %>{})
|
||||||
|
end
|
||||||
|
|
||||||
|
defp apply_action(socket, :index, _params) do
|
||||||
|
socket
|
||||||
|
|> assign(:page_title, "Listing <%= schema.human_plural %>")
|
||||||
|
|> assign(:<%= schema.singular %>, nil)
|
||||||
|
end
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def handle_event("delete", %{"id" => id}, socket) do
|
||||||
|
<%= schema.singular %> = <%= inspect context.alias %>.get_<%= schema.singular %>!(id)
|
||||||
|
{:ok, _} = <%= inspect context.alias %>.delete_<%= schema.singular %>(<%= schema.singular %>)
|
||||||
|
|
||||||
|
{:noreply, assign(socket, :<%= schema.collection %>, list_<%=schema.plural %>())}
|
||||||
|
end
|
||||||
|
|
||||||
|
defp list_<%= schema.plural %> do
|
||||||
|
<%= inspect context.alias %>.list_<%= schema.plural %>()
|
||||||
|
end
|
||||||
|
end
|
35
priv/templates/temple.gen.live/index.html.lexs
Normal file
35
priv/templates/temple.gen.live/index.html.lexs
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
h1 do: "Listing <%= schema.human_plural %>"
|
||||||
|
|
||||||
|
if @live_action in [:new, :edit] do
|
||||||
|
live_modal @socket, <%= inspect context.web_module %>.<%= inspect Module.concat(schema.web_namespace, schema.alias) %>Live.FormComponent,
|
||||||
|
id: @<%= schema.singular %>.id || :new,
|
||||||
|
title: @page_title,
|
||||||
|
action: @live_action,
|
||||||
|
<%= schema.singular %>: @<%= schema.singular %>,
|
||||||
|
return_to: Routes.<%= schema.route_helper %>_index_path(@socket, :index)
|
||||||
|
end
|
||||||
|
|
||||||
|
table do
|
||||||
|
thead do
|
||||||
|
tr do
|
||||||
|
<%= for {k, _} <- schema.attrs do %> th do: "<%= Phoenix.Naming.humanize(Atom.to_string(k)) %>"
|
||||||
|
<% end %>
|
||||||
|
th
|
||||||
|
end
|
||||||
|
end
|
||||||
|
tbody id: "<%= schema.plural %>" do
|
||||||
|
for <%= schema.singular %> <- @<%= schema.collection %> do
|
||||||
|
tr id: "<%= schema.singular %>-<%%= <%= schema.singular %>.id %>" do
|
||||||
|
<%= for {k, _} <- schema.attrs do %> td do: <%= schema.singular %>.<%= k %>
|
||||||
|
<% end %>
|
||||||
|
td do
|
||||||
|
span do: live_redirect "Show", to: Routes.<%= schema.route_helper %>_show_path(@socket, :show, <%= schema.singular %>)
|
||||||
|
span do: live_patch "Edit", to: Routes.<%= schema.route_helper %>_index_path(@socket, :edit, <%= schema.singular %>)
|
||||||
|
span do: link "Delete", to: "#", phx_click: "delete", phx_value_id: <%= schema.singular %>.id, data: [confirm: "Are you sure?"]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
span do: live_patch "New <%= schema.human_singular %>", to: Routes.<%= schema.route_helper %>_index_path(@socket, :new)
|
23
priv/templates/temple.gen.live/live_helpers.ex
Normal file
23
priv/templates/temple.gen.live/live_helpers.ex
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
defmodule <%= inspect context.web_module %>.LiveHelpers do
|
||||||
|
import Phoenix.LiveView.Helpers
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Renders a component inside the `<%= inspect context.web_module %>.ModalComponent` component.
|
||||||
|
|
||||||
|
The rendered modal receives a `:return_to` option to properly update
|
||||||
|
the URL when the modal is closed.
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
<%%= live_modal @socket, <%= inspect context.web_module %>.<%= inspect Module.concat(schema.web_namespace, schema.alias) %>Live.FormComponent,
|
||||||
|
id: @<%= schema.singular %>.id || :new,
|
||||||
|
action: @live_action,
|
||||||
|
<%= schema.singular %>: @<%= schema.singular %>,
|
||||||
|
return_to: Routes.<%= schema.singular %>_index_path(@socket, :index) %>
|
||||||
|
"""
|
||||||
|
def live_modal(socket, component, opts) do
|
||||||
|
path = Keyword.fetch!(opts, :return_to)
|
||||||
|
modal_opts = [id: :modal, return_to: path, component: component, opts: opts]
|
||||||
|
live_component(socket, <%= inspect context.web_module %>.ModalComponent, modal_opts)
|
||||||
|
end
|
||||||
|
end
|
110
priv/templates/temple.gen.live/live_test.exs
Normal file
110
priv/templates/temple.gen.live/live_test.exs
Normal file
|
@ -0,0 +1,110 @@
|
||||||
|
defmodule <%= inspect context.web_module %>.<%= inspect Module.concat(schema.web_namespace, schema.alias) %>LiveTest do
|
||||||
|
use <%= inspect context.web_module %>.ConnCase
|
||||||
|
|
||||||
|
import Phoenix.LiveViewTest
|
||||||
|
import <%= inspect context.module %>Fixtures
|
||||||
|
|
||||||
|
@create_attrs <%= inspect schema.params.create %>
|
||||||
|
@update_attrs <%= inspect schema.params.update %>
|
||||||
|
@invalid_attrs <%= inspect for {key, _} <- schema.params.create, into: %{}, do: {key, nil} %>
|
||||||
|
|
||||||
|
defp create_<%= schema.singular %>(_) do
|
||||||
|
<%= schema.singular %> = <%= schema.singular %>_fixture()
|
||||||
|
%{<%= schema.singular %>: <%= schema.singular %>}
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "Index" do
|
||||||
|
setup [:create_<%= schema.singular %>]
|
||||||
|
|
||||||
|
test "lists all <%= schema.plural %>", %{conn: conn, <%= schema.singular %>: <%= schema.singular %>} do
|
||||||
|
{:ok, _index_live, html} = live(conn, Routes.<%= schema.route_helper %>_index_path(conn, :index))
|
||||||
|
|
||||||
|
assert html =~ "Listing <%= schema.human_plural %>"<%= if schema.string_attr do %>
|
||||||
|
assert html =~ <%= schema.singular %>.<%= schema.string_attr %><% end %>
|
||||||
|
end
|
||||||
|
|
||||||
|
test "saves new <%= schema.singular %>", %{conn: conn} do
|
||||||
|
{:ok, index_live, _html} = live(conn, Routes.<%= schema.route_helper %>_index_path(conn, :index))
|
||||||
|
|
||||||
|
assert index_live |> element("a", "New <%= schema.human_singular %>") |> render_click() =~
|
||||||
|
"New <%= schema.human_singular %>"
|
||||||
|
|
||||||
|
assert_patch(index_live, Routes.<%= schema.route_helper %>_index_path(conn, :new))
|
||||||
|
|
||||||
|
assert index_live
|
||||||
|
|> form("#<%= schema.singular %>-form", <%= schema.singular %>: @invalid_attrs)
|
||||||
|
|> render_change() =~ "can't be blank"
|
||||||
|
|
||||||
|
{:ok, _, html} =
|
||||||
|
index_live
|
||||||
|
|> form("#<%= schema.singular %>-form", <%= schema.singular %>: @create_attrs)
|
||||||
|
|> render_submit()
|
||||||
|
|> follow_redirect(conn, Routes.<%= schema.route_helper %>_index_path(conn, :index))
|
||||||
|
|
||||||
|
assert html =~ "<%= schema.human_singular %> created successfully"<%= if schema.string_attr do %>
|
||||||
|
assert html =~ "some <%= schema.string_attr %>"<% end %>
|
||||||
|
end
|
||||||
|
|
||||||
|
test "updates <%= schema.singular %> in listing", %{conn: conn, <%= schema.singular %>: <%= schema.singular %>} do
|
||||||
|
{:ok, index_live, _html} = live(conn, Routes.<%= schema.route_helper %>_index_path(conn, :index))
|
||||||
|
|
||||||
|
assert index_live |> element("#<%= schema.singular %>-#{<%= schema.singular %>.id} a", "Edit") |> render_click() =~
|
||||||
|
"Edit <%= schema.human_singular %>"
|
||||||
|
|
||||||
|
assert_patch(index_live, Routes.<%= schema.route_helper %>_index_path(conn, :edit, <%= schema.singular %>))
|
||||||
|
|
||||||
|
assert index_live
|
||||||
|
|> form("#<%= schema.singular %>-form", <%= schema.singular %>: @invalid_attrs)
|
||||||
|
|> render_change() =~ "can't be blank"
|
||||||
|
|
||||||
|
{:ok, _, html} =
|
||||||
|
index_live
|
||||||
|
|> form("#<%= schema.singular %>-form", <%= schema.singular %>: @update_attrs)
|
||||||
|
|> render_submit()
|
||||||
|
|> follow_redirect(conn, Routes.<%= schema.route_helper %>_index_path(conn, :index))
|
||||||
|
|
||||||
|
assert html =~ "<%= schema.human_singular %> updated successfully"<%= if schema.string_attr do %>
|
||||||
|
assert html =~ "some updated <%= schema.string_attr %>"<% end %>
|
||||||
|
end
|
||||||
|
|
||||||
|
test "deletes <%= schema.singular %> in listing", %{conn: conn, <%= schema.singular %>: <%= schema.singular %>} do
|
||||||
|
{:ok, index_live, _html} = live(conn, Routes.<%= schema.route_helper %>_index_path(conn, :index))
|
||||||
|
|
||||||
|
assert index_live |> element("#<%= schema.singular %>-#{<%= schema.singular %>.id} a", "Delete") |> render_click()
|
||||||
|
refute has_element?(index_live, "#<%= schema.singular %>-#{<%= schema.singular %>.id}")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "Show" do
|
||||||
|
setup [:create_<%= schema.singular %>]
|
||||||
|
|
||||||
|
test "displays <%= schema.singular %>", %{conn: conn, <%= schema.singular %>: <%= schema.singular %>} do
|
||||||
|
{:ok, _show_live, html} = live(conn, Routes.<%= schema.route_helper %>_show_path(conn, :show, <%= schema.singular %>))
|
||||||
|
|
||||||
|
assert html =~ "Show <%= schema.human_singular %>"<%= if schema.string_attr do %>
|
||||||
|
assert html =~ <%= schema.singular %>.<%= schema.string_attr %><% end %>
|
||||||
|
end
|
||||||
|
|
||||||
|
test "updates <%= schema.singular %> within modal", %{conn: conn, <%= schema.singular %>: <%= schema.singular %>} do
|
||||||
|
{:ok, show_live, _html} = live(conn, Routes.<%= schema.route_helper %>_show_path(conn, :show, <%= schema.singular %>))
|
||||||
|
|
||||||
|
assert show_live |> element("a", "Edit") |> render_click() =~
|
||||||
|
"Edit <%= schema.human_singular %>"
|
||||||
|
|
||||||
|
assert_patch(show_live, Routes.<%= schema.route_helper %>_show_path(conn, :edit, <%= schema.singular %>))
|
||||||
|
|
||||||
|
assert show_live
|
||||||
|
|> form("#<%= schema.singular %>-form", <%= schema.singular %>: @invalid_attrs)
|
||||||
|
|> render_change() =~ "can't be blank"
|
||||||
|
|
||||||
|
{:ok, _, html} =
|
||||||
|
show_live
|
||||||
|
|> form("#<%= schema.singular %>-form", <%= schema.singular %>: @update_attrs)
|
||||||
|
|> render_submit()
|
||||||
|
|> follow_redirect(conn, Routes.<%= schema.route_helper %>_show_path(conn, :show, <%= schema.singular %>))
|
||||||
|
|
||||||
|
assert html =~ "<%= schema.human_singular %> updated successfully"<%= if schema.string_attr do %>
|
||||||
|
assert html =~ "some updated <%= schema.string_attr %>"<% end %>
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
27
priv/templates/temple.gen.live/modal_component.ex
Normal file
27
priv/templates/temple.gen.live/modal_component.ex
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
defmodule <%= inspect context.web_module %>.ModalComponent do
|
||||||
|
use <%= inspect context.web_module %>, :live_component
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def render(assigns) do
|
||||||
|
live_temple do
|
||||||
|
div id: @id,
|
||||||
|
class: "phx-modal",
|
||||||
|
phx_capture_click: "close",
|
||||||
|
phx_window_keydown: "close",
|
||||||
|
phx_key: "escape",
|
||||||
|
phx_target: "##{@id}",
|
||||||
|
phx_page_loading: true do
|
||||||
|
|
||||||
|
div class: "phx-modal-content" do
|
||||||
|
live_patch raw("×"), to: @return_to, class: "phx-modal-close"
|
||||||
|
live_component @socket, @component, @opts
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def handle_event("close", _, socket) do
|
||||||
|
{:noreply, push_patch(socket, to: socket.assigns.return_to)}
|
||||||
|
end
|
||||||
|
end
|
21
priv/templates/temple.gen.live/show.ex
Normal file
21
priv/templates/temple.gen.live/show.ex
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
defmodule <%= inspect context.web_module %>.<%= inspect Module.concat(schema.web_namespace, schema.alias) %>Live.Show do
|
||||||
|
use <%= inspect context.web_module %>, :live_view
|
||||||
|
|
||||||
|
alias <%= inspect context.module %>
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def mount(_params, _session, socket) do
|
||||||
|
{:ok, socket}
|
||||||
|
end
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def handle_params(%{"id" => id}, _, socket) do
|
||||||
|
{:noreply,
|
||||||
|
socket
|
||||||
|
|> assign(:page_title, page_title(socket.assigns.live_action))
|
||||||
|
|> assign(:<%= schema.singular %>, <%= inspect context.alias %>.get_<%= schema.singular %>!(id))}
|
||||||
|
end
|
||||||
|
|
||||||
|
defp page_title(:show), do: "Show <%= schema.human_singular %>"
|
||||||
|
defp page_title(:edit), do: "Edit <%= schema.human_singular %>"
|
||||||
|
end
|
22
priv/templates/temple.gen.live/show.html.lexs
Normal file
22
priv/templates/temple.gen.live/show.html.lexs
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
h1 do: "Show <%= schema.human_singular %>"
|
||||||
|
|
||||||
|
if @live_action in [:edit] do
|
||||||
|
live_modal @socket, <%= inspect context.web_module %>.<%= inspect Module.concat(schema.web_namespace, schema.alias) %>Live.FormComponent,
|
||||||
|
id: @<%= schema.singular %>.id,
|
||||||
|
title: @page_title,
|
||||||
|
action: @live_action,
|
||||||
|
<%= schema.singular %>: @<%= schema.singular %>,
|
||||||
|
return_to: Routes.<%= schema.route_helper %>_show_path(@socket, :show, @<%= schema.singular %>)
|
||||||
|
end
|
||||||
|
|
||||||
|
ul do
|
||||||
|
<%= for {k, _} <- schema.attrs do %>
|
||||||
|
li do
|
||||||
|
strong do: "<%= Phoenix.Naming.humanize(Atom.to_string(k)) %>:"
|
||||||
|
@<%= schema.singular %>.<%= k %>
|
||||||
|
end
|
||||||
|
<% end %>
|
||||||
|
end
|
||||||
|
|
||||||
|
span do: live_patch "Edit", to: Routes.<%= schema.route_helper %>_show_path(@socket, :edit, @<%= schema.singular %>), class: "button"
|
||||||
|
span do: live_redirect "Back", to: Routes.<%= schema.route_helper %>_index_path(@socket, :index)
|
Reference in a new issue