Allow defcomponent to work with runtime values for assigns
Also allows tags and defcomponents to accept maps in addition to keyword lists
This commit is contained in:
parent
6c8246fe89
commit
8daf85fdb3
6 changed files with 99 additions and 21 deletions
|
@ -2,7 +2,7 @@ defmodule Mix.Tasks.Temple.Gen.Html do
|
|||
@shortdoc "Generates controller, views, and context for an HTML resource in Temple"
|
||||
|
||||
@moduledoc """
|
||||
Generates controller, views, and context for an HTML resource Temple.
|
||||
Generates controller, views, and context for an HTML resource in Temple.
|
||||
|
||||
mix temple.gen.html Accounts User users name:string age:integer
|
||||
|
||||
|
|
|
@ -115,7 +115,7 @@ defmodule Temple do
|
|||
|
||||
## Assigns
|
||||
|
||||
Components accept a keyword list of assigns and can be referenced in the body of the component by a module attribute of the same name.
|
||||
Components accept a keyword list or a map of assigns and can be referenced in the body of the component by a module attribute of the same name.
|
||||
|
||||
This works exactly the same as EEx templates.
|
||||
|
||||
|
@ -149,28 +149,41 @@ defmodule Temple do
|
|||
"""
|
||||
defmacro defcomponent(name, [do: _] = block) do
|
||||
quote do
|
||||
defmacro unquote(name)(props \\ []) do
|
||||
defmacro unquote(name)() do
|
||||
outer = unquote(Macro.escape(block))
|
||||
name = unquote(name)
|
||||
|
||||
{inner, props} = Keyword.pop(props, :do, nil)
|
||||
|
||||
quote do
|
||||
unquote(name)(unquote(props), unquote(inner))
|
||||
_ = unquote(outer)
|
||||
end
|
||||
end
|
||||
|
||||
defmacro unquote(name)(props_or_block)
|
||||
|
||||
defmacro unquote(name)([{:do, inner}]) do
|
||||
name = unquote(name)
|
||||
|
||||
quote do
|
||||
unquote(name)([], unquote(inner))
|
||||
end
|
||||
end
|
||||
|
||||
defmacro unquote(name)(props) do
|
||||
name = unquote(name)
|
||||
|
||||
quote do
|
||||
unquote(name)(unquote(props), nil)
|
||||
end
|
||||
end
|
||||
|
||||
defmacro unquote(name)(props, inner) do
|
||||
import Kernel, except: [div: 2]
|
||||
|
||||
outer =
|
||||
unquote(Macro.escape(block))
|
||||
|> Macro.prewalk(&Temple.Utils.insert_props(&1, [{:children, inner} | props]))
|
||||
|> Macro.prewalk(&Temple.Utils.insert_props(&1, props, inner))
|
||||
|
||||
name = unquote(name)
|
||||
|
||||
quote do
|
||||
unquote(outer)
|
||||
_ = unquote(outer)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -6,7 +6,7 @@ defmodule Temple.Tags do
|
|||
|
||||
## Attributes
|
||||
|
||||
Tags accept a keyword list of attributes to be emitted into the element's opening tag. Multi-word attribute keys written in snake_case (`data_url`) will be transformed into kebab-case (`data-url`).
|
||||
Tags accept a keyword list or a map of attributes to be emitted into the element's opening tag. Multi-word attribute keys written in snake_case (`data_url`) will be transformed into kebab-case (`data-url`).
|
||||
|
||||
## Children
|
||||
|
||||
|
@ -21,8 +21,11 @@ defmodule Temple.Tags do
|
|||
# empty non-void element
|
||||
div()
|
||||
|
||||
# non-void element with attributes
|
||||
# non-void element with keyword list attributes
|
||||
div class: "text-red", id: "my-el"
|
||||
#
|
||||
# non-void element with map attributes
|
||||
div %{:class => "text-red", "id" => "my-el"}
|
||||
|
||||
# non-void element with children
|
||||
div do
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
defmodule Temple.Utils do
|
||||
@moduledoc false
|
||||
|
||||
def put_open_tag(buff, el, attrs) when is_list(attrs) do
|
||||
def put_open_tag(buff, el, attrs) when is_list(attrs) or is_map(attrs) do
|
||||
put_buffer(buff, "<#{el}#{compile_attrs(attrs)}>")
|
||||
end
|
||||
|
||||
|
@ -23,17 +23,23 @@ defmodule Temple.Utils do
|
|||
partial |> Phoenix.HTML.html_escape() |> Phoenix.HTML.safe_to_string()
|
||||
end
|
||||
|
||||
def insert_props({:@, _, [{name, _, _}]}, props) when is_atom(name) do
|
||||
props[name]
|
||||
def insert_props({:@, _, [{:children, _, _}]}, _, inner) do
|
||||
inner
|
||||
end
|
||||
|
||||
def insert_props(ast, _inner), do: ast
|
||||
def insert_props({:@, _, [{name, _, _}]}, props, _) when is_atom(name) do
|
||||
quote do
|
||||
Access.get(unquote_splicing([props, name]))
|
||||
end
|
||||
end
|
||||
|
||||
def insert_props(ast, _, _), do: ast
|
||||
|
||||
def compile_attrs([]), do: ""
|
||||
|
||||
def compile_attrs(attrs) do
|
||||
for {name, value} <- attrs, into: "" do
|
||||
name = name |> Atom.to_string() |> String.replace("_", "-")
|
||||
name = name |> to_string() |> String.replace("_", "-")
|
||||
|
||||
" " <> name <> "=\"" <> to_string(value) <> "\""
|
||||
end
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
defmodule Temple.HtmlTest do
|
||||
defmodule Temple.TagsTest do
|
||||
use ExUnit.Case, async: true
|
||||
use Temple
|
||||
|
||||
|
@ -101,6 +101,17 @@ defmodule Temple.HtmlTest do
|
|||
assert result == ~s{<div class="hello"><div class="hi"></div></div>}
|
||||
end
|
||||
|
||||
test "renders an attribute passed in as a map on a div" do
|
||||
{:safe, result} =
|
||||
htm do
|
||||
div %{class: "hello"} do
|
||||
div %{"class" => "hi"}
|
||||
end
|
||||
end
|
||||
|
||||
assert result == ~s{<div class="hello"><div class="hi"></div></div>}
|
||||
end
|
||||
|
||||
test "renders an attribute on a div passed as a variable" do
|
||||
attrs1 = [class: "hello"]
|
||||
attrs2 = [class: "hi"]
|
|
@ -104,8 +104,7 @@ defmodule TempleTest do
|
|||
variable_as_prop(bob: bob)
|
||||
end
|
||||
|
||||
assert result ==
|
||||
~s|<div id="hi"></div>|
|
||||
assert result == ~s|<div id="hi"></div>|
|
||||
end
|
||||
|
||||
test "can pass a variable as a prop to a component with a block" do
|
||||
|
@ -122,5 +121,51 @@ defmodule TempleTest do
|
|||
|
||||
assert result == ~s|<div id="hi"><div></div></div>|
|
||||
end
|
||||
|
||||
test "can pass all of the props as a variable" do
|
||||
import Component
|
||||
|
||||
props = [bob: "hi"]
|
||||
|
||||
{:safe, result} =
|
||||
htm do
|
||||
variable_as_prop(props)
|
||||
end
|
||||
|
||||
assert result == ~s|<div id="hi"></div>|
|
||||
end
|
||||
|
||||
test "can pass all of the props as a variable with a block" do
|
||||
import Component
|
||||
|
||||
props = [bob: "hi"]
|
||||
|
||||
{:safe, result} =
|
||||
htm do
|
||||
variable_as_prop_with_block props do
|
||||
div()
|
||||
end
|
||||
end
|
||||
|
||||
assert result == ~s|<div id="hi"><div></div></div>|
|
||||
end
|
||||
|
||||
test "can pass a map as props with a block" do
|
||||
import Component
|
||||
|
||||
props = %{bob: "hi"}
|
||||
|
||||
{:safe, result} =
|
||||
htm do
|
||||
variable_as_prop_with_block props do
|
||||
div()
|
||||
end
|
||||
variable_as_prop_with_block %{bob: "hi"} do
|
||||
div()
|
||||
end
|
||||
end
|
||||
|
||||
assert result == ~s|<div id="hi"><div></div></div><div id="hi"><div></div></div>|
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
Reference in a new issue