Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
branch: master
Fetching contributors…

Cannot retrieve contributors at this time

2772 lines (2055 sloc) 53.81 kb
#LyX 1.6.7 created this file. For more info see http://www.lyx.org/
\lyxformat 345
\begin_document
\begin_header
\textclass book
\use_default_options false
\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
AJAX and Comet in Lift
\begin_inset CommandInset label
LatexCommand label
name "cha:AJAX-and-COMET"
\end_inset
\end_layout
\begin_layout Standard
In this chapter we're going to discuss AJAX and Comet, two approaches to
improving the user experience through dynamic web pages.
While a full treatment of the techniques and technologies behind these
approaches is beyond the scope of this book
\begin_inset Foot
status open
\begin_layout Plain Layout
There are a number of good resources on the web that you can find by searching
for
\begin_inset Quotes eld
\end_inset
AJAX
\begin_inset Quotes erd
\end_inset
.
\end_layout
\end_inset
, we're going to cover the basics of how AJAX and Comet work.
In particular, we're going to look at how Lift handles them behind the
scenes to simplify your work.
\end_layout
\begin_layout Section
What are AJAX and Comet, really?
\end_layout
\begin_layout Standard
AJAX and Comet are variations on the traditional model of the web application
request/response lifecycle.
In the traditional model, the user starts by making a request for a page.
The server receives this request, performs processing, then sends a response
back to the user.
The response is then rendered by the user's browser.
At this point there are no further interactions between the user and the
server until the user clicks on a link or performs some other action that
starts a completely new request/response lifecycle.
AJAX and Comet extend this model to allow for asynchronous updates from
either the user to the server (AJAX), or from the server back to the user
(Comet).
\end_layout
\begin_layout Standard
If we take the example of adding a comment to a blog post, the traditional
model has the user fill in a form, hit the submit button, and send the
request to the server.
The server processes and adds the comment and then sends the updated blog
post back to the user with the newly added comment.
At the same time, if other people are viewing the blog post, they won't
see the new comment until they reload the page.
\end_layout
\begin_layout Standard
The AJAX model of this session changes such that the display of the new
comment is not tied to the response from the server.
When the user hits submit, the request to add the comment is sent to the
server
\emph on
in the background
\emph default
.
While it's being processed by the server, a JavaScript fragment (the
\begin_inset Quotes eld
\end_inset
J
\begin_inset Quotes erd
\end_inset
in AJAX) updates the user's page via DOM
\begin_inset Foot
status open
\begin_layout Plain Layout
Document Object Model.
More information can be found at
\begin_inset CommandInset href
LatexCommand href
target "http://www.w3.org/DOM/"
\end_inset
\end_layout
\end_inset
and adds the comment without the need for a full page reload.
\end_layout
\begin_layout Standard
Comet changes the traditional model by using a long-polling HTTP request
in the background that allows the server to push data to the browser without
requiring additional requests.
Essentially this is like AJAX, except in the opposite direction.
\end_layout
\begin_layout Standard
While the AJAX model increases the richness of the User Experience for a
single client at a time, Comet can do the same for multiple users.
Going back to our example of a blog post, Comet would enable the server
to notify anyone viewing the current blog post to automatically have their
pages updated when the new comment is added.
\end_layout
\begin_layout Standard
Figures
\begin_inset CommandInset ref
LatexCommand ref
reference "subfig:Traditional-Application-Model"
\end_inset
,
\begin_inset CommandInset ref
LatexCommand ref
reference "subfig:AJAX-Application-Model"
\end_inset
, and
\begin_inset CommandInset ref
LatexCommand ref
reference "subfig:COMET-Application-Model"
\end_inset
show graphical representations of how the models differ in terms of timeline
and server interaction.
\end_layout
\begin_layout Standard
\begin_inset Float figure
wide false
sideways false
status open
\begin_layout Plain Layout
\align center
\begin_inset Float figure
wide false
sideways false
status open
\begin_layout Plain Layout
\begin_inset Caption
\begin_layout Plain Layout
Traditional Application Model
\begin_inset CommandInset label
LatexCommand label
name "subfig:Traditional-Application-Model"
\end_inset
\end_layout
\end_inset
\end_layout
\begin_layout Plain Layout
\begin_inset Graphics
filename images/Traditional_Model.pdf
width 3in
\end_inset
\end_layout
\end_inset
\begin_inset Float figure
wide false
sideways false
status open
\begin_layout Plain Layout
\begin_inset Caption
\begin_layout Plain Layout
AJAX Application Model
\begin_inset CommandInset label
LatexCommand label
name "subfig:AJAX-Application-Model"
\end_inset
\end_layout
\end_inset
\end_layout
\begin_layout Plain Layout
\begin_inset Graphics
filename images/Ajax_Model.pdf
width 3in
\end_inset
\end_layout
\end_inset
\end_layout
\begin_layout Plain Layout
\begin_inset Float figure
wide false
sideways false
status open
\begin_layout Plain Layout
\end_layout
\begin_layout Plain Layout
\begin_inset Caption
\begin_layout Plain Layout
Comet Application Model
\begin_inset CommandInset label
LatexCommand label
name "subfig:COMET-Application-Model"
\end_inset
\end_layout
\end_inset
\end_layout
\begin_layout Plain Layout
\begin_inset Graphics
filename images/COMET_Model.pdf
width 3in
\end_inset
\end_layout
\end_inset
\end_layout
\begin_layout Plain Layout
\begin_inset Caption
\begin_layout Plain Layout
\begin_inset CommandInset label
LatexCommand label
name "fig:model-comparisons"
\end_inset
Application Model Comparisons
\end_layout
\end_inset
\end_layout
\end_inset
\end_layout
\begin_layout Section
Using AJAX in Lift
\end_layout
\begin_layout Standard
In previous chapters we've shown how to synchronously process forms (chapter
\begin_inset CommandInset ref
LatexCommand ref
reference "cha:Forms-in-Lift"
\end_inset
) and use JavaScript to perform client-side behavior (chapter
\begin_inset CommandInset ref
LatexCommand ref
reference "cha:Lift-and-Javascript"
\end_inset
).
AJAX blends these Lift techniques to give you powerful support for asynchronous
client-server interaction.
As with standard form and link elements, Lift uses methods on the
\family typewriter
SHtml
\family default
object to generate AJAX components in a concise manner.
We'll cover each of the AJAX-specific
\family typewriter
SHtml
\family default
methods in a later section, but for now we want to cover the high-level
aspects of using AJAX in Lift.
\end_layout
\begin_layout Standard
The first thing we want to point out is that AJAX generators take callback
methods just like regular element generators.
The major difference is that while standard
\family typewriter
SHtml
\family default
generator callbacks return
\family typewriter
scala.Any
\family default
, AJAX callbacks must return a
\family typewriter
net.liftweb.http.js.JsCmd
\family default
.
The reason is that the return from the callback is itself a client-side
callback that can be used to update the client content.
An example is shown in Listing
\begin_inset CommandInset ref
LatexCommand ref
reference "lst:A-simple-AJAX-example"
\end_inset
.
In this example we generate a button, that when clicked, will log a message
and then set the contents of the div named
\family typewriter
my-div
\family default
to a
\family typewriter
Text
\family default
element.
As you can see, adding client-side content changes is trivial.
\end_layout
\begin_layout Standard
\begin_inset listings
inline false
status open
\begin_layout Plain Layout
\begin_inset Caption
\begin_layout Plain Layout
A simple AJAX example
\begin_inset CommandInset label
LatexCommand label
name "lst:A-simple-AJAX-example"
\end_inset
\end_layout
\end_inset
\end_layout
\begin_layout Plain Layout
import _root_.net.liftweb.http.SHtml._
\end_layout
\begin_layout Plain Layout
// The next two imports are used to get some implicit conversions
\end_layout
\begin_layout Plain Layout
// in scope.
\end_layout
\begin_layout Plain Layout
import _root_.net.liftweb.http.JE._
\end_layout
\begin_layout Plain Layout
import _root_.net.liftweb.http.JsCmds._
\end_layout
\begin_layout Plain Layout
// Use logging facilities
\end_layout
\begin_layout Plain Layout
import _root_.net.liftweb.util.Log
\end_layout
\begin_layout Plain Layout
\end_layout
\begin_layout Plain Layout
// define a snippet method
\end_layout
\begin_layout Plain Layout
def myFunc(html: NodeSeq) : NodeSeq = {
\end_layout
\begin_layout Plain Layout
bind("hello", html, "button" -> ajaxButton(Text("Press me"), {() =>
\end_layout
\begin_layout Plain Layout
Log.info("Got an AJAX call")
\end_layout
\begin_layout Plain Layout
SetHtml("my-div", Text("That's it"))
\end_layout
\begin_layout Plain Layout
})
\end_layout
\begin_layout Plain Layout
}
\end_layout
\end_inset
\end_layout
\begin_layout Standard
The second important aspect of Lift's AJAX support is that behind the scenes
Lift provides a robust mechanism for AJAX submission.
For example, Lift provides its own JavaScript that handles retrying when
the submission times out.
You can control the timeout duration and retry count through
\family typewriter
LiftRule
\family default
's
\family typewriter
ajaxPostTimeout
\family default
(in milliseconds) and
\family typewriter
ajaxRetryCount
\family default
variables, respectively.
\end_layout
\begin_layout Standard
The third aspect of Lift's AJAX support is that it's so easy to enable.
Lift automatically takes care of adding the proper JavaScript libraries
to your templates when they're rendered, and sets up the proper callback
dispatch for you.
By default, dispatch is done relative to the
\family typewriter
/ajax_request
\family default
path in your web context, but Lift allows you change this via the
\family typewriter
LiftRules.ajaxPath
\family default
variable.
\end_layout
\begin_layout Standard
The final aspect is the flexibility the library provides.
Besides standard form elements and links that can be AJAXified, Lift also
provides the
\family typewriter
SHtml.ajaxCall
\family default
method which constructs a
\family typewriter
JsExp
\family default
that you can use directly on
\emph on
any
\emph default
element.
In addition, it allows you to construct a
\family typewriter
String
\family default
argument to your callback function via JavaScript so that you have full
access to client-side data.
\end_layout
\begin_layout Section
A more complex AJAX example
\begin_inset CommandInset label
LatexCommand label
name "sub:A-more-complex-ajax-example"
\end_inset
\end_layout
\begin_layout Standard
Let's take a look on a comparison example.
We've seen how to use
\family typewriter
SHtml.ajaxButton
\family default
, so let's see in Listing
\begin_inset CommandInset ref
LatexCommand ref
reference "lst:Ajax-comparison-example"
\end_inset
how can we achieve the same effect using
\family typewriter
SHtml.ajaxCall
\family default
and
\family typewriter
SHtml.ajaxInvoke
\family default
:
\end_layout
\begin_layout Standard
\begin_inset listings
inline false
status open
\begin_layout Plain Layout
\end_layout
\begin_layout Plain Layout
\begin_inset Caption
\begin_layout Plain Layout
AJAX comparison example
\begin_inset CommandInset label
LatexCommand label
name "lst:Ajax-comparison-example"
\end_inset
\end_layout
\end_inset
\end_layout
\begin_layout Plain Layout
class SimpleSnippet {
\end_layout
\begin_layout Plain Layout
import _root_.net.liftweb.http.js.{JE,JsCmd,JsCmds}
\end_layout
\begin_layout Plain Layout
import JsCmds._ // For implicits
\end_layout
\begin_layout Plain Layout
import JE.{JsRaw,Str}
\end_layout
\begin_layout Plain Layout
\end_layout
\begin_layout Plain Layout
def ajaxFunc1() : JsCmd = JsRaw("alert('Button1 clicked')")
\end_layout
\begin_layout Plain Layout
\end_layout
\begin_layout Plain Layout
def ajaxFunc2(str: String) : JsCmd = {
\end_layout
\begin_layout Plain Layout
println("Received " + str)
\end_layout
\begin_layout Plain Layout
JsRaw("alert('Button2 clicked')")
\end_layout
\begin_layout Plain Layout
}
\end_layout
\begin_layout Plain Layout
\end_layout
\begin_layout Plain Layout
def ajaxFunc3() : JsCmd = JsRaw("alert('Button3 clicked')")
\end_layout
\begin_layout Plain Layout
\end_layout
\begin_layout Plain Layout
def renderAJAXButtons(xhtml: Group): NodeSeq = {
\end_layout
\begin_layout Plain Layout
bind("ex", xhtml,
\end_layout
\begin_layout Plain Layout
"button1" -> SHtml.ajaxButton("Press me", ajaxFunc1 _),
\end_layout
\begin_layout Plain Layout
"button2" ->
\end_layout
\begin_layout Plain Layout
// ajaxCall and ajaxInvoke actually returns a pair (String,
JsExp).
\end_layout
\begin_layout Plain Layout
// The String is used for garbage collection, so we only need
\end_layout
\begin_layout Plain Layout
// to use the JsExp element (_2).
\end_layout
\begin_layout Plain Layout
<button onclick={SHtml.ajaxCall(Str("Button-2"), ajaxFunc2
_)._2}>
\end_layout
\begin_layout Plain Layout
Press me 2</button>,
\end_layout
\begin_layout Plain Layout
"button3" ->
\end_layout
\begin_layout Plain Layout
<button onclick={SHtml.ajaxInvoke(ajaxFunc3 _)._2}>
\end_layout
\begin_layout Plain Layout
Press me 3</button>)
\end_layout
\begin_layout Plain Layout
}
\end_layout
\begin_layout Plain Layout
}
\end_layout
\begin_layout Plain Layout
\end_layout
\end_inset
\end_layout
\begin_layout Standard
Basically, in Listing
\begin_inset CommandInset ref
LatexCommand ref
reference "lst:Ajax-comparison-example"
\end_inset
, we created three AJAX buttons using three different SHtml functions.
The difference between
\family typewriter
ajaxCall
\family default
and
\family typewriter
ajaxInvoke
\family default
is that for
\family typewriter
ajaxCall
\family default
you can specify a
\family typewriter
JsExp
\family default
parameter that will be executed on the client side.
The result of this
\family typewriter
JsExp
\family default
will be sent to the server.
In our case this parameter is simply a static String,
\family typewriter
Str(
\begin_inset Quotes eld
\end_inset
Button-2
\begin_inset Quotes erd
\end_inset
)
\family default
, but you can provide any
\family typewriter
JsExp
\family default
code here to calculate a client-side value to be passed to your callback.
For an overview of the rest of the
\family typewriter
SHtml
\family default
generator functions please see Chapter
\begin_inset CommandInset ref
LatexCommand ref
reference "cha:Forms-in-Lift"
\end_inset
.
\end_layout
\begin_layout Section
AJAX Generators in Detail
\begin_inset CommandInset label
LatexCommand label
name "sub:AJAX-Generators-in-detail"
\end_inset
\end_layout
\begin_layout Standard
The following table provides a brief synopsis of the AJAX generator methods
on the
\begin_inset Newline linebreak
\end_inset
\family typewriter
net.liftweb.http.SHtml
\family default
object:
\end_layout
\begin_layout Standard
\begin_inset Tabular
<lyxtabular version="3" rows="15" columns="2">
<features islongtable="true" headBottomDL="true">
<column alignment="left" valignment="top" width="0">
<column alignment="left" valignment="top" width="4in">
<row endhead="true">
<cell alignment="center" valignment="top" topline="true" bottomline="true" leftline="true" usebox="none">
\begin_inset Text
\begin_layout Plain Layout
Function name
\end_layout
\end_inset
</cell>
<cell alignment="center" valignment="top" topline="true" bottomline="true" leftline="true" rightline="true" usebox="none">
\begin_inset Text
\begin_layout Plain Layout
Description
\end_layout
\end_inset
</cell>
</row>
<row>
<cell alignment="center" valignment="top" topline="true" leftline="true" usebox="none">
\begin_inset Text
\begin_layout Plain Layout
ajaxButton
\end_layout
\end_inset
</cell>
<cell alignment="center" valignment="top" topline="true" leftline="true" rightline="true" usebox="none">
\begin_inset Text
\begin_layout Plain Layout
Renders a button that will submit an AJAX request to server
\end_layout
\end_inset
</cell>
</row>
<row>
<cell alignment="center" valignment="top" topline="true" leftline="true" usebox="none">
\begin_inset Text
\begin_layout Plain Layout
a
\end_layout
\end_inset
</cell>
<cell alignment="center" valignment="top" topline="true" leftline="true" rightline="true" usebox="none">
\begin_inset Text
\begin_layout Plain Layout
Renders an anchor tag that when clicked will submit an AJAX request
\end_layout
\end_inset
</cell>
</row>
<row>
<cell alignment="center" valignment="top" topline="true" leftline="true" usebox="none">
\begin_inset Text
\begin_layout Plain Layout
makeAJAXCall
\end_layout
\end_inset
</cell>
<cell alignment="center" valignment="top" topline="true" leftline="true" rightline="true" usebox="none">
\begin_inset Text
\begin_layout Plain Layout
Renders the JavaScript code that will submit an AJAX request
\end_layout
\end_inset
</cell>
</row>
<row>
<cell alignment="center" valignment="top" topline="true" leftline="true" usebox="none">
\begin_inset Text
\begin_layout Plain Layout
span
\end_layout
\end_inset
</cell>
<cell alignment="center" valignment="top" topline="true" leftline="true" rightline="true" usebox="none">
\begin_inset Text
\begin_layout Plain Layout
Renders a span element that when clicked will execute a
\family typewriter
JsCmd
\end_layout
\end_inset
</cell>
</row>
<row>
<cell alignment="center" valignment="top" topline="true" leftline="true" usebox="none">
\begin_inset Text
\begin_layout Plain Layout
ajaxCall
\end_layout
\end_inset
</cell>
<cell alignment="center" valignment="top" topline="true" leftline="true" rightline="true" usebox="none">
\begin_inset Text
\begin_layout Plain Layout
Renders the JavaScript code that will submit an AJAX request but it will
also send the value returned by the
\family typewriter
JsExp
\family default
provided.
\end_layout
\end_inset
</cell>
</row>
<row>
<cell alignment="center" valignment="top" topline="true" leftline="true" usebox="none">
\begin_inset Text
\begin_layout Plain Layout
ajaxInvole
\end_layout
\end_inset
</cell>
<cell alignment="center" valignment="top" topline="true" leftline="true" rightline="true" usebox="none">
\begin_inset Text
\begin_layout Plain Layout
Similar to
\family typewriter
ajaxCall
\family default
but there is no value to be computed and sent to the server
\end_layout
\end_inset
</cell>
</row>
<row>
<cell alignment="center" valignment="top" topline="true" leftline="true" usebox="none">
\begin_inset Text
\begin_layout Plain Layout
toggleKids
\end_layout
\end_inset
</cell>
<cell alignment="center" valignment="top" topline="true" leftline="true" rightline="true" usebox="none">
\begin_inset Text
\begin_layout Plain Layout
Provides the toggle effect on an element.
When clicked it will also send an AJAX call
\end_layout
\end_inset
</cell>
</row>
<row>
<cell alignment="center" valignment="top" topline="true" leftline="true" usebox="none">
\begin_inset Text
\begin_layout Plain Layout
ajaxText
\end_layout
\end_inset
</cell>
<cell alignment="center" valignment="top" topline="true" leftline="true" rightline="true" usebox="none">
\begin_inset Text
\begin_layout Plain Layout
Renders an input text element that will send an AJAX request on blur.
\end_layout
\end_inset
</cell>
</row>
<row>
<cell alignment="center" valignment="top" topline="true" leftline="true" usebox="none">
\begin_inset Text
\begin_layout Plain Layout
jsonText
\end_layout
\end_inset
</cell>
<cell alignment="center" valignment="top" topline="true" leftline="true" rightline="true" usebox="none">
\begin_inset Text
\begin_layout Plain Layout
Renders an input type text element the will send a JSON request on blur.
\end_layout
\end_inset
</cell>
</row>
<row>
<cell alignment="center" valignment="top" topline="true" leftline="true" usebox="none">
\begin_inset Text
\begin_layout Plain Layout
ajaxCheckbox
\end_layout
\end_inset
</cell>
<cell alignment="center" valignment="top" topline="true" leftline="true" rightline="true" usebox="none">
\begin_inset Text
\begin_layout Plain Layout
Renders a checkbox element that when clicked will send an AJAX call
\end_layout
\end_inset
</cell>
</row>
<row>
<cell alignment="center" valignment="top" topline="true" leftline="true" usebox="none">
\begin_inset Text
\begin_layout Plain Layout
ajaxSelect
\end_layout
\end_inset
</cell>
<cell alignment="center" valignment="top" topline="true" leftline="true" rightline="true" usebox="none">
\begin_inset Text
\begin_layout Plain Layout
Renders a select element then sends an AJAX call when the value changes
\end_layout
\end_inset
</cell>
</row>
<row>
<cell alignment="center" valignment="top" topline="true" leftline="true" usebox="none">
\begin_inset Text
\begin_layout Plain Layout
ajaxForm
\end_layout
\end_inset
</cell>
<cell alignment="center" valignment="top" topline="true" leftline="true" rightline="true" usebox="none">
\begin_inset Text
\begin_layout Plain Layout
Wraps a
\family typewriter
NodeSeq
\family default
that represents the form's content and makes an AJAX call when the form
is submitted.
\end_layout
\end_inset
</cell>
</row>
<row>
<cell alignment="center" valignment="top" topline="true" leftline="true" usebox="none">
\begin_inset Text
\begin_layout Plain Layout
jsonForm
\end_layout
\end_inset
</cell>
<cell alignment="center" valignment="top" topline="true" leftline="true" rightline="true" usebox="none">
\begin_inset Text
\begin_layout Plain Layout
Similar to
\family typewriter
ajaxForm
\family default
, but on the client side, the form is JSONified and the JSON content sent
to the server and processed by
\family typewriter
JsonHandler
\end_layout
\end_inset
</cell>
</row>
<row>
<cell alignment="center" valignment="top" topline="true" bottomline="true" leftline="true" usebox="none">
\begin_inset Text
\begin_layout Plain Layout
swappable
\end_layout
\end_inset
</cell>
<cell alignment="center" valignment="top" topline="true" bottomline="true" leftline="true" rightline="true" usebox="none">
\begin_inset Text
\begin_layout Plain Layout
Renders a span that contains one visible element and the other hidden.
When the visible element is clicked it will be hidden and the other one
will be shown
\end_layout
\end_inset
</cell>
</row>
</lyxtabular>
\end_inset
\end_layout
\begin_layout Section
Comet and Lift
\begin_inset CommandInset label
LatexCommand label
name "sec:COMET"
\end_inset
\end_layout
\begin_layout Standard
Figure
\begin_inset CommandInset ref
LatexCommand ref
reference "subfig:COMET-Application-Model"
\end_inset
diagrams the interaction between client and server in the Comet.
model.
There are several resources on the web that explain the history and specific
techniques related to Comet
\begin_inset Foot
status open
\begin_layout Plain Layout
\begin_inset CommandInset href
LatexCommand href
target "http://en.wikipedia.org/wiki/Comet_(programming)"
\end_inset
is a good start.
\end_layout
\end_inset
, so we won't get too detailed here.
In essence Comet is not a technology but a technique which allows a web
application to push messages from server to client.
There are a couple of approaches used to make this work, but the approach
that Lift uses is long polling, so that's what we'll be covering here.
As an example, consider a web chat application where you can chat real-time
with friends.
Let's take a quick look at how receiving a message using Comet works in
Lift:
\end_layout
\begin_layout Enumerate
The client sends an AJAX request to the server asking for any new messages.
\end_layout
\begin_layout Enumerate
The server does not respond immediately but waits until there is a message
that needs to be sent for that client.
\end_layout
\begin_layout Enumerate
When a message is available, the server responds to the initial request
from the client with the new message(s).
\end_layout
\begin_layout Enumerate
The client receives the response, processes it, and issues another AJAX
request, and the process continues.
\end_layout
\begin_layout Standard
Of course, things are more complicated then that.
For instance, it may take a while until the response is actually returned
to the client.
During this delay, the connection could be dropped for any number of reasons.
The client should be smart enough to re-establish the connection automatically.
But there is another problem - scalability.
If we have these long-running connections, the server would typically put
the processing threads into a waiting state until messages are available
to send back to the client.
Having many waiting threads is a scalability killer because numerous threads
from the web container’s thread pool will lie in the wait state doing nothing
until, before you know it, your entire thread pool is empty.
The immediate consequence is that your server can not do any other request
processing.
Because of this, a thread-per-connection approach combined with long-running
connections is totally unacceptable.
\end_layout
\begin_layout Standard
The key to scalability is NON-BLOCKING IO.
Most operating systems support non-blocking I/O, which actually means that
when you utilize an I/O resource for reading or writing (say the streams
from a socket) there is no blocking operation.
So if you read from a stream your read function would immediately return
regardless of whether there is data available or not.
In Java, non-blocking I/O is provided by the Java New I/O (NIO) library
using Selectors and perhaps the Reactor pattern
\begin_inset Foot
status open
\begin_layout Plain Layout
A nice overview of NIO is at
\begin_inset CommandInset href
LatexCommand href
target "http://gee.cs.oswego.edu/dl/cpjslides/nio.pdf"
\end_inset
\end_layout
\end_inset
.
This has a major impact on scalability because the threads are only held
as long as there is work to do.
Once they're finished with the available data, they are returned to the
thread pool so that they may be reused for processing other requests.
In this model the threads are allocated to connections only when data is
available for processing, which inherently leads to better resource utilization.
\end_layout
\begin_layout Standard
\begin_inset VSpace defskip
\end_inset
\end_layout
\begin_layout Standard
\align center
\begin_inset Box Shadowbox
position "t"
hor_pos "c"
has_inner_box 1
inner_pos "t"
use_parbox 0
width "75col%"
special "none"
height "1in"
height_special "totalheight"
status open
\begin_layout Plain Layout
Note: This is somewhat off-topic, but if you're looking to do a lot of work
with NIO and networking, we recommend looking at the Apache MINA project
at
\begin_inset CommandInset href
LatexCommand href
target "http://mina.apache.org/"
\end_inset
.
MINA provides some nice abstractions for NIO that allows you use a stateful
approach to developing NIO applications without having to deal with a lot
of the underlying details of using NIO.
\end_layout
\end_inset
\end_layout
\begin_layout Standard
\begin_inset VSpace defskip
\end_inset
\end_layout
\begin_layout Standard
Having nonblocking I/O enabled by the web container also has a major impact
on application scalability with regard to long-lived connections from client
to server.
In addition, the Lift framework has support for Jetty Continuations, which
work like this:
\end_layout
\begin_layout Enumerate
You application receives a request and wants to wait to respond, as there
is no message yet.
\end_layout
\begin_layout Enumerate
You call suspend on the Jetty Continuation object.
Here, Jetty will throw a special exception that will be caught in the container.
The current thread is immediately returned to the thread pool, so it can
process other requests.
\end_layout
\begin_layout Enumerate
Assume that, after a while, you have a message for that particular client.
You call resume on the same Continuation object.
This time, Jetty will actually replay the initial HTTP request, and your
servlet behaves like that request was just received from the client and,
of course, returns the appropriate response.
\end_layout
\begin_layout Standard
More details on Jetty’s Continuations are available on the Jetty web site
at
\begin_inset Flex URL
status collapsed
\begin_layout Plain Layout
http://docs.codehaus.org/display/JETTY/Continuations
\end_layout
\end_inset
.
\end_layout
\begin_layout Standard
If you run your Lift application in a Jetty container, Lift will automatically
detect that and utilize the Continuation mechanism.
Currently, on other containers, Comet in Lift will still work but won’t
scale as well because Continuations aren't supported.
However, the Servlet 3.0 spec contains a more generic facility, called Suspended
Requests, that will make this feature usable across a variety of containers.
\end_layout
\begin_layout Subsection
Actors in Scala
\end_layout
\begin_layout Standard
It is important to understand that Comet support in Lift is primarily driven
via Scala Actors.
We won't go into too much detail regarding Scala Actors, as you can find
very detailed information in the paper by Philipp Haller,
\emph on
Actors that Unify Threads And Events
\emph default
\begin_inset Foot
status open
\begin_layout Plain Layout
\begin_inset Flex URL
status open
\begin_layout Plain Layout
http://lamp.epfl.ch/~phaller/doc/haller07coord.pdf
\end_layout
\end_inset
\end_layout
\end_inset
.
\end_layout
\begin_layout Standard
Scala Actors are based on the concepts of the Erlang
\begin_inset Foot
status open
\begin_layout Plain Layout
\begin_inset CommandInset href
LatexCommand href
target "http://erlang.org/"
\end_inset
\end_layout
\end_inset
Actors model where an Actor is an asynchronous component that receives
messages and sends or replies to messages.
In Erlang, processes communicate via a very simple and effective messaging
system built into the VM.
\end_layout
\begin_layout Standard
In Scala, however, Actors are supported at the library level and not at
the language level.
While less integrated, this does provide greater flexibility as the Actors
library evolution does not impact the language itself.
Since Scala typically sits on top of the JVM, Scala Actors are not bound
to processes but rather to JVM threads.
The key to understanding the scalability of Scala Actors is that there
is no one-to-one relationship between Actors and Threads.
For instance, when an Actor is waiting for a message we don't end up having
a thread waiting for a lock.
Instead, the Actor body is impersonated by a closure that captures the
rest of the computation.
This closure is 'cached' internally until a message is designated for this
Actor to consume.
In particular, Scala's Actor library leverages the
\family typewriter
match
\family default
construct to allow very fine-grained selection of messages for processing.
Another interesting note is that the Actor body (
\family typewriter
react
\family default
function) never returns normally; in fact, the return type of the react
function is
\family typewriter
Nothing
\family default
.
\end_layout
\begin_layout Standard
Let's take a look on a simple Actor-based example in Listing
\begin_inset CommandInset ref
LatexCommand ref
reference "lst:PingPong-example"
\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
PingPong example
\begin_inset CommandInset label
LatexCommand label
name "lst:PingPong-example"
\end_inset
\end_layout
\end_inset
\end_layout
\begin_layout Plain Layout
import scala.actors._
\end_layout
\begin_layout Plain Layout
import scala.actors.Actor._
\end_layout
\begin_layout Plain Layout
\end_layout
\begin_layout Plain Layout
object PingPong extends Application {
\end_layout
\begin_layout Plain Layout
var count = 0;
\end_layout
\begin_layout Plain Layout
val pong = actor {
\end_layout
\begin_layout Plain Layout
loop {
\end_layout
\begin_layout Plain Layout
react {
\end_layout
\begin_layout Plain Layout
case Ping => println("Actor Pong Received Ping")
\end_layout
\begin_layout Plain Layout
sender ! Pong
\end_layout
\begin_layout Plain Layout
case Stop => println("Stopping Pong")
\end_layout
\begin_layout Plain Layout
exit()
\end_layout
\begin_layout Plain Layout
}
\end_layout
\begin_layout Plain Layout
}
\end_layout
\begin_layout Plain Layout
}
\end_layout
\begin_layout Plain Layout
val ping = actor {
\end_layout
\begin_layout Plain Layout
pong ! Ping
\end_layout
\begin_layout Plain Layout
loop {
\end_layout
\begin_layout Plain Layout
react {
\end_layout
\begin_layout Plain Layout
case Pong => println("Actor Ping Received Pong")
\end_layout
\begin_layout Plain Layout
count = count + 1;
\end_layout
\begin_layout Plain Layout
if (count < 3) {
\end_layout
\begin_layout Plain Layout
sender ! Ping
\end_layout
\begin_layout Plain Layout
} else {
\end_layout
\begin_layout Plain Layout
sender ! Stop
\end_layout
\begin_layout Plain Layout
println("Stopping Ping")
\end_layout
\begin_layout Plain Layout
exit()
\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
case object Ping
\end_layout
\begin_layout Plain Layout
case object Pong
\end_layout
\begin_layout Plain Layout
case object Stop
\end_layout
\end_inset
\end_layout
\begin_layout Standard
This is a trivial example in which we have two Actors exchanging
\family typewriter
Ping
\family default
,
\family typewriter
Pong
\family default
and
\family typewriter
Stop
\family default
messages (note that the messages are case objects for pattern matching
purposes).
Also note that we did not explicitly used threads anywhere.
We also did not use any thread blocking technique such as synchronized
blocks.
The reason is that we don't have to.
Actors' message-passing mechanism is generally thread-safe (although deadlock
is still possible due to dependent Actors
\begin_inset Foot
status open
\begin_layout Plain Layout
\begin_inset CommandInset href
LatexCommand href
target "http://ruben.savanne.be/articles/concurrency-in-erlang-scala"
\end_inset
\end_layout
\end_inset
).
Note that threads are used internally and in this specific example the
execution may even occur on the same thread.
The reason is that internally the Actors library uses a thread pool, and
when an Actor receives a message the execution occurs in a thread from
the thread pool.
This is also a key to Actors' scalability, because they allow threads to
be used very efficiently and returned to the pool as soon as the Actor
consumes the message.
\end_layout
\begin_layout Standard
Getting deeper into the details of actions is beyond the scope of this book,
but we recommend that you read other materials in order to fully understand
Scala actors.
In particular, Philipp Haller has a nice page summarizing papers and tutorials
on actors at
\begin_inset Flex URL
status collapsed
\begin_layout Plain Layout
http://lamp.epfl.ch/~phaller/actors.html
\end_layout
\end_inset
.
\end_layout
\begin_layout Subsection
Building a Comet Application in Lift
\end_layout
\begin_layout Standard
As we have seen, Comet support in Lift is provided by Scala Actors.
Lift greatly simplifies the use of Actors by providing a
\family typewriter
CometActor
\family default
trait that does almost all the work.
You simply extend
\family typewriter
CometActor
\family default
with your own class and fill in some implementation methods.
\end_layout
\begin_layout Standard
\align center
\begin_inset Box Shadowbox
position "t"
hor_pos "c"
has_inner_box 1
inner_pos "t"
use_parbox 0
width "75col%"
special "none"
height "1in"
height_special "totalheight"
status open
\begin_layout Plain Layout
Note that your CometActor classes needs to exist in a
\family typewriter
comet
\family default
subpackage as configured by
\family typewriter
LiftRules.addToPackages
\family default
.
For example, if you call
\family typewriter
LiftRules.addToPackages(
\begin_inset Quotes eld
\end_inset
com.myapp
\begin_inset Quotes erd
\end_inset
)
\family default
in your boot method, your comet actors must exist in the
\family typewriter
com.myapp.comet
\family default
package.
\end_layout
\end_inset
\end_layout
\begin_layout Standard
Let's take a look at a simple example.
Let's say that we want to build a Clock snippet where the server will update
the client page with the current server time every 10 seconds.
First, we need a template, as shown in Listing
\begin_inset CommandInset ref
LatexCommand ref
reference "lst:Comet-Clock-markup"
\end_inset
.
\end_layout
\begin_layout Standard
\begin_inset listings
lstparams "language=XML"
inline false
status open
\begin_layout Plain Layout
\begin_inset Caption
\begin_layout Plain Layout
Comet Clock markup example
\begin_inset CommandInset label
LatexCommand label
name "lst:Comet-Clock-markup"
\end_inset
\end_layout
\end_inset
\end_layout
\begin_layout Plain Layout
<lift:surround with="default" at="content">
\end_layout
\begin_layout Plain Layout
<lift:comet type="Clock" name="Other">
\end_layout
\begin_layout Plain Layout
Current Time: <clk:time>Missing Clock</clk:time>
\end_layout
\begin_layout Plain Layout
</lift:comet>
\end_layout
\begin_layout Plain Layout
</lift:surround>
\end_layout
\end_inset
\end_layout
\begin_layout Standard
In our template, we use the
\family typewriter
<lift:comet>
\family default
tag to bind the
\family typewriter
CometActor
\family default
to the portion of the template where it will render content, and the body
of the
\family typewriter
<lift:comet>
\family default
tag is quite similar to the body of a snippet.
The
\family typewriter
<clk:time>
\family default
tag will be bound by the
\family typewriter
Clock
\family default
actor.
The
\family typewriter
type
\family default
attribute tells Lift which
\family typewriter
CometActor
\family default
to call, and the
\family typewriter
name
\family default
attribute is the name of this
\family typewriter
CometActor
\family default
.
The
\family typewriter
name
\family default
attribute is a discriminator that allows you to have more then one
\family typewriter
CometActor
\family default
of the same type on a given page.
Next, we need to define our actor as shown in Listing
\begin_inset CommandInset ref
LatexCommand ref
reference "lst:Clock-Comet-Actor"
\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
Clock Comet Actor example
\begin_inset CommandInset label
LatexCommand label
name "lst:Clock-Comet-Actor"
\end_inset
\end_layout
\end_inset
\end_layout
\begin_layout Plain Layout
package com.myapp.comet
\end_layout
\begin_layout Plain Layout
\end_layout
\begin_layout Plain Layout
class Clock extends CometActor {
\end_layout
\begin_layout Plain Layout
override def defaultPrefix = Full("clk")
\end_layout
\begin_layout Plain Layout
\end_layout
\begin_layout Plain Layout
def render = bind("time" -> timeSpan)
\end_layout
\begin_layout Plain Layout
\end_layout
\begin_layout Plain Layout
def timeSpan = (<span id="time">{timeNow}</span>)
\end_layout
\begin_layout Plain Layout
\end_layout
\begin_layout Plain Layout
// schedule a ping every 10 seconds so we redraw
\end_layout
\begin_layout Plain Layout
ActorPing.schedule(this, Tick, 10000L)
\end_layout
\begin_layout Plain Layout
\end_layout
\begin_layout Plain Layout
override def lowPriority : PartialFunction[Any, Unit] = {
\end_layout
\begin_layout Plain Layout
case Tick => {
\end_layout
\begin_layout Plain Layout
println("Got tick " + new Date());
\end_layout
\begin_layout Plain Layout
partialUpdate(SetHtml("time", Text(timeNow.toString)))
\end_layout
\begin_layout Plain Layout
// schedule an update in 10 seconds
\end_layout
\begin_layout Plain Layout
ActorPing.schedule(this, Tick, 10000L)
\end_layout
\begin_layout Plain Layout
}
\end_layout
\begin_layout Plain Layout
}
\end_layout
\begin_layout Plain Layout
}
\end_layout
\begin_layout Plain Layout
case object Tick
\end_layout
\end_inset
\end_layout
\begin_layout Standard
First, our actor defines the default prefix, which should be used for all
nodes that will be bound inside
\family typewriter
<lift:comet>
\family default
tag.
In our case, we’re using the
\family typewriter
clk
\family default
prefix.
\end_layout
\begin_layout Standard
Next, we have the
\family typewriter
render
\family default
function where we do the binding between the
\family typewriter
<clk:time>
\family default
node and the result of the
\family typewriter
timespan
\family default
function.
Basically, the
\family typewriter
<clk:time>
\family default
node will be replaced by the
\family typewriter
span
\family default
element returned by the
\family typewriter
timespan
\family default
function.
It is important to note that Comet content rendered by the
\family typewriter
<lift:comet>
\family default
tag is a
\family typewriter
<span>
\family default
tag by default.
This default can be changed by overriding the
\family typewriter
parentTag
\family default
function in your comet actor.
\end_layout
\begin_layout Standard
\family typewriter
timeNow
\family default
is a function from the
\family typewriter
net.liftweb.util.TimeHelpers
\family default
trait that returns the current system time.
We use the
\family typewriter
net.liftweb.util.ActorPing.schedule
\family default
method to send a
\family typewriter
Tick
\family default
message back to our actor after 10 seconds.
This method is part of the the
\family typewriter
Clock
\family default
class default constructor, and therefore will be called when the
\family typewriter
Clock
\family default
class is instantiated.
\end_layout
\begin_layout Standard
Finally, we have the
\family typewriter
lowPriority
\family default
function that returns a
\family typewriter
PartialFunction
\family default
.
To process messages in your CometActor, you can override the following
functions:
\family typewriter
highPriority
\family default
,
\begin_inset Newline linebreak
\end_inset
\family typewriter
mediumPriority
\family default
, and
\family typewriter
lowPriority
\family default
.
This multiplicity of functions is just a way of prioritizing application
messages.
The only thing that we do here is to pattern match the messages.
In this simple example, we have only the
\family typewriter
Tick
\family default
object.
When a
\family typewriter
Tick
\family default
is sent by the
\family typewriter
ActorPing
\family default
, our code gets executed and the following actions occur:
\end_layout
\begin_layout Enumerate
We print the current time to the console (just for fun)
\end_layout
\begin_layout Enumerate
We call
\family typewriter
partialUpdate
\family default
function.
With a partial update we can update specific fragments on the client side
and not actually re-render the entire content that the CometActor may produce.
This optimization allows us to send something very specific to be updated
on the client side.
If we call
\family typewriter
reRender(true)
\family default
instead, the entire real estate on the client side will be re-rendered.
Getting back to our
\family typewriter
partialUpdate
\family default
call, we are basically sending a
\family typewriter
JsCmd
\family default
that we use to set the XHTML content for the element that has the id “time”.
This is the
\family typewriter
span
\family default
element returned by the
\family typewriter
timeSpan
\family default
function.
Since
\family typewriter
partialUpdate
\family default
takes a
\family typewriter
JsCmd
\family default
, you can use it to do just about anything on the client side accessible
from JavaScript.
\end_layout
\begin_layout Enumerate
We tell
\family typewriter
ActorPing
\family default
to send another Tick message after 10 seconds.
\end_layout
\begin_layout Standard
As you have seen, with just a few lines of code, we were able to create
a Clock application in which the server updates the client every 10 seconds.
Of course, this is just a trivial example, but now, you should have a clear
picture of how CometActor works, so you can build more complex cases for
your Lift application.
\end_layout
\begin_layout Standard
\begin_inset VSpace defskip
\end_inset
\end_layout
\begin_layout Standard
\align center
\begin_inset Box Shadowbox
position "t"
hor_pos "c"
has_inner_box 1
inner_pos "t"
use_parbox 0
width "75col%"
special "none"
height "1in"
height_special "totalheight"
status open
\begin_layout Plain Layout
Note: As described earlier It is also possible to use notices (notice/warning/er
ror) from your comet actor.
The CometActor trait already has notice, warning and error methods on it
that will properly handle sending these messages to the client.
Do not use the notice/warning/error methods on S, since they assume a stateful
response and will not work from within a Comet callback.
\end_layout
\end_inset
\end_layout
\begin_layout Section
Coordinating Between Multiple Comet Clients
\end_layout
\begin_layout Standard
So far, our example has only shown a self-contained
\family typewriter
CometActor
\family default
for the clock.
But what if we want to have interaction between different clients? Scala’s
actors are still the answer, but with a twist—we can use a singleton actor
object that coordinates with the CometActor objects so that it can send
messages to all of them.
First, we define our singleton actor, as shown in Listing
\begin_inset CommandInset ref
LatexCommand ref
reference "lst:Singleton-Actor"
\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
Singleton Actor
\begin_inset CommandInset label
LatexCommand label
name "lst:Singleton-Actor"
\end_inset
\end_layout
\end_inset
\end_layout
\begin_layout Plain Layout
case class SubscribeClock(clock : Clock)
\end_layout
\begin_layout Plain Layout
case class UnsubClock(clock : Clock)
\end_layout
\begin_layout Plain Layout
\end_layout
\begin_layout Plain Layout
object ClockMaster extends Actor {
\end_layout
\begin_layout Plain Layout
private var clocks : List[Clock] = Nil
\end_layout
\begin_layout Plain Layout
def act = loop {
\end_layout
\begin_layout Plain Layout
react {
\end_layout
\begin_layout Plain Layout
case SubscribeClock(clk) =>
\end_layout
\begin_layout Plain Layout
clocks ::= clk
\end_layout
\begin_layout Plain Layout
case UnsubClock(clk) =>
\end_layout
\begin_layout Plain Layout
clocks -= clk
\end_layout
\begin_layout Plain Layout
case Tick =>
\end_layout
\begin_layout Plain Layout
clocks.foreach(_ ! Tick)
\end_layout
\begin_layout Plain Layout
}
\end_layout
\begin_layout Plain Layout
}
\end_layout
\begin_layout Plain Layout
}
\end_layout
\end_inset
\end_layout
\begin_layout Standard
We've defined two case classes representing messages for subscribing and
unsubscribing to the
\family typewriter
ClockMaster
\family default
actor.
The
\family typewriter
ClockMaster
\family default
itself is a simple Actor (not a
\family typewriter
CometActor
\family default
) that defines a simple message loop.
It can either subscribe a new clock, unsubscribe to an existing clock,
or distribute a Tick to all subscribed clocks.
The other half of this equation slightly modifies our Clock class (as shown
in Listing
\begin_inset CommandInset ref
LatexCommand ref
reference "lst:Modified-Clock-Class"
\end_inset
) so that it subscribes and unsubscribes to the ClockMaster at initialization
and shutdown, respectively.
\end_layout
\begin_layout Standard
\begin_inset listings
inline false
status open
\begin_layout Plain Layout
\begin_inset Caption
\begin_layout Plain Layout
Modified Clock Class
\begin_inset CommandInset label
LatexCommand label
name "lst:Modified-Clock-Class"
\end_inset
\end_layout
\end_inset
\end_layout
\begin_layout Plain Layout
...
\end_layout
\begin_layout Plain Layout
override def localSetup {
\end_layout
\begin_layout Plain Layout
ClockMaster ! SubscribeClock(this)
\end_layout
\begin_layout Plain Layout
super.localSetup()
\end_layout
\begin_layout Plain Layout
}
\end_layout
\begin_layout Plain Layout
override def localShutdown {
\end_layout
\begin_layout Plain Layout
ClockMaster ! UnsubClock(this)
\end_layout
\begin_layout Plain Layout
super.localShutdown()
\end_layout
\begin_layout Plain Layout
}
\end_layout
\end_inset
\end_layout
\begin_layout Standard
Now, we can add an AJAX button (to an administration page, of course) that
would allow the administrator to update everyone’s clocks at once.
Listing
\begin_inset CommandInset ref
LatexCommand ref
reference "lst:The-Admin-Tick"
\end_inset
shows how we would bind in the button.
\end_layout
\begin_layout Standard
\begin_inset listings
inline false
status open
\begin_layout Plain Layout
\begin_inset Caption
\begin_layout Plain Layout
The Admin Tick
\begin_inset CommandInset label
LatexCommand label
name "lst:The-Admin-Tick"
\end_inset
\end_layout
\end_inset
\end_layout
\begin_layout Plain Layout
bind("admin", xhtml, "tick" ->
\end_layout
\begin_layout Plain Layout
SHtml.ajaxButton("Tock!", {
\end_layout
\begin_layout Plain Layout
() => ClockMaster ! Tick
\end_layout
\begin_layout Plain Layout
}))
\end_layout
\end_inset
\end_layout
\begin_layout Standard
Here’s what’s happening behind the scenes in our modified Clock application.
Lift first identifies a Comet request by matching against the path given
by the
\family typewriter
LiftRules.cometPath
\family default
variable.
Essentially the flow is as follows:
\end_layout
\begin_layout Enumerate
Lift gets a Comet request.
\end_layout
\begin_layout Enumerate
Lift checks the
\family typewriter
CometActor
\family default
s to see if there are any messages.
If there are no messages to be sent to this client, and the application
is running in a Jetty container, the Jetty continuation is suspended, but
no response is actually sent to client.
\end_layout
\begin_layout Enumerate
Later, when your Comet actor is asked to render or partially update, the
response is calculated, and the Jetty continuation is resumed.
\end_layout
\begin_layout Enumerate
When Lift gets the resumed request from the container it returns the response
calculated by the
\family typewriter
CometActor
\family default
to the client.
\end_layout
\begin_layout Standard
Note that
\family typewriter
CometActor
\family default
s work even if you are not using Jetty container; the only issue is that
you won’t benefit from the improved scalability of the suspend/resume mechanism
offered by the Jetty container.
\end_layout
\begin_layout Section
Summary
\end_layout
\begin_layout Standard
In this chapter, we explored how easily you can create AJAX and Comet interfaces
in Lift.
We discussed the underlying techniques used for AJAX and Comet, as well
as how Lift provides support functions and classes to simplify writing
apps that utilize these techniques.
We showed examples of how to use the
\family typewriter
SHtml
\family default
object to create AJAX-enabled form elements and how to customize things
like the AJAX request path in Lift.
We reviewed Scala actors and how the
\family typewriter
CometActor
\family default
trait is used to make a Comet event handler.
We also discussed how Lift works to alleviate scalability issues with Comet
on supported containers.
Finally, we wrote a simple Clock application and showed how you can mix
AJAX and Comet in the same application.
\end_layout
\end_body
\end_document
Jump to Line
Something went wrong with that request. Please try again.