1a5837d1b7
Components work very similarly to how they worked before, but with a few differences. To define a component, you can create a file in your configured temple components directory, which defaults to `lib/components`. You would probably want ot change that to be `lib/my_app_web/components` if you are building a phoenix app. This file should be of the `.exs` extension, and contain any temple compatible code. You can then use this component in any other temple template. For example, if I were to define a `flex` component, I would create a file called `lib/my_app_web/components/flex.exs`, with the following contents. ```elixir div class: "flex #{@temple[:class]}", id: @id do @children end ``` And we could use the component like so ```elixir flex class: "justify-between items-center", id: "arnold" do div do: "Hi" div do: "I'm" div do: "Arnold" div do: "Schwarzenegger" end ``` We've demonstated several features to components in this example. We can pass assigns to our component, and access them just like we would in a normal phoenix template. If they don't match up with any assigns we passed to our component, they will be rendered as-is, and will become a normal Phoenix assign. You can also access a special `@temple` assign. This allows you do optionally pass an assign, and not have the `@my_assign` pass through. If you didn't pass it to your component, it will evaluate to nil. The block passed to your component can be accessed as `@children`. This allows your components to wrap a body of markup from the call site. In order for components to trigger a recompile when they are changed, you can call `use Temple.Recompiler` in your `lib/my_app_web.ex` file, in the `view`, `live_view`, and `live_component` functions ```elixir def view do quote do # ... use Temple.Recompiler # ... end end ```
254 lines
9.2 KiB
Elixir
254 lines
9.2 KiB
Elixir
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)
|
|
|
|
Mix.Phoenix.copy_from(
|
|
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
|