Thursday 2 July 2009

another LIFT rant...


Yesterday I got so frustrated with Liftweb and it's lack of documentation that I decided to pick up apache wicket. Now I had picked up wicket almost a year ago and despite all it's cool features I decided against pursueing it any further. Well guess what those same reasons popped up again yesterday. Needless to say I'm back with lift. I've seen a lot of complaints about lift's documentation and all that but I'm not convinced that the reason I'm still with lift is the self same reason I wont pick up wicket anytime soon. Enough ranting.
So I figure what's the point of joining the rants when I could spend my time better contributing in my own small way to solving the problem? So I decided to write a little tutorial (seriously this is nothing serious) about lift.

Some foundations...

Lift is written in scala (so if you dont know scala you might as well stop reading now). It is said to have picked up the nice things from all the other frameworks and of course since scala is said to scale well, it is expected that lift scales well (dont take my word for it). In anycase... enough cliches and truism... here's how I understand lift.

In lift there are basically 3 parts to your web app

1. The view - your normal (well formed) xhtml document. The special thing though is that there are all these special tags that correspond to class methods that you arbitrarily create.
2. The boot system - this is a class where all the configuration of your webapp is stored. For instance you'd store the package where you have the tag classes I mentioned above here.
3. Your special tags - ok, seriously, this can get really confusing but humour me for just a moment.

So how does this all come together? Well, the special tags I mentioned above are actually called 'snippets' - and yes they are much more than special tags. They are more like mini-controllers that have been thrown into your html file. So when lift is processing a request for an html view and it encounters any one of these special tags (snippets), the class method corresponding to the special tag's name is called. This class method is free to roam the wild and do what ever it likes (after all I just said it's a mini controller). But it's not all about the snippets.... the view (html files) that I talked about also have this cool feature called templating. What happens in essense is that you can have a template html file (one or more) that defines the common functionality. Then you can have other xhtml fragments in other files that then define only the part of your website that changes. No, I'm not exactly talking inheritance here but then again I'm not sure what I'm talking about ;)

pre implementation
Ok so let's see an implementation to get some concrete understanding of all the nonsense I've just said... I'll basically keep it really simple...

We'll have one web page (actually a template and 2 snippets)
One class with two methods

The website will basically give potential spellings of english words in yoruba. On that note some of the other things I'll touch on are session variables, request variables and .... that's it!
I just have to say one more thing... lift relies a lot on maven so if you hate maven... why would you hate someone that's never done anything to harm you?

view implementation

Remember the template we talked about earlier? well this is how it works. You have a standard xhtml file that contains how you want your site to look. Inside that file you can place <lift:bind name="content" /> tags at any position where you think the content should change dynamically. You then define other xhtml files that would define dynamic content. These other xhtml files will specify what template LIFT should use by using the <lift:surround with="default" at="content"></lift:surround> tag. These xhtml files would also contain special tags that map to class methods that you define. For instance <lift:yoruba.spell form="GET">, in the example below, tells lift that it should load and call the spell method from the "Yoruba" class defined in the snippet package.


<lift:surround with="default" at="content">

  <h2>Welcome to your project!</h2>
  <p>
    <lift:yoruba.spell form="GET">
      word: <yo:word /><br/>
      <yo:submit />
      <yo:spellings />
    </lift:yoruba.spell>
  </p>
</lift:surround>



snippet implementation

The snippet is a plain old scala class (POSC just doesn't do it as an acronym now does it?). In any case, this method should contain a method called spell (because in your html file you refered to a method called "spell" within the "Yoruba" class. This method should call a special method called bind where you define the default values of xhtml fields and how the corresponding class variables are populated when a request comes in. Ideally you should have a variable that corresponds to each <yo:submit /> tag within the <lift:yoruba.spell form="GET"> tag. The first parameter of the bind method is "yo". The 2nd should be pretty obvious. The third though, simply says where ever the <yo:word /> tag is encountered the default value is yoruba.word and the field that should populated when a request gets to the server is defined by the method "word = _" (if you dont understand this construct google "scala partial functions" as my fingers are beginning to ache!).

One note worthy thing is that the other of parameters in the bind method is important (or so I think). So for instance if I had put the submit binding definition after the spelling one then potentially the calculated spelling would only make it to the client on the next response not the current one. **I think this is something I should verify but then again there's a reason for the disclaimer at the top of this blog**

Another thing to note is that since each request comes on it's own and may (would) trigger another instance of your snippet class if you set the value of a field when the request comes in it may see that value set when the response gets back to the client. Ways around this include using RequestVar/SessionVar as I've done here or using S.mapSnippet("yoruba.spell", a local method defined within Yoruba.spell) to change the binding on the snippet. The result of the later makes smart use of scala's perspective on scope and the fact that scala accepts higher level methods. Actually you'll see this later method used here. I opted to go with the SessionVar/RequestVar approach because I want previous spellings to show up on the screen as long as the session hasn't timed out. To use these SessionVar, you create an object that extends SessionVar along with a default constructor. The use of RequestVar is a little different but it's explained in the LIFT Book (it may be a good idea for you at this time to google "liftbook master.pdf" if you've not already done so).


package eni.ola.snippet


import scala.xml.{ NodeSeq, Text }

import net.liftweb._
import http._
import SHtml._
import S._

import util._
import Helpers._

class Yoruba {
  object spellings extends SessionVar[NodeSeq](Text(""))
  var word = ""
  
  def spell(xhtml:NodeSeq):NodeSeq = {
    def process():Unit = {
      val x = koole(word.toString, null)
      
      spellings(
        <div>{ x }</div>
        <hr/>
        <span>{ spellings.is }</span>
      )
    }
    
    def yoBind(xhtml:NodeSeq):NodeSeq =
        bind("yo", xhtml,
                "word" -> SHtml.text(word, word = _),

                "submit" -> SHtml.submit("epeller", process),
                "spellings" -> spellings.is
        )
    

    yoBind(xhtml)
  }

  
  val afabeti = Set("a", "\u00E1", "\u00E0") ::
                        Set("e", "\u00e8", "\u00e9", "e\u0329", "\u00E8\u0329", "\u00E9\u0329") ::
                        Set("i", "\u00EC", "\u00ED") ::
                        Set("o", "\u00F2", "\u00F3", "o\u0329", "\u00F2\u0329", "\u00F3\u0329") ::
                        Set("u", "\u00F9", "\u00Fa") ::
                        Set("s", "s\u0329") :: Nil  
  def koole(word:String, combos:List[String]):String = {
    if(word.trim == "")
      combos.reduceLeft(_+", "+_)
    else if(combos == null || combos.isEmpty)
      koole(word, List(""))
    else{
      //get list of possible letters for current letter
      val letter = word(0).toString     
      val /span>choice = { Set(letter) :: afabeti.filter(_.contains(letter)) }.reduceLeft(_ ++ _).toList


      //append the list to each existing combo
      koole(word.substring(1), choice.flatMap(x => combos.map(_+x).toList))     

    }

  }
}



en fin

So what I've done here is pretty basic. I've not touched the boot class at all as I simply just tweaked/created 2 files. The learning curve of LIFT may be steep but it sure is concise. The error messages dont help that much either but you can't win them all now can you? I assumed that you already know how to create a LIFT project using maven. If you dont here's what you do...

Install eclipse (I'm sorry Netbeans fans but I can't bring myself to work in an ugly IDE) from eclipse.org.
Install the scala eclipse plugin and m2eclipse plugin (for maven) - if you dont know where to get these from then google or bing (bing sounds so perverse).
Once you have all these set up, create a maven project. When it's time to select archetype choose "lift-archetype-basic" and voila!
To run/demo the project, you can set up a maven run configuration for your project and make the goal "jetty:run".

koole - is the Yoruba word for write it down
afabeti - is my own yoruba transliteration for the english word alphabet

Phew! I definitely won't come back to this blog for another 2 months or so... quantum mechanics beckons :(
But never say never... after all the EPL season starts this month for us Gooners (someone has to talk some sense to those ArsenalAmerica.com guys - I'm not sure they know how much changes to their site may affect my sanity). Aight... happy 4th of July

No comments: