Up: Part I

6 Forms in Lift

In this chapter we’re going to discuss the specifics of how you generate and process forms with Lift. Besides standard GET/POST form processing, Lift provides AJAX forms (Chapter 11↓) as well as JSON form processing (Section 10.4.1↓), but we’re going to focus on the standard stuff here. We’re going to assume that you have a general knowledge of basic HTML form tags as well as how CGI form processing works.

6.1 Form Fundamentals

Let’s start with the basics of Lift form processing. A form in Lift is usually produced via a snippet that contains the additional form attribute. As we mentioned in Section 5.1↑, this attribute takes the value GET or POST, and when present makes the snippet code embed the proper form tags around the snippet HTML. Listing 6.1↓ shows an example of a form that we will be discussing throughout this section.
An Example Form Template
<lift:Ledger.add form="POST">
  <entry:description /> <entry:amount /><br />
  <entry:submit />
</lift:Ledger.add>
The first thing to understand about Lift’s form support is that you generally don’t use the HTML tags for form elements directly, but rather you use generator functions on
net.liftweb.http.SHtml. The main reason for this is that it allows Lift to set up all of the internal plumbing so that you keep your code simple. Additionally, we use Lift’s binding mechanism (Section 5.3.1↑) to “attach” the form elements in the proper location. In our example in Listing 6.1↑, we have bindings for a description field, an amount, and a submit button.
Our next step is to define the form snippet itself. Corresponding to our example template is Listing 6.1↓. This shows our add method with a few vars to hold the form data and a binding to the proper form elements. We’ll cover the processEntryAdd method in a moment; for now let’s look at what we have inside the add method.
An Example Form Snippet
def add (xhtml : NodeSeq) : NodeSeq = {
  var desc = ""
  var amount = "0"
​
  def processEntryAdd () { ... }
​
  bind("entry", xhtml,
       "description" -> SHtml.text(desc, desc = _),
       "amount" -> SHtml.text(amount, amount = _),
       "submit" -> SHtml.submit("Add", processEntryAdd))
}
First, you may be wondering why we use vars defined inside the method. Normally, these vars would be locally scoped (stack-based) and would be discarded as soon as the method returns. The beauty of Scala and Lift is that the right hand argument of each of the SHtml functions is actually a function itself. Because these functions, also known as anonymous closures, reference variables in local scope, Scala magically transforms them to heap variables behind the scenes. Lift, in turn, adds the function callbacks for each form element into its session state so that when the form is submitted, the appropriate closure is called and the state is updated. This is also why we define the processEntryAdd function inside of the add method: by doing so, the processEntryAdd function also has access to the closure variables. In our example, we’re using Scala’s placeholder “_” shorthand [X]  [X] For more details on placeholders, see the Scala Language Specification, section 6.23 to define our functions. Your description processing function could also be defined as:
newDesc => description = newDesc
One important thing to remember, however, is that each new invocation of the add method (for each page view) will get its own unique instance of the variables that we’ve defined. That means that if you want to retain values between submission and re-rendering of the form, you’ll want to use RequestVars (Section 3.11↑) or a StatefulSnippet (Section 5.3.3↑) instead . Generally you will only use vars defined within the snippet method when your form doesn’t require validation and you don’t need any of the submitted data between snippet executions. An example of using RequestVars for your form data would be if you want to do form validation and retain submitted values if validation fails, as shown in Listing 6.1↓. In this instance, we set an error message (more in Chapter B↓). Since we don’t explicitly redirect, the same page is loaded (the default “action” for a page in Lift is the page itself) and the current RequestVar value of description is used as the default value of the text box.
Using RequestVars with Forms
object description extends RequestVar("")
object amount extends RequestVar("0")
​
def add (xhtml : NodeSeq) : NodeSeq = {
  def processEntryAdd () =
    if (amount.toDouble <= 0) {
      S.error("Invalid amount")
    } else {
      // ... process Add ...
      redirectTo(...)
    }
​
  bind("entry", xhtml,
       "description" -> SHtml.text(description.is, description(_)),
  ...
}
The next thing to look at is how the form elements are generated. We use the SHtml helper object to generate a form element of the appropriate type for each variable. In our case, we just want text fields for the description and amount, but SHtml provides a number of other form element types that we’ll be covering later in this section. Generally, an element generator takes an argument for the initial value as well as a function to process the submitted value. Usually both of these arguments will use a variable, but there’s nothing stopping you from doing something such as
“description” -> SHtml.text(“”, println(“Description = “ + _))
Finally, our submit function executes the partially applied processEntryAdd function, which, having access to the variables we’ve defined, can do whatever it needs to do when the submit button is pressed.

6.2 Attributes for Form Elements

In addition to the approaches shown in Section 5.4↑, the SHtml generator functions allow you to apply attributes by passing the attribute name/value pairs as final arguments. This is usually simpler, and in some cases is much simpler than using the “%” operator directly. For example, checkbox and radio form elements are acutally returned as ChoiceHolder instances, which do not directly support the “%” operator. Listing 6.2↓ shows how to apply the same attributes as Listing 5.4.1↑ using the varargs approach.
Applying Attributes as Varargs
val myInput = SHtml.text("", processText(_), "id" -> "inputField",
  "class" -> "highlighted")

6.3 An Overview of Form Elements

Now that we’ve covered the basics of forms, we’re going to go into a little more detail for each form element generator method on SHtml. The a method (all 3 variants) as well as the ajax* methods are specific to AJAX forms, which are covered in detail in Chapter 11↓. The json* methods are covered in Section 10.4.1↓. We’ll be covering the fileUpload method in detail in Section 6.4↓. One final note before we dive in is that most generator methods have an overload with a trailing asterisk (i.e. hidden_*); these are generally equivalent to the overloads without an asterisk but are intended for Lift’s internal use.

6.3.1 checkbox

The checkbox method generates a checkbox form element, taking an initial Boolean value as well as a function (Boolean) ⇒ Any that is called when the checkbox is submitted. If you’ve done a lot of HTML form processing you might wonder how this actually occurs, since an unchecked checkbox is not actually submitted as part of a form. Lift works around this by adding a hidden form element for each checkbox with the same element name, but with a false value, to ensure that the callback function is always called. Because more than one XML node is returned by the generator, you can’t use the % metadata mechanism to set attributes on the check box element. Instead, use attribute pairs as arguments to the generator function as outlined in Section 5.4.1↑.
For example, Listing 6.3.1↓ shows a checkbox with an id of “snazzy” and a class attribute set to “woohoo.”
A Checkbox Example
SHtml.checkbox_id(false, if (_) frobnicate(), 
                  Full("snazzy"), "class" -> "woohoo")

6.3.2 hidden

The hidden method generates a hidden form field. Unlike the HTML hidden field, the hidden tag is not intended to hold a plain value; rather, in Lift it takes a function () ⇒ Any argument that is called when the form is submitted. As with most of the other generators, it also takes a final varargs sequence of Pair[String,String] attributes to be added to the XML node. Listing 6.3.2↓ shows an example of using a hidden field to “log” information. (When the form is submitted, “Form was submitted” will be printed to stdout. This can be a useful trick for debugging if you’re not using a full-blown IDE.)
A Hidden Example
SHtml.hidden(() => println("Form was submitted"))

6.3.3 link

The link method generates a standard HTML link to a page (an <a> tag, or anchor), but also ensures that a given function is executed when the link is clicked. The first argument is the web context relative link path, the second argument is the () ⇒ Any function that will be executed when the link is clicked, and the third argument is a NodeSeq that will make up the body of the link. You may optionally pass one or more Pair[String,String] attributes to be added to the link element. Listing 6.3.3↓ shows using a link to load an Expense entry for editing from within a table. In this case we’re using a RequestVar to hold the entry to edit, so the link function is a closure that loads the current Expense entry. This combination of link and RequestVars is a common pattern for passing objects between different pages.
A Link Example
object currentExpense extends RequestVar[Box[Expense]](Empty)
​
def list (xhtml : NodeSeq) : NodeSeq = {
  ...
  val entriesXml = 
    entries.map(entry => 
      bind("entry", chooseTemplate("expense", "entries", xhtml),
        ...
        "edit" -> SHtml.link("/editExpense", 
          () => currentExpense(Full(entry)),
          Text("Edit")))
  )
}

6.3.4 text and password

The text and password methods generate standard text and password input fields, respectively. While both take string default values and (String) ⇒ Any functions to process the return, the password text field masks typed characters and doesn’t allow copying the value from the box on the client side. Listing 6.3.4↓ shows an example of using both text and password for a login page.
A Text Field Example
def login(xhtml : NodeSeq) : NodeSeq = {
  var user = ""; var pass = "";
  def auth () = { ... }
  bind("login", xhtml,
       "user" -> SHtml.text(user, user = _, "maxlength" -> "40")
       "pass" -> SHtml.password(pass, pass = _)
       "submit" -> SHtml.submit("Login", auth))
}
Alternatively, you might want the user (but not the password) to be stored in a RequestVar so that if the authentication fails the user doesn’t have to retype it. Listing 6.3.4↓ shows how the snippet would look in this case.
A RequestVar Text Field Example
object user extends RequestVar[String]("")
def login(xhtml : NodeSeq) : NodeSeq = {
  var pass = "";
  def auth () = { ... }
  bind("login", xhtml,
       "user" -> SHtml.text(user.is, user(_), "maxlength" -> "40"),
       "pass" -> SHtml.password(pass, pass = _),
       "submit" -> SHtml.submit("Login", auth))
}

6.3.5 textarea

The textarea method generates a textarea HTML form element. Generally the functionality mirrors that of text, although because it’s a textarea, you can control width and height by adding cols and rows attributes as shown in Listing 6.3.5↓. (You can, of course, add any other HTML attributes in the same manner.)
A Textarea Example
var noteText = ""
val notes = 
  SHtml.textarea(noteText, noteText = _, 
                 "cols" -> "80", "rows" -> "8")

6.3.6 submit

Submit generates the submit form element (typically a button). It requires two parameters: a String value to use as the button label, and a function () ⇒ Any that can be used to process your form results. One important thing to note about submit is that form elements are processed in the order that they appear in the HTML document. This means that you should put your submit element last in your forms: any items after the submit element won’t have been “set” by the time the submit function is called. Listings 6.3.4↑ and 6.3.4↑ use the SHtml.submit method for the authentication handler invocation.

6.3.7 multiselect

Up to this point we’ve covered some fairly simple form elements. Multiselect is a bit more complex in that it doesn’t just process single values. Instead, it allows you to select multiple elements out of an initial Seq and then process each selected element individually. Listing 6.3.7↓ shows using a multiselect to allow the user to select multiple categories for a ledger entry. We assume that a Category entity has an id synthetic key as well as a String name value. The first thing we do is map the collection of all categories into pairs of (value, display) strings. The value is what will be returned to our processing function, while the display string is what will be shown in the select box for the user. Next, we turn the current entry’s categories into a Seq of just value strings, and we create a Set variable to hold the returned values. Finally, we do our form binding. In this example we use a helper function, loadCategory (not defined here), that takes a String representing a Category’s primary key and returns the category. We then use this helper method to update the Set that we created earlier. Note that the callback function will be executed for each selected item in the multiselect, which is why the callback takes a String argument instead of a Set[String]. This is also why we have to use our own set to manage the values. Depending on your use case, you may or may not need to store the returned values in a collection.
Using multiselect
def mySnippet ... {
  val possible = allCategories.map(c => (c.id.toString, c.name))
  val current = currentEntry.categories.map(c => c.id.toString)
  var updated = Set.empty[Category]
  bind (..., 
    "categories" -> 
      SHtml.multiselect(possible, current, updated += loadCategory(_)))
}

6.3.8 radio

The radio method generates a set of radio buttons that take String values and return a single String (the selected button) on form submission. The values are used as labels for the Radio buttons, so you may need to set up a Map to translate back into useful values. The radio method also takes a Box[String] that can be used to pre-select one of the buttons. The value of the Box must match one of the option values, or if you pass Empty no buttons will be selected. Listing 6.3.8↓ shows an example of using radio to select a color. In this example, we use a Map from color names to the actual color values for the translation. To minimize errors, we use the keys property of the Map to generate the list of options.
Using radio for Colors
import java.awt.Color
var myColor : Color = _
val colorMap = Map("Red" -> Color.red,
                   "White" -> Color.white,
                   "Blue" -> Color.blue)
val colors = SHtml.radio(colorMap.keys.toList, Empty, myColor = colorMap(_))

6.3.9 select

The select method is very similar to the multiselect method except that only one item may be selected from the list of options. That also means that the default option is a Box[String] instead of a Seq[String]. As with multiselect, you pass a sequence of (value, display) pairs as the options for the select, and process the return with a (String) ⇒ Any function. Listing 6.3.9↓ shows an example of using a select to choose an account to view.
A select Example
var selectedAccount : Account = _
val accounts = User.accounts.map(acc => (acc.id.toString, acc.name))
val chooseAccount = 
  SHtml.select(accounts, Empty, 
               selectedAccount = loadAccount(_), "class" -> "myselect")
An important thing to note is that Lift will verify that the value submitted in the form matches one of the options that was passed in. If you need to do dynamic updating of the list, then you’ll need to use untrustedSelect (Section 6.3.11↓).

6.3.10 selectObj

One of the drawbacks with the select and multiselect generators is that they deal only in Strings; if you want to select objects you need to provide your own code for mapping from the strings. The selectObj generator method handles all of this for you. Instead of passing a sequence of (value string, display string) pairs, you pass in a sequence of (object, display string) pairs. Similarly, the default value is a Box[T] and the callback function is (T) ⇒ Any , where T is the type of the object (selectObj is a generic function). Listing 6.3.10↓ shows a reworking of our radio example (Listing 6.3.8↑) to select Colors directly. Note that we set the select to default to Color.red by passing in a Full Box.
Using selectObj for Colors
... standard Lift imports ...
import _root_.java.awt.Color
​
class SelectSnippet {
  def chooseColor (xhtml : NodeSeq) : NodeSeq = {
    var myColor = Color.red
    val options = List(Color.red, Color.white, Color.blue)
    val colors = SHtml.selectObj(options, Full(myColor), myColor = _)
    bind(...)
  }
}

6.3.11 untrustedSelect

The untrustedSelect generator is essentially the same as the select generator, except that the value returned in the form isn’t validated against the original option sequence. This can be useful if you want to update the selection on the client side using JavaScript.

6.4 File Uploads

File uploads are a special case of form submission that allow the client to send a local file to the server. This is accomplished by using multipart forms. You can enable this by setting the multipart attribute on your snippet tag to true. Listing 6.4↓ shows how we can add a file upload to our existing expense entry form so that users can attach scanned receipts to their expenses. We modify our template to add a new form, shown below. Note the multipart=”true” attribute.
File Upload Template
<lift:AddEntry.addEntry form="POST" multipart="true">
  ... existing headers ...
  <td>Receipt (JPEG or PNG)</td>
  ... existing form fields ...
  <td><e:receipt /></td>
  ...
</lift:AddEntry.addEntry>
On the server side, Listing 6.4↓ shows how we modify the existing addEntry snippet to handle the (optional) file attachment. We’ve added some logic to the existing form submission callback to check to make sure that the image is of the proper type, then we use the SHtml file upload generator with a callback that sets our fileHolder variable. The callback for the fileUpload generator takes a FileParamHolder, a special case class that contains information about the uploaded file. The FileParamHolder case class has four parameters:
nameThe name of the form field that this file is associated with, as sent by the client
mimeTypeThe mime type as sent by the client
filenameThe filename as sent by the client
fileAn Array[Byte] containing the uploaded file contents
In our example, we want to save the file data into a MappedBinary field on our expense entry. You could just as easily process the data in place using a scala.io.Source or
java.io.ByteArrayInputStream, or output it using a java.io.FileOutputStream.
File Upload Snippet
class AddEntry {
  ...
  // Add a variable to hold the FileParamHolder on submission
  var fileHolder : Box[FileParamHolder] = Empty
  ...
  def doTagsAndSubmit (t : String) {
    ...
    val e : Expense = ...
    // Add the optional receipt if it’s the correct type
    val receiptOk = fileHolder match {
      // An empty upload gets reported with a null mime type,
      // so we need to handle this special case
      case Full(FileParamHolder(_, null, _, _)) => true
      case Full(FileParamHolder(_, mime, _, data))
        if mime.startsWith("image/") => {
          e.receipt(data).receiptMime(mime)
          true
        }
      case Full(_) => {
        S.error("Invalid receipt attachment")
        false
      }
      case _ => true 
    }
​
    (e.validate, receiptOk) match {
    ...
    }
    ...
  }
​
  bind("e", in,
       ...
       "receipt" -> SHtml.fileUpload(fileHolder = _),
       "tags" -> SHtml.text(tags, doTagsAndSubmit))
  }
}
By default, Lift will utilize the InMemoryFileParamHolder to represent uploaded file data. This implementation reads the uploaded data directly into memory (you retrieve the byte array with the file val). If you would prefer to have Lift write uploaded data to disk and then give you a server-local filename to work with, you can use the LiftRules.handleMimeFile configuration hook to instead use the OnDiskFileParamHolder, as shown in Listing 6.4↓. The OnDiskFileParamHolder class has an additional property, localFile, that is a java.io.File object for the temporary upload file.
Using OnDiskFileParamHolder
// in Boot.boot:
LiftRules.handleMimeFile = OnDiskFileParamHolder.apply
Up: Part I

(C) 2012 Lift 2.0 EditionWritten by Derek Chen-Becker, Marius Danciu and Tyler Weir