The total chat app, including the ask/answer component for soliciting a name comments, etc. is listed on this page. There is no special code to support AJAX/Comet (all the wrapping is done automatically by Lift).
When the Chat comet widget is added to the page, it needs to solict the user for a "chat name". It asks the "AskName" comet widget for the name. Until the AskName comet widget provides a name, all rendering messages are forwarded to AskName. Here's the code for the "AskName":
class AskName extends CometActor { def render = ajaxForm(<div>What is your username?</div> ++ text("",name => answer(name.trim)) ++ <input type="submit" value="Enter"/>) }
When the user submits the form, the question asked by the Chat comet widget is answered with the value the user submitted. This is similar to the ask/answer paradigm in Seaside, except that there's no need for continuations.
Now, onto the heart of the chat app:
class Chat extends CometActor with CometListener { private var userName = "" private var chats: List[ChatLine] = Nil private lazy val infoId = uniqueId + "_info" private lazy val infoIn = uniqueId + "_in" private lazy val inputArea = findKids(defaultXml, "chat", "input") private lazy val bodyArea = findKids(defaultXml, "chat", "body") private lazy val singleLine = deepFindKids(bodyArea, "chat", "list") // handle an update to the chat lists // by diffing the lists and then sending a partial update // to the browser override def lowPriority = { case ChatServerUpdate(value) => val update = (value -- chats).reverse.map(b => AppendHtml(infoId, line(b))) partialUpdate(update) chats = value } // render the input area by binding the // appropriate dynamically generated code to the // view supplied by the template override lazy val fixedRender: Box[NodeSeq] = ajaxForm(After(100, SetValueAndFocus(infoIn, "")), bind("chat", inputArea, "input" -> text("", sendMessage _, "id" -> infoIn))) // send a message to the chat server private def sendMessage(msg: String) = ChatServer ! ChatServerMsg(userName, msg.trim) // display a line private def line(c: ChatLine) = bind("list", singleLine, "when" -> hourFormat(c.when), "who" -> c.user, "msg" -> c.msg) // display a list of chats private def displayList(in: NodeSeq): NodeSeq = chats.reverse.flatMap(line) // render the whole list of chats override def render = bind("chat", bodyArea, "name" -> userName, AttrBindParam("id", Text(infoId), "id"), "list" -> displayList _) // setup the component override def localSetup { askForName super.localSetup } // register as a listener def registerWith = ChatServer // ask for the user's name private def askForName { if (userName.length == 0) { ask(new AskName, "what's your username") { case s: String if (s.trim.length > 2) => userName = s.trim reRender(true) case _ => askForName reRender(false) } } } }
This example demonstrates the power of Scala's Actors and Lift. With very few lines of code, we've got a complete AJAX/Comet app that has Seaside style Ask/Answer for building modal dialogs.