Using Type Classes for Lift Snippet Binding

Back in 2009 I was inspired by “this article”:http://logji.blogspot.com/2009/10/composable-bindings-in-lift-part-ii.html by “Kris Nuttycombe”:https://profiles.google.com/kris.nuttycombe/about which demonstrated how to use implicit conversions to declare composable, reusable Lift snippet bindings.

At the time that article was written, Scala was still in the 2.7.x series and Lift only had the bind() helpers. As I write this today, Lift has its new CSS-style transformers and the Scala language has moved on quite considerably - especially with regard to the handling of implicits. With this in mind I wanted to show how one could make a similar setup but by leveraging Scala context bounds and Lift’s CSS-style transformers.

Use Case

Consider that you were building a system that displayed products. It’s quite feasible that everywhere you wanted to display said product a same or similar binding would be required. A classic point in case is the product listing vs the checkout; the latter would only require a few of the product aspects to be displayed but there would be enough commonality with the listing usage as to not want to repeat yourself.

For the sake of this article Product will be represented like so:

<code>case class Product(name: String, price: Long)
</code>

Binding

In order to make something implicitly bindable, that is to let the compiler know when it should apply the in-scope binding we need to make a bit of infrastructure to facility this:

<code>import net.liftweb.util.CssSel

object Bindings {
  class Bind[T](what: T){
    def bind(implicit bind: DataBinding[T]) = bind(what)
  }
  type DataBinding[T] = T => CssSel
  implicit def asCssSelector[T](in : T): Bind[T] = new Bind[T](in)
}</code>

Here the Bindings object contains a converter class call Bind[T] whos method bind takes an implicit parameter of DataBinding[T]. In more lay terms, given T (ultimately, Product type) apply an implicit conversion from T => CssSel, where CssSel is the internal Lift type for CSS transformers. Finally, the asCssSelector method does the actual conversion to a Bind[T] instance just like any regular implicit conversion.

So all that remains is to implement a binding for Product and show how you can use that in your snippet classes:

<code>import Bindings._
import net.liftweb.util.Helpers._

object ProductBinding extends DataBinding[Product]{
  def apply(product: Product) = 
    ".name" #> product.name
}

class MySnippet {
  private implicit val binder = ProductBinding
  
  def render: CssSel = 
    Product("whatever", 1234L).bind
}</code>

In this example, ProductBinding defines the generic binding that one requires for Product usage within the system. In your code, this would certainly be a lot more fully featured, but for the sake of example it just binds the name. Within the MySnippet class one can then simply call Product("whatever", 1234L).bind and thats all that needs doing. In order to put the implicit binding in-scope, the ProductBinding singleton is just assigned to a value. This could have just as easily been held somewhere else in a common binding object and then imported to save the clutter, but here it is explicitly defined for the snippet.

There is an alternative syntax which you might prefer for the snippet implementation that uses context bounds like so:

<code>def render[Product : Bind] = 
  Product("whatever", 1234L).bind
</code>

Importantly, with both implementation styles not that you can also add snippet-specific additions to the generic binding. For example:

<code>def render: CssSel = 
    Product("whatever", 1234L).bind & 
    ".thing" #> "example"
</code>

Hopefully you found this useful. For more information like this, please pick up a copy of “Lift in Action”:http://affiliate.manning.com/idevaffiliate.php?id=1138_235

comments powered by Disqus