Temple is a macro DSL for writing HTML markup in Elixir and a template engine for Phoenix.
Conventional template languages like EEx use a form of interpolation to embed a programming language into a markup language, which can result in some ugly code that can be difficult to write and debug.
Temple is written using pure elixir.
Let's take a look.
use Temple
@items ["eggs", "milk", "bacon"]
temple do
h2 "todos"
ul class: "list" do
for item <- @items do
li class: "item" do
div class: "checkbox" do
div class: "bullet hidden"
end
div item
end
end
end
script """
function toggleCheck({currentTarget}) {
currentTarget.children[0].children[0].classList.toggle("hidden");
}
let items = document.querySelectorAll("li");
Array.from(items).forEach(checkbox =>
checkbox.addEventListener("click", toggleCheck)
);
"""
end
Phoenix Template Engine
By implementing the Phoenix.Template.Engine
behaviour, Temple becomes a full fledged template engine.
# config/config.exs
config :phoenix, :template_engines, exs: Temple.Engine
# lib/blog_web.ex
def view do
quote do
# ...
use Temple
end
end
# lib/blog_web/templates/post/show.html.exs
h2 @post.title
div do
text "By #{Enum.join(@post.authors, ", ")}"
end
article do
text @post.body
end
aside do
for c <- @post.comments do
div c.name
div c.body
end
end
Phoenix.HTML
All of the Phoenix form and link helpers have been wrapped to be compatible with Temple.
The semantics and naming of some helpers have been change to work as macros and avoid name conflicts with standard HTML5 tags.
# takes a block and has access to the variable `form`
form_for @post, Routes.post_path(@conn, :create) do
# prefixed with `phx_` to avoid a conflict with the <label> tag
phx_label form, :title
text_input form, :title
phx_label form, :authors
multiple_select form, :authors, @users
phx_label form, :body
text_area form, :body
end
Components
Temple offers React-ish style API for extracting reusable components.
defcomponent
will define macros for you to use in your markup without them looking any different from normal tags.
# layout_view.ex
defmodule MyAppWeb.LayoutView do
use MyAppWeb, :view
defcomponent :nav_item do
div id: @id, class: "flex flex-col" do
div @name, class: "margin-bottom-2"
div @description
end
end
end
# app.html.exs
html do
head do
# stuff
end
body do
header do
nav do
for item <- @nav_items do
nav_item id: item.key,
name: item.name,
description: item.description
end
end
end
main role: "main", class: "container" do
p get_flash(@conn, :info), class: "alert alert-info", role: "alert"
p get_flash(@conn, :error), class: "alert alert-danger", role: "alert"
partial render(@view_module, @view_template, assigns)
end
end
end
Components can also take children if passed a block and are accessed via the @children
variable.
defcomponent :takes_children do
div class: "some-wrapping-class" do
@children
end
end
takes_children do
span do
text "child one"
end
span do
text "child two"
end
span do
text "child three"
end
end
# <div class="some-wrapping-class">
# <span>child one</span>
# <span>child two</span>
# <span>child three</span>
# </div>
Wrapping up
If you're interested in using Temple, you can install it from Hex and check it out on GitHub.
Let me know what you think!