Channels ▼
RSS

Web Development

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


In the first installment in this two-part series on the Lift Web Framework, I explained how to set up a project with Lift and how to work with the framework's concept of snippets. In this article, I explain how take advantage of lazy loading, parallel execution, Comet server pushes, wiring, and REST support.

Rendering Snippets with Lazy Loading

Sometimes, a snippet takes a long time to render and you don't want to wait for it to return a Web page to the Web browser. Lift supports lazy loading of snippets to assist you in this scenario. You need only add some simple markup to a snippet and Lift will do the necessary work to return the Web page to the Web browser quickly, displaying a spinning icon until the snippet finishes computation, and then delivers the necessary HTML to the Web browser.

The following listing shows a new version of the TrendTopic.hottest method with a line that calls Thread.Sleep to simulate a computation that requires three seconds:

class TrendTopic {
  def hottest: CssSel = {
    Thread.sleep(3000)
    "#hottest *" #> "Functional programming"
  }

}

The next listing shows a new version of the Game.render method with a line that calls Thread.Sleep to simulate a computation that requires two seconds. (This way, each snippet that is rendered in the view takes a different amount of time to perform the computation and so will demonstrate visually how lazy loading works.)

class Game {
  def render(in: NodeSeq): NodeSeq = {
    Thread.sleep(2000)
    val cssSel = "#gameName *" #> "Invaders" &
    			 "#highestScore *" #> randomInt(15000)
    cssSel(in)    			 
  }

}

Now that the two methods invoked by the snippets in the index.html view require a few seconds to be processed, the user would have to wait a long time for the page to be rendered in the Web browser. Thus, you need to enclose the markup that executes the snippet within the following markup to instruct Lift to use lazy loading for the snippet:

<div data-lift="lazy-load">
</div>

The following lines show the new version of the /src/main/webapp/index.html view that renders the two snippets with lazy loading:

<!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>The hottest trend topic</h2>
			<p>
				<div data-lift="lazy-load">
					<span class="lift:trendTopic.hottest"><span id="hottest">Hottest trend topic goes here</span> </span>
				</div>
			</p>

			<h2>The highest scores</h2>
			<p>
				<div data-lift="lazy-load">
					<span data-lift="game"><span id="gameName">Game's name goes here</span> <span id="highestScore">Highest score goes here</span> </span>
				</div>
			</p>

		</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 spinning icons because the methods are sleeping for a few seconds (see Figure 1).

Lift Web Framework
Figure 1: The index.html view displaying the spinning icons for the two snippets with lazy loading.

After two seconds, the Game.render method stops sleeping and Lift will deliver the resulting HTML for the snippet to the Web browser with the game name and the highest score. The TrendTopic.hottest method goes on sleeping for a while longer, so the spinning icon for the snippet will still be visible (see Figure 2). Finally, Lift delivers the resulting HTML for the snippet and you will see the same content as in the previous version without lazy loading.

Lift Web Framework
Figure 2: The index.html view displaying the results for one of the snippets with lazy loading.

Rendering Snippets with Parallel Execution

When snippets take a long time to render, you can also instruct Lift to execute specific snippets in parallel during the rendering of the Web page. Lift will fork off an additional thread for each snippet that must be rendered in parallel, and when all the parallel renders finish, Lift sends the final rendered page to the Web browser.

As you might guess, you just need to add some simple markup that sets the lift:parallel attribute to true and Lift will do the necessary work to execute each snippet that requires parallel execution in a different thread.

The following lines show the new version of the /src/main/webapp/index.html view that renders the two snippets with parallel execution in two different threads:

<!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>The hottest trend topic</h2>
			<p>
				<span class="lift:trendTopic.hottest?parallel=true"><span id="hottest">Hottest trend topic goes here</span> </span>
			</p>

			<h2>The highest scores</h2>
			<p>
				<span data-lift="game?parallel=true"><span id="gameName">Game's name goes here</span> <span id="highestScore">Highest score goes here</span> </span>
			</p>

		</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 only have to wait about three seconds to view the results because Lift renders both snippets in parallel. If you change the value of the lift:parallel attribute to false, you will notice that the Web page will require at least five seconds to appear in the Web browser because one snippet requires two seconds and the other requires three seconds to render.

Working with Comet Server Pushes

You often need to update something like the "hottest trend" topic in a Web page as the real-time information changes. I'll take advantage of Comet's server pushes to demonstrate how you can write just a few lines of code to easily update content when something changes.

The following listing shows the code for a new CometTrendTopic.scala file that defines the CometTrendTopic class with three methods: defaultPrefix, render, and lowPriority. Notice that the code is included in the code.comet package, which is the default package for Comet actors based on the rules I defined in Boot.scala in the previous article. The file also defines the TrendTopic case object. You must place the file in src/main/scala. I've included the most frequently used imports for new classes that extend the CometActor trait.

package code
package comet

import net.liftweb._
import http._
import SHtml._ 
import net.liftweb.common.{Box, Full}
import net.liftweb.util._
import net.liftweb.actor._
import net.liftweb.util.BindPlus._
import net.liftweb.util.Helpers._
import net.liftweb.http.js.JsCmds.{SetHtml}
import net.liftweb.http.js.JE.Str
import _root_.scala.xml.{Text, NodeSeq}


class CometTrendTopic extends CometActor {
  override def defaultPrefix = Full("trend") 
  
  def render = bind("hottest" -> <span id="hottest">No hot trendtopics yet!</span>, "time" -> <span id="time">No updates yet!</span>)

  // Schedule an update every 5 seconds
  Schedule.schedule(this, TrendTopic, 5 seconds)

		
  override def lowPriority = {
    case TrendTopic => {
      // Update the hottest trend topic
      partialUpdate(SetHtml("hottest", Text(if (randomInt(2) equals 1) "Functional Programming" else "Lift Web Framework")))
      // Update the latest time the trend topic was set
      partialUpdate(SetHtml("time", Text(now.toString)))
      // Schedule an update every 5 seconds
      Schedule.schedule(this, TrendTopic, 5 seconds)
    }
  }
}

case object TrendTopic

The following lines show the new version of the /src/main/webapp/index.html view that renders a Comet component of type CometTrendTopic to display the hottest trend topic, which the server will update with a push every five seconds:

<!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>The hottest trend topic</h2>
			<p>
				<div class="lift:comet?type=CometTrendTopic">
					<trend:hottest>Hottest trend topic goes here</trend:hottest>
					<br/>Last update: <trend:time>Last update time goes here</trend:time>				
				</div>
			</p>

			<h2>The highest scores</h2>
			<p>
				<span data-lift="game"><span id="gameName">Game's name goes here</span> <span id="highestScore">Highest score goes here</span> </span>
			</p>

		</div>
	</body>
</html>

The markup within the div of the lift:comet?type=CometTrendTopic class defines two placeholders for the updates that the Comet component performs through binding: trend:hottest and trend:time. The prefix for both placeholders is trend. CometTrendTopic extends the net.liftweb.http.CometActor trait and overrides the defaultPrefix method to return the default prefix string, trend, in a full box.

The render method returns a scala.xml.NodeSeq by returning the result of two chained calls to the CometActor.bind method. Notice the nice syntax to replace trend:hottest with a span named hottest and trend:time with a span called time:

def render = bind("hottest" -> <span id="hottest">No hot trendtopics yet!</span>, "time" -> <span id="time">No updates yet!</span>)

Lift uses many helpers to make it easy for you to bind HTML elements to the placeholders you define in the markup for your Comet component. It is easy to read and understand the HTML that the code is binding to the hottest and time placeholders because it is the same HTML you would write in the HTML editor (without quotation marks). You can also use the CSS selector transforms I introduced in the first article in this series. However, in this case, I wanted to show you another way of building a NodeSeq.

Schedule.schedule(this, TrendTopic, 5 seconds) calls the net.liftweb.util.Schedule.schedule method to schedule an update every five seconds. The target actor is this, the message is the TrendTopic case object, and the delay is five seconds. Notice the convenient way of specifying the 5 seconds timespan. The delay parameter is of type net.liftweb.util.Helpers.TimeSpan. A TimeSpanBuilder case class allows you to build a TimeSpan with an amount and a method that specifies the time unit. Thus, 5 seconds is equivalent to 5.seconds. You might specify 1 second because second is also defined. If you still haven't fallen in love with Scala, Lift provides many features that might make that happen.


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.
 

Video