Permalink
Find file
Fetching contributors…
Cannot retrieve contributors at this time
5232 lines (3744 sloc) 83.4 KB
#LyX 1.6.7 created this file. For more info see http://www.lyx.org/
\lyxformat 345
\begin_document
\begin_header
\textclass book
\use_default_options true
\language english
\inputencoding auto
\font_roman palatino
\font_sans helvet
\font_typewriter courier
\font_default_family default
\font_sc false
\font_osf false
\font_sf_scale 100
\font_tt_scale 100
\graphics default
\paperfontsize default
\spacing single
\use_hyperref false
\papersize default
\use_geometry false
\use_amsmath 1
\use_esint 1
\cite_engine basic
\use_bibtopic false
\paperorientation portrait
\secnumdepth 3
\tocdepth 3
\paragraph_separation indent
\defskip medskip
\quotes_language english
\papercolumns 1
\papersides 1
\paperpagestyle default
\tracking_changes false
\output_changes false
\author ""
\author ""
\end_header
\begin_body
\begin_layout Chapter
Snippets
\begin_inset Index
status open
\begin_layout Plain Layout
Snippets
\end_layout
\end_inset
\begin_inset CommandInset label
LatexCommand label
name "cha:Snippets"
\end_inset
\end_layout
\begin_layout Standard
Put simply, a snippet is a Scala method that transforms input XML into output
XML
\begin_inset Index
status open
\begin_layout Plain Layout
XML
\end_layout
\end_inset
.
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
\begin_inset Index
status open
\begin_layout Plain Layout
View-First
\end_layout
\end_inset
rendering architecture.
Although snippets aren't the only mechanism Lift has for rendering page
views (see Views, Section
\begin_inset CommandInset ref
LatexCommand vref
reference "sec:Views"
\end_inset
, Custom Dispatch, Section
\begin_inset CommandInset ref
LatexCommand vref
reference "sec:Custom-dispatch-func"
\end_inset
, or even the REST API, Chapter
\begin_inset CommandInset ref
LatexCommand ref
reference "cha:Web-Services"
\end_inset
), they're so widely used and so important that we feel they warrant their
own chapter.
\end_layout
\begin_layout Standard
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.
\end_layout
\begin_layout Section
The Snippet Tag
\begin_inset Index
status open
\begin_layout Plain Layout
snippet
\end_layout
\end_inset
\begin_inset Index
status open
\begin_layout Plain Layout
Tags ! snippet
\end_layout
\end_inset
\begin_inset CommandInset label
LatexCommand label
name "sec:The-Snippet-Tag"
\end_inset
\begin_inset CommandInset label
LatexCommand label
name "sub:snippet-tag"
\end_inset
\end_layout
\begin_layout LyX-Code
\family typewriter
Usage: <lift:snippet type="snippetName" ...options...
/>
\end_layout
\begin_layout LyX-Code
<lift:snippetName ...options...
/>
\end_layout
\begin_layout LyX-Code
<div class=
\begin_inset Quotes erd
\end_inset
lift:snippetName?opt1=...;opt2=...;opt3=...
\begin_inset Quotes erd
\end_inset
/>
\end_layout
\begin_layout Standard
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
\begin_inset CommandInset ref
LatexCommand ref
reference "sub:Snippet-Dispatch"
\end_inset
.
\end_layout
\begin_layout Standard
\align center
\begin_inset Box Shadowbox
position "t"
hor_pos "c"
has_inner_box 1
inner_pos "t"
use_parbox 0
width "75col%"
special "none"
height "1in"
height_special "totalheight"
status open
\begin_layout Plain Layout
Note that there is a shorthand for the
\family typewriter
type
\family default
attribute simply by appending the snippet name after the
\family typewriter
lift:
\family default
prefix.
If you use this shorthand, make sure to avoid naming your snippets the
same as Lift's built-in tags, such as
\family typewriter
surround
\family default
,
\family typewriter
children
\family default
,
\family typewriter
embed
\family default
, etc.
\end_layout
\end_inset
\end_layout
\begin_layout Standard
In addition to the the
\family typewriter
type
\family default
attribute, Lift will process several other options:
\end_layout
\begin_layout Description
form If the
\family typewriter
form
\begin_inset Index
status open
\begin_layout Plain Layout
Forms ! snippet tag
\end_layout
\end_inset
\family default
attribute is included with a value of either
\begin_inset Quotes eld
\end_inset
POST
\begin_inset Quotes erd
\end_inset
or
\begin_inset Quotes eld
\end_inset
GET
\begin_inset Quotes erd
\end_inset
, 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.
\end_layout
\begin_layout Description
multipart
\begin_inset Index
status open
\begin_layout Plain Layout
Forms ! multipart
\end_layout
\end_inset
\begin_inset Index
status open
\begin_layout Plain Layout
Forms ! file upload
\end_layout
\end_inset
The
\family typewriter
multipart
\family default
attribute is a boolean (the default is false, specify
\begin_inset Quotes eld
\end_inset
yes
\begin_inset Quotes erd
\end_inset
,
\begin_inset Quotes eld
\end_inset
true
\begin_inset Quotes erd
\end_inset
or
\begin_inset Quotes eld
\end_inset
1
\begin_inset Quotes erd
\end_inset
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
\begin_inset CommandInset ref
LatexCommand ref
reference "sec:File-Uploads"
\end_inset
).
If you don't also specify the
\family typewriter
form
\family default
attribute then this won't do anything.
\end_layout
\begin_layout Description
eager_eval
\begin_inset Index
status open
\begin_layout Plain Layout
Snippets ! eager_eval
\end_layout
\end_inset
The eager_eval attribute is a boolean (the default is false, specify
\begin_inset Quotes eld
\end_inset
yes
\begin_inset Quotes erd
\end_inset
,
\begin_inset Quotes eld
\end_inset
true
\begin_inset Quotes erd
\end_inset
or
\begin_inset Quotes eld
\end_inset
1
\begin_inset Quotes erd
\end_inset
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
\begin_inset CommandInset ref
LatexCommand ref
reference "sub:Eager-Eval"
\end_inset
.
\end_layout
\begin_layout Standard
With Lift 2.2's Designer-Friendly Templates (Section
\begin_inset CommandInset ref
LatexCommand vref
reference "sec:Designer-Friendly-Templates"
\end_inset
), 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
\begin_inset CommandInset ref
LatexCommand ref
reference "lst:Invoking-Snippets-Via-Class"
\end_inset
shows an example of how we can use the standard
\family typewriter
lift:surround
\family default
processing by modiying the
\family typewriter
class
\family default
of our content element.
\end_layout
\begin_layout Standard
\begin_inset listings
inline false
status open
\begin_layout Plain Layout
\begin_inset Caption
\begin_layout Plain Layout
Invoking Snippets Via the Class Attribute
\begin_inset CommandInset label
LatexCommand label
name "lst:Invoking-Snippets-Via-Class"
\end_inset
\end_layout
\end_inset
\end_layout
\begin_layout Plain Layout
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
\end_layout
\begin_layout Plain Layout
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
\end_layout
\begin_layout Plain Layout
<html xmlns="http://www.w3.org/1999/xhtml">
\end_layout
\begin_layout Plain Layout
<head>
\end_layout
\begin_layout Plain Layout
<title>Not really</title>
\end_layout
\begin_layout Plain Layout
</head>
\end_layout
\begin_layout Plain Layout
<body class="lift:content_id=real_content">
\end_layout
\begin_layout Plain Layout
<div class="lift:surround?with=default;at=content" id="real_content">
\end_layout
\begin_layout Plain Layout
<h1>Welcome to your project!</h1>
\end_layout
\begin_layout Plain Layout
</div>
\end_layout
\begin_layout Plain Layout
</body>
\end_layout
\begin_layout Plain Layout
</html>
\end_layout
\end_inset
\end_layout
\begin_layout Section
Snippet Dispatch
\begin_inset CommandInset label
LatexCommand label
name "sub:Snippet-Dispatch"
\end_inset
\begin_inset Index
status open
\begin_layout Plain Layout
Snippets ! Dispatch
\end_layout
\end_inset
\end_layout
\begin_layout Standard
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
\family typewriter
S.mapSnippet
\family default
\begin_inset Index
status open
\begin_layout Plain Layout
mapSnippet
\end_layout
\end_inset
.
We'll cover each in the following sections.
\end_layout
\begin_layout Subsection
Implicit Dispatch Via Reflection
\begin_inset Index
status open
\begin_layout Plain Layout
Reflection
\end_layout
\end_inset
\begin_inset Index
status open
\begin_layout Plain Layout
Snippets ! Implicit dispatch
\end_layout
\end_inset
\begin_inset CommandInset label
LatexCommand label
name "sub:Implicit-Dispatch-Via"
\end_inset
\end_layout
\begin_layout Standard
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
\begin_inset CommandInset ref
LatexCommand ref
reference "sub:Stateless-versus-Stateful"
\end_inset
), 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:
\end_layout
\begin_layout Enumerate
Via the
\family typewriter
type
\family default
attribute on the snippet tag.
The value should be
\begin_inset Quotes eld
\end_inset
\family typewriter
ClassName
\family default
:
\family typewriter
method
\family default
\begin_inset Quotes erd
\end_inset
for the particular snippet method you want to have handle the tag
\end_layout
\begin_layout Enumerate
Via a tag suffix of
\family typewriter
Class.method
\family default
.
This is the same as specifying the
\family typewriter
type=
\begin_inset Quotes erd
\end_inset
Class:method
\begin_inset Quotes erd
\end_inset
\family default
attribute
\end_layout
\begin_layout Enumerate
Via a tag suffix of just
\family typewriter
Class
\family default
.
This will use the
\family typewriter
render
\family default
method on the specified class to handle the tag
\end_layout
\begin_layout Standard
Classes are resolved as specified in Section
\begin_inset CommandInset ref
LatexCommand ref
reference "sub:Class-Resolution"
\end_inset
.
\end_layout
\begin_layout Standard
\align center
\begin_inset Box Shadowbox
position "t"
hor_pos "c"
has_inner_box 1
inner_pos "t"
use_parbox 0
width "75col%"
special "none"
height "1in"
height_special "totalheight"
status open
\begin_layout Plain Layout
The most important thing to remember when using implicit dispatch is that
your snippet classes must be members of a
\family typewriter
snippet
\family default
subpackage as registered by
\family typewriter
LiftRules.addToPackages
\family default
.
For example, if you have
\family typewriter
LiftRules.addToPackages(
\begin_inset Quotes eld
\end_inset
com.foo
\begin_inset Quotes erd
\end_inset
)
\family default
in your
\family typewriter
Boot.boot
\family default
method, snippets should be members of
\family typewriter
com.foo.snippet
\family default
.
\end_layout
\end_inset
\end_layout
\begin_layout Standard
Listing
\begin_inset CommandInset ref
LatexCommand ref
reference "lst:Snippet-tag-equivalence"
\end_inset
shows three equivalent snippet tags.
Note: these are only equivalent because the method name is
\begin_inset Quotes eld
\end_inset
render.
\begin_inset Quotes erd
\end_inset
If we had chosen a different method, e.g.,
\begin_inset Quotes eld
\end_inset
list,
\begin_inset Quotes erd
\end_inset
then the third example below will still call a
\begin_inset Quotes eld
\end_inset
render
\begin_inset Quotes erd
\end_inset
method.
\end_layout
\begin_layout Standard
\align center
\family typewriter
\begin_inset Box Shadowbox
position "t"
hor_pos "c"
has_inner_box 1
inner_pos "t"
use_parbox 0
width "75col%"
special "none"
height "1in"
height_special "totalheight"
status open
\begin_layout Plain Layout
It's important to note that with pure implicit dispatch, Java's reflection
allows access to
\emph on
any
\emph default
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.
\end_layout
\begin_layout Plain Layout
Another important note is that lookup via reflection is relatively expensive
\begin_inset Foot
status open
\begin_layout Plain Layout
See
\begin_inset Flex URL
status collapsed
\begin_layout Plain Layout
http://www.jguru.com/faq/view.jsp?EID=246569
\end_layout
\end_inset
for a more thorough explanation
\end_layout
\end_inset
operation, yet another reason that we recommend explicit dispatch for productio
n sites.
\end_layout
\end_inset
\end_layout
\begin_layout Standard
\begin_inset listings
inline false
status open
\begin_layout Plain Layout
\begin_inset Caption
\begin_layout Plain Layout
Snippet Tag Equivalence
\begin_inset CommandInset label
LatexCommand label
name "lst:Snippet-tag-equivalence"
\end_inset
\end_layout
\end_inset
\end_layout
\begin_layout Plain Layout
<lift:snippet type="MyClass:render" />
\end_layout
\begin_layout Plain Layout
<lift:MyClass.render />
\end_layout
\begin_layout Plain Layout
<lift:MyClass />
\end_layout
\end_inset
\end_layout
\begin_layout Standard
In addition to
\begin_inset Quotes eld
\end_inset
pure
\begin_inset Quotes erd
\end_inset
implicit dispatch, you can exert a little more control on which method
in a given class handles a snippet by implementing the
\family typewriter
net.liftweb.http.DispatchSnippet
\begin_inset Index
status open
\begin_layout Plain Layout
DispatchSnippet
\end_layout
\end_inset
\family default
trait.
This trait contains a single method, dispatch, of type
\begin_inset Formula $PartialFunction[String,NodeSeq\Rightarrow NodeSeq]$
\end_inset
that maps the method name (the
\begin_inset Quotes eld
\end_inset
method
\begin_inset Quotes erd
\end_inset
part of
\begin_inset Quotes eld
\end_inset
Class.method
\begin_inset Quotes erd
\end_inset
or
\begin_inset Quotes eld
\end_inset
Class:method
\begin_inset Quotes erd
\end_inset
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
\begin_inset CommandInset ref
LatexCommand ref
reference "lst:Using-DispatchSnippet-to-select"
\end_inset
shows how you can control the dispatch by providing a custom
\family typewriter
dispatch
\family default
def.
\end_layout
\begin_layout Standard
\begin_inset listings
inline false
status open
\begin_layout Plain Layout
\begin_inset Caption
\begin_layout Plain Layout
Using DispatchSnippet to Control Snippet Method Selection
\begin_inset CommandInset label
LatexCommand label
name "lst:Using-DispatchSnippet-to-select"
\end_inset
\end_layout
\end_inset
\end_layout
\begin_layout Plain Layout
package com.foo.snippet
\end_layout
\begin_layout Plain Layout
\end_layout
\begin_layout Plain Layout
import scala.xml.{NodeSeq,Text}
\end_layout
\begin_layout Plain Layout
import net.liftweb.http.DispatchSnippet
\end_layout
\begin_layout Plain Layout
\end_layout
\begin_layout Plain Layout
class SomeSnippetClass extends DispatchSnippet {
\end_layout
\begin_layout Plain Layout
def dispatch : DispatchIt = {
\end_layout
\begin_layout Plain Layout
// We have to use a partially-applied (trailing "_") version
\end_layout
\begin_layout Plain Layout
// of the functions that we dispatch to
\end_layout
\begin_layout Plain Layout
case "foo" => myFooMethod _
\end_layout
\begin_layout Plain Layout
case "bar" => someOtherBarMethod _
\end_layout
\begin_layout Plain Layout
case _ => catchAllMethod _
\end_layout
\begin_layout Plain Layout
}
\end_layout
\begin_layout Plain Layout
\end_layout
\begin_layout Plain Layout
def myFooMethod (xhtml : NodeSeq) : NodeSeq = { ...
}
\end_layout
\begin_layout Plain Layout
def someOtherBarMethod (xhtml : NodeSeq) : NodeSeq = { ...
}
\end_layout
\begin_layout Plain Layout
def catchAllMethod(xhtml : NodeSeq) : NodeSeq = Text("You're being naughty!")
\end_layout
\begin_layout Plain Layout
}
\end_layout
\end_inset
\end_layout
\begin_layout Standard
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
\family typewriter
DispatchSnippet
\begin_inset Index
status open
\begin_layout Plain Layout
DispatchSnippet
\end_layout
\end_inset
\family default
trait.
Even with
\family typewriter
DispatchSnippet
\family default
, however, the implicit class resolution still uses reflection, so if you're
trying to make things performant you should use explicit dispatch instead.
\end_layout
\begin_layout Subsection
Explicit Dispatch
\begin_inset Index
status open
\begin_layout Plain Layout
Snippets ! Explicit Dispatch
\end_layout
\end_inset
\begin_inset CommandInset label
LatexCommand label
name "sub:Explicit-Dispatch"
\end_inset
\end_layout
\begin_layout Standard
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
\family typewriter
LiftRules.snippetDispatch
\family default
, which points Lift to
\family typewriter
DispatchSnippet
\family default
instances, and
\family typewriter
LiftRules.snippets
\family default
, which points Lift directly at methods.
\end_layout
\begin_layout Standard
Let's first take a look at
\family typewriter
LiftRules.snippetDispatch
\family default
, the more generic option.
When a snippet tag is encountered with a snippet name of the form
\family typewriter
A.B
\family default
or
\family typewriter
A:B
\family default
, Lift will take the first portion (
\family typewriter
A
\family default
) and use that as the lookup for
\family typewriter
snippetDispatch
\family default
.
The
\family typewriter
PartialFunction
\family default
needs to return an instance of
\family typewriter
DispatchSnippet
\family default
, so typically you will implement your explicit dispatch snippets using
an
\family typewriter
object
\family default
instead of a
\family typewriter
class
\family default
.
Listing
\begin_inset CommandInset ref
LatexCommand ref
reference "lst:Defining-an-Explicit-snippet"
\end_inset
shows how we define our object.
Note that the
\family typewriter
dispatch
\family default
method will be executed with the
\begin_inset Quotes eld
\end_inset
\family typewriter
B
\family default
\begin_inset Quotes erd
\end_inset
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
\begin_inset CommandInset ref
LatexCommand ref
reference "lst:Using-DispatchSnippet-to-select"
\end_inset
.
\begin_inset listings
inline false
status open
\begin_layout Plain Layout
\begin_inset Caption
\begin_layout Plain Layout
Defining an Explicit Snippet Object
\begin_inset CommandInset label
LatexCommand label
name "lst:Defining-an-Explicit-snippet"
\end_inset
\end_layout
\end_inset
\end_layout
\begin_layout Plain Layout
// The package *doesn't* need to be "snippet" because there's
\end_layout
\begin_layout Plain Layout
// no reflection involved here
\end_layout
\begin_layout Plain Layout
package com.foo.logic
\end_layout
\begin_layout Plain Layout
\end_layout
\begin_layout Plain Layout
import scala.xml.{NodeSeq,Text}
\end_layout
\begin_layout Plain Layout
import net.liftweb.http.DispatchSnippet
\end_layout
\begin_layout Plain Layout
\end_layout
\begin_layout Plain Layout
object HelloWorld extends DispatchSnippet {
\end_layout
\begin_layout Plain Layout
// We define dispatch as a val so that it doesn't get re-created
\end_layout
\begin_layout Plain Layout
// on each request
\end_layout
\begin_layout Plain Layout
val dispatch : DispatchIt = {
\end_layout
\begin_layout Plain Layout
case name => render(name) _
\end_layout
\begin_layout Plain Layout
}
\end_layout
\begin_layout Plain Layout
\end_layout
\begin_layout Plain Layout
def render (name : String)(ignore : NodeSeq) : NodeSeq =
\end_layout
\begin_layout Plain Layout
Text("Hello, world! Invoked as " + name)
\end_layout
\begin_layout Plain Layout
}
\end_layout
\end_inset
\end_layout
\begin_layout Standard
Now that we have our snippet object, we can bind it to a particular snippet
name in our
\family typewriter
Boot.boot
\family default
method, as shown in Listing
\begin_inset CommandInset ref
LatexCommand ref
reference "lst:Binding-Our-Explicit-snippet"
\end_inset
.
It's interesting to note that this is actually how Lift defines many of
its tags, such as
\family typewriter
<lift:embed/>
\family default
,
\family typewriter
<lift:surround/>
\family default
, and
\family typewriter
<lift:comet/>
\family default
.
In our case, we've bound our snippet object to
\family typewriter
<lift:HelloWorld/>
\family default
, and because our
\family typewriter
DispatchSnippet
\family default
uses a simple variable binding for its
\family typewriter
dispatch
\family default
method case, we can invoke the same snippet with
\family typewriter
<lift:HelloWorld.hey />
\family default
,
\family typewriter
<lift:HelloWorld.useless/>
\family default
, or even
\family typewriter
<lift:HelloWorld.this_is_getting_silly/>
\family default
, and the snippet will tell us what name it was invoked with (
\family typewriter
<lift:HelloWorld/>
\family default
will invoke with the name
\begin_inset Quotes eld
\end_inset
render
\begin_inset Quotes erd
\end_inset
, following Lift's normal snippet tag conventions).
Noe that if you're setting up a dispatch for a
\family typewriter
StatefulSnippet
\family default
\begin_inset Index
status open
\begin_layout Plain Layout
StatefulSnippet
\end_layout
\end_inset
\begin_inset Index
status open
\begin_layout Plain Layout
Snippets ! stateful
\end_layout
\end_inset
, return a new instance of your
\family typewriter
StatefulSnippet
\family default
class.
\family typewriter
StatefulSnippet
\family default
instances will properly register themselves ahead of the
\family typewriter
snippetDispatch
\family default
partial function on each successive request.
\end_layout
\begin_layout Standard
\begin_inset listings
inline false
status open
\begin_layout Plain Layout
\begin_inset Caption
\begin_layout Plain Layout
Binding Our Explicit Snippet Object
\begin_inset CommandInset label
LatexCommand label
name "lst:Binding-Our-Explicit-snippet"
\end_inset
\end_layout
\end_inset
\end_layout
\begin_layout Plain Layout
class Boot {
\end_layout
\begin_layout Plain Layout
def boot {
\end_layout
\begin_layout Plain Layout
...
\end_layout
\begin_layout Plain Layout
LiftRules.snippetDispatch.append {
\end_layout
\begin_layout Plain Layout
case "HelloWorld" => com.foo.logic.HelloWorld
\end_layout
\begin_layout Plain Layout
// For StatefulSnippets, return a *new instance*
\end_layout
\begin_layout Plain Layout
case "HelloConversation" =>
\end_layout
\begin_layout Plain Layout
new com.foo.logic.StatefulHelloWorld
\end_layout
\begin_layout Plain Layout
}
\end_layout
\begin_layout Plain Layout
}
\end_layout
\begin_layout Plain Layout
}
\end_layout
\end_inset
\end_layout
\begin_layout Standard
Now let's look at
\family typewriter
LiftRules.snippets
\family default
.
This is a more fine-grained approach to explicit dispatch that doesn't
require the
\family typewriter
DispatchSnippet
\family default
trait.
Instead, we bind a list of snippet name components corresponding to the
parts of the snippet name separated by either
\begin_inset Quotes eld
\end_inset
:
\begin_inset Quotes erd
\end_inset
or
\begin_inset Quotes eld
\end_inset
.
\begin_inset Quotes erd
\end_inset
, and point it directly at a given snippet method.
Assuming we're using the same snippet object in Listing
\begin_inset CommandInset ref
LatexCommand ref
reference "lst:Defining-an-Explicit-snippet"
\end_inset
, we can bind the
\family typewriter
<lift:HelloWorld/>
\family default
tag by setting up
\family typewriter
LiftRules.snippets
\family default
in our Boot.boot method as shown in Listing
\begin_inset CommandInset ref
LatexCommand ref
reference "lst:Explicitly-Binding-a-snippet"
\end_inset
.
Notice that in order to bind the same way that we did with
\family typewriter
snippetDispatch
\family default
, 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.
\begin_inset listings
inline false
status open
\begin_layout Plain Layout
\begin_inset Caption
\begin_layout Plain Layout
Explicitly Binding a Snippet Method
\begin_inset CommandInset label
LatexCommand label
name "lst:Explicitly-Binding-a-snippet"
\end_inset
\end_layout
\end_inset
\end_layout
\begin_layout Plain Layout
import com.foo.logic
\end_layout
\begin_layout Plain Layout
class Boot {
\end_layout
\begin_layout Plain Layout
def boot {
\end_layout
\begin_layout Plain Layout
...
\end_layout
\begin_layout Plain Layout
LiftRules.snippets.append {
\end_layout
\begin_layout Plain Layout
// Matches a tag without a suffix (<lift:HelloWorld />)
\end_layout
\begin_layout Plain Layout
case List("HelloWorld") => HelloWorld.render("no name") _
\end_layout
\begin_layout Plain Layout
case List("HelloWorld", name) => HelloWorld.render(name) _
\end_layout
\begin_layout Plain Layout
}
\end_layout
\begin_layout Plain Layout
}
\end_layout
\begin_layout Plain Layout
}
\end_layout
\end_inset
\end_layout
\begin_layout Subsection
Per-request Remapping
\begin_inset Index
status open
\begin_layout Plain Layout
Snippets ! Per-request remapping
\end_layout
\end_inset
\end_layout
\begin_layout Standard
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
\begin_inset CommandInset ref
LatexCommand ref
reference "lst:Remapping-A-Snippet"
\end_inset
shows how we can conditionally
\begin_inset Quotes eld
\end_inset
blank
\begin_inset Quotes erd
\end_inset
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.
\end_layout
\begin_layout Standard
\begin_inset listings
inline false
status open
\begin_layout Plain Layout
\begin_inset Caption
\begin_layout Plain Layout
Remapping A Snippet
\begin_inset CommandInset label
LatexCommand label
name "lst:Remapping-A-Snippet"
\end_inset
\end_layout
\end_inset
\end_layout
\begin_layout Plain Layout
import scala.xml.NodeSeq
\end_layout
\begin_layout Plain Layout
import net.liftweb.http.S
\end_layout
\begin_layout Plain Layout
\end_layout
\begin_layout Plain Layout
class Display {
\end_layout
\begin_layout Plain Layout
def header (xhtml : NodeSeq) : NodeSeq = {
\end_layout
\begin_layout Plain Layout
...
\end_layout
\begin_layout Plain Layout
// If simple is set, we don't display complexStuff
\end_layout
\begin_layout Plain Layout
S.param("simple").foreach {
\end_layout
\begin_layout Plain Layout
S.mapSnippet("complexStuff", ignore => Text(""))
\end_layout
\begin_layout Plain Layout
}
\end_layout
\begin_layout Plain Layout
}
\end_layout
\begin_layout Plain Layout
\end_layout
\begin_layout Plain Layout
def complexStuff (xhtml : NodeSeq) : NodeSeq = {
\end_layout
\begin_layout Plain Layout
...
\end_layout
\begin_layout Plain Layout
}
\end_layout
\begin_layout Plain Layout
}
\end_layout
\end_inset
\end_layout
\begin_layout Section
Snippet Methods
\begin_inset CommandInset label
LatexCommand label
name "sec:Snippets"
\end_inset
\begin_inset Note Note
status open
\begin_layout Plain Layout
Must revise from here down
\end_layout
\end_inset
\end_layout
\begin_layout Standard
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
\family typewriter
scala.xml.NodeSeq
\family default
\begin_inset Index
status collapsed
\begin_layout Plain Layout
NodeSeq
\end_layout
\end_inset
argument and returning a
\family typewriter
NodeSeq
\family default
.
\end_layout
\begin_layout Standard
\align center
\begin_inset Box Shadowbox
position "t"
hor_pos "c"
has_inner_box 1
inner_pos "t"
use_parbox 0
width "75col%"
special "none"
height "1in"
height_special "totalheight"
status open
\begin_layout Plain Layout
Note: Although Scala can often infer return types, it's important to explicitly
specify the return type of your snippet methods as
\family typewriter
NodeSeq
\family default
.
Failure to do so may prevent Lift from locating the snippet method if you're
using implicit dispatch (Section
\begin_inset CommandInset ref
LatexCommand ref
reference "sub:Implicit-Dispatch-Via"
\end_inset
), in which case the snippet won't execute!
\end_layout
\end_inset
\end_layout
\begin_layout Standard
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
\emph on
after
\emph default
the snippet method processes them.
You may reverse the order of processing by specifying the
\family typewriter
eager_eval
\family default
attribute on the tag (Section
\begin_inset CommandInset ref
LatexCommand ref
reference "sub:Eager-Eval"
\end_inset
).
As an example, let's say we wanted a snippet that would output the current
balance of our ledger, shown in Listing
\begin_inset CommandInset ref
LatexCommand ref
reference "lst:Returning-tags-snippet"
\end_inset
.
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
\family typewriter
lift:Util.time
\family default
snippet will be processed after our snippet method returns.
\end_layout
\begin_layout Standard
\begin_inset listings
inline false
status open
\begin_layout Plain Layout
\begin_inset Caption
\begin_layout Plain Layout
Returning Tags from a Snippet
\begin_inset CommandInset label
LatexCommand label
name "lst:Returning-tags-snippet"
\end_inset
\end_layout
\end_inset
\end_layout
\begin_layout Plain Layout
class Ledger {
\end_layout
\begin_layout Plain Layout
def balance (content : NodeSeq) : NodeSeq =
\end_layout
\begin_layout Plain Layout
<p>{currentLedger.formattedBalance}
\end_layout
\begin_layout Plain Layout
as of <lift:Util.time /></p>
\end_layout
\begin_layout Plain Layout
}
\end_layout
\end_inset
\end_layout
\begin_layout Standard
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.
\end_layout
\begin_layout Subsection
Binding Values in Snippets
\begin_inset CommandInset label
LatexCommand label
name "sub:Binding-Values-in-snippets"
\end_inset
\begin_inset Index
status open
\begin_layout Plain Layout
Binding
\end_layout
\end_inset
\begin_inset Index
status open
\begin_layout Plain Layout
Snippets ! binding
\end_layout
\end_inset
\end_layout
\begin_layout Standard
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
\family typewriter
NodeSeq
\family default
within your snippet to help keep presentation and code separate.
First, remember that the input
\family typewriter
NodeSeq
\family default
consists of the child elements for the snippet tag in your template.
\end_layout
\begin_layout Standard
\begin_inset listings
inline false
status open
\begin_layout Plain Layout
\begin_inset Caption
\begin_layout Plain Layout
Snippet Tag Children
\begin_inset CommandInset label
LatexCommand label
name "lst:Snippet-tag-children"
\end_inset
\end_layout
\end_inset
\end_layout
\begin_layout Plain Layout
<lift:Ledger.balance>
\end_layout
\begin_layout Plain Layout
<ledger:balance/> as of <ledger:time />
\end_layout
\begin_layout Plain Layout
</lift:Ledger.balance>
\end_layout
\end_inset
\end_layout
\begin_layout Standard
For example, given a template containing the snippet tag shown in Listing
\begin_inset CommandInset ref
LatexCommand ref
reference "lst:Snippet-tag-children"
\end_inset
, the Ledger.balance method receives
\end_layout
\begin_layout LyX-Code
<ledger:balance/> as of <ledger:time />
\end_layout
\begin_layout Standard
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
\begin_inset Quotes eld
\end_inset
bind
\begin_inset Quotes erd
\end_inset
, or replace, these elements with data inside your snippet.
The
\family typewriter
Helpers.bind
\family default
\begin_inset Foot
status open
\begin_layout Plain Layout
net.liftweb.util.Helpers.
Technically the bind method is overloaded, and can even fill in values
for the lift:bind tag, but this is advanced usage and we're not going to
cover that here.
\end_layout
\end_inset
method takes three arguments:
\end_layout
\begin_layout Enumerate
The prefix for the tags you wish to bind, in this instance,
\begin_inset Quotes eld
\end_inset
ledger
\begin_inset Quotes erd
\end_inset
\end_layout
\begin_layout Enumerate
The
\family typewriter
NodeSeq
\family default
that contains the tags you wish to bind
\end_layout
\begin_layout Enumerate
One or more
\family typewriter
BindParam
\family default
elements that map the tag name to a replacement value
\end_layout
\begin_layout Standard
While you can create your own
\family typewriter
BindParam
\family default
instances by hand, we generally recommend importing
\family typewriter
Helpers._
\family default
, which among other things contains a convenient implicit conversion to
BindParam using the
\begin_inset Quotes eld
\end_inset
->
\begin_inset Quotes erd
\end_inset
operator.
With this knowledge in hand, we can change our previous definition of the
balance method in Listing
\begin_inset CommandInset ref
LatexCommand ref
reference "lst:Returning-tags-snippet"
\end_inset
to that in Listing
\begin_inset CommandInset ref
LatexCommand ref
reference "lst:Binding-the-ledger-balance"
\end_inset
below.
\end_layout
\begin_layout Standard
\begin_inset listings
inline false
status open
\begin_layout Plain Layout
\begin_inset Caption
\begin_layout Plain Layout
Binding the Ledger Balance
\begin_inset CommandInset label
LatexCommand label
name "lst:Binding-the-ledger-balance"
\end_inset
\end_layout
\end_inset
\end_layout
\begin_layout Plain Layout
class Ledger {
\end_layout
\begin_layout Plain Layout
def balance (content : NodeSeq ) : NodeSeq =
\end_layout
\begin_layout Plain Layout
bind ("ledger", content,
\end_layout
\begin_layout Plain Layout
"balance" -> Text(currentLedger.formattedBalance),
\end_layout
\begin_layout Plain Layout
"time" -> Text((new java.util.Date).toString))
\end_layout
\begin_layout Plain Layout
}
\end_layout
\end_inset
\end_layout
\begin_layout Standard
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.
\end_layout
\begin_layout Standard
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
\begin_inset Index
status open
\begin_layout Plain Layout
Attributes ! discarded in bind
\end_layout
\end_inset
if you use the
\begin_inset Quotes eld
\end_inset
->
\begin_inset Quotes erd
\end_inset
binding operator.
See Section
\begin_inset CommandInset ref
LatexCommand ref
reference "sec:Handling-XHTML-Attributes"
\end_inset
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
\begin_inset Quotes eld
\end_inset
-%>
\begin_inset Quotes erd
\end_inset
\begin_inset Index
status open
\begin_layout Plain Layout
Attributes ! -%> operator
\end_layout
\end_inset
binding operator instead.
\end_layout
\begin_layout Subsection
CSS Selector Transforms
\begin_inset CommandInset label
LatexCommand label
name "sub:CSS-Selector-Transforms"
\end_inset
\begin_inset Index
status open
\begin_layout Plain Layout
CSS ! selector transforms
\end_layout
\end_inset
\begin_inset Index
status open
\begin_layout Plain Layout
Binding ! with CSS
\end_layout
\end_inset
\begin_inset Index
status open
\begin_layout Plain Layout
Snippets ! binding with CSS
\end_layout
\end_inset
\begin_inset Index
status open
\begin_layout Plain Layout
CSS Transforms
\end_layout
\end_inset
\end_layout
\begin_layout Standard
In addition to the binding support detailed in Section
\begin_inset CommandInset ref
LatexCommand ref
reference "sub:Binding-Values-in-snippets"
\end_inset
, 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
\begin_inset CommandInset ref
LatexCommand vref
reference "sec:HTML5-Support"
\end_inset
) by using the attributes on specific elements.
Let's start by looking at a basic example, corresponding to the examples
in Section
\begin_inset CommandInset ref
LatexCommand ref
reference "sub:Binding-Values-in-snippets"
\end_inset
.
\end_layout
\begin_layout Standard
Listing
\begin_inset CommandInset ref
LatexCommand ref
reference "lst:A-Simple-CSS-snippet"
\end_inset
shows a Designer-Friendly version of Listing
\begin_inset CommandInset ref
LatexCommand ref
reference "lst:Snippet-tag-children"
\end_inset
.
You can see that we're invoking the
\family typewriter
Ledger.balance
\family default
snippet via the class attribute, and we've specified the binding elements
as normal
\family typewriter
<span/>
\family default
elements with
\family typewriter
id
\family default
attributes.
\end_layout
\begin_layout Standard
\begin_inset listings
inline false
status open
\begin_layout Plain Layout
\begin_inset Caption
\begin_layout Plain Layout
A Simple CSS Snippet
\begin_inset CommandInset label
LatexCommand label
name "lst:A-Simple-CSS-snippet"
\end_inset
\end_layout
\end_inset
\end_layout
\begin_layout Plain Layout
<div class="lift:Ledger.balance">
\end_layout
\begin_layout Plain Layout
<span id="balance">$0</span> as of <span id="time">midnight</span>
\end_layout
\begin_layout Plain Layout
</div>
\end_layout
\end_inset
\end_layout
\begin_layout Standard
Now, we need to perform the CSS transform within our snippet.
The binding implicits for CSS transforms are found on the
\family typewriter
net.liftweb.util.BindHelpers
\family default
object/trait, so you should import it (in particular, the
\family typewriter
strToCssBindPromoter
\family default
method).
Listing
\begin_inset CommandInset ref
LatexCommand ref
reference "lst:Binding-the-Ledger-with-CSS"
\end_inset
shows how we modify the snippet in Listing
\begin_inset CommandInset ref
LatexCommand ref
reference "lst:Binding-the-ledger-balance"
\end_inset
to utilize the new CSS transform.
\end_layout
\begin_layout Standard
\begin_inset listings
inline false
status open
\begin_layout Plain Layout
\begin_inset Caption
\begin_layout Plain Layout
Binding the Ledger Balance with CSS
\begin_inset CommandInset label
LatexCommand label
name "lst:Binding-the-Ledger-with-CSS"
\end_inset
\end_layout
\end_inset
\end_layout
\begin_layout Plain Layout
import net.liftweb.util.BindHelpers._
\end_layout
\begin_layout Plain Layout
\end_layout
\begin_layout Plain Layout
class Ledger {
\end_layout
\begin_layout Plain Layout
def balance = "#balance" #> currentLedger.formattedBalance &
\end_layout
\begin_layout Plain Layout
"#time" #> (new java.util.Date).toString
\end_layout
\begin_layout Plain Layout
}
\end_layout
\end_inset
\end_layout
\begin_layout Standard
As you can see in this example, CSS transforms are comprised of three parts:
the transform selector, the transform operator (
\family typewriter
#>
\family default
), and the right hand side value.
This value can be a number of different things, which we'll cover in Section
\begin_inset CommandInset ref
LatexCommand ref
reference "sub:Replacement-Values"
\end_inset
, but in our case we're using a
\family typewriter
MappedField
\family default
and a
\family typewriter
String
\family default
.
Additionally, you can chain transforms together with the
\family typewriter
&
\family default
operator.
\end_layout
\begin_layout Subsubsection
CSS Selector Syntax
\end_layout
\begin_layout Standard
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:
\end_layout
\begin_layout Itemize
\family typewriter
#foo
\family default
- Selects the element with an
\family typewriter
id
\family default
attribute of
\begin_inset Quotes eld
\end_inset
foo
\begin_inset Quotes erd
\end_inset
\end_layout
\begin_layout Itemize
\family typewriter
.foo
\family default
- Selects all elements with a CSS
\family typewriter
class
\family default
of
\begin_inset Quotes eld
\end_inset
foo
\begin_inset Quotes erd
\end_inset
\end_layout
\begin_layout Itemize
\family typewriter
@foo
\family default
- Selects all elements with a
\family typewriter
name
\family default
attribute of
\begin_inset Quotes eld
\end_inset
foo
\begin_inset Quotes erd
\end_inset
\end_layout
\begin_layout Itemize
\family typewriter
attrName=attrValue
\family default
- Selects all elements with an attribute of
\begin_inset Quotes eld
\end_inset
attrName
\begin_inset Quotes erd
\end_inset
equal to
\begin_inset Quotes eld
\end_inset
attrValue
\begin_inset Quotes erd
\end_inset
\end_layout
\begin_layout Itemize
\family typewriter
element
\family default
- Selects all
\begin_inset Quotes eld
\end_inset
element
\begin_inset Quotes erd
\end_inset
elements (e.g.
span, h1, etc.)
\end_layout
\begin_layout Itemize
\family typewriter
:type
\family default
- Selects all elements with a
\family typewriter
type
\family default
of
\begin_inset Quotes eld
\end_inset
type
\begin_inset Quotes erd
\end_inset
.
The type must be one of:
\end_layout
\begin_deeper
\begin_layout Itemize
\family typewriter
button
\end_layout
\begin_layout Itemize
\family typewriter
checkbox
\end_layout
\begin_layout Itemize
\family typewriter
file
\end_layout
\begin_layout Itemize
\family typewriter
password
\end_layout
\begin_layout Itemize
\family typewriter
radio
\end_layout
\begin_layout Itemize
\family typewriter
reset
\end_layout
\begin_layout Itemize
\family typewriter
submit
\end_layout
\begin_layout Itemize
\family typewriter
text
\end_layout
\end_deeper
\begin_layout Itemize
\family typewriter
*
\family default
- Selects all elements
\end_layout
\begin_layout Standard
The element matching the selector is replaced by the result of processing
the replacement.
That means that in the example of Listing
\begin_inset CommandInset ref
LatexCommand ref
reference "lst:A-Simple-CSS-snippet"
\end_inset
the
\family typewriter
span
\family default
elements will be replaced with straight Text elements, resulting in the
markup shown in Listing
\begin_inset CommandInset ref
LatexCommand ref
reference "lst:Sample-CSS-Transform-result"
\end_inset
(in other words, no remaining markup).
\end_layout
\begin_layout Standard
\begin_inset listings
lstparams "language=XML"
inline false
status open
\begin_layout Plain Layout
\begin_inset Caption
\begin_layout Plain Layout
Sample CSS Transform Result
\begin_inset CommandInset label
LatexCommand label
name "lst:Sample-CSS-Transform-result"
\end_inset
\end_layout
\end_inset
\end_layout
\begin_layout Plain Layout
$12.42 as of Fri Jan 14 08:29:50 MST 2011
\end_layout
\end_inset
\end_layout
\begin_layout Standard
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:
\end_layout
\begin_layout Itemize
\family typewriter
*
\family default
- Replaces the children of the selected element.
For example, if we changed our selector in Listing
\begin_inset CommandInset ref
LatexCommand ref
reference "lst:Binding-the-Ledger-with-CSS"
\end_inset
from
\family typewriter
\begin_inset Quotes eld
\end_inset
#balance
\begin_inset Quotes erd
\end_inset
\family default
to
\family typewriter
\begin_inset Quotes eld
\end_inset
#balance *
\begin_inset Quotes erd
\end_inset
\family default
, we would be replacing the text node child (
\begin_inset Quotes eld
\end_inset
$0
\begin_inset Quotes erd
\end_inset
), with the resulting markup:
\end_layout
\begin_deeper
\begin_layout LyX-Code
<span id="balance">$12.42</span> as of Fri Jan 14 08:29:50 MST 2011
\end_layout
\begin_layout Standard
Note that when we perform child replacement, the parent's attributes
\begin_inset Index
status open
\begin_layout Plain Layout
CSS Transforms ! attribute copying
\end_layout
\end_inset
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
\begin_inset CommandInset ref
LatexCommand ref
reference "sub:Replacement-Values"
\end_inset
.
\end_layout
\end_deeper
\begin_layout Itemize
\family typewriter
*+
\family default
- Appends to the children of the selected element.
For example, given the template
\end_layout
\begin_deeper
\begin_layout LyX-Code
<span id="love">I love </span>
\end_layout
\begin_layout Standard
The transform
\end_layout
\begin_layout LyX-Code
"#love *+" #> "figs"
\end_layout
\begin_layout Standard
Would result in the markup
\end_layout
\begin_layout LyX-Code
<span id="love">I love figs</span>
\end_layout
\end_deeper
\begin_layout Itemize
\family typewriter
-*
\family default
- Prepends to the children of the selected element.
This operates the same as appending.
\end_layout
\begin_layout Itemize
\family typewriter
[name]
\family default
-
\begin_inset Index
status open
\begin_layout Plain Layout
CSS Transforms ! attribute replacement
\end_layout
\end_inset
Sets the value of the
\family typewriter
\begin_inset Quotes erd
\end_inset
name
\begin_inset Quotes erd
\end_inset
\family default
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
\family typewriter
href
\family default
of a link via CSS transform for the template
\end_layout
\begin_deeper
\begin_layout LyX-Code
<a href="#">ReplaceMe</a>
\end_layout
\begin_layout Standard
We could perform this by chaining two selections together, one for the child
element (link text) and one for the
\family typewriter
href
\family default
attribute:
\end_layout
\begin_layout LyX-Code
"a *" #> "This is the link text"&
\end_layout
\begin_layout LyX-Code
"a [href]" #> "http://foo.com/bar"
\end_layout
\begin_layout Standard
Note that the order of the selections is not important.
\end_layout
\end_deeper
\begin_layout Itemize
\family typewriter
[name+]
\family default
- 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
\family typewriter
[name]
\family default
qualifier.
One example would be adding to the
\family typewriter
class
\family default
attribute for a given element:
\end_layout
\begin_deeper
\begin_layout LyX-Code
"tr [class+]" #> (if (index % 2) "odd" else "even")
\end_layout
\end_deeper
\begin_layout Itemize
\family typewriter
^^
\family default
- Makes the selected element the root of the returned elements.
This can be used to select a particular element from a template, similar
to
\family typewriter
BindHelpers.chooseTemplate
\family default
.
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
\begin_inset CommandInset ref
LatexCommand ref
reference "lst:A-Simple-CSS-snippet"
\end_inset
, we could do so by changing our snippet code to:
\end_layout
\begin_deeper
\begin_layout LyX-Code
def balance = "#balance ^^" #> "ignore" &
\end_layout
\begin_layout LyX-Code
"#balance" #> currentLedger.formattedBalance
\end_layout
\end_deeper
\begin_layout Subsubsection
Right Hand Side Values
\begin_inset CommandInset label
LatexCommand label
name "sub:Replacement-Values"
\end_inset
\end_layout
\begin_layout Standard
The right hand side of a CSS transform operates on the selected element
to either transform or replace it.
It can be one of:
\end_layout
\begin_layout Itemize
\family typewriter
String
\family default
constant - returns a Text node for the
\family typewriter
String
\family default
.
For example, in Listing
\begin_inset CommandInset ref
LatexCommand ref
reference "lst:Binding-the-Ledger-with-CSS"
\end_inset
:
\end_layout
\begin_deeper
\begin_layout LyX-Code
"#time" #> (new java.util.Date).toString
\end_layout
\end_deeper
\begin_layout Itemize
A
\family typewriter
NodeSeq
\family default
constant - returns the
\family typewriter
NodeSeq
\family default
itself.
In Listing
\begin_inset CommandInset ref
LatexCommand ref
reference "lst:Binding-the-Ledger-with-CSS"
\end_inset
we could have done this instead:
\end_layout
\begin_deeper
\begin_layout LyX-Code
"#time" #> Text((new java.util.Date).toString)
\end_layout
\end_deeper
\begin_layout Itemize
\begin_inset Formula $NodeSeq\Rightarrow NodeSeq$
\end_inset
- a function that transforms the selected element.
Note that CSS transforms extend
\begin_inset Formula $NodeSeq\Rightarrow NodeSeq$
\end_inset
, so you can nest transforms like
\end_layout
\begin_deeper
\begin_layout LyX-Code
// Select the element with id "entry" and then bind its
\end_layout
\begin_layout LyX-Code
// nested "name" element
\end_layout
\begin_layout LyX-Code
"#entry" #> { "#name" #> account.name }
\end_layout
\end_deeper
\begin_layout Itemize
\family typewriter
net.liftweb.util.Bindable
\family default
- Instances that implement the
\family typewriter
Bindable
\family default
trait will be automatically converted into a
\family typewriter
NodeSeq
\family default
.
Among other things,
\family typewriter
MappedField
\family default
and
\family typewriter
Record.Field
\family default
support this, which allows us to use instances directly:
\end_layout
\begin_deeper
\begin_layout LyX-Code
"#description" #> account.description
\end_layout
\end_deeper
\begin_layout Itemize
\family typewriter
Boolean
\family default
,
\family typewriter
Int
\family default
,
\family typewriter
Long
\family default
or
\family typewriter
Symbol
\family default
- These are automatically promoted to a
\family typewriter
String
\family default
via the
\family typewriter
net.liftweb.util.StringPromotable
\family default
trait and implicits on its companion object
\end_layout
\begin_deeper
\begin_layout LyX-Code
"#viewperm" #> account.isViewableBy(someUser)
\end_layout
\end_deeper
\begin_layout Itemize
A
\family typewriter
Box
\family default
,
\family typewriter
Option
\family default
or
\family typewriter
Seq
\family default
of
\family typewriter
String
\family default
,
\family typewriter
NodeSeq
\family default
,
\family typewriter
Bindable
\family default
or values convertable by
\family typewriter
StringPromotable
\family default
- These will be converted into a
\family typewriter
net.liftweb.util.IterableConst
\family default
, which is used to compute a
\family typewriter
Seq[NodeSeq]
\family default
.
If your selector replaces the children of the selected element (*), the
\family typewriter
IterableConst
\family default
is applied to the selected element once for each item in the
\family typewriter
Seq
\family default
.
In other words, you get a copy of the selected element for each original
input.
For example, given the template:
\end_layout
\begin_deeper
\begin_layout LyX-Code
<h2>Account names:</h2>
\end_layout
\begin_layout LyX-Code
<ul>
\end_layout
\begin_layout LyX-Code
<li id="item">Account</li>
\end_layout
\begin_layout LyX-Code
</ul>
\end_layout
\begin_layout Standard
We can iterate over a list of accounts with the CSS transform:
\end_layout
\begin_layout LyX-Code
"#item *" #> accounts.map(_.name.toString)
\end_layout
\begin_layout Standard
Which, assuming a set of accounts named
\begin_inset Quotes eld
\end_inset
A
\begin_inset Quotes erd
\end_inset
,
\begin_inset Quotes eld
\end_inset
B
\begin_inset Quotes erd
\end_inset
, and
\begin_inset Quotes eld
\end_inset
C
\begin_inset Quotes erd
\end_inset
, results in:
\end_layout
\begin_layout LyX-Code
<h2>Account names:</h2>
\end_layout
\begin_layout LyX-Code
<ul>
\end_layout
\begin_layout LyX-Code
<li id="item">A</li>
\end_layout
\begin_layout LyX-Code
<li>B</li>
\end_layout
\begin_layout LyX-Code
<li>C</li>
\end_layout
\begin_layout LyX-Code
</ul>
\end_layout
\begin_layout Standard
\align center
\begin_inset Box Shadowbox
position "t"
hor_pos "c"
has_inner_box 1
inner_pos "t"
use_parbox 0
width "80col%"
special "none"
height "1in"
height_special "totalheight"
status open
\begin_layout Plain Layout
Note that the
\family typewriter
id
\family default
attribute is only placed on the first transformed element.
Subsequent replacements strip the
\family typewriter
id
\family default
attribute so that it remains unique on the page.
This special handling applies only to the
\family typewriter
id
\family default
attribute; other attributes, such as
\family typewriter
class
\family default
, are not similarly stripped.
\end_layout
\end_inset
\end_layout
\begin_layout Standard
Passing in a
\family typewriter
None
\family default
/
\family typewriter
Empty
\family default
will remove the selected element.
For example, you can delete an attribute
\begin_inset Index
status open
\begin_layout Plain Layout
CSS Transforms ! deleting attribute
\end_layout
\end_inset
\begin_inset Index
status open
\begin_layout Plain Layout
Attributes ! delete with CSS transform
\end_layout
\end_inset
with the following code:
\end_layout
\begin_layout LyX-Code
val blank: Option[String] = None
\end_layout
\begin_layout LyX-Code
"#thing [class]" #> blank
\end_layout
\end_deeper
\begin_layout Itemize
A
\family typewriter
Box
\family default
,
\family typewriter
Option
\family default
or
\family typewriter
Seq
\family default
of
\begin_inset Formula $NodeSeq\Rightarrow NodeSeq$
\end_inset
- These will be converted into a
\family typewriter
net.liftweb.util.IterableFunc
\family default
, and follow the same rules for replacement as
\family typewriter
IterableConst
\family default
(e.g.
child replacement repetition).
\end_layout
\begin_layout Subsection
Stateless versus Stateful Snippets
\begin_inset CommandInset label
LatexCommand label
name "sub:Stateless-versus-Stateful"
\end_inset
\begin_inset Index
status open
\begin_layout Plain Layout
StatefulSnippet
\end_layout
\end_inset
\begin_inset Index
status open
\begin_layout Plain Layout
Snippets ! stateful vs stateless
\end_layout
\end_inset
\end_layout
\begin_layout Standard
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
\begin_inset CommandInset ref
LatexCommand ref
reference "sub:Explicit-Dispatch"
\end_inset
).
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:
\end_layout
\begin_layout Itemize
Store the state in a cookie
\begin_inset Index
status open
\begin_layout Plain Layout
cookie
\end_layout
\end_inset
(Section
\begin_inset CommandInset ref
LatexCommand ref
reference "sec:Cookies"
\end_inset
).
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.
\end_layout
\begin_layout Itemize
Store the state in a
\family typewriter
SessionVar
\family default
\begin_inset Index
status open
\begin_layout Plain Layout
SessionVar
\end_layout
\end_inset
(Section
\begin_inset CommandInset ref
LatexCommand ref
reference "sec:Session-and-Request"
\end_inset
).
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.
\end_layout
\begin_layout Itemize
Pass the state around in a
\family typewriter
RequestVar
\family default
\begin_inset Index
status open
\begin_layout Plain Layout
RequestVar
\end_layout
\end_inset
by setting
\begin_inset Quotes eld
\end_inset
injector
\begin_inset Quotes erd
\end_inset
functions in your page transition functions (e.g.
\family typewriter
SHtml.link
\family default
,
\family typewriter
S.redirectTo
\family default
, etc).
We cover this technique in Section
\begin_inset CommandInset ref
LatexCommand ref
reference "sec:Session-and-Request"
\end_inset
.
\end_layout
\begin_layout Itemize
Use a
\family typewriter
StatefulSnippet
\family default
\begin_inset Index
status open
\begin_layout Plain Layout
StatefulSnippet
\end_layout
\end_inset
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.
\end_layout
\begin_layout Standard
Using a
\family typewriter
StatefulSnippet
\family default
is very similar to using a normal snippet but with the addition of a few
mechanisms.
First, the
\family typewriter
State
\family default
fulSnippet trait extends
\family typewriter
DispatchSnippet
\family default
(see Section
\begin_inset CommandInset ref
LatexCommand ref
reference "sub:Implicit-Dispatch-Via"
\end_inset
), allowing you to define which methods handle which snippets based on the
\family typewriter
dispatch
\family default
method.
Because Scala allows
\family typewriter
def
\family default
s to be implemented by
\family typewriter
var
\family default
s in subclasses, we can redefine the dispatch behavior as a result of snippet
processing.
\end_layout
\begin_layout Standard
Another thing to remember when using
\family typewriter
StatefulSnippet
\family default
s is that when you render a form, a hidden field is added to the form that
permits the same instance of the
\family typewriter
StatefulSnippet
\family default
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
\family typewriter
StatefulSnippet.link
\family default
method (instead of
\family typewriter
SHtml.link
\family default
); similarly, if you need to redirect to a different page, the
\family typewriter
StatefulSnippet
\family default
trait defines a
\family typewriter
redirectTo
\family default
method.
In either of these instances, a function map is added to the link or redirect,
respectively, that causes the instance to be reattached.
\end_layout
\begin_layout Standard
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
\begin_inset Note Note
status open
\begin_layout Plain Layout
Might need a better example now that we have Screen/Wizard
\end_layout
\end_inset
.
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
\family typewriter
StatefulSnippet
\family default
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.
\end_layout
\begin_layout Standard
Listing
\begin_inset CommandInset ref
LatexCommand ref
reference "lst:Using-a-StatefulSnippet"
\end_inset
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
\begin_inset CommandInset ref
LatexCommand ref
reference "lst:The-StatefulSnippet-Example"
\end_inset
.
Remember to call
\family typewriter
unregisterThisSnippet()
\family default
when you're finished with your workflow in order to stop the current instance
from being used.
\end_layout
\begin_layout Standard
\begin_inset listings
inline false
status open
\begin_layout Plain Layout
\begin_inset Caption
\begin_layout Plain Layout
Using a StatefulSnippet
\begin_inset CommandInset label
LatexCommand label
name "lst:Using-a-StatefulSnippet"
\end_inset
\end_layout
\end_inset
\end_layout
\begin_layout Plain Layout
...
standard Lift imports ...
\end_layout
\begin_layout Plain Layout
import scala.xml.Text
\end_layout
\begin_layout Plain Layout
\end_layout
\begin_layout Plain Layout
class BridgeKeeper extends StatefulSnippet {
\end_layout
\begin_layout Plain Layout
// Define the dispatch for snippets.
Note that we are defining
\end_layout
\begin_layout Plain Layout
// it as a var so that the snippet for each portion of the
\end_layout
\begin_layout Plain Layout
// multi-part form can update it after validation.
\end_layout
\begin_layout Plain Layout
var dispatch : DispatchIt = {
\end_layout
\begin_layout Plain Layout
// We default to dispatching the "challenge" snippet to our
\end_layout
\begin_layout Plain Layout
// namePage snippet method.
We'll update this below
\end_layout
\begin_layout Plain Layout
case "challenge" => firstPage _
\end_layout
\begin_layout Plain Layout
}
\end_layout
\begin_layout Plain Layout
\end_layout
\begin_layout Plain Layout
// Define our state variables:
\end_layout
\begin_layout Plain Layout
var (name,quest,color) = ("","","")
\end_layout
\begin_layout Plain Layout
\end_layout
\begin_layout Plain Layout
// Our first form page
\end_layout
\begin_layout Plain Layout
def firstPage (xhtml : NodeSeq) : NodeSeq = {
\end_layout
\begin_layout Plain Layout
def processName (nm : String) {
\end_layout
\begin_layout Plain Layout
name = nm
\end_layout
\begin_layout Plain Layout
if (name != "") {
\end_layout
\begin_layout Plain Layout
dispatch = { case "challenge" => questPage _ }
\end_layout
\begin_layout Plain Layout
} else {
\end_layout
\begin_layout Plain Layout
S.error("You must provide a name!")
\end_layout
\begin_layout Plain Layout
}
\end_layout
\begin_layout Plain Layout
}
\end_layout
\begin_layout Plain Layout
bind("form", xhtml,
\end_layout
\begin_layout Plain Layout
"question" -> Text("What is your name?"),
\end_layout
\begin_layout Plain Layout
"answer" -> SHtml.text(name, processName))
\end_layout
\begin_layout Plain Layout
}
\end_layout
\begin_layout Plain Layout
\end_layout
\begin_layout Plain Layout
def questPage (xhtml : NodeSeq) : NodeSeq = {
\end_layout
\begin_layout Plain Layout
def processQuest (qst : String) {
\end_layout
\begin_layout Plain Layout
quest = qst
\end_layout
\begin_layout Plain Layout
if (quest != "") {
\end_layout
\begin_layout Plain Layout
dispatch = {
\end_layout
\begin_layout Plain Layout
case "challenge" if name == "Arthur" => swallowPage _
\end_layout
\begin_layout Plain Layout
case "challenge" => colorPage _
\end_layout
\begin_layout Plain Layout
}
\end_layout
\begin_layout Plain Layout
} else {
\end_layout
\begin_layout Plain Layout
S.error("You must provide a quest!")
\end_layout
\begin_layout Plain Layout
}
\end_layout
\begin_layout Plain Layout
}
\end_layout
\begin_layout Plain Layout
bind("form", xhtml,
\end_layout
\begin_layout Plain Layout
"question" -> Text("What is your quest?"),
\end_layout
\begin_layout Plain Layout
"answer" -> SHtml.text(quest, processQuest))
\end_layout
\begin_layout Plain Layout
}
\end_layout
\begin_layout Plain Layout
\end_layout
\begin_layout Plain Layout
def colorPage (xhtml : NodeSeq) : NodeSeq = {
\end_layout
\begin_layout Plain Layout
def processColor (clr : String) {
\end_layout
\begin_layout Plain Layout
color = clr
\end_layout
\begin_layout Plain Layout
if (color.toLowercase.contains "No,") {
\end_layout
\begin_layout Plain Layout
// This is a cleanup that removes the mapping for this
\end_layout
\begin_layout Plain Layout
// StatefulSnippet from the session.
This will happen
\end_layout
\begin_layout Plain Layout
// over time with GC, but it's best practice to manually
\end_layout
\begin_layout Plain Layout
// do this when you're finished with the snippet
\end_layout
\begin_layout Plain Layout
this.unregisterThisSnippet()
\end_layout
\begin_layout Plain Layout
S.redirectTo("/pitOfEternalPeril")
\end_layout
\begin_layout Plain Layout
} else if (color != "") {
\end_layout
\begin_layout Plain Layout
this.unregisterThisSnippet()
\end_layout
\begin_layout Plain Layout
S.redirectTo("/scene24")
\end_layout
\begin_layout Plain Layout
} else {
\end_layout
\begin_layout Plain Layout
S.error("You must provide a color!")
\end_layout
\begin_layout Plain Layout
}
\end_layout
\begin_layout Plain Layout
}
\end_layout
\begin_layout Plain Layout
bind("form", xhtml,
\end_layout
\begin_layout Plain Layout
"question" -> Text("What is your favorite color?"),
\end_layout
\begin_layout Plain Layout
"answer" -> SHtml.text(color, processColor))
\end_layout
\begin_layout Plain Layout
}
\end_layout
\begin_layout Plain Layout
// and so on for the swallowPage snippet
\end_layout
\begin_layout Plain Layout
...
\end_layout
\begin_layout Plain Layout
}
\end_layout
\end_inset
\end_layout
\begin_layout Standard
\begin_inset listings
lstparams "language=XML"
inline false
status open
\begin_layout Plain Layout
\begin_inset Caption
\begin_layout Plain Layout
The StatefulSnippet Example Template
\begin_inset CommandInset label
LatexCommand label
name "lst:The-StatefulSnippet-Example"
\end_inset
\end_layout
\end_inset
\end_layout
\begin_layout Plain Layout
<lift:surround with="default" at="content">
\end_layout
\begin_layout Plain Layout
<lift:BridgeKeeper.challenge form="POST">
\end_layout
\begin_layout Plain Layout
<form:question /> : <form:answer /> <br />
\end_layout
\begin_layout Plain Layout
<input type="submit" value="Answer" />
\end_layout
\begin_layout Plain Layout
</lift:BridgeKeeper.challenge>
\end_layout
\begin_layout Plain Layout
</lift:surround>
\end_layout
\end_inset
\end_layout
\begin_layout Standard
If you're using implicit dispatch (Section
\begin_inset CommandInset ref
LatexCommand ref
reference "sub:Implicit-Dispatch-Via"
\end_inset
), then you're done.
If you want to use explicit dispatch
\begin_inset Index
status open
\begin_layout Plain Layout
Snippets ! explicit dispatch with StatefulSnippet
\end_layout
\end_inset
\begin_inset Index
status open
\begin_layout Plain Layout
StatefulSnippet ! explicit dispatch
\end_layout
\end_inset
, however, you need to do a little more work than usual in the
\family typewriter
LiftRules.snippetDispatch
\family default
setup.
Listing
\begin_inset CommandInset ref
LatexCommand ref
reference "lst:Explicit-Dispatch-with-stateful"
\end_inset
shows how we can bind our own StatefulSnippet classes without using reflection.
\end_layout
\begin_layout Standard
\begin_inset listings
inline false
status open
\begin_layout Plain Layout
\begin_inset Caption
\begin_layout Plain Layout
Explicit Dispatch with Stateful Snippets
\begin_inset CommandInset label
LatexCommand label
name "lst:Explicit-Dispatch-with-stateful"
\end_inset
\end_layout
\end_inset
\end_layout
\begin_layout Plain Layout
// In your boot method:
\end_layout
\begin_layout Plain Layout
LiftRules.snippetDispatch.append {
\end_layout
\begin_layout Plain Layout
// S.snippetForClass checks to see if an instance has already
\end_layout
\begin_layout Plain Layout
// registered.
This is the case after form submission or when
\end_layout
\begin_layout Plain Layout
// we use the StatefulSnippet.link or .redirectTo methods
\end_layout
\begin_layout Plain Layout
case "BridgeKeeper" => S.snippetForClass("TestHello") openOr {
\end_layout
\begin_layout Plain Layout
// If we haven't already registered an instance, create one
\end_layout
\begin_layout Plain Layout
val inst = new com.test.TestHello
\end_layout
\begin_layout Plain Layout
// The name is what Lift uses to locate an instance (S.snippetForClass)
\end_layout
\begin_layout Plain Layout
// We need to add it so that the Stateful callback functions can
\end_layout
\begin_layout Plain Layout
// self-register
\end_layout
\begin_layout Plain Layout
inst.addName("TestHello")
\end_layout
\begin_layout Plain Layout
// Register this instance for the duration of the request
\end_layout
\begin_layout Plain Layout
S.overrideSnippetForClass("TestHello", inst)
\end_layout
\begin_layout Plain Layout
inst
\end_layout
\begin_layout Plain Layout
}
\end_layout
\end_inset
\end_layout
\begin_layout Subsection
Eager Evaluation
\begin_inset CommandInset label
LatexCommand label
name "sub:Eager-Eval"
\end_inset
\end_layout
\begin_layout Standard
As we mentioned in Section
\begin_inset CommandInset ref
LatexCommand ref
reference "sec:Snippets"
\end_inset
, 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
\emph on
before
\emph default
the snippet, then you need to specify the
\family typewriter
eager_eval
\family default
\begin_inset Index
status open
\begin_layout Plain Layout
eager eval
\end_layout
\end_inset
attribute on the tag:
\end_layout
\begin_layout LyX-Code
<lift:Hello.world eager_eval=
\begin_inset Quotes erd
\end_inset
true
\begin_inset Quotes erd
\end_inset
>...</lift:Hello.world>
\end_layout
\begin_layout Standard
This is especially useful if you're using an embedded
\begin_inset Index
status open
\begin_layout Plain Layout
embed
\end_layout
\end_inset
\begin_inset Index
status open
\begin_layout Plain Layout
Templates ! embedded
\end_layout
\end_inset
\begin_inset Index
status open
\begin_layout Plain Layout
Tags ! embed
\end_layout
\end_inset
template (Section
\begin_inset CommandInset ref
LatexCommand ref
reference "sub:embed-tag"
\end_inset
).
Consider Listing
\begin_inset CommandInset ref
LatexCommand ref
reference "lst:Embedding-and-eager"
\end_inset
: in this case, the
\family typewriter
eager_eval
\family default
parameter makes Lift process the
\family typewriter
<lift:embed />
\family default
tag before it executes the
\family typewriter
Hello.world
\family default
snippet method.
If the
\begin_inset Quotes eld
\end_inset
formTemplate
\begin_inset Quotes erd
\end_inset
template looks like Listing
\begin_inset CommandInset ref
LatexCommand ref
reference "lst:The-formTemplate-template"
\end_inset
, then the
\family typewriter
Hello.world
\family default
snippet sees the
\family typewriter
<hello:name />
\family default
and
\family typewriter
<hello:time />
\family default
XML tags as its
\family typewriter
NodeSeq
\family default
input.
If the
\family typewriter
eager_eval
\family default
attribute is removed, however, the Hello.world snippet sees only a
\family typewriter
<lift:embed />
\family default
tag that will be processed after it returns.
\end_layout
\begin_layout Standard
\begin_inset listings
lstparams "language=XML"
inline false
status open
\begin_layout Plain Layout
\begin_inset Caption
\begin_layout Plain Layout
Embedding and eager evaluation
\begin_inset CommandInset label
LatexCommand label
name "lst:Embedding-and-eager"
\end_inset
\end_layout
\end_inset
\end_layout
\begin_layout Plain Layout
<lift:Hello.world eager_eval="true">
\end_layout
\begin_layout Plain Layout
<lift:embed what="formTemplate" />
\end_layout
\begin_layout Plain Layout
</lift:Hello.world>
\end_layout
\end_inset
\end_layout
\begin_layout Standard
\begin_inset listings
lstparams "language=XML"
inline false
status open
\begin_layout Plain Layout
\begin_inset Caption
\begin_layout Plain Layout
The formTemplate template
\begin_inset CommandInset label
LatexCommand label
name "lst:The-formTemplate-template"
\end_inset
\end_layout
\end_inset
\end_layout
\begin_layout Plain Layout
<lift:children>
\end_layout
\begin_layout Plain Layout
<hello:name />
\end_layout
\begin_layout Plain Layout
<hello:time />
\end_layout
\begin_layout Plain Layout
</lift:children>
\end_layout
\end_inset
\end_layout
\begin_layout Standard
\begin_inset Note Note
status open
\begin_layout Plain Layout
Add example of same snippet on multiple pages.
\end_layout
\end_inset
\end_layout
\begin_layout Section
\begin_inset CommandInset label
LatexCommand label
name "sec:Handling-XHTML-Attributes"
\end_inset
Handling XHTML Attributes in Snippets
\begin_inset Index
status open
\begin_layout Plain Layout
Attributes
\end_layout
\end_inset
\begin_inset Index
status open
\begin_layout Plain Layout
XML ! attribute handling
\end_layout
\end_inset
\end_layout
\begin_layout Standard
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.
\end_layout
\begin_layout Subsection
\begin_inset CommandInset label
LatexCommand label
name "sub:Direct-attr-manip"
\end_inset
Direct Manipulation in Code
\end_layout
\begin_layout Standard
You can apply attributes directly to XHTML elements using the
\begin_inset Quotes eld
\end_inset
%
\begin_inset Quotes erd
\end_inset
operator
\begin_inset Index
status open
\begin_layout Plain Layout
Attributes ! % operator
\end_layout
\end_inset
to apply a
\begin_inset Newline linebreak
\end_inset
\family typewriter
scala.xml.UnprefixedAttribute
\family default
instance
\begin_inset Foot
status open
\begin_layout Plain Layout
there's a corresponding
\family typewriter
PrefixedAttribute
\family default
as well
\end_layout
\end_inset
to an element.
Lift's
\family typewriter
net.liftweb.util.Helpers
\family default
trait contains an implicit conversion from a
\family typewriter
Pair[String,_]
\family default
to an
\family typewriter
UnprefixedAttribute
\family default
called
\family typewriter
pairToUnprefixed
\family default
that allows you to use a simpler syntax.
You may chain invocations of
\begin_inset Quotes eld
\end_inset
%
\begin_inset Quotes erd
\end_inset
to apply multiple attributes.
For example, Listing
\begin_inset CommandInset ref
LatexCommand ref
reference "lst:Applying-Attributes-percent"
\end_inset
shows how you can apply an
\begin_inset Quotes eld
\end_inset
id
\begin_inset Quotes erd
\end_inset
and
\begin_inset Quotes eld
\end_inset
class
\begin_inset Quotes erd
\end_inset
attribute to a text box and to a normal paragraph.
\end_layout
\begin_layout Standard
\begin_inset listings
inline false
status open
\begin_layout Plain Layout
val myInput = SHtml.text("", processText(_)) % ("id" -> "inputField") %
\end_layout
\begin_layout Plain Layout
("class" -> "highlighted")
\end_layout
\begin_layout Plain Layout
\begin_inset Caption
\begin_layout Plain Layout
\begin_inset CommandInset label
LatexCommand label
name "lst:Applying-Attributes-percent"
\end_inset
Applying Attributes with
\family typewriter
%
\end_layout
\end_inset
\end_layout
\end_inset
\end_layout
\begin_layout Standard
\align center
\begin_inset Box Shadowbox
position "t"
hor_pos "c"
has_inner_box 1
inner_pos "t"
use_parbox 0
width "75col%"
special "none"
height "1in"
height_special "totalheight"
status open
\begin_layout Plain Layout
Note that the % metadata mechanism is actually part of the Scala XML library.
Specifically,
\family typewriter
scala.xml.Elem
\family default
has a
\family typewriter
%
\family default
method that allows the user to update the attributes on a given XML element
by passing in a
\family typewriter
scala.xml.UnprefixedAttribute
\family default
.
We suggest reading more about this in the Scala API documents, or in the
Scala XML docbook at
\begin_inset CommandInset href
LatexCommand href
target "http://burak.emir.googlepages.com/scalaxbook.docbk.html"
\end_inset
.
\end_layout
\end_inset
\end_layout
\begin_layout Subsection
\begin_inset CommandInset label
LatexCommand label
name "sub:XHTML-Attribute-Pass-through"
\end_inset
XHTML Attribute Pass-through
\begin_inset Index
status open
\begin_layout Plain Layout
Attributes ! binding
\end_layout
\end_inset
\begin_inset Index
status open
\begin_layout Plain Layout
XML ! preserving attributes
\end_layout
\end_inset
\end_layout
\begin_layout Standard
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
\begin_inset CommandInset ref
LatexCommand ref
reference "lst:Snippet-mixin-attributes"
\end_inset
shows how you can utilize the
\begin_inset Quotes eld
\end_inset
-%>
\begin_inset Quotes erd
\end_inset
\begin_inset Index
status open
\begin_layout Plain Layout
Attributes ! -%> operator
\end_layout
\end_inset
binding operator instead of
\begin_inset Quotes eld
\end_inset
->
\begin_inset Quotes erd
\end_inset
to preserve attributes.
\end_layout
\begin_layout Standard
\begin_inset listings
inline false
status open
\begin_layout Plain Layout
\begin_inset Caption
\begin_layout Plain Layout
Snippet mixin attributes
\begin_inset CommandInset label
LatexCommand label
name "lst:Snippet-mixin-attributes"
\end_inset
\end_layout
\end_inset
\end_layout
\begin_layout Plain Layout
// the markup
\end_layout
\begin_layout Plain Layout
<lift:Ledger.balance>
\end_layout
\begin_layout Plain Layout
<ledger:time id="myId"/>
\end_layout
\begin_layout Plain Layout
</lift:Ledger.balance>
\end_layout
\begin_layout Plain Layout
\end_layout
\begin_layout Plain Layout
// The snippet class
\end_layout
\begin_layout Plain Layout
\end_layout
\begin_layout Plain Layout
class Ledger {
\end_layout
\begin_layout Plain Layout
def balance (content : NodeSeq ) : NodeSeq = {
\end_layout
\begin_layout Plain Layout
bind ("ledger", content,
\end_layout
\begin_layout Plain Layout
"time" -%> <span>{(new java.util.Date).toString}</span>)
\end_layout
\begin_layout Plain Layout
}
\end_layout
\begin_layout Plain Layout
}
\end_layout
\end_inset
\end_layout
\begin_layout Standard
The resulting node will be something like
\end_layout
\begin_layout LyX-Code
\family typewriter
<span id=
\begin_inset Quotes erd
\end_inset
myId
\begin_inset Quotes erd
\end_inset
>Sat Mar 28 16:43:48 EET 2009</span>
\end_layout
\begin_layout Standard
In addition to the