Modules

History Key

  • New content
  • Removed content

Recent Versions

Choose two versions to compare, or click the link to view it.

  1. 7. 7 months by eltimn
  2. 6. about 1 year by sorenbs
  3. 5. over 1 year by sorenbs
  4. 4. over 1 year by tjweir
  5. 3. over 1 year by pr1001
  6. 2. over 1 year by pr1001
  7. 1. about 2 years by hseeberger
 

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 Snippets) 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 Bootstrapping), 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.

However, it hasn’t been clear to the average Lift user how best to share bits of general purpose, or at least common, code among separate Lift projects. A set of common conventions, outlined below, and additions to SiteMap in Lift 2.2 seek to address this. The goal is for there to be an ecosystem of modules that modules that developers can easily drop into their Lift apps, similar to the Ruby on Rails community and its culture of creating and sharing plugins. A vibrant module community means that code that previously would have gotten sucked into the Lift vortex under lift-modules could live outside of the Lift project. This both makes it easier for more people to contribute code and also means that modules can be updated and released to the public on their maintainers’ own schedules.

Conventions

Lift modules must follow a set of simple common conventions so that users can easily and confidently add them to their own apps. There is no minimum version of Lift required for Lift modules. The conventions are as follows:

  • The module must have a package name that ends in its module name. For example: net.lift_modules.HelloWorldnet.liftmodules.HelloWorld
  • There must be an object with the module’s name that the Lift app using it can import in Boot.scala. Given this usage, you may want to place its source file in src/main/scala/bootstrap/lift_modules/ModuleName.scalasrc/main/scala/bootstrap/liftmodules/ModuleName.scala but this isn’t required. The object might be imported like so: import net.lift_modules.HelloWorld.HelloWorldnet.liftmodules.HelloWorld.HelloWorld
  • The object must have an init method which returns Unit (i.e. doesn’t return anything) and is called in the Lift app’s Boot.scala to initialize the module. Every possible effort should be taken to automatically gather all necessary information, whether from the main Lift app or from props files, so that the init method can be called like so: ModuleName.init. However, it is acceptable for the init method to take parameters if there is no better alternative. Naturally this must be documented for module users.

Examples

Peter Robinett has created a HelloWorld module and an example app which uses it.

Modules and SiteMap

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 SiteMap) 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
/**
* 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)
 
<p>
/**
   * 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))
</p>

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.

Potential Modules

  • Converting the current list of modules
  • Ostrich stat viewer
  • drop-in chat
  • Some of the django/pinax modules
  • Server debugging in the browser via glimpse