SWAFViews (swaf_views v0.2.0)

SWAFViews is a template library dedicated to HTML generation. It only depends on EEx. It precompiles templates as functions for fast rendering and protects the rendered code from injection.

Getting started

SWAFViews scans a hierarchy of directories looking for .html.eex files. For every directory it traverses, it creates a module named after that directory. It finally generates a function named after the name of the template file (see below).

Install it as a dependency

To use SWAFViews in a project, first include it as a dependency in your preject's mix.exs file:

defp deps do
  [
    # if you want to play with the source code and contribute
    # {:swaf_views, path: "../swaf_views"},
    {:swaf_views, "~> 0.2"}
  ]
end

Define the root module for the templates

Then define an .ex file that will be the root for the module hierarchy of the templates. For exemple:

# file: lib/my_app/web/views.ex

defmodule MyApp.Web.Views do
  use SWAFViews,
  template_dir: "templates"
end

All the functions and modules will be injected below MyApp.Web.Views.

template_dir defines where the template are located. It is the only parameter and it is mandatory. It has to point to an existing directory.

With the previous configuration, the templates are in the templates directory at the root of the project.

Create a set of templates

Templates are .html.eex files with eex syntax (we'll get on the content of the template files below). They are located in a hierarchy of directories. Each sub-directory will generate a sub-module. Files need to end with .html.eex. Files and directories prefixed with _ won't be collected. See the compiler output below.

templates/
 index.html.eex
 _index2.html.eex
 partials
     footer.html.eex
     header.html.eex  
     header.html.eex~

With the hierarchy above, some of the functions that will be created are:

  • MyApp.Web.Views.index()
  • MyApp.Web.Views.Partials.header()
  • etc

Each function comes in two versions, func/0 and func/1. func/1 accepts a map that will be made available for the template in the assigns variable. func/0 is a convenience function where the parameter defaults to %{}.

Call of template functions in controller's action

defmodule MyApp.Web.Controller.PageController do
  alias MyApp.Web.Views
  require Logger
  alias Plug.Conn
  
  def index(conn) do
    params = conn.assigns
    |> Map.merge(%{params: conn.params})
    |> Map.merge(%{a: 4, b: 12})
    
    conn
    |> Conn.put_resp_content_type("text/html")
    |> Conn.send_resp(200, Views.index(params))    
  end
end

Compile the application and the templates

For the time being, when a new template is defined, you need to quit iex and run mix compile or iex -S mix. However, if an exeisting template is modified, yoiu can call recompile in the REPL.

$ iex -S mix
[... some output ...] 
==> my_app
Compiling 2 files (.ex)
-- Compiling templates from directory 'templates'
  ** Ignoring '"templates/partials/footer.html.eex~"' (not a .html.eex file)
  ** Ignoring '"templates/_index2.html.eex"' (underscored "_index2.html.eex")
  -- Compiling template from file 'partials/footer.html.eex'
  -- Compiling template from file 'partials/header.html.eex'
  -- Compiling template from file '/index.html.eex'
Generated my_app app

Generated template functions

As said previously, the compiler generates two versions of each function. For the template file, say, templates/index.html.eex, the compiler will generate:

  • MyApp.Web.Views.index/1: this function accepts a map which will be passed inside the template as the assigns parameter. In a controller, it can be called as:

    Conn.send_resp(200, Views.index(%{name: "Polo", title: "The title of the page"}))

    Then, inside the template, we can get the parameters with the usual @<key> syntax:

    Hi <%= @name %>
    <p/>
    The title of this page is <%= @title %>

    Notice that the assigns map is sanitized against XSS injection before it is passed to a template function.

  • MyApp.Web.Views.index/0: is function a convenience function which actually is equivalent to the call MyApp.Web.Views.index(%{})

Notice that the assigns map is sanitized against XSS injection before it is passed to a template function.

Content of the template files

The template files are .eex files so please refer the the EEx official documentation.

Few points to notice:

  • The syntax @some_key works if some_key is an atom in the assigns map. If a key is has some other type, say a binary, use <%= assigns["key"] %> syntax.

  • A template can be called from within another template. In this case the full name is required. For exemple:

    <%= MyApp.Web.Views.Partials.header(assigns) %>
    <center>
      This is the body of the page
    </center>
    <%= MyApp.Web.Views.Partials.footer(assigns) %>