Module based Component API
This commit is contained in:
parent
113a75a7eb
commit
7be82e003f
18 changed files with 134 additions and 42 deletions
|
@ -2,6 +2,10 @@
|
|||
|
||||
## Master
|
||||
|
||||
### Breaking
|
||||
|
||||
Components are now defined using modules. You can convert your existing components by configuring your component prefix and wrapping your current component files in the `Temple.Component` behaviour implementation.
|
||||
|
||||
### Bugs
|
||||
|
||||
- Did not correctly parse expressions with do blocks where the expression had two or more arguments before the block
|
||||
|
|
28
README.md
28
README.md
|
@ -58,28 +58,32 @@ end
|
|||
|
||||
### Components
|
||||
|
||||
To define a component, you can create a file in your configured temple
|
||||
components directory, which defaults to `lib/components`. You would
|
||||
probably want to change that to be `lib/my_app_web/components` if you
|
||||
are building a phoenix app.
|
||||
To define a component, you can define a module that that starts with your defined components prefix. The last name in the module should be a came-cases version of the component name.
|
||||
|
||||
This module should implement the `Temple.Component` behaviour.
|
||||
|
||||
```elixir
|
||||
# config/config.exs
|
||||
|
||||
config :temple, :components_path, "./lib/my_app_web/components"
|
||||
config :temple, :components_prefix, MyAppWeb.Components
|
||||
```
|
||||
|
||||
This file should be of the `.exs` extension.
|
||||
|
||||
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.
|
||||
For example, if I were to define a `flex` component, I would create the following module.
|
||||
|
||||
```elixir
|
||||
div class: "flex #{@temple[:class]}", id: @id do
|
||||
@children
|
||||
defmodule MyAppWeb.Components.Flex do
|
||||
@behavior Temple.Component
|
||||
|
||||
@impl Temple.Component
|
||||
def render do
|
||||
quote do
|
||||
div class: "flex #{@temple[:class]}", id: @id do
|
||||
@children
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
use Mix.Config
|
||||
|
||||
config :temple, :component_prefix, Temple.Components
|
||||
import_config "#{Mix.env()}.exs"
|
||||
|
|
|
@ -12,8 +12,7 @@ config :elixir, :time_zone_database, Tzdata.TimeZoneDatabase
|
|||
config :temple_demo,
|
||||
ecto_repos: [TempleDemo.Repo]
|
||||
|
||||
config :phoenix, :template_engines,
|
||||
exs: Temple.Engine
|
||||
config :phoenix, :template_engines, exs: Temple.Engine
|
||||
|
||||
# Configures the endpoint
|
||||
config :temple_demo, TempleDemoWeb.Endpoint,
|
||||
|
@ -31,11 +30,13 @@ config :logger, :console,
|
|||
# Use Jason for JSON parsing in Phoenix
|
||||
config :phoenix, :json_library, Jason
|
||||
|
||||
config :temple, :aliases,
|
||||
label: :_label,
|
||||
link: :_link,
|
||||
textarea: :_textarea
|
||||
|
||||
config :temple,
|
||||
aliases: [
|
||||
label: :_label,
|
||||
link: :_link,
|
||||
textarea: :_textarea
|
||||
],
|
||||
component_prefix: TempleDemoWeb.Component
|
||||
|
||||
# Import environment specific config. This must remain at the bottom
|
||||
# of this file so it overrides the configuration defined above.
|
||||
|
|
23
lib/temple/component.ex
Normal file
23
lib/temple/component.ex
Normal file
|
@ -0,0 +1,23 @@
|
|||
defmodule Temple.Component do
|
||||
@moduledoc """
|
||||
Behaviour for defining temple components.
|
||||
"""
|
||||
|
||||
@doc """
|
||||
The render callback must return AST to be inserted into the markup.
|
||||
|
||||
Since you need to return AST, it is typical to wrap the contents of the function in a `quote` block like:
|
||||
|
||||
```
|
||||
@impl Temple.Component
|
||||
def render() do
|
||||
quote do
|
||||
div do
|
||||
@children
|
||||
end
|
||||
end
|
||||
end
|
||||
```
|
||||
"""
|
||||
@callback render() :: Macro.t()
|
||||
end
|
|
@ -1,15 +1,20 @@
|
|||
defmodule Temple.Parser.Components do
|
||||
@behaviour Temple.Parser
|
||||
@components_path Application.get_env(:temple, :components_path, "./lib/components")
|
||||
@component_prefix Application.fetch_env!(:temple, :component_prefix)
|
||||
|
||||
alias Temple.Parser
|
||||
|
||||
def applicable?({name, meta, _}) when is_atom(name) do
|
||||
!meta[:temple_component_applied] && File.exists?(Path.join([@components_path, "#{name}.exs"]))
|
||||
!meta[:temple_component_applied] &&
|
||||
match?({:module, _}, name |> component_module() |> Code.ensure_compiled())
|
||||
end
|
||||
|
||||
def applicable?(_), do: false
|
||||
|
||||
defp component_module(name) do
|
||||
Module.concat([@component_prefix, Macro.camelize(to_string(name))])
|
||||
end
|
||||
|
||||
def run({name, _meta, args}, _buffer) do
|
||||
{assigns, children} =
|
||||
case args do
|
||||
|
@ -26,9 +31,9 @@ defmodule Temple.Parser.Components do
|
|||
{nil, nil}
|
||||
end
|
||||
|
||||
ast =
|
||||
File.read!(Path.join([@components_path, "#{name}.exs"]))
|
||||
|> Code.string_to_quoted!()
|
||||
component_module = Module.concat([@component_prefix, Macro.camelize(to_string(name))])
|
||||
|
||||
ast = apply(component_module, :render, [])
|
||||
|
||||
{name, meta, args} =
|
||||
ast
|
||||
|
|
12
test/support/components/component.ex
Normal file
12
test/support/components/component.ex
Normal file
|
@ -0,0 +1,12 @@
|
|||
defmodule Temple.Components.Component do
|
||||
@behaviour Temple.Component
|
||||
|
||||
@impl Temple.Component
|
||||
def render do
|
||||
quote do
|
||||
div class: @assign do
|
||||
@children
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,3 +0,0 @@
|
|||
div class: @assign do
|
||||
@children
|
||||
end
|
12
test/support/components/component2.ex
Normal file
12
test/support/components/component2.ex
Normal file
|
@ -0,0 +1,12 @@
|
|||
defmodule Temple.Components.Component2 do
|
||||
@behaviour Temple.Component
|
||||
|
||||
@impl Temple.Component
|
||||
def render do
|
||||
quote do
|
||||
div class: @class do
|
||||
@children
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,3 +0,0 @@
|
|||
div class: @class do
|
||||
@children
|
||||
end
|
12
test/support/components/has_temple.ex
Normal file
12
test/support/components/has_temple.ex
Normal file
|
@ -0,0 +1,12 @@
|
|||
defmodule Temple.Components.HasTemple do
|
||||
@behaviour Temple.Component
|
||||
|
||||
@impl Temple.Component
|
||||
def render do
|
||||
quote do
|
||||
div class: @temple[:class] do
|
||||
@children
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,3 +0,0 @@
|
|||
div class: @temple[:class] do
|
||||
@children
|
||||
end
|
12
test/support/components/inner.ex
Normal file
12
test/support/components/inner.ex
Normal file
|
@ -0,0 +1,12 @@
|
|||
defmodule Temple.Components.Inner do
|
||||
@behaviour Temple.Component
|
||||
|
||||
@impl Temple.Component
|
||||
def render do
|
||||
quote do
|
||||
div id: "inner", outer_id: @outer_id do
|
||||
@children
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,3 +0,0 @@
|
|||
div id: "inner", outer_id: @outer_id do
|
||||
@children
|
||||
end
|
12
test/support/components/outer.ex
Normal file
12
test/support/components/outer.ex
Normal file
|
@ -0,0 +1,12 @@
|
|||
defmodule Temple.Components.Outer do
|
||||
@behaviour Temple.Component
|
||||
|
||||
@impl Temple.Component
|
||||
def render do
|
||||
quote do
|
||||
inner outer_id: "from-outer" do
|
||||
@children
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,3 +0,0 @@
|
|||
inner outer_id: "from-outer" do
|
||||
@children
|
||||
end
|
12
test/support/components/section.ex
Normal file
12
test/support/components/section.ex
Normal file
|
@ -0,0 +1,12 @@
|
|||
defmodule Temple.Components.Section do
|
||||
@behaviour Temple.Component
|
||||
|
||||
@impl Temple.Component
|
||||
def render do
|
||||
quote do
|
||||
section class: "foo!" do
|
||||
@children
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,3 +0,0 @@
|
|||
section class: "foo!" do
|
||||
@children
|
||||
end
|
Reference in a new issue