/*
* Copyright 2007-2010 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 _root_.net.liftweb.common._
import _root_.net.liftweb.actor._
import _root_.scala.collection.mutable.{ListBuffer}
import _root_.net.liftweb.util.Helpers._
import _root_.net.liftweb.util._
import _root_.net.liftweb.json._
import _root_.scala.xml.{NodeSeq, Text, Elem, Unparsed, Node, Group, Null, PrefixedAttribute, UnprefixedAttribute}
import _root_.scala.collection.immutable.TreeMap
import _root_.scala.collection.mutable.{HashSet, ListBuffer}
import _root_.net.liftweb.http.js._
import JsCmds._
import JE._
import _root_.java.util.concurrent.atomic.AtomicLong
/**
* 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
}
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[Box[LiftCometActor]] {
this.set(Empty)
}
object AddAListener {
def apply(who: SimpleActor[Any]) = new AddAListener(who, { case _ => true } )
}
case class AddAListener(who: SimpleActor[Any], shouldUpdate: PartialFunction[Any, Boolean])
case class RemoveAListener(who: SimpleActor[Any])
object ListenerManager {
type ActorTest = (SimpleActor[Any], PartialFunction[Any, Boolean])
}
import ListenerManager._
/**
* 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.
*/
trait ListenerManager {
self: SimpleActor[Any] =>
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)
}
protected def updateListeners() {
val update = updateIfPassesTest(createUpdate) _
listeners foreach update
}
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 updateListeners method needs a message to send to subscribed
* Actors.
*/
protected def createUpdate: Any
protected def highPriority: PartialFunction[Any, Unit] = Map.empty
protected def mediumPriority: PartialFunction[Any, Unit] = Map.empty
protected def lowPriority: PartialFunction[Any, Unit] = Map.empty
}
trait CometListener extends CometListenee
trait CometListenee extends CometActor {
protected def registerWith: SimpleActor[Any]
/**
* Override this in order to selectively update listeners based on the given message.
*/
protected def shouldUpdate: PartialFunction[Any, Boolean] = { case _ => true}
override protected def localSetup() {
registerWith ! AddAListener(this, shouldUpdate)
super.localSetup()
}
override protected def localShutdown() {
registerWith ! RemoveAListener(this)
super.localShutdown()
}
}
trait LiftCometActor extends TypedActor[Any, Any] with ForwardableActor[Any, Any] {
def uniqueId: String
private[http] def callInitCometActor(theSession: LiftSession,
theType: Box[String],
name: Box[String],
defaultXml: NodeSeq,
attributes: Map[String, String]) {
initCometActor(theSession, theType, name, defaultXml, attributes)
}
protected def initCometActor(theSession: LiftSession,
theType: Box[String],
name: Box[String],
defaultXml: 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
}
/**
* Takes care of the plumbing for building Comet-based Web Apps
*/
@serializable
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
private var lastRendering: RenderOut = _
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
private var _defaultXml: NodeSeq = _
def defaultXml = _defaultXml
private var _name: Box[String] = Empty
def name = _name
private var _theType: Box[String] = Empty
def theType = _theType
private var _attributes: Map[String, String] = Map.empty
def attributes = _attributes
def lifespan: Box[TimeSpan] = Empty
private var _running = true
private var _shutDownAt = millis
/**
* Is the CometActor running?
*/
protected def running = _running
protected def initCometActor(theSession: LiftSession,
theType: Box[String],
name: Box[String],
defaultXml: NodeSeq,
attributes: Map[String, String]) {
lastRendering = RenderOut(Full(defaultXml),
Empty, Empty, Empty, false)
this._theType = theType
this._theSession = theSession
this._defaultXml = defaultXml
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"/>
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), Null)) %
(new PrefixedAttribute("lift", "when", Text(time.toString), Null))
def messageHandler = {
val what = composeFunction
val myPf: PartialFunction[Any, Unit] = new PartialFunction[Any, Unit] {
def apply(in: Any): Unit =
CurrentCometActor.doWith(Full(CometActor.this)) {
S.initIfUninitted(theSession) {
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(Full(CometActor.this)) {
S.initIfUninitted(theSession) {
S.functionLifespan(true) {
what.isDefinedAt(in)
}
}
}
}
myPf
}
def fixedRender: Box[NodeSeq] = Empty
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)
}
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
}
}
}
case PerformSetupComet =>
// this ! RelinkToActorWatcher
localSetup
performReRender(true)
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.functionLifespan(true) {
reply(msgs.map(_()))
}
case AskQuestion(what, who, otherlisteners) =>
this.spanId = who.uniqueId
this.listeners = otherlisteners ::: this.listeners
startQuestion(what)
whosAsking = Full(who)
this.reRender(true)
case AnswerQuestion(what, otherListeners) =>
S.functionLifespan(true) {
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)
}
}
case ShutdownIfPastLifespan =>
for{
ls <- lifespan if (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 rendered = AnswerRender(new XmlOrJsCmd(spanId, Empty, Empty,
Full(cmd), Empty, buildSpan, false, notices toList),
whosAsking openOr this, time, false)
clearNotices
listeners.foreach(_._2(rendered))
listeners = Nil
}
}
/**
* 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.
*/
def render: RenderOut
def reRender(sendAll: Boolean) {
this ! ReRender(sendAll)
}
private def performReRender(sendAll: Boolean) {
lastRenderTime = Helpers.nextNum
wasLastFullRender = sendAll & hasOuter
deltas = Nil
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))
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
*/
protected def localSetup(): Unit = {}
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 = {}
protected def composeFunction = composeFunction_i
private def composeFunction_i = {
// 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
}
def bind(prefix: String, vals: BindParam*): NodeSeq = bind(prefix, _defaultXml, vals: _*)
def bind(vals: BindParam*): NodeSeq = bind(_defaultPrefix, vals: _*)
protected def ask(who: LiftCometActor, what: Any)(answerWith: Any => Unit) {
who.callInitCometActor(theSession, Full(who.uniqueId), name, defaultXml, attributes)
theSession.addCometActor(who)
// who.link(this)
who ! PerformSetupComet
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)
}
implicit def xmlToXmlOrJsCmd(in: NodeSeq): RenderOut = new RenderOut(Full(in), fixedRender, if (autoIncludeJsonCode) Full(jsonToIncludeInCode) else Empty, Empty, false)
implicit def jsToXmlOrJsCmd(in: JsCmd): RenderOut = new RenderOut(Empty, Empty, if (autoIncludeJsonCode) Full(in & jsonToIncludeInCode) else Full(in), 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", (fixedXhtml.openOr(Text("")) ++
spanFunc(0, Helpers.stripHead(xml)))) & JsCmds.JsTry(js, false)
case (Full(xml), _, true) => LiftRules.jsArtifacts.setHtml(id + "_outer", (fixedXhtml.openOr(Text("")) ++
spanFunc(0, Helpers.stripHead(xml))))
case (_, Full(js), _) => js
case _ => JsCmds.Noop
}) & JsCmds.JsTry(JsCmds.Run("destroy_" + id + " = function() {" + (destroy.openOr(JsCmds.Noop).toJsCmd) + "};"), false)
S.messagesFromList(notices toList)
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(""))
}
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 object PerformSetupComet 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)
}
/**
* @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><tr><td>... just doing innerHtml on <tr> 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
}
}