Skip to content

Instantly share code, notes, and snippets.

@tuhlmann
Created March 8, 2012 08:26
Show Gist options
  • Save tuhlmann/1999655 to your computer and use it in GitHub Desktop.
Save tuhlmann/1999655 to your computer and use it in GitHub Desktop.
AsyncRender - Asynchronously render parts of a template
package me.sgrouples.snippet
/*
* Copyright 2007-2011 WorldWide Conferencing, LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import net.liftweb.http.DispatchSnippet
import net.liftweb.http.TransientRequestVar
import net.liftweb.util.Helpers
import net.liftweb.util.Helpers._
import net.liftweb.common._
import net.liftweb.http.js.JsCmd
import scala.xml.NodeSeq
import net.liftweb.http.LiftSession
import net.liftweb.util.Schedule
import net.liftweb.http.CometActor
import net.liftweb.http.S
import net.liftweb.http.LiftRules
import net.liftweb.http.CometCreationInfo
import net.liftweb.http.PerformSetupComet2
import net.liftweb.http.js.JsCmds.Replace
import scala.xml.Comment
import scala.collection.mutable.ListBuffer
import java.util.Date
import net.liftweb.http.js.JsCmds._
import net.liftweb.http.SHtml
import net.liftweb.http.js.JE.JsRaw
import net.liftweb.http.RequestVar
import net.liftweb.http.SessionVar
/**
*
*/
object AsyncRender extends DispatchSnippet {
private object myFuncName extends RequestVar(Helpers.nextFuncName)
private object myActor extends RequestVar[Box[CometActor]](Empty)
def dispatch: DispatchIt = {
case _ => render _
}
def apply(nodeSeqFunc: ()=>NodeSeq): NodeSeq = render(nodeSeqFunc)
def render(xhtml: NodeSeq): NodeSeq = render(()=>{ xhtml })
def render(nodeSeqFunc: ()=>NodeSeq): NodeSeq = {
(for (session <- S.session ?~ ("FIXME: Invalid session")) yield {
// if we haven't created the actor yet, register on this
// thread to create the AsyncRenderComet actor
if (myActor.isEmpty) {
LiftRules.cometCreationFactory.request.set(
(ccinfo: CometCreationInfo) =>
ccinfo match {
case CometCreationInfo(theType @ "SgrouplesAsyncRenderComet", name, defaultXml, attributes, session) => {
val ret = new SgrouplesAsyncRenderComet()
ret.initCometActor(session, Full(theType), name, defaultXml, attributes)
ret ! PerformSetupComet2(if (ret.sendInitialReq_?) S.request.map(_.snapshot) else Empty)
// and save it in the request var
myActor.set(Full(ret))
Full(ret)
}
case _ => Empty
})
}
val id = Helpers.nextFuncName
val func: () => JsCmd =
session.buildDeferredFunction(() => Replace(id, nodeSeqFunc()))
<div id={id}>
{
S.attr("template") match {
case Full(template) if (template == "empty") => <span></span>
case Full(template) => <lift:embed what={template}/>
case _ => <img src="/images/ajax-loader.gif" alt="Loading"/>
}
}
</div> ++ Script(SnippetHelpers.DocumentReady(SHtml.ajaxInvoke(() => contentIdAvailable(id))._2)
) ++ (myActor.is match {
case Full(actor) => actor ! Ready(id, func); NodeSeq.Empty
case _ => session.setupComet("SgrouplesAsyncRenderComet",
Full(myFuncName.is), Ready(id, func) )
NodeSeq.Empty
<tail><lift:comet type="SgrouplesAsyncRenderComet" name={myFuncName.is}/></tail>
})
}) match {
case Full(x) => x
case Empty => Comment("FIX"+ "ME: session or request are invalid")
case Failure(msg, _, _) => Comment(msg)
}
}
def contentIdAvailable(id: String): Unit = {
//println("We have ID "+id)
myActor.is match {
case Full(actor) => actor ! IdIsAvailable(id, true)
case _ => "No Actor Found, Should never happen."
}
}
}
private case class Ready(id: String, js: () => JsCmd)
private case class Render(id: String, js: JsCmd)
private case class IdIsAvailable(id: String, isAvailable: Boolean)
/**
* The Comet Actor for sending down the computed page fragments
*
*/
class SgrouplesAsyncRenderComet extends CometActor {
private var isCallbackFuntionInitialized = false
private val waitingElements = collection.mutable.Map[String, JsCmd]()
private val readyIds = ListBuffer[String]()
//override def lifespan: Box[TimeSpan] = Full(90 seconds)
def render = NodeSeq.Empty
// make this method visible so that we can initialize the actor
override def initCometActor(theSession: LiftSession, theType: Box[String], name: Box[String],
defaultXml: NodeSeq, attributes: Map[String, String]) {
super.initCometActor(theSession, theType, name, defaultXml, attributes)
}
override def lowPriority : PartialFunction[Any, Unit] = {
// farm the request off to another thread
case Ready(id, js) => Schedule.schedule(() => this ! Render(id, js()), 0 seconds)
// render it
case Render(id, js) => {
readyIds.find(_ == id) match {
case Some(listId) => partialUpdate(js); readyIds -= id
case _ => waitingElements += (id -> js)
}
}
case IdIsAvailable(id, isAvailable) => {
//println("ID AVAILABLE: "+id)
waitingElements.get(id) match {
case Some(js) => partialUpdate(js); waitingElements.remove(id)
case _ => readyIds += id
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment