Introducing Temple: An elegant HTML library for Elixir and Phoenix

Jul 12, 2019 2 minute read #elixir #temple #phoenix #programming
Table of Contents
  1. Phoenix Template Engine
  2. Phoenix.HTML
  3. Components
  4. Wrapping up

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 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!