Simply Lift by David Pollak - HTML preview

PLEASE NOTE: This is an HTML preview only and some elements such as links or page numbers may be incorrect.
Download the book in PDF, ePub, Kindle for a complete version.

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

page 23.

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>

index-31_1.png

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.

index-32_1.png

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