alvinalexander.com | career | drupal | java | mac | mysql | perl | scala | uml | unix  
is broken in IE */ @serializable case class RenderOut(xhtml: Box[NodeSeq], fixedXhtml: Box[NodeSeq], script: Box[JsCmd], destroyScript: Box[JsCmd], ignoreHtmlOnJs: Boolean) { def this(xhtml: NodeSeq) = this (Full(xhtml), Empty, Empty, Empty, false) def this(xhtml: NodeSeq, js: JsCmd) = this (Full(xhtml), Empty, Full(js), Empty, false) def this(xhtml: NodeSeq, js: JsCmd, destroy: JsCmd) = this (Full(xhtml), Empty, Full(js), Full(destroy), false) def ++(cmd: JsCmd) = RenderOut(xhtml, fixedXhtml, script.map(_ & cmd) or Full(cmd), destroyScript, ignoreHtmlOnJs) } @serializable private[http] object Never

Other Lift Framework examples (source code examples)

Here is a short list of links related to this Lift Framework CometActor.scala source code file:

Lift Framework example source code file (CometActor.scala)

This example Lift Framework source code file (CometActor.scala) is included in the DevDaily.com "Java Source Code Warehouse" project. The intent of this project is to help you "Learn Java by Example" TM.

Java - Lift Framework tags/keywords

any, box, box, cometmessage, empty, empty, full, jscmd, nodeseq, nodeseq, partialfunction, string, string, unit, util

The Lift Framework CometActor.scala source code

/*
 * 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.
 */

package net.liftweb
package http

import net.liftweb.common._
import net.liftweb.actor._
import scala.collection.mutable.{ListBuffer}
import net.liftweb.util.Helpers._
import net.liftweb.util._
import net.liftweb.json._
import scala.xml.{NodeSeq, Text, Elem, Node, Group, Null, PrefixedAttribute, UnprefixedAttribute}
import scala.collection.immutable.TreeMap
import scala.collection.mutable.{HashSet, ListBuffer}
import net.liftweb.http.js._
import JsCmds._
import JE._
import java.util.concurrent.atomic.AtomicLong
import java.util.Locale

/**
 * An actor that monitors other actors that are linked with it. If a watched
 * actor terminates,this actor captures the Exit messag, executes failureFuncs
 * and resurects the actor.
 */
object ActorWatcher extends scala.actors.Actor with Loggable {
  import scala.actors.Actor._
  def act = loop {
    react {
      case scala.actors.Exit(actor: scala.actors.Actor, why: Throwable) =>
        failureFuncs.foreach(f => tryo(f(actor, why)))

      case _ =>
    }
  }

  private def startAgain(a: scala.actors.Actor, ignore: Throwable) {
    a.start
    a ! RelinkToActorWatcher
  }

  private def logActorFailure(actor: scala.actors.Actor, why: Throwable) {
    logger.warn("The ActorWatcher restarted " + actor + " because " + why, why)
  }

  /**
   * If there's something to do in addition to starting the actor up, pre-pend the
   * actor to this List
   */
  @volatile var failureFuncs: List[(scala.actors.Actor, Throwable) => Unit] = logActorFailure _ ::
          startAgain _ :: Nil

  this.trapExit = true
  this.start
}

/**
 * This is used as an indicator message for linked actors.
 *
 * @see ActorWatcher
 */
case object RelinkToActorWatcher

trait DeltaTrait {
  def toJs: JsCmd
}

trait CometState[DeltaType <: DeltaTrait,
MyType <: CometState[DeltaType, MyType]] {
  self: MyType =>

  def -(other: MyType): Seq[DeltaType]

  def render: NodeSeq
}

trait CometStateWithUpdate[UpdateType, DeltaType <: DeltaTrait,
MyType <: CometStateWithUpdate[UpdateType,
        DeltaType, MyType]]
        extends CometState[DeltaType, MyType]
{
  self: MyType =>
  def process(in: UpdateType): MyType
}

trait StatefulComet extends CometActor {
  type Delta <: DeltaTrait
  type State <: CometState[Delta, State]

  /**
   * Test the parameter to see if it's an updated state object
   */
  def testState(in: Any): Box[State]

  /**
   * Return the empty state object
   */
  def emptyState: State

  /**
   * The current state objects
   */
  protected var state: State = emptyState

  /**
   * If there's some ThreadLocal variable that needs to be setup up
   * before processing the state deltas, set it up here.
   */
  protected def setupLocalState[T](f: => T): T = f

  private[http] override val _lowPriority = {
    val pf: PartialFunction[Any, Unit] = {
      case v if testState(v).isDefined =>
        testState(v).foreach {
          ns =>
                  if (ns ne state) {
                    val diff = ns - state
                    state = ns
                    partialUpdate(setupLocalState {diff.map(_.toJs).foldLeft(Noop)(_ & _)})
                  }
        }
    }

    pf orElse super._lowPriority
  }

  /**
   * The Render method
   */
  def render = state.render
}

object CurrentCometActor extends ThreadGlobal[LiftCometActor]

object AddAListener {
  def apply(who: SimpleActor[Any]) = new AddAListener(who, { case _ => true } )
}

/**
 * This is a message class for use with ListenerManager and CometListener
 * instances. The use of the shouldUpdate function is deprecated, and
 * should instead be handled by the message processing partial functions
 * on the CometListner instances themselves.
 *
 * @see CometListener
 * @see ListenerManager
 */
case class AddAListener(who: SimpleActor[Any], shouldUpdate: PartialFunction[Any, Boolean])

/**
 * This is a message class for use with ListenerManager and CometListener
 * instances.
 *
 * @see CometListener
 * @see ListenerManager
 */
case class RemoveAListener(who: SimpleActor[Any])


object ListenerManager {
  type ActorTest = (SimpleActor[Any], PartialFunction[Any, Boolean])
}


/**
 * This trait manages a set of Actors in a publish/subscribe pattern. When you extend your Actor with
 * this trait, you automatically get handling for sending messages out to all subscribed Actors. Simply
 * override the high-, medium-, or lowPriority handlers to do your message processing. When you want to update
 * all subscribers, just call the updateListeners method. The createUpdate method is used to generate
 * the message that you want sent to all subscribers.
 *
 * Note that the AddAListener and RemoveAListener messages (for subscription control) are processed
 * after any highPriority or mediumPriority messages are processed, so take care to avoid overly
 * broad matches in those handlers that might consume internal messages.
 *
 * For example, you could write a simple service to provide clock ticks using the following code:
 *
 * <pre name="code" class="scala">
 * case object Tick
 * 
 * object Ticker extends ListenerManager {
 *   import net.liftweb.util.ActorPing
 * 
 *   // Set up the initial tick
 *   ActorPing.schedule(this, Tick, 1000L)
 *
 *   // This is a placeholder, since we're only interested
 *   // in Ticks
 *   def createUpdate = "Registered"
 * 
 *   override def mediumPriority = {
 *     case Tick => {
 *       updateListeneres(Tick)
 *       ActorPing.schedule(this, Tick, 1000L)
 *     }
 *   }
 * }
 * </pre>
 *
 * A client CometActor could look like:
 *
 * <pre name="code" class="scala">
 * class CometClock extends CometListener {
 *   val registerWith = Ticker
 *
 *   ... handling code ...
 * }
 * </pre>
 *
 * @see CometListener
 *   
 */
trait ListenerManager {
  self: SimpleActor[Any] =>

  import ListenerManager._

  /**
   * This is the list of all registered actors
   */
  private var listeners: List[ActorTest] = Nil

  protected def messageHandler: PartialFunction[Any, Unit] =
    highPriority orElse mediumPriority orElse
            listenerService orElse lowPriority

  protected def listenerService: PartialFunction[Any, Unit] =
    {
      case AddAListener(who, shouldUpdate) =>
        val pair = (who, shouldUpdate)
        listeners ::= pair
        updateIfPassesTest(createUpdate)(pair)

      case RemoveAListener(who) =>
        listeners = listeners.filter(_._1 ne who)
    }

  /**
   * Update the listeners with the message generated by createUpdate
   */
  protected def updateListeners() {
    val update = updateIfPassesTest(createUpdate) _
    listeners foreach update
  }

  /**
   * Update the listeners with a message that we create. Note that
   * with this invocation the createUpdate method is not used.
   */
  protected def updateListeners(msg: Any) {
    listeners foreach (_._1 ! msg)
  }

  /**
   * This method provides legacy functionality for filtering messages
   * before sending to each registered actor. It is deprecated in
   * favor of doing the filtering in the registered Actor's
   * message handling partial functions instead.
   */
  @deprecated("Accept/reject logic should be done in the partial function that handles the message.")
  protected def updateIfPassesTest(update: Any)(info: ActorTest) {
    info match {
      case (who, test) => if (test.isDefinedAt(update) && test(update)) who ! update
    }
  }

  /**
   * This method is called when the <pre>updateListeners()
method * needs a message to send to subscribed Actors. In particular, createUpdate * is used to create the first message that a newly subscribed CometListener * will receive. */ protected def createUpdate: Any /** * Override this method to process high priority messages. Note: * <b>you must not process messages with a wildcard (match all), since * this will intercept the messages used for subscription control. */ protected def highPriority: PartialFunction[Any, Unit] = Map.empty /** * Override this method to process medium priority messages. See * the highPriority method for an important note on wildcard * processing. * * @see #highPriority */ protected def mediumPriority: PartialFunction[Any, Unit] = Map.empty /** * Override this method to process low priority messages. */ protected def lowPriority: PartialFunction[Any, Unit] = Map.empty } /** * This is a legacy trait, left over from Lift's * Scala 2.7 support. You should use or migrate to * CometListener instead. * * @see CometListener */ @deprecated("Use the CometListener trait instead.") trait CometListenee extends CometListener { self: CometActor => } /** * A LiftActorJ with ListenerManager. Subclass this class * to get a Java-useable LiftActorJ with ListenerManager */ abstract class LiftActorJWithListenerManager extends LiftActorJ with ListenerManager { protected override def messageHandler: PartialFunction[Any, Unit] = highPriority orElse mediumPriority orElse listenerService orElse lowPriority orElse _messageHandler } /** * This trait adds functionality to automatically register with a given * Actor using AddAListener and RemoveAListener control messages. The most * typical usage would be to register with an instance of ListenerManager. * You will need to provide a def/val for the <pre>registerWith member * to control which Actor to connect to. * * See ListenerManager for a complete example. * * @see ListenerManager */ trait CometListener extends CometActor { self: CometActor => /** * This controls which Actor to register with for updates. Typically * this will be an instance of ListenerActor, although you can provide * your own subscription handling on top of any SimpleActor. */ protected def registerWith: SimpleActor[Any] /** * Override this in order to selectively update listeners based on the given message. * This method has been deprecated because it's executed in a seperate context from * the session's context. This causes problems. Accept/reject logic should be done * in the partial function that handles the message. */ @deprecated("Accept/reject logic should be done in the partial function that handles the message.") protected def shouldUpdate: PartialFunction[Any, Boolean] = { case _ => true} abstract override protected def localSetup() { registerWith ! AddAListener(this, shouldUpdate) super.localSetup() } abstract override protected def localShutdown() { registerWith ! RemoveAListener(this) super.localShutdown() } } trait LiftCometActor extends TypedActor[Any, Any] with ForwardableActor[Any, Any] with Dependent { def uniqueId: String private[http] def callInitCometActor(theSession: LiftSession, theType: Box[String], name: Box[String], defaultHtml: NodeSeq, attributes: Map[String, String]) { initCometActor(theSession, theType, name, defaultHtml, attributes) } protected def initCometActor(theSession: LiftSession, theType: Box[String], name: Box[String], defaultHtml: NodeSeq, attributes: Map[String, String]): Unit def jsonCall: JsonCall def theType: Box[String] def name: Box[String] def hasOuter: Boolean def buildSpan(time: Long, xml: NodeSeq): NodeSeq def parentTag: Elem /** * Poke the CometActor and cause it to do a partial update Noop which * will have the effect of causing the component to redisplay any * Wiring elements on the component. * This method is Actor-safe and may be called from any thread, not * just the Actor's message handler thread. */ def poke(): Unit = {} /** * Is this CometActor going to capture the initial Req * object? If yes, override this method and return true * and override captureInitialReq to capture the Req. Why * have to explicitly ask for the Req? In order to send Req * instances across threads, the Req objects must be snapshotted * which is the process of reading the POST or PUT body from the * HTTP request stream. We don't want to do this unless we * have to, so by default the Req is not snapshotted/sent. But * if you want it, you can have it. */ def sendInitialReq_? : Boolean = false /** * If the predicate cell changes, the Dependent will be notified */ def predicateChanged(which: Cell[_]): Unit = {poke()} /** * The locale for the session that created the CometActor */ def cometActorLocale: Locale = _myLocale private var _myLocale = Locale.getDefault() private[http] def setCometActorLocale(loc: Locale) { _myLocale = loc } } /** * Subclass from this class if you're in Java-land * and want a CometActor */ abstract class CometActorJ extends LiftActorJ with CometActor { override def lowPriority = _messageHandler } /** * Subclass from this class if you want a CometActorJ with * CometListeners */ abstract class CometActorJWithCometListener extends CometActorJ with CometListener { override def lowPriority = _messageHandler } /** * Takes care of the plumbing for building Comet-based Web Apps */ trait CometActor extends LiftActor with LiftCometActor with BindHelpers { private val logger = Logger(classOf[CometActor]) val uniqueId = Helpers.nextFuncName private var spanId = uniqueId private var lastRenderTime = Helpers.nextNum /** * If we're going to cache the last rendering, here's the * private cache */ private[this] var _realLastRendering: RenderOut = _ /** * The last rendering (cached or not) */ private def lastRendering: RenderOut = if (dontCacheRendering) { val ret = (render ++ jsonInCode): RenderOut theSession.updateFunctionMap(S.functionMap, spanId, lastRenderTime) ret } else { _realLastRendering } /** * set the last rendering... ignore if we're not caching */ private def lastRendering_=(last: RenderOut) { if (!dontCacheRendering) { _realLastRendering = last } } private var wasLastFullRender = false @transient private var listeners: List[(ListenerId, AnswerRender => Unit)] = Nil private var askingWho: Box[LiftCometActor] = Empty private var whosAsking: Box[LiftCometActor] = Empty private var answerWith: Box[Any => Any] = Empty private var deltas: List[Delta] = Nil private var jsonHandlerChain: PartialFunction[Any, JsCmd] = Map.empty private val notices = new ListBuffer[(NoticeType.Value, NodeSeq, Box[String])] private var lastListenTime = millis private var _theSession: LiftSession = _ def theSession = _theSession @volatile private var _defaultHtml: NodeSeq = _ @deprecated("Use defaultHtml") def defaultXml = _defaultHtml /** * The template that was passed to this component during comet * initializations */ def defaultHtml: NodeSeq = _defaultHtml private var _name: Box[String] = Empty /** * The optional name of this CometActors */ def name: Box[String] = _name private var _theType: Box[String] = Empty /** * The optional type of this CometActor */ def theType: Box[String] = _theType private var _attributes: Map[String, String] = Map.empty def attributes = _attributes /** * The lifespan of this component. By default CometActors * will last for the entire session that they were created in, * even if the CometActor is not currently visible. You can * set the lifespan of the CometActor. If the CometActor * isn't visible on any page for some period after its lifespan * the CometActor will be shut down. */ def lifespan: Box[TimeSpan] = Empty private var _running = true private var _shutDownAt = millis /** * Is the CometActor running? */ protected def running = _running /** * It's seriously suboptimal to override this method. Instead * use localSetup() */ protected def initCometActor(theSession: LiftSession, theType: Box[String], name: Box[String], defaultHtml: NodeSeq, attributes: Map[String, String]) { if (!dontCacheRendering) { lastRendering = RenderOut(Full(defaultHtml), Empty, Empty, Empty, false) } this._theType = theType this._theSession = theSession this._defaultHtml = defaultHtml this._name = name this._attributes = attributes } def defaultPrefix: Box[String] = Empty private lazy val _defaultPrefix: String = (defaultPrefix or _name) openOr "comet" /** * Set to 'true' if we should run "render" on every page load */ protected def devMode = false def hasOuter = true def parentTag = <div style="display: inline"/> /** * Return the list of ListenerIds of all long poll agents that * are waiting for this CometActor to change its state. This method * is useful for detecting presence. */ protected def cometListeners: List[ListenerId] = listeners.map(_._1) /** * This method will be called when there's a change in the long poll * listeners. The method does nothing, but allows you to get a granular * sense of how many browsers care about this CometActor. Note that * this method should not block for any material time and if there's * any processing to do, use Scheduler.schedule or send a message to this * CometActor. Do not change the Actor's state from this method. */ protected def listenerTransition(): Unit = {} private def _handleJson(in: Any): JsCmd = if (jsonHandlerChain.isDefinedAt(in)) jsonHandlerChain(in) else handleJson(in) /** * Prepends the handler to the Json Handlers. Should only be used * during instantiation * * @param h -- the PartialFunction that can handle a JSON request */ def appendJsonHandler(h: PartialFunction[Any, JsCmd]) { jsonHandlerChain = h orElse jsonHandlerChain } def handleJson(in: Any): JsCmd = Noop /** * If there's actor-specific JSON behavior on failure to make the JSON * call, include the JavaScript here. */ def onJsonError: Box[JsCmd] = Empty lazy val (jsonCall, jsonInCode) = S.buildJsonFunc(Full(_defaultPrefix), onJsonError, _handleJson) /** * Override this method to deal with JSON sent from the browser via the sendJson function. This * is based on the Lift JSON package rather than the handleJson stuff based on the older util.JsonParser. This * is the prefered mechanism. If you use the jsonSend call, you will get a JObject(JField("command", cmd), JField("param", params)) */ def receiveJson: PartialFunction[JsonAST.JValue, JsCmd] = Map() /** * The JavaScript call that you use to send the data to the server. For example: * <button onclick={jsonSend("Hello", JsRaw("Dude".encJs))}>Click</button> */ def jsonSend: JsonCall = _sendJson /** * The call that packages up the JSON and tosses it to the server. If you set autoIncludeJsonCode to true, * then this will be included in the stuff sent to the server. */ def jsonToIncludeInCode: JsCmd = _jsonToIncludeCode private lazy val (_sendJson, _jsonToIncludeCode) = S.createJsonFunc(Full(_defaultPrefix), onJsonError, receiveJson _) /** * Set this method to true to have the Json call code included in the Comet output */ def autoIncludeJsonCode: Boolean = false /** * Creates the span element acting as the real estate for commet rendering. */ def buildSpan(time: Long, xml: NodeSeq): NodeSeq = { Elem(parentTag.prefix, parentTag.label, parentTag.attributes, parentTag.scope, Group(xml)) % new UnprefixedAttribute("id", Text(spanId), if (time > 0L) { new PrefixedAttribute("lift", "when", time.toString, Null) } else {Null}) } protected override def messageHandler = { val what = composeFunction val myPf: PartialFunction[Any, Unit] = new PartialFunction[Any, Unit] { def apply(in: Any): Unit = CurrentCometActor.doWith(CometActor.this) { S.initIfUninitted(theSession) { RenderVersion.doWith(uniqueId) { S.functionLifespan(true) { what.apply(in) if (S.functionMap.size > 0) { theSession.updateFunctionMap(S.functionMap, uniqueId, lastRenderTime) S.clearFunctionMap } } } } } def isDefinedAt(in: Any): Boolean = CurrentCometActor.doWith(CometActor.this) { S.initIfUninitted(theSession) { RenderVersion.doWith(uniqueId) { S.functionLifespan(true) { what.isDefinedAt(in) } } } } } myPf } /** * A part of the CometActor's screen real estate that is not * updated by default with reRender(). This block of HTML is * useful for the editor part of a Comet-based control where * the data is JSON and updated with partialUpdates. */ def fixedRender: Box[NodeSeq] = Empty /** * Calculate fixedRender and capture the postpage javascript */ protected def calcFixedRender: Box[NodeSeq] = fixedRender.map(ns => theSession.postPageJavaScript() match { case Nil => ns case xs => { ns ++ Script(xs) } }) /** * We have to cache fixedRender and only change it if * the tempalte changes or we get a reRender(true) */ private def internalFixedRender: Box[NodeSeq] = if (!cacheFixedRender) { calcFixedRender } else { cachedFixedRender.get } private val cachedFixedRender: FatLazy[Box[NodeSeq]] = FatLazy(calcFixedRender) /** * By default, we do not cache the value of fixedRender. If it's * expensive to recompute it each time there's a convertion * of something to a RenderOut, override this method if you * want to cache fixedRender */ protected def cacheFixedRender = false /** * A helpful implicit conversion that takes a NodeSeq => NodeSeq * (for example a CssSel) and converts it to a Box[NodeSeq] by * applying the function to defaultHtml */ protected implicit def nodeSeqFuncToBoxNodeSeq(f: NodeSeq => NodeSeq): Box[NodeSeq] = Full(f(defaultHtml)) /** * Handle messages sent to this Actor before the */ def highPriority: PartialFunction[Any, Unit] = Map.empty def lowPriority: PartialFunction[Any, Unit] = Map.empty def mediumPriority: PartialFunction[Any, Unit] = Map.empty private[http] def _lowPriority: PartialFunction[Any, Unit] = { case s => logger.debug("CometActor " + this + " got unexpected message " + s) } private lazy val _mediumPriority: PartialFunction[Any, Unit] = { case l@Unlisten(seq) => { lastListenTime = millis askingWho match { case Full(who) => forwardMessageTo(l, who) // forward l case _ => listeners = listeners.filter(_._1 != seq) } listenerTransition() } case l@Listen(when, seqId, toDo) => { lastListenTime = millis askingWho match { case Full(who) => forwardMessageTo(l, who) // who forward l case _ => if (when < lastRenderTime) { toDo(AnswerRender(new XmlOrJsCmd(spanId, lastRendering, buildSpan _, notices toList), whosAsking openOr this, lastRenderTime, wasLastFullRender)) clearNotices } else { deltas.filter(_.when > when) match { case Nil => listeners = (seqId, toDo) :: listeners case all@(hd :: xs) => toDo(AnswerRender(new XmlOrJsCmd(spanId, Empty, Empty, Full(all.reverse.foldLeft(Noop)(_ & _.js)), Empty, buildSpan, false, notices toList), whosAsking openOr this, hd.when, false)) clearNotices } } } listenerTransition() } case PerformSetupComet2(initialReq) => { // this ! RelinkToActorWatcher localSetup() captureInitialReq(initialReq) performReRender(true) } /** * Update the defaultHtml... sent in dev mode */ case UpdateDefaultXml(xml) => { val redo = xml != _defaultHtml _defaultHtml = xml if (redo) { performReRender(false) } } case AskRender => askingWho match { case Full(who) => forwardMessageTo(AskRender, who) // forward AskRender case _ => { if (!deltas.isEmpty || devMode) performReRender(false) reply(AnswerRender(new XmlOrJsCmd(spanId, lastRendering, buildSpan _, notices toList), whosAsking openOr this, lastRenderTime, true)) clearNotices } } case ActionMessageSet(msgs, req) => S.doCometParams(req.params) { S.jsToAppend() match { case Nil => case js => partialUpdate(js) } reply(msgs.map(_()) ::: List(S.noticesToJsCmd)) } case AskQuestion(what, who, otherlisteners) => { this.spanId = who.uniqueId this.listeners = otherlisteners ::: this.listeners startQuestion(what) whosAsking = Full(who) this.reRender(true) listenerTransition() } case AnswerQuestion(what, otherListeners) => askingWho.foreach { ah => { reply("A null message to release the actor from its send and await reply... do not delete this message") // askingWho.unlink(self) ah ! ShutDown this.listeners = this.listeners ::: otherListeners this.askingWho = Empty val aw = answerWith answerWith = Empty aw.foreach(_(what)) performReRender(true) listenerTransition() } } case ShutdownIfPastLifespan => for{ ls <- lifespan if listeners.isEmpty && (lastListenTime + ls.millis) < millis } { this ! ShutDown } case ReRender(all) => performReRender(all) case Error(id, node) => notices += ((NoticeType.Error, node, id)) case Warning(id, node) => notices += ((NoticeType.Warning, node, id)) case Notice(id, node) => notices += ((NoticeType.Notice, node, id)) case ClearNotices => clearNotices case ShutDown => logger.info("The CometActor " + this + " Received Shutdown") askingWho.foreach(_ ! ShutDown) theSession.removeCometActor(this) _localShutdown() case PartialUpdateMsg(cmdF) => { val cmd: JsCmd = cmdF.apply val time = Helpers.nextNum val delta = JsDelta(time, cmd) theSession.updateFunctionMap(S.functionMap, uniqueId, time) S.clearFunctionMap val m = millis deltas = (delta :: deltas).filter(d => (m - d.timestamp) < 120000L) if (!listeners.isEmpty) { val postPage = theSession.postPageJavaScript() val rendered = AnswerRender(new XmlOrJsCmd(spanId, Empty, Empty, Full(cmd & postPage), Empty, buildSpan, false, notices toList), whosAsking openOr this, time, false) clearNotices listeners.foreach(_._2(rendered)) listeners = Nil listenerTransition() } } } /** * It's the main method to override, to define what is rendered by the CometActor * * There are implicit conversions for a bunch of stuff to * RenderOut (including NodeSeq). Thus, if you don't declare the return * turn to be something other than RenderOut and return something that's * coersable into RenderOut, the compiler "does the right thing"(tm) for you. * <br/> * There are implicit conversions for NodeSeq, so you can return a pile of * XML right here. There's an implicit conversion for NodeSeq => NodeSeq, * so you can return a function (e.g., a CssBindFunc) that will convert * the defaultHtml to the correct output. There's an implicit conversion * from JsCmd, so you can return a pile of JavaScript that'll be shipped * to the browser.<br/> * Note that the render method will be called each time a new browser tab * is opened to the comet component or the comet component is otherwise * accessed during a full page load (this is true if a partialUpdate * has occured.) You may want to look at the fixedRender method which is * only called once and sets up a stable rendering state. */ def render: RenderOut /** * Cause the entire component to be reRendered and pushed out * to any listeners. This method will cause the entire component * to be rendered which can result in a huge blob of JavaScript to * be sent to the client. It's a much better practice to use * partialUpdate for non-trivial CometActor components. * * @param sendAll -- Should the fixed part of the CometActor be * rendered. */ def reRender(sendAll: Boolean) { this ! ReRender(sendAll) } /** * Cause the entire component to be reRendered and pushed out * to any listeners. This method will cause the entire component * to be rendered which can result in a huge blob of JavaScript to * be sent to the client. It's a much better practice to use * partialUpdate for non-trivial CometActor components. */ def reRender() {reRender(false)} /** * Set this method to true if you want to avoid caching the * rendering. This trades space for time. */ protected def dontCacheRendering: Boolean = false /** * Clear the common dependencies for Wiring. This * method will clearPostPageJavaScriptForThisPage() and * unregisterFromAllDependencies(). The combination * will result in a clean slate for Wiring during a redraw. * You can change the behavior of the wiring dependency management * by overriding this method */ protected def clearWiringDependencies() { if (!manualWiringDependencyManagement) { theSession.clearPostPageJavaScriptForThisPage() unregisterFromAllDepenencies() } } /** * By default, Lift deals with managing wiring dependencies. * This means on each full render (a full render will * happen on reRender() or on a page load if there have been * partial updates.) You may want to manually deal with * wiring dependencies. If you do, override this method * and return true */ protected def manualWiringDependencyManagement = false private def performReRender(sendAll: Boolean) { lastRenderTime = Helpers.nextNum if (sendAll) { cachedFixedRender.reset } if (sendAll || !cacheFixedRender) { clearWiringDependencies() } wasLastFullRender = sendAll & hasOuter deltas = Nil if (!dontCacheRendering) { lastRendering = render ++ jsonInCode } theSession.updateFunctionMap(S.functionMap, spanId, lastRenderTime) val rendered: AnswerRender = AnswerRender(new XmlOrJsCmd(spanId, lastRendering, buildSpan _, notices toList), this, lastRenderTime, sendAll) clearNotices listeners.foreach(_._2(rendered)) listeners = Nil } def unWatch = partialUpdate(Call("liftComet.lift_unlistWatch", uniqueId)) /** * Poke the CometActor and cause it to do a partial update Noop which * will have the effect of causing the component to redisplay any * Wiring elements on the component. * This method is Actor-safe and may be called from any thread, not * just the Actor's message handler thread. */ override def poke(): Unit = { if (running) { partialUpdate(Noop) } } /** * Perform a partial update of the comet component based * on the JsCmd. This means that the JsCmd will be sent to * all of the currently listening browser tabs. This is the * preferred method over reRender to update the component */ protected def partialUpdate(cmd: => JsCmd) { this ! PartialUpdateMsg(() => cmd) } protected def startQuestion(what: Any) {} /** * This method will be called after the Actor has started. Do any setup here. * DO NOT do initialization in the constructor or in initCometActor... do it here. */ protected def localSetup(): Unit = {} /** * Comet Actors live outside the HTTP request/response cycle. * However, it may be useful to know what Request led to the * creation of the CometActor. You can override this method * and capture the initial Req object. Note that keeping a reference * to the Req may lead to memory retention issues if the Req contains * large message bodies, etc. It's optimal to capture the path * or capture any request parameters that you care about rather * the keeping the whole Req reference. */ protected def captureInitialReq(initialReq: Box[Req]) {} private def _localShutdown() { localShutdown() clearNotices listeners = Nil askingWho = Empty whosAsking = Empty deltas = Nil jsonHandlerChain = Map.empty _running = false _shutDownAt = millis } /** * This method will be called as part of the shut-down of the actor. Release any resources here. */ protected def localShutdown(): Unit = {} /** * Compose the Message Handler function. By default, * composes highPriority orElse mediumePriority orElse internalHandler orElse * lowPriority orElse internalHandler. But you can change how * the handler works if doing stuff in highPriority, mediumPriority and * lowPriority is not enough */ protected def composeFunction: PartialFunction[Any, Unit] = composeFunction_i private def composeFunction_i: PartialFunction[Any, Unit] = { // if we're no longer running don't pass messages to the other handlers // just pass them to our handlers if (!_running && (millis - 20000L) > _shutDownAt) _mediumPriority orElse _lowPriority else highPriority orElse mediumPriority orElse _mediumPriority orElse lowPriority orElse _lowPriority } /** * A helper for binding which uses the defaultHtml property. */ def bind(prefix: String, vals: BindParam*): NodeSeq = bind(prefix, _defaultHtml, vals: _*) /** * A helper for binding which uses the defaultHtml property and the * default prefix. */ def bind(vals: BindParam*): NodeSeq = bind(_defaultPrefix, vals: _*) /** * Ask another CometActor a question. That other CometActor will * take over the screen real estate until the question is answered. */ protected def ask(who: LiftCometActor, what: Any)(answerWith: Any => Unit) { who.callInitCometActor(theSession, Full(who.uniqueId), name, defaultHtml, attributes) theSession.addCometActor(who) // who.link(this) who ! PerformSetupComet2(Empty) askingWho = Full(who) this.answerWith = Full(answerWith) who ! AskQuestion(what, this, listeners) // this ! AskRender } protected def answer(answer: Any) { whosAsking.foreach(_ !? AnswerQuestion(answer, listeners)) whosAsking = Empty performReRender(false) } /** * Convert a NodeSeq => NodeSeq to a RenderOut. The render method * returns a RenderOut. This method implicitly (in Scala) or explicitly * (in Java) will convert a NodeSeq => NodeSeq to a RenderOut. This * is helpful if you use Lift's CSS Seletor Transforms to define * rendering. */ protected implicit def nsToNsFuncToRenderOut(f: NodeSeq => NodeSeq) = new RenderOut((Box !! defaultHtml).map(f), internalFixedRender, if (autoIncludeJsonCode) Full(jsonToIncludeInCode & S.jsToAppend()) else { S.jsToAppend match { case Nil => Empty case x :: Nil => Full(x) case xs => Full(xs.reduceLeft(_ & _)) } }, Empty, false) /** * Convert a Seq[Node] (the superclass of NodeSeq) to a RenderOut. * The render method * returns a RenderOut. This method implicitly (in Scala) or explicitly * (in Java) will convert a NodeSeq to a RenderOut. This * is helpful if you return a NodeSeq from your render method. */ protected implicit def arrayToRenderOut(in: Seq[Node]): RenderOut = new RenderOut(Full(in: NodeSeq), internalFixedRender, if (autoIncludeJsonCode) Full(jsonToIncludeInCode & S.jsToAppend()) else { S.jsToAppend match { case Nil => Empty case x :: Nil => Full(x) case xs => Full(xs.reduceLeft(_ & _)) } }, Empty, false) protected implicit def jsToXmlOrJsCmd(in: JsCmd): RenderOut = new RenderOut(Empty, internalFixedRender, if (autoIncludeJsonCode) Full(in & jsonToIncludeInCode & S.jsToAppend()) else Full(in & S.jsToAppend()), Empty, false) implicit def pairToPair(in: (String, Any)): (String, NodeSeq) = (in._1, Text(in._2 match {case null => "null" case s => s.toString})) implicit def nodeSeqToFull(in: NodeSeq): Box[NodeSeq] = Full(in) implicit def elemToFull(in: Elem): Box[NodeSeq] = Full(in) /** * Similar with S.error */ def error(n: String) {error(Text(n))} /** * Similar with S.error */ def error(n: NodeSeq) {notices += ((NoticeType.Error, n, Empty))} /** * Similar with S.error */ def error(id: String, n: NodeSeq) {notices += ((NoticeType.Error, n, Full(id)))} /** * Similar with S.error */ def error(id: String, n: String) {error(id, Text(n))} /** * Similar with S.notice */ def notice(n: String) {notice(Text(n))} /** * Similar with S.notice */ def notice(n: NodeSeq) {notices += ((NoticeType.Notice, n, Empty))} /** * Similar with S.notice */ def notice(id: String, n: NodeSeq) {notices += ((NoticeType.Notice, n, Full(id)))} /** * Similar with S.notice */ def notice(id: String, n: String) {notice(id, Text(n))} /** * Similar with S.warning */ def warning(n: String) {warning(Text(n))} /** * Similar with S.warning */ def warning(n: NodeSeq) {notices += ((NoticeType.Warning, n, Empty))} /** * Similar with S.warning */ def warning(id: String, n: NodeSeq) {notices += ((NoticeType.Warning, n, Full(id)))} /** * Similar with S.warning */ def warning(id: String, n: String) {warning(id, Text(n))} private def clearNotices {notices clear} } abstract class Delta(val when: Long) { def js: JsCmd val timestamp = millis } case class JsDelta(override val when: Long, js: JsCmd) extends Delta(when) sealed abstract class CometMessage /** * Impersonates the actual comet response content */ private[http] class XmlOrJsCmd(val id: String, _xml: Box[NodeSeq], _fixedXhtml: Box[NodeSeq], val javaScript: Box[JsCmd], val destroy: Box[JsCmd], spanFunc: (Long, NodeSeq) => NodeSeq, ignoreHtmlOnJs: Boolean, notices: List[(NoticeType.Value, NodeSeq, Box[String])]) { def this(id: String, ro: RenderOut, spanFunc: (Long, NodeSeq) => NodeSeq, notices: List[(NoticeType.Value, NodeSeq, Box[String])]) = this (id, ro.xhtml, ro.fixedXhtml, ro.script, ro.destroyScript, spanFunc, ro.ignoreHtmlOnJs, notices) val xml = _xml.flatMap(content => S.session.map(s => s.processSurroundAndInclude("JS SetHTML id: " + id, content))) val fixedXhtml = _fixedXhtml.flatMap(content => S.session.map(s => s.processSurroundAndInclude("JS SetHTML id: " + id, content))) /** * Returns the JsCmd that will be sent to client */ def toJavaScript(session: LiftSession, displayAll: Boolean): JsCmd = { var ret: JsCmd = JsCmds.JsTry(JsCmds.Run("destroy_" + id + "();"), false) & ((if (ignoreHtmlOnJs) Empty else xml, javaScript, displayAll) match { case (Full(xml), Full(js), false) => LiftRules.jsArtifacts.setHtml(id, Helpers.stripHead(xml)) & JsCmds.JsTry(js, false) case (Full(xml), _, false) => LiftRules.jsArtifacts.setHtml(id, Helpers.stripHead(xml)) case (Full(xml), Full(js), true) => LiftRules.jsArtifacts.setHtml(id + "_outer", ( spanFunc(0, Helpers.stripHead(xml)) ++ fixedXhtml.openOr(Text("")))) & JsCmds.JsTry(js, false) case (Full(xml), _, true) => LiftRules.jsArtifacts.setHtml(id + "_outer", ( spanFunc(0, Helpers.stripHead(xml)) ++ fixedXhtml.openOr(Text("")))) case (_, Full(js), _) => js case _ => JsCmds.Noop }) & JsCmds.JsTry(JsCmds.Run("destroy_" + id + " = function() {" + (destroy.openOr(JsCmds.Noop).toJsCmd) + "};"), false) S.appendNotices(notices) ret = S.noticesToJsCmd & ret ret } def inSpan: NodeSeq = xml.openOr(Text("")) ++ javaScript.map(s => Script(s)).openOr(Text("")) def outSpan: NodeSeq = Script(Run("var destroy_" + id + " = function() {" + (destroy.openOr(JsCmds.Noop).toJsCmd) + "}")) ++ fixedXhtml.openOr(Text("")) } /** * Update the comet XML on each page reload in dev mode */ case class UpdateDefaultXml(xml: NodeSeq) extends CometMessage case class PartialUpdateMsg(cmd: () => JsCmd) extends CometMessage case object AskRender extends CometMessage case class AnswerRender(response: XmlOrJsCmd, who: LiftCometActor, when: Long, displayAll: Boolean) extends CometMessage case class PerformSetupComet2(initialReq: Box[Req]) extends CometMessage case object ShutdownIfPastLifespan extends CometMessage case class AskQuestion(what: Any, who: LiftCometActor, listeners: List[(ListenerId, AnswerRender => Unit)]) extends CometMessage case class AnswerQuestion(what: Any, listeners: List[(ListenerId, AnswerRender => Unit)]) extends CometMessage case class Listen(when: Long, uniqueId: ListenerId, action: AnswerRender => Unit) extends CometMessage case class Unlisten(uniqueId: ListenerId) extends CometMessage case class ActionMessageSet(msg: List[() => Any], req: Req) extends CometMessage case class ReRender(doAll: Boolean) extends CometMessage case class ListenerId(id: Long) case class Error(id: Box[String], msg: NodeSeq) extends CometMessage case class Warning(id: Box[String], msg: NodeSeq) extends CometMessage case class Notice(id: Box[String], msg: NodeSeq) extends CometMessage case object ClearNotices extends CometMessage object Error { def apply(node: NodeSeq): Error = Error(Empty, node) def apply(node: String): Error = Error(Empty, Text(node)) def apply(id: String, node: String): Error = Error(Full(id), Text(node)) def apply(id: String, node: NodeSeq): Error = Error(Full(id), node) } object Warning { def apply(node: NodeSeq): Warning = Warning(Empty, node) def apply(node: String): Warning = Warning(Empty, Text(node)) def apply(id: String, node: String): Warning = Warning(Full(id), Text(node)) def apply(id: String, node: NodeSeq): Warning = Warning(Full(id), node) } object Notice { def apply(node: NodeSeq): Notice = Notice(Empty, node) def apply(node: String): Notice = Notice(Empty, Text(node)) def apply(id: String, node: String): Notice = Notice(Full(id), Text(node)) def apply(id: String, node: NodeSeq): Notice = Notice(Full(id), node) } /** * The RenderOut case class contains the rendering for the CometActor. * Because of the implicit conversions, RenderOut can come from * <br/> * @param xhtml is the "normal" render body * @param fixedXhtml is the "fixed" part of the body. This is ignored unless reRender(true) * @param script is the script to be executed on render. This is where you want to put your script * @param destroyScript is executed when the comet widget is redrawn ( e.g., if you register drag or mouse-over or some events, you unregister them here so the page doesn't leak resources.) * @param ignoreHtmlOnJs -- if the reason for sending the render is a Comet update, ignore the xhtml part and just run the JS commands. This is useful in IE when you need to redraw the stuff inside <table>
... just doing innerHtml on
... this post is sponsored by my books ...

#1 New Release!

FP Best Seller

 

new blog posts

 

Copyright 1998-2024 Alvin Alexander, alvinalexander.com
All Rights Reserved.

A percentage of advertising revenue from
pages under the /java/jwarehouse URI on this website is
paid back to open source projects.