Version 14, last updated by David Lee at 17 Apr 20:03 UTC

This page includes short tips that are hard to find on the mailing list, but are worth knowing about.

Server side function order.

There are times that you want to control the order in which callback functions are called in a form. You can do this using S.formGroup. See this thread for an example.

This is an explanation by David P. about how functions are called on the server.

Functions on the server are executed in the order that the form elements are created on the server, unless you use the S.formGroup stuff that Diego mentions.
Each function is assigned a unique number from a source that is monotonically increasing. In addition to that number, there’s a random string of 6 characters appended. That makes up the GUID that maps to the function on the server. When Lift handles a request, it sorts the incoming parameters (GET, PUT, POST, DELETE) and, in sorted order, looks up the parameter name in the session’s function table. If the function exists, Lift applies the function to that parameter’s values.
S.formGroup increases or decreases the monotonically increasing number with a constant (100,000 times the formGroup) so that the GUID will be guaranteed to be sorted either before or after the GUIDs in the default (form group 0).
A long time ago, Kris Nuttycomb put forth a proposal for making Lift’s form elements monadic so that they would be evaluated in the order accessed by the handler code, rather than in the order that they were defined during page rendering. It’s still an open ticket, but I have not spent the time to make it happen.
I hope this helps and if you have more questions, please post them.
Thanks,
—David

Alternatives to OSGI.

From time to time people ask about Lift’s support for OSGI, unless a committer puts all the work, which seems unlikely, this isn’t going to happen.
But not all is lost, if what you want is to add “modules” to your Lift application, there are a few alternatives that were pointed out by Naftoli on this thread , in summary the options are:

If you don’t mind having to restart the servlet then you don’t need osgi. There are other ways to do this kind of thing.
You can use traits to decouple features, and package them into various jars. Then you can use the service provider interface or JCL etc. to load jars in a known location and find classes that implement various traits. See java.sun.com/developer/technicalArticles/javase/extensible/ and https://github.com/kamranzafar/JCL/.
Another option is http://github.com/marcusatbang/Hooks.

Show error if Database or Service is down.

LiftRules.statelessDispatch.prepend {
  case _ if theDBIsDown => () => Full(HtmlResponse(generateDBDownError))
}

That will be tested for every page that Lift processes. It doesn’t break the URL (so the user can hit reload and once the DB is back, then they’ll get the page they were on). — David P.

Focus the cursor on a form field.

If you have something like

var name= ""
var lastName= ""
...
render ={
"@name" #> SHtml.text(name, name = _) &
"@last_name" #> SHtml.text(lastName, lastName = _)
}

and you would like the cursor to be on the name field. All you need to do is wrap the SHtml call like this:

var name= ""
var lastName= ""
...
render ={
"@name" #> JsCmds.FocusOnLoad(SHtml.text(name, name = _)) &
"@last_name" #> SHtml.text(lastName, lastName = _)
}

Execute server side code after JavaScript confirm

JsCmds.Confirm("Do you want to delete this entry?", SHtml.ajaxInvoke(() => {delete the entry on the server side})._2.cmd)

Access Query parameters in Wizard

You can access a query parameter on the localSetup method. So you can then go ahead and set a WizardVar to store this information.

object hname extends WizardVar[Box[String]](Empty)

override def localSetup() {
  super.localSetup()
  hname.set(S.param("hname"))
}

Click a button and get a file download (with no redirect)

The function handlers for SHtml.link looks like:

def link(to: String, func: () => Any, body: NodeSeq,  attrs: ElemAttr*)

Note that the func is () => Any. So, in this case, just return a LiftResponse from the func and you’re good to go.

Using SSL during development – SBT

You can provide your own jetty.xml file using:

configurationFiles in container.Configuration := Seq(file("jetty.xml"))

And make sure your jetty.xml has the correct configuration to enable SSL

See The docs for more information

How to cache values in SessionVars with expiration.


case class CacheValue[T](compute: () => T, lifespanInMillis: Long) {
  private var currentValue: Box[T] = Empty
  private var lastCalc: Long = 0
  def get: T = synchronized {
    if (lastCalc + lifespanInMillis < Helpers.millis) {
      currentValue = Empty
    }
    currentValue match {
      case Full(v) => v
      case _ => {
        val ret = compute()
        lastCalc = millis
        currentValue = Full(ret)
        ret
      }
    }
  }
}


object MyCachedThing extends SessionVar(CacheValue(() => "hello", 5000))

Case Insensitive Mongo queries with Rogue.

Database queries in Mongo are normally case sensitive. If you need to search in a case insensitive way, for instance
for a name field, you can do this:


MyMongoRecord where (_.nameOfField matches Pattern.compile(valueOfField, Pattern.CASE_INSENSITIVE)) fetch()

Futures with callback – Non-blocking Futures

When you use Lift Futures, you normally see the option to send a message to an actor that will return a future, you do some other work on your code, and then call .get on the future to work with the result.
Sometimes you don’t have anything else to do “in between” sending the message and working with the result. So you may be tempted to call .get right away. This causes the thread to block until the future is satisfied.

But there is a better way!

You can use foreach or a for comprehension without a yield on a future and the computation will be resumed on a separate thread when the future is satisfied

val result: LAFuture[Result] = <longComputation that returns some other actor>
result.foreach(r => myActor ! r) // returns immediately and when the future is satisfied, the message send it processed.

You can see the full discussion on the mailing list