5 Snippets↓
Put simply, a snippet is a Scala method that transforms input XML into output XML
↓. Snippets act as independent (or dependent, if you want) pieces of logic that you insert into your page to perform rendering. As such, snippets form the backbone of Lift’s View-First
↓ rendering architecture. Although snippets aren’t the only mechanism Lift has for rendering page views (see Views, Section
4.4 on page 1↑, Custom Dispatch, Section
3.8 on page 1↑, or even the REST API, Chapter
15↓), they’re so widely used and so important that we feel they warrant their own chapter.
In this chapter we will cover the ins and outs of snippets, from the snippet tag that you place in your templates, through how the snippet method is resolved, to the snippet method definition itself. We’ll also cover related topics and some advanced functionality in snippets for those looking to push Lift’s boundaries.
5.1 The Snippet Tag↓↓
Usage: <lift:snippet type="snippetName" ...options... />
<lift:snippetName ...options... />
<div class=”lift:snippetName?opt1=...;opt2=...;opt3=...” />
The snippet tag is what you use to tell Lift where and how to invoke a snippet method on given XML content. The most important part of the tag is the snippet name, which is used to resolve which snippet method will process the snippet tag contents. We’ll cover how the snippet name is resolved to a concrete method in section
5.2↓.
Note that there is a shorthand for the type attribute simply by appending the snippet name after the lift: prefix. If you use this shorthand, make sure to avoid naming your snippets the same as Lift’s built-in tags, such as surround, children, embed, etc.
In addition to the the type attribute, Lift will process several other options:
form If the
form↓ attribute is included with a value of either “POST” or “GET”, then an appropriate form tag will be emitted into the XHTML using the specified submission method. If you omit this tag from a snippet that generates a form, the form elements will display but the form won’t submit.
multipart ↓↓The
multipart attribute is a boolean (the default is false, specify “yes”, “true” or “1” to enable) that specifies whether a generated form tag should be set to use multipart form submission. This is most typically used for file uploads (Section
6.4↓). If you don’t also specify the
form attribute then this won’t do anything.
eager_eval ↓The eager_eval attribute is a boolean (the default is false, specify “yes”, “true” or “1” to enable) that controls the order of processing for the snippet tag contents. Normally, the snippet is processed and then the XML returned from the snippet is further processed for Lift tags. Enabling eager_eval reverses this order so that the contents of the snippet tag are processed first. We cover this in more detail with an example in Section
5.3.4↓.
With Lift 2.2’s Designer-Friendly Templates (Section
4.2 on page 1↑), you can also specify a snippet tag as part of the class attribute for a given element. Attributes for snippets invoked in this manner are passed via a query string. Listing
5.1↓ shows an example of how we can use the standard
lift:surround processing by modiying the
class of our content element.
Invoking Snippets Via the Class Attribute
<!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 class="lift:surround?with=default;at=content" id="real_content">
<h1>Welcome to your project!</h1>
</div>
</body>
</html>
5.2 Snippet Dispatch ↓
The first step taken by Lift when evaluating a snippet tag is to resolve what snippet method will actually process the content. There are several mechanisms that are used to resolve the method, but they can be broken down into two main approaches: dispatch via reflection and explicit dispatch. In addition, Lift allows per-request remapping of snippet names via
S.mapSnippet↓. We’ll cover each in the following sections.
5.2.1 Implicit Dispatch Via Reflection↓↓
The simplest, and default, approach to resolving snippet names is to use implicit dispatch via reflection. When using implicit dispatch, Lift will use the snippet name specified in the snippet tag to first locate a class. Lift will then either instantiate a class, or if it’s a stateful snippet (we’ll cover stateful snippets in Section
5.3.3↓), retrieve the current instance. One Lift has a class instance, it uses the snippet name to further determine which method in the class to execute. There are three ways to specify this:
-
Via the type attribute on the snippet tag. The value should be “ClassName:method” for the particular snippet method you want to have handle the tag
-
Via a tag suffix of Class.method. This is the same as specifying the type=”Class:method” attribute
-
Via a tag suffix of just Class. This will use the render method on the specified class to handle the tag
Classes are resolved as specified in Section
3.2.1↑.
The most important thing to remember when using implicit dispatch is that your snippet classes must be members of a snippet subpackage as registered by LiftRules.addToPackages. For example, if you have LiftRules.addToPackages(“com.foo”) in your Boot.boot method, snippets should be members of com.foo.snippet.
Listing
5.2.1↓ shows three equivalent snippet tags. Note: these are only equivalent because the method name is “render.” If we had chosen a different method, e.g., “list,” then the third example below will still call a “render” method.
It’s important to note that with pure implicit dispatch, Java’s reflection allows access to
any method on the enclosing class, no matter what the protection on the method is set to (e.g. private, protected). Because of this, it’s possible to invoke private and protected methods via implicit dispatch, which could be a security concern.This is one reason that we recommend using either DispatchSnippet or explicit dispatch for production sites. We’ll cover both of these approaches momentarily.
Another important note is that lookup via reflection is relatively expensive operation, yet another reason that we recommend explicit dispatch for production sites.
<lift:snippet type="MyClass:render" />
<lift:MyClass.render />
<lift:MyClass />
In addition to “pure” implicit dispatch, you can exert a little more control on which method in a given class handles a snippet by implementing the
net.liftweb.http.DispatchSnippet↓ trait. This trait contains a single method, dispatch, of type
PartialFunction[String, NodeSeq ⇒ NodeSeq] that maps the method name (the “method” part of “Class.method” or “Class:method” as described above) to a particular method. Only method names defined in the dispatch PartialFunction can be executed; any methods that aren’t covered by the partial function will result in a snippet failure. Listing
5.2.1↓ shows how you can control the dispatch by providing a custom
dispatch def.
Using DispatchSnippet to Control Snippet Method Selection
package com.foo.snippet
import scala.xml.{NodeSeq,Text}
import net.liftweb.http.DispatchSnippet
class SomeSnippetClass extends DispatchSnippet {
def dispatch : DispatchIt = {
// We have to use a partially-applied (trailing "_") version
// of the functions that we dispatch to
case "foo" => myFooMethod _
case "bar" => someOtherBarMethod _
case _ => catchAllMethod _
}
def myFooMethod (xhtml : NodeSeq) : NodeSeq = { ... }
def someOtherBarMethod (xhtml : NodeSeq) : NodeSeq = { ... }
def catchAllMethod(xhtml : NodeSeq) : NodeSeq = Text("You’re being naughty!")
}
To summarize, implicit dispatch is the default method by which Lift resolves snippet tag names to the actual class and method that will process the snippet tag contents. Although implicit dispatch is simple to use and works well, security concerns lead us to recommend the use of the
DispatchSnippet↓ trait. Even with
DispatchSnippet, however, the implicit class resolution still uses reflection, so if you’re trying to make things performant you should use explicit dispatch instead.
5.2.2 Explicit Dispatch↓
Explicit dispatch allows you to have direct control over which methods will be executed for a given snippet name. There are two ways that you can define snippet name to method mappings: via LiftRules.snippetDispatch, which points Lift to DispatchSnippet instances, and LiftRules.snippets, which points Lift directly at methods.
Let’s first take a look at
LiftRules.snippetDispatch, the more generic option. When a snippet tag is encountered with a snippet name of the form
A.B or
A:B, Lift will take the first portion (
A) and use that as the lookup for
snippetDispatch. The
PartialFunction needs to return an instance of
DispatchSnippet, so typically you will implement your explicit dispatch snippets using an
object instead of a
class. Listing
5.2.2↓ shows how we define our object. Note that the
dispatch method will be executed with the “
B” portion of the snippet name (as we defined above) as its argument. Other than the fact that it’s an object, the definition is essentially identical to our implicit dispatch class in Listing
5.2.1↑.
Defining an Explicit Snippet Object
// The package *doesn’t* need to be "snippet" because there’s
// no reflection involved here
package com.foo.logic
import scala.xml.{NodeSeq,Text}
import net.liftweb.http.DispatchSnippet
object HelloWorld extends DispatchSnippet {
// We define dispatch as a val so that it doesn’t get re-created
// on each request
val dispatch : DispatchIt = {
case name => render(name) _
}
def render (name : String)(ignore : NodeSeq) : NodeSeq =
Text("Hello, world! Invoked as " + name)
}
Now that we have our snippet object, we can bind it to a particular snippet name in our
Boot.boot method, as shown in Listing
5.2.2↓. It’s interesting to note that this is actually how Lift defines many of its tags, such as
<lift:embed/>,
<lift:surround/>, and
<lift:comet/>. In our case, we’ve bound our snippet object to
<lift:HelloWorld/>, and because our
DispatchSnippet uses a simple variable binding for its
dispatch method case, we can invoke the same snippet with
<lift:HelloWorld.hey />,
<lift:HelloWorld.useless/>, or even
<lift:HelloWorld.this_is_getting_silly/>, and the snippet will tell us what name it was invoked with (
<lift:HelloWorld/> will invoke with the name “render”, following Lift’s normal snippet tag conventions). Noe that if you’re setting up a dispatch for a
StatefulSnippet↓↓, return a new instance of your
StatefulSnippet class.
StatefulSnippet instances will properly register themselves ahead of the
snippetDispatch partial function on each successive request.
Binding Our Explicit Snippet Object
class Boot {
def boot {
...
LiftRules.snippetDispatch.append {
case "HelloWorld" => com.foo.logic.HelloWorld
// For StatefulSnippets, return a *new instance*
case "HelloConversation" =>
new com.foo.logic.StatefulHelloWorld
}
}
}
Now let’s look at
LiftRules.snippets. This is a more fine-grained approach to explicit dispatch that doesn’t require the
DispatchSnippet trait. Instead, we bind a list of snippet name components corresponding to the parts of the snippet name separated by either “:” or “.”, and point it directly at a given snippet method. Assuming we’re using the same snippet object in Listing
5.2.2↑, we can bind the
<lift:HelloWorld/> tag by setting up
LiftRules.snippets in our Boot.boot method as shown in Listing
5.2.2↓. Notice that in order to bind the same way that we did with
snippetDispatch, we need two lines to match the un-suffixed and suffixed versions. If you omit the un-suffixed line you will get a snippet failure.
Explicitly Binding a Snippet Method
import com.foo.logic
class Boot {
def boot {
...
LiftRules.snippets.append {
// Matches a tag without a suffix (<lift:HelloWorld />)
case List("HelloWorld") => HelloWorld.render("no name") _
case List("HelloWorld", name) => HelloWorld.render(name) _
}
}
}
5.2.3 Per-request Remapping↓
The final piece of snippet mapping that we want to discuss is per-request remapping. The S.mapSnippet method allows you to modify which snippet method will service a given snippet tag within your page processing. For example, Listing
5.2.3↓ shows how we can conditionally “blank” a snippet based on logic in a second snippet. This functionality isn’t used frequently as the other types of snippet dispatch, but it’s here in case you need it.
import scala.xml.NodeSeq
import net.liftweb.http.S
class Display {
def header (xhtml : NodeSeq) : NodeSeq = {
...
// If simple is set, we don’t display complexStuff
S.param("simple").foreach {
S.mapSnippet("complexStuff", ignore => Text(""))
}
}
def complexStuff (xhtml : NodeSeq) : NodeSeq = {
...
}
}
5.3 Snippet Methods
Now that we’ve examined how Lift determines which snippet to execute, let’s look at what a snippet method actually does. A snippet method is essentially a transform, taking a single
scala.xml.NodeSeq↓ argument and returning a
NodeSeq.
Note: Although Scala can often infer return types, it’s important to explicitly specify the return type of your snippet methods as
NodeSeq. Failure to do so may prevent Lift from locating the snippet method if you’re using implicit dispatch (Section
5.2.1↑), in which case the snippet won’t execute!
The argument passed to the snippet method is the XML content of the snippet tag. Because Lift processes XML from the root element down to the child elements (outside-in), the contents of the snippet tag aren’t processed until
after the snippet method processes them. You may reverse the order of processing by specifying the
eager_eval attribute on the tag (Section
5.3.4↓). As an example, let’s say we wanted a snippet that would output the current balance of our ledger, shown in Listing
5.3↓. We simply return an XML Text node with the formatted balance. Note that the XML result from a snippet is itself processed recursively, so the
lift:Util.time snippet will be processed after our snippet method returns.
Returning Tags from a Snippet
class Ledger {
def balance (content : NodeSeq) : NodeSeq =
<p>{currentLedger.formattedBalance}
as of <lift:Util.time /></p>
}
It is this hierarchical processing of template tags that makes Lift so flexible. For those of you coming to Lift with some JSP experience, Lift is designed to let you write something similar to tag libraries, but that are much more powerful and much simpler to use.
5.3.1 Binding Values in Snippets ↓↓
So far we’ve shown our snippets generating complete output and ignoring the input to the method. Lift actually provides some very nice facilities for using the input NodeSeq within your snippet to help keep presentation and code separate. First, remember that the input NodeSeq consists of the child elements for the snippet tag in your template.
<lift:Ledger.balance>
<ledger:balance/> as of <ledger:time />
</lift:Ledger.balance>
For example, given a template containing the snippet tag shown in Listing
5.3.1↑, the Ledger.balance method receives
<ledger:balance/> as of <ledger:time />
as its input parameter. This is perfectly correct XML, although it may look a little strange at first unless you’ve used prefixed elements in XML before. The key is that Lift allows you to selectively “bind”, or replace, these elements with data inside your snippet. The Helpers.bind method takes three arguments:
-
The prefix for the tags you wish to bind, in this instance, “ledger”
-
The NodeSeq that contains the tags you wish to bind
-
One or more BindParam elements that map the tag name to a replacement value
While you can create your own
BindParam instances by hand, we generally recommend importing
Helpers._, which among other things contains a convenient implicit conversion to BindParam using the “->” operator. With this knowledge in hand, we can change our previous definition of the balance method in Listing
5.3↑ to that in Listing
5.3.1↓ below.
Binding the Ledger Balance
class Ledger {
def balance (content : NodeSeq ) : NodeSeq =
bind ("ledger", content,
"balance" -> Text(currentLedger.formattedBalance),
"time" -> Text((new java.util.Date).toString))
}
As you can see here, we actually gain a line of code over our previous effort, but the trade-off makes it far simpler for us to change the layout just by editing the template.
One last aspect of binding that we want to discuss is that any attributes set on the input elements that are being bound will be discarded
↓ if you use the “->” binding operator. See Section
5.4↓ for more details on how you manipulate attributes in bindings, including how you can retain attributes on binding elements from your templates by using the “-%>”
↓ binding operator instead.
5.3.2 CSS Selector Transforms ↓↓↓↓
In addition to the binding support detailed in Section
5.3.1↑, Lift 2.2 introduces binding via CSS transforms as part of its support for designer friendly templates. These allow you to bind values into template XHTML (or HTML5, see Section
4.3 on page 1↑) by using the attributes on specific elements. Let’s start by looking at a basic example, corresponding to the examples in Section
5.3.1↑.
Listing
5.3.2↓ shows a Designer-Friendly version of Listing
5.3.1↑. You can see that we’re invoking the
Ledger.balance snippet via the class attribute, and we’ve specified the binding elements as normal
<span/> elements with
id attributes.
<div class="lift:Ledger.balance">
<span id="balance">$0</span> as of <span id="time">midnight</span>
</div>
Now, we need to perform the CSS transform within our snippet. The binding implicits for CSS transforms are found on the
net.liftweb.util.BindHelpers object/trait, so you should import it (in particular, the
strToCssBindPromoter method). Listing
5.3.2↓ shows how we modify the snippet in Listing
5.3.1↑ to utilize the new CSS transform.
Binding the Ledger Balance with CSS
import net.liftweb.util.BindHelpers._
class Ledger {
def balance = "#balance" #> currentLedger.formattedBalance &
"#time" #> (new java.util.Date).toString
}
As you can see in this example, CSS transforms are comprised of three parts: the transform selector, the transform operator (
#>), and the right hand side value. This value can be a number of different things, which we’ll cover in Section
5.3.2.2↓, but in our case we’re using a
MappedField and a
String. Additionally, you can chain transforms together with the
& operator.
5.3.2.1 CSS Selector Syntax
The selector syntax is based on a subset of CSS, so if you already know that you’re well on your way. The syntax can operate on elements based on id or class, and can also operate on attributes of those elements. Let’s look at the basic syntax:
-
#foo - Selects the element with an id attribute of “foo”
-
.foo - Selects all elements with a CSS class of “foo”
-
@foo - Selects all elements with a name attribute of “foo”
-
attrName=attrValue - Selects all elements with an attribute of “attrName” equal to “attrValue”
-
element - Selects all “element” elements (e.g. span, h1, etc.)
-
:type - Selects all elements with a type of “type”. The type must be one of:
-
button
-
checkbox
-
file
-
password
-
radio
-
reset
-
submit
-
text
-
* - Selects all elements
The element matching the selector is replaced by the result of processing the replacement. That means that in the example of Listing
5.3.2↑ the
span elements will be replaced with straight Text elements, resulting in the markup shown in Listing
5.3.2.1↓ (in other words, no remaining markup).
Sample CSS Transform Result
$12.42 as of Fri Jan 14 08:29:50 MST 2011
You can further refine the replacement with an optional qualifier. We’ve already seen how omitting the qualifer results in wholesale replacement of the matching element, but there are a few additional options:
-
* - Replaces the children of the selected element. For example, if we changed our selector in Listing 5.3.2↑ from “#balance” to “#balance *”, we would be replacing the text node child (“$0”), with the resulting markup:
<span id="balance">$12.42</span> as of Fri Jan 14 08:29:50 MST 2011
Note that when we perform child replacement, the parent’s attributes
↓ are carried over to the resulting element. There is an exception to this in the case of iterated replacements, which we’ll cover in Section
5.3.2.2↓.
-
*+ - Appends to the children of the selected element. For example, given the template
<span id="love">I love </span>
The transform
"#love *+" #> "figs"
Would result in the markup
<span id="love">I love figs</span>
-
-* - Prepends to the children of the selected element. This operates the same as appending.
-
[name] - ↓Sets the value of the ”name” attribute on the selected element. If the attribute already exists on the selected element, its value is replaced, otherwise the attribute is added. For example, if we wanted to replace both the link text and href of a link via CSS transform for the template
<a href="#">ReplaceMe</a>
We could perform this by chaining two selections together, one for the child element (link text) and one for the href attribute:
"a *" #> "This is the link text"&
"a [href]" #> "http://foo.com/bar"
Note that the order of the selections is not important.
-
[name+] - Appends a value to the attribute on the selected element. If the attribute doesn’t already exist on the element then this behaves the same as the [name] qualifier. One example would be adding to the class attribute for a given element:
"tr [class+]" #> (if (index % 2) "odd" else "even")
-
^^ - Makes the selected element the root of the returned elements. This can be used to select a particular element from a template, similar to BindHelpers.chooseTemplate. The right hand side for the selected element is ignored, but you can chain further transforms to modify the returned element. For example, if we decided to only output the balance in Listing 5.3.2↑, we could do so by changing our snippet code to:
def balance = "#balance ^^" #> "ignore" &
"#balance" #> currentLedger.formattedBalance
5.3.2.2 Right Hand Side Values
The right hand side of a CSS transform operates on the selected element to either transform or replace it. It can be one of:
-
String constant - returns a Text node for the String. For example, in Listing 5.3.2↑:
"#time" #> (new java.util.Date).toString
-
A NodeSeq constant - returns the NodeSeq itself. In Listing 5.3.2↑ we could have done this instead:
"#time" #> Text((new java.util.Date).toString)
-
NodeSeq ⇒ NodeSeq - a function that transforms the selected element. Note that CSS transforms extend NodeSeq ⇒ NodeSeq, so you can nest transforms like
// Select the element with id "entry" and then bind its
// nested "name" element
"#entry" #> { "#name" #> account.name }
-
net.liftweb.util.Bindable - Instances that implement the Bindable trait will be automatically converted into a NodeSeq. Among other things, MappedField and Record.Field support this, which allows us to use instances directly:
"#description" #> account.description
-
Boolean, Int, Long or Symbol - These are automatically promoted to a String via the net.liftweb.util.StringPromotable trait and implicits on its companion object
"#viewperm" #> account.isViewableBy(someUser)
-
A Box, Option or Seq of String, NodeSeq, Bindable or values convertable by StringPromotable - These will be converted into a net.liftweb.util.IterableConst, which is used to compute a Seq[NodeSeq]. If your selector replaces the children of the selected element (*), the IterableConst is applied to the selected element once for each item in the Seq. In other words, you get a copy of the selected element for each original input. For example, given the template:
<h2>Account names:</h2>
<ul>
<li id="item">Account</li>
</ul>
We can iterate over a list of accounts with the CSS transform:
"#item *" #> accounts.map(_.name.toString)
Which, assuming a set of accounts named “A”, “B”, and “C”, results in:
<h2>Account names:</h2>
<ul>
<li id="item">A</li>
<li>B</li>
<li>C</li>
</ul>
Note that the id attribute is only placed on the first transformed element. Subsequent replacements strip the id attribute so that it remains unique on the page. This special handling applies only to the id attribute; other attributes, such as class, are not similarly stripped.
Passing in a
None/
Empty will remove the selected element. For example, you can delete an attribute
↓↓ with the following code:
val blank: Option[String] = None
"#thing [class]" #> blank
-
A Box, Option or Seq of NodeSeq ⇒ NodeSeq - These will be converted into a net.liftweb.util.IterableFunc, and follow the same rules for replacement as IterableConst (e.g. child replacement repetition).
5.3.3 Stateless versus Stateful Snippets ↓↓
The lifecycle of a snippet is stateless by default. That means that for each request, Lift creates a new instance of the snippet class to execute (or uses the same staic method if using explicit dispatch, Section
5.2.2↑). Any changes you make to instance variables will be discarded after the request is processed. If you want to keep some state around, you have a couple of options:
-
Store the state in a cookie↓ (Section 3.10↑). This can be useful if you have data that you want to persist across sessions. The down side is that you have to manage the cookie as well as deal with any security implications for the data in the cookie as it’s stored on the user’s machine.
-
Store the state in a SessionVar↓ (Section 3.11↑). This is a little easier to manage than cookies, but you still have to handle adding and removing the session data if you don’t want it around for the duration of the session. As with a cookie, it is global, which means that it will be the same for all snippet instances for a given session.
-
Pass the state around in a RequestVar↓ by setting “injector” functions in your page transition functions (e.g. SHtml.link, S.redirectTo, etc). We cover this technique in Section 3.11↑.
-
Use a StatefulSnippet↓ subclass. This is ideal for small, conversational state, such as a form that spans multiple pages or for a page where you have multiple variables that you want to be able to tweak individually.
Using a
StatefulSnippet is very similar to using a normal snippet but with the addition of a few mechanisms. First, the
StatefulSnippet trait extends
DispatchSnippet (see Section
5.2.1↑), allowing you to define which methods handle which snippets based on the
dispatch method. Because Scala allows
defs to be implemented by
vars in subclasses, we can redefine the dispatch behavior as a result of snippet processing.
Another thing to remember when using StatefulSnippets is that when you render a form, a hidden field is added to the form that permits the same instance of the StatefulSnippet that created the form to be the target of the form submission. If you need to link to a different page, but would like the same snippet instance to handle snippets on that page, use the StatefulSnippet.link method (instead of SHtml.link); similarly, if you need to redirect to a different page, the StatefulSnippet trait defines a redirectTo method. In either of these instances, a function map is added to the link or redirect, respectively, that causes the instance to be reattached.
When might you use a stateful snippet? Consider a multi-part form where you’d like to have a user enter data over several pages. You’ll want the application to maintain the previously entered data while you validate the current entry, but you don’t want to have to deal with a lot of hidden form variables. Using a StatefulSnippet instance greatly simplifies writing the snippet because you can keep all of your pertinent information around as instance variables instead of having to insert and extract them from every request, link, etc.
Listing
5.3.3↓ shows an example of a stateful snippet that handles the above example. Note that for this example, the URL (and therefore, the template) don’t change between pages. The template we use is shown in Listing
5.3.3↓. Remember to call
unregisterThisSnippet() when you’re finished with your workflow in order to stop the current instance from being used.
... standard Lift imports ...
import scala.xml.Text
class BridgeKeeper extends StatefulSnippet {
// Define the dispatch for snippets. Note that we are defining
// it as a var so that the snippet for each portion of the
// multi-part form can update it after validation.
var dispatch : DispatchIt = {
// We default to dispatching the "challenge" snippet to our
// namePage snippet method. We’ll update this below
case "challenge" => firstPage _
}
// Define our state variables:
var (name,quest,color) = ("","","")
// Our first form page
def firstPage (xhtml : NodeSeq) : NodeSeq = {
def processName (nm : String) {
name = nm
if (name != "") {
dispatch = { case "challenge" => questPage _ }
} else {
S.error("You must provide a name!")
}
}
bind("form", xhtml,
"question" -> Text("What is your name?"),
"answer" -> SHtml.text(name, processName))
}
def questPage (xhtml : NodeSeq) : NodeSeq = {
def processQuest (qst : String) {
quest = qst
if (quest != "") {
dispatch = {
case "challenge" if name == "Arthur" => swallowPage _
case "challenge" => colorPage _
}
} else {
S.error("You must provide a quest!")
}
}
bind("form", xhtml,
"question" -> Text("What is your quest?"),
"answer" -> SHtml.text(quest, processQuest))
}
def colorPage (xhtml : NodeSeq) : NodeSeq = {
def processColor (clr : String) {
color = clr
if (color.toLowercase.contains "No,") {
// This is a cleanup that removes the mapping for this
// StatefulSnippet from the session. This will happen
// over time with GC, but it’s best practice to manually
// do this when you’re finished with the snippet
this.unregisterThisSnippet()
S.redirectTo("/pitOfEternalPeril")
} else if (color != "") {
this.unregisterThisSnippet()
S.redirectTo("/scene24")
} else {
S.error("You must provide a color!")
}
}
bind("form", xhtml,
"question" -> Text("What is your favorite color?"),
"answer" -> SHtml.text(color, processColor))
}
// and so on for the swallowPage snippet
...
}
The StatefulSnippet Example Template
<lift:surround with="default" at="content">
<lift:BridgeKeeper.challenge form="POST">
<form:question /> : <form:answer /> <br />
<input type="submit" value="Answer" />
</lift:BridgeKeeper.challenge>
</lift:surround>
If you’re using implicit dispatch (Section
5.2.1↑), then you’re done. If you want to use explicit dispatch
↓↓, however, you need to do a little more work than usual in the
LiftRules.snippetDispatch setup. Listing
5.3.3↓ shows how we can bind our own StatefulSnippet classes without using reflection.
Explicit Dispatch with Stateful Snippets
// In your boot method:
LiftRules.snippetDispatch.append {
// S.snippetForClass checks to see if an instance has already
// registered. This is the case after form submission or when
// we use the StatefulSnippet.link or .redirectTo methods
case "BridgeKeeper" => S.snippetForClass("TestHello") openOr {
// If we haven’t already registered an instance, create one
val inst = new com.test.TestHello
// The name is what Lift uses to locate an instance (S.snippetForClass)
// We need to add it so that the Stateful callback functions can
// self-register
inst.addName("TestHello")
// Register this instance for the duration of the request
S.overrideSnippetForClass("TestHello", inst)
inst
}
5.3.4 Eager Evaluation
As we mentioned in Section
5.3↑, Lift processes the contents of a snippet tag after it processes the tag itself. If you want the contents of a snippet tag to be processed
before the snippet, then you need to specify the
eager_eval↓ attribute on the tag:
<lift:Hello.world eager_eval=”true”>...</lift:Hello.world>
This is especially useful if you’re using an embedded
↓↓↓ template (Section
4.5.7↑). Consider Listing
5.3.4↓: in this case, the
eager_eval parameter makes Lift process the
<lift:embed /> tag before it executes the
Hello.world snippet method. If the “formTemplate” template looks like Listing
5.3.4↓, then the
Hello.world snippet sees the
<hello:name /> and
<hello:time /> XML tags as its
NodeSeq input. If the
eager_eval attribute is removed, however, the Hello.world snippet sees only a
<lift:embed /> tag that will be processed after it returns.
Embedding and eager evaluation
<lift:Hello.world eager_eval="true">
<lift:embed what="formTemplate" />
</lift:Hello.world>
The formTemplate template
<lift:children>
<hello:name />
<hello:time />
</lift:children>
5.4 Handling XHTML Attributes in Snippets↓↓
It’s a common requirement that elements contain XHTML attributes to control things like style, provide an id, register javascript event handlers, and other functionality. Lift provides two main approaches to applying attributes to elements either in your snippet code or directly in the XHTML template.
5.4.1 Direct Manipulation in Code
You can apply attributes directly to XHTML elements using the “%” operator
↓ to apply a
scala.xml.UnprefixedAttribute instance to an element. Lift’s
net.liftweb.util.Helpers trait contains an implicit conversion from a
Pair[String,_] to an
UnprefixedAttribute called
pairToUnprefixed that allows you to use a simpler syntax. You may chain invocations of “%” to apply multiple attributes. For example, Listing
5.4.1↓ shows how you can apply an “id” and “class” attribute to a text box and to a normal paragraph.
Applying Attributes with
%
val myInput = SHtml.text("", processText(_)) % ("id" -> "inputField") %
("class" -> "highlighted")
Note that the % metadata mechanism is actually part of the Scala XML library. Specifically,
scala.xml.Elem has a
% method that allows the user to update the attributes on a given XML element by passing in a
scala.xml.UnprefixedAttribute. We suggest reading more about this in the Scala API documents, or in the Scala XML docbook at
http://burak.emir.googlepages.com/scalaxbook.docbk.html.
5.4.2 XHTML Attribute Pass-through↓↓
The second main approach to modifying XHTML attributes is to specify them directly in your templates. This has the benefit of allowing your template designers to directly manipulate things like style-related attributes and keeping the markup and the logic separate. Listing
5.4.2↓ shows how you can utilize the “-%>”
↓ binding operator instead of “->” to preserve attributes.
// the markup
<lift:Ledger.balance>
<ledger:time id="myId"/>
</lift:Ledger.balance>
// The snippet class
class Ledger {
def balance (content : NodeSeq ) : NodeSeq = {
bind ("ledger", content,
"time" -%> <span>{(new java.util.Date).toString}</span>)
}
}
The resulting node will be something like
<span id=”myId”>Sat Mar 28 16:43:48 EET 2009</span>
In addition to the “-%>” binding operator, there is also the “_id_>”
↓operator, which uses the element’s name as its “id” attribute. Listing shows a snippet method using the “_id_>” attribute and Listing shows the resulting markup.
Binding with the _id_> operator
def idByName (xhtml : NodeSeq) : NodeSeq =
bind("example", xhtml, "name" _id_> <span>Fred</span>)
<!-- Input: -->
<lift:HelloWorld.idByName>
Hi, <example:name />
</lift:HelloWorld.idByName>
<!-- Output: -->
Hi, <span id="name">Fred</span>
(C) 2012 Lift 2.0 EditionWritten by Derek Chen-Becker, Marius Danciu and Tyler Weir