Channels ▼
RSS

Web Development

Building Web Applications with Lift


The Lift Web Framework, which recently released v. 2.5.1, is one of the most popular and powerful Web frameworks. Lift, which is written in Scala, takes full advantage of many of the language's features including its straightforward construction of Domain-Specific Languages (DSLs), built-in support for the Actor model and asynchronous messaging, and excellent scalability. However, Lift doesn't take the typical Model-View-Controller (MVC) approach, and its unique model is worth learning about, even if you don't plan to use it. In this first article in a short series dedicated to Lift, I'll explain some of these interesting features.

A Complex Web Page Is More Than a Single HTTP GET

The main goal of Lift was to make sure that static templates are strictly used for display only; thus, designers can manipulate them using standard design tools without undesired noise. With Lift, static templates can never contain business logic.

Lift uses an approach called "View First" to bind logic to templates. In MVC frameworks, a specific URL triggers a controller method. As you might guess, in Lift, the view comes first, so a URL is bound to a specific page and there is no particular component on the page that is dominant. Once Lift checks that access control for a specific URL is granted, it resolves the path to a related and localized version of the view and loads it. Lift processes the view as a template to transform it into the dynamic content you want to return in response to the URL request. The template is a valid HTML page with makers that tell Lift how to interpret some pieces. Editing and maintaining the pages using design tools doesn't cause any problems.

Lift's design treats a complex Web page as more than a single HTTP GET, which enables it to handle pages using its View First approach. One of the main problems of most Web frameworks is that they treat a Web page as a single HTTP GET, but this approach works well only for the simplest pages. When you start working on complex Web pages, the HTTP GET approach isn't an ideal fit and you end up with different pieces of code everywhere without good support at the framework level for the evolving requirements.

To simplify rendering complex Web pages, Lift provides the following features out of the box:

  • Ajax. Lift makes it easy to work with Ajax forms and multipage input screens (wizards). You don't need to leave Scala to work with Ajax behavior. You can get rid of unnecessary JavaScript or jQuery in your views.
  • Comet. Server push updates are made easy and take advantage of Scala actors. You need only take care of updating the component when something changes. It's really easy to push content from the server to the Web browser.
  • Parallelized rendering. With just a very simple change in the markup code, you can instruct Lift to render different snippets required for a single Web page in parallel.
  • Lazy loading. You can use specific markup code to instruct Lift to display a spinning icon until a snippet finishes computation and delivers the HTML to the Web browser. You don't need to make changes to your code to provide support for lazy loading, you just need to activate it when you consider it necessary.
  • Wiring. Lift borrows a common practice from spreadsheets and allows you to declare interdependent items. When any of the related elements change, the dependent elements update automatically. If you have ever coded a Shopping Cart, you can imagine how much simpler it can be to update totals in many places with a framework that provides this feature.

If you are accustomed to working with MVC frameworks, you'll find it easy to understand Lift after you shift your view to its unique implementation. First, you need to focus on the view and all the content you want to be displayed in a Web page. A single view can include the necessary markup code to render many pieces of dynamic content in Scala methods. Then, you can decide the best way to render the dynamic content based on your performance needs and the desired user experience. For example, if the rendering takes too much time, it might be a good idea to take advantage of lazy loading or parallelized rendering. I'll provide easy-to-understand examples that use these features in this series.

Working with Lift in Eclipse

To start working with Lift, you just need to download the latest release. Decompress the download file and go to the scala_210 folder where there are four subfolders that target Scala 2.10:

  • lift_basic
  • lift_blank
  • lift_json
  • lift_mvc

The simplest project is included in the lift_blank subfolder. From this subfolder, you can launch the Simple Build Tool (also known as SBT). In Linux, execute ./sbt; and in Windows, sbt.bat. The scala_29 folder contains the same four projects but targeting the earlier Scala 2.09; however, I always want to work with the latest version.

Once you launch SBT, type the container:start command and — after SBT downloads and updates the necessary content — you will be able to go to http://localhost:8080 to see the home page of the blank Lift application (Figure 1).

Lift Web Framework
Figure 1: The home page of the lift_blank project in a browser.

You can run the eclipse with-source=true SBT command to create the Eclipse project files for the project.

Once SBT creates the Eclipse project files, you can import the project in Eclipse with support for Scala. Because the Scala IDE is a special version of Eclipse, you can also import the project in a Scala IDE. You just need to follow these steps in order to import the project:

  • Select File | Import… and then choose General | Existing Projects into Workspace in Select an import source.
  • Click Next and enter or browse to the lift_blank folder as the root directory. The project name (Lift 2.5 starter template) will appear in the Projects list. Select it and click Finish. Eclipse will import the project and you will be able to navigate through its elements in Package Explorer (see Figure 2).

Lift Web Framework
Figure 2: The elements of the lift_blank project in the Eclipse Package Explorer.

The simplest way to start working with Lift is to use the lift_blank project as a baseline for your Lift Web application. You just need to make the necessary changes to the configuration file and add the files you want for your application.

Boot.scala: The Lift Configuration File

In src/main/scala, you will find a Boot.scala source file that is part of the bootstrap.liftweb package. This file defines the Boot class with a boot method. When you start a Lift application, Lift instantiates this class and executes the boot method once to allow you to configure Lift's environment, in a process known as "Lift's boot." Thus, Boot.scala is the Lift configuration file, written in the Scala programming language.

One of Lift's main goals is to reduce the number of programming languages a developer is required to use to develop and maintain a Web application. To this end, you can perform the configuration using Scala code instead of defining the properties in an XML file. You can also define values for properties that change according to the specific environment in text files with the .props extension. For example, some properties will be different for test and production environments. By default, an empty default.props file is located at /src/main/resources/props. Let's dive deep on the way you can change the configuration using the Boot class.

Listing One shows the initial code that defines the Boot class in the lift_blank project:

Listing One.

package bootstrap.liftweb

import net.liftweb._
import util._
import Helpers._

import common._
import http._
import sitemap._
import Loc._
import net.liftmodules.JQueryModule
import net.liftweb.http.js.jquery._


/**
 * A class that's instantiated early and run.  It allows the application
 * to modify lift's environment
 */
class Boot {
  def boot {
    // where to search snippet
    LiftRules.addToPackages("code")

    // Build SitefoMap
    val entries = List(
      Menu.i("Home") / "index", // the simple way to declare a menu

      // more complex because this menu allows anything in the
      // /static path to be visible
      Menu(Loc("Static", Link(List("static"), true, "/static/index"), 
	       "Static Content")))

    // set the sitemap.  Note if you don't want access control for
    // each page, just comment this line out.
    LiftRules.setSiteMap(SiteMap(entries:_*))

    //Show the spinny image when an Ajax call starts
    LiftRules.ajaxStart =
      Full(() => LiftRules.jsArtifacts.show("ajax-loader").cmd)
    
    // Make the spinny image go away when it ends
    LiftRules.ajaxEnd =
      Full(() => LiftRules.jsArtifacts.hide("ajax-loader").cmd)

    // Force the request to be UTF-8
    LiftRules.early.append(_.setCharacterEncoding("UTF-8"))

    // Use HTML5 for rendering
    LiftRules.htmlProperties.default.set((r: Req) =>
      new Html5Properties(r.userAgent))

    //Init the jQuery module, see http://liftweb.net/jquery for more information.
    LiftRules.jsArtifacts = JQueryArtifacts
    JQueryModule.InitParam.JQuery=JQueryModule.JQuery172
    JQueryModule.init()

  }
}

The code changes many of Lift's execution rules by calling methods and setting values for fields of the net.Liftweb.http.LiftRules singleton. The code calls the following four methods:

  • addToPackages
  • setSiteMap
  • early.append
  • htmlProperties.default.set

In addition, the code sets values for the following fields.

  • ajaxStart
  • ajaxEnd
  • jsArtifacts

After Lift executes the boot method, the parameters you changed will be frozen. As with many other modern Web frameworks, Lift searches for items in certain locations by convention. Lift looks for the classes that implement snippets in the different packages defined in LiftRules with the addToPackages method with the .snippet suffix. The boot method makes a single call to addToPackages to add the code package, so Lift will look for snippets in code.snippet.

LiftRules.addToPackages("code")

Notice HelloWorld.scala is defined in the code.snippet package and located in /src/main/scala. Thus, the first two lines of this source file define its package:

  package code
  package snippet

If you also want Lift to look for snippets in the drdobbs.scala.code.snippet package, you should add the following line to the boot method:

  LiftRules.addToPackages("drdobbs.scala.code")

The following lines set values for ajaxStart and ajaxEnd, which define the JavaScript to execute at the beginning of an Ajax request and when it ends. The default behavior is to show the spinny image during an Ajax call and to make it go away when the call ends. Both ajaxStart and ajaxEnd are Box[() => JsCmd], where Box is the net.liftweb.common.Box class and JsCmd is the net.liftweb.http.js.JsCmd trait.

LiftRules.ajaxStart =
  Full(() => LiftRules.jsArtifacts.show("ajax-loader").cmd)

LiftRules.ajaxEnd =
  Full(() => LiftRules.jsArtifacts.hide("ajax-loader").cmd)

As you might guess, JsCmd represents a JavaScript command in Lift. However, the Box class is very important in Lift and requires further explanation. The Box class is a container that is able to declare that it is either Full or an EmptyBox. You can think of Lift's Box as an improved version of Scala's Option, which provides a mechanism for being explicit about a value existing or not existing without using nulls. When a Box is Full, it means it contains a single non-null value. On the other hand, when a Box is an EmptyBox, it can be any of the following:

  • Empty.
  • Failure.
  • ParamFailure.

A ParamFailure is a Failure with an additional typesafe parameter that allows you to store information related to the failure. Lift's philosophy inherits from Scala regarding exceptions; so in Lift, exceptions are for exceptional conditions. For example, Lift uses exceptions when an external resource isn't available, but it encourages working with the ParamFailure value offered by Box when a parameter isn't supplied. Thus, both Failure and ParamFailure contain information about why the Box is empty and include exception information, chained failures, and a String message. The Box provides many methods that make it easy to transform it to Failure and chain failure messages. As happens with Scala's Option, the isEmpty method indicates whether the box contains a value.

For example, the following lines define a Box[String] with an Empty value and then generate a new Failure based on the empty box by calling the x~ method with a message explaining why it became a failure:

  val x: Box[String] = Empty
  val failure = x.?~("Empty means failure")


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