Permalink
Find file
Fetching contributors…
Cannot retrieve contributors at this time
2547 lines (1790 sloc) 43 KB
#LyX 1.6.5 created this file. For more info see http://www.lyx.org/
\lyxformat 345
\begin_document
\begin_header
\textclass book
\use_default_options true
\language english
\inputencoding auto
\font_roman default
\font_sans default
\font_typewriter default
\font_default_family default
\font_sc false
\font_osf false
\font_sf_scale 100
\font_tt_scale 100
\graphics default
\paperfontsize default
\spacing single
\use_hyperref false
\papersize default
\use_geometry false
\use_amsmath 1
\use_esint 1
\cite_engine basic
\use_bibtopic false
\paperorientation portrait
\secnumdepth 3
\tocdepth 3
\paragraph_separation indent
\defskip medskip
\quotes_language english
\papercolumns 1
\papersides 1
\paperpagestyle default
\tracking_changes false
\output_changes false
\author ""
\author ""
\end_header
\begin_body
\begin_layout Chapter
Forms in Lift
\begin_inset CommandInset label
LatexCommand label
name "cha:Forms-in-Lift"
\end_inset
\end_layout
\begin_layout Standard
In this chapter we're going to discuss the specifics of how you generate
and process forms with Lift.
Besides standard GET/POST form processing, Lift provides AJAX forms (Chapter
\begin_inset CommandInset ref
LatexCommand ref
reference "cha:AJAX-and-COMET"
\end_inset
) as well as JSON form processing (Section
\begin_inset CommandInset ref
LatexCommand ref
reference "sub:JSON-forms"
\end_inset
), but we're going to focus on the standard stuff here.
We're going to assume that you have a general knowledge of basic HTML form
tags as well as how CGI
\begin_inset Index
status open
\begin_layout Plain Layout
CGI
\end_layout
\end_inset
form processing works.
\end_layout
\begin_layout Section
Form Fundamentals
\begin_inset CommandInset label
LatexCommand label
name "sec:Form-Fundamentals"
\end_inset
\end_layout
\begin_layout Standard
Let's start with the basics of Lift form processing.
A form in Lift is usually produced via a snippet that contains the additional
\family typewriter
form
\family default
attribute.
As we mentioned in Section
\begin_inset CommandInset ref
LatexCommand ref
reference "sub:snippet-tag"
\end_inset
, this attribute takes the value GET or POST, and when present makes the
snippet code embed the proper form tags around the snippet HTML.
Listing
\begin_inset CommandInset ref
LatexCommand ref
reference "lst:An-example-form-template"
\end_inset
shows an example of a form that we will be discussing throughout this section.
\begin_inset Note Note
status open
\begin_layout Plain Layout
Make more pokcetchangey
\end_layout
\end_inset
\end_layout
\begin_layout Standard
\begin_inset listings
inline false
status open
\begin_layout Plain Layout
\begin_inset Caption
\begin_layout Plain Layout
An Example Form Template
\begin_inset CommandInset label
LatexCommand label
name "lst:An-example-form-template"
\end_inset
\end_layout
\end_inset
\end_layout
\begin_layout Plain Layout
<lift:Ledger.add form="POST">
\end_layout
\begin_layout Plain Layout
<entry:description /> <entry:amount /><br />
\end_layout
\begin_layout Plain Layout
<entry:submit />
\end_layout
\begin_layout Plain Layout
</lift:Ledger.add>
\end_layout
\end_inset
\end_layout
\begin_layout Standard
The first thing to understand about Lift's form support is that you generally
don't use the HTML tags for form elements directly, but rather you use
generator functions on
\begin_inset Newline linebreak
\end_inset
\family typewriter
net.liftweb.http.SHtml
\family default
.
The main reason for this is that it allows Lift to set up all of the internal
plumbing so that you keep your code simple.
Additionally, we use Lift's binding mechanism (Section
\begin_inset CommandInset ref
LatexCommand ref
reference "sub:Binding-Values-in-snippets"
\end_inset
) to
\begin_inset Quotes eld
\end_inset
attach
\begin_inset Quotes erd
\end_inset
the form elements in the proper location.
In our example in Listing
\begin_inset CommandInset ref
LatexCommand ref
reference "lst:An-example-form-template"
\end_inset
, we have bindings for a description field, an amount, and a submit button.
\end_layout
\begin_layout Standard
Our next step is to define the form snippet itself.
Corresponding to our example template is Listing
\begin_inset CommandInset ref
LatexCommand ref
reference "lst:An-example-form-snippet"
\end_inset
.
This shows our add method with a few vars to hold the form data and a binding
to the proper form elements.
We'll cover the
\family typewriter
processEntryAdd
\family default
method in a moment; for now let's look at what we have inside the add method.
\end_layout
\begin_layout Standard
\begin_inset listings
inline false
status open
\begin_layout Plain Layout
\begin_inset Caption
\begin_layout Plain Layout
An Example Form Snippet
\begin_inset CommandInset label
LatexCommand label
name "lst:An-example-form-snippet"
\end_inset
\end_layout
\end_inset
\end_layout
\begin_layout Plain Layout
def add (xhtml : NodeSeq) : NodeSeq = {
\end_layout
\begin_layout Plain Layout
var desc = ""
\end_layout
\begin_layout Plain Layout
var amount = "0"
\end_layout
\begin_layout Plain Layout
\end_layout
\begin_layout Plain Layout
def processEntryAdd () { ...
}
\end_layout
\begin_layout Plain Layout
\end_layout
\begin_layout Plain Layout
bind("entry", xhtml,
\end_layout
\begin_layout Plain Layout
"description" -> SHtml.text(desc, desc = _),
\end_layout
\begin_layout Plain Layout
"amount" -> SHtml.text(amount, amount = _),
\end_layout
\begin_layout Plain Layout
"submit" -> SHtml.submit("Add", processEntryAdd))
\end_layout
\begin_layout Plain Layout
}
\end_layout
\end_inset
\end_layout
\begin_layout Standard
First, you may be wondering why we use vars defined inside the method.
Normally, these vars would be locally scoped (stack-based) and would be
discarded as soon as the method returns.
The beauty of Scala and Lift is that the right hand argument of each of
the SHtml functions is actually a function itself.
Because these functions, also known as anonymous closures
\begin_inset Index
status open
\begin_layout Plain Layout
closure
\end_layout
\end_inset
, reference variables in local scope, Scala magically transforms them to
heap variables behind the scenes.
Lift, in turn, adds the function callbacks for each form element into its
session state so that when the form is submitted, the appropriate closure
is called and the state is updated.
This is also why we define the
\family typewriter
processEntryAdd
\family default
function inside of the
\family typewriter
add
\family default
method: by doing so, the
\family typewriter
processEntryAdd
\family default
function
\emph on
also
\emph default
has access to the closure variables.
In our example, we're using Scala's placeholder
\begin_inset Quotes eld
\end_inset
_
\begin_inset Quotes erd
\end_inset
shorthand
\begin_inset Foot
status open
\begin_layout Plain Layout
For more details on placeholders, see the
\emph on
Scala Language Specification
\emph default
, section 6.23
\end_layout
\end_inset
to define our functions.
Your description processing function could also be defined as:
\end_layout
\begin_layout LyX-Code
newDesc => description = newDesc
\end_layout
\begin_layout Standard
One important thing to remember, however, is that each new invocation of
the add method (for each page view) will get its own unique instance of
the variables that we've defined.
That means that if you want to retain values between submission and re-renderin
g of the form, you'll want to use
\family typewriter
RequestVar
\family default
s (Section
\begin_inset CommandInset ref
LatexCommand ref
reference "sec:Session-and-Request"
\end_inset
) or a
\family typewriter
StatefulSnippet
\family default
(Section
\begin_inset CommandInset ref
LatexCommand ref
reference "sub:Stateless-versus-Stateful"
\end_inset
) instead .
Generally you will only use vars defined within the snippet method when
your form doesn't require validation and you don't need any of the submitted
data between snippet executions.
An example of using
\family typewriter
RequestVar
\family default
s for your form data would be if you want to do form validation and retain
submitted values if validation fails, as shown in Listing
\begin_inset CommandInset ref
LatexCommand ref
reference "lst:Using-RequestVars-with-forms"
\end_inset
.
In this instance, we set an error message (more in Chapter
\begin_inset CommandInset ref
LatexCommand ref
reference "cha:Message-Handling"
\end_inset
).
Since we don't explicitly redirect, the same page is loaded (the default
\begin_inset Quotes eld
\end_inset
action
\begin_inset Quotes erd
\end_inset
for a page in Lift is the page itself) and the current RequestVar value
of description is used as the default value of the text box.
\end_layout
\begin_layout Standard
\begin_inset listings
inline false
status open
\begin_layout Plain Layout
\begin_inset Caption
\begin_layout Plain Layout
Using RequestVars with Forms
\begin_inset CommandInset label
LatexCommand label
name "lst:Using-RequestVars-with-forms"
\end_inset
\end_layout
\end_inset
\end_layout
\begin_layout Plain Layout
object description extends RequestVar("")
\end_layout
\begin_layout Plain Layout
object amount extends RequestVar("0")
\end_layout
\begin_layout Plain Layout
\end_layout
\begin_layout Plain Layout
def add (xhtml : NodeSeq) : NodeSeq = {
\end_layout
\begin_layout Plain Layout
def processEntryAdd () =
\end_layout
\begin_layout Plain Layout
if (amount.toDouble <= 0) {
\end_layout
\begin_layout Plain Layout
S.error("Invalid amount")
\end_layout
\begin_layout Plain Layout
} else {
\end_layout
\begin_layout Plain Layout
// ...
process Add ...
\end_layout
\begin_layout Plain Layout
redirectTo(...)
\end_layout
\begin_layout Plain Layout
}
\end_layout
\begin_layout Plain Layout
\end_layout
\begin_layout Plain Layout
bind("entry", xhtml,
\end_layout
\begin_layout Plain Layout
"description" -> SHtml.text(description.is, description(_)),
\end_layout
\begin_layout Plain Layout
...
\end_layout
\begin_layout Plain Layout
}
\end_layout
\end_inset
\end_layout
\begin_layout Standard
The next thing to look at is how the form elements are generated.
We use the SHtml helper object to generate a form element of the appropriate
type for each variable.
In our case, we just want text fields for the description and amount, but
SHtml provides a number of other form element types that we'll be covering
later in this section.
Generally, an element generator takes an argument for the initial value
as well as a function to process the submitted value.
Usually both of these arguments will use a variable, but there's nothing
stopping you from doing something such as
\end_layout
\begin_layout LyX-Code
\begin_inset Quotes eld
\end_inset
description
\begin_inset Quotes erd
\end_inset
-> SHtml.text(
\begin_inset Quotes eld
\end_inset
\begin_inset Quotes erd
\end_inset
, println(
\begin_inset Quotes eld
\end_inset
Description =
\begin_inset Quotes eld
\end_inset
+ _))
\end_layout
\begin_layout Standard
Finally, our submit function executes the partially applied
\family typewriter
processEntryAdd
\family default
function, which, having access to the variables we've defined, can do whatever
it needs to do when the submit button is pressed.
\end_layout
\begin_layout Section
\begin_inset CommandInset label
LatexCommand label
name "sec:Attributes-for-Form-Elems"
\end_inset
Attributes for Form Elements
\begin_inset Index
status open
\begin_layout Plain Layout
Attributes ! in form elements
\end_layout
\end_inset
\end_layout
\begin_layout Standard
In addition to the approaches shown in Section
\begin_inset CommandInset ref
LatexCommand ref
reference "sec:Handling-XHTML-Attributes"
\end_inset
, the SHtml generator functions allow you to apply attributes by passing
the attribute name/value pairs as final arguments.
This is usually simpler, and in some cases is much simpler than using the
\begin_inset Quotes eld
\end_inset
%
\begin_inset Quotes erd
\end_inset
operator directly.
For example, checkbox and radio form elements are acutally returned as
ChoiceHolder instances, which do not directly support the
\begin_inset Quotes eld
\end_inset
%
\begin_inset Quotes erd
\end_inset
operator.
Listing
\begin_inset CommandInset ref
LatexCommand ref
reference "lst:Applying-Attributes-vararg"
\end_inset
shows how to apply the same attributes as Listing
\begin_inset CommandInset ref
LatexCommand ref
reference "lst:Applying-Attributes-percent"
\end_inset
using the varargs approach.
\end_layout
\begin_layout Standard
\begin_inset listings
inline false
status open
\begin_layout Plain Layout
val myInput = SHtml.text("", processText(_), "id" -> "inputField",
\end_layout
\begin_layout Plain Layout
"class" -> "highlighted")
\end_layout
\begin_layout Plain Layout
\begin_inset Caption
\begin_layout Plain Layout
\begin_inset CommandInset label
LatexCommand label
name "lst:Applying-Attributes-vararg"
\end_inset
Applying Attributes as Varargs
\end_layout
\end_inset
\end_layout
\end_inset
\end_layout
\begin_layout Section
An Overview of Form Elements
\end_layout
\begin_layout Standard
Now that we've covered the basics of forms, we're going to go into a little
more detail for each form element generator method on SHtml.
The
\family typewriter
a
\family default
method (all 3 variants) as well as the
\family typewriter
ajax
\family default
* methods are specific to AJAX forms, which are covered in detail in Chapter
\begin_inset CommandInset ref
LatexCommand ref
reference "cha:AJAX-and-COMET"
\end_inset
.
The
\family typewriter
json*
\family default
methods are covered in Section
\begin_inset CommandInset ref
LatexCommand ref
reference "sub:JSON-forms"
\end_inset
.
We'll be covering the fileUpload method in detail in Section
\begin_inset CommandInset ref
LatexCommand ref
reference "sec:File-Uploads"
\end_inset
.
One final note before we dive in is that most generator methods have an
overload with a trailing asterisk (i.e.
\family typewriter
hidden_*
\family default
); these are generally equivalent to the overloads without an asterisk but
are intended for Lift's internal use.
\end_layout
\begin_layout Subsection
checkbox
\end_layout
\begin_layout Standard
The
\family typewriter
checkbox
\family default
method generates a checkbox form element, taking an initial Boolean value
as well as a function
\begin_inset Formula $(Boolean)\Rightarrow Any$
\end_inset
that is called when the checkbox is submitted.
If you've done a lot of HTML form processing you might wonder how this
actually occurs, since
\emph on
an unchecked checkbox is not actually submitted as part of a form
\emph default
.
Lift works around this by adding a hidden form element for each checkbox
with the same element name, but with a false value, to ensure that the
callback function is always called.
Because more than one XML node is returned by the generator, you can’t
use the % metadata mechanism to set attributes on the check box element.
Instead, use attribute pairs as arguments to the generator function as
outlined in Section
\begin_inset CommandInset ref
LatexCommand ref
reference "sub:Direct-attr-manip"
\end_inset
.
\end_layout
\begin_layout Standard
For example, Listing
\begin_inset CommandInset ref
LatexCommand ref
reference "lst:A-checkbox-example"
\end_inset
shows a checkbox with an id of
\begin_inset Quotes eld
\end_inset
snazzy
\begin_inset Quotes erd
\end_inset
and a class attribute set to
\begin_inset Quotes eld
\end_inset
woohoo.
\begin_inset Quotes erd
\end_inset
\begin_inset Note Note
status open
\begin_layout Plain Layout
Discuss toForm here
\end_layout
\end_inset
\end_layout
\begin_layout Standard
\begin_inset listings
inline false
status open
\begin_layout Plain Layout
\begin_inset Caption
\begin_layout Plain Layout
A Checkbox Example
\begin_inset CommandInset label
LatexCommand label
name "lst:A-checkbox-example"
\end_inset
\end_layout
\end_inset
\end_layout
\begin_layout Plain Layout
SHtml.checkbox_id(false, if (_) frobnicate(),
\end_layout
\begin_layout Plain Layout
Full("snazzy"), "class" -> "woohoo")
\end_layout
\end_inset
\end_layout
\begin_layout Subsection
hidden
\end_layout
\begin_layout Standard
The
\family typewriter
hidden
\family default
method generates a hidden form field.
Unlike the HTML hidden field, the hidden tag is not intended to hold a
plain value; rather, in Lift it takes a function
\begin_inset Formula $()\Rightarrow Any$
\end_inset
argument that is called when the form is submitted.
As with most of the other generators, it also takes a final varargs sequence
of
\family typewriter
Pair[String,String]
\family default
attributes to be added to the XML node.
Listing
\begin_inset CommandInset ref
LatexCommand ref
reference "lst:A-hidden-example"
\end_inset
shows an example of using a hidden field to
\begin_inset Quotes eld
\end_inset
log
\begin_inset Quotes erd
\end_inset
information.
(When the form is submitted,
\begin_inset Quotes eld
\end_inset
Form was submitted
\begin_inset Quotes erd
\end_inset
will be printed to stdout.
This can be a useful trick for debugging if you're not using a full-blown
IDE.)
\end_layout
\begin_layout Standard
\begin_inset listings
inline false
status open
\begin_layout Plain Layout
\begin_inset Caption
\begin_layout Plain Layout
A Hidden Example
\begin_inset CommandInset label
LatexCommand label
name "lst:A-hidden-example"
\end_inset
\end_layout
\end_inset
\end_layout
\begin_layout Plain Layout
SHtml.hidden(() => println("Form was submitted"))
\end_layout
\end_inset
\end_layout
\begin_layout Subsection
link
\end_layout
\begin_layout Standard
The link method generates a standard HTML link to a page (an
\family typewriter
<a>
\family default
tag, or anchor
\begin_inset Index
status open
\begin_layout Plain Layout
anchor
\end_layout
\end_inset
), but also ensures that a given function is executed when the link is clicked.
The first argument is the web context relative link path, the second argument
is the
\begin_inset Formula $()\Rightarrow Any$
\end_inset
function that will be executed when the link is clicked, and the third
argument is a NodeSeq that will make up the body of the link.
You may optionally pass one or more
\family typewriter
Pair[String,String]
\family default
attributes to be added to the link element.
Listing
\begin_inset CommandInset ref
LatexCommand ref
reference "lst:A-link-example"
\end_inset
shows using a link to load an
\family typewriter
Expense
\family default
entry for editing from within a table.
In this case we're using a
\family typewriter
RequestVar
\family default
to hold the entry to edit, so the link function is a closure that loads
the current
\family typewriter
Expense
\family default
entry.
This combination of link and
\family typewriter
RequestVar
\family default
s is a common pattern for passing objects between different pages.
\end_layout
\begin_layout Standard
\begin_inset listings
inline false
status open
\begin_layout Plain Layout
\begin_inset Caption
\begin_layout Plain Layout
A Link Example
\begin_inset CommandInset label
LatexCommand label
name "lst:A-link-example"
\end_inset
\end_layout
\end_inset
\end_layout
\begin_layout Plain Layout
object currentExpense extends RequestVar[Box[Expense]](Empty)
\end_layout
\begin_layout Plain Layout
\end_layout
\begin_layout Plain Layout
def list (xhtml : NodeSeq) : NodeSeq = {
\end_layout
\begin_layout Plain Layout
...
\end_layout
\begin_layout Plain Layout
val entriesXml =
\end_layout
\begin_layout Plain Layout
entries.map(entry =>
\end_layout
\begin_layout Plain Layout
bind("entry", chooseTemplate("expense", "entries", xhtml),
\end_layout
\begin_layout Plain Layout
...
\end_layout
\begin_layout Plain Layout
"edit" -> SHtml.link("/editExpense",
\end_layout
\begin_layout Plain Layout
() => currentExpense(Full(entry)),
\end_layout
\begin_layout Plain Layout
Text("Edit")))
\end_layout
\begin_layout Plain Layout
)
\end_layout
\begin_layout Plain Layout
}
\end_layout
\end_inset
\end_layout
\begin_layout Subsection
text and password
\end_layout
\begin_layout Standard
The text and password methods generate standard
\family typewriter
text
\family default
and
\family typewriter
password
\family default
\family typewriter
input
\family default
fields, respectively.
While both take string default values and
\begin_inset Formula $(String)\Rightarrow Any$
\end_inset
functions to process the return, the password text field masks typed characters
and doesn't allow copying the value from the box on the client side.
Listing
\begin_inset CommandInset ref
LatexCommand ref
reference "lst:A-text-field"
\end_inset
shows an example of using both text and password for a login page.
\end_layout
\begin_layout Standard
\begin_inset listings
inline false
status open
\begin_layout Plain Layout
\begin_inset Caption
\begin_layout Plain Layout
A Text Field Example
\begin_inset CommandInset label
LatexCommand label
name "lst:A-text-field"
\end_inset
\end_layout
\end_inset
\end_layout
\begin_layout Plain Layout
def login(xhtml : NodeSeq) : NodeSeq = {
\end_layout
\begin_layout Plain Layout
var user = ""; var pass = "";
\end_layout
\begin_layout Plain Layout
def auth () = { ...
}
\end_layout
\begin_layout Plain Layout
bind("login", xhtml,
\end_layout
\begin_layout Plain Layout
"user" -> SHtml.text(user, user = _, "maxlength" -> "40")
\end_layout
\begin_layout Plain Layout
"pass" -> SHtml.password(pass, pass = _)
\end_layout
\begin_layout Plain Layout
"submit" -> SHtml.submit("Login", auth))
\end_layout
\begin_layout Plain Layout
}
\end_layout
\end_inset
\end_layout
\begin_layout Standard
Alternatively, you might want the user (but not the password) to be stored
in a
\family typewriter
RequestVar
\family default
so that if the authentication fails the user doesn't have to retype it.
Listing
\begin_inset CommandInset ref
LatexCommand ref
reference "lst:A-RV-text-field"
\end_inset
shows how the snippet would look in this case.
\end_layout
\begin_layout Standard
\begin_inset listings
inline false
status open
\begin_layout Plain Layout
\begin_inset Caption
\begin_layout Plain Layout
A RequestVar Text Field Example
\begin_inset CommandInset label
LatexCommand label
name "lst:A-RV-text-field"
\end_inset
\end_layout
\end_inset
\end_layout
\begin_layout Plain Layout
object user extends RequestVar[String]("")
\end_layout
\begin_layout Plain Layout
def login(xhtml : NodeSeq) : NodeSeq = {
\end_layout
\begin_layout Plain Layout
var pass = "";
\end_layout
\begin_layout Plain Layout
def auth () = { ...
}
\end_layout
\begin_layout Plain Layout
bind("login", xhtml,
\end_layout
\begin_layout Plain Layout
"user" -> SHtml.text(user.is, user(_), "maxlength" -> "40"),
\end_layout
\begin_layout Plain Layout
"pass" -> SHtml.password(pass, pass = _),
\end_layout
\begin_layout Plain Layout
"submit" -> SHtml.submit("Login", auth))
\end_layout
\begin_layout Plain Layout
}
\end_layout
\end_inset
\end_layout
\begin_layout Subsection
textarea
\end_layout
\begin_layout Standard
The textarea method generates a
\family typewriter
textarea
\family default
HTML form element.
Generally the functionality mirrors that of
\family typewriter
text
\family default
, although because it's a
\family typewriter
textarea
\family default
, you can control width and height by adding
\family typewriter
cols
\family default
and
\family typewriter
rows
\family default
attributes as shown in Listing
\begin_inset CommandInset ref
LatexCommand ref
reference "lst:A-textarea-example"
\end_inset
.
(You can, of course, add any other HTML attributes in the same manner.)
\end_layout
\begin_layout Standard
\begin_inset listings
inline false
status open
\begin_layout Plain Layout
\begin_inset Caption
\begin_layout Plain Layout
A Textarea Example
\begin_inset CommandInset label
LatexCommand label
name "lst:A-textarea-example"
\end_inset
\end_layout
\end_inset
\end_layout
\begin_layout Plain Layout
var noteText = ""
\end_layout
\begin_layout Plain Layout
val notes =
\end_layout
\begin_layout Plain Layout
SHtml.textarea(noteText, noteText = _,
\end_layout
\begin_layout Plain Layout
"cols" -> "80", "rows" -> "8")
\end_layout
\end_inset
\end_layout
\begin_layout Subsection
submit
\end_layout
\begin_layout Standard
Submit generates the submit form element (typically a button).
It requires two parameters: a String value to use as the button label,
and a function
\begin_inset Formula $()\Rightarrow Any$
\end_inset
that can be used to process your form results.
One important thing to note about submit is that form elements are processed
in the order that they appear in the HTML document.
This means that you should put your submit element last in your forms:
any items after the submit element won't have been
\begin_inset Quotes eld
\end_inset
set
\begin_inset Quotes erd
\end_inset
by the time the submit function is called.
Listings
\begin_inset CommandInset ref
LatexCommand ref
reference "lst:A-text-field"
\end_inset
and
\begin_inset CommandInset ref
LatexCommand ref
reference "lst:A-RV-text-field"
\end_inset
use the
\family typewriter
SHtml.submit
\family default
method for the authentication handler invocation.
\end_layout
\begin_layout Subsection
multiselect
\end_layout
\begin_layout Standard
Up to this point we've covered some fairly simple form elements.
Multiselect is a bit more complex in that it doesn't just process single
values.
Instead, it allows you to select multiple elements out of an initial Seq
and then process each selected element individually.
Listing
\begin_inset CommandInset ref
LatexCommand ref
reference "lst:Using-multiselect"
\end_inset
shows using a multiselect to allow the user to select multiple categories
for a ledger entry.
We assume that a Category entity has an id synthetic key as well as a String
name value.
The first thing we do is map the collection of all categories into pairs
of (
\family typewriter
value
\family default
,
\family typewriter
display
\family default
) strings.
The value is what will be returned to our processing function, while the
display string is what will be shown in the select box for the user.
Next, we turn the current entry's categories into a Seq of just value strings,
and we create a Set variable to hold the returned values.
Finally, we do our form binding.
In this example we use a helper function,
\family typewriter
loadCategory
\family default
(not defined here), that takes a String representing a Category's primary
key and returns the category.
We then use this helper method to update the Set that we created earlier.
Note that the callback function will be executed
\emph on
for each selected item
\emph default
in the multiselect, which is why the callback takes a String argument instead
of a
\family typewriter
Set[String]
\family default
.
This is also why we have to use our own set to manage the values.
Depending on your use case, you may or may not need to store the returned
values in a collection.
\end_layout
\begin_layout Standard
\begin_inset listings
inline false
status open
\begin_layout Plain Layout
\begin_inset Caption
\begin_layout Plain Layout
Using multiselect
\begin_inset CommandInset label
LatexCommand label
name "lst:Using-multiselect"
\end_inset
\end_layout
\end_inset
\end_layout
\begin_layout Plain Layout
def mySnippet ...
{
\end_layout
\begin_layout Plain Layout
val possible = allCategories.map(c => (c.id.toString, c.name))
\end_layout
\begin_layout Plain Layout
val current = currentEntry.categories.map(c => c.id.toString)
\end_layout
\begin_layout Plain Layout
var updated = Set.empty[Category]
\end_layout
\begin_layout Plain Layout
bind (...,
\end_layout
\begin_layout Plain Layout
"categories" ->
\end_layout
\begin_layout Plain Layout
SHtml.multiselect(possible, current, updated += loadCategory(_)))
\end_layout
\begin_layout Plain Layout
}
\end_layout
\end_inset
\end_layout
\begin_layout Subsection
radio
\end_layout
\begin_layout Standard
The radio method generates a set of radio buttons that take String values
and return a single String (the selected button) on form submission.
The values are used as labels for the Radio buttons, so you may need to
set up a
\family typewriter
Map
\family default
to translate back into useful values.
The radio method also takes a Box[String] that can be used to pre-select
one of the buttons.
The value of the Box must match one of the option values, or if you pass
Empty no buttons will be selected.
Listing
\begin_inset CommandInset ref
LatexCommand ref
reference "lst:Using-radio-for"
\end_inset
shows an example of using radio to select a color.
In this example, we use a
\family typewriter
Map
\family default
from color names to the actual color values for the translation.
To minimize errors, we use the
\family typewriter
keys
\family default
property of the Map to generate the list of options.
\begin_inset Note Note
status open
\begin_layout Plain Layout
Discuss the toForm method here.
\end_layout
\end_inset
\end_layout
\begin_layout Standard
\begin_inset listings
inline false
status open
\begin_layout Plain Layout
\begin_inset Caption
\begin_layout Plain Layout
Using radio for Colors
\begin_inset CommandInset label
LatexCommand label
name "lst:Using-radio-for"
\end_inset
\end_layout
\end_inset
\end_layout
\begin_layout Plain Layout
import java.awt.Color
\end_layout
\begin_layout Plain Layout
var myColor : Color = _
\end_layout
\begin_layout Plain Layout
val colorMap = Map("Red" -> Color.red,
\end_layout
\begin_layout Plain Layout
"White" -> Color.white,
\end_layout
\begin_layout Plain Layout
"Blue" -> Color.blue)
\end_layout
\begin_layout Plain Layout
val colors = SHtml.radio(colorMap.keys.toList, Empty, myColor = colorMap(_))
\end_layout
\end_inset
\end_layout
\begin_layout Subsection
select
\end_layout
\begin_layout Standard
The select method is very similar to the multiselect method except that
only one item may be selected from the list of options.
That also means that the default option is a
\family typewriter
Box[String]
\family default
instead of a
\family typewriter
Seq[String]
\family default
.
As with
\family typewriter
multiselect
\family default
, you pass a sequence of (value, display) pairs as the options for the select,
and process the return with a
\begin_inset Formula $(String)\Rightarrow Any$
\end_inset
function.
Listing
\begin_inset CommandInset ref
LatexCommand ref
reference "lst:A-select-example"
\end_inset
shows an example of using a select to choose an account to view.
\begin_inset Note Note
status open
\begin_layout Plain Layout
Need a better example
\end_layout
\end_inset
\end_layout
\begin_layout Standard
\begin_inset listings
inline false
status open
\begin_layout Plain Layout
\begin_inset Caption
\begin_layout Plain Layout
A select Example
\begin_inset CommandInset label
LatexCommand label
name "lst:A-select-example"
\end_inset
\end_layout
\end_inset
\end_layout
\begin_layout Plain Layout
var selectedAccount : Account = _
\end_layout
\begin_layout Plain Layout
val accounts = User.accounts.map(acc => (acc.id.toString, acc.name))
\end_layout
\begin_layout Plain Layout
val chooseAccount =
\end_layout
\begin_layout Plain Layout
SHtml.select(accounts, Empty,
\end_layout
\begin_layout Plain Layout
selectedAccount = loadAccount(_), "class" -> "myselect")
\end_layout
\end_inset
\end_layout
\begin_layout Standard
An important thing to note is that Lift will verify that the value submitted
in the form matches one of the options that was passed in.
If you need to do dynamic updating of the list, then you'll need to use
\family typewriter
untrustedSelect
\family default
(Section
\begin_inset CommandInset ref
LatexCommand ref
reference "sub:untrustedSelect"
\end_inset
).
\end_layout
\begin_layout Subsection
selectObj
\end_layout
\begin_layout Standard
One of the drawbacks with the select and multiselect generators is that
they deal only in Strings; if you want to select objects you need to provide
your own code for mapping from the strings.
The
\family typewriter
selectObj
\family default
generator method handles all of this for you.
Instead of passing a sequence of (value string, display string) pairs,
you pass in a sequence of (object, display string) pairs.
Similarly, the default value is a
\family typewriter
Box[T]
\family default
and the callback function is
\begin_inset Formula $(T)\Rightarrow Any$
\end_inset
, where T is the type of the object (
\family typewriter
selectObj
\family default
is a generic function).
Listing
\begin_inset CommandInset ref
LatexCommand ref
reference "lst:Using-selectObj-for"
\end_inset
shows a reworking of our radio example (Listing
\begin_inset CommandInset ref
LatexCommand ref
reference "lst:Using-radio-for"
\end_inset
) to select Colors directly.
Note that we set the select to default to
\family typewriter
Color.red
\family default
by passing in a
\family typewriter
Full
\family default
Box.
\end_layout
\begin_layout Standard
\begin_inset listings
inline false
status open
\begin_layout Plain Layout
\begin_inset Caption
\begin_layout Plain Layout
Using selectObj for Colors
\begin_inset CommandInset label
LatexCommand label
name "lst:Using-selectObj-for"
\end_inset
\end_layout
\end_inset
\end_layout
\begin_layout Plain Layout
...
standard Lift imports ...
\end_layout
\begin_layout Plain Layout
import _root_.java.awt.Color
\end_layout
\begin_layout Plain Layout
\end_layout
\begin_layout Plain Layout
class SelectSnippet {
\end_layout
\begin_layout Plain Layout
def chooseColor (xhtml : NodeSeq) : NodeSeq = {
\end_layout
\begin_layout Plain Layout
var myColor = Color.red
\end_layout
\begin_layout Plain Layout
val options = List(Color.red, Color.white, Color.blue)
\end_layout
\begin_layout Plain Layout
val colors = SHtml.selectObj(options, Full(myColor), myColor = _)
\end_layout
\begin_layout Plain Layout
bind(...)
\end_layout
\begin_layout Plain Layout
}
\end_layout
\begin_layout Plain Layout
}
\end_layout
\end_inset
\end_layout
\begin_layout Subsection
untrustedSelect
\begin_inset CommandInset label
LatexCommand label
name "sub:untrustedSelect"
\end_inset
\end_layout
\begin_layout Standard
The
\family typewriter
untrustedSelect
\family default
generator is essentially the same as the select generator, except that
the value returned in the form isn't validated against the original option
sequence.
This can be useful if you want to update the selection on the client side
using JavaScript.
\end_layout
\begin_layout Section
File Uploads
\begin_inset CommandInset label
LatexCommand label
name "sec:File-Uploads"
\end_inset
\begin_inset Index
status open
\begin_layout Plain Layout
File uploads
\end_layout
\end_inset
\begin_inset Index
status open
\begin_layout Plain Layout
Forms ! File uploads
\end_layout
\end_inset
\end_layout
\begin_layout Standard
File uploads are a special case of form submission that allow the client
to send a local file to the server.
This is accomplished by using multipart forms.
You can enable this by setting the
\family typewriter
multipart
\family default
attribute on your snippet tag to
\family typewriter
true
\family default
.
Listing
\begin_inset CommandInset ref
LatexCommand ref
reference "lst:File-upload-template"
\end_inset
shows how we can add a file upload to our existing expense entry form so
that users can attach scanned receipts to their expenses.
We modify our template to add a new form, shown below.
Note the
\family typewriter
multipart=
\begin_inset Quotes erd
\end_inset
true
\begin_inset Quotes erd
\end_inset
\family default
attribute.
\end_layout
\begin_layout Standard
\begin_inset listings
inline false
status open
\begin_layout Plain Layout
\begin_inset Caption
\begin_layout Plain Layout
File Upload Template
\begin_inset CommandInset label
LatexCommand label
name "lst:File-upload-template"
\end_inset
\end_layout
\end_inset
\end_layout
\begin_layout Plain Layout
<lift:AddEntry.addEntry form="POST" multipart="true">
\end_layout
\begin_layout Plain Layout
...
existing headers ...
\end_layout
\begin_layout Plain Layout
<td>Receipt (JPEG or PNG)</td>
\end_layout
\begin_layout Plain Layout
...
existing form fields ...
\end_layout
\begin_layout Plain Layout
<td><e:receipt /></td>
\end_layout
\begin_layout Plain Layout
...
\end_layout
\begin_layout Plain Layout
</lift:AddEntry.addEntry>
\end_layout
\end_inset
\end_layout
\begin_layout Standard
On the server side, Listing
\begin_inset CommandInset ref
LatexCommand ref
reference "lst:File-upload-snippet"
\end_inset
shows how we modify the existing
\family typewriter
addEntry
\family default
snippet to handle the (optional) file attachment.
We've added some logic to the existing form submission callback to check
to make sure that the image is of the proper type, then we use the
\family typewriter
SHtml
\family default
file upload generator with a callback that sets our
\family typewriter
fileHolder
\family default
variable.
The callback for the
\family typewriter
fileUpload
\family default
generator takes a
\family typewriter
FileParamHolder
\family default
, a special case class that contains information about the uploaded file.
The FileParamHolder case class has four parameters:
\end_layout
\begin_layout List
\labelwidthstring 00.00.0000
\family typewriter
name
\family default
The name of the form field that this file is associated with, as sent by
the client
\end_layout
\begin_layout List
\labelwidthstring 00.00.0000
\family typewriter
mimeType
\family default
The mime type as sent by the client
\end_layout
\begin_layout List
\labelwidthstring 00.00.0000
\family typewriter
filename
\family default
The filename as sent by the client
\end_layout
\begin_layout List
\labelwidthstring 00.00.0000
\family typewriter
file
\family default
An
\family typewriter
Array[Byte]
\family default
containing the uploaded file contents
\end_layout
\begin_layout Standard
In our example, we want to save the file data into a
\family typewriter
MappedBinary
\family default
field on our expense entry.
You could just as easily process the data in place using a
\family typewriter
scala.io.Source
\family default
or
\begin_inset Newline linebreak
\end_inset
\family typewriter
java.io.ByteArrayInputStream
\family default
, or output it using a
\family typewriter
java.io.FileOutputStream
\family default
.
\end_layout
\begin_layout Standard
\begin_inset listings
inline false
status open
\begin_layout Plain Layout
\begin_inset Caption
\begin_layout Plain Layout
File Upload Snippet
\begin_inset CommandInset label
LatexCommand label
name "lst:File-upload-snippet"
\end_inset
\end_layout
\end_inset
\end_layout
\begin_layout Plain Layout
class AddEntry {
\end_layout
\begin_layout Plain Layout
...
\end_layout
\begin_layout Plain Layout
// Add a variable to hold the FileParamHolder on submission
\end_layout
\begin_layout Plain Layout
var fileHolder : Box[FileParamHolder] = Empty
\end_layout
\begin_layout Plain Layout
...
\end_layout
\begin_layout Plain Layout
def doTagsAndSubmit (t : String) {
\end_layout
\begin_layout Plain Layout
...
\end_layout
\begin_layout Plain Layout
val e : Expense = ...
\end_layout
\begin_layout Plain Layout
// Add the optional receipt if it's the correct type
\end_layout
\begin_layout Plain Layout
val receiptOk = fileHolder match {
\end_layout
\begin_layout Plain Layout
// An empty upload gets reported with a null mime type,
\end_layout
\begin_layout Plain Layout
// so we need to handle this special case
\end_layout
\begin_layout Plain Layout
case Full(FileParamHolder(_, null, _, _)) => true
\end_layout
\begin_layout Plain Layout
case Full(FileParamHolder(_, mime, _, data))
\end_layout
\begin_layout Plain Layout
if mime.startsWith("image/") => {
\end_layout
\begin_layout Plain Layout
e.receipt(data).receiptMime(mime)
\end_layout
\begin_layout Plain Layout
true
\end_layout
\begin_layout Plain Layout
}
\end_layout
\begin_layout Plain Layout
case Full(_) => {
\end_layout
\begin_layout Plain Layout
S.error("Invalid receipt attachment")
\end_layout
\begin_layout Plain Layout
false
\end_layout
\begin_layout Plain Layout
}
\end_layout
\begin_layout Plain Layout
case _ => true
\end_layout
\begin_layout Plain Layout
}
\end_layout
\begin_layout Plain Layout
\end_layout
\begin_layout Plain Layout
(e.validate, receiptOk) match {
\end_layout
\begin_layout Plain Layout
...
\end_layout
\begin_layout Plain Layout
}
\end_layout
\begin_layout Plain Layout
...
\end_layout
\begin_layout Plain Layout
}
\end_layout
\begin_layout Plain Layout
\end_layout
\begin_layout Plain Layout
bind("e", in,
\end_layout
\begin_layout Plain Layout
...
\end_layout
\begin_layout Plain Layout
"receipt" -> SHtml.fileUpload(fileHolder = _),
\end_layout
\begin_layout Plain Layout
"tags" -> SHtml.text(tags, doTagsAndSubmit))
\end_layout
\begin_layout Plain Layout
}
\end_layout
\begin_layout Plain Layout
}
\end_layout
\end_inset
\end_layout
\begin_layout Standard
By default
\begin_inset Index
status open
\begin_layout Plain Layout
Forms ! saving file uploads to disk
\end_layout
\end_inset
, Lift will utilize the
\family typewriter
InMemoryFileParamHolder
\family default
\begin_inset Index
status open
\begin_layout Plain Layout
InMemoryFileParamHolder
\end_layout
\end_inset
to represent uploaded file data.
This implementation reads the uploaded data directly into memory (you retrieve
the byte array with the
\family typewriter
file
\family default
val).
If you would prefer to have Lift write uploaded data to disk and then give
you a server-local filename to work with, you can use the
\family typewriter
LiftRules.handleMimeFile
\family default
configuration hook to instead use the
\family typewriter
OnDiskFileParamHolder
\family default
\begin_inset Index
status open
\begin_layout Plain Layout
OnDiskFileParamHolder
\end_layout
\end_inset
, as shown in Listing
\begin_inset CommandInset ref
LatexCommand ref
reference "lst:Using-OnDiskFileParamHolder"
\end_inset
.
The OnDiskFileParamHolder class has an additional property,
\family typewriter
localFile
\family default
, that is a
\family typewriter
java.io.File
\family default
object for the temporary upload file.
\end_layout
\begin_layout Standard
\begin_inset listings
inline false
status open
\begin_layout Plain Layout
\begin_inset Caption
\begin_layout Plain Layout
\begin_inset CommandInset label
LatexCommand label
name "lst:Using-OnDiskFileParamHolder"
\end_inset
Using OnDiskFileParamHolder
\end_layout
\end_inset
\end_layout
\begin_layout Plain Layout
// in Boot.boot:
\end_layout
\begin_layout Plain Layout
LiftRules.handleMimeFile = OnDiskFileParamHolder.apply
\end_layout
\end_inset
\end_layout
\end_body
\end_document