Simply Lift by David Pollak - HTML preview

PLEASE NOTE: This is an HTML preview only and some elements such as links or page numbers may be incorrect.
Download the book in PDF, ePub, Kindle for a complete version.

}

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