8.3 Modules
Lift has supported modules from the first version of the project in 2007. Lift’s entire handling of the HTTP request/response cycle is open to hooks. Further, Lift’s templating mechanism where resulting HTML pages are composed by transforming page content via snippets (See
↑) which are simply functions that take HTML and return HTML:
NodeSeq => NodeSeq. Because Lift’s snippet resolution mechanism is open and any code referenced in Boot (See
↑), any code can be a Lift “module” by virtue of registering its snippets and other resources in
LiftRules. Many Lift modules already exist including PayPal, OAuth, OpenID, LDAP, and even a module containing many jQuery widgets.
The most difficult issue relating to integration of external modules into Lift is how to properly insert the module’s menu items into a SiteMap (See
↑) menu hierarchy. Lift 2.2 introduces a more flexible mechanism for mutating the
SiteMap:
SiteMap mutators.
SiteMap mutators are functions that rewrite the SiteMap based on rules for where to insert the module’s menus in the menu hierarchy. Each module may publish markers. For example, here are the markers for ProtoUser:
/**
* Insert this LocParam into your menu if you want the
* User’s menu items to be inserted at the same level
* and after the item
*/
final case object AddUserMenusAfter extends Loc.LocParam[Any]
/**
* replace the menu that has this LocParam with the User’s menu
* items
*/
final case object AddUserMenusHere extends Loc.LocParam[Any]
/**
* Insert this LocParam into your menu if you want the
* User’s menu items to be children of that menu
*/
final case object AddUserMenusUnder extends Loc.LocParam[Any]
The module also makes a SiteMap mutator available, this can either be returned from the module’s init method or via some other method on the module. ProtoUser makes the sitemapMutator method available which returns a SiteMap => SiteMap.
The application can add the marker to the appropriate menu item:
Menu("Home") / "index" >> User.AddUserMenusAfter
And when the application registers the SiteMap with LiftRules, it applies the mutator:
LiftRules.setSiteMapFunc(() => User.sitemapMutator(sitemap()))
Because the mutators are composable:
val allMutators = User.sitemapMutator andThen FruitBat.sitemapMutator
LiftRules.setSiteMapFunc(() => allMutators(sitemap()))
For each module, the implementation of the mutators is pretty simple:
private lazy val AfterUnapply = SiteMap.buildMenuMatcher(_ == AddUserMenusAfter)
private lazy val HereUnapply = SiteMap.buildMenuMatcher(_ == AddUserMenusHere)
private lazy val UnderUnapply = SiteMap.buildMenuMatcher(_ == AddUserMenusUnder)
/**
* The SiteMap mutator function
*/
def sitemapMutator: SiteMap => SiteMap = SiteMap.sitemapMutator {
case AfterUnapply(menu) => menu :: sitemap
case HereUnapply(_) => sitemap
case UnderUnapply(menu) => List(menu.rebuild(_ ::: sitemap))
}(SiteMap.addMenusAtEndMutator(sitemap))
We’ve defined some extractors that help with pattern matching. SiteMap.buildMenuMatcher is a helper method to make building the extractors super-simple. Then we supply a PartialFunction[Menu, List[Menu]] which looks for the marker LocParam and re-writes the menu based on the marker. If there are no matches, the additional rule is fired, in this case, we append the menus at the end of the SiteMap.
(C) 2012 David Pollak