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
|
## 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
|
### Bugs
|
||||||
|
|
||||||
- Did not correctly parse expressions with do blocks where the expression had two or more arguments before the block
|
- 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
|
### Components
|
||||||
|
|
||||||
To define a component, you can create a file in your configured temple
|
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.
|
||||||
components directory, which defaults to `lib/components`. You would
|
|
||||||
probably want to change that to be `lib/my_app_web/components` if you
|
This module should implement the `Temple.Component` behaviour.
|
||||||
are building a phoenix app.
|
|
||||||
|
|
||||||
```elixir
|
```elixir
|
||||||
# config/config.exs
|
# 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.
|
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
|
For example, if I were to define a `flex` component, I would create the following module.
|
||||||
file called `lib/my_app_web/components/flex.exs`, with the following
|
|
||||||
contents.
|
|
||||||
|
|
||||||
```elixir
|
```elixir
|
||||||
div class: "flex #{@temple[:class]}", id: @id do
|
defmodule MyAppWeb.Components.Flex do
|
||||||
@children
|
@behavior Temple.Component
|
||||||
|
|
||||||
|
@impl Temple.Component
|
||||||
|
def render do
|
||||||
|
quote do
|
||||||
|
div class: "flex #{@temple[:class]}", id: @id do
|
||||||
|
@children
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
use Mix.Config
|
use Mix.Config
|
||||||
|
|
||||||
|
config :temple, :component_prefix, Temple.Components
|
||||||
import_config "#{Mix.env()}.exs"
|
import_config "#{Mix.env()}.exs"
|
||||||
|
|
|
@ -12,8 +12,7 @@ config :elixir, :time_zone_database, Tzdata.TimeZoneDatabase
|
||||||
config :temple_demo,
|
config :temple_demo,
|
||||||
ecto_repos: [TempleDemo.Repo]
|
ecto_repos: [TempleDemo.Repo]
|
||||||
|
|
||||||
config :phoenix, :template_engines,
|
config :phoenix, :template_engines, exs: Temple.Engine
|
||||||
exs: Temple.Engine
|
|
||||||
|
|
||||||
# Configures the endpoint
|
# Configures the endpoint
|
||||||
config :temple_demo, TempleDemoWeb.Endpoint,
|
config :temple_demo, TempleDemoWeb.Endpoint,
|
||||||
|
@ -31,11 +30,13 @@ config :logger, :console,
|
||||||
# Use Jason for JSON parsing in Phoenix
|
# Use Jason for JSON parsing in Phoenix
|
||||||
config :phoenix, :json_library, Jason
|
config :phoenix, :json_library, Jason
|
||||||
|
|
||||||
config :temple, :aliases,
|
config :temple,
|
||||||
label: :_label,
|
aliases: [
|
||||||
link: :_link,
|
label: :_label,
|
||||||
textarea: :_textarea
|
link: :_link,
|
||||||
|
textarea: :_textarea
|
||||||
|
],
|
||||||
|
component_prefix: TempleDemoWeb.Component
|
||||||
|
|
||||||
# Import environment specific config. This must remain at the bottom
|
# Import environment specific config. This must remain at the bottom
|
||||||
# of this file so it overrides the configuration defined above.
|
# 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
|
defmodule Temple.Parser.Components do
|
||||||
@behaviour Temple.Parser
|
@behaviour Temple.Parser
|
||||||
@components_path Application.get_env(:temple, :components_path, "./lib/components")
|
@component_prefix Application.fetch_env!(:temple, :component_prefix)
|
||||||
|
|
||||||
alias Temple.Parser
|
alias Temple.Parser
|
||||||
|
|
||||||
def applicable?({name, meta, _}) when is_atom(name) do
|
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
|
end
|
||||||
|
|
||||||
def applicable?(_), do: false
|
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
|
def run({name, _meta, args}, _buffer) do
|
||||||
{assigns, children} =
|
{assigns, children} =
|
||||||
case args do
|
case args do
|
||||||
|
@ -26,9 +31,9 @@ defmodule Temple.Parser.Components do
|
||||||
{nil, nil}
|
{nil, nil}
|
||||||
end
|
end
|
||||||
|
|
||||||
ast =
|
component_module = Module.concat([@component_prefix, Macro.camelize(to_string(name))])
|
||||||
File.read!(Path.join([@components_path, "#{name}.exs"]))
|
|
||||||
|> Code.string_to_quoted!()
|
ast = apply(component_module, :render, [])
|
||||||
|
|
||||||
{name, meta, args} =
|
{name, meta, args} =
|
||||||
ast
|
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