A Birds-eye View with Lift

Organize and control content with the Lift web framework

Lift is a web framework built for the Scala programming language, running on the Java Virtual Machine. Version 2.5 recently shipped, and I’m highlighting features of the framework that I find appealing.

Last time it was transforms and REST services, and this time it’s two features around organizing and controlling access to content.

SiteMap

How do you manage the HTML pages on site? Maybe some pages are available to everyone, others require login or some condition for access, and often pages are grouped in some way. Lift has an optional mechanism for expressing all this in one place, and it’s called SiteMap. This is how it looks in code:

lazy val home = Menu.i("home") / "index"

lazy val poets = Menu.i("poets") / "list" submenus (
  Menu("Larkin") / "a-z" / "larkin",
  Menu("Tennyson") / "a-z" / "tennyson"
)

LiftRules.setSiteMap(SiteMap(home, poets))

We’ve defined a site made up of a home page, and pages for poets we like. Why is this a good thing?

First, it’s a pretty readable representation of the site in (compiler checked) Scala code. You can probably figure out that we have a page called “home” found at /index.html (or / as it’s usually known).

What might not be obvious is that Menu.i is using “home” as a key into your translation resources, meaning the link text will be the localized version—if you’re building an internationalized site. The link to the page can be generated by a Menu.builder, a snippet you can use on pages to give your users navigation of the SiteMap.

The second menu item is called “poets,” served up from a template called list.html. On the home page, the navigation snippet would give you a link to the “poets” page, but by default is smart enough to hide the submenus until you visit the list of poets.

SiteMap also performs access control: if you use SiteMap, HTML pages have to be listed in it to be accessed. It’s a simple line of defense, but you can improve on this by adding location parameters. These are rules for controlling access to a page.

Let’s say we wanted to only offer some content to customers paying for our premium plan:

 val PremiumCustomersOnly = If(
  () => false,
  () => S.redirectTo("https://shop.example.org/") )  

This is an if-then-else construct, made up of two functions. The first function is a test for what qualifies as a premium customer. It’s an arbitrary computation, and in this example it’s false, meaning nobody is a premium customer (in reality, we’d perhaps check a property of the logged user). If you nevertheless try to get to the page, the second function kicks in, and you’d be sent to our store front.

We can apply the “premium customer” rule to any part of the site, but we’re going to just restrict Tennyson poems:

Menu("Tennyson") / "a-z" / "tennyson" >> PremiumCustomersOnly

If you don’t like the >> method name, use rule instead. If you just don’t like this domain specific language (DSL), you can construct a SiteMap in longer-form method calls.

What’s great about SiteMap is the flexibility it gives you: if you can’t get the behavior you want out of the box, you can write custom functions and classes, and drop them into the SiteMap. If you want menus displayed in a completely different way, not handled by Menu.builder customization, you can write a different navigation snippet. Location parameter rules allow you to isolate logic, and combine them as needed to parts of your site.

But if that’s not for you, don’t use it: it’s not required for building an app with Lift, it’s just that many find it powerful and helpful.

Menu Param

For pages with parameters in the URL, you won’t want to manually list them in the SiteMap, because that would be long and dull. For a URL like /fact/larkin you probably want to extract the poet name and show the relevant information. That’s a job for Lift’s Menu Param.

This is neat because it gives us type-safety via the SiteMap. I’ll explain with an example. Say we want to write code that works with a PoetFact:

case class PoetFact(first: String, surname: String, born: Int)

val database = Map[String, PoetFact](
  "larkin"   -> PoetFact("Philip", "Larkin", 1922),
  "tennyson" -> PoetFact("Alfred", "Tennyson", 1809)
)

We can match on the URLs /fact/larkin and /fact/tennyson in SiteMap with Menu.param[T]:

lazy val fact = Menu.param[PoetFact]("poet", "Poet Fact",
  surname => database.get(surname),
  fact => fact.surname.toLowerCase
  ) / "fact" >> Hidden

This is a menu item for PoetFact, and it goes into SiteMap like any other menu. You’ll notice the location is /fact and we’re hiding it from the navigation (Hidden is one of the built-ins in Lift).

The two functions supplied give Lift:

  1. A way to go from a poet’s surname to a fact, which might not be found—it’s a String => Box[PoetFact] function and Box is Lift’s enhanced Option;
  2. The reverse, a PoetFact => String, for when we need to generate URLs.

On the presentation side of things, fact.html could be:

<dl data-lift="PoetInfo">
  <dt>First Name</dt>
  <dd class="first-name">Fred</dd>

   <dt>Surname</dt>
   <dd class="last-name">Bloggs</dd>

   <dt>Born</dt>
   <dd class="dob">2013</dd>
</dl>

…and we replace the placeholder values by working with our type-safe PoetFact class:

package code.snippet
class PoetInfo(fact: PoetFact) {
  def render =
    ".first-name *" #> fact.first &
    ".last-name *" #> fact.surname &
    ".dob *" #> fact.born
}

We’ve given Lift a function to take a URL parameter and turn it into a class, and then we get to use that class.

What’s great about this is the separation of munging URLs into classes from getting on and working with those classes.

Learning About Lift

That was two more features to give you a taste of Lift. If you want to find out more:

Lift 2.5 has just been released, and 3.0 is in the pipeline. It’s a great time to get involved.

tags: , , ,