After the specified delay, Lift will send a message to the CometTrendTopic
actor. In this case, Lift will call the overridden lowPriority
method to perform the update with the TrendTopic
case object as the message. It is possible to override any of the following methods that return a PartialFunction[Any, Unit]
to process messages in a CometActor
:
highPriority
mediumPriority
lowPriority
The different methods allow you to choose the most convenient priority for your message-processing function. The lowPriority
method just pattern matches the messages. In this case, a TrendTopic
case object is the only possible message to receive. When the CometTrendTopic
actor receives a TrendTopic
, the returned partial function performs many actions. First, the code calls the CometActor.partialUpdate
method to update the comet component with a net.liftweb.http.js.JsCmd
(a JavaScript command). All the listening browser tabs will receive the JsCmd
and perform the update. Because partialUpdate
receives a JsCmd
, you can use it to do anything that can be done on the client side with JavaScript. The SetHtml
case class receives the ID of the node whose content must be replaced and the new content. The following line replaces the content of the node whose ID is equal to hottest
with a random string:
partialUpdate(SetHtml("hottest", Text(if (randomInt(2) equals 1) "Functional Programming" else "Lift Web Framework")))
The new line calls partialUpdate
and uses the SetHtml
case class to replace the content of the node whose ID is equal to hottest
with a the current time:
partialUpdate(SetHtml("time", Text(now.toString)))
Finally, the following line sends another TrendTopic
message after five seconds. This way, both the trend topic and the current time will be updated every five seconds. Comet is extremely powerful and Lift does a lot of work under the hood to allow you to focus on the messages that a CometActor
has to process and the updates that need to be made to the NodeSeq
. If you've ever had to do something similar with other frameworks that don't provide features to simplify server push updates, you will understand how much Lift really eases your workload with Comet and the actor model.
Declaring Interdependent Items with Lift's Wiring
Lift's Wiring enables you to declare interdependent items so that when any of the related elements change, the dependent elements update automatically. For example, if you want to create a rectangle area calculator in a spreadsheet, you would use one cell for the length value and another cell for the width value, and then you write a formula to display the results of the width multiplied by the length. Whenever you update the length value, the spreadsheet will update the calculated value. You can declare the same formulaic relationship between cells with Lift's Wiring.
The following listing shows the code for a new RectangleAreaWiring.scala
file that defines the RectangleAreaWiring
. Notice that the code is included in the code.snippet
package, which is the default package for snippets based on the rules I defined in Boot.scala
in the previous article. You must place the file in src/main/scala
.
package code package snippet import scala.xml.{NodeSeq, Text} import net.liftweb._ import net.liftweb.util._ import net.liftweb.common._ import code.lib._ import http._ import SHtml._ import Helpers._ import util._ import js._ import js.JsCmds._ import js.jquery._ class RectangleAreaWiring { private object RectangleInformation { val length = ValueCell(0d) val width = ValueCell(0d) val area = length.lift(width) {_ * _} } def length = ajaxText(RectangleInformation.length.get.toString, doubleToJsCmd(RectangleInformation.length.set)) def width = ajaxText(RectangleInformation.width.get.toString, doubleToJsCmd(RectangleInformation.width.set)) def area = WiringUI.toNode(RectangleInformation.area, JqWiringSupport.fade)(doubleDraw) private def doubleDraw: (Double, NodeSeq) => NodeSeq = (d, ns) => Text(java.text.NumberFormat.getNumberInstance.format(d)) private implicit def doubleToJsCmd(in: Double => Any): String => JsCmd = str => { asDouble(str).foreach(in) } }
The RectangleAreaWiring
class defines a RectangleInformation
private object that declares the cells and the relationships between them. RectangleInformation
defines two net.liftweb.util.ValueCell[Double]
variables: length
and width
, with an initial value of 0. A ValueCell
holds a value that can be mutated; therefore, the values for both length
and width
can change.
RectangleInformation
also defines a net.liftweb.util.Cell[Double]
variable: area
. The call to the length.lift
method creates a new Cell
by applying a function to the length
ValueCell
. The val area = length.lift(width) {_ * _}
applies a multiplication to length
and width
to calculate the value for the area
Cell
. This way, whenever either the length
or width
ValueCell
instances change, area
recalculates the value. Lift assigns a FuncCell
to area
because it has a value based on a formula applied to other cells.
I want the user to enter the values for both length
and width
, so ValueCell
instances are the appropriate type of Cell
s. The area value is based on a formula applied to the value of length
and width
, so a FuncCell
is the appropriate type of Cell
. Lift provides another type of Cell
, a DynamicCell
, and you can use it when you need to hold a value that changes every time the cell is accessed (as in a random number).
The RectangleInformation
private object defines all the cells and their relationships. Then, one method per cell performs the necessary UI wiring. The following code makes the UI wiring for length
and uses one of the Ajax generator methods included on the net.liftweb.http.SHtml
object, ajaxText
. This method renders an input text element that sends an Ajax request on blur. To set the value, the code uses the doubleToJsCmd
method that converts a double value to a JsCmd
. The method isn't part of any official Lift helpers, but it has been included by David Pollak, Lift's founder, in many Lift documentation examples and is really useful.
def length = ajaxText(RectangleInformation.length.get.toString, doubleToJsCmd(RectangleInformation.length.set))
The following line does the same UI wiring for width
:
def width = ajaxText(RectangleInformation.width.get.toString, doubleToJsCmd(RectangleInformation.width.set))
Finally, the following line defines the UI wiring for area
by calling the toNode
method of the net.liftweb.http.WiringUI
object. The method registers the postPageJavaScript
that will update the element with a new value given a cell and a function that can generate a NodeSeq
from the Cell
's value and the template value. In this case, I've used the JqWiringSupport.fade
JavaScript effect for the update. This way, when the area
field updates, its old value fades out, and the new value fades in.
def area = WiringUI.toNode(RectangleInformation.area, JqWiringSupport.fade)(doubleDraw)
The following lines show the new version of the /src/main/webapp/index.html
view that displays the rectangle area calculator with the two fields for length and width. Both fields are defined as input
elements with a class that binds them to the methods exposed by the RectangleAreaWiring
snippet class. The area is defined as a span
element with a class that binds it to the
area
method exposed by the same class.
<!DOCTYPE html> <html> <head> <meta content="text/html; charset=UTF-8" http-equiv="content-type" /> <title>Home</title> </head> <body class="lift:content_id=main"> <div id="main" class="lift:surround?with=default;at=content"> <h2>Rectangle area calculator</h2> <div> <div>Length: <input class="lift:RectangleAreaWiring.length"></div> <div>Width: <input class="lift:RectangleAreaWiring.width"></div> <div>Rectangle area: <span class="lift:RectangleAreaWiring.area">Area</span> (square units)</div> </div> </div> </body> </html>
Now, when you navigate to http://localhost:8080/index to view the Web page with the rendered dynamic content of the snippets, you will see two fields for length
and width
and the calculated area. If you enter a value for length and then for width, you will see how the fade effect is applied to the area old value and that the result is shown (Figure 3). Obviously, I've used a very simple example, but you can take full advantage of cells to build complex Web applications. Lift's Wiring is really very useful and the code is easy to maintain.
Figure 3: The index.html view displaying the calculated area for the rectangle based on the values of length and width.