Channels ▼

Web Development

Building Web Apps with Lift: Lazy Loading, Push, and Parallel Execution

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,
    def width = ajaxText(RectangleInformation.width.get.toString,
    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 Cells. 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,

The following line does the same UI wiring for width:

  def width = ajaxText(RectangleInformation.width.get.toString,

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>
		<meta content="text/html; charset=UTF-8" http-equiv="content-type" />
	<body class="lift:content_id=main">
		<div id="main" class="lift:surround?with=default;at=content">
			<h2>Rectangle area calculator</h2>
				<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>

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.

Lift Web Framework
Figure 3: The index.html view displaying the calculated area for the rectangle based on the values of length and width.

Related Reading

More Insights

Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.