C Lift Helpers
C.1 Introduction
Lift provides a fairly useful collection of helper artifacts. The helpers are essentially utility functions that minimize the need for boilerplate code. This appendix is intended to introduce some of the more common utility classes and objects to you so that you’re familiar with them. If you would like more details, you can look at the API documentation for the net.liftweb.util package.
C.2 Box (or Scala’s Option class on steroids)
net.liftweb.util.Box (or Scala’s
scala.Option class on steroids) is a utility class that mimics Scala’s
Option type (also heavily used inside Lift). To understand some of the underlying concepts and assumptions, let’s take a quick look at
Option class first. The
Option class allows a type-safe way of dealing with a situation where you may or may not have a result.
Option has two values, either
Some(value), where
value is actually the value, and
None, which is used to represent nothing. A typical example for
Option is outlined using Scala’s Map type. Listing
C.2↓ shows a definition of a
Map, a successful attempt to get the value of key
a, and an attempt to get the value of key
i. Notice that when we retrieved the existing key-value pair for
a, the value returned was
Some(A) and when we asked for the value of key
i, we received None.
scala> val cap = Map("a" -> "A", "b" -> "B")
cap: scala.collection.immutable.Map[java.lang.String,java.lang.String] =
Map(a -> A, b -> B)
scala> cap.get("a")
res1: Option[java.lang.String] = Some(A)
scala> cap.get("i")
res2: Option[java.lang.String] = None
Getting the value out of an
Option is usually handled via Scala’s matching mechanism or via the
getOrElse function, as shown in Listing
C.2↓:
Fetch value from an Option
def prettyPrint(foo: Option[String]): String = foo match {
case Some(x) => x
case None => "Nothing found."
}
// Which would be used in conjunction with the previous code:
scala> prettyPrint(cap.get("a"))
res7: String = A
scala> prettyPrint(cap.get("i"))
res8: String = Nothing found.
Box in Lift covers the same base functionality as Option but expands the semantics for missing values. If we have an Option that is None at some point, we can’t really tell why that Option is None, although in many situations, knowing why would be quite helpful. With Box, on the other hand, you have either have a Full instance (corresponding to Some with Option) or an instance that subclasses EmptyBox (corresponding to None). EmptyBox can either be an Empty instance or a Failure instance incorporating the cause for the failure. So you can think of Box as a container with three states: full, empty, or empty for a particular reason. The Failure case class takes three arguments: a String message to describe the failure, a Box[Throwable] for an optional exception related to the failure, and a Box[Failure] for chaining based on earlier Failures.
As an example of how we can use
Box instances in real code, consider the case where we have to do a bunch of null checks, perform an operation, and then perform more null checks, other operations, and so on. Listing
C.2↓ shows an example of this sort of structure.
Pseudocode nested operations example
x = getSomeValue();
if (x != null) {
y = getSomeOtherValue();
if (y != null) {
compute(x, y);
}
}
This is tedious and error-prone in practice. Now let’s see if we can do better by combining Lift’s
Box with Scala’s for comprehensions as shown in Listing
C.2↓.
Box nested operations example
def getSomeValue(): Box[Int] = Full(12)
def getSomeOtherValue(): Box[Int] = Full(2)
def compute(x: Int, y: Int) = x * y
val res = for ( x <- getSomeValue();
y <- getSomeOtherValue() if x > 10) yield compute(x, y)
println(res)
In Listing
C.2↑, we have two values,
x and
y, and we want to do some computation with these values. But we must ensure that computation is done on the correct data. For instance, the computation cannot be done if
getSomeValue returns no value. In this context, the two functions return a
Box[Int]. The interesting part is that if either or both of the two functions return an
Empty Box instead of
Full (
Empty impersonating the nonexistence of the value), the
res value will also be
Empty. However, if both functions return a
Full (like in Listing
C.2↑), the computation is called. In our example the two functions return
Full(12) and
Full(2), so
res will be a
Full(24).
But we have something else interesting here: the if x > 10 statement (this is called a “guard” in Scala). If the call to getSomeValue returns a value less than or equal to 10, the y variable won’t be initialized, and the res value will be Empty. This is just a taste of some of the power of using Box for comprehensions; for more details on for comprehensions, see The Scala Language Specification, section 6.19, or one of the many Scala books available.
Lift’s
Box extends
Option with a few ideas, mainly the fact that you can add a message about why a
Box is
Empty.
Empty corresponds to
Option’s
None and
Full to
Option’s Some. So you can pattern match against a
Box as shown in Listing
C.2↓.
a match {
Full(author) => Text("I found the author " + author.niceName)
Empty => Text("No author by that name.")
// message may be something like "Database disconnected."
Failure(message, _, _) => Text("Nothing found due to " + message)
}
def confirmDelete {
(for (val id <- param("id"); // get the ID
val user <- User.find(id)) // find the user
yield {
user.delete_!
notice("User deleted")
redirectTo("/simple/index.html")
}) getOrElse {error("User not found"); redirectTo("/simple/index.html")}
}
In conjunction with Listing
C.2↑, we can use other
Box functions, such as the
openOr function shown in Listing
C.2↓.
lazy val UserBio = UserBio.find(By(UserBio.id, id)) openOr (new UserBio)
def view (xhtml: NodeSeq): NodeSeq = passedAuthor.map({ author =>
// do bind, etc here and return a NodeSeq
}) openOr Text("Invalid author")
We won’t be detailing all of the Box functions here, but a few words on the most common function might be benficial.
Function name
|
Description
|
Short example. Assume myBox is a Box
|
openOr
|
Returns the value contained by this Box. If the Box is Empty
|
myBox openOr “The box is Empty”
|
map
|
Apply a function on the values of this Box and return something else.
|
myBox map (value => value + “ suffix”)
|
dmap
|
Equivalent with map(..) openOr default_value. The default value will be returned in case the map is Empty
|
myBox dmap(“default”)(value => value + “ suffix”)
|
!!
|
If the argument is null in will return an Empty, otherwise a Full containing the arguent’s value. Note this this is a method on the Box object, not a given Box instance.
|
Box !! (<a reference>)
|
?~
|
Transforms an Empty to a Failure and passing a message. If the Box is a Full it will just return this.
|
myBox ?~ (“Error message”)
|
isDefined
|
Returns true if this Box contains a value
|
myBox isDefined
|
isEmpty
|
Retun true is this Boxis empty
|
myBox isEmpty
|
asA[B]
|
Return a Full[B] if the content of this Box is of type B, otherwise return Empty
|
myBox asA[Person]
|
isA[B]
|
Return a Full[B] if the contents of this Box is an instance of the specified class, otherwise return Empty
|
myBox isA[Person]
|
Note that Box contains a set of implicit conversion functions from/to Option and from/to Iterable.
Remember that Box is heavily used in Lift and most of the Lift’s API’s operates with
Boxes. The rationale is to avoid
null references and to operate safely in context where values may be missing. Of course, a
Box can be set to
null manually but we strongly recommend against doing so. There are cases, however, where you are using some third party Java libraries with APIs that return
null values. To cope with such cases in Lift you can use the
!! function to
Box that value. Listing
C.2↓ shows how we can deal with a possible
null value.
var x = getSomeValueThatMayBeNull();
var boxified = Box !! x
In this case the boxified variable will be Empty if x is null or Full(x) if x is a valid value/reference..
C.3 ActorPing
It provides convenient functionality to schedule messages to Actors.
// Assume myActor an existing Actor
// And a case object MyMessage
// Send the MyMessage message after 15 seconds
ActorPing.schedule(myActor, MyMessage, 15 seconds)
// Send the MyMessage message every 15 seconds. The cycle is stopped
// if recipient actor exits or replied back with UnSchedule message
ActorPing.scheduleAtFixedRate(myActor, MyMessage, 0 seconds, 15 seconds)
C.4 ClassHelpers
Provides convenient functions for loading classes using Java reflection, instantiating dinamically loaded classes, invoking methods vis reflection etc.
import _root_.net.liftweb.util.Helpers._
// lookup the class Bar in the three packages specified in th list
findClass("Bar", "com.foo" :: "com.bar" :: "com.baz" :: Nil)
invokeMethod(myClass, myInstance, "doSomething")
C.5 CodeHelpers
Provides a convenient way of telling why a boolean expression failed. For instance we are seeing manytime code like:
var isTooYoung = false;
var isTooBig = false;
var isTooLazy = true;
var exp = isTooYoung && isTooBig && isTooLazy
As you can see we have no way of telling if the exp was false because of isTooYoung, isTooBig or isTooLazy unless we test them again. But let’s see this:
import net.liftweb.util._
import net.liftweb.util.MonadicConversions._
val exp = (isTooYoung ~ "too young") &&
(isTooBad ~ "too bad") &&
(isToLazy ~ "too lazy")
println(exp match {
case False(msgs) =>
msgs mkString("Test failed because it is ’", "’ and ’", "’.")
case _ => "success"
})
Now if exp is a False we can tell why it failed as we have the messages now.
C.6 ControlHelpers
Provides convenient functions for try/catch situations. For example:
tryo {
// code here. Any exception thrown here will be silently caught
}
tryo((e: Throwable) => println(e)) {
// code here. Any exception here willbe caught add passed to
// the above function.
}
tryo(List(classOf[ClassNotFoundException], classOf[IOException])) {
// code here. If IOException or ClassNotFoundException is thrown
// (or a subclass of the two) they will be ignored. Any other
// exception will be rethrown.
}
C.7 CSSHelpers
This provide a convenient functionality to fix relative root paths in CSS (Cascade Stylesheet) files. Here is an example:
Assume this entry in a CSS file:
.boxStyle {
background-image: url(’/img/bkg.png’)
}
//in your code you can say
CSSHelpers.fixCSS(reader, "/myliftapp")
// where reader is a java.io.Reader that provides the
// content of the CSS file.
Now if your application is not deployed in the ROOT context path (“/”) and say it is deployed with the context root /myliftapp then the background picture will probably notbe found. Say http://my.domain.com/img/bkg.png is an unknown path. However http://my.domain.com/myliftapp/img/bkg.png is known. In the example above we are calling fixCSS so that it will automatically replace the root relative paths such that background-image: url(’/img/bkg.png’) becomes background-image: url(’/myliftapp/img/bkg.png’). To use that in your lift application you can do:
def boot(){
...
LiftRules.fixCSS("styles" :: "theme" :: Nil, Empty)
...
}
When the /styles/theme.css file Lift will apply the prefix specified. But in this case we provided an Empty Box. This actually means that Lift will apply the context path returned by S.contextPath function which as you know returns the context path from the HttpSession.
Internally when you call fixCSS a dispatch function is automatically created and pre-pended to LiftRules.dispatch. This is needed in order to intercept the browser request to this .css resource. Also internally we are telling Lift the this resource must be server by Lift and not by container.
The way it works internally is that we are using Scala combinator parsers to augment only the root relative paths with the given prefix.
C.8 BindHelpers
Binders are extensiveley discussed in other chapters so we won’t reiterate them here.
<lift:CountGame.run form="post">
<choose:guess>
Guess a number between 1 and 100.<br/>
Last guess: <count:last/><br />
Guess: <count:input/><br/>
<input type="submit" value="Guess"/>
</choose:guess>
<choose:win>
You Win!!<br />
You guessed <count:number/> after <count:count/> guesses.<br/>
</choose:win>
</lift:CountGame.run>
You can use the Helpers.chooseTemplate method to extract portions of a given XML input:
Choose template Scala code
import net.liftweb.util._
import Helpers._
class CountGame {
def run(xhtml: NodeSeq): NodeSeq = {
...
chooseTemplate("choose", "win", xhtml);
}
}
So in the snippet conditionally we can choose between parts of the snippet template. In the case above only the childs of <choose:win> node will be returned by the snippetfunction, hence rendered.
C.9 HttpHelpers
This provides helper functions for HTTP parameters manipulation, URL encoding/decoding etc. However there is some interesting functionality available that lets you choose between tags of a snippet.
Lift provides its own JSON parser if you ever need one. At a first glance it may be a bit redundant with Scala’s JSON parser but infact Scala’sparser has its own problems with large JSON objects hence List’s uses its own JSON parser implemented of course using combinator parsers.
Provides utility functions for calculating the distance between words
C.12 ListHelpers
Provides utility functions for manipulating lists that are not provided by Scala libraries.
C.13 NamedPartialFunctions
Provides extremly useful functions for invoking partial functions that are chained in lists of functions.
var f1: PartialFunction[Int,Int] = {
case 10 => 11
case 12 => 14
}
var f2: PartialFunction[Int,Int] = {
case 20 => 11
case 22 => 14
}
NamedPF(10, f1 :: f2 :: Nil)
Remember that many LiftRules variable are RuleSeq-s. Meaning that most of the times we re talking about lists of partial functions. Hence internally lift uses NamedPF for invoking such functions that are ultimately provided by the user. Please see LiftRules.dispatch
C.14 SecurityHelpers
Provides various functions used for random number generation, encryption/decriptions (blowfish), hash calculations (MD5, SHA, SHA-256) and so on.
C.15 TimeHelpers
Utility functions for time operations. For instance if also provides a set of implicit conversion functions that allow you to type “10 seconds” and returns the value in milliseconds.
(C) 2012 Lift 2.0 EditionWritten by Derek Chen-Becker, Marius Danciu and Tyler Weir