}
33
}
It’s pretty simple. If the request is a post and the query parameters exist, then display notices with
the name and age and redirect back the application’s home page.
There are plenty of reasons not to do things this way.
First, if there’s a naming mis-match between the HTML and the Scala code, you might miss a form
field... and keeping the naming aligned is not always easy.
Second, forms with predictable names lead to replay attacks. If an attacker can capture the form
submits you’ve made and substitute new values for import fields, they can more easily hack your
application.
Third, keeping state around becomes very difficult with manual forms. You have to resort to
hidden fields that contain primary keys or other information that can be tampered with.
Lift provides you with much more powereful and secure mechanisms for dealing with HTML
forms.
4.2. ONSUBMIT
29
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
server1. 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:
Listing 4.3: onsubmit.html
1
<div id="main" class="lift:surround?with=default&at=content">
2
<div>
3
Using Lift's SHtml.onSubmit, we've got better control
4
over the form processing.
5
</div>
6
7
<div>
8
<form class="lift:OnSubmit?form=post">
9
Name: <input name="name"><br>
10
Age: <input name="age" value="0"><br>
11
<input type="submit" value="Submit">
12
</form>
13
</div>
14
</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:
Listing 4.4: OnSubmit.scala
1
package code
2
package snippet
3
4
import net.liftweb._
5
import http._
6
import util.Helpers._
7
import scala.xml.NodeSeq
8
9
/**
10
* A snippet that binds behavior, functions,
11
* to HTML elements
12
*/
13
object OnSubmit {
14
def render = {
15
// define some variables to put our values into
16
var name = ""
1Before you get all upset about statefulness and such, please read about Lift and State (see 20 on page 131).
30
CHAPTER 4. FORMS
17
var age = 0
18
19
// process the form
20
def process() {
21
// if the age is < 13, display an error
22
if (age < 13) S.error("Too young!")
23
else {
24
// otherwise give the user feedback and
25
// redirect to the home page
26
S.notice("Name: "+name)
27
S.notice("Age: "+age)
28
S.redirectTo("/")
29
}
30
}
31
32
// associate each of the form elements
33
// with a function... behavior to perform when the
34
// for element is submitted
35
"name=name" #> SHtml.onSubmit(name = _) & // set the name
36
// set the age variable if we can convert to an Int
37
"name=age" #> SHtml.onSubmit(s => asInt(s).foreach(age = _)) &
38
// when the form is submitted, process the variable
39
"type=submit" #> SHtml.onSubmitUnit(process)
40
}
41
}
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
4.3. STATEFUL SNIPPETS
31
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.
4.3
Stateful Snippets
In order for us to give the user a better experience, we need to capture the state of the name and age
variables across the multiple form submissions. The mechanism that Lift has for doing this is the
Stateful Snippet2. A snippet that subclasses StatefulSnippet has an extra hidden parameter
automatically inserted into the form which ensures that during processing of that form, the same
instance of the StatefulSnippet will be used3.
Let’s look at the HTML template:
Listing 4.5: stateful.html
1
<div id="main" class="lift:surround?with=default&at=content">
2
<div>
3
Using stateful snippets for a better
4
user experience
5
</div>
6
7
<div>
8
<div class="lift:Stateful?form=post">
9
Name: <input name="name"><br>
10
Age: <input name="age" value="0"><br>
11
<input type="submit" value="Submit">
12
</div>
13
</div>
14
</div>
The template looks pretty much like the template in onsubmit.html. Let’s look at the snippet
itself:
Listing 4.6: Stateful.scala
1
package code
2
package snippet
3
4
import net.liftweb._
5
import http._
6
import common._
7
import util.Helpers._
8
import scala.xml.NodeSeq
2There are no stateless snippets. A Stateful Snippet doesn’t consume any more server-side resources than does a
form composed via SHtml.onSubmit(). Oh, and state is not a barier to scalaing. See Chapter 20.
3Earlier I talked about the security implications of hidden form parameters. The hidden parameter mechanism is not
vulnerable to the same issues because the hidden parameter itself is just a GUID that causes a function to be invoked
on the server. No state is exposed to the client, so there’s nothing for a hacker to capture or mutate that would allow
for the exploitation of a vulnerability.
32
CHAPTER 4. FORMS
9
10
/**
11
* A stateful snippet. The state associated with this
12
* snippet is in instance variables
13
*/
14
class Stateful extends StatefulSnippet {
15
// state unique to this instance of the stateful snippet
16
private var name = ""
17
private var age = "0"
18
19
// capture from whence the user came so we
20
// can send them back
21
private val whence = S.referer openOr "/"
22
23
// StatefulSnippet requires an explicit dispatch
24
// to the method.
25
def dispatch = {case "render" => render}
26
27
// associate behavior with each HTML element
28
def render =
29
"name=name" #> SHtml.text(name, name = _, "id" -> "the_name") &
30
"name=age" #> SHtml.text(age, age = _) &
31
"type=submit" #> SHtml.onSubmitUnit(process)
32
33
// process the form
34
private def process() =
35
asInt(age) match {
36
case Full(a) if a < 13 => S.error("Too young!")
37
case Full(a) => {
38
S.notice("Name: "+name)
39
S.notice("Age: "+a)
40
S.redirectTo(whence)
41
}
42
43
case _ => S.error("Age doesn't parse as a number")
44
}
45
}
There’s a fair amount different here. First, the class definition: class Stateful extends
StatefulSnippet. Because the snippet instance itself contains state, it can’t be an object sin-
gleton. It must be declared as a class so there are multiple instances.
We capture state (name, age and from whence the user came), in instance variables.
StatefulSnippets require a dispatch method which does method dispatching explicitly
rather than “by-convention.”
The render method uses familiar CSS Selector Transforms to associate markup with behavior.
However, rather than using SHtml.onSubmit, we’re using SHtml.text to explicitly generate
an HTML <input> element with both the name and value attributes set. In the case of the first
input, we’re also explicitly setting the id attribute. We’re not using it in the application, but it’s a
way to demonstrate how to add extra attributes.
Finally, the process() method attempts to covert the age String into an Int. If it’s an Int, but
4.4. REQUESTVARS
33
less than 13, we present an error. If the String cannot be parsed to an Int, we present an error,
otherwise we do notify the user and go back to the page the user came from.
Note in this example, we preserve the form values, so if you type something wrong in the name
or age fields, what you typed is presented to you again.
The big difference between the resulting HTML for StatefulSnippets and other snippets is
the insertion of <input name="F1071441222401LO3" type="hidden" value="true">
in the form. This hidden field associates the snippet named “Stateful” with the instance of State-
ful that was used to initially generate the form.
Let’s look at an alternative mechanism for creating a nice user experience.
4.4
RequestVars
In this example, we’re going to preserve state during the request by placing state in RequestVars
(see 7.8 on page 84).
Lift has type-safe containers for state called XXXVars. There are SessionVars that have session
scope, WizardVars that are scoped to a Wizard and RequestVars that are scoped to the cur-
rent request4. Vars are defined as singletons: private object name extends Request-
Var(""). They are typed (in this case, the type is String) and they have a default value.
So, let’s look at the HTML which looks shockingly like the HTML in the last two examples:
Listing 4.7: requestvar.html
1
<div id="main" class="lift:surround?with=default&at=content">
2
<div>
3
Using RequestVars to store state
4
</div>
5
6
<div>
7
<form class="lift:ReqVar?form=post">
8
Name: <input name="name"><br>
9
Age: <input name="age" id="the_age" value="0"><br>
10
<input type="submit" value="Submit">
11
</form>
12
</div>
13
</div>
Now, let’s look at the snippet code:
Listing 4.8: ReqVar.scala
1
package code
2
package snippet
3
4
import net.liftweb._
5
import http._
6
import common._
4In this case, “request” means full HTML page load and all subsquent Ajax operations on that page. There’s also a
TransientRequestVar that has the scope of the current HTTP request.
34
CHAPTER 4. FORMS
7
import util.Helpers._
8
import scala.xml.NodeSeq
9
10
/**
11
* A RequestVar-based snippet
12
*/
13
object ReqVar {
14
// define RequestVar holders for name, age, and whence
15
private object name extends RequestVar("")
16
private object age extends RequestVar("0")
17
private object whence extends RequestVar(S.referer openOr "/")
18
19
def render = {
20
// capture the whence... which forces evaluation of
21
// the whence RequestVar unless it's already been set
22
val w = whence.is
23
24
// we don't need an explicit function because RequestVar
25
// extends Settable{type=String}, so Lift knows how to
26
// get/set the RequestVar for text element creation
27
"name=name" #> SHtml.textElem(name) &
28
// add a hidden field that sets whence so we
29
// know where to go
30
"name=age" #> (SHtml.textElem(age) ++
31
SHtml.hidden(() => whence.set(w))) &
32
"type=submit" #> SHtml.onSubmitUnit(process)
33
}
34
35
// process the same way as
36
// in Stateful
37
private def process() =
38
asInt(age.is) match {
39
case Full(a) if a < 13 => S.error("Too young!")
40
case Full(a) => {
41
S.notice("Name: "+name)
42
S.notice("Age: "+a)
43
S.redirectTo(whence)
44
}
45
46
case _ => S.error("Age doesn't parse as a number")
47
}
48
}
The snippet is a singleton because the state is kept in the RequestVars.
We use SHtml.textElem() to generate the <input> tag. We can pass the RequestVar into the
method and the function that gets/sets the RequestVar is generated for us.
The use of this mechanism for doing stateful forms versus the StatefulSnippet mechanism is one
of personal choice. Neither one is better, they are just different.
Next, let’s look at how to get more granular with error messages.
4.5. FIELD ERRORS
35
4.5
Field Errors
In the prior examples, we displayed an error to the user. However, we didn’t tell the user what
field resulted in the error. Let’s be a little more granular about error reporting.
First, let’s look at the HTML:
Listing 4.9: fielderror.html
1
<div id="main" class="lift:surround?with=default&at=content">
2
<div>
3
Let's get granular about error messages
4
</div>
5
6
<div>
7
<div class="lift:FieldErrorExample?form=post">
8
Name: <input name="name"><br>
9
Age: <span class="lift:Msg?id=age&errorClass=error">error</span>
10
<input name="age" id="the_age" value="0"><br>
11
<input type="submit" value="Submit">
12
</div>
13
</div>
14
</div>
This HTML is different. Note: Age:
<span class="lift:Msg?id=age&errorClass=error">error</span>.
We mark an area in the markup to put the error message.
Let’s look at our snippet code which is very similar to Stateful.scala with a small, but impor-
tant difference:
Listing 4.10: FieldErrorExample.scala
1
package code
2
package snippet
3
4
import net.liftweb._
5
import http._
6
import common._
7
import util.Helpers._
8
import scala.xml.NodeSeq
9
10
/**
11
* A StatefulSnippet like Stateful.scala
12
*/
13
class FieldErrorExample extends StatefulSnippet {
14
private var name = ""
15
private var age = "0"
16
private val whence = S.referer openOr "/"
17
18
def dispatch = {case _ => render}
19
20
def render =
21
"name=name" #> SHtml.text(name, name = _) &
22
"name=age" #> SHtml.text(age, age = _) &
23
"type=submit" #> SHtml.onSubmitUnit(process)
36
CHAPTER 4. FORMS
24
25
// like Stateful
26
private def process() =
27
asInt(age) match {
28
// notice the parameter for error corresponds to
29
// the id in the Msg span
30
case Full(a) if a < 13 => S.error("age", "Too young!")
31
case Full(a) => {
32
S.notice("Name: "+name)
33
S.notice("Age: "+a)
34
S.redirectTo(whence)
35
}
36
37
// notice the parameter for error corresponds to
38
// the id in the Msg span
39
case _ => S.error("age", "Age doesn't parse as a number")
40
}
41
}
The key difference is: case Full(a) if a < 13 => S.error("age" , "Too young!").
Note that we pass "age" to S.error and this corresponds to the id in the Msg snippet in markup.
This tells Lift how to associate the error message and the markup.
But there’s a better way to do complex forms in Lift: LiftScreen.
4.6
LiftScreen
Much of what we do to build web applications is generating screens that associate input with
dynamic content. Lift provides Screen and Wizard for building single page and multi-page input
forms with validation, back-button support, etc.
So, let’s look at the HTML for a screen:
Listing 4.11: screen.html
1
<div id="main" class="lift:surround?with=default&at=content">
2
<div>
3
Let's use Lift's LiftScreen to build complex
4
simple screen input forms.
5
</div>
6
7
<div class="lift:ScreenExample">
8
Put your form here
9
</div>
10
</div>
We don’t explicitly declare the form elements. We just point to the snippet which looks like:
Listing 4.12: ScreenExample.scala
1
package code
2
package snippet
4.7. WIZARD
37
3
4
import net.liftweb._
5
import http._
6
7
/**
8