| 
Lift Framework example source code file (CometActor.scala)
 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  | Other Lift Framework examples (source code examples)Here is a short list of links related to this Lift Framework CometActor.scala source code file:  | 
| ... this post is sponsored by my books ... | |
         
           #1 New Release!  | 
      
         
           FP Best Seller  | 
  
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.