One of Lift's key features in Comet (or server push) support and associated Ajax support. This page contains a multi-user chat application with Comet (server-push) that updates the browser when anyone sends a chat message. There's an input box that allows the user to send a line into the chat and that's done via an asynchronous call to the server (the page is not reloaded as part of the call).
Listing:
/comet.html
<ul class="lift:comet?type=Chat">
<li>Line 1</li>
<li class="clearable">Line 2</li>
<li class="clearable">Line 3</li>
</ul>
We simply define the markup and mark the <ul> tag
with a snippet invocation that loads the Chat
Comet component. Let's look at the Chat
component.
Listing:
/net/liftweb/seventhings/comet/Chat.scala
/**
* The comet chat component
*/
class Chat extends CometActor with CometListener {
private var msgs: Vector[String] = Vector() // private state
// register this component
def registerWith = ChatServer
// listen for messages
override def lowPriority = {
case v: Vector[String] => msgs = v; reRender()
}
// render the component
def render = ClearClearable & "li *" #> msgs
}
Once again, something that's hard or impossible
in other web frameworks is is trivial in Lift.
The developer doesn't worry about the plumbing of how
the browser polls for changes. By default Lift uses
long polling and multiplexes multiple comet components
into a single long poll, but when web sockets are
standardized, Lift will automatically support them
without requiring any code changes. The developer
focuses on the semantics of "when this changes on the
server, update the component" and Lift takes care of the
rest.
Note that you can attempt to attack the site with
a cross site scripting attack (e.g., type <script>alert('I ownz your machine')</script> into the chat box). You'll see that Lift
properly escapes the input without any developer intervention.
Let's take a look at the markup for the Ajax input form:
Listing:
/comet.html
<form class="lift:Form.ajax">
<input class="lift:ChatIn" id="chat_in">
<input type="submit" value="Chat">
</form>
We designate the form as Ajax with the Form.ajax
snippet. The text input invokes the ChatIn
snippet. Let's look at that snippet:
Listing:
/net/liftweb/seventhings/snippet/ChatIn.scala
/**
* Handle input by sending the input line
* to the ChatServer
*/
object ChatIn {
// max count per session
private object lineCnt extends SessionVar(0)
def render =
"*" #> SHtml.onSubmit(s => {
if (s.length < 50 && lineCnt < 20) { // 20 lines per session
ChatServer ! s // send the message
lineCnt.set(lineCnt.is + 1)
}
SetValById("chat_in", "") // clear the input box
})
}
Once again, we didn't need to do any plumbing. Lift
takes care of associating the behavior (sending the input
String to the ChatServer.) We didn't
set up routes or anything else. Further, this code is
resistant to replay attacks because the automatically
generated routes are dynamic and random and cannot
be predicted. And in case you're worried about doing
things via standard REST calls, Lift has
excellent REST support.
And Lift supports Comet for data as well as
HTML, so you can use Lift to serve your
Cappuccino,
SproutCore, ExtJS, etc. apps and get full Comet support.
