Channels ▼
RSS

Web Development

Building Web Applications with Lift


Having explored the workings of Lift's Box, you can understand the usage of Full when the previously shown code sets the values of both LiftRules.ajaxStart and LiftRules.ajaxEnd. The following line shows a simple example of how to create a full Box with a String value:

  val fullBox = Full("I have a String")

As you might guess, if you leave both LiftRules.ajaxStart and LiftRules.ajaxEnd with an Empty value, Lift will disable the execution of JavaScript at the beginning of an Ajax request and when it ends. Lift favors heavy use of Box, so it is a good idea to use it instead of Option when you work on a Lift Web application. This approach maintain consistency with the framework philosophy.

The following line establishes a rule that sets the default character encoding for requests to UTF-8 instead of the default platform encoding:

  LiftRules.early.append(_.setCharacterEncoding("UTF-8"))

In previous versions, Lift treated templates as XHTML and generated XHTML to the Web browser. However, Lift 2.2 introduced support for HTML 5, so you can define templates in HTML 5 and Lift will generate HTML 5 to the Web browser. (Obviously, it is convenient to work with HTML 5.) Because the default rule was to use XHTML for backward compatibility with previous Lift versions, it is necessary to add the following rule to use HTML 5 for both the parser and serializer:

  LiftRules.htmlProperties.default.set((r: Req) =>
    new Html5Properties(r.userAgent))

Finally, the boot method initializes the jQuery module. Lift includes a bundled version of jQuery, but you easily replace it with your desired version. There is also some code to build the SiteMap that defines navigation and access control. However, I'll leave Lift's SiteMap feature for later discussion and focus on Lift snippets.

Working with Lift Snippets to Inject Dynamic Content

The lift_blank project includes a basic class in the code.snippet package called HelloWorld. I won't use that class; instead, I'll add two other classes to demonstrate two different ways of generating dynamic content for a single view.

The Listing Two shows the code for a new TrendTopic.scala file that defines the TrendTopic class with a hottest method. You must place the file in src/main/scala. I've included the most frequently used imports when you write Lift snippets.

Listing Two.

package code
package snippet

import scala.xml.{NodeSeq, Text}
import net.liftweb.util._
import net.liftweb.common._
import code.lib._
import Helpers._

class TrendTopic {
  // Replace the contents of the element with id "hottest" with the hottest trend topic
  def hottest = {
    //I'll uncomment the following line later 
    //to demonstrate parallel execution and lazy loading features
    //Thread.sleep(3000)
    "#hottest *" #> "Functional programming"
  }

}

Listing Three shows the code for a new Game.scala file that defines the Game class with a render method. You must also place the file in src/main/scala.

Listing Three.

package code
package snippet

import scala.xml.{NodeSeq, Text}
import net.liftweb.util._
import net.liftweb.common._
import code.lib._
import Helpers._
import net.liftweb._
import http._

class Game {
  // Replace the contents of the elements with id "gameName" and "highestScore" 
  // with the game name received as a parameter and its highest score
  def render(in: NodeSeq): NodeSeq = {
    //I'll uncomment the following line later 
    //to demonstrate parallel execution and lazy loading features
    //Thread.sleep(2000)
    val cssSel = "#gameName *" #> "Invaders" &
    			 "#highestScore *" #> randomInt(15000)
    cssSel(in)    			 
  }

}

In /src/main/webapp, you will find an index.html view that includes code to render dynamic content generated by the HelloWorld class. The following lines replace the content of index.html with a new view that includes the necessary markup code to render pieces of dynamic content with the previously added TrendTopic and Game Scala classes.

<!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"><span id="hottest">Hottest trend topic goes here</span> </span>
			</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>

I've reused the index.html view because it is already included in the SiteMap, so you can easily type container:start in SBT and navigate to http://localhost:8080/index to view the Web page with the rendered dynamic content of the snippets (see Figure 3).

Lift Web Framework
Figure 3: The index.html view displaying the results of rendering the two snippets.

The view is an HTML page with many specific class attributes:

  • <body class="lift:content_id=main">: Indicates that the element with id equal to main contains the actual page content.
  • <div id="main" class="lift:surround?with=default;at=content">: Defines the element with the actual page content, invokes a Lift snippet that surrounds the div with a template named default, and inserts this div and its children at the element with id equal to content in the default template. The markup makes Lift wrap the default chrome around the div.

Within the div with id equal to main, there are two markups that render content provided by Lift snippets with a different syntax:

  • <span class="lift:trendTopic.hottest"><span id="hottest">Hottest trend topic goes here</span> </span>: The class attribute lift:trendTopic.hottest makes Lift create an instance of the TrendTopic class and execute its hottest method to transform the HTML included in the span. A class attribute with lift:mySnippet will look for the MySnippet class and call the default method by convention: render. You can specify the desired method when you don't want to use render. For example, lift:mySnippet.run will look for the MySnippet class and call the run method. You can also use a prefix of l: instead of lift:, so l:mySnippet.run is equivalent to lift:mySnippet.run. Notice that the markup defines a span with an id equal to hottest within the main span. The hottest method will replace the contents of this span.
  • <span data-lift="game"><span id="gameName">Game's name goes here</span> <span id="highestScore">Highest score goes here</span> </span>: The class attribute that I described earlier is the preferred mechanism to invoke Lift snippets. However, you can find Lift applications that rely on the data-lift attribute to perform the same action. The data-lift attribute game makes Lift create an instance of the Game class and call the default method by convention: render. With the data-lift attribute, you can also specify the desired method. For example, data-lift="game.run" will look for the Game class and call the run method. Notice that the markup defines a span with an id equal to highestScore within the main span. The render method will replace the contents of this span.

The hottest method of the TrendTopic class replaces the contents of the element with an id equal to hottest with the hottest trend topic, "Functional Programming." The method is just a single line. In the original content, I included an additional line because I'll use the same snippet for other cases. However, the hottest method could be defined with any of the following equivalent lines:

  def hottest = "#hottest *" #> "Functional programming"
  def hottest: CssSel = "#hottest *" #> "Functional programming"

The method creates a CSS selector transform of type net.liftweb.util.CssSel that puts the "Functional programming" String value in the element whose id is equal to hottest and was part of the span that invoked the snippet. In this case, after the method returns the CSS selector transform, Lift produces the resulting scala.xml.NodeSeq (DOM) output. The #> method transforms a NodeSeq (DOM) based on rules. Snippet methods take the original template XML, process it, and return the new processed version.

The render method of the Game class replaces the contents of two elements with the following ids: gameName and highestScore. In this case, the method takes a NodeSeq as input and returns a NodeSeq output by applying a specified CSS selector transform to the input:

  def render(in: NodeSeq): NodeSeq = {
      val cssSel = "#gameName *" #> "Invaders" &
                   "#highestScore *" #> randomInt(15000)
      cssSel(in) 
  }

Notice the use of the & method to combine the CSS selector transforms. Lift takes advantage of the way you can name and call methods in Scala to easily build a DSL with CSS selector transforms. Once you get used to the symbols of the different methods, you will find the code easy to write, maintain, and read.

The in parameter of the render method contains the element that includes the snippet call with all its children as a sequence of XML elements (a NodeSeq). The render method uses the CSS selector transforms to process the NodeSeq received as an input, and returns a new NodeSeq that Lift will use to replace the original content. Of course, I've provided a very simple example of the way you can work with snippets in Lift. However, I'm sure that you'll see how easy it is to do whatever you want with the content you receive as input.

Obviously, processing the sequence of XML elements and returning the output will usually require a greater effort and more time than in this example. For more complex scenarios, Lift provides interesting features such as parallel execution and lazy loading. I'll explore them in the next article, next week. In addition, trend topics and highest scores usually change after a few minutes. Thus, they represent a great opportunity to explore server pushes in Lift with Comet. I'll also dive deep on Comet, wiring, and REST support in the next installment. This way, you will have a good idea of many important features offered by Lift and you can decide whether it is a good fit for any of your Web projects based on Scala.


Gaston Hillar is a frequent contributor to Dr. Dobb's.


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