-
Notifications
You must be signed in to change notification settings - Fork 0
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
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.
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.
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">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.
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.
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.