Diving into Lift with updated Lift in Action project (chapter 2)

Here's what I did to get the Lift in Action tutorial auction application dev'ed up and running with Lift 2.5 and sbt 0.12.1:

[Edit: Since I started on this over the last couple of days, my work has been made considerably easier by the timely publication of Instant Lift Web Applications by Torsten Uhlmann. Congratulations on this up-to-date and pithy masterpiece! In any case I want to persevere on this front too.]

When comparing Lift to other web application frameworks, even though Lift gets the kudos for being the most solid, performant and productive option, people often remark how hard it is to dive into, particularly the fact that Lift in Action, a blessing aimed at making things easy to dive into, contains examples based on superceded releases of Lift, Scala and particularly the sbt builder. So this article is the first in a series of articles designed to straighten that out and allow people to easily dive into an excellent web application framework, and an excellent book also. 

Creating the project

What I want to do is to clone Lift 2.5, and then, based on the lift_basic starter project template, import just that folder into Eclipse as the prepped starter scoffold for the project.

So in any working directory, I clone Lift

$ git clone git://github.com/lift/lift_25_sbt.git
Cloning into 'lift_25_sbt'...
remote: Counting objects: 937, done.
remote: Compressing objects: 100% (548/548), done.
remote: Total 937 (delta 330), reused 877 (delta 270)
Receiving objects: 100% (937/937), 3.05 MiB | 352 KiB/s, done.
Resolving deltas: 100% (330/330), done.
$ mv lift_25_sbt/ lift_25_auction_app
$ cd lift_25_auction_app/scala_29/lift_basic/

I then edited the Name and Organization fields in the file build.sbt :

$ head build.sbt 
name := "Lift in Action Auction App"
version := "0.0.1"
organization := "com.awebfactory"
scalaVersion := "2.9.1"

Importing the project into Eclipse

After editing build.sbt, I ran the local version of sbt and created the Eclipse project:

$ ./sbt
> eclipse
[info] About to create Eclipse project files for your project(s).
[info] Successfully created Eclipse project files for project(s):
[info] Lift in Action Auction App
>

I then started up Eclipse and switched to Scala Perspective, and hit File > Import >> General >> Existing Projects into Workspace and clicked on the Next button. I selected the prepped lift_basic folder as the root directory, then made sure I selected the Copy projects into workspace checkbox in order to have a clean project with its own root directory and none of the cruft of other unused project starter templates. I clicked on the Finish button. I immediately had a nice clean project imported into Eclipse, into the default workspace (on my MacBook Air ~/Documents/workspace/Lift in Action Auction App).

If I wish, I could have deleted the original directory where I did the prepping before importing via copy into Eclipse.

Inspecting the project

The project explorer document tree was essentially the same as in section 2.2.2 of Lift in Action, allowing for differences in Lift and sbt versions, of course. There is a wonderful document explaining the differences we should expect to see on GitHub: https://github.com/harrah/xsbt/wiki/Migrating-from-SBT-0.7.x-to-0.10.x (It's actually talking about sbt 0.12). Essentially, the old project directory has become something else, and there is a new build.sbt file that defines project properties. This is the file we edited in order to prepare the project for import into Eclipse.

Then we have:

  • src/main/webapp/WEB-INF/web.xml

The main src directories are clearly visible:

  • src/main/scala
    • bootstrap.liftweb
      • Boot.scala
  • src/main/resources
  • src/main/webapp

The test folders are also visible.

Running the bare-bones project

The equivalent to Lift in Action section 2.2.3 Booting the application involves running sbt from the command line just as before, but the command set is totally different.

(There is discussion on the Scala-IDE wiki on running SBT inside Eclipse, https://www.assembla.com/spaces/scala-ide/wiki/SBT-based_build_manager, but I find it more natural to continue using the command line myself).

$ ./sbt
[info] Loading project definition from /Users/victorkane/Documents/workspace/Lift in Action Auction App/project
[info] Updating {file:/Users/victorkane/Documents/workspace/Lift%20in%20Action%20Auction%20App/project/}default-55c3c0...
[info] Resolving org.scala-sbt#precompiled-2_10_0-m7;0.12.1 ...
[info] Done updating.
[info] Set current project to Lift in Action Auction App (in build file:/Users/victorkane/Documents/workspace/Lift%20in%20Action%20Auction%20App/)
[info] Updating {file:/Users/victorkane/Documents/workspace/Lift%20in%20Action%20Auction%20App/}default-e6e5b9...
[info] Resolving org.eclipse.jetty#jetty-io;8.1.7.v20120910 ...
[info] Done updating.
> container:start
[info] Compiling 4 Scala sources to /Users/victorkane/Documents/workspace/Lift in Action Auction App/target/scala-2.9.1/classes...
[info] jetty-8.1.7.v20120910
[info] NO JSP Support for /, did not find org.apache.jasper.servlet.JspServlet
[info] started o.e.j.w.WebAppContext{/,[file:/Users/victorkane/Documents/workspace/Lift%20in%20Action%20Auction%20App/src/main/webapp/]}
[info] started o.e.j.w.WebAppContext{/,[file:/Users/victorkane/Documents/workspace/Lift%20in%20Action%20Auction%20App/src/main/webapp/]}
[info] Started SelectChannelConnector@0.0.0.0:8080
[success] Total time: 13 s, completed Jan 31, 2013 11:48:50 AM
>

Pointing a browser at http://localhost:8080/ gives the expected result.

In order to stop app execution with the default container (jetty), you can either exit sbt or do container:stop and then exit:

> container:stop
[info] stopped o.e.j.w.WebAppContext{/,[file:/Users/victorkane/Documents/workspace/Lift%20in%20Action%20Auction%20App/src/main/webapp/]}
[success] Total time: 0 s, completed Jan 31, 2013 11:52:44 AM
> exit
$

Snippets and templating overview

In my opinion one of the most exciting aspects of Lift is the exploding the outmoded "page" concept in dynamic modern web applications. An HTML page rendered in the browser is composed of any number of dynamic views (it's the content, not the page!). And via Comet, AJAX, etc., one or more views may be updated constantly in response to user input without refreshing the page. Lift does this via the designer friendly "View First" principle, explained in Chapter 1 of Lift in Action.

Lift's anti-MVC View First pattern (View-Snippet-Model: VSM) is explained very clearly and in detail in section 1.2.2. Most applications use "Template views that bind dynamic content into a predefined markup template", in conjunction with Snippets:

"to generate dynamic content and mediate changes in the model back to the view.... In terms of Lift’s view-first architecture, the snippet will usually call the model for some values.... Whatever the operation, when the model is asked to do something, it applies whatever business logic it needs to and then responds appropriately to the snippet.... The actual mechanism for updating the view isn’t important for this discussion (full page load, AJAX, or some other method). Rather, the model responds and the response is passed to the view via the snippet."

So where's the code? If you have followed the above steps, the view corresponding to the home page is in the HTML5 template src/main/webapp/index.html:

<!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>Welcome to your project!</h2>
<p>
<span class="lift:helloWorld.howdy">
Welcome to your Lift app at <span id="time">Time goes here</span>
</span>
</p>
</div>
</body>
</html>

The body class attribute tells lift that the template starts in div#main. In div#main, the class attribute lift:surround invokes the built-in Lift surround snippet, which surrounds div#main with src/main/webapp/templates-hidden/default.html parent (surround) template.

Then, the contained span class attribute "lift:helloWorld.howdy" invokes the custom snippet (method of class HelloWorld) def howdy found in src/main/scala/code/snippet/HelloWorld.scala, which specifies that the selector #time * be replaced with the expression date.map(_.toString).

This will all be replaced with the Lift in Action Auction app code as this is built in later chapters, but our purpose here has been to begin to see common Lift snippets and how they work with the templates provided, the aim of section 2.3.2 Templating Overview in Lift in Action.

Putting everything on GitHub so folks can follow along and clone

I have put the current state of affairs up on this public repo: https://github.com/victorkane/LiftInActionAuctionApp, and commiting each chapter as a tag. Now at "chapter02".

Since complete instructions have been given, we're going to follow common practice with Lift Web Applications, which is to version the src directory only, that's what we are interested in following. Initially, the following files and directories will be involved:

bash-3.2$ tree .
.
├── main
│   ├── java
│   ├── resources
│   │   ├── logback.xml
│   │   └── props
│   │   └── default.props
│   ├── scala
│   │   ├── bootstrap
│   │   │   └── liftweb
│   │   │   └── Boot.scala
│   │   └── code
│   │   ├── comet
│   │   ├── lib
│   │   │   └── DependencyFactory.scala
│   │   ├── model
│   │   │   └── User.scala
│   │   ├── snippet
│   │   │   └── HelloWorld.scala
│   │   └── view
│   └── webapp
│   ├── WEB-INF
│   │   └── web.xml
│   ├── images
│   │   └── ajax-loader.gif
│   ├── index.html
│   ├── static
│   │   └── index.html
│   └── templates-hidden
│   ├── default.html
│   └── wizard-all.html
└── test
├── java
├── resources
│   └── logback-test.xml
└── scala
├── RunWebApp.scala
└── code
└── snippet
└── HelloWorldTest.scala

24 directories, 15 files