Skip to content

Instantly share code, notes, and snippets.

@alebon
Created September 16, 2013 11:00
Show Gist options
  • Save alebon/6579232 to your computer and use it in GitHub Desktop.
Save alebon/6579232 to your computer and use it in GitHub Desktop.
package demo.js
import net.liftweb.http.js.JsCmd
object JsCommands30 {
/**
* JsSchedule the execution of the JsCmd using setTimeout()
* @param what the code to execute
*/
case class JsSchedule(what: JsCmd) extends JsCmd {
def toJsCmd = s"""setTimeout(function()
{
${what.toJsCmd}
} , 0);"""
}
}
/*
* Copyright 2007-2012 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 js._
import net.liftweb.json.JsonAST.JValue
import net.liftweb.http.js.JE.JsObj
import net.liftweb.http.js.JsCmds._Noop
import scala.xml.NodeSeq
import net.liftweb.common.{Empty, Box}
import net.liftweb.actor.LAScheduler
import net.liftweb.json.{JsonAST, Printer, DefaultFormats, Extraction}
import net.liftweb.util.Helpers
import demo.js.JsCommands30
import net.liftweb.util.Helpers._
import net.liftweb.common.Full
import scala.Some
import net.liftweb.http.js.JE.JsRaw
import net.liftweb.json.JsonAST.JString
object Lift30Session {
/**
* Build a bunch of round-trip calls between the client and the server.
* The client calls the server with a parameter and the parameter gets
* marshalled to the server and the code is executed on the server.
* The result can be an item (JValue) or a Stream of Items.
*
* If the
* The // HERE
*/
def buildRoundtrip(info: Seq[RoundTripInfo], liftSession: LiftSession): JsExp = {
val ca = new CometActor {
/**
* 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
* coercible 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 occurred.) You may want to look at the fixedRender method which is
* only called once and sets up a stable rendering state.
*/
def render: RenderOut = NodeSeq.Empty
//override def lifespan = Full(LiftRules.clientActorLifespan.vend.apply(this))
override def hasOuter = false
override def parentTag = <div style="display: none"/>
override def lowPriority: PartialFunction[Any, Unit] = {
case jsCmd: JsCmd => partialUpdate(JsCommands30.JsSchedule(JsCmds.JsTry(jsCmd, false)))
case jsExp: JsExp => partialUpdate(JsCommands30.JsSchedule(JsCmds.JsTry(jsExp.cmd, false)))
case ItemMsg(guid, value) =>
partialUpdate(JsCommands30.JsSchedule(JsRaw(s"liftAjax.sendEvent(${guid.encJs}, {'success': ${Printer.compact(JsonAST.render(value))}} )").cmd))
case DoneMsg(guid) =>
partialUpdate(JsCommands30.JsSchedule(JsRaw(s"liftAjax.sendEvent(${guid.encJs}, {'done': true} )").cmd))
case FailMsg(guid, msg) =>
partialUpdate(JsCommands30.JsSchedule(JsRaw(s"liftAjax.sendEvent(${guid.encJs}, {'failure': ${msg.encJs} })").cmd))
case _ =>
}
}
//nasyncComponents.put(ca.theType -> ca.name, ca)
//nasyncById.put(ca.uniqueId, ca)
ca.callInitCometActor(S.session.get, Full(Helpers.nextFuncName), Full(Helpers.nextFuncName), NodeSeq.Empty, Map.empty)
implicit val defaultFormats = DefaultFormats
ca ! PerformSetupComet2(Empty)
//ca ! SetDeltaPruner(lastWhenDeltaPruner)
//val node: Elem = ca.buildSpan(ca.renderClock, NodeSeq.Empty)
//S.addCometAtEnd(node)
val currentReq: Box[Req] = S.request.map(_.snapshot)
val renderVersion = RenderVersion.get
val jvmanifest: Manifest[JValue] = implicitly
val map = Map(info.map(i => i.name -> i) :_*)
def fixIt(in: Any): JValue = {
in match {
case jv: JValue => jv
case a => Extraction.decompose(a)
}
}
def localFunc(in: JValue): JsCmd = {
LAScheduler.execute(() => {
liftSession.executeInScope(currentReq, renderVersion)(
for {
JString(guid) <- in \ "guid"
JString(name) <- in \ "name"
func <- map.get(name)
payload = in \ "payload"
reified <- if (func.manifest == jvmanifest) Some(payload) else {
try {Some(payload.extract(defaultFormats, func.manifest))} catch {
case e: Exception =>
//logger.error("Failed to extract "+payload+" as "+func.manifest, e)
ca ! FailMsg(guid, "Failed to extract payload as "+func.manifest+" exception "+ e.getMessage)
None
}
}
} {
func match {
case StreamRoundTrip(_, func) =>
try {
for (v <- func.asInstanceOf[Function1[Any, Stream[Any]]](reified)) {
v match {
case jsCmd: JsCmd => ca ! jsCmd
case jsExp: JsExp => ca ! jsExp
case v => ca ! ItemMsg(guid,fixIt(v))
}
}
ca ! DoneMsg(guid)
} catch {
case e: Exception => ca ! FailMsg(guid, e.getMessage)
}
case SimpleRoundTrip(_, func) =>
try {
func.asInstanceOf[Function1[Any, Any]](reified ) match {
case jsCmd: JsCmd => ca ! jsCmd
case jsExp: JsExp => ca ! jsExp
case v => ca ! ItemMsg(guid, fixIt(v))
}
ca ! DoneMsg(guid)
} catch {
case e: Exception => ca ! FailMsg(guid, e.getMessage)
}
case HandledRoundTrip(_, func) =>
try {
func.asInstanceOf[Function2[Any, RoundTripHandlerFunc, Unit]](reified, new RoundTripHandlerFunc {
@volatile private var done_? = false
def done() {
if (!done_?) {
done_? = true
ca ! DoneMsg(guid)
}
}
def failure(msg: String) {
if (!done_?) {
done_? = true
ca ! FailMsg(guid, msg)
}
}
/**
* Send some JavaScript to execute on the client side
* @param value
*/
def send(value: JsCmd): Unit = {
if (!done_?) {
ca ! value
}
}
/**
* Send some javascript to execute on the client side
* @param value
*/
def send(value: JsExp): Unit = {
if (!done_?) {
ca ! value
}
}
def send(value: JValue) {
if (!done_?) {
ca ! ItemMsg(guid, value)
}
}
})
} catch {
case e: Exception => ca ! FailMsg(guid, e.getMessage)
}
}
})
})
_Noop
}
lazy val theFunc = JsRaw(s"""function(v) {${SHtml.jsonCall(JsRaw("v"), localFunc(_)).toJsCmd}}""")
lazy val build: (String, JsExp) = "_call_server" -> theFunc
JsObj(build :: info.map(info => info.name -> JsRaw(
s"""
|function(param) {
| var promise = new liftAjax.Promise();
| liftAjax.associate(promise);
| this._call_server({guid: promise.guid, name: ${info.name.encJs}, payload: param});
| return promise;
|}
|""".stripMargin)).toList :_*)
}
private case class ItemMsg(guid: String, item: JValue)
private case class DoneMsg(guid: String)
private case class FailMsg(guid: String, msg: String)
}
/**
* Stuff related to round trip messages
*/
sealed trait RoundTripInfo {
def name: String
def manifest: Manifest[_]
}
/**
* The companion objects. Has tasty implicits
*/
object RoundTripInfo {
implicit def streamBuilder[T](in: (String, T => Stream[Any]))(implicit m: Manifest[T]): RoundTripInfo =
StreamRoundTrip(in._1, in._2)(m)
implicit def simpleBuilder[T](in: (String, T => Any))(implicit m: Manifest[T]): RoundTripInfo =
SimpleRoundTrip(in._1, in._2)(m)
implicit def handledBuilder[T](in: (String, (T, RoundTripHandlerFunc) => Unit))(implicit m: Manifest[T]): RoundTripInfo =
HandledRoundTrip(in._1, in._2)(m)
}
/**
* A function (well, an interface with a bunch of methods on it) to call
* depending on the state of the round trip function.
*/
trait RoundTripHandlerFunc {
/**
* Send data back to the client. This may be called
* many times and each time, more data gets sent back to the client.
* @param value the data to send back.
*/
def send(value: JValue): Unit
/**
* Send some JavaScript to execute on the client side
* @param value
*/
def send(value: JsCmd): Unit
/**
* Send some javascript to execute on the client side
* @param value
*/
def send(value: JsExp): Unit
/**
* When you are done sending data back to the client, call this method
*/
def done(): Unit
/**
* If there's a failure related to the computation, call this method.
* @param msg
*/
def failure(msg: String): Unit
}
final case class StreamRoundTrip[T](name: String, func: T => Stream[Any])(implicit val manifest: Manifest[T]) extends RoundTripInfo
final case class SimpleRoundTrip[T](name: String, func: T => Any)(implicit val manifest: Manifest[T]) extends RoundTripInfo
final case class HandledRoundTrip[T](name: String, func: (T, RoundTripHandlerFunc) => Unit)(implicit val manifest: Manifest[T]) extends RoundTripInfo
(function() {
window.liftAjax = {
lift_ajaxQueue: [],
lift_ajaxInProcess: null,
lift_doCycleQueueCnt: 0,
lift_ajaxShowing: false,
lift_ajaxRetryCount: 3,
lift_ajaxHandler: function(theData, theSuccess, theFailure, responseType){
var toSend = {retryCnt: 0};
toSend.when = (new Date()).getTime();
toSend.theData = theData;
toSend.onSuccess = theSuccess;
toSend.onFailure = theFailure;
toSend.responseType = responseType;
toSend.version = liftAjax.lift_ajaxVersion++;
// Make sure we wrap when we hit JS max int.
var version = liftAjax.lift_ajaxVersion
if ((version - (version + 1) != -1) || (version - (version - 1) != 1))
liftAjax.lift_ajaxVersion = 0;
if (liftAjax.lift_uriSuffix) {
theData += '&' + liftAjax.lift_uriSuffix;
toSend.theData = theData;
liftAjax.lift_uriSuffix = undefined;
}
liftAjax.lift_ajaxQueue.push(toSend);
liftAjax.lift_ajaxQueueSort();
liftAjax.lift_doCycleQueueCnt++;
liftAjax.lift_doAjaxCycle();
return false; // buttons in forms don't trigger the form
},
knownPromises: {},
randStr: function() {
return Math.floor((1 + Math.random()) * 0x10000).toString(16).substring(1);},
makeGuid: function() {return this.randStr() + this.randStr() + '-' + this.randStr() + '-' + this.randStr() + '-' +
this.randStr() + '-' + this.randStr() + this.randStr() + this.randStr();},
Promise: function() {
return {
guid: liftAjax.makeGuid(),
"_values": [],
'_events': [],
'_failMsg': "",
'_valueFuncs': [],
'_doneFuncs': [],
'_failureFuncs': [],
'_eventFuncs': [],
'_done': false,
'_failed': false,
processMsg: function(evt) {if (this._done || this._failed) return;
this._events.push(evt);
for (var v in this._eventFuncs) {try {this._eventFuncs[v](evt);} catch (e) {liftAjax.lift_defaultLogError(e);}};
if (evt.done) {this.doneMsg();} else if (evt.success) {this.successMsg(evt.success);} else if (evt.failure) {this.failMsg(evt.failure);}},
successMsg: function(value) {if (this._done || this._failed) return; this._values.push(value); for (var f in this._valueFuncs) {this._valueFuncs[f](value);}},
failMsg: function(msg) {if (this._done || this._failed) return; liftAjax._removeIt(this.guid); this._failed = true; this._failMsg = msg; for (var f in this._failureFuncs) {this._failureFuncs[f](msg);}},
doneMsg: function() {if (this._done || this._failed) return; liftAjax._removeIt(this.guid); this._done = true; for (var f in this._doneFuncs) {this._doneFuncs[f]();}},
then: function(f) {this._valueFuncs.push(f); for (var v in this._values) {try {f(this._values[v]);} catch (e) {liftAjax.lift_defaultLogError(e);;}} return this;},
fail: function(f) {this._failureFuncs.push(f); if (this._failed) {try {f(this._failMsg);} catch (e) {liftAjax.lift_defaultLogError(e);;}}; return this;},
done: function(f) {this._doneFuncs.push(f); if (this._done) {try {f();} catch (e) {liftAjax.lift_defaultLogError(e);;}} return this;},
onEvent: function(f) {this._eventFuncs.push(f); for (var v in this._events) {try {f(this._events[v]);} catch (e) {liftAjax.lift_defaultLogError(e);;}}; return this;},
map: function(f) {var ret = new liftAjax.Promise(); this.done(function() {ret.doneMsg();}); this.fail(function (m) {ret.failMsg(m);}); this.then(function (v) {ret.successMsg(f(v));}); return ret;}
};
},
_removeIt: function(g) {this.knownPromises[g] = undefined;},
sendEvent: function(g, evt) {
var p = this.knownPromises[g];
if (p) {
p.processMsg(evt);
}
},
associate: function(promise) {this.knownPromises[promise.guid] = promise;},
lift_uriSuffix: undefined,
lift_logError: function(msg) {
liftAjax.lift_defaultLogError(msg);
},
lift_defaultLogError: function(msg) {
if (console && typeof console.error == 'function')
console.error(msg);
else
alert(msg);
},
lift_ajaxQueueSort: function() {
liftAjax.lift_ajaxQueue.sort(function (a, b) {return a.when - b.when;});
},
lift_defaultFailure: function() {
alert("The server cannot be contacted at this time");
},
lift_startAjax: function() {
liftAjax.lift_ajaxShowing = true;
jQuery('#'+"ajax-spinner").show();
},
lift_endAjax: function() {
liftAjax.lift_ajaxShowing = false;
jQuery('#'+"ajax-spinner").hide();
},
lift_testAndShowAjax: function() {
if (liftAjax.lift_ajaxShowing && liftAjax.lift_ajaxQueue.length == 0 && liftAjax.lift_ajaxInProcess == null) {
liftAjax.lift_endAjax();
} else if (!liftAjax.lift_ajaxShowing && (liftAjax.lift_ajaxQueue.length > 0 || liftAjax.lift_ajaxInProcess != null)) {
liftAjax.lift_startAjax();
}
},
lift_traverseAndCall: function(node, func) {
if (node.nodeType == 1) func(node);
var i = 0;
var cn = node.childNodes;
for (i = 0; i < cn.length; i++) {
liftAjax.lift_traverseAndCall(cn.item(i), func);
}
},
lift_successRegisterGC: function() {
setTimeout("liftAjax.lift_registerGC()", 75000);
},
lift_failRegisterGC: function() {
setTimeout("liftAjax.lift_registerGC()", 15000);
},
lift_registerGC: function() {
var data = "__lift__GC=_",
version = null;
jQuery.ajax({ url : liftAjax.addPageNameAndVersion("/ajax_request/", version), data : data, type : "POST", dataType : "script", timeout : 5000, cache : false, success : liftAjax.lift_successRegisterGC, error : liftAjax.lift_failRegisterGC });
},
lift_sessionLost: function() {
location.reload();
},
lift_doAjaxCycle: function() {
if (liftAjax.lift_doCycleQueueCnt > 0) liftAjax.lift_doCycleQueueCnt--;
var queue = liftAjax.lift_ajaxQueue;
if (queue.length > 0) {
var now = (new Date()).getTime();
if (liftAjax.lift_ajaxInProcess == null && queue[0].when <= now) {
var aboutToSend = queue.shift();
liftAjax.lift_ajaxInProcess = aboutToSend;
var successFunc = function(data) {
liftAjax.lift_ajaxInProcess = null;
if (aboutToSend.onSuccess) {
aboutToSend.onSuccess(data);
}
liftAjax.lift_doCycleQueueCnt++;
liftAjax.lift_doAjaxCycle();
};
var failureFunc = function() {
liftAjax.lift_ajaxInProcess = null;
var cnt = aboutToSend.retryCnt;
if (arguments.length == 3 && arguments[1] == 'parsererror') {
liftAjax.lift_logError('The server call succeeded, but the returned Javascript contains an error: '+arguments[2])
} else
if (cnt < liftAjax.lift_ajaxRetryCount) {
aboutToSend.retryCnt = cnt + 1;
var now = (new Date()).getTime();
aboutToSend.when = now + (1000 * Math.pow(2, cnt));
queue.push(aboutToSend);
liftAjax.lift_ajaxQueueSort();
} else {
if (aboutToSend.onFailure) {
aboutToSend.onFailure();
} else {
liftAjax.lift_defaultFailure();
}
}
liftAjax.lift_doCycleQueueCnt++;
liftAjax.lift_doAjaxCycle();
};
if (aboutToSend.responseType != undefined &&
aboutToSend.responseType != null &&
aboutToSend.responseType.toLowerCase() === "json") {
liftAjax.lift_actualJSONCall(aboutToSend.theData, successFunc, failureFunc);
} else {
var theData = aboutToSend.theData,
version = aboutToSend.version;
liftAjax.lift_actualAjaxCall(theData, version, successFunc, failureFunc);
}
}
}
liftAjax.lift_testAndShowAjax();
if (liftAjax.lift_doCycleQueueCnt <= 0) liftAjax.lift_doCycleIn200()
},
lift_doCycleIn200: function() {
liftAjax.lift_doCycleQueueCnt++;
setTimeout("liftAjax.lift_doAjaxCycle();", 200);
},
lift_ajaxVersion: 0,
addPageNameAndVersion: function(url, version) {
var replacement = 'ajax_request/'+lift_page;
if (version!=null)
replacement += ('-'+version.toString(36)) + (liftAjax.lift_ajaxQueue.length > 35 ? 35 : liftAjax.lift_ajaxQueue.length).toString(36);
return url.replace('ajax_request', replacement);
},
lift_actualAjaxCall: function(data, version, onSuccess, onFailure) {
jQuery.ajax({ url : liftAjax.addPageNameAndVersion("/ajax_request/", version), data : data, type : "POST", dataType : "script", timeout : 5000, cache : false, success : onSuccess, error : onFailure });
},
lift_actualJSONCall: function(data, onSuccess, onFailure) {
var version = null;
jQuery.ajax({ url : liftAjax.addPageNameAndVersion("/ajax_request/", version), data : data, type : "POST", dataType : "json", timeout : 5000, cache : false, success : onSuccess, error : onFailure });
}
};
window.liftUtils = {
lift_blurIfReturn: function(e) {
var code;
if (!e) var e = window.event;
if (e.keyCode) code = e.keyCode;
else if (e.which) code = e.which;
var targ;
if (e.target) targ = e.target;
else if (e.srcElement) targ = e.srcElement;
if (targ.nodeType == 3) // defeat Safari bug
targ = targ.parentNode;
if (code == 13) {targ.blur(); return false;} else {return true;};
}
};
})();
jQuery(document).ready(function() {liftAjax.lift_doCycleIn200();});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment