All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.liftweb.builtin.comet.AsyncRenderComet.scala Maven / Gradle / Ivy

The newest version!
package net.liftweb
package builtin
package comet

import scala.xml.NodeSeq

import common._
import http._
  import js._
import util._
  import Helpers._

case class Compute(js: () => JsCmd)
private case class Render(js: JsCmd)

/**
 * `AsyncRenderComet` facilitates rendering anything that produces a `JsCmd`
 * independently from a page request. All you have to do is create one and
 * send it a `Compute` message with the function that will produce the `JsCmd`.
 * `AsyncRenderComet` will take ownership of the function and run it in
 * a separate thread comet context, sending the results down using a
 * `partialUpdate` when it is done.
 *
 * Note that if you want to run a function that requires the context of the
 * request you're running in, you'll want to use `LiftSession`'s
 * `buildDeferredFunction` method to make sure that when the function is
 * executed in a separate thread, it will retain request context.
 *
 * In general, consider using one of:
 *  - `AsyncRenderComet.asyncRender`.
 *  - The `lazy` snippet.
 *  - The `CanBind` implicits in the `net.liftweb.http` package that allow using
 *    `LAFuture` and Scala `Future` objects as the right-hand-side of a CSS
 *    selector binding.
 * 
 * None of these requires explicit use of `buildDeferredFunction`.
 */
class AsyncRenderComet extends MessageCometActor {

  override def lifespan: Box[TimeSpan] = Full(90.seconds)

  // make this method visible so that we can initialize the actor
  override def initCometActor(creationInfo: CometCreationInfo): Unit = {
    super.initCometActor(creationInfo)
  }

  // FIXME add lifecycle management that will nuke the comet here and on the
  // FIXME client when nothing is left to compute
  override def lowPriority : PartialFunction[Any, Unit] = {
    // farm the request off to another thread
    case Compute(js) => 
      Schedule.schedule(() => this ! Render(js()), 0.seconds)

    // render it
    case Render(js) => 
      partialUpdate(js)
  }
}

object AsyncRenderComet {
  private object pageAsyncRenderer extends TransientRequestVar[Box[AsyncRenderComet]](
    S.findOrCreateComet[AsyncRenderComet](
      cometName = Full(s"lazy-${S.renderVersion}"),
      cometHtml = NodeSeq.Empty,
      cometAttributes = Map.empty,
      receiveUpdatesOnPage = true
    )
  )

  /**
   * If you're going to be managing the asynchronicity of the render externally,
   * make sure to call this so that the async plumbing will be set up on the
   * page when it gets sent down.
   *
   * When possible, prefer the use of the `lazy` snippet, the `asyncRender`
   * function, or the `CanBind` implicits for `Future` and `LAFuture`.
   *
   * Returns a `Failure` if something went wrong with setting up the
   * asynchronous render.
   */
  def setupAsync: Box[Unit] = {
    // Dereference to make sure the comet exists.
    pageAsyncRenderer.is.map(_ => ()) ?~! "Failed to create async renderer."
  }

  /**
   * If you're going to be managing the asynchronicity of the render externally
   * (e.g., with futures), call this when you're ready to render your results
   * and the rendering will be sent down to the client.
   *
   * When possible, prefer the use of the `lazy` snippet, the `asyncRender`
   * function, or the `CanBind` implicits for `Future` and `LAFuture`.
   *
   * Returns a `Failure` if something went wrong with looking up the
   * asynchronous renderer.
   */
  def completeAsyncRender(command: JsCmd): Box[Unit] = {
    pageAsyncRenderer.is.map(_ ! Render(command)) ?~! "Failed to create async renderer."
  }

  /**
   * Render the given function on a separate thread and send the resulting
   * JavaScript to the current page when the function completes. Wraps the
   * function so that it is executed in the current request and session context.
   *
   * Returns a `Failure` if something went wrong with setting up the
   * asynchronous render.
   */
  def asyncRender(renderFunction: ()=>JsCmd): Box[Unit] = {
    for {
      session <- S.session ?~ "Asynchronous rendering requires a session context."
      renderer <- pageAsyncRenderer.is ?~! "Failed to create async renderer."
    } yield {
      renderer ! Compute(session.buildDeferredFunction(renderFunction))
    }
  }

  /**
   * Similar to `asyncRender`, but any wrapping of the function in a request
   * context is expected to be done before `renderFunction` is passed to this,
   * while `asyncRender` takes care of the wrapping for you.
   */
  def asyncRenderDeferred(renderFunction: ()=>JsCmd): Box[Unit] = {
    pageAsyncRenderer.is.map { renderer =>
      renderer ! Compute(renderFunction)
    } ?~! "Failed to create async renderer."
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy