Up: Chapter 3

3.4 Snippets and Dynamic content

Lift templates contain no executable code. They are pure, raw, valid HTML.
Lift uses snippets to transform sections of the HTML page from static to dynamic. The key word is transform.
Lift’s snippets are Scala functions: NodeSeq => NodeSeq. A NodeSeq is a collection of XML nodes. An snippet can only transform input NodeSeq to output NodeSeq. Well, not exactly... a snippet may also have side effects including setting cookies, doing database transactions, etc. But the core transformation concept is important. First, it isolates snippet functionality to discrete parts of the page. This means that each snippet, each NodeSeq => NodeSeq, is a component. Second, it means that pages are recursively built, but remain as valid HTML at all times. This means that the developer has to work hard to introduce a cross site scripting vulnerability. Third, the designers don’t have to worry about learning to program anything in order to design HTML pages because the program execution is abstracted away from the HTML rather than embedded in the HTML.

3.4.1 Snippets in markup

In order to indicate that content is dynamic, the markup contains a snippet invocation. That typically takes the form class="someclass someothercss lift:mysnippet". If a class attribute contains lift:xxx, the xxx will be resolved to a snippet. The snippet may take attributes. Attributes are encoded like URL parameters... offset by a ? (question mark), then name=value, separted by ? (question mark), ; (semicolon) or & (ampersand). name and value are URL encoded.
You may also invoke snippets with XML tags:
<lift:my_snippet cat="foo">
  <div>xxxx</div>
</lift:my_snippet>
Note that the Html5 parser will force all tags to lower case so <lift:MySnipet> will become <lift:mysnippet>.
Lift 2.3 will also allow snippet invocation in the form <div l="mysnippet?param=value">xxx</div>.
The latter two mechanisms for invoking snippets will not result in valid Html5 templates.

3.4.2 Snippet resolution

Lift has a very complex set of rules to resolve from snippet name to NodeSeq => NodeSeq (see 23.1 on page 1↓). For now, the simplest mechanism is to have a class or object in the snippet package that matches the snippet name.
So lift:HelloWorld will look for the code.snippet.HelloWorld class and invoke the render method.
lift:CatFood.fruitbat will look for the code.snippet.CatFood class and invoke the fruitbat method.

3.4.3 Dynamic Example

Let’s look at the dynamic.html page:
dynamic.html
<!DOCTYPE html>
<html>
  <head>
    <meta content="text/html; charset=UTF-8" http-equiv="content-type" />
    <title>Dynamic</title>
  </head>
  <body class="lift:content_id=main">
    <div id="main" class="lift:surround?with=default;at=content">
      This page has dynamic content.
      The current time is <span class="lift:HelloWorld">now</span>.
    </div>
  </body>
</html>
​
This template invokes the HelloWorld snippet defined in HelloWorld.scala:
HelloWorld.scala
package code
package snippet
​
import lib._
​
import net.liftweb._
import util.Helpers._
import common._
import java.util.Date
​
class HelloWorld {
  lazy val date: Box[Date] = DependencyFactory.inject[Date] // inject the date
​
  def render = "* *" #> date.map(_.toString)
}
​
And the dynamic content becomes:
<span>Thu Dec 30 16:31:13 PST 2010</span>
The HelloWorld snippet code is simple.
lazy val date: Box[Date] = DependencyFactory.inject[Date]
Uses dependency injection (see 8.2 on page 1↓) to get a Date instance.
Then:
def render = "* *" #> date.map(_.toString)
Creates a CSS Selector Transform (see 7.10 on page 1↓) that inserts the String value of the injected Date into the markup, in this case the <span> that invoked the snippet.

3.4.4 Embedded Example

We’ve seen how we can embed a template using: <div class="lift:embed?what=_embedme">xxx</div>.
Let’s look at the _embedme.html template:
<!DOCTYPE html>
<html>
  <head>
    <meta content="text/html; charset=UTF-8" http-equiv="content-type" />
    <title>I'm embeded</title>
  </head>
  <body class="lift:content_id=main">
    <div id="main">
      Howdy.  I'm a bit of embedded content.  I was 
      embedded from <span class="lift:Embedded.from">???</span>.
    </div>
  </body>
</html>
​
And the invoked Embedded.scala program:
Embedded.scala
package code
package snippet
​
import lib._
​
import net.liftweb._
import http._
import util.Helpers._
import common._
import java.util.Date
​
/**
 * A snippet that lists the name of the current page
 */
object Embedded {
  def from = "*" #> S.location.map(_.name)
}
​
The template invokes the from method on the Embedded snippet. In this case, the snippet is an object singleton because it does not take any constructor parameters and has no instance variabled.
The from method:
  def from = "*" #> S.location.map(_.name)
Creates a CSS Selector Transform that replaces the contents with the name of the current location.

3.4.5 Param Example

Above, we saw how to create a Loc[ParamInfo] to capture URL parameters. Let’s look at the /param/xxx page and see how we can access the parameters:
param.html
<!DOCTYPE html>
<html>
  <head>
    <meta content="text/html; charset=UTF-8" http-equiv="content-type" />
    <title>Param</title>
  </head>
  <body class="lift:content_id=main">
    <div id="main" class="lift:surround?with=default;at=content">
      <div>
        Thanks for visiting this page.  The parameter is
        <span class="lift:ShowParam">???</span>.
      </div>
      
      <div>
        Another way to get the param: <span class="lift:Param">???</span>.
      </div>
      
    </div>
  </body>
</html>
​
And let’s look at two different snippets that can access the ParamInfo for the page:
Param.scala
package code
package snippet
​
import lib._
​
import net.liftweb._
import util.Helpers._
import common._
import http._
import sitemap._
import java.util.Date
​
// capture the page parameter information
case class ParamInfo(theParam: String)
​
// a snippet that takes the page parameter information
class ShowParam(pi: ParamInfo)  {
  def render = "*" #> pi.theParam
}
​
object Param {
  // Create a menu for /param/somedata
  val menu = Menu.param[ParamInfo]("Param", "Param", 
                                   s => Full(ParamInfo(s)), 
                                   pi => pi.theParam) / "param"
  lazy val loc = menu.toLoc
​
  def render = "*" #> loc.currentValue.map(_.theParam)
}
Each snippet has a render method. However, the ShowParam class takes a constructor parameter which contains the ParamInfo from the current Loc[_]. If the current Loc does not have the type parameter ParamInfo, no instance of ShowParam would be created and the snippet could not be resolved. But we do have a Loc[ParamInfo], so Lift constructs a ShowParam with the Loc’s currentValue and then the render method is invoked and it returns a CSS Selector Transform which is a NodeSeq => NodeSeq.
The object Param’s render method accesses the Loc[ParamInfo] directly. The render method gets the Loc’s currentValue and uses that to calculate the return value, the CSS Selector Transform.

3.4.6 Recursive

Lift’s snippets are evaluated lazily. This means that the body of the snippet is not executed until the outer snippet is executed which allows you to return markup from a snippet that itself contains a snippet or alternatively, choose part of the snippet body that itself contains a snippet invocation. For example, in this markup:
recurse.html
<div id="main" class="lift:surround?with=default&at=content">
  <div>
    This demonstrates Lift's recursive snippets
  </div>
​
  <div class="lift:Recurse">
    <div id="first" class="lift:FirstTemplate">
      The first template.
    </div>
​
    <div id="second" class="lift:SecondTemplate">
      The second template.
    </div>
  </div>
  
  <div>
    <ul>
      <li>Recursive: <a href="/recurse/one">First snippet</a></li>
      <li>Recursive: <a href="/recurse/two">Second snippet</a></li>
      <li>Recursive: <a href="/recurse/both">Both snippets</a></li>
    </ul>
  </div>
</div>
​
​
The Recurse snippet chooses one of both of the <div>’s, each of which invokes a snippet themselves. Here’s the Scala:
Recurse.scala
package code
package snippet
​
import lib._
​
import net.liftweb._
import util._
import Helpers._
import http._
import scala.xml.NodeSeq
​
/**
 * The choices
 */
sealed trait Which
final case class First() extends Which
final case class Second() extends Which
final case class Both() extends Which
​
/**
 * Choose one or both of the templates
 */
class Recurse(which: Which) {
  // choose the template
  def render = which match {
    case First() => "#first ^^" #> "*" // choose only the first template
    case Second() => "#second ^^" #> "*" // choose only the second template
    case Both() => ClearClearable // it's a passthru
  }
}
​
/**
 * The first template snippet
 */
object FirstTemplate {
  // it's a passthru, but has the notice side effect
  def render(in: NodeSeq) = {
    S.notice("First Template Snippet executed")
    in
  }
}
​
/**
 * The second template snippet
 */
object SecondTemplate {
  // it's a passthru, but has the notice side effect
  def render(in: NodeSeq) = {
    S.notice("Second Template Snippet executed")
    in
  }
}
Depending on the value of which, one or both parts of the markup will be chosen. And each part of the markup itself invokes a snippet which displays a notice and passes the markup through.
Using this technique, you can have a snippet that chooses one or many different snippets or returns a lift:embed snippet, thus allowing for very dynamic markup generation.

3.4.7 Summary

We’ve seen some simple examples of Lift’s snippet mechanism used to generate dynamic content. You can read more on snippets (see 7.1 on page 1↓).
Up: Chapter 3

(C) 2012 David Pollak