4 Templates in Lift
An XHTML page, being the central component of a web application, is likewise the central component of Lift’s request processing. In Lift, we go a step further and utilize a flexible yet powerful templating engine that allows us to compose an XHTML
↓ page not only out of one or more XML
↓ files, but also from methods that can programmaticaly generate template XML. Additionally, Lift 2.2
↓ brings designer-friendly templates (Section
4.2↓) and HTML5 support (Section
4.3↓). Designer-friendly templates, in particular, can simplify working with a designer because they allow templates to be fully valid XHTML or HTML5.
In this chapter we’ll discuss template capabilities and syntax, including built-in tags provided by Lift that perform special template processing (Section
4.5↓). We will also cover how you can write your own View classes, Scala code that can programmatically generate template XML (Section
4.4↓). We’ll finish up the chapter with some discussion on miscellaneous templating functionality.
4.1 Template XML
Templates
↓ form the backbone of Lift’s flexibility and power. A template is an XML document that contains Lift-specific tags, see
4.5↓, as well as whatever content you want returned to the user.
A note on nomenclature: typically when people discuss “templates” in books or on the mailing list they’re talking about XML files. We’ll cover programmatic generation of template XML in Section
4.4↓.
Lift includes several built-in XML tags for specific actions. These utilize prefixed XML elements and are of the form
<lift:tag_name/>. Lift also allows you to define your own tags, which are called
snippets↓ (Chapter
5↓). These user-defined tags are linked to Scala methods and these methods can process the XML contents of the snippet tag, or can generate their own content from scratch. A simple template is shown in Listing
4.1↓.
<lift:surround with="default" at="content">
<head><title>Hello!</title></head>
<lift:Hello.world />
</lift:surround>
Notice the tags that are of the form
<lift:name> which in this case are
<lift:surround> and
<lift:snippet>. These are two examples of Lift-specific tags. We’ll discuss all of the tags that users will use in Section
4.5↓, but let’s briefly discuss the two shown here. We use the built-in
<lift:surround>↓ tag (Section
4.5.17↓) to make Lift embed our current template inside the “default” template. We also use
<lift:snippet> tag (aliased to Hello.world) to execute a snippet that we defined. In this instance, we execute the method
world in the class
Hello to generate some content.
4.1.1 Locating Template XML
During request processing, Lift first tries to match against the
LiftRules.viewDispatch↓↓ function to see if an explicit View method is defined for the request. If there isn’t a
viewDispatch match, then Lift next tries to locate a file in the template directory tree (typically in a WAR archive) that matches the request. Lift tries several suffixes (html, xhtml, htm, and no suffix) and also tries to match based on the client’s
Accept-Language header. The pattern Lift uses is:
<path to template>[_<language tag>][.<suffix>]
Because Lift will implicitly search for suffixes, it’s best to leave the suffix off of your links within the web app. If you have a link with an href of /test/template.xhtml, it will only match that file, but if you use /test/template for the href and you have the following templates in your web app:
-
/test/template.xhtml
-
/test/template_es_ES.xhtml (Spanish localized for Spain)
-
/test/template_ja.xhtml
then Lift will use the appropriate template based on the user’s requested language if a corresponding template is available. For more information regarding internationalization please see Appendix
D↓. In addition to normal templates, your application can make use of hidden templates
↓. These are templates that are located under the
/templates-hidden directory of your web app. Technically, Lift hides files in any directory ending in “-hidden”, but templates-hidden is somewhat of a de facto standard. Like the
WEB-INF directory, the contents cannot be directly requested by clients. They can, however, be used by other templates through mechanisms such as the
<lift:surround> and
<lift:embed> tags (Section
4.5.7↓). If a static file can’t be located then Lift will attempt to locate a View class (Section
4.4↓) that will process the request. If Lift cannot locate an appropriate template based on the request path then it will return a 404 to the user.
4.1.2 Processing Template XML
Once Lift has located the correct template, the next step is to process the contents. It is important to understand that Lift processes XML tags recursively, from the outermost tag to the innermost tag. That means that in our example Listing
4.1↑, the surround tag gets processed first. In this case the surround loads the default template and embeds our content at the appropriate location. The next tag to be processed is the
<lift:Hello.world/> snippet. This tag is essentially an alias for the lift:snippet tag (specifically,
<lift:snippet type=“Hello:world”>) , and will locate the Hello class and execute the world method on it. If you omit the “method” part of the type and only specify the class (
<lift:Hello> or
<lift:snippet type=“Hello”>), then Lift will attempt to call the
render method of the class.
To give a more complex example that illustrates the order of tag processing, consider Listing
4.1.2↓. In this example we have several nested snippet tags, starting with
<A.snippet />. Listing
4.1.2↓ shows the backing code for this example. Snippets are covered in more detail in Chapter
5↓.
A Recursive Tag Processing Example
<lift:A.snippet>
<p>Hello, <A:name />!</p>
<p>
<lift:B.snippet>
<B:title />
<lift:C.snippet />
</lift:B.snippet>
</p>
</lift:A.snippet>
The first thing that happens is that the contents of the
<lift:A.snippet> tag are passed as a
NodeSeq argument to the
A.snippet method. In the
A.snippet method we bind
↓, or replace, the
<A:name /> tag with an XML Text node of “The A snippet”. The rest of the input is left as-is and is returned to Lift for more processing. Lift examines the returned
NodeSeq for more lift tags and finds the
<lift:B.snippet> tag. The contents of the
<lift:B.snippet> tag are passed as a
NodeSeq argument to the
B.snippet method, where the
<B.title /> tag is bound with the XML Text node “The B snippet”. The rest of the contents are left unchanged and the transformed
NodeSeq is returned to Lift, which scans for and finds the
<lift:C.snippet /> tag. Since there are no child elements for the
<lift:C.snippet /> tag, the
C.snippet method is invoked with an empty
NodeSeq and the
C.snippet returns the Text node “The C snippet”.
The Recursive Tag Snippets Code
... standard Lift imports ...
class A {
def snippet (xhtml : NodeSeq) : NodeSeq =
bind("A", xhtml, "name" -> Text("The A snippet"))
}
class B {
def snippet (xhtml : NodeSeq) : NodeSeq =
bind("B", xhtml, "title" -> Text("The B snippet"))
}
class C {
def snippet (xhtml : NodeSeq) : NodeSeq = Text("The C snippet")
}
While the contents of the
A.snippet tag are passed to the
A.snippet method, there’s no requirement that the contents are actually used. For example, consider what would happen if we swapped the B and C snippet tags in our template, as shown in Listing
4.1.2↓. In this example, the
C.snippet method is called before the
B.snippet method. Since our
C.snippet method returns straight XML that doesn’t contain the B snippet tag, the B snippet will never be executed! We’ll cover how the
eager_eval tag attribute can be used to reverse this behavior in Section
5.3.4↓.
The Swapped Recursive Snippet Template
<lift:A.snippet>
<p>Hello, <A:name />!</p>
<p>
<lift:C.snippet>
<lift:B.snippet>
<B:title />
</lift:B.snippet>
</lift:C.snippet>
</p>
</lift:A.snippet>
<!-- After the A and C snippets have been processed: -->
<p>Hello, The A snippet</p>
<p>The C snippet</p>
As you can see, templates are a nice way of setting up your layout and then writing a few methods to fill in the XML fragments that make up your web applications. They provide a simple way to generate a uniform look for your site, particularly if you assemble your templates using the surround and embed tags. If you’d like programmatic control over the template XML used for a particular request, you’ll want to use a View, which is discussed in the next section.
4.2 Designer-Friendly Templates ↓↓
New in Lift 2.2 is the ability to use fully valid XHTML (or HTML5, which we’ll discuss in Section
4.3↓) for your templates. There are a number of features involved in designer-friendly templates (or DFTs for short), so let’s go through each one.
4.2.1 Determining the Content Element↓
In XML-based templates, the entire XML file is considered to hold the contents of the template. In DFTs, we want to be able to include the full XHTML or HTML5 markup, including tags like <DOCTYPE>, <html/>, etc. without necessarily including all of that in the output of the template (for example, in an embedded template). Lift supports choosing a child element of the template to represent the actual contents via the use of one of two related mechanisms.
The first mechanism is to put a
lift:content_id attribute on the HTML element, as shown in Listing
4.2.1↓. The drawback to this approach is that you have to specify the “lift” namespace in the html tag or you might get validation errors.
Assigning a Content ID on the HTML element
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:lift="http://liftweb.net"
lift:content_id="real_content">
<head>
<title>Not really</title>
</head>
<body>
<div id="real_content">
<h1>Welcome to your project!</h1>
</div>
</body>
</html>
The second, safer approach, is to specific the
lift:content_id marker as part of the body element’s
class attribute, as shown in Listing
4.2.1↓.
Assigning a Content ID in the Body class
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>Not really</title>
</head>
<body class="lift:content_id=real_content">
<div id="real_content">
<h1>Welcome to your project!</h1>
</div>
</body>
</html>
4.2.2 Invoking Snippets Via the Class Attribute↓
In XML-based templates, Lift looks for tags with the “lift” prefix to process. In DFTs, the
class attribute is used instead for invocation. This form of invocation is discussed in more detail in Section
5.1 on page 1↓.
4.2.3 Binding via CSS transforms↓
Lift 2.2 introduces a new feature for binding values into snippet markup by using CSS id and class attributes instead of prefixed XML elements. This support is detailed in Section
5.3.2 on page 1↓.
4.3 HTML5 Support ↓↓↓
We just discussed Templates and saw that through a combination of an XML file, Lift tags, and Scala code we can respond to requests made by a user. You can also generate an XHTML response entirely in code using a View. Custom dispatch is a similar method which can be used to programmatically return any kind of response (not just XHTML), and is covered in more depth in Section
3.8↑.
A view function is a normal Scala method of type
() ⇒ scala.xml.NodeSeq↓. The
NodeSeq that’s returned from a view is processed for template tags in the same way that XML loaded from a static file would be. As we showed in Section
3.5↑, there are two ways that a View can be invoked. The first is by defining a partial function for
LiftRules.viewDispatch↓↓, which allows you to dispatch to any statically-available method (i.e. on an
object, not a
class), or to a
LiftView (explained in a moment) object for any arbitrary request path. The second way that a View can be invoked is by reflection: if the first element of the request path matches the class name of the View (as defined in Section
3.2.1↑), then the second element is used to look up the View function depending on which trait the View class implements. For performance reasons, explicit dispatch via
LiftRules.viewDispatch is recommended because reflection incurs a significant cost for each request. When you use
LiftRules.viewDispatch, you need to provide an instance of
scala.lang.Either to differentiate the dispatch type: a
scala.lang.Left indicates a method returning a
Box[NodeSeq], while a
scala.lang.Right indicates a
LiftView object. If you want to dispatch a request to a
LiftView object, the match in the
LiftRules.viewDispatch is made on all path components except the last one (e.g.
List.init), and that object’s
dispatch method is checked against the last component of the path to further determine which method on the object will handle the request. Listing
4.4↓ shows how we can define an RSS view as a feed using explicit dispatch. Note that we use extraction on the requested path in this case to provide account-specific feeds and a security token to prevent feed browsing.
// In Boot.boot:
LiftRules.viewDispatch.append {
// This is an explicit dispatch to a particular method based on the path
case List("Expenses", "recent", acctId, authToken) =>
Left(() => Full(RSSView.recent(acctId, authToken)))
// This is a dispatch via the same LiftView object. The path
// "/Site/news" will match this dispatch because of our dispatch
// method defined in RSSView. The path "/Site/stuff/news" will not
// match because the dispatch will be attempted on List("Site","stuff")
case List("Site") => Right(RSSView)
}
// Define the View object:
object RSSView extends LiftView {
def dispatch = {
case "news" => siteNews
}
def recent(acctId : String, authToken : String)() : NodeSeq = {
// User auth, account retrieval here
...
<lift:surround with="rss" at="content">
...
</lift:surround>
}
// Display a general RSS feed for the entire site
def siteNews() : NodeSeq = { ... }
}
If you want to use reflection for dispatch
↓↓ then there are two traits that you can use when implementing a view class: one is the
LiftView↓ trait, the other is the
InsecureLiftView↓ trait, both under the
net.liftweb.http package. As you may be able to tell from the names, we would prefer that you extend the
LiftView trait. The
InsecureLiftView determines method dispatch by turning a request path into a class and method name. For example, if we have a path
/MyStuff/enumerate, then Lift will look for a class called
MyStuff in the view subpackage (class resolution is covered in Section
3.2.1↑) and if it finds
MyStuff and it has a method called
enumerate, then Lift will execute the
enumerate method and return its result to the user. The main concern here is that Lift uses reflection
↓ to get the method with
InsecureLiftView, so it can access any method in the class, even ones that you don’t intend to make public. A better way to invoke a View is to extend the
LiftView trait, which defines a dispatch partial function. This dispatch function maps a string (the “method name”) to a function that will return a
NodeSeq. Listing
4.4↓ shows a custom
LiftView class where the path
/ExpenseView/enumerate will map to the
ExpenseView.doEnumerate method. If a user attempts to go to
/ExpenseView/privateMethod they’ll get a 404 because
privateMethod is not defined in the dispatch method. If, however, our
ExpenseView class implemented the
InsecureLiftView trait and someone visited
/ExpenseView/privateMethod, we would lose our hard drive (on Unix at least).
class ExpenseView extends LiftView {
override def dispatch = {
case "enumerate" => doEnumerate _
}
def doEnumerate () : NodeSeq = {
...
<lift:surround with="default" at="content">
{ expenseItems.toTable }
</lift:surround>
}
def privateMethod () : NodeSeq = {
Runtime.getRuntime.exec("rm -rf /")
}
}
A major difference between Views and other programmatic rendering approaches (such as Custom Dispatch) is that the
NodeSeq returned from the View method is processed for template tags including
surrounds and
includes, just as it would be for a snippet. That means that you can use the full power of the templating system from within your View, as shown in Listing
4.4↑’s
doEnumerate method.
Since you can choose not to include any of the pre-defined template XHTML, you can easily generate any XML-based content, such as Atom or RSS feeds, using a View.
In the earlier sections on Templates and Views we briefly touched on some of Lift’s built-in tags, namely, <lift:snippet/> and <lift:surround/>. In this section we’ll go into more detail on those tags as well as cover the rest of Lift’s tags.
The
<lift:a/> tag is used internally by
SHtml.a to create an anchor tag that will call an AJAX function when clicked. This tag is generally not used directly by developers. See Section
11.4 on page 1↓ for more details.
Usage: <lift:bind name=”binding_name” />
The
<lift:bind/> tag is used as a placeholder for insertion of content within included templates when using the
<lift:surround/> and <lift:embed/> tags. See Section
4.7↓ for examples and discussion.
Usage: <lift:bind-at name=”binding_name”>contents</lift:bind-at>
The
<lift:bind-at/> tag is used to replace named
<lift:bind/> tags within
<lift:surround/> and
<lift:embed/> tags. See Section
4.7↓ for examples and discussion.
Usage: <lift:children>...multiple xml nodes here...</lift:children>
The purpose of the
<lift:children/> tag is to allow you to create fragment templates with more than one root element that still parse as valid XML. For example, Listing
4.5.4↓ shows a template that we might want to embed into other templates. The problem is that XML requires a single root element, and in this case we have two.
A Non-Conforming XML Fragment
<div>First Div</div>
<div>Second Div</div>
By using the
<lift:children/> tag, as shown in Listing
4.5.4↓, we have a valid XML file. Lift essentially replaces the
<lift:children/> tag with its contents.
A Conforming XML Fragment
<lift:children>
<div>First Div</div>
<div>Second Div</div>
</lift:children>
Usage: <lift:comet type="ClassName" name=”optional”/>
The
<lift:comet/> tag embeds a Comet actor into your page. The class of the Comet actor is specified by the
type attribute. The
name attribute tells Lift to create a unique instance of the Comet actor; for example, you could have one Comet actor for site updates and another for admin messages. The contents of the tag are used by the Comet actor to bind a response. Listing
4.5.5↓ shows an example of a Comet binding that displays expense entries as they’re added. Comet is covered in more detail in Chapter
11↓.
<div class="accountUpdates">
<lift:comet type="AccountMonitor">
<ul><account:entries>
<li><entry:time/> : <entry:user /> : <entry:amount /></li>
</account:entries></ul>
</lift:comet>
</div>
As we mention in the embed tag documentation, mixing Comet with AJAX responses can be a bit tricky due to the embedded JavaScript that Comet uses.
Usage: <lift:CSS.blueprint />
<lift:CSS.fancyType />
The <lift:CSS/> tag is used to insert the blueprint and (optionally) fancyType CSS stylesheets
Usage: <lift:embed what="template_name" />
The
embed tag allows you to embed a template within another template. This can be used to assemble your pages from multiple smaller templates, and it also allows you to access templates from JavaScript commands (Chapter
10↓). As with the
surround tag, the template name can be either the base filename or a fully-qualified path.
Note that if you use the embed tag to access templates from within a JsCmd (typically an AJAX call), any JavaScript in the embedded template won’t be executed. This includes, but is not limited to, Comet widgets.
The snippet tag is covered in detail in Section
5.1 on page 1↓, part of the chapter on snippets.
Usage: <lift:surround with="template_name" at=”binding”>
children
</lift:surround>
The
surround tag surrounds the child nodes with the named template. The child nodes are inserted into the named template at the binding point specified by the
at parameter (we’ll cover the bind tag in Section
4.5.2↑). Typically, templates that will be used to surround other templates are incomplete by themselves, so we usually store them in the
<app root>/templates-hidden subdirectory so that they can’t be accessed directly. Having said that, “incomplete” templates may be placed in any directory that templates would normally go in. The most common usage of
surround is to permit you to use a “master” template for your site CSS, menu, etc. An example use of
surround is shown in Listing
4.5.17↓. We’ll show the counterpart master template in the section on the bind tag. Note also that the surrounding template name can be either a fully-qualified path (i.e. “/templates-hidden/default”), or just the base filename (“default”). In the latter case, Lift will search all subdirectories of the app root for the template. By default, Lift will use “/templates-hidden/default” if you don’t specify a
with attribute, so Listings
4.5.17↓ and
4.5.17↓ are equivalent.
<lift:surround with="default" at="content">
<p>Welcome to PocketChange!</p>
</lift:surround>
Surrounding with the default template
<lift:surround at="content">
<p>Welcome to PocketChange!</p>
</lift:surround>
Note that you can use multiple surround templates for different functionality, and surrounds can be nested. For example, you might want to have a separate template for your administrative pages that adds a menu to your default template. In that case, your
admin.html could look like Listing
4.5.17↓. As you can see, we’ve named our bind point in the admin template “content” so that we keep things consistent for the rest of our templates. So if, for example, we were going to nest the template in Listing
4.5.17↑ above into the
admin.html template in Listing
4.5.17↓, all we’d need to do is change it’s
with attribute from “default” to “admin.”
<lift:surround with="default" at="content">
<lift:Admin.menu />
<lift:bind name="content" />
</lift:surround>
You cannot have a hidden template with the same name as a sub-directory of your webapp directory. For example, if you had an admin.html template in /templates-hidden, you could not also have an admin directory.
4.5.21 with-resource-id
4.6 Head and Tail Merge
Another feature of Lift’s template processing is the ability to merge the HTML
head↓ element in a template with the
head element in the surrounding template. In our example, Listing
4.1↑, notice that we’ve specified a
head tag inside the template. Without the head merge, this
head tag would show up in the default template where our template gets bound. Lift is smart about this, though, and instead takes the content of the
head element and merges it into the outer template’s
head element. This means that you can use a
surround tag to keep a uniform default template, but still do things such as changing the title of the page, adding scripts or special CSS, etc. For example, if you have a table in a page that you’d like to style with jQuery’s TableSorter, you could add a head element to insert the appropriate script:
<lift:surround with="default" at="foo">
<head><script src="/scripts/tablesorter.js" type="text/javascript" /><head>
...
</lift:surround>
In this manner, you’ll import TableSorter for this template alone.
4.7 Binding
(C) 2012 Lift 2.0 EditionWritten by Derek Chen-Becker, Marius Danciu and Tyler Weir