Up: Chapter 4

4.2 OnSubmit

Some of Lift’s design reflects VisualBasic... associating user behavior with a user interface element. It’s a simple, yet very powerful concept. Each form element is associated with a function on the server [C]  [C] Before you get all upset about statefulness and such, please read about Lift and State (see 20 on page 1↓).. Further, because functions in Scala close over scope (capture the variables currently in scope), it’s both easy and secure to keep state around without exposing that state to the web client.
So, let’s see how it works. First, the HTML:
onsubmit.html
<div id="main" class="lift:surround?with=default&at=content">
  <div>
    Using Lift's SHtml.onSubmit, we've got better control
    over the form processing.
  </div>
  
  <div>
    <form class="lift:OnSubmit?form=post">
      Name: <input name="name"><br>
      Age: <input name="age" value="0"><br>
      <input type="submit" value="Submit">
    </form>
  </div>
</div>
​
​
The only different thing in this HTML is <form class="lift:OnSubmit?form=post">. The snippet, behavior, of the form is to invoke OnSubmit.render. The form=post attribute makes the form into a post-back. It sets the method and action attributes on the <form> tag: <form method="post" action="/onsubmit">.
Let’s look at the snippet:
OnSubmit.scala
package code
package snippet
​
import net.liftweb._
import http._
import util.Helpers._
import scala.xml.NodeSeq
​
/**
 * A snippet that binds behavior, functions,
 * to HTML elements
 */
object OnSubmit {
  def render = {
    // define some variables to put our values into
    var name = ""
    var age = 0
​
    // process the form
    def process() {
      // if the age is < 13, display an error
      if (age < 13) S.error("Too young!")
      else {
        // otherwise give the user feedback and
        // redirect to the home page
        S.notice("Name: "+name)
        S.notice("Age: "+age)
        S.redirectTo("/")
      }
    }
​
    // associate each of the form elements
    // with a function... behavior to perform when the
    // for element is submitted
    "name=name" #> SHtml.onSubmit(name = _) & // set the name
    // set the age variable if we can convert to an Int
    "name=age" #> SHtml.onSubmit(s => asInt(s).foreach(age = _)) &
    // when the form is submitted, process the variable
    "type=submit" #> SHtml.onSubmitUnit(process)
  }
}
Like DumbForm.scala, the snippet is implemented as a singleton. The render method declares two variables: name and age. Let’s skip the process() method and look at the was we’re associating behavior with the form elements.
"name=name" #> SHtml.onSubmit(name = _) takes the incoming HTML elements with the name attribute equal to “name” and, via the SHtml.onSubmit method, associating a function with the form element. The function takes a String parameter and sets the value of the name variable to the String. The resulting HTML is <input name="F10714412223674KM">. The new name attribute is a GUID (globally unique identifier) that associates the function (set the name to the input) with the form element. When the form is submitted, via normal HTTP post or via Ajax, the function will be executed with the value of the form element. On form submit, perform this function.
Let’s see about the age form field: "name=age" #> SHtml.onSubmit(s => asInt(s).foreach(age = _)). The function that’s executed uses Helpers.asInt to try to parse the String to an Int. If the parsing is successful, the age variable is set to the parsed Int.
Finally, we associate a function with the submit button: "type=submit" #> SHtml.onSubmitUnit(process). SHtml.onSubmitUnit method takes a function that takes no parameters (rather than a function that takes a single String as a parameter) and applies that function when the form is submitted.
The process() method closes over the scope of the name and age variables and when that method is lifted to a function, it still closes over the variables... that means that when the function is applied, it refers to the same instances of the name and age variables as the other functions in this method. However, if we had 85 copies of the form open in 85 browsers, each would be closing over different instances of the name and age variables. In this way, Lift allows your application to contain complex state without exposing that complex state to the browser.
The problem with this form example is that if you type an incorrect age, the whole form is reset. Let’s see how we can do better error handling.
Up: Chapter 4

(C) 2012 David Pollak