Skip to content

Latest commit

 

History

History
262 lines (189 loc) · 7.41 KB

7-12_templating-with-selmer.asciidoc

File metadata and controls

262 lines (189 loc) · 7.41 KB

Templating with Selmer

by Dmitri Sotnikov

Problem

You want to create server-side page templates using syntax similar to that of Django and Jinja. You want to be able to insert dynamic content and use template inheritance to structure the templates.

Solution

Use the Selmer library to create your template and call it with a context map containing the dynamic content.

To follow along with this recipe, start a REPL using lein-try:

$ lein try selmer

A Selmer template is an HTML file with special tags that is populated with dynamic content at runtime. A simple template (base.html) might look like the following:

<!DOCTYPE html>
<html lang="en">
  <body>
    <header>

      <h1>{{header}}</h1>

      <ul id="navigation">

        {% for item in nav-items %}
        <li>
            <a href="{{item.link}}">{{item.name}}</a>
        </li>
        {% endfor %}

      </ul>
    </header>
  </body>
</html>

The template can then be rendered by calling the selmer.parser/render-file function:

(require '[selmer.parser :refer [render-file render]])

(println
  (render-file "base.html"
               {:header "Hello Selmer"
                :nav-items [{:name "Home" :link "/"}
                            {:name "About" :link "/about"}]}))

When render-file runs, it will populate the tags with the content provided. The value will be returned as a string, suitable for use as the response body in a Ring application (for example). Here the result is simply printed to standard output, for easy inspection.

We can apply filters to variables for additional post-processing at runtime. Here, we use the upper filter to convert our heading to uppercase:

<h1>{{header|upper}}</h1>

We can extract parts of the template into individual snippets using the include tag. For example, if we wanted to define the header in a separate file header.html:

<header>

  <h1>{{header}}</h1>

  <ul id="navigation">

    {% for item in nav-items %}
    <li>
        <a href="{{item.link}}">{{item.name}}</a>
    </li>
    {% endfor %}

  </ul>
</header>

we could then include it as follows:

<!DOCTYPE html>
<html lang="en">
  <body>

    {% include "header.html" %}

  </body>
</html>

The include tag will simply be replaced by the contents of the file it points to when the template is compiled.

We can also extend our base template when we create individual pages. To do that, we first define a block in our base template. This will serve as an anchor for the child template to override:

<!DOCTYPE html>
<html lang="en">
  <body>

    {% include "header.html" %}

    {% block content %}
    {% endblock %}

  </body>
</html>

The child template will reference the parent using the extends tag and define its own content for the content block:

{% extends "base.html" %}

{% block content %}

<h1>This is the home page of the site</h1>
<p>some exciting content follows</p>

{% endblock %}

Discussion

Selmer provides a powerful and familiar templating tool with many tags and filters for easily accomplishing many common tasks. It separates the view logic from presentation by design.

Selmer is also performant because it compiles the templates and ensures that only the dynamic content needs to be evaluated when serving a request.

Selmer concepts

Selmer includes two types of elements, variables and tags.

Variables are used to render values from the context map on the page. The {{ and }} are used to indicate the start and end of a variable.

In many cases, you may wish to post-process the value of a variable. For example, you might want to convert it to uppercase, pluralize it, or parse it as a date. Variable filters (described in the following subsection) are used for this purpose.

Tags are used to add various functionality to the template, such as looping and conditions. We already saw examples of the for, include, and extends tags. The tags use {% and %} to define their content.

The default tag characters might conflict with client-side frameworks such as AngularJS. In this case, we can specify custom tags by passing a map containing any of the following keys to the parser:

:tag-open
:tag-close
:filter-open
:filter-close
:tag-second
:custom-tags
:custom-filters

If we wanted to use [ and ] as our opening and closing tags, we could call the render function as follows:

(render (str "[% for ele in foo %] "
             "{{I'm not a tag, but the next one is}} [{ele}] [%endfor%]")
        {:foo [1 2 3]}
        {:tag-open \[
         :tag-close \]})

The render function works just like render-file, except that it accepts the template content as a string.

Defining filters

Selmer provides a rich set of filters that allow decorating of the dynamic content. Some of the filters include capitalize, pluralize, hash, length, and sort.

However, if you need a custom filter that’s not part of the library, you can trivially add one yourself. For example, if we wanted to parse Markdown using the markdown-clj library and display it on the page, we could write the following filter:[1]

(require '[markdown.core :refer [md-to-html-string]]
         '[selmer.filters :refer [add-filter!]])

(add-filter! :markdown md-to-html-string)

We can now use this filter in our templates to render our Markdown content:

<h2>Blog Posts</h2>
<ul>
  {% for post in posts %}
    <li>{{post.title|markdown|safe}}</li>
{% endfor %}
</ul>

Note that we had to chain the markdown filter with the safe filter. This is due to the fact that Selmer escapes variable content by default. We can change our filter definition to indicate that its content does not need escaping as follows:

(add-filter! :markdown (fn [s] [:safe (md-to-html-string s)]))
Defining tags

Again, we can define custom tags in addition to those already present in the library. This is done by calling the selmer.parser/add-tag! function.

Let’s say we wish to add a tag that will capitalize its contents:

(require '[selmer.parser :refer [add-tag!]])

(add-tag! :uppercase
          (fn [args context-map content]
            (.toUpperCase (get-in content [:uppercase :content])))
          :enduppercase)

(render "{% uppercase %}foo {{bar}} baz{% enduppercase %}" {:bar "injected"})
Inheritance

We already saw some examples of template inheritance. Each template can extend a single template and include any number of templates in its content.

The templates can extend templates that themselves extend other templates. In this case, the blocks found in the outermost child will override any other blocks with the same name.

See Also


1. You’ll need to restart a new REPL with lein-try including markdown-clj to try this.