Skip to content

Making an HTML Template

Brenton Ashworth edited this page Jun 18, 2013 · 9 revisions

Making an HTML Template

Working with the Data UI has allowed us to concentrate on application logic without having to worry about what things will look like in the real user interface. When building a real application we will often be referring to wireframes or mock ups so that we know what the application will actually do.

One of the key ideas of Pedestal is that it separates rendering from application logic. We can work on the two separately. In this tutorial we have waited to do the rendering work until after we finished the application logic. In a real project this work can be done in parallel.

In this section you will create a simple HTML template which will be used by your application. In the process you will be introduced to the following Pedestal concepts:

  • Static vs Dynamic Templates
  • Template Slicing

Organizing our work

With the development project running, click on the Design link in the tools menu or just go to:

http://localhost:3000/design.html

The purpose of this page is to provide links to all of the HTML templates for this project. Templates can be arranged in many different ways. You may want to have everything that you need for application in one page or you may want to make templates more closely reflect the pages of your application.

The file behind this page is located at

tools/public/design.html

You may add anything to this file. The changes shown below make the page a bit more readable.

<div class="section panel">
  <h2>Tutorial Templates</h2>
  <ul>
    <li><a href="/design/tutorial-client.html">Plain Counter Page</a></li>
  </ul>
</div>

Looking at the source for this page we are introduced to the first of two special tags which are used for combining HTML files

<_within file="tooling.html">
   <div>...</div>
</_within>

From the development tool, we can refer to this page as we did above.

http://localhost:3000/design.html

When the design.html file is loaded it will see the _within tag and will load the page tooling.html and put the content of design.html within tooling.html.

How does it know where to put the content? design.html has a top-level div with id content. tooling.html also has div with id content. The div#content in tooling.html is simply replaced with the div#content of design.html. Only the top-level elements in the _within section are inserted into the parent file.

There is also an _include tag which allows us to include the contents of one file into another.

It is very important to realize that _within and _include are only used at development time. They are not part of Pedestal's templating system. These tags are only recognized by the development server while templates are under development. As such, these tags are only meant to provide designers with a very simple way to not repeat themselves.

Creating a template

The source for our template is located in

app/templates/tutorial-client.html

We can get to it by loading:

http://localhost:3000/design/tutorial-client.html

Below is the complete source for the updated template. Most of this is standard HTML.

<_within file="application.html">

  <div id="content">

    <div class="row-fluid" template="tutorial" field="id:id">

      <div class="span6">

        <div>
          <button class="btn btn-success" id="inc-button">Increment Counter</button>
        </div>

        <div class="counter-row">
          <span class="counter-label">Counter:</span>
          <span class="counter" field="content:my-counter">40</span>
        </div>

        <div id="other-counters">
          <div template="other-counter" class="counter-row" field="id:id">
            <span class="counter-label">Counter for <span field="content:counter-id">ahbnytnh</span>:</span>
            <span class="counter" field="content:count">10</span>
          </div>
        </div>

      </div>

      <div class="span2">

        <div class="stat-label">
          Max Count
        </div>
        <div class="stat" field="content:max-count">40</div>

        <div class="stat-label">
          Avg Count
        </div>
        <div class="stat" field="content:average-count">25</div>

        <div class="stat-label">
          Total Count
        </div>
        <div class="stat" field="content:total-count">50</div>

      </div>
      <div class="span3">

        <div class="stat-label">
          Max Dataflow Time
        </div>
        <div class="stat" field="content:dataflow-time-max">35</div>

        <div class="stat-label">
          Avg Dataflow Time
        </div>
        <div class="stat" field="content:dataflow-time-avg">9</div>

        <div class="stat-label">
          Current Dataflow time
        </div>
        <div class="stat" field="content:dataflow-time">6</div>

      </div>

    </div>

  </div>

</_within>

All the special stuff that makes this a Pedestal template can be explained by focusing on two lines in this files.

Pedestal uses two special attributes to define a template: template and field. An HTML element with a template attribute is a distinct template which can be referred to by name. The field attribute is used to describe how data is mapped from a Clojure map to this template.

<div class="row-fluid" template="tutorial" field="id:id">

In the example above we create a template named tutorial. This div and its contents are a single template. The field attribute indicates that the value under the key :id in the provided map should be set as the id attribute of this div.

We can define a mapping to several attributes by creating a comma delimited list.

field="class:c,width:w,id:i"

The names to the right of the colon are the keys in the supplied map. The names to the left of the colon are the HTML attributes. To fill this template we might supply the data below.

{:c "example-class"
 :w 200
 :my-id "example-id"}
<span class="counter" field="content:my-counter">40</span>

Other than setting attributes, we may want to set the content of an element. To do this we use the special attribute name content.

You may have noticed that the HTML above contains a template within a template. Later in this section we will see how to sort this out.

Update CSS

The CSS for this example is located in the file

app/assets/stylesheets/tutorial-client.css

The updated CSS is shown below.

#content {
    margin-top: 30px;
}

.counter-row {
    margin-top: 20px;
    margin-bottom: 10px;
}

.counter-label {
    font-size: 30px;
    font-weight: bold;
    color: #888;
}

.counter {
    font-size: 30px;
    font-weight: bold;
    padding-left: 10px;
}

.stat-label {
    font-size: 16px;
    font-weight: bold;
    color: #666;
    margin-top: 10px;
}

.stat {
    font-size: 16px;
    font-weight: bold;
}

As one final improvement, change the class container to container-fluid in the main div under body in app/templates/application.html.

<div class="container-fluid">

Slicing Templates

Pedestal provides a very flexible way to write template source files. In order to use these templates in your application we need to extract them from the source.

In our ClojureScript code, we would like to have a map were we can look up a template, add it to the DOM and make changes to it. Pedestal provides functions to help us extract templates from source files.

The file

app/src/tutorial_client/html_templates.clj

contains the code which does this.

(defmacro tutorial-client-templates
  []
  {:tutorial-client-page (dtfn (tnodes "tutorial-client.html" "tutorial" [[:#other-counters]])
                               #{:id})
   :other-counter (dtfn (tnodes "tutorial-client.html" "other-counter") #{:id})})

This is a Clojure macro but you can think of it as a normal Clojure function. The only thing that is different about this function is that it will be run at compile time. When we compile our ClojureScript code, any call to tutorial-client-templates will be replaced with the return value of this function.

This code uses functions form the io.pedestal.app.templates namespace.

The tnodes function converts a template in a file into a sequence of Enlive nodes.

(tnodes "tutorial-client.html" "other-counter")

The third argument to tnodes is a vector in Enlive selectors that we would like to have "cleared". This means that the contents of the these nodes will not be included. This is helpful when you have some example data in the HTML that you don't want in the actual template, as is the case here, when you do not what to include an inner template in an outer one.

(tnodes "tutorial-client.html" "tutorial" [[:#other-counters]])

Because this is a sequence of Enlive nodes we can perform any other transformations that we would like.

There are two ways to turn this data into a template, we can use the functions tfn or dtfn.

tfn is short for template function and will create a function which, when passed a map, will fill in the template with the values in the map and return a string of HTML which can be added to the DOM. Use this when you don't need to update the template after it has been added to the DOM.

dtfn is short for dynamic template function and will create a template which can be updated even after it has been added to the DOM. dtfn takes two arguments, the sequence of nodes and a set of fields static fields. Static fields can be set once when the template is first added to the DOM, but they cannot be changed later. In the example above we would like for the ids to be fields that we set but do want them to change later so we mark them as static fields.

Checking Templates from the REPL

When slicing templates, it can be helpful to confirm that they work form the REPL before trying to use them in your application. In the tutorial-client.html-templates namespace, try some of the following:

Create the template map.

(def t (tutorial-client-templates))

Run the dynamic template function.

(def tf (:other-counter t))
(tf)

Get the template constructor function and create the initial template with an id value of 42.

(let [_ ctor] (tf)
  (ctor {:id 42}))

Dynamic templates are hard to work with manually. In the rendering section we will see that we don't have to do much on this manual manipulation in practice.

Please note that any time we make changes to the namespace tutorial-client.html-templates the server will have to be restarted of this code reloaded in order for the changes to take effect.

Next steps

In the next step we will write the rendering code which allows our application to use these templates.

The tag for this step is step6.

Home | Rendering

Clone this wiki locally