Skip to content
Browse files

Got the Wiring stuff fully integrated with Comet and added some goodi…

…es on the way
  • Loading branch information...
1 parent 3927fcc commit 603db8bda2b1329e2355321b80229198688379c2 @dpp dpp committed
View
22 core/util/src/main/scala/net/liftweb/util/ListHelpers.scala
@@ -41,7 +41,25 @@ final case class InsertAfterDelta[T](item: T, after: T) extends DeltaInfo[T]
*/
trait ListHelpers {
- def delta[T, Res](old: List[T], newList: List[T])(f: DeltaInfo[T] => Res): List[Res] = {
+ /**
+ * Compute the deltas between two sequences of a given type.
+ * Apply the function based on the differences between the two
+ * lists. The resulting List of commands will be returned.
+ */
+ def delta[T, Res](old: Box[Seq[T]], newList: Seq[T])(f: DeltaInfo[T] => Res): List[Res] = delta(old openOr Nil, newList)(f)
+
+
+ /**
+ * Compute the deltas between two sequences of a given type.
+ * Apply the function based on the differences between the two
+ * lists. The resulting List of commands will be returned.
+ * The algorithm used to calculate the diffs is not very efficient
+ * and can degrade to O(n^2), so it's not great for large collections.
+ * Internally the Seq[T] are converted to a List[T]. Finally,
+ * it's highly recommended that T be immutable and does proper equals
+ * testing (e.g., a case class).
+ */
+ def delta[T, Res](old: Seq[T], newList: Seq[T])(f: DeltaInfo[T] => Res): List[Res] = {
import scala.collection.mutable.ListBuffer
import scala.annotation._
@@ -83,7 +101,7 @@ trait ListHelpers {
}
}
- loop(old, newList)
+ loop(old.toList, newList.toList)
ret.toList
}
View
21 core/util/src/main/scala/net/liftweb/util/ValueHolder.scala
@@ -25,10 +25,11 @@ trait ValueHolder {
*
* @deprecated
*/
+ @deprecated("Use get")
def is: ValueType
/**
- * An alternative way to get the value
+ * get the value
*/
def get: ValueType
}
@@ -38,6 +39,24 @@ trait ValueHolder {
*/
trait Settable extends ValueHolder {
def set(in: ValueType): ValueType
+
+ /**
+ * Perform an atomic update of this Settable.
+ * The current value is passed to the function and the ValueHolder
+ * is set to the result of the function. This is enclosed in the
+ * performAtomicOperation method which will, by default, synchronize
+ * this instance
+ */
+ def atomicUpdate(f: ValueType => ValueType): ValueType =
+ performAtomicOperation(set(f(get)))
+
+ /**
+ * Perform an atomic operation on the Settable. By default
+ * synchronizes the instance, but it could use other mechanisms
+ */
+ def performAtomicOperation[T](f: => T): T = synchronized {
+ f
+ }
}
trait SettableValueHolder extends Settable
View
108 web/webkit/src/main/scala/net/liftweb/http/CometActor.scala
@@ -684,6 +684,37 @@ trait CometActor extends LiftActor with LiftCometActor with BindHelpers {
*/
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
@@ -750,16 +781,22 @@ trait CometActor extends LiftActor with LiftCometActor with BindHelpers {
_defaultHtml = xml
- if (redo) performReRender(false)
+ 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 _ => {
+ 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) =>
@@ -779,20 +816,18 @@ trait CometActor extends LiftActor with LiftCometActor with BindHelpers {
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)
+ askingWho.foreach {
+ ah => {
+ reply("A null message to release the actor from its send and await reply... do not delete this message")
+ ah ! ShutDown
+ this.listeners = this.listeners ::: otherListeners
+ this.askingWho = Empty
+ val aw = answerWith
+ answerWith = Empty
+ aw.foreach(_(what))
+ performReRender(true)
}
- //}
+ }
case ShutdownIfPastLifespan =>
for{
@@ -826,11 +861,10 @@ trait CometActor extends LiftActor with LiftCometActor with BindHelpers {
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 &
- theSession.
- postPageJavaScript()),
+ Full(cmd & postPage),
Empty, buildSpan, false,
notices toList),
whosAsking openOr this, time, false)
@@ -893,8 +927,30 @@ trait CometActor extends LiftActor with LiftCometActor with BindHelpers {
*/
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() {
+ theSession.clearPostPageJavaScriptForThisPage()
+ unregisterFromAllDepenencies()
+ }
+
private def performReRender(sendAll: Boolean) {
lastRenderTime = Helpers.nextNum
+
+ if (sendAll) {
+ cachedFixedRender.reset
+ }
+
+ if (sendAll || !cacheFixedRender) {
+ clearWiringDependencies()
+ }
+
wasLastFullRender = sendAll & hasOuter
deltas = Nil
@@ -923,7 +979,9 @@ trait CometActor extends LiftActor with LiftCometActor with BindHelpers {
* just the Actor's message handler thread.
*/
override def poke(): Unit = {
- if (running) partialUpdate(Noop)
+ if (running) {
+ partialUpdate(Noop)
+ }
}
/**
@@ -1019,7 +1077,7 @@ trait CometActor extends LiftActor with LiftCometActor with BindHelpers {
* rendering.
*/
protected implicit def nsToNsFuncToRenderOut(f: NodeSeq => NodeSeq) =
- new RenderOut((Box !! defaultHtml).map(f), fixedRender, if (autoIncludeJsonCode) Full(jsonToIncludeInCode & S.jsToAppend()) else {
+ 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)
@@ -1034,7 +1092,7 @@ trait CometActor extends LiftActor with LiftCometActor with BindHelpers {
* (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), fixedRender, if (autoIncludeJsonCode) Full(jsonToIncludeInCode & S.jsToAppend()) else {
+ 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)
@@ -1042,7 +1100,7 @@ trait CometActor extends LiftActor with LiftCometActor with BindHelpers {
}
}, Empty, false)
- protected implicit def jsToXmlOrJsCmd(in: JsCmd): RenderOut = new RenderOut(Empty, fixedRender, if (autoIncludeJsonCode) Full(in & jsonToIncludeInCode & S.jsToAppend()) else Full(in & S.jsToAppend()), 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}))
View
15 web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala
@@ -801,6 +801,21 @@ class LiftSession(private[http] val _contextPath: String, val uniqueId: String,
}
/**
+ * Clear the PostPage JavaScript functions for the current page.
+ * This is used by CometActor to remove the PostPage JavaScript
+ * functions from the given component during redraw.
+ */
+ def clearPostPageJavaScriptForThisPage() {
+ testStatefulFeature {
+ accessPostPageFuncs {
+ val rv: String = RenderVersion.get
+
+ postPageFunctions -= rv
+ }
+ }
+ }
+
+ /**
* Associate a function that renders JavaScript with the current page.
* This function will be run and the resulting JavaScript will be appended
* to any rendering associated with this page... the normal page render,
View
2 web/webkit/src/main/scala/net/liftweb/http/js/JsCommands.scala
@@ -644,6 +644,8 @@ trait JsCmd extends HtmlFixer with ToJsCmd {
def &(other: JsCmd): JsCmd = JsCmds.CmdPair(this, other)
def toJsCmd: String
+
+ override def toString() = "JsCmd("+toJsCmd+")"
}
object JsCmd {
View
59 web/webkit/src/main/scala/net/liftweb/http/js/jquery/JqJsCmds.scala
@@ -92,6 +92,64 @@ object JqWiringSupport {
}
}
}
+
+ /**
+ * Takes two sequences, the id of a containing component and a couple of
+ * functions and generates the jQuery-based JavaScript to update the browser
+ * DOM with the deltas between the old list and the new list.
+ */
+ def calculateDeltas[T](oldList: Seq[T], newList: Seq[T],id: String)(calcId: T => String, calcNodeSeq: T => NodeSeq): JsCmd =
+ calculateDeltas[T](Full(oldList), newList, id)(calcId, calcNodeSeq)
+
+ /**
+ * Takes two sequences, the id of a containing component and a couple of
+ * functions and generates the jQuery-based JavaScript to update the browser
+ * DOM with the deltas between the old list and the new list.
+ *
+ * @param oldList -- the old list. If it is Empty, then it is treated as Nil
+ * @param newList -- the new version of the list of items
+ * @param id -- the id of the enclosing DOM node. Used for appending and inserting DOM nodes
+ * @param calcId -- given a T, calculate the id of the DOM node for the T
+ * @param calcNodeSeq -- given a T, calculate the DOM that represents the T
+ *
+ * @return the JsCmd that inserts, appends, removes, etc. the DOM so that
+ * the DOM represents the new List
+ */
+ def calculateDeltas[T](oldList: Box[Seq[T]], newList: Seq[T],id: String)(calcId: T => String, calcNodeSeq: T => NodeSeq): JsCmd = {
+ Helpers.delta(oldList, newList) {
+ case RemoveDelta(ci) => new JsCmd {
+ def toJsCmd = "jQuery('#'+"+calcId(ci).encJs+").remove();"
+ }
+
+ case AppendDelta(ci) =>
+ new JsCmd {
+ val toJsCmd =
+ fixHtmlFunc("inline", calcNodeSeq(ci)) {
+ "jQuery('#'+"+id.encJs+").append("+
+ _+
+ ");"}
+ }
+
+ case InsertAtStartDelta(ci) =>
+ new JsCmd {
+ val toJsCmd =
+ fixHtmlFunc("inline", calcNodeSeq(ci)) {
+ "jQuery('#'+"+id.encJs+").prepend("+
+ _+
+ ");"}
+ }
+
+ case InsertAfterDelta(ci, prior) =>
+ new JsCmd {
+ val toJsCmd =
+ fixHtmlFunc("inline", calcNodeSeq(ci)) {
+ "jQuery('#'+"+calcId(prior).encJs+").after("+
+ _+
+ ");"}
+ }
+ }
+ }
+
}
object JqJE {
@@ -399,4 +457,3 @@ object JqJsCmds {
}
}
-

0 comments on commit 603db8b

Please sign in to comment.
Something went wrong with that request. Please try again.