Lift is known for its great Ajax and Comet support, and in this chapter, we’ll explore these.
For an introduction to Lift’s Ajax and Comet features, see Simply Lift, Chapter 9 of Lift in Action (Perrett, 2012, Manning Publications, Co.), or watch Diego Medina’s video presentation.
The source code for this chapter is at https://github.com/LiftCookbook/cookbook_ajax.
Use ajaxInvoke
:
package
code.snippet
import
net.liftweb.util.Helpers._
import
net.liftweb.http.SHtml
import
net.liftweb.http.js.
{
JsCmd
,
JsCmds
}
import
net.liftweb.common.Loggable
object
AjaxInvoke
extends
Loggable
{
def
callback
()
:
JsCmd
=
{
logger
.
info
(
"The button was pressed"
)
JsCmds
.
Alert
(
"You clicked it"
)
}
def
button
=
"button [onclick]"
#>
SHtml
.
ajaxInvoke
(
callback
)
}
In this snippet, we are binding the click event of a button to an ajaxInvoke
: when the button is pressed, Lift
arranges for the function you gave ajaxInvoke
to be executed.
That function, callback
, is just logging a message and returning a JavaScript alert to the browser. The corresponding HTML might include:
<div
data-lift=
"AjaxInvoke.button"
>
<button>
Click Me</button>
</div>
The signature of the function you pass to ajaxInvoke
is
Unit => JsCmd
, meaning you can trigger a range of behaviours: from
returning Noop
if you want nothing to happen, through changing DOM
elements, all the way up to executing arbitrary JavaScript.
The previous example uses a button but will work on any element that
has an event you can bind to. We’re binding to onclick
but it could be any event
the DOM exposes.
Related to ajaxInvoke
are the following functions:
-
SHtml.onEvent
-
Calls a function with the signature
String => JsCmd
because it is passed thevalue
of the node it is attached to. In the previous example, this would be the empty string, as the button has no value. -
SHtml.ajaxCall
-
This is more general than
onEvent
, as you give it the expression you want passed to your server-side code. -
SHtml.jsonCall
- This is even more general still: you give it a function that will return a JSON object when executed on the client, and this object will be passed to your server-side function.
Let’s look at each of these in turn.
You can use onEvent
with any element that has a value
attribute and responds to the event you choose to bind to. The function you supply to onEvent
is called with the element’s value. As an example, we can write a snippet that presents a challenge to the user and validates the response:
package
code.snippet
import
scala.util.Random
import
net.liftweb.util.Helpers._
import
net.liftweb.http.SHtml
import
net.liftweb.http.js.JsCmds.Alert
object
OnEvent
{
def
render
=
{
val
x
,
y
=
Random
.
nextInt
(
10
)
val
sum
=
x
+
y
"p *"
#>
"What is %d + %d?"
.
format
(
x
,
y
)
&
"input [onchange]"
#>
SHtml
.
onEvent
(
answer
=>
if
(
answer
==
sum
.
toString
)
Alert
(
"Correct!"
)
else
Alert
(
"Try again"
)
)
}
}
This snippet prompts the user to add the two random numbers presented in the <p>
tag, and binds a validation function to the <input>
on the page:
<div
data-lift=
"OnEvent"
>
<p>
Problem appears here</p>
<input
placeholder=
"Type your answer"
></input>
</div>
When onchange
is triggered (by pressing Return or the Tab key, for example), the text entered is sent to our onEvent
function as a String
. On the server-side, we check the answer and send back “Correct!” or “Try again” as a JavaScript alert.
Where onEvent
sends this.value
to your server-side code, ajaxCall
allows you to specify the client-side expression used to produce a value.
To demonstrate this, we can create a template that includes two elements: a button and a text field. We’ll bind our function to the button, but read a value from the input field:
<div
data-lift=
"AjaxCall"
>
<input
id=
"num"
value=
"41"
></input>
<button>
Increment</button>
</div>
We want to arrange for the button to read the num
field, increment it, and return it back to the input field:
package
code.snippet
import
net.liftweb.util.Helpers._
import
net.liftweb.http.SHtml
import
net.liftweb.http.js.JE.ValById
import
net.liftweb.http.js.JsCmds._
object
AjaxCall
{
def
increment
(
in
:
String
)
:
String
=
asInt
(
in
).
map
(
_
+
1
).
map
(
_
.
toString
)
openOr
in
def
render
=
"button [onclick]"
#>
SHtml
.
ajaxCall
(
ValById
(
"num"
),
s
=>
SetValById
(
"num"
,
increment
(
s
))
)
}
The first argument to ajaxCall
is the expression that will produce a value for our function. It can be any JsExp
, and we’ve
used ValById
, which looks up the value of an element by the ID attribute. We could have used a regular jQuery expression to achieve the same effect with JsRaw("$('#num').val()")
.
Our second argument to ajaxCall
takes the value of the JsExp
expression as a String
. We’re using one of Lift’s JavaScript commands to replace the value with a new value. The new value is the result of incrementing the number (providing it is a number).
The end result is that you press the button and the number updates. It should go without saying that these are simple illustrations, and you probably don’t want a server round-trip to add one to a number. The techniques come into their own when there is some action of value to perform on the server.
You may have guessed that onEvent
is implemented as an ajaxCall
for JsRaw("this.value")
.
Both ajaxCall
and onEvent
end up evaluating a String => JsCmd
function. By contrast, jsonCall
has the signature JValue => JsCmd
, meaning you can pass more complex data structures from JavaScript to your Lift application.
To demonstrate this, we’ll create a template that asks for input, has a function to convert the input into JSON, and a button to send the JSON to the server:
<div
data-lift=
"JsonCall"
>
<p>
Enter an addition question:</p>
<div>
<input
id=
"x"
>
+<input
id=
"y"
>
=<input
id=
"z"
>
.</div>
<button>
Check</button>
</div>
<script
type=
"text/javascript"
>
//<![CDATA[
function currentQuestion() {
return {
first: parseInt($('#x').val()),
second: parseInt($('#y').val()),
answer: parseInt($('#z').val())
};
}
// ]]>
The currentQuestion
function is creating an object, which will be turned into a JSON string when sent to the server. On the server, we’ll check that this JSON represents a valid integer addition problem:
package
code.snippet
import
net.liftweb.util.Helpers._
import
net.liftweb.http.SHtml
import
net.liftweb.http.js.
{
JsCmd
,
JE
}
import
net.liftweb.common.Loggable
import
net.liftweb.json.JsonAST._
import
net.liftweb.http.js.JsCmds.Alert
import
net.liftweb.json.DefaultFormats
object
JsonCall
extends
Loggable
{
implicit
val
formats
=
DefaultFormats
case
class
Question
(
first
:
Int
,
second
:
Int
,
answer
:
Int
)
{
def
valid_?
=
first
+
second
==
answer
}
def
render
=
{
def
validate
(
value
:
JValue
)
:
JsCmd
=
{
logger
.
info
(
value
)
value
.
extractOpt
[
Question
].
map
(
_
.
valid_?
)
match
{
case
Some
(
true
)
=>
Alert
(
"Looks good"
)
case
Some
(
false
)
=>
Alert
(
"That doesn't add up"
)
case
None
=>
Alert
(
"That doesn't make sense"
)
}
}
"button [onclick]"
#>
SHtml
.
jsonCall
(
JE
.
Call
(
"currentQuestion"
),
validate
_
)
}
}
Working from the bottom of this snippet up, we see a binding of the <button>
to the jsonCall
. The value we’ll be working on is the value provided by the JavaScript function called currentQuestion
. This was defined on the template page. When the button is clicked, the JavaScript function is called and the resulting value will be presented to validate
, which is our JValue => JsCmd
function.
All validate
does is log the JSON data and alert back if the question looks correct or not. To do this, we use the Lift JSON ability to extract JSON to a case class and call the valid_?
test to see if the numbers add up. This will evaluate to Some(true)
if the addition works, Some(false)
if the addition isn’t correct, or None
if the input is missing or not a valid integer.
Running the code and entering 1, 2, and 3 into the text fields will produce the following in the server log:
JObject
(
List
(
JField
(
first
,
JInt
(
1
)),
JField
(
second
,
JInt
(
2
)),
JField
(
answer
,
JInt
(
3
))))
This is the JValue
representation of the JSON.
Call Server When Select Option Changes includes an example of SHtml.onEvents
, which will bind a function to a number of events on a NodeSeq
.
Exploring Lift, Chapter 10, lists various JsExp
classes you can use for ajaxCall
.
Ajax JSON Form Processing using JsonHandler
to send JSON data from a form to the server.
Register a String => JsCmd
function with SHtml.ajaxSelect
.
In this example, we will look up the distance from Earth to the planet a user selects. This lookup will happen on the server and update the browser with the result. The interface is:
<div
data-lift=
"HtmlSelectSnippet"
>
<div>
<label
for=
"dropdown"
>
Planet:</label>
<select
id=
"dropdown"
></select>
</div>
<div
id=
"distance"
>
Distance will appear here</div>
</div>
The snippet code binds the <select>
element to send the selected value to the server:
package
code.snippet
import
net.liftweb.common.Empty
import
net.liftweb.util.Helpers._
import
net.liftweb.http.SHtml.ajaxSelect
import
net.liftweb.http.js.JsCmd
import
net.liftweb.http.js.JsCmds.SetHtml
import
xml.Text
class
HtmlSelectSnippet
{
// Our "database" maps planet names to distances:
type
Planet
=
String
type
LightYears
=
Double
val
database
=
Map
[
Planet
,LightYears
](
"Alpha Centauri Bb"
->
4.23
,
"Tau Ceti e"
->
11.90
,
"Tau Ceti f"
->
11.90
,
"Gliese 876 d"
->
15.00
,
"82 G Eridani b"
->
19.71
)
def
render
=
{
// To show the user a blank label and blank value option:
val
blankOption
=
(
""
->
""
)
// The complete list of options includes everything in our database:
val
options
:
List
[(
String
,String
)]
=
blankOption
::
database
.
keys
.
map
(
p
=>
(
p
,
p
)).
toList
// Nothing is selected by default:
val
default
=
Empty
// The function to call when an option is picked:
def
handler
(
selected
:
String
)
:
JsCmd
=
{
SetHtml
(
"distance"
,
Text
(
database
(
selected
)
+
" light years"
))
}
// Bind the <select> tag:
"select"
#>
ajaxSelect
(
options
,
default
,
handler
)
}
}
The last line of the code is doing the work for us. It is generating the options and binding
the selection to a function called handler
. The handler
function is called with the value
of the selected item.
We’re using the same String
(the planet name) for the option label and value, but they could be
different.
To understand what’s going on here, take a look at the HTML that Lift produces:
<select
id=
"dropdown"
onchange=
"liftAjax.lift_ajaxHandler('F470183993611Y15ZJU=' +
this.options[this.selectedIndex].value, null, null, null)"
>
<option
value=
""
></option>
<option
value=
"Tau Ceti e"
>
Tau Ceti e</option>
...</select>
The handler
function has been stored by Lift under the identifier of F470183993611Y15ZJU
(in this particular rendering). An onchange
event handler is attached to the <select>
element and is responsible for transporting the selected value to the server, and bringing a value back. The lift_ajaxHandler
JavaScript function is defined in liftAjax.js, which is automatically added to your page.
If you need to additionally capture the selected value on a regular form submission, you can make use of SHtml.onEvents
. This attaches event listeners to a NodeSeq
, triggering a server-side function when the event occurs. We can use this with a regular form with a regular select box, but wire in Ajax calls to the server when the select changes.
To make use of this, our snippet changes very little:
var
selectedValue
:
String
=
""
"select"
#>
onEvents
(
"onchange"
)(
handler
)
{
select
(
options
,
default
,
selectedValue
=
_
)
}
&
"type=submit"
#>
onSubmitUnit
(
()
=>
S
.
notice
(
"Destination "
+
selectedValue
))
We are arranging for the same handler
function to be called when an onchange
event is triggered. This event binding is applied to a regular SHtml.select
, which is storing the selectedValue
when the form is submitted. We also bind a submit button to a function that generates a notice of which planet was selected.
The corresponding HTML also changes little. We need to add a button and make sure the snippet is marked as a form with ?form
:
<div
data-lift=
"HtmlSelectFormSnippet?form=post"
>
<div>
<label
for=
"dropdown"
>
Planet:</label>
<select
id=
"dropdown"
></select>
</div>
<div
id=
"distance"
>
Distance will appear here</div>
<input
type=
"submit"
value=
"Book Ticket"
/>
</div>
Now when you change a selected value, you see the dynamically updated distance calculation, but pressing the “Book Ticket” button also delivers the value to the server.
Use a Select Box with Multiple Options describes how to use classes rather than String
values for select boxes.
Bind your JavaScript directly to the event handler you want to run.
Here’s an example where we make a button slowly fade away when you press it, but notice that we’re setting up this binding in our server-side Lift code:
package
code.snippet
import
net.liftweb.util.Helpers._
object
ClientSide
{
def
render
=
"button [onclick]"
#>
"$(this).fadeOut()"
}
In the template, we’d perhaps say:
<div
data-lift=
"ClientSide"
>
<button>
Click Me</button>
</div>
Lift will render the page as:
<button
onclick=
"$(this).fadeOut()"
>
Click Me</button>
Lift includes a JavaScript abstraction that you can use to build up more elaborate expressions for the client-side. For example, you can combine basic commands:
import
net.liftweb.http.js.JsCmds.
{
Alert
,
RedirectTo
}
def
render
=
"button [onclick]"
#>
(
Alert
(
"Here we go..."
)
&
RedirectTo
(
"http://liftweb.net"
))
This pops up an alert dialog and then sends you to http://liftweb.net. The HTML would be rendered as:
<button
onclick=
"alert("Here we go...");
window.location = "http://liftweb.net";"
>
Click Me</button>
Another option is to use JE.Call
to execute a JavaScript function with
parameters. Suppose we have this function defined:
function
greet
(
who
,
times
)
{
for
(
i
=
0
;
i
<
times
;
i
++
)
alert
(
"Hello "
+
who
);
}
We could bind a client-side button press to this client-side function like this:
import
net.liftweb.http.js.JE
def
render
=
"button [onclick]"
#>
JE
.
Call
(
"greet"
,
"World!"
,
3
)
On the client-side, we’d see:
<button
onclick=
"greet("World!",3)"
>
Click Me For Greeting</button>
Note that the types String
and Int
have been preserved in the JavaScript syntax of the call. This has happened because JE.Call
takes a variable number of JsExp
arguments after the JavaScript function name. There are wrappers for JavaScript primitive types (JE.Str
, JE.Num
, JsTrue
, JsFalse
) and implicit conversions to save you having to wrap the Scala values yourself.
Chapter 10 of Exploring Lift gives a list of JsCmds
and JE
expressions.
Wrap the input with a FocusOnLoad
command:
package
code.snippet
import
net.liftweb.util.Helpers._
import
net.liftweb.http.js.JsCmds.FocusOnLoad
class
Focus
{
def
render
=
"name=username"
#>
FocusOnLoad
(<
input
type
=
"text"
/>)
}
The CSS transform in render
will match against a name="username"
element in the HTML and
replace it with a text input field that will be focused on automatically
when the page loads.
Although we’re focusing on inline HTML, this could be any NodeSeq
, such as the one produced by SHtml.text
.
FocusOnLoad
is an example of a NodeSeq => NodeSeq
transformation. It appends to the NodeSeq
with the
JavaScript required to set focus on that field.
The JavaScript that performs the focus simply looks up the node in the DOM by ID and calls focus
on it. Although the previous example code doesn’t specify an ID, the command FocusOn
is smart enough to add one automatically for us.
There are two related JsCmd
choices:
-
Focus
- Takes an element ID and sets focus on the element
-
SetValueAndFocus
-
Similar to
Focus
, but takes an additionalString
value to populate the element with
These two are useful if you need to set focus from Ajax or Comet components dynamically.
The source for FocusOnLoad
is worth checking out to understand how it and related commands are constructed. This may help you package your own JavaScript functionality up into commands that can be used in CSS binding expressions.
If you need to set multiple CSS classes, encode a space between the
class names (e.g., class=boxed+primary
).
The form.ajax
construction is a regular snippet call: the Form
snippet is one of the handful of built-in snippets, and in this case, we’re calling the ajax
method on that object. However, normally snippet calls do not copy attributes into the resulting markup, but this snippet is implemented to do exactly that.
For an example of accessing these query parameters in your own snippets, see HTML Conditional Comments.
Simply Lift, Chapter 4, introduces Ajax forms.
You want to load an entire page—a template with snippets—inside of the current page (i.e., without a browser refresh).
Use Template
to load the template and SetHtml
to place the content
on the page.
Let’s populate a <div>
with the site home page when a button is pressed:
<div
data-lift=
"TemplateLoad"
>
<div
id=
"inject"
>
Content will appear here</div>
<button>
Load Template</button>
</div>
The corresponding snippet would be:
package
code.snippet
import
net.liftweb.util.Helpers._
import
net.liftweb.http.
{
SHtml
,
Templates
}
import
net.liftweb.http.js.JsCmds.
{
SetHtml
,
Noop
}
import
net.liftweb.http.js.JsCmd
object
TemplateLoad
{
def
content
:
JsCmd
=
Templates
(
"index"
::
Nil
).
map
(
ns
=>
SetHtml
(
"inject"
,
ns
))
openOr
Noop
def
render
=
"button [onclick]"
#>
SHtml
.
ajaxInvoke
(
content
_
)
}
Clicking the button will cause the content of /index.html to be
loaded into the inject
element.
Templates
produces a Box[NodeSeq]
. In the previous example, we map this content into a JsCmd
that will populate the inject
<div>
.
If you write unit tests to access templates, be aware that you may need to modify your development or testing environment to include the webapps folder. To do this for SBT, add the following to build.sbt:
unmanagedResourceDirectories
in
Test
<+=
(
baseDirectory
)
{
_
/
"src/main/webapp"
}
For this to work in your IDE, you’ll need to add webapp as a source folder to locate templates.
Trigger Server-Side Code from a Button describes ajaxInvoke
and related methods.
Use S.appendJs
, which places your JavaScript just before the closing </body>
tag, along with other JavaScript produced by Lift.
In this HTML, we have placed a <script>
tag in the middle of the page and marked it with a snippet called JavaScriptTail
:
<!DOCTYPE html>
<head>
<meta
content=
"text/html; charset=UTF-8"
http-equiv=
"content-type"
/>
<title>
JavaScript in Tail</title>
</head>
<body
data-lift-content-id=
"main"
>
<div
id=
"main"
data-lift=
"surround?with=default;at=content"
>
<h2>
JavaScript in the tail of the page</h2>
<script
type=
"text/javascript"
data-lift=
"JavaScriptTail"
>
</script>
<p>
The JavaScript about to be run will have been moved to the end of this page, just before the closing body tag.</p>
</div>
</body>
</html>
The <script>
content will be generated by a snippet.
It doesn’t need to be a <script>
tag; the snippet just replaces the content with nothing, but
hanging the snippet on the <script>
tag is a reminder of the purpose of the snippet:
package
code.snippet
import
net.liftweb.util.Helpers._
import
net.liftweb.http.js.JsCmds.Alert
import
net.liftweb.http.S
import
xml.NodeSeq
class
JavaScriptTail
{
def
render
=
{
S
.
appendJs
(
Alert
(
"Hi"
))
"*"
#>
NodeSeq
.
Empty
}
}
Although the snippet is rendering nothing, it calls S.appendJs
with a JsCmd
. This will produce the following in the page just before the end of the body:
<script
type=
"text/javascript"
>
// <![CDATA[
jQuery
(
document
).
ready
(
function
()
{
alert
(
"Hi"
);
});
// ]]>
</script>
Observe that the snippet was in the middle of the template, but the JavaScript appears at the end of the rendered page.
There are three other ways you could tackle this problem. The first is to move your JavaScript to an external file, and simply include it on the page where you want it. For substantial JavaScript code, this might make sense.
The second is a variation on S.appendJs
: S.appendGlobalJs
works in the same way but does not include the jQuery ready
around your JavaScript. This means you have no guarantee the DOM has loaded when your function is called.
A third option is wrap your JavaScript in a <lift:tail>
snippet:
class
JavascriptTail
{
def
render
=
"*"
#>
<
lift
:
tail>
{
Script
(
OnLoad
(
Alert
(
"
Hi
"
)))}
</lift:tail>
}
Note that lift:tail
is a general purpose Lift snippet and can be used to move various kinds of content to the end of the page, not just JavaScript.
Adding to the Head of a Page discusses a related Lift snippet for moving content to the head of the page.
Snippet Not Found When Using HTML5 describes the different ways of invoking a snippet, such as <lift:tail>
versus data-lift="tail"
.
You’re using a Comet actor and you want to arrange for some JavaScript to be executed in the event of the session being lost.
Configure your JavaScript via LiftRules.noCometSessionCmd
.
As an example, we can modify the standard Lift chat demo to save the message being typed in the event of the session loss. In the style of the demo, we would have an Ajax form for entering a message and the Comet chat area for displaying messages received:
<form
data-lift=
"form.ajax"
>
<input
type=
"text"
data-lift=
"ChatSnippet"
id=
"message"
placeholder=
"Type a message"
/>
</form>
<div
data-lift=
"comet?type=ChatClient"
>
<ul>
<li>
A message</li>
</ul>
</div>
To this we can add a function, stash
, which we want to be called in the event of a Comet session being lost:
<script
type=
"text/javascript"
>
// <![CDATA[
function
stash
()
{
saveCookie
(
"stashed"
,
$
(
'#message'
).
val
());
location
.
reload
();
}
jQuery
(
document
).
ready
(
function
()
{
var
stashedValue
=
readCookie
(
"stashed"
)
||
""
;
$
(
'#message'
).
val
(
stashedValue
);
deleteCookie
(
"stashed"
);
});
// Definition of saveCookie, readCookie, deleteCookie omitted.
</script>
Our stash
function will save the current value of the input field in a cookie called stashed
. We arrange, on page load, to check for that cookie and insert the value into our message field.
The final part is to modify Boot.scala to register our stash
function:
import
net.liftweb.http.js.JsCmds.Run
LiftRules
.
noCometSessionCmd
.
default
.
set
(
()
=>
Run
(
"stash()"
)
)
In this way, if a session is lost while composing a chat message, the browser will stash the message, and when the page reloads the message will be recovered.
To test the example, type a message into the message field, then restart your Lift application. Wait 10 seconds, and you’ll see the effect.
Without changing noCometSessionCmd
, the default behaviour of Lift is to arrange for the browser to load the home page, which is controlled by the LiftRules.noCometSessionPage
setting. This is carried out via the JavaScript function lift_sessionLost
in the file cometAjax.js.
By providing our own () => JsCmd
function to LiftRules.noCometSessionCmd
, Lift arranges to call this function and deliver the JsCmd
to the browser, rather than lift_sessionLost
. If you watch the HTTP traffic between your browser and Lift, you’ll see the stash
function call being returned in response to a Comet request.
This recipe has focused on the handling of loss of session for Comet; for Ajax, there’s a corresponding LiftRules.noAjaxSessionCmd
setting.
You’ll find the The ubiquitous Chat app in Simply Lift.
Being able to debug HTTP traffic is a useful way to understand how your Comet or Ajax application is performing. There are many plugins and products to help with this, such as the HttpFox plugin for Firefox.
You want to offer your users an Ajax file upload tool, with progress bars and drag-and-drop support.
Add Sebastian Tschan’s jQuery File Upload widget to your project, and implement a REST end point to receive files.
The first step is to download the widget and drag the js folder into your application as src/main/webapp/js. We can then use the JavaScript in a template:
<!DOCTYPE HTML>
<html>
<head>
<meta
charset=
"utf-8"
>
<title>
jQuery File Upload Example</title>
</head>
<body>
<h1>
Drag files onto this page</h1>
<input
id=
"fileupload"
type=
"file"
name=
"files[]"
data-url=
"/upload"
multiple
>
<div
id=
"progress"
style=
"width:20em; border: 1pt solid silver; display: none"
>
<div
id=
"progress-bar"
style=
"background: green; height: 1em; width:0%"
></div>
</div>
<script
src=
"//ajax.googleapis.com/ajax/libs/jquery/1.8.3/jquery.min.js"
>
</script>
<script
src=
"js/vendor/jquery.ui.widget.js"
></script>
<script
src=
"js/jquery.iframe-transport.js"
></script>
<script
src=
"js/jquery.fileupload.js"
></script>
<script>
$
(
function
()
{
$
(
'#fileupload'
).
fileupload
({
dataType
:
'json'
,
add
:
function
(
e
,
data
)
{
$
(
'#progress-bar'
).
css
(
'width'
,
'0%'
);
$
(
'#progress'
).
show
();
data
.
submit
();
},
progressall
:
function
(
e
,
data
)
{
var
progress
=
parseInt
(
data
.
loaded
/
data
.
total
*
100
,
10
)
+
'%'
;
$
(
'#progress-bar'
).
css
(
'width'
,
progress
);
},
done
:
function
(
e
,
data
)
{
$
.
each
(
data
.
files
,
function
(
index
,
file
)
{
$
(
'<p/>'
).
text
(
file
.
name
).
appendTo
(
document
.
body
);
});
$
(
'#progress'
).
fadeOut
();
}
});
});
</script>
</body>
</html>
This template provides an input field for files, an area to use as a progress indicator, and configures the widget when the page loads in a jQuery $( ... )
block. This is just regular usage of the JavaScript widget, and nothing particularly Lift-specific.
The final part is to implement a Lift REST service to receive the file or files. The URL of the service, /upload, is set in data-url
on the input
field, and that’s the address we match on:
package
code.rest
import
net.liftweb.http.rest.RestHelper
import
net.liftweb.http.OkResponse
object
AjaxFileUpload
extends
RestHelper
{
serve
{
case
"upload"
::
Nil
Post
req
=>
for
(
file
<-
req
.
uploadedFiles
)
{
println
(
"Received: "
+
file
.
fileName
)
}
OkResponse
()
}
}
This implementation simply logs the name of the file received and acknowledges successful delivery with a 200 status code back to the widget.
As with all REST services, it needs to be registered in Boot.scala:
LiftRules
.
dispatch
.
append
(
code
.
rest
.
AjaxFileUpload
)
By default, the widget makes the whole HTML page a drop-target for files, meaning you can drag a file onto the page and it will immediately be uploaded to your Lift application.
In this recipe, we’ve shown just the basic integration of the widget with a Lift application. The demo site for the widget shows other capabilities, and provides documentation on how to integrate them.
Many of the features just require JavaScript configuration. For example, we’ve used the widget’s add
, progressall
, and done
handlers to show, update, and then fade out a progress bar. When the upload is completed, the name of the uploaded file is appended to the page.
In the REST service, the uploaded files are available via the uploadedFiles
method on the request. When Lift receives a multipart form, it automatically extracts files as uploadedFiles
, each of which is a FileParamHolder
that gives us access to the fileName
, length
, mimeType
, and fileStream
.
By default, uploaded files are held in memory, but that can be changed (see Discussion in File Upload).
In this recipe, we return a 200 (OkResponse
). If we wanted to signal to the widget that a file was rejected, we can return another code. For example, perhaps we want to reject all files except PNG images. On the server-side we can do that by replacing the OkResponse
with a test:
import
net.liftweb.http.
{
ResponseWithReason
,
BadResponse
,
OkResponse
}
if
(
req
.
uploadedFiles
.
exists
(
_
.
mimeType
!=
"image/png"
))
ResponseWithReason
(
BadResponse
(),
"Only PNGs"
)
else
OkResponse
()
We would mirror this with a fail
handler in the client JavaScript:
fail
:
function
(
e
,
data
)
{
alert
(
data
.
errorThrown
);
}
If we uploaded, say, a JPEG, the browser would show an alert dialog reporting “Only PNGs.”
Diego Medina has posted a Gist of Lift REST code to integrate more fully with the image upload and image reviewing features of the widget, specifically implementing the JSON response that the widget expects for that functionality.
File Upload describes the basic file upload behaviour of Lift and how to control where files are stored.
Antonio Salazar Cardozo has posted example code for performing Ajax file upload using Lift’s Ajax mechanisms. This avoids external JavaScript libraries.
You want a wired UI element to have a different format than plain conversion to a string. For example, you’d like to display a floating-point value as a currency.
Use the WiringUI.toNode
method to create a wiring node that can render the output formatted as you desire.
As an example, consider an HTML template to display the quantity of an item being purchased and the subtotal:
<div
data-lift=
"Wiring"
>
<table>
<tbody>
<tr><td>
Quantity</td><td
id=
"quantity"
>
?</td></tr>
<tr><td>
Subtotal</td><td
id=
"subtotal"
>
?</td></tr>
</tbody>
</table>
<button
id=
"add"
>
Add Another One</button>
</div>
We’d like the subtotal to display as US dollars. The snippet would be:
package
code.snippet
import
java.text.NumberFormat
import
java.util.Locale
import
scala.xml.
{
NodeSeq
,
Text
}
import
net.liftweb.util.Helpers._
import
net.liftweb.util.
{
Cell
,
ValueCell
}
import
net.liftweb.http.
{
S
,
WiringUI
}
import
net.liftweb.http.SHtml.ajaxInvoke
import
net.liftweb.http.js.JsCmd
class
Wiring
{
val
cost
=
ValueCell
(
1.99
)
val
quantity
=
ValueCell
(
1
)
val
subtotal
=
cost
.
lift
(
quantity
)(
_
*
_
)
val
formatter
=
NumberFormat
.
getCurrencyInstance
(
Locale
.
US
)
def
currency
(
cell
:
Cell
[
Double
])
:
NodeSeq
=>
NodeSeq
=
WiringUI
.
toNode
(
cell
)((
value
,
ns
)
=>
Text
(
formatter
format
value
))
def
increment
()
:
JsCmd
=
{
quantity
.
atomicUpdate
(
_
+
1
)
S
.
notice
(
"Added One"
)
}
def
render
=
"#add [onclick]"
#>
ajaxInvoke
(
increment
)
&
"#quantity *"
#>
WiringUI
.
asText
(
quantity
)
&
"#subtotal *"
#>
currency
(
subtotal
)
}
We have defined a currency
method to format the subtotal
not as a Double
but as a currency amount using the Java number-formatting capabilities. This will result in values like “$19.90” being shown rather than “19.9.”
The primary WiringUI
class makes it easy to bind a cell as text. The asText
method works by converting a value to a String
and wrapping it in a Text
node. This is done via toNode
, however, we can use the toNode
method directly
to generate a transform function that is both hooked into the wiring UI and
uses our code for the translation of the item.
The mechanism is type-safe. In this example, cost
is a Double
cell, quantity
is an Int
cell, and subtotal
is inferred as a Cell[Double]
. This is why our formatting function is passed value
as a Double
.
Note that the function passed to toNode
must return a NodeSeq
. This gives a great deal of flexibility as you can return any kind of markup in a NodeSeq
. Our example complies with this signature by wrapping a text value in a Text
object.
The WiringUI.toNode
requires a (T, NodeSeq) => NodeSeq
. In the previous example, we ignore the NodeSeq
, but the value would be the contents of the element we’ve bound to. Given the input:
<td
id=
"subtotal"
>
?</td>
this would mean the NodeSeq
passed to us would just be the text node representing “?”. With a richer template we can use CSS selectors. For example, we can modify the template:
<td>
Subtotal</td><td
id=
"subtotal"
>
<i>
The value is<b
class=
"amount"
>
?</b></i>
</td>
Now we can apply a CSS selector to change just the amount
element:
(
value
,
ns
)
=>
(
".amount *"
#>
Text
(
formatter
format
value
))
apply
ns
)
Chapter 6 of Simply Lift describes Lift’s Wiring mechanism, and gives a detailed shopping example.
Get Lift Cookbook now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.