Chapter 3. Forms Processing in Lift

This chapter looks at how to process form data with Lift: submitting forms, working with form elements. The end result of a form submission can be records being updated in a database, so you may also find Chapter 7 or Chapter 8 useful, as they discuss relational databases and MongoDB, respectively.

To the extent that form processing is passing data to a server, there are also recipes in Chapter 5 that are relevant to form processing.

You’ll find many of the examples from this chapter as source code at https://github.com/LiftCookbook/cookbook_forms.

Plain Old Form Processing

Problem

You want to process form data in a regular, old-fashioned, non-Ajax way.

Solution

Extract form values with S.param, process the values, and produce some output.

For example, we can show a form, process an input value, and give a message back as a notice. The template is a regular HTML form, with the addition of a snippet:

<form data-lift="Plain" action="/plain" method="post">
  <input type="text" name="name" placeholder="What's your name?">
  <input type="submit" value="Go">
</form>

In the snippet, we can pick out the value of the field name with S.param("name"):

package code.snippet

import net.liftweb.common.Full
import net.liftweb.http.S
import net.liftweb.util.PassThru

object Plain {

  def render = S.param("name") match {
    case Full(name) =>
      S.notice("Hello "+name)
      S.redirectTo("/plain")
    case _ =>
      PassThru
  }

}

The first time through this snippet, there will be no parameter, so we just pass back the form unchanged to the browser, which is what PassThru is doing. You can then enter a value into the name field and submit the form. This will result in Lift processing the template again, but this time, with a value for the name input. The result will be your browser redirected to a page with a message set for display.

Discussion

Manually plucking parameters from a request isn’t making the best use of Lift, but sometimes you need to do it, and S.param is the way you can process request parameters.

The result of S.param is a Box[String], and in the previous example, we pattern match on this value. With more than one parameter, you’ve probably seen S.param used in this way:

def render = {
  for {
    name <- S.param("name")
    pet <- S.param("petName")
  } {
    S.notice("Hello %s and %s".format(name,pet))
    S.redirectTo("/plain")
  }

 PassThru
}

If both name and petName are provided, the body of the for will be evaluated.

S.params(name)
Produces a List[String] for all the request parameters with the given name
S.post_? and S.get_?
Tells you if the request was a GET or POST
S.getRequestHeader(name)
Gives the Box[String] for a header in the request with the given name
S.request
Accesses the Box[Req], which gives you access to further HTTP-specific information about the request

As an example of using S.request, we could produce a List[String] for the values of all request parameters that have name somewhere in their parameter name:

val names = for {
  req <- S.request.toList
  paramName <- req.paramNames
  if paramName.toLowerCase contains "name"
  value <- S.param(paramName)
} yield value

Note that by opening up S.request we can access all the parameter names via the paramNames function on Req.

Screen or Wizard provide alternatives for form processing, but sometimes you just want to pull values from a request, as demonstrated in this recipe.

See Also

Simply Lift covers a variety of ways of processing forms.

Ajax Form Processing

Problem

You want to process a form on the server via Ajax, without reloading the whole page.

Solution

Mark your form as an Ajax form with data-lift="form.ajax" and supply a function to run on the server when the form is submitted.

Here’s an example of a form that will collect our name and submit it via Ajax to the server:

<form data-lift="form.ajax">
  <div data-lift="EchoForm">
    <input type="text" name="name" placeholder="What's your name?">
    <input type="submit">
  </div>
</form>

<div id="result">Your name will be echoed here</div>

The following snippet will echo back the name via Ajax:

package code.snippet

import net.liftweb.util.Helpers._
import net.liftweb.http.SHtml.{text,ajaxSubmit}
import net.liftweb.http.js.JsCmd
import net.liftweb.http.js.JsCmds.SetHtml
import xml.Text

object EchoForm extends {

  def render = {

    var name = ""

    def process() : JsCmd = SetHtml("result", Text(name))

    "@name" #> text(name, s => name = s) &
    "type=submit" #> ajaxSubmit("Click Me", process)
  }
}

The render method is binding the name input field to a function that will assign whatever the user enters to the variable name. Note you’ll more typically see s => name = s written in the shorter form of name = _.

When the button is pressed, the process function is called, which will return the value of the name back to the element in the HTML with an ID of result.

Discussion

The data-lift="form.ajax" part of this recipe ensures that Lift adds the Ajax processing mechanics to the form. This means the <form> element in the output will end up as something like this:

<form id="F2203365740CJME2G" action="javascript://"
  onsubmit="liftAjax.lift_ajaxHandler(
    jQuery('#'+&quot;F2203365740CJME2G&quot;).serialize(),
    null, null, &quot;javascript&quot;);return false;">
  ...
</form>

In other words, when the form is asked to submit, Lift will serialise the form via Ajax. This means you don’t necessarily need the submit button at all. In this example with a single text field, if you omit the submit button you can trigger serialisation by pressing Return. This will trigger the s => name = s function, which was bound in our regular data-lift="EchoForm" snippet. In other words, the value name will be assigned even without a submit button.

Adding in a submit button gives us a way to perform actions once all the field’s functions have been executed.

Notice that Lift’s approach is to serialise the form to the server, execute the functions associated with the fields, execute the submit function (if any), then return a JavaScript result to the client. The default serialisation process is to use jQuery’s serialization method on the form. This serialises fields except submit buttons and file uploads.

Submit styling

The SHtml.ajaxSubmit function generates a <input type="submit"> element for the page. You may prefer to use a styled button for submit. For example, with Twitter Bootstrap, a button with an icon would require the following markup:

<button id="submit" class="btn btn-primary btn-large">
  <i class="icon-white icon-ok"></i> Submit
</button>

Pressing a <button> inside a form triggers the submit. However, if you bound that button with SHtml.ajaxSubmit, the content, and therefore the styling, would be lost.

To fix this, you can assign a function to a hidden field. This function will be called when the form is submitted just like any other field. The only part of our snippet that changes is the CSS selector binding:

import net.liftweb.http.SHtml.hidden

"@name" #> text(name, s => name = s) &
"button *+" #> hidden(process)

The *+ replacement rule means append a value to the child node of the button. This will include a hidden field in the form, something like this:

<input type="hidden" name="F11202029628285OIEC2" value="true">

When the form is submitted, the hidden field is submitted, and like any field, Lift will call the function associated with it: process, in this case.

The effect is something like ajaxSubmit, but not exactly the same. In this instance, we’re appending a hidden field after the <button>, but you could place it anywhere on the form you find convenient. However, there’s one complication: when is process called? Is it before the name has been assigned or after? That depends on the order in which the fields are rendered. That’s to say, in your HTML template, placing the button before the text field (and therefore moving the hidden field’s position in this example), the process function is called before the name has been set.

There are a couple of ways around that. Either, ensure your hidden fields used in this way appear late in your form, or make sure the function is called late with a formGroup:

import net.liftweb.http.SHtml.hidden
import net.liftweb.http.S

"@name" #> text(name, s => name = s) &
"button *+" #> S.formGroup(1000) { hidden(process) }

The formGroup addition manipulates the function identifier to ensure it sorts later, resulting in the function process being called later than fields in the default group (0).

Lift 2.6 and 3.0 may contain ajaxOnSubmit, which will give the reliability of ajaxSubmit and the flexibility of the hidden-field approach. If you want to try it in Lift 2.5, Antonio Salazar Cardozo has created a helper you can include in your project.

See Also

Function order is discussed in the Lift Cool Tips wiki page.

For more details about the form serialisation process, take a look at the jQuery documentation.

“Ajax File Upload” describes Ajax file uploads.

Ajax JSON Form Processing

Problem

You want to process a form via Ajax, sending the data in JSON format.

Solution

Make use of Lift’s jlift.js JavaScript library and JsonHandler class.

As an example, we can create a "motto server" that will accept an institution name and the institution’s motto and perform some action on these values. We’re just going to echo the name and motto back to the client.

Consider this HTML, which is not in a form, but includes jlift.js:

<html>
<head>
  <title>JSON Form</title>
</head>
<body data-lift-content-id="main">

<div id="main" data-lift="surround?with=default;at=content">

  <h1>Json Form example</h1>

  <!-- Required for JSON forms processing -->
  <script src="/classpath/jlift.js" data-lift="tail"></script>

  <div data-lift="JsonForm" >

    <script id="jsonScript" data-lift="tail"></script>

    <div id="jsonForm">

      <label for="name">
        Institution
        <input id="name" type="text" name="name" value="Royal Society" />
      </label>

      <label for="motto">
        Motto
        <input id="motto" type="text" name="motto" value="Nullius in verba" />
      </label>

      <input type="submit" value="Send" />

    </div>

    <div id="result">
      Result will appear here.
    </div>

  </div>

</div>
</body>
</html>

This HTML presents the user with two fields, a name and a motto, wrapped in a <div> called jsonForm. There’s also a placeholder for some results, and you’ll notice a jsonScript placeholder for some JavaScript code. The jsonForm will be manipulated to ensure it is sent via Ajax, and the jsonScript will be replaced with Lift’s code to perform the serialisation. This happens in the snippet code:

package code.snippet

import scala.xml.{Text, NodeSeq}

import net.liftweb.util.Helpers._
import net.liftweb.util.JsonCmd
import net.liftweb.http.SHtml.jsonForm
import net.liftweb.http.JsonHandler
import net.liftweb.http.js.JsCmd
import net.liftweb.http.js.JsCmds.{SetHtml, Script}

object JsonForm {

  def render =
    "#jsonForm" #> ((ns:NodeSeq) => jsonForm(MottoServer, ns)) &
    "#jsonScript" #> Script(MottoServer.jsCmd)

  object MottoServer extends JsonHandler {

    def apply(in: Any): JsCmd = in match {
      case JsonCmd("processForm", target, params: Map[String, String], all) =>
        val name = params.getOrElse("name", "No Name")
        val motto = params.getOrElse("motto", "No Motto")
        SetHtml("result",
          Text("The motto of %s is %s".format(name,motto)) )
    }
  }
}

Like many snippets, this Scala code contains a render method that binds to elements on the page. Specifically, jsonForm is being replaced with SHtml.jsonForm, which will take a NodeSeq (which are the input fields), and turns it into a form that will submit the values as JSON. The submission will be to our MottoServer code.

The jsonScript element is bound to JavaScript that will perform the transmission and encoding of the values to the server.

If you click the "Send" button and observe the network traffic, you’ll see the following sent to the server:

{
  "command": "processForm",
  "params": {"name":"Royal Society","motto":"Nullius in verba"}
}

This is the value of the all parameter in the JsonCmd being pattern matched against in MottoServer.apply. Lift has taken care of the plumbing to make this happen.

The result of the pattern match in the example is to pick out the two field values and send back JavaScript to update the results <div> with: "The motto of the Royal Society is Nullius in verba."

Discussion

The JsonHandler class and the SHtml.jsonForm method are together performing a lot of work for us. The jsonForm method is arranging for form fields to be encoded as JSON and sent, via Ajax, to our MottoServer as a JsonCmd. In fact, it’s a JsonCmd with a default command name of "processForm".

Our MottoServer class is looking for (matching on) this JsonCmd, extracting the values of the form fields, and echoing these back to the client as a JsCmd that updates a <div> on the page.

The MottoServer.jsCmd part is generating the JavaScript required to deliver the form fields to the server. As we will see later, this is providing a general purpose function we can use to send different JSON values and commands to the server.

Notice also, from the network traffic, that the form fields sent are serialised with the names they are given on the page. There are no "F…" values sent that map to function calls on the server. A consequence of this is that any fields dynamically added to the page will also be serialised to the server, where they can be picked up in the MottoServer.

The script jlift.js is providing the plumbing to make much of this happen.

Before going on, convince yourself that we’re generating JavaScript on the server side (MottoServer.jsCmd), which is executed on the client side when the form is submitted, to deliver results to the server.

Additional commands

In the previous example, we match on a JsonCmd with a command name of "processForm". You may be wondering what other commands can be supplied, or what the meaning of the target value is.

To demonstrate how you can implement other commands, we can add two additional buttons. These buttons will just convert the motto to upper- or lowercase. The server-side render method changes as follows:

def render =
  "#jsonForm" #> ((ns:NodeSeq) => jsonForm(MottoServer, ns)) &
  "#jsonScript" #> Script(
    MottoServer.jsCmd &
    Function("changeCase", List("direction"),
      MottoServer.call("processCase", JsVar("direction"),
        JsRaw("$('#motto').val()"))
    )
  )

The JsonForm is unchanged. We still include MottoServer.jsCmd, and we still want to wrap the fields and submit them as before. What we’ve added is an extra JavaScript function called changeCase, which takes one argument called direction and as a body calls the MottoServer with various parameters. When it is rendered on the page, it would appear as something like this:

function changeCase(direction) {
  F299202CYGIL({'command': "processCase", 'target': direction,
    'params': $('#motto').val() });
}

The F299202CYGIL function (or similar name) is generated by Lift as part of MottoServer.jsCmd, and it is responsible for delivering data to the server. The data it is delivering, in this case, is a JSON structure consisting of a different command (processCase), a target of whatever the JavaScript value direction evaluates to, and a parameter that is the result of the jQuery expression for the value of the #motto form field.

When is the changeCase function called? That’s up to us, and one very simple way to call the function would be by this addition to the HTML:

<button onclick="javascript:changeCase('upper')">Upper case the Motto</button>
<button onclick="javascript:changeCase('lower')">Lower case the Motto</button>

When either of these buttons are pressed, the result will be a JSON value sent to the server with the command of processCase and the direction and params set accordingly. All that is left is to modify our MottoServer to pick up this JsonCmd on the server:

object MottoServer extends JsonHandler {

  def apply(in: Any): JsCmd = in match {

    case JsonCmd("processForm", target, params: Map[String, String], all) =>
      val name = params.getOrElse("name", "No Name")
      val motto = params.getOrElse("motto", "No Motto")
      SetHtml("result",
        Text("The motto of %s is %s".format(name,motto)) )

    case JsonCmd("processCase", direction, motto: String, all) =>
      val update =
        if (direction == "upper") motto.toUpperCase
        else motto.toLowerCase
      SetValById("motto", update)

  }
}

The first JsonCmd is unchanged. The second matches on the parameters sent and results in updating the form fields with an upper- or lowercase version of the motto.

See Also

The Lift demo site contains further examples of JsonHandler.

If you want to process JSON via REST, take a look at the stateless JSON examples.

Lift in Action, section 9.1.4 discusses "Using JSON forms with AJAX," as does section 10.4 of Exploring Lift.

Use a Date Picker for Input

Problem

You want to provide a date picker to make it easy for users to supply dates to your forms.

Solution

Use a standard Lift SHtml.text input field and attach a JavaScript date picker to it. In this example, we will use the jQuery UI date picker.

Our form will include an input field called birthday to be used as a date picker, and also the jQuery UI JavaScript and CSS:

<!DOCTYPE html>
<head>
  <meta content="text/html; charset=UTF-8" http-equiv="content-type" />
  <title>jQuery Date Picker</title>
</head>
<body data-lift-content-id="main">
<div id="main" data-lift="surround?with=default;at=content">

  <h3>When's your birthday?</h3>

  <link data-lift="head" type="text/css" rel="stylesheet"
    href="//cdnjs.cloudflare.com/ajax/libs/jqueryui/1.10.2/css/smoothness
          /jquery-ui-1.10.2.custom.min.css">
  </link>

  <script data-lift="tail"
    src="//cdnjs.cloudflare.com/ajax/libs/jqueryui/1.10.2/jquery-ui.min.js">
  </script>

  <div data-lift="JqDatePicker?form=post">
    <input type="text" id="birthday">
    <input type="submit" value="Submit">
  </div>

</div>
</body>
</html>

This would normally produce a regular text input field, but we can change that by adding JavaScript to attach the date picker to the input field. You could do this in the template, but in this example, we’re enhancing the text field as part of the snippet code:

package code.snippet

import java.util.Date
import java.text.SimpleDateFormat

import net.liftweb.util.Helpers._
import net.liftweb.http.{S, SHtml}
import net.liftweb.http.js.JsCmds.Run
import net.liftweb.common.Loggable

class JqDatePicker extends Loggable {

  val dateFormat = new SimpleDateFormat("yyyy-MM-dd")

  val default = (dateFormat format now)

  def logDate(s: String) : Unit = {
    val date : Date = tryo(dateFormat parse s) getOrElse now
    logger.info("Birthday: "+date)
  }

  def render = {
    S.appendJs(enhance)
    "#birthday" #> SHtml.text(default, logDate)
  }

  val enhance =
    Run("$('#birthday').datepicker({dateFormat: 'yy-mm-dd'});")
}

Notice in render we are binding a regular SHtml.text field to the element with the ID of birthday, but also appending JavaScript to the page. JavaScript selects the birthday input field and attaches a configured date picker to it.

When the field is submitted, the logDate method is called with the value of the text field. We parse the text into a java.util.Date object. The tryo Lift helper will catch any ParseException and return a Box[Date], which we open, or default to the current date if a bad date is supplied.

Running this code and submitting the form will produce a log message such as:

INFO  code.snippet.DatePicker - Birthday: Sun Apr 21 00:00:00 BST 2013

Discussion

The approach outlined in this recipe can be used with other date picker libraries. The key point is to configure the date picker to submit a date in a format you can parse when the value is submitted to the server. This is the "wire format" of the date, and does not have to necessarily be the same format the user sees in the browser, depending on the browser or the JavaScript library being used.

HTML5 date pickers

The HTML5 specification includes support for a variety of date input types: datetime, datetime-local, date, month, time, and week. For example:

<input type="date" name="birthday" value="2013-04-21">

This type of input field will submit a date in yyyy-mm-dd format, which our snippet would be able to process.

As more browsers implement these types, it will become possible to depend on them. However, you can default to the HTML5 browser-native date pickers today and fall back to a JavaScript library as required. The difference is shown in Figure 3-1.

An input field with the jQuery UI date picker attached, compared to the browser-native date picker in Chrome
Figure 3-1. An input field with the jQuery UI date picker attached, compared to the browser-native date picker in Chrome

To detect whether the browser supports type="date" inputs, we can use the Modernizr library. This is an additional script in our template:

<script data-lift="tail"
  src="//cdnjs.cloudflare.com/ajax/libs/modernizr/2.6.2/modernizr.min.js">
</script>

We will use this in our snippet. In fact, there are two changes we need to make to the snippet:

  1. Add the type="date" attribute to the input field.
  2. Modify the JavaScript to only attach the jQuery UI date picker in browsers that don’t support the type="date" input.

In code, that becomes:

def render = {
  S.appendJs(enhance)
  "#birthday" #> SHtml.text(default, logDate, ("type"->"date"))
}

val enhance = Run(
  """
    |if (!Modernizr.inputtypes.date) {
    | $('input[type=date]').datepicker({dateFormat: 'yy-mm-dd'});
    |}
  """.stripMargin)

The "type" -> "date" parameter on SHtml.text is setting the attribute type to the value date on the resulting <input> field.

When this snippet runs, and the page is rendered, the jQuery UI date picker will be attached to input fields of type="date" only if the browser doesn’t support that type already.

See Also

Dive into HTML5 describes how to detect browser features.

The jQuery UI API documentation lists the various configuration options for the date picker.

The HTML5 date input types submit dates in RFC 3339 format.

Making Suggestions with Autocomplete

Problem

You want to provide an autocomplete widget, to give users suggestions as they type into a text field.

Solution

Use a JavaScript autocomplete widget, for example, the jQuery UI autocomplete via the AutoComplete class from the Lift widgets module.

Start by adding the Lift widgets module to your build.sbt:

libraryDependencies += "net.liftmodules" %% "widgets_2.5" % "1.3"

To enable the autocomplete widget, initialise it in Boot.scala:

import net.liftmodules.widgets.autocomplete.AutoComplete
AutoComplete.init()

We can create a regular form snippet:

<form data-lift="ProgrammingLanguages?form=post">
  <input id="autocomplete">
  <input type="submit">
</form>

Connect the AutoComplete class to the element with the ID of autocomplete:

package code.snippet

import net.liftweb.util.Helpers._
import net.liftweb.common.Loggable

import net.liftmodules.widgets.autocomplete.AutoComplete

class ProgrammingLanguages extends Loggable {

  val languages = List(
    "C", "C++", "Clojure", "CoffeeScript",
    "Java", "JavaScript",
    "POP-11", "Prolog", "Python", "Processing",
    "Scala", "Scheme", "Smalltalk", "SuperCollider"
  )

  def render = {

    val default = ""

    def suggest(value: String, limit: Int) =
      languages.filter(_.toLowerCase.startsWith(value))

    def submit(value: String) : Unit =
      logger.info("Value submitted: "+value)

    "#autocomplete" #> AutoComplete(default, suggest, submit)
  }

}

The last line of this snippet shows the binding of the AutoComplete class, which takes:

  • A default value to show
  • A function that will produce suggestions from the text value entered—the result is a Seq[String] of suggestions
  • A function to call when the form is submitted

Running this code renders as shown in Figure 3-2.

The rendering of the ProgrammingLanguages snippet
Figure 3-2. The rendering of the ProgrammingLanguages snippet

When the form is submitted, the submit function will be passed the selected value. The submit function is simply logging this value:

 INFO  code.snippet.ProgrammingLanguages - Value submitted: Scala

Discussion

The autocomplete widget uses jQuery autocomplete. This can be seen by examining the NodeSeq produced by the AutoComplete.apply method:

 <span>
  <head>
  <link type="text/css" rel="stylesheet"
    href="/classpath/autocomplete/jquery.autocomplete.css">
  </link>
  <script type="text/javascript"
    src="/classpath/autocomplete/jquery.autocomplete.js">
  </script>
  <script type="text/javascript">
// <![CDATA[
  jQuery(document).ready(function(){
    var data = "/ajax_request?F846528841915S2RBI0=foo";
    jQuery("#F846528841916S3QZ0V").
      autocomplete(data, {minChars:0,matchContains:true}).
      result(function(event, dt, formatted) {
       jQuery("#F846528841917CF4ZGL").val(formatted);
      }
     );
  });;
// ]]>
</script>
  </head>
  <input type="text" value="" id="F846528841916S3QZ0V"></input>
  <input name="F846528841917CF4ZGL" type="hidden" value=""
   id="F846528841917CF4ZGL"></input>
</span>

This chunk of markup is generated from the AutoComplete(default, suggest, submit) call. What’s happening here is that the jQuery UI autocomplete JavaScript and CSS, which is bundled with the Lift widgets module, is being included on the page. Recall from “Adding to the Head of a Page” that Lift will merge the <head> part of this markup into the <head> of the final HTML page.

When the page loads, the jQuery UI autocomplete function is bound to the input field, and configured with a URL, which will deliver the user’s input to our suggest function. All suggest needs to do is return a Seq[String] of values for the jQuery autocomplete code to display to the user.

jQuery 1.9

jQuery 1.9 removed the $.browser method that the Autocomplete Widget needs. The work-around for this is to include the jQuery migration package in your template:

<script data-lift="head"
  src="http://code.jquery.com/jquery-migrate-1.2.1.js">
</script>

Submitting new values

One peculiarity of the AutoComplete widget is that if you type in a new value—one not suggested—and press submit, it is not sent to the server. That is, you need to click on one of the suggestions to select it. If that’s not the behaviour you want, you can adjust it.

Inside the render method, we can modify the behaviour by adding JavaScript to the page:

import net.liftweb.http.S
import net.liftweb.http.js.JsCmds.Run
S.appendJs(Run(
"""
  |$('#autocomplete input[type=text]').bind('blur',function() {
  |  $(this).next().val($(this).val());
  |});
""".stripMargin))

With this in place, when the input field loses focus—for example, when the submit button is pressed—the value of the input field is stored as the value to be sent to the server.

Alternative autocomplete JavaScript

Looking at the way the widget module builds autocomplete functionality may give you an insight into how you can incorporate other JavaScript autocomplete libraries into your Lift application. The idea is to include the JavaScript library, connect it to an element on the page, and then arrange for the server to be called to generate suggestions. Of course, if you only have a few items for the user to pick from, you could just include those items on the page, rather than making a round trip to the server.

As an example of server-generated suggestions, we can look at the Typeahead component that is included in Twitter Bootstrap.

To incorporate Typeahead, the template needs to change to include the library and mark the input field in the way Typeahead expects:

We’ve included a placeholder with an ID of js for the JavaScript that will call back to the server. We’ll get to that in a moment.

The way Typeahead works is that we attach it to our input field and tell it to call a JavaScript function when it needs to make suggestions. That JavaScript function is going to be called askServer, and it is given two arguments: the input the user has typed so far (query), and a JavaScript function to call when the suggestions are available (callback). The Lift snippet needs to use the query value and then call the JavaScript callback function with whatever suggestions are made.

A snippet to implement this would be as follows:

package code.snippet

import net.liftweb.util.Helpers._
import net.liftweb.common.{Full, Empty, Loggable}

import net.liftweb.http._
import net.liftweb.http.js.JsCmds._
import net.liftweb.http.js.JsCmds.Run
import net.liftweb.http.js.JE.JsVar
import net.liftweb.json.JsonAST._
import net.liftweb.json.DefaultFormats

class ProgrammingLanguagesTypeAhead extends Loggable {

  val languages = List(
    "C", "C++", "Clojure", "CoffeeScript",
    "Java", "JavaScript",
    "POP-11", "Prolog", "Python", "Processing",
    "Scala", "Scheme", "Smalltalk", "SuperCollider"
  )

  def render = {

    implicit val formats = DefaultFormats

    def suggest(value: JValue) : JValue = {
      logger.info("Making suggestion for: "+value)

      val matches = for {
        q <- value.extractOpt[String].map(_.toLowerCase).toList
        lang <- languages.filter(_.toLowerCase startsWith q)
      } yield JString(lang)

      JArray(matches)
    }

    val callbackContext = new JsonContext(Full("callback"),Empty)

    val runSuggestion =
      SHtml.jsonCall(JsVar("query"), callbackContext, suggest _ )

    S.appendJs(Run(
      """
        |$('#autocomplete').typeahead({
        |  source: askServer
        |});
      """.stripMargin))

    "#js *" #> Function("askServer", "query" :: "callback" :: Nil,
                  Run(runSuggestion.toJsCmd)) &
    "#autocomplete" #> SHtml.text("", s => logger.info("Submitted: "+s))

  }
}

Working from the bottom of the snippet, we bind a regular Lift SHtml.text input to the autocomplete field. This will receive the selected value when the form is submitted. We also bind the JavaScript placeholder to a JavaScript function definition called askServer. This is the function that Typeahead will call when it wants suggestions.

The JavaScript function we’re defining takes two arguments: the query and callback. The body of askServer causes it to run something called runSuggestion, which is a jsonCall to the server, with the value of the query.

The suggestions are made by the suggest function, which expects to be able to find a String in the passed in JSON value. It uses this value to find matches in the list of languages. These are returned as a JArray of JString, which is treated as JSON data back on the client.

What does the client do with the data? It calls the callback function with the suggestions, which results in the display updating. We specify its callback via JsonContext, which is a class that lets us specify a custom function to call on success of the request to the server.

It may help to understand this by looking at the JavaScript generated in the HTML page for askServer:

<script id="js">
function askServer(query, callback) {
  liftAjax.lift_ajaxHandler('F268944843717UZB5J0=' +
    encodeURIComponent(JSON.stringify(query)), callback, null, "json")
}
</script>

As the user types into the text field, Typeahead calls askServer with the input supplied. Lift’s Ajax support arranges for that value, query, to be serialised to our suggest function on the server, and for the results to be passed to callback. At that point, Typeahead takes over again and displays the suggestions.

Typing Scala to the text field and pressing submit will produce a sequence like this on the server:

INFO  c.s.ProgrammingLanguagesTypeAhead - Making suggestion for: JString(Sc)
INFO  c.s.ProgrammingLanguagesTypeAhead - Making suggestion for: JString(Sca)
INFO  c.s.ProgrammingLanguagesTypeAhead - Making suggestion for: JString(Sca)
INFO  c.s.ProgrammingLanguagesTypeAhead - Making suggestion for: JString(Scal)
INFO  c.s.ProgrammingLanguagesTypeAhead - Making suggestion for: JString(Scala)
INFO  c.s.ProgrammingLanguagesTypeAhead - Submitted: Scala

See Also

“Trigger Server-Side Code from a Button” describes jsonCall.

The behaviour of the widget module with respect to new values is described in a ticket on the module’s GitHub page. Enhancing modules is one route to get involved with Lift, and Chapter 11 describes other ways to contribute.

The jQuery UI Autocomplete documentation describes how to configure the widget. The version included with the Lift widgets module is version 1.0.2.

The Typeahead widget is part of Twitter Bootstrap.

Offering Choices with Radio Buttons

Problem

You want users to select an option using radio buttons.

Solution

Use SHtml.radioElem to present the options as radio buttons.

To illustrate this, let’s create a form to allow a user to indicate his gender:

object BirthGender extends Enumeration {
  type BirthGender = Value
  val Male = Value("Male")
  val Female = Value("Female")
  val NotSpecified = Value("Rather Not Say")
}

We’re using an enumeration, but it could be any class. The toString of the class will be used as the label shown to the user.

To present these options and handle the selection of an option, we use this enumeration in a snippet:

package code.snippet

import net.liftweb.common._
import net.liftweb.util.Helpers._
import net.liftweb.http.SHtml
import net.liftweb.http.SHtml.ChoiceHolder

object Radio extends Loggable {

  import BirthGender._

  val options : Seq[BirthGender] = BirthGender.values.toSeq

  val default : Box[BirthGender] = Empty

  val radio : ChoiceHolder[BirthGender] =
    SHtml.radioElem(options, default) { selected =>
      logger.info("Choice: "+selected)
    }

  def render = ".options" #> radio.toForm
}

Rather than generate the radio buttons in one expression on the render method, we’ve pulled out the intermediate values to show their types. The radio.toForm call is generating the radio buttons, and we’re binding them to the CSS selector .option in the following template:

<div data-lift="Radio?form=post">

  <span class="options">
    <input type="radio">Option 1</input>
    <input type="radio">Option 2</input>
  </span>

  <input type="submit" value="Submit">

</div>

The class="options" span will be replaced with the radio buttons from the code, and when the form is submitted, the function supplied to SHtml.radioElem will be called, resulting in the selected value being logged. For example, if no radio button is selected:

INFO  code.snippet.Radio - Choice: Empty

or if the third button was selected:

INFO  code.snippet.Radio - Choice: Full(Rather Not Say)

Discussion

Many of the Lift SHtml methods return a NodeSeq, which can be directly bound into our HTML. However, radioElem is different in that it gives us a ChoiceHolder[T], and to generate a NodeSeq from that, we’re calling toForm. This has implications for how you customise radio buttons, as we’ll see later.

The radioElem method expects three parameters:

SHtml.radioElem(options, default) { selected =>
  logger.info("Choice: "+selected)
}

The first is the set of options to show, as a Seq[T]. The second is the value to be preselected, as a Box[T]. In the example, we have no preselected value, which is represented as Empty. Finally, there’s the function to run when the form is submitted, of type Box[T] => Any.

Note that even if the user selects no value, your function will be called, and it will be passed the value Empty.

To understand a little more of what’s happening, take a look at the default HTML produced by radioElem:

<span>
 <input value="F317293945993CDMQZ" type="radio" name="F317293946030HYAFP">
 <input name="F317293946030HYAFP" type="hidden" value="F317293946022HCGEG">
 Male<br>
</span>
<span>
 <input value="F31729394600RIE253" type="radio" name="F317293946030HYAFP">
 Female<br>
</span>
<span>
 <input value="F317293946011OMEMM" type="radio" name="F317293946030HYAFP">
 Rather Not Say<br>
</span>

Notice that:

  • All the input fields have the same randomly generated name.
  • The input fields have randomly generated values.
  • There’s a hidden field added to the first item.

This might be a surprise if you were just expecting something like this:

<input type="radio" name="gender" value="Male">Male<br>
<input type="radio" name="gender" value="Female">Female<br>
<input type="radio" name="gender" value="NotSpecified">Rather Not Say<br>

By using random values, Lift has helped us by protecting against a range of form-based attacks, such as submitting values we’re not expected, or setting values on fields we don’t want set.

Each of the random radio button values is associated, on the server, with a BirthGender value from our options sequence. When the form is submitted, Lift picks out the selected value (if any), looks up the corresponding BirthGender value, and calls our function.

The hidden field ensures that the function will be called even if no radio button is selected. This is because the browser will at least submit the hidden field, and this is enough to trigger the server-side function.

Customising the HTML

The default HTML wraps each radio button in a <span> and separates them with a <br>. Let’s change that to make it work well with the Twitter Bootstrap framework, and put each choice in a <label> and give it a class.

To customise the HTML, you need to understand that the ChoiceHolder is a container for a sequence of items. Each item is a ChoiceItem:

final case class ChoiceItem[T](key: T, xhtml: NodeSeq)

The key in our example is a BirthGender instance, and the xhtml is the radio button input field (plus the hidden field for the first option). With this knowledge, we can write a helper to generate a NodeSeq in the style we want:

import scala.xml.NodeSeq
import net.liftweb.http.SHtml.ChoiceItem

object LabelStyle {
  def htmlize[T](item: ChoiceItem[T]) : NodeSeq =
   <label class="radio">{item.xhtml} {item.key.toString}</label>

  def toForm[T](holder: ChoiceHolder[T]) : NodeSeq =
   holder.items.flatMap(htmlize)
}

The htmlize method produces a <label> element with the class we want, and it contains the radio input (item.xhtml) and the text of the label (item.key.toString). The toForm is applying the htmlize function to all the choices.

We can apply this in our snippet:

def render = ".options" #> LabelStyle.toForm(radio)

and the result would be the following:

<label class="radio">
 <input value="F234668654428LWW305" type="radio" name="F234668654432WS5LWK">
 <input name="F234668654432WS5LWK" type="hidden" value="F234668654431KYJB3S">
 Male
</label>
<label class="radio">
 <input value="F234668654429MB5RF3" type="radio" name="F234668654432WS5LWK">
 Female
</label>
<label class="radio">
 <input value="F234668654430YULGC1" type="radio" name="F234668654432WS5LWK">
 Rather Not Say
</label>

The toForm method could be wrapping the choices in some other HTML, such as a <ul>. But in this case, it’s not: it’s just applying htmlize to each ChoiceItem. As a consequence of this, we could make LabelStyle the default across our Lift application:

ChoiceHolder.htmlize = c => LabelStyle.htmlize(c)

This works because toForm on ChoiceHolder defers to ChoiceHolder.htmlize, and ChoiceHolder.htmlize is a variable you can assign to.

String values

If you want to work directly with String values for options, you can use SHtml.radio. Although it too produces a ChoiceHolder, it differs from radioElem in that it uses the same String as both the label and the value. The function associated with each option is called only if a value is selected by the user.

An SHtml.radio version of our example would look like this:

SHtml.radio(
  "Male" :: "Female" :: "Rather Not Say" :: Nil,
  Empty,
  selected => logger.info("Choice: "+selected)
)

This is a similar structure to radioElem: there’s a list of options, a default, a function to call, and it produces a ChoiceHolder[String]. When a form is submitted, our function is passed the String value of the selected option. If no radio buttons are selected, the function is not called.

Conditionally Disable a Checkbox

Problem

You want to add the disabled attribute to a SHtml.checkbox based on a conditional check.

Solution

Create a CSS selector transform to add the disabled attribute, and apply it to your checkbox transform.

For example, suppose you have a simple checkbox:

class Likes {
  var likeTurtles = false
  def render =
    "#like" #> SHtml.checkbox(likeTurtles, likeTurtles = _ )
}

and a corresponding template:

<!DOCTYPE html>
<head>
  <meta content="text/html; charset=UTF-8" http-equiv="content-type" />
  <title>Disable Checkboxes</title>
</head>
<body data-lift-content-id="main">
<div id="main" data-lift="surround?with=default;at=content">

  <div>Select the things you like:</div>

  <form data-lift="Likes">
    <label for="like">Do you like turtles?</label>
    <input id="like" type="checkbox">
  </form>

</div>
</body>
</html>

Further, suppose you want to disable it roughly 50% of the time. We could do that by adjusting the NodeSeq generated from SHtml.checkbox:

package code.snippet

import net.liftweb.util.Helpers._
import net.liftweb.util.PassThru
import net.liftweb.http.SHtml

class Likes {
  var likesTurtles = false

  def disable =
    if (math.random > 0.5d) "* [disabled]" #> "disabled"
    else PassThru

  def render =
    "#like" #> disable( SHtml.checkbox(likesTurtles, likesTurtles = _) )
}

When the checkbox is rendered, it will be disabled roughly half the time.

Discussion

The disable method returns a NodeSeq => NodeSeq function, meaning when we apply it, we need to give it a NodeSeq, which is exactly what SHtml.checkbox provides.

The [disabled] part of the CSS selector is selecting the disabled attribute and replacing it with the value on the right of the #>, which is "disabled" in this example.

What this combination means is that half the time the disabled attribute will be set on the checkbox, and half the time the checkbox NodeSeq will be left untouched because PassThru does not change the NodeSeq.

See Also

“Returning Snippet Markup Unchanged” describes the PassThru function.

Use a Select Box with Multiple Options

Problem

You want to show a number of options in a select box, and allow the user to select multiple values.

Solution

Use SHtml.multiSelect(options, default, selection). Here’s an example where a user can select up to three options:

package code.snippet

import net.liftweb.util.Helpers._
import net.liftweb.http.SHtml
import net.liftweb.common.Loggable

class MultiSelect extends Loggable {

  case class Item(id: String, name: String)

  val inventory =
    Item("a", "Coffee") ::
    Item("b", "Milk") ::
    Item("c", "Sugar") :: Nil

  val options : List[(String,String)] =
    inventory.map(i => (i.id -> i.name))

  val default = inventory.head.id :: Nil

  def render = {

    def selection(ids: List[String]) : Unit = {
      logger.info("Selected: "+ids)
    }

    "#opts *" #>
      SHtml.multiSelect(options, default, selection)
  }
}

In this example, the user is being presented with a list of three items, with the first one selected, as shown in Figure 3-3. When the form is submitted, the selection function is called, with a list of the selected option values.

Selecting from multiple options
Figure 3-3. Selecting from multiple options

The template to go with the snippet could be:

<div data-lift="MultiSelect?form=post">
  <p>What can I get you?</p>
  <div id="opts">options go here</div>
   <input type="submit" value="Place Order">
</div>

This will render as something like:

<form action="/" method="post"><div>
  <p>What can I get you?</p>
  <div id="opts">
   <select name="F25749422319ALP1BW" multiple="true">
     <option value="a" selected="selected">Coffee</option>
     <option value="b">Milk</option>
     <option value="c">Sugar</option>
   </select>
  </div>
  <input value="Place Order" type="submit">
</form>

Discussion

Recall that an HTML select consists of a set of options, each of which has a value and a name. To reflect this, the previous example takes our inventory of objects and turns it into a list of string pairs, called options.

The function given to SHtml.multiSelect will receive the values (IDs), not the names, of the options. That is, if you ran the code, and selected "Coffee" and "Milk," the function would see List("a", "b").

Selecting no options

Be aware that if no options are selected, your handling function is not called. This is described in issue 1139.

One way to work around this is to add a hidden function to reset the list. For example, we could modify the previous code to be a stateful snippet and remember the values we selected:

package code.snippet

import net.liftweb.util.Helpers._
import net.liftweb.http.{StatefulSnippet, SHtml}
import net.liftweb.common.Loggable

class MultiSelectStateful extends StatefulSnippet with Loggable {

  def dispatch = {
    case _ => render
  }

  case class Item(id: String, name: String)

  val inventory =
    Item("a", "Coffee") ::
    Item("b", "Milk") ::
    Item("c", "Sugar") :: Nil
  val options : List[(String,String)] =
    inventory.map(i => (i.id -> i.name))

  var current = inventory.head.id :: Nil

  def render = {

    def logSelected() =
      logger.info("Values selected: "+current)

    "#opts *" #> (
      SHtml.hidden( () => current = Nil) ++
      SHtml.multiSelect(options, current, current = _)
    ) &
    "type=submit" #> SHtml.onSubmitUnit(logSelected)

  }

}

The template is unchanged, and the snippet has been modified to introduce a current value and a hidden function to reset the value. We’ve bound the submit button to simply log the selected values when the form is submitted.

Each time the form is submitted the current list of IDs is set to whatever you have selected in the browser. But note that we have started with a hidden function that resets current to the empty list. This means that if the receiving function in multiSelect is never called, because nothing is selected, the value stored in current would reflect this and be Nil.

That may be useful, depending on what behaviour you need in your application.

Type-safe options

If you don’t want to work in terms of String values for an option, you can use multiSelectObj. In this variation, the list of options still provides a text name, but the value is in terms of a class. Likewise, the list of default values will be a list of class instances.

The only changes to the code are to produce a List[(Item,String)] for the options, and use an Item as a default:

val options : List[(Item,String)] =
  inventory.map(i => (i -> i.name))

val default = inventory.head :: Nil

The call to generate the multiselect from this data is similar, but note our selection function now receives a list of Item:

def render = {

  def selection(items: List[Item]) : Unit = {
    logger.info("Selected: "+items)
  }

  "#opts *" #>
    SHtml.multiSelectObj(options, default, selection)
  }

Enumerations

You can use multiSelectObj with enumerations:

package code.snippet

import net.liftweb.util.Helpers._
import net.liftweb.http.SHtml
import net.liftweb.common.Loggable

class MultiSelectEnum extends Loggable {

  object Item extends Enumeration {
    type Item = Value
    val Coffee, Milk, Sugar = Value
  }

  import Item._

  val options : List[(Item,String)] =
    Item.values.toList.map(i => (i -> i.toString))

  val default = Item.Coffee :: Nil

  def render = {

    def selection(items: List[Item]) : Unit = {
      logger.info("Selected: "+items)
    }

    "#opts *" #>
      SHtml.multiSelectObj(options, default, selection)
  }

}

The enumeration version works in the same way as the type-safe version.

See Also

The "Submit styling" discussion in “Ajax Form Processing” discusses the use of hidden fields as function calls.

“Call Server When Select Option Changes” describes how to trigger a server-side action when a selection changes in the browser.

Chapter 6 of Exploring Lift, "Forms in Lift," discusses multiselect and other types of form elements.

File Upload

Problem

You want a snippet to allow users to upload a file to your Lift application.

Solution

Use a FileParamHolder in your snippet, and extract file information from it when the form is submitted.

Start with a form that is marked as multipart=true:

<html>
<head>
  <title>File Upload</title>
  <script id="jquery" src="/classpath/jquery.js" type="text/javascript">
  </script>
  <script id="json" src="/classpath/json.js" type="text/javascript"></script>
</head>
<body>
<form data-lift="FileUploadSnippet?form=post;multipart=true">
   <label for="file">
     Select a file: <input id="file"></input>
   </label>
   <input type="submit" value="Submit"></input>
</form>
</body>
</html>

We bind the file input to SHtml.fileUpload and the submit button to a function to process the uploaded file:

package code.snippet

import net.liftweb.util.Helpers._
import net.liftweb.http.SHtml._
import net.liftweb.http.FileParamHolder
import net.liftweb.common.{Loggable, Full, Empty, Box}


class FileUploadSnippet extends Loggable {

  def render = {

    var upload : Box[FileParamHolder] = Empty

    def processForm() = upload match {
      case Full(FileParamHolder(_, mimeType, fileName, file)) =>
        logger.info("%s of type %s is %d bytes long" format (
         fileName, mimeType, file.length) )

      case _ => logger.warn("No file?")
    }

    "#file" #> fileUpload(f => upload = Full(f)) &
      "type=submit" #> onSubmitUnit(processForm)
  }
}

The fileUpload binding ensures that the file is assigned to the upload variable. This allows us to access the Array[Byte] of the file in the processForm method when the form is submitted.

Discussion

HTTP includes an encoding type of multipart/form-data that was introduced to support binary data uploads. The ?form=post;multipart=true parameters in the template mark the form with this encoding, and the HTML generated will look like this:

<form enctype="multipart/form-data" method="post" action="/fileupload">

When the browser submits the form, Lift detects the multipart/form-data encoding and extracts any files from the request. These are available as uploadedFiles on a Req object, for example:

val files : List[FileParamHolder] = S.request.map(_.uploadedFiles) openOr Nil

However, as we’re dealing with a form with a single upload field, it’s easier to use SHtml.fileUpload to bind the input to our upload variable. Lift arranges for the function f => upload = Full(f) to be called when a file is selected and uploaded via this field. If the file is zero length, the function is not called.

The default behaviour for Lift is to read the file into memory and present it as a FileParamHolder. In this recipe, we’re pattern matching on the fields of the FileParamHolder and simply printing out what we know about the file. We’re ignoring the first parameter, which will be Lift’s generated name for the field, but capturing the mime type, original filename, and the raw data that was in the file.

You probably don’t want to use this method for very large files. In fact, LiftRules provides a number of size restrictions that you can control:

LiftRules.maxMimeFileSize
The maximum size of any single file uploaded (7 MB by default)
LiftRules.maxMimeSize
The maximum size of the multipart upload in total (8 MB by default)

Why two settings? Because when the form is submitted, there may be a number of fields on the form. For example, in the recipe, the value of the submit button is sent as one of the parts, and the file is sent as another. Hence, you might want to limit file size, but allow for some field values, or multiple files, to be submitted.

If you hit the size limit, an exception will be thrown from the underlying file upload library. You can catch the exception, as described in “Catch Any Exception”:

LiftRules.exceptionHandler.prepend {
  case (_, _, x : FileUploadIOException) =>
    ResponseWithReason(BadResponse(), "Unable to process file. Too large?")
}

Be aware that the container (Jetty, Tomcat) or any web server (Apache, Nginx) may also have limits on file upload sizes.

Uploading a file into memory may be fine for some situations, but you may want to upload larger items to disk and then process them in Lift as a stream. Lift supports this via the following setting:

LiftRules.handleMimeFile = OnDiskFileParamHolder.apply

The handleMimeFile variable expects to be given a function that takes a field name, mime type, filename, and InputStream and returns a FileParamHolder. The default implementation of this is the InMemFileParamHolder, but changing to OnDiskFileParamHolder means Lift will write the file to disk first. You can of course implement your own handler in addition to using OnDiskFileParamHolder or InMemFileParamHolder.

With OnDiskFileParamHolder, the file will be written to a temporary location (System.getProperty("java.io.tmpdir")), but it’s up to you to remove it when you’re done with the file. For example, our snippet could change to:

def processForm() = upload match {

  case Full(content : OnDiskFileParamHolder) =>
    logger.info("File: "+content.localFile.getAbsolutePath)
    val in: InputStream = content.fileStream
    // ...do something with the stream here...
    val wasDeleted_? = content.localFile.delete()

  case _ => logger.warn("No file?")
}

Be aware that OnDiskFileParamHolder implements FileParamHolder, so would match the original FileParamHolder pattern used in the recipe. However, if you access the file field of OnDiskFileParamHolder, you’ll bring the file into memory, which would defeat the point of storing it on disk to process it as a stream.

If you want to monitor the progress of the upload on the server side, you can. There’s a hook in LiftRules that is called as the upload is running:

def progressPrinter(bytesRead: Long, contentLength: Long, fieldIndex: Int) {
  println("Read %d of %d for %d" format (bytesRead, contentLength, fieldIndex))
}

LiftRules.progressListener = progressPrinter

This is the progress of the whole multipart upload, not just the file being uploaded. In particular, the contentLength may not be known (in which case, it will be -1), but if it is known, it is the size of the complete multipart upload. In the example in this recipe, that would include the size of the file, but also the submit button value. This also explains the fieldIndex, which is a counter as to which part is being processed. It will take on the values of 0 and 1 for the two parts in this example.

See Also

The HTTP file upload mechanics are described in RFC 1867, Form-based File Upload in HTML.

“Accept Binary Data in a REST Service” discusses file upload in the context of a REST service.

See “Ajax File Upload” for an example of an Ajax file upload through integration with a JavaScript library, providing progress indicators and drag-and-drop support.

DRY Forms with Layout

Problem

You want to use DRY declarative forms, like LiftScreen, but you want to be able to completely control how the form is rendered, instead of a linear layout of fields. "LiftScreen"

Solution

Use CssBoundLiftScreen, with field bindings for naming placements, and optionally override the layout of specific individual field elements.

Here’s an example snippet:

package code.snippet

import scala.xml.NodeSeq
import net.liftweb.http._
import net.liftweb.http.FieldBinding.Self

object AccountInfoEditor extends CssBoundLiftScreen {
  val formName = "accountedit"

  override def allTemplate = savedDefaultXml
  protected def defaultAllTemplate = super.allTemplate

  // Pull the definition of the "normal" field from a template, if it exists
  override def defaultFieldNodeSeq: NodeSeq =
    Templates("accounts" :: "account_edit_field" :: Nil).openOr(
    <div>
      <label class="label field"></label>
      <span class="value fieldValue"></span>
      <span class="help"></span>
      <div class="errors">
        <div class="error"></div>
      </div>
    </div>)

  override def finish() {
    println("Account Edited for: "+firstName)
    S.notice("Done.")
  }

  // An example source of an account:
  case class Account(firstName: String, lastName: String, address: String)
  def accountToEdit = new Account("Ada", "Lovelace", "Ockham Park, Surrey")

  // Fields:
  val firstName = field("First Name", accountToEdit.firstName,
    trim, valMinLen(1, "First name is required"),
    FieldBinding("firstName"))

  val lastName = field("Last Name", accountToEdit.lastName,
    trim, valMinLen(1, "Last name is required"),
    FieldBinding("lastName"))

  val address = textarea("Address", accountToEdit.address,
    trim, valMinLen(1, "Address is required"), valMaxLen(255, "Address too long"),
    FieldBinding("address", Self))

}

This snippet will present a (hypothetical) user account record, allowing a user to edit three fields. The fields have validation, and will be bound into a template.

A corresponding template might be called webapp/accountinfo.html:

<!DOCTYPE html>
<head>
  <meta content="text/html; charset=UTF-8" http-equiv="content-type" />
  <title>Custom CSS Binding Screen</title>
</head>
<body data-lift-content-id="main">
<div id="main" data-lift="surround?with=default;at=content">

  <div data-lift="AccountInfoEditor">

    <div>
      <!--
      Drop regular Lift Screen elements where you want them.
      Here we're putting the Next (or Finish) button at the top.
      -->
      <button class="next"></button>
    </div>

    <div class="fields">

      <h2>Account Details</h2>

      <!--
        The template applied to each field is taken from
        accounts/account_edit_field.html
      -->
      <div id="accountedit_firstName_field" class="large"></div>
      <div id="accountedit_lastName_field" class="large"></div>

      <!--
      The code binds this field with Self, meaning the
      field template is inline in this template and will
      be different:
      -->
      <div id="accountedit_address_field">
        <div class="large">The address where you like gifts sent:</div>
        <div class="errors">
          <div class="error"></div>
        </div>
        <span class="value fieldValue" style="width:10em; height:5em"></span>
      </div>

    </div>

  </div>

</div>

</body>
</html>

Running the snippet will cause CssBoundLiftScreen to apply the defaultFieldNodeSeq layout to each field, with the exception of the "address" field that we have customised inside the template.

Pressing the "Finish" button triggers validations, presents errors (if any), or completes the edit via the finish method.

Discussion

CssBoundLiftScreen uses the same model (by default) as LiftScreen, but with CSS classes to identify elements in the default template (e.g., wizard-all.html). This powerful mechanism eliminates repetition in both code and HTML; however, LiftScreen sacrifices flexibility in that there is no way to make highly customized forms.

This restriction is removed in CssBoundLiftScreen by allowing you to control every element of the form rendering. You can control everything form field placement to the layout of a single field. You do so by embedding the custom template at the snippet usage site:

<div data-lift="AccountInfoEditor">
   <!-- template for Screen goes here -->
</div>

and supply some additional binding hints. The form is still very minimal, while being completely under the control of the designer.

The example in the solution section demonstrates the most important aspects. In particular, you should note that you must still supply a source for the template, and that you can designate alternatives for sub-portions of the template. In the example, we allow the HTML designer direct access to the field template for this screen by making it possible to put the layout in a custom template file accounts/account_edit_field.html.

The rest is very easy. You must supply a formName declaration (in Scala) for CssBoundLiftScreen, and a FieldBinding as an argument to each field. This, combined with an internal function (that you can override), will generate unique (but known) names that can be used in the template. The default pattern is: "formName_fieldName_field". So, if you name your form "myform", and bind a field to "address", then your HTML template should include a div with the ID "myform_address_field". That is all you need for normal bindings that use your field template.

To tweak the field layout for a particular field, you may specify Self in the Scala field binding. This indicates the template for that particular field should be sourced from the field’s div.

If you want to get even fancier there are additional field bindings, such as Dynamic(() => NodeSeq), which uses the supplied function to generate a template for the field each time it is rendered.

See Also

Further examples can be found in:

LiftScreen is described on the Lift Wiki and in Simply Lift.