60
Menu.i("Static") / "static" / **)
61
62
// set the sitemap. Note if you don't want access control for
63
// each page, just comment this line out.
64
LiftRules.setSiteMapFunc(() => sitemap())
65
66
//Show the spinny image when an Ajax call starts
67
LiftRules.ajaxStart =
68
Full(() => LiftRules.jsArtifacts.show("ajax-loader").cmd)
69
70
// Make the spinny image go away when it ends
71
LiftRules.ajaxEnd =
3.1. STARTING AT THE BEGINNING: BOOT.SCALA
13
72
Full(() => LiftRules.jsArtifacts.hide("ajax-loader").cmd)
73
74
// Force the request to be UTF-8
75
LiftRules.early.append(_.setCharacterEncoding("UTF-8"))
76
77
// Use HTML5 for rendering
78
LiftRules.htmlProperties.default.set((r: Req) =>
79
new Html5Properties(r.userAgent))
80
}
81
}
Rather than keeping configuration parameters in XML files, Lift keeps configuration parameters
in code in Boot. Boot is executed once when the servlet container loads the Lift application. You
can change many of Lift’s execution rules in the LiftRules singleton during boot, but after boot,
these parameters are frozen.
3.1.1
LiftRules rules
Most of the configuration parameters that define how Lift will convert an HTTP request into a
response are contained in the LiftRules singleton. Some of the parameters for LiftRules are
used commonly and some are very infrequently changed from their default. LiftRules can be
changed during boot, but not at other times. So, set all your configuration in boot (or in methods
that are called from boot).
3.1.2
Properties and Run modes
While many properties for your running application can be defined in Boot.scala, there are
some properties that are best defined in a text file. Lift supports multiple properties files per
project. The properties files are loaded based on the user, machine and run mode.
If you want to provide a configuration file for a subset of your application or for a specific envi-
ronment, Lift expects configuration files to be named in a manner relating to the context in which
they are being used. The standard name format is:
modeName.userName.hostName.props
examples:
dpp.yak.props
test.dpp.yak.props
production.moose.props
staging.dpp.props
test.default.props
default.props
with hostName and userName being optional, and modeName being one of "test", "staging", "pro-
duction", "pilot", "profile", or blank (for development mode). The standard Lift properties file
extension is "props".
Place properties files in the src/main/resources/props directory in your project and they
will be packaged up as part of the build process.
14
CHAPTER 3. SNIPPETS AND SITEMAP
When
you’re
developing
your
Lift
application,
the
run
mode
(see
net.liftweb.util.Props.mode) will be Development.
When you deploy your appli-
cation, pass -Drun.mode=production to your web container.
In production mode, Lift
aggressively caches templates, snippet classes, etc.
3.1.3
By convention
Lift, like Rails, will look for items in certain locations by convention. For example, Lift will look
for classes that implement snippets in the xxx.snippet package where the xxx part is the main
package for your application. You define one or more packages for Lift to look in with:
1
// where to search snippet
2
LiftRules.addToPackages("code")
Here, we’ve added the code package to the list of packages that Lift will search through. You can
also do LiftRules.addToPackages("com.fruitbat.mydivision.myapplication").
3.1.4
Misc Rules
We’ll skip the sitemap definition until the next section. This rule defines how to show a spinning
icon during Ajax calls (Lift will automatically show the spinning icon if this function is enabled):
1
//Show the spinny image when an Ajax call starts
2
LiftRules.ajaxStart =
3
Full(() => LiftRules.jsArtifacts.show("ajax-loader").cmd)
And this rule sets the default character encoding to UTF-8 rather than the default platform encod-
ing:
1
// Force the request to be UTF-8
2
LiftRules.early.append(_.setCharacterEncoding("UTF-8"))
Okay... you get the idea... there are plenty of parameters to tune during boot.
3.1.5
Html5
Prior to Lift 2.2, Lift treated all templates as XHTML and emitted XHTML to the browser. When
the Lift project started in early 2007, this seemed like a Really Good Idea™. Turns out the world has
not adopted XHTML and some JavaScript libraries, e.g. Google Maps, doesn’t work on XHTML
pages. Lift 2.2 introduced optional Html5 support both in the parser (so it could read Html5
templates rather than requiring well formed XML in templates) and emits Html5 to the browser.
Lift still processes pages as Scala NodeSeq elements, so no changes are required to the application.
In order to keep Lift 2.2 apps backward compatible with Lift’s XHTML support, by default the
XHTML parser/serializer are used. However, it’s recommended to use the Html5 support which
can be turned on in boot with:
3.2. SITEMAP
15
1
// Use HTML5 for rendering
2
LiftRules.htmlProperties.default.set((r: Req) =>
3
new Html5Properties(r.userAgent))
3.2
SiteMap
Lift has an optional feature called SiteMap. You don’t have to use it. But if you do set a sitemap
in boot, then Lift will use the sitemap as a white list of HTML pages for your site (note that REST
URLs do not need to be listed in the sitemap). SiteMap defines navigation and access control,
allows you to create hierarchical menus, grouped menu items, display the entire sitemap, a relative
sitemap, as well breadcrumbs. This section will discuss some of SiteMap’s capabilities.
3.2.1
Defining the SiteMap
The SiteMap must be defined in boot and is only defined once1. Typcially, you will define a
function that returns a SiteMap instance:
1
// Build SiteMap
2
def sitemap(): SiteMap = ...
And then define the SiteMap in LiftRules:
1
// set the sitemap. Note if you don't want access control for
2
// each page, just comment this line out.
3
LiftRules.setSiteMapFunc(() => sitemap())
In development mode, the function will be called on each page load to rebuilt the SiteMap. In all
other Lift run modes, the sitemap will be built once during boot.
A SiteMap is a collection of Menu instances. Each Menu has one Loc[_] and a set of Menu
instances as submenus (zero or more). Each Menu instance has a unique name.
If an HTML page is not defined in the sitemap, Lift will not serve it. SiteMap is a white list of
pages to serve. Further, the Loc[_] has parameters that can include multiple access control rules.
3.2.2
Simplest SiteMap
The simplest sitemap defines a single page:
1
def sitemap(): SiteMap = SiteMap(Menu.i("Home") / "index")
1In development mode, the sitemap can be changed dynamically to support changes to site content without having
to re-start your application each time navigation changes. This is a development-time feature only. There are significant
performance penalties associated with rebuilding the sitemap on each page load including forcing the serialization
of serving pages. There are plenty of features in SiteMap that allow you to enable/disable menu items and have
dynamically generated submenus. Don’t rely on Lift’s development-mode menu reloading for your application design.
16
CHAPTER 3. SNIPPETS AND SITEMAP
This is a SiteMap with a single menu item. The Menu has the name “Home” and will be displayed
as the localized (see 8.1 on page 92) string “Home”. The Menu.i method generates a Menu with a
Loc[Unit].
3.2.3
Menu and Loc[_]
You may be wondering why a Menu and a Loc[_] (short for location, pronouned “Loke”) are
separate and why the Loc takes a type parameter.
A Menu contains a location and many submenus. The original thought was that you could have a
single Loc[_] that might be placed in different places in the menu hierarchy. So, historically, they
are separated, but there’s a one to one relation between them.
The Loc[_] takes a type parameter which defines a current value type for the Loc. For example,
if the Loc refers to a page that will display a wiki page, then the type parameter of the Loc would
be WikiPage: Loc[WikiPage].
Each Loc can have many parameters (know as LocParam, “loke param”) that define behavior for
the Loc[_]. These parameters include access control testing, template definition, title, group, etc.
3.2.4
Access Control
You can control access to the URL/page represented by the Loc with the If() LocParam:
1
/**
2
* Calculate if the page should be displayed.
3
* In this case, it will be visible every other minute
4
*/
5
def displaySometimes_? : Boolean =
6
(millis / 1000L / 60L) % 2 == 0
7
8
Menu.i("Sometimes") / "sometimes" >> If(displaySometimes_? _,
9
S ? "Can't view now")
We define a method that returns true if access is allowed. Adding the If() LocParam will
restrict access to the page unless the function returns true. Menu items will not be visible for
pages that do not pass the access control rules and even if the user types the URL into the browser,
the page will not be displayed (by default, the user will be redirected by to the home page and an
error will be displayed.)
3.2.5
Hidden and Group
Menu items can be hidden from the default menu hierarchy even if the page is accessible. The
Hidden LocParam says “hide from default menu.”
1
Menu.i("About") / "about" >> Hidden >> LocGroup("bottom")
Menu items can also be grouped together in a named group and then displayed:
3.2. SITEMAP
17
1
<span class="lift:Menu.group?group=bottom"></span>
Which results in:
1
<a href="/about">About</a> <a href="/feedback">Feedback</a> <a href="/sitemap">Sitemap</a> 3.2.6
Submenus
You can nest menus:
1
// A menu with submenus
2
Menu.i("Info") / "info" submenus(
3
Menu.i("About") / "about" >> Hidden >> LocGroup("bottom"),
4
Menu.i("Contact") / "contact",
5
Menu.i("Feedback") / "feedback" >> LocGroup("bottom"))
The About, Contact and Feedback pages are nested under the Info page.
3.2.7
Parameters
You can parse the incoming URL and extract parameters from it into type-safe variables:
1
// capture the page parameter information
2
case class ParamInfo(theParam: String)
3
4
// Create a menu for /param/somedata
5
val menu = Menu.param[ParamInfo]("Param", "Param",
6
s => Full(ParamInfo(s)),
7
pi => pi.theParam) / "param"
The above code creates a menu called “Param”. The menu is for the url /param/xxx where xxx
can match anything.
When the URL /param/dogfood or /param/fruitbat is presented, it matches the Loc and
the function (s => Full(ParamInfo(s))) is invoked. If it returns a Full Box, the value is
placed in the Loc’s currentValue.
It’s possible to hand-write Loc implementation that will match many URL parameters.
For information on accessing the captured parameters (in this case the ParamInfo), see 3.4.5 on
3.2.8
Wildcards
You can create menus that match all the contents of a given path. In this case, all the html files in
/static/ will be served. That includes /static/index, /static/fruitbat, and /stat-
ic/moose/frog/wombat/meow.
18
CHAPTER 3. SNIPPETS AND SITEMAP
1
// more complex because this menu allows anything in the
2
// /static path to be visible
3
Menu.i("Static") / "static" / **
Note that Lift will not serve any files or directories that start with . (period) or _ (underscore) or
end with -hidden.
3.2.9
Summary
We’ve demonstrated how to create a SiteMap with many different kinds of menu items. Next,
let’s look at the views.
3.3
View First
Once the access control is granted by SiteMap, Lift loads the view related to the URL. There are
many mechanisms that Lift uses to resolve a path to a view, but the simplest is a one to one
mapping between the URL path and the files in /src/main/webapp. If the URL is /index, then
Lift will look for the localized (see 8.1 on page 92) version of /src/main/webapp/index.html.
Once Lift loads the template, Lift processes it to transform it into the dynamic content you want
to return in response to the URL input.
3.3.1
Page source
Let’s look at the page source:
Listing 3.2: index.html
1
<!DOCTYPE html>
2
<html>
3
<head>
4
<meta content="text/html; charset=UTF-8" http-equiv="content-type" />
5
<title>Home</title>
6
</head>
7
<body class="lift:content_id=main">
8
<div id="main" class="lift:surround?with=default&at=content">
9
<div>Hello World. Welcome to your Lift application.</div>
10
<div>Check out a page with <a href="/param/foo">query parameters</a>.</div>
11
12
<span class="lift:embed?what=_embedme">
13
replaced with embedded content
14
</span>
15
16
<div>
17
BADTAB<ul>
18
BADTAB
<li>Recursive: <a href="/recurse/one">First snippet</a></li>
19
BADTAB
<li>Recursive: <a href="/recurse/two">Second snippet</a></li>
20
BADTAB
<li>Recursive: <a href="/recurse/both">Both snippets</a></li>
21
BADTAB</ul>
3.3. VIEW FIRST
19
22
</div>
23
</div>
24
</body>
25
</html>
We can open the page in our browser:
3.3.2
Dynamic content
The template is a legal HTML page. But there are marker in the page to tell Lift how to interpret
the HTML.
If the <body> tag contains a class attribute lift:content_id=xxxx, then Lift will find the
element with the matching id and use that as the starting point for rendering the page. This
allows your designers to edit and maintain the pages in the same hierarchy that you use for your
application.
3.3.3
Surround and page chrome
The template processing starts with:
1
<div id="main" class="lift:surround?with=default&at=content">
The class attribute lift:surround?with=default;at=content instructs Lift to surround
the current Element with the template named default.html (typically located in the /templates-
hidden/ directory), and place the current page’s content at the element with the “content” id.
This pattern allows us to wrap a common chrome around every page on our site. You can also
specify different template to use for surrounding. Further, the template itself can choose different
templates to use for surrounding.
3.3.4
Embed
In addition to surrounding the page with chrome, you can also embed another file. For example,
you could have a shopping cart component that is embedded in certain pages. We embed with:
1
<span class="lift:embed?what=_embedme">
2
replaced with embedded content
3
</span>
Once again, the command is signalled with a class attribute that starts with lift:. In this case,
we embed a template from the file _embedme.html.
20
CHAPTER 3. SNIPPETS AND SITEMAP
3.3.5
Results
The resulting dynamically generated page looks like:
3.4
Snippets and Dynamic content
Lift templates contain no executable code. They are pure, raw, valid HTML.
Lift uses snippets to transform sections of the HTML page from static to dynamic. The key word
is transform.
Lift’s snippets are Scala functions: NodeSeq => NodeSeq. A NodeSeq is a collection of XML
nodes. An snippet can only transform input NodeSeq to output NodeSeq. Well, not exactly...
a snippet may also have side effects including setting cookies, doing database transactions, etc.
But the core transformation concept is important. First, it isolates snippet functionality to discrete
parts of the page. This means that each snippet, each NodeSeq => NodeSeq, is a component.
Second, it means that pages are recursively built, but remain as valid HTML at all times. This
means that the developer has to work hard to introduce a cross site scripting vulnerability. Third,
the designers don’t have to worry about learning to program anything in order to design HTML
pages because the program execution is abstracted away from the HTML rather than embedded
in the HTML.
3.4.1
Snippets in markup
In order to indicate that content is dynamic, the markup contains a snippet invocation. That typ-
ically takes the form class="someclass someothercss lift:mysnippet". If a class at-
tribute contains lift:xxx, the xxx will be resolved to a snippet. The snippet may take attributes.
Attributes are encoded like URL parameters... offset by a ? (question mark), then name=value,
separted by ? (question mark), ; (semicolon) or & (ampersand). name and value are URL en-
coded.
You may also invoke snippets with XML tags:
1
<lift:my_snippet cat="foo">
2
<div>xxxx</div>
3
</lift:my_snippet>
3.4. SNIPPETS AND DYNAMIC CONTENT
21
Note that the Html5 parser will force all tags to lower case so <lift:MySnipet> will become
<lift:mysnippet>.
Lift
2.3
will
also
allow
snippet
invocation
in
the
form
<div
l="mysnippet?param=value">xxx</div>.
The latter two mechanisms for invoking snippets will not result in valid Html5 templates.
3.4.2
Snippet resolution
Lift has a very complex set of rules to resolve from snippet name to NodeSeq => NodeSeq (see 23.1
on page 141). For now, the simplest mechanism is to have a class or object in the snippet
package that matches the snippet name.
So lift:HelloWorld will look for the code.snippet.HelloWorld class and invoke the ren-
der method.
lift:CatFood.fruitbat will look for the code.snippet.CatFood class and invoke the
fruitbat method.
3.4.3
Dynamic Example
Let’s look at the dynamic.html page:
Listing 3.3: dynamic.html
1
<!DOCTYPE html>
2
<html>
3
<head>
4
<meta content="text/html; charset=UTF-8" http-equiv="content-type" />
5
<title>Dynamic</title>
6
</head>
7
<body class="lift:content_id=main">
8
<div id="main" class="lift:surround?with=default;at=content">
9
This page has dynamic content.
10
The current time is <span class="lift:HelloWorld">now</span>.
11
</div>
12
</body>
13
</html>
This template invokes the HelloWorld snippet defined in HelloWorld.scala:
Listing 3.4: HelloWorld.scala
1
package code
2
package snippet
3
4
import lib._
5
6
import net.liftweb._
7
import util.Helpers._
8
import common._
9
import java.util.Date
22
CHAPTER 3. SNIPPETS AND SITEMAP
10
11
class HelloWorld {
12
lazy val date: Box[Date] = DependencyFactory.inject[Date] // inject the date
13
14
def render = "* *" #> date.map(_.toString)
15
}
And the dynamic content becomes:
1
<span>Thu Dec 30 16:31:13 PST 2010</span>
The HelloWorld snippet code is simple.
1
lazy val date: Box[Date] = DependencyFactory.inject[Date]
Uses dependency injection (see 8.2 on page 94) to get a Date instance.
Then:
1
def render = "* *" #> date.map(_.toString)
Creates a C