Binding Futures in Lift 2

In the Lift project I’m currently working on, we have a long-running background task which outcomes need to be displayed on screen once computed. I would like to describe a simple enhancement for Lift bindings that allows to bind Future[T] using CSS selectors the same way as any other simple type.

The traditional request-response lifecycle model assumes, that the client side is responsible for initiating the interaction sending HTTP request. Client’s request is processed by server that prepares and sends back HTTP response. At this point interaction is over, unless client sends another request that starts completely new request/response lifecycle.

In case of Future[T], this traditional model is not enough. It’s the server side that is aware when Future evaluation is completed so, theoretically, the server side should initiate the interaction. This behaviour can be simulated using Comet. The Comet model is based on long-running HTTP request that allows server to push data to the client. When client receives data from server, new long-running HTTP request is triggered. This way, we can simulate behaviour that server side is capable of initiating the interaction and may send notifications to client at any time.

Using Comet is sometimes not desirable, though. In my case, there is a very simple Lift snippet with only some part of DOM lazy loading. This lazy-loading part is computed by a relatively long-running background task (comparing to the page load time). Once computed, information is never updated, so only one “server push” is required.

Because I didn’t want to use Comet, I had to come up with my own solution. What I wanted to achieve was the possibility to bind Futures and LAFutures (Lift wrapper for Future) using CSS selectors just like in case of simple types:

1
2
"#scala-future *" #> Future { Thread.sleep(5000); "hello" } &
"#lift-lafuture *" #> LAFuture.build { Thread.sleep(6000); "world" }

Important requirement was that the code containing Future bindings might be rendered using IdMemoize transform. It means that there was absolutely no guarantee, that Futures were bound to static DOM elements visible at the first render time and never updated. Part of DOM containing nodes to which Futures were bound could be re-rendered at any time and after each such operation, Lift should attempt to resolve these Futures.

The code below is all you need to make it happen – the only thing required to do is to import FutureBinds._ into your snippet and you are free to bind Futures using CSS selectors just like in the example code above.

FutureBinds.scalaGitHub
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
package com.ontheserverside.lib

import net.liftweb.actor.LAFuture
import net.liftweb.http.SHtml
import net.liftweb.http.js.JsCmd
import net.liftweb.http.js.JsCmds.{After, OnLoad, Replace, Script}
import net.liftweb.util.Helpers._
import net.liftweb.util._

import scala.concurrent.duration.Duration
import scala.concurrent.{Await, ExecutionContext, Future}
import scala.xml.NodeSeq

object FutureBinds {

  private def futureTransform[FutureType, T](
    innerTransform: CanBind[T],
    futureCompleted_? : (FutureType) => Boolean,
    resolveFuture: (FutureType) => T
  ): CanBind[FutureType] = new CanBind[FutureType] {

    def apply(future: => FutureType)(ns: NodeSeq): Seq[NodeSeq] = {

      List(BindHelpers.findOrCreateId { id =>
        val concreteFuture = future
        lazy val updateFunc = SHtml.ajaxInvoke(() => resolveAndUpdate).exp.cmd

        def resolveAndUpdate: JsCmd  = {
          if (futureCompleted_?(concreteFuture)) {
            Replace(id, innerTransform(resolveFuture(concreteFuture))(ns).flatten)
          } else {
            After(1 seconds, updateFunc)
          }
        }

        _ => <div id={id} class="loading"><img src="/images/ajax-loader.gif" alt="Loading"/></div> ++ Script(OnLoad(updateFunc))
      }(ns))
    }
  }

  implicit def futureTransform[T](implicit innerTransform: CanBind[T], executionContext: ExecutionContext): CanBind[Future[T]] = {
    futureTransform[Future[T],T](innerTransform, (future) => future.isCompleted, (future) => Await.result(future, Duration.Inf))
  }

  implicit def lafutureTransform[T](implicit innerTransform: CanBind[T]): CanBind[LAFuture[T]] = {
    futureTransform[LAFuture[T],T](innerTransform, (future) => future.complete_?, (future) => future.get)
  }
}

Each one second, client asks server “has Future completed?”. If so, server tells client to update its DOM with Future evaluation result. Otherwise, server says “Not yet. Please ask me again later”.

Alternative approach for binding Futures has been presented by Antonio in his lift-future-canbind-example on GitHub. It’s very clever idea that takes advantage of Lift’s <lift:lazy-load> mechanism. The FutureBinds code in that example looks much simpler but there is one important limitation. Under the hood, lazy load uses Lift’s comet and comets can’t be sent down to the client via AJAX at the moment. That’s just framework limitation. So even if we agree on using comet to handle Future bindings internally, we will not be able do render code containing Future bindings dynamically with IdMemoize.

Sample application with my FutureBinds implementation is available on GitHub.

Comments

Author

photo

View Piotr Dyraga's LinkedIn profile  Piotr Dyraga
Software engineering consultant experienced in a wide range of projects (banking, logistics, computer networks and others). Please feel free to contact me if you are looking for development services for your project.

Recent Posts