Bubble Foundry


Understanding Lift’s SiteMap

by Peter.

This post will probably only be useful for people developing using Scala and the Lift framework so, my other readers, consider yourself warned!

Lift uses a SiteMap object to represent the structure of your site. Based upon this it determines what, if anything, should respond to a request at a given URL. The old Lift wiki has a page describing the basics, but since you’re here, you might as well read my post. And I go into more detail. First, the SiteMap is defined in Boot.scala, where you’ll have something like:

val entries = Menu(Loc("Home", List("index"), "Home")) ::
Menu(Loc("Edit Blog", List("edit"), "Edit User", Hidden)) ::
Post.menus :::
User.sitemap


LiftRules.setSiteMap(SiteMap(entries:_*))

As you can see, we create a SiteMap from a List of Menus. Post.menus is a List of Menus automatically created by the CRUDify trait mixed into the Post model. User.sitemap is a List of Menus created by the MetaMegaProtoUser trait mixed into the User model. Side note: it’s annoying that these two traits have different menu creation method names.

Right now all our Menus simply same one parameter, a Loc, so let’s look at that. First, as you probably realized, a Loc is a location in the site. Here are the details of the constructor:

def apply(theName : String, theLink : Link[NullLocParams], theText : LinkText[NullLocParams], theParams : LocParam*)

Create a Loc (Location) instance

param
params – — access test, title calculation, etc.
link – — the Link to the page
text – — the text to display when the link is displayed
name – — the name of the location. This must be unique across your entire sitemap. It’s used to look up a menu item in order to create a link to the menu on a page.

With this information, let’s look at Loc("Home", List("index"), "Home"). The first “Home” is our unique name for the Loc, while the second is the text to display. Now, the link: doesn’t it take a Link[NullLocParams], not a List[String]? It does, but there’s an implicit conversion to make our lives easier:

implicit def strLstToLink(in: Seq[String]): Link[NullLocParams] = new Link[NullLocParams](in.toList)

So what is a Link? It’s just that, a link to the view file to be displayed.

The Home and Edit Blog declarations are single entries that corresponds to XHTML views. Lift looks for the corresponding templates within a defined search path (I’m afraid I don’t know the exact directories) and in our case it would find them at src/main/webroot/index.html and src/main/webroot/edit.html. If we have Loc("New Blog", "blogs" :: "new" :: Nil, "New Blog") then the view file should be at src/main/webroot/blogs/new.html.

Unfortunately there’s a gotcha in how URLs are handled in Lift: /edit and /edit/ are treated differently, with the former mapping to src/main/webroot/edit.html and the latter to src/main/webroot/edit/index.html. There have been detailed discussions about this, so if URL handling in this fashion strikes you as wrong, check them out for alternatives.

The Hidden property hints at another thing: SiteMap has a second, closely related purpose: creating menus of links to pages in the site. As the Lift tutorial says:

The <lift:Menu.builder /> element is a special element that builds a menu based on the SiteMap. The SiteMap is a high-level site directory component that not only provides a centralized place to define a site menu, but allows you to control when certain links are displayed (based on, say, whether a user is logged in or what roles they have) and provides a page-level access control mechanism.

Personally I think that Lift’s approach makes a lot of sense but it can get complicated if you’re using Menu.builder but have more than a few entries in your SiteMap that you don’t want to show up in your menu. Of course, if you’re doing something like a REST API (which naturally shouldn’t be in your menus), you should hook in earlier to Lift’s request-response cycle. For example: LiftRules.statelessDispatchTable.prepend(RestAPI.dispatch). But I digress, and let’s get back to Hidden.

Hidden hints at the fact that you can drop in various bits of application logic to control how Loc‘s exist in SiteMaps. Remember that the final argument to Loc‘s apply method takes a LocParam (actually, it will take as many as you give it)? If, Unless, and Test extend LocParam to let you provide your own code to determine whether the page should be displayed.  Using these methods you can build any sort of conditional access system, whether it’s allowing site administrators to special admin pages or the millionth user to claim a prize.