This is my Blog.

It's full of opinions on a wide range of stuff.

A detailed look at Lift’s view code binding system

Overview

This stuff crops up time after time again, and people are always asking about how bind() works and what they should actually be doing with it within there lift apps. I've seen some fairly ugly abuse of scala.xml._ within Lift snippets... however, not only does this tightly couple your HTML design with the snippet code behind - and thus turn into a nightmare for your designers - your missing out on some of lifts best features!

All the code snippets below presume the following imports:

import _root_.scala.xml.{NodeSeq,Text,Node,Elem}
import _root_.net.liftweb.util.{Box,Full,Empty,Helpers,Log}
import _root_.net.liftweb.util.Helpers._
import _root_.net.liftweb.http.{S,SHtml}

Also, all the methods are a part of the snippet class "Demo".

Basic Binding

So, if we want to deal with a view control on the server side, we need to bind it to a namespace in the markup. In our first example, we'll just create a text input and populate its value.

Scala Snippet

var tempValue: String = "" 

def exampleOne(xhtml: NodeSeq): NodeSeq = bind("example", xhtml,
  "item_one" -> SHtml.text(
   tempValue, // the "read" value
   tempValue = _), // the "write" function
  "submit" -> SHtml.submit("Submit", () => Log.info(tempValue))
)

s

So, lets just review quickly. We've made a string variable that were going to assign the value of our text field when the form is submitted. Furthermore, if you watch the console window you see that upon submission the value you entered is output to the console.

XHTML Code

<lift:demo.example_one form="post">
  <p>Sample input: <example:item_one example:id="demo_id"></example:item_one></p>
  <p><example:submit></example:submit></p>
</lift:demo.example_one>

You can see how the prefix we specified in the bind function ("example") is now used in the markup body of the snippet. Also, note how the actual snippet call uses snakecase ("exampleone") when our method is called "exampleOne" - Lift is clever enough to know what method you are trying to use... this ensures your designers dont moan about camel cased markup!

Binding++

The previous example was illustrative, but pretty usless in real applications. Lets take a look at something that is a bit more feature full - namely, a small form that updates a database table using the Mapper persistence framework.

Before we begin lets assume that we have a model class called "User", and it has the fields:

  • first_name - MappedString
  • last_name - MappedString
  • email - MappedEmail
  • display_email - MappedBoolean

In this fictional form, users just enter their details and a new database user row is added.

Scala Snippet

 def exampleTwo(xhtml: NodeSeq): NodeSeq = {
  var user: User = new User
  def submitHandler() = {
    if(user.saveMe){
      S.redirectTo("thank-you")
    }
  }
  bind("example",xhtml,
    "first_name" -> SHtml.text(user.first_name, user.first_name(_)),
    "last_name" -> SHtml.text(user.last_name, user.last_name(_))
    "users_email" -> SHtml.text(user.email, user.email(_)),
    "display_email" -> SHtml.checkbox(false, user.display_email(_)),
    "submit" -> SHtml.submit("Submit", submitHandler _)
  )
}

Here we have something slightly more complex - the principals are exactly the same but with more fields and a nested method to handle the submit button. In this case, the submit button just saves a new record to the database and then if successful redirects the user to a thank-you page.

XHTML Code

<lift:demo.example_two form="post">
  <p>
    <label for="first_name">
      User name <example:first_name example:id="first_name"></example:first_name>
    </label>
  </p>
  <p>
    <label for="last_name">
      Last name <example:last_name example:id="last_name"></example:last_name>
    </label>
  </p>
  <p>
    <label for="users_email">
      Email <example:users_email example:id="users_email"></example:users_email>
    </label>
  </p>
  <p>
    Display your email? <example:display_email></example:display_email>
  </p>
  <p><example:submit></example:submit></p>
</lift:demo.example_two>

You can see how we have neatly wrapped our inputs in labels (a largely view centric concern) without any changes or specific ties to the server side logic that does the database insert. This example also shows another common mistake that new-commers make... there is no crazy magic binding going on between the names of the bind placeholders and what you put in them... its purely a convention thing / coincidence that they are called the same. You are free to call them whatever you feel appropriate.

Advanced Binding

Its when you need advanced binding that lift really comes into play - lets take the oh-so common idiom of one article having many "tags"; as you are reading this on my blog i'll assume you are familiar with the notion.

So, assuming we have a Article model object with the appropriate fields, and we have a Tag model object that has a MappedForeignKey field relating back to the Article model object. We'll assume we have a utility method in the article object that grabs all the tags for that article - this will allow us to do something like:

article.tags.flatMap(tag => ....)

Lets get down to business:

Scala Snippet

def exampleThree(xhtml: NodeSeq): NodeSeq = { 
  // load article based on a fictional url parameter
  val article = Article.find(By(Article.link, S.param("permalink")))
  article.flatMap(a => bind("a", xhtml,
    "body" -> Text(a.body), // outputs the article body
    "tags" -> a.tags.flatMap(t => 
      bind("t", chooseTemplate("tag","list", xhtml),
        "name" -> Text(t.name)
      )
    )
  ))
}

After loading a single article based on the URL parameter passed to the snippet by some routing or query string parameter (doing this is out of scope for this article - check on the lift wiki) - we can the bind the contents of that model object and also iterate over the list of tags associated with that article.

The new stuff here is both the nested bind, and the chooseTemplate(...) method - this actually lets us tell the binding that for this nested bind, were going to be using a set template within the passed / input XHTML. Very cool - you'll see this working in the view code below.

XHTML Code

<lift:demo.example_three>
  <p><a:body></a:body></p>
  <p>Tagged with: <a:tags> 
  <tag:list>
    | <t:name></t:name> |
  </tag:list>
</a:tags></p>
</lift:demo.example_three>

You can see how we output the normal dynamic information just as we would any other bind item, wrapping the body in a scala.xml.Text node. We then posistion the tags placeholder of the outer bind in the correct place. Following that we then define the tag template and the content inside the tag template using the "t" prefix we specified in the nested bind function within the exampleThree method def. This should be fairly self evident that the chooseTemplate method is selecting the tag:list template and using that for each itteration of the nested bind - this is way cool and extremely usfull in most applications.

This concludes the tutorial - I hope this has been useful for someone :-)

blog comments powered by Disqus