diff --git a/code/game/machinery/cryo.dm b/code/game/machinery/cryo.dm
index c8498e2bdd2..c3ab2b9b429 100644
--- a/code/game/machinery/cryo.dm
+++ b/code/game/machinery/cryo.dm
@@ -94,7 +94,7 @@
*
* @return nothing
*/
-/obj/machinery/atmospherics/unary/cryo_cell/ui_interact(mob/user, ui_key = "main")
+/obj/machinery/atmospherics/unary/cryo_cell/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null)
if(user == occupant || user.stat)
return
@@ -141,20 +141,20 @@
for(var/datum/reagent/R in beaker:reagents.reagent_list)
beakerContents.Add(list(list("name" = R.name, "volume" = R.volume))) // list in a list because Byond merges the first list...
data["beakerContents"] = beakerContents
-
- var/datum/nanoui/ui = nanomanager.get_open_ui(user, src, ui_key)
+
+ // update the ui if it exists, returns null if no ui is passed/found
+ ui = nanomanager.try_update_ui(user, src, ui_key, ui, data)
if (!ui)
- // the ui does not exist, so we'll create a new one
+ // the ui does not exist, so we'll create a new() one
+ // for a list of parameters and their descriptions see the code docs in \code\modules\nano\nanoui.dm
ui = new(user, src, ui_key, "cryo.tmpl", "Cryo Cell Control System", 520, 410)
- // When the UI is first opened this is the data it will use
- ui.set_initial_data(data)
+ // when the ui is first opened this is the data it will use
+ ui.set_initial_data(data)
+ // open the new ui window
ui.open()
- // Auto update every Master Controller tick
+ // auto update every Master Controller tick
ui.set_auto_update(1)
- else
- // The UI is already open so push the new data to it
- ui.push_data(data)
- return
+
//user.set_machine(src)
/obj/machinery/atmospherics/unary/cryo_cell/Topic(href, href_list)
diff --git a/code/modules/nano/nanoexternal.dm b/code/modules/nano/nanoexternal.dm
index 8ceb45e6adc..2331debd9be 100644
--- a/code/modules/nano/nanoexternal.dm
+++ b/code/modules/nano/nanoexternal.dm
@@ -31,11 +31,12 @@
* ui_interact is currently defined for /atom/movable
*
* @param user /mob The mob who is interacting with this ui
- * @param ui_key string A string key to use for this ui. Allows for multiple unique uis on one obj/mob (defaut value "main")
+ * @param ui_key string A string key to use for this ui. Allows for multiple unique uis on one obj/mob (defaut value "main")
+ * @param ui /datum/nanoui This parameter is passed by the nanoui process() proc when updating an open ui
*
* @return nothing
*/
-/atom/movable/proc/ui_interact(mob/user, ui_key = "main")
+/atom/movable/proc/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null)
return
// Used by the Nano UI Manager (/datum/nanomanager) to track UIs opened by this mob
diff --git a/code/modules/nano/nanomanager.dm b/code/modules/nano/nanomanager.dm
index c1be3cca799..b7b45060bb7 100644
--- a/code/modules/nano/nanomanager.dm
+++ b/code/modules/nano/nanomanager.dm
@@ -1,8 +1,9 @@
// This is the window/UI manager for Nano UI
// There should only ever be one (global) instance of nanomanger
/datum/nanomanager
- // the list of current open /nanoui UIs
+ // a list of current open /nanoui UIs, grouped by src_object and ui_key
var/open_uis[0]
+ // a list of current open /nanoui UIs, not grouped, for use in processing
var/list/processing_uis = list()
/**
@@ -13,6 +14,29 @@
/datum/nanomanager/New()
return
+ /**
+ * Get an open /nanoui ui for the current user, src_object and ui_key and try to update it with data
+ *
+ * @param user /mob The mob who opened/owns the ui
+ * @param src_object /obj|/mob The obj or mob which the ui belongs to
+ * @param ui_key string A string key used for the ui
+ * @param ui /datum/nanoui An existing instance of the ui (can be null)
+ * @param data list The data to be passed to the ui, if it exists
+ *
+ * @return /nanoui Returns the found ui, for null if none exists
+ */
+/datum/nanomanager/proc/try_update_ui(var/mob/user, src_object, ui_key, var/datum/nanoui/ui, data)
+ if (isnull(ui)) // no ui has been passed, so we'll search for one
+ {
+ ui = get_open_ui(user, src_object, ui_key)
+ }
+ if (!isnull(ui))
+ // The UI is already open so push the data to it
+ ui.push_data(data)
+ return ui
+
+ return null
+
/**
* Get an open /nanoui ui for the current user, src_object and ui_key
*
@@ -20,7 +44,7 @@
* @param src_object /obj|/mob The obj or mob which the ui belongs to
* @param ui_key string A string key used for the ui
*
- * @return /nanoui Returns the found ui, for null if none exists
+ * @return /nanoui Returns the found ui, or null if none exists
*/
/datum/nanomanager/proc/get_open_ui(var/mob/user, src_object, ui_key)
var/src_object_key = "\ref[src_object]"
@@ -38,7 +62,7 @@
/**
* Update all /nanoui uis attached to src_object
*
- * @param src_object /obj|/mob The obj or mob which the uis belong to
+ * @param src_object /obj|/mob The obj or mob which the uis are attached to
*
* @return int The number of uis updated
*/
@@ -54,6 +78,48 @@
ui.process(1)
update_count++
return update_count
+
+ /**
+ * Update /nanoui uis belonging to user
+ *
+ * @param user /mob The mob who owns the uis
+ * @param src_object /obj|/mob If src_object is provided, only update uis which are attached to src_object (optional)
+ * @param ui_key string If ui_key is provided, only update uis with a matching ui_key (optional)
+ *
+ * @return int The number of uis updated
+ */
+/datum/nanomanager/proc/update_user_uis(var/mob/user, src_object = null, ui_key = null)
+ if (isnull(user.open_uis) || !istype(user.open_uis, /list) || open_uis.len == 0)
+ return 0 // has no open uis
+
+ var/update_count = 0
+ for (var/datum/nanoui/ui in user.open_uis)
+ if ((isnull(src_object) || !isnull(src_object) && ui.src_object == src_object) && (isnull(ui_key) || !isnull(ui_key) && ui.ui_key == ui_key))
+ ui.process(1)
+ update_count++
+
+ return update_count
+
+ /**
+ * Close /nanoui uis belonging to user
+ *
+ * @param user /mob The mob who owns the uis
+ * @param src_object /obj|/mob If src_object is provided, only close uis which are attached to src_object (optional)
+ * @param ui_key string If ui_key is provided, only close uis with a matching ui_key (optional)
+ *
+ * @return int The number of uis closed
+ */
+/datum/nanomanager/proc/close_user_uis(var/mob/user, src_object = null, ui_key = null)
+ if (isnull(user.open_uis) || !istype(user.open_uis, /list) || open_uis.len == 0)
+ return 0 // has no open uis
+
+ var/close_count = 0
+ for (var/datum/nanoui/ui in user.open_uis)
+ if ((isnull(src_object) || !isnull(src_object) && ui.src_object == src_object) && (isnull(ui_key) || !isnull(ui_key) && ui.ui_key == ui_key))
+ ui.close()
+ close_count++
+
+ return close_count
/**
* Add a /nanoui ui to the list of open uis
@@ -106,11 +172,7 @@
//
/datum/nanomanager/proc/user_logout(var/mob/user)
- if (isnull(user.open_uis) || !istype(user.open_uis, /list) || open_uis.len == 0)
- return 0 // has no open uis
-
- for (var/datum/nanoui/ui in user.open_uis)
- ui.close();
+ return close_user_uis(user)
/**
* This is called when a player transfers from one mob to another
@@ -133,5 +195,6 @@
newMob.open_uis.Add(ui)
oldMob.open_uis.Cut()
-
+
+ return 1 // success
diff --git a/code/modules/nano/nanoui.dm b/code/modules/nano/nanoui.dm
index d488e8b5c70..7b684aefb25 100644
--- a/code/modules/nano/nanoui.dm
+++ b/code/modules/nano/nanoui.dm
@@ -92,8 +92,8 @@ nanoui is used to open and update nano browser uis
*/
/datum/nanoui/proc/add_common_assets()
add_script("libraries.min.js") // The jQuery library
+ add_script("nano_config.js") // The NanoConfig JS, this is used to store configuration values.
add_script("nano_update.js") // The NanoUpdate JS, this is used to receive updates and apply them.
- add_script("nano_config.js") // The NanoUpdate JS, this is used to receive updates and apply them.
add_script("nano_base_helpers.js") // The NanoBaseHelpers JS, this is used to set up template helpers which are common to all templates
add_stylesheet("shared.css") // this CSS sheet is common to all UIs
add_stylesheet("icons.css") // this CSS sheet is common to all UIs
@@ -256,8 +256,12 @@ nanoui is used to open and update nano browser uis
*/
/datum/nanoui/proc/get_header()
var/head_content = ""
+
+ for (var/filename in scripts)
+ head_content += " "
+
for (var/filename in stylesheets)
- head_content += ""
+ head_content += " "
var/templatel_data[0]
for (var/key in templates)
@@ -277,35 +281,24 @@ nanoui is used to open and update nano browser uis
- [head_content]
-
-
+ [head_content]
+
+
[title ? "
[title]
" : ""]
+
Initiating...
"}
/**
@@ -314,13 +307,8 @@ nanoui is used to open and update nano browser uis
* @return string HTML footer content
*/
/datum/nanoui/proc/get_footer()
- var/scriptsContent = ""
-
- for (var/filename in scripts)
- scriptsContent += ""
return {"
- [scriptsContent]
a";cd=b3.getElementsByTagName("*")||[];cb=b3.getElementsByTagName("a")[0];if(!cb||!cb.style||!cd.length){return ce}cc=l.createElement("select");b5=cc.appendChild(l.createElement("option"));ca=b3.getElementsByTagName("input")[0];cb.style.cssText="top:1px;float:left;opacity:.5";ce.getSetAttribute=b3.className!=="t";ce.leadingWhitespace=b3.firstChild.nodeType===3;ce.tbody=!b3.getElementsByTagName("tbody").length;ce.htmlSerialize=!!b3.getElementsByTagName("link").length;ce.style=/top/.test(cb.getAttribute("style"));ce.hrefNormalized=cb.getAttribute("href")==="/a";ce.opacity=/^0.5/.test(cb.style.opacity);ce.cssFloat=!!cb.style.cssFloat;ce.checkOn=!!ca.value;ce.optSelected=b5.selected;ce.enctype=!!l.createElement("form").enctype;ce.html5Clone=l.createElement("nav").cloneNode(true).outerHTML!=="<:nav>";ce.inlineBlockNeedsLayout=false;ce.shrinkWrapBlocks=false;ce.pixelPosition=false;ce.deleteExpando=true;ce.noCloneEvent=true;ce.reliableMarginRight=true;ce.boxSizingReliable=true;ca.checked=true;ce.noCloneChecked=ca.cloneNode(true).checked;cc.disabled=true;ce.optDisabled=!b5.disabled;try{delete b3.test}catch(b8){ce.deleteExpando=false}ca=l.createElement("input");ca.setAttribute("value","");ce.input=ca.getAttribute("value")==="";ca.value="t";ca.setAttribute("type","radio");ce.radioValue=ca.value==="t";ca.setAttribute("checked","t");ca.setAttribute("name","t");b9=l.createDocumentFragment();b9.appendChild(ca);ce.appendChecked=ca.checked;ce.checkClone=b9.cloneNode(true).cloneNode(true).lastChild.checked;if(b3.attachEvent){b3.attachEvent("onclick",function(){ce.noCloneEvent=false});b3.cloneNode(true).click()}for(b6 in {submit:true,change:true,focusin:true}){b3.setAttribute(b7="on"+b6,"t");ce[b6+"Bubbles"]=b7 in a1||b3.attributes[b7].expando===false}b3.style.backgroundClip="content-box";b3.cloneNode(true).style.backgroundClip="";ce.clearCloneStyle=b3.style.backgroundClip==="content-box";for(b6 in bI(ce)){break}ce.ownLast=b6!=="0";bI(function(){var cf,ci,ch,cg="padding:0;margin:0;border:0;display:block;box-sizing:content-box;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;",e=l.getElementsByTagName("body")[0];if(!e){return}cf=l.createElement("div");cf.style.cssText="border:0;width:0;height:0;position:absolute;top:0;left:-9999px;margin-top:1px";e.appendChild(cf).appendChild(b3);b3.innerHTML="
").append(bI.parseHTML(ca)).find(e):ca)}).complete(b9&&function(cb,ca){b3.each(b9,b4||[cb.responseText,ca,cb])})}return this};bI.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(e,b3){bI.fn[b3]=function(b4){return this.on(b3,b4)}});bI.extend({active:0,lastModified:{},etag:{},ajaxSettings:{url:W,type:"GET",isLocal:A.test(b1[1]),global:true,processData:true,async:true,contentType:"application/x-www-form-urlencoded; charset=UTF-8",accepts:{"*":aU,text:"text/plain",html:"text/html",xml:"application/xml, text/xml",json:"application/json, text/javascript"},contents:{xml:/xml/,html:/html/,json:/json/},responseFields:{xml:"responseXML",text:"responseText",json:"responseJSON"},converters:{"* text":String,"text html":true,"text json":bI.parseJSON,"text xml":bI.parseXML},flatOptions:{url:true,context:true}},ajaxSetup:function(b3,e){return e?r(r(b3,bI.ajaxSettings),e):r(bI.ajaxSettings,b3)},ajaxPrefilter:bK(v),ajaxTransport:bK(a6),ajax:function(b7,b4){if(typeof b7==="object"){b4=b7;b7=aE}b4=b4||{};var cg,ci,b8,cn,cc,b3,cj,b5,cb=bI.ajaxSetup({},b4),cp=cb.context||cb,ce=cb.context&&(cp.nodeType||cp.jquery)?bI(cp):bI.event,co=bI.Deferred(),cl=bI.Callbacks("once memory"),b9=cb.statusCode||{},cf={},cm={},b6=0,ca="canceled",ch={readyState:0,getResponseHeader:function(cq){var e;if(b6===2){if(!b5){b5={};while((e=ae.exec(cn))){b5[e[1].toLowerCase()]=e[2]}}e=b5[cq.toLowerCase()]}return e==null?null:e},getAllResponseHeaders:function(){return b6===2?cn:null},setRequestHeader:function(cq,cr){var e=cq.toLowerCase();if(!b6){cq=cm[e]=cm[e]||cq;cf[cq]=cr}return this},overrideMimeType:function(e){if(!b6){cb.mimeType=e}return this},statusCode:function(cq){var e;if(cq){if(b6<2){for(e in cq){b9[e]=[b9[e],cq[e]]}}else{ch.always(cq[ch.status])}}return this},abort:function(cq){var e=cq||ca;if(cj){cj.abort(e)}cd(0,e);return this}};co.promise(ch).complete=cl.add;ch.success=ch.done;ch.error=ch.fail;cb.url=((b7||cb.url||W)+"").replace(an,"").replace(aF,b1[1]+"//");cb.type=b4.method||b4.type||cb.method||cb.type;cb.dataTypes=bI.trim(cb.dataType||"*").toLowerCase().match(aa)||[""];if(cb.crossDomain==null){cg=aS.exec(cb.url.toLowerCase());cb.crossDomain=!!(cg&&(cg[1]!==b1[1]||cg[2]!==b1[2]||(cg[3]||(cg[1]==="http:"?"80":"443"))!==(b1[3]||(b1[1]==="http:"?"80":"443"))))}if(cb.data&&cb.processData&&typeof cb.data!=="string"){cb.data=bI.param(cb.data,cb.traditional)}n(v,cb,b4,ch);if(b6===2){return ch}b3=cb.global;if(b3&&bI.active++===0){bI.event.trigger("ajaxStart")}cb.type=cb.type.toUpperCase();cb.hasContent=!o.test(cb.type);b8=cb.url;if(!cb.hasContent){if(cb.data){b8=(cb.url+=(ax.test(b8)?"&":"?")+cb.data);delete cb.data}if(cb.cache===false){cb.url=M.test(b8)?b8.replace(M,"$1_="+bN++):b8+(ax.test(b8)?"&":"?")+"_="+bN++}}if(cb.ifModified){if(bI.lastModified[b8]){ch.setRequestHeader("If-Modified-Since",bI.lastModified[b8])}if(bI.etag[b8]){ch.setRequestHeader("If-None-Match",bI.etag[b8])}}if(cb.data&&cb.hasContent&&cb.contentType!==false||b4.contentType){ch.setRequestHeader("Content-Type",cb.contentType)}ch.setRequestHeader("Accept",cb.dataTypes[0]&&cb.accepts[cb.dataTypes[0]]?cb.accepts[cb.dataTypes[0]]+(cb.dataTypes[0]!=="*"?", "+aU+"; q=0.01":""):cb.accepts["*"]);for(ci in cb.headers){ch.setRequestHeader(ci,cb.headers[ci])}if(cb.beforeSend&&(cb.beforeSend.call(cp,ch,cb)===false||b6===2)){return ch.abort()}ca="abort";for(ci in {success:1,error:1,complete:1}){ch[ci](cb[ci])}cj=n(a6,cb,b4,ch);if(!cj){cd(-1,"No Transport")}else{ch.readyState=1;if(b3){ce.trigger("ajaxSend",[ch,cb])}if(cb.async&&cb.timeout>0){cc=setTimeout(function(){ch.abort("timeout")},cb.timeout)}try{b6=1;cj.send(cf,cd)}catch(ck){if(b6<2){cd(-1,ck)}else{throw ck}}}function cd(cu,cq,cv,cs){var e,cy,cw,ct,cx,cr=cq;if(b6===2){return}b6=2;if(cc){clearTimeout(cc)}cj=aE;cn=cs||"";ch.readyState=cu>0?4:0;e=cu>=200&&cu<300||cu===304;if(cv){ct=g(cb,ch,cv)}ct=ad(cb,ct,ch,e);if(e){if(cb.ifModified){cx=ch.getResponseHeader("Last-Modified");if(cx){bI.lastModified[b8]=cx}cx=ch.getResponseHeader("etag");if(cx){bI.etag[b8]=cx}}if(cu===204||cb.type==="HEAD"){cr="nocontent"}else{if(cu===304){cr="notmodified"}else{cr=ct.state;cy=ct.data;cw=ct.error;e=!cw}}}else{cw=cr;if(cu||!cr){cr="error";if(cu<0){cu=0}}}ch.status=cu;ch.statusText=(cq||cr)+"";if(e){co.resolveWith(cp,[cy,cr,ch])}else{co.rejectWith(cp,[ch,cr,cw])}ch.statusCode(b9);b9=aE;if(b3){ce.trigger(e?"ajaxSuccess":"ajaxError",[ch,cb,e?cy:cw])}cl.fireWith(cp,[ch,cr]);if(b3){ce.trigger("ajaxComplete",[ch,cb]);if(!(--bI.active)){bI.event.trigger("ajaxStop")}}}return ch},getJSON:function(e,b3,b4){return bI.get(e,b3,b4,"json")},getScript:function(e,b3){return bI.get(e,aE,b3,"script")}});bI.each(["get","post"],function(e,b3){bI[b3]=function(b4,b6,b7,b5){if(bI.isFunction(b6)){b5=b5||b7;b7=b6;b6=aE}return bI.ajax({url:b4,type:b3,dataType:b5,data:b6,success:b7})}});function g(ca,b9,b6){var e,b5,b4,b7,b3=ca.contents,b8=ca.dataTypes;while(b8[0]==="*"){b8.shift();if(b5===aE){b5=ca.mimeType||b9.getResponseHeader("Content-Type")}}if(b5){for(b7 in b3){if(b3[b7]&&b3[b7].test(b5)){b8.unshift(b7);break}}}if(b8[0] in b6){b4=b8[0]}else{for(b7 in b6){if(!b8[0]||ca.converters[b7+" "+b8[0]]){b4=b7;break}if(!e){e=b7}}b4=b4||e}if(b4){if(b4!==b8[0]){b8.unshift(b4)}return b6[b4]}}function ad(ce,b6,cb,b4){var b3,b9,cc,b7,b5,cd={},ca=ce.dataTypes.slice();if(ca[1]){for(cc in ce.converters){cd[cc.toLowerCase()]=ce.converters[cc]}}b9=ca.shift();while(b9){if(ce.responseFields[b9]){cb[ce.responseFields[b9]]=b6}if(!b5&&b4&&ce.dataFilter){b6=ce.dataFilter(b6,ce.dataType)}b5=b9;b9=ca.shift();if(b9){if(b9==="*"){b9=b5}else{if(b5!=="*"&&b5!==b9){cc=cd[b5+" "+b9]||cd["* "+b9];if(!cc){for(b3 in cd){b7=b3.split(" ");if(b7[1]===b9){cc=cd[b5+" "+b7[0]]||cd["* "+b7[0]];if(cc){if(cc===true){cc=cd[b3]}else{if(cd[b3]!==true){b9=b7[0];ca.unshift(b7[1])}}break}}}}if(cc!==true){if(cc&&ce["throws"]){b6=cc(b6)}else{try{b6=cc(b6)}catch(b8){return{state:"parsererror",error:cc?b8:"No conversion from "+b5+" to "+b9}}}}}}}}return{state:"success",data:b6}}bI.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/(?:java|ecma)script/},converters:{"text script":function(e){bI.globalEval(e);return e}}});bI.ajaxPrefilter("script",function(e){if(e.cache===aE){e.cache=false}if(e.crossDomain){e.type="GET";e.global=false}});bI.ajaxTransport("script",function(b4){if(b4.crossDomain){var e,b3=l.head||bI("head")[0]||l.documentElement;return{send:function(b5,b6){e=l.createElement("script");e.async=true;if(b4.scriptCharset){e.charset=b4.scriptCharset}e.src=b4.url;e.onload=e.onreadystatechange=function(b8,b7){if(b7||!e.readyState||/loaded|complete/.test(e.readyState)){e.onload=e.onreadystatechange=null;if(e.parentNode){e.parentNode.removeChild(e)}e=null;if(!b7){b6(200,"success")}}};b3.insertBefore(e,b3.firstChild)},abort:function(){if(e){e.onload(aE,true)}}}}});var bp=[],a4=/(=)\?(?=&|$)|\?\?/;bI.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=bp.pop()||(bI.expando+"_"+(bN++));this[e]=true;return e}});bI.ajaxPrefilter("json jsonp",function(b5,e,b6){var b8,b3,b4,b7=b5.jsonp!==false&&(a4.test(b5.url)?"url":typeof b5.data==="string"&&!(b5.contentType||"").indexOf("application/x-www-form-urlencoded")&&a4.test(b5.data)&&"data");if(b7||b5.dataTypes[0]==="jsonp"){b8=b5.jsonpCallback=bI.isFunction(b5.jsonpCallback)?b5.jsonpCallback():b5.jsonpCallback;if(b7){b5[b7]=b5[b7].replace(a4,"$1"+b8)}else{if(b5.jsonp!==false){b5.url+=(ax.test(b5.url)?"&":"?")+b5.jsonp+"="+b8}}b5.converters["script json"]=function(){if(!b4){bI.error(b8+" was not called")}return b4[0]};b5.dataTypes[0]="json";b3=a1[b8];a1[b8]=function(){b4=arguments};b6.always(function(){a1[b8]=b3;if(b5[b8]){b5.jsonpCallback=e.jsonpCallback;bp.push(b8)}if(b4&&bI.isFunction(b3)){b3(b4[0])}b4=b3=aE});return"script"}});var af,av,aw=0,aN=a1.ActiveXObject&&function(){var e;for(e in af){af[e](aE,true)}};function bC(){try{return new a1.XMLHttpRequest()}catch(b3){}}function bd(){try{return new a1.ActiveXObject("Microsoft.XMLHTTP")}catch(b3){}}bI.ajaxSettings.xhr=a1.ActiveXObject?function(){return !this.isLocal&&bC()||bd()}:bC;av=bI.ajaxSettings.xhr();bI.support.cors=!!av&&("withCredentials" in av);av=bI.support.ajax=!!av;if(av){bI.ajaxTransport(function(e){if(!e.crossDomain||bI.support.cors){var b3;return{send:function(b9,b4){var b7,b5,b8=e.xhr();if(e.username){b8.open(e.type,e.url,e.async,e.username,e.password)}else{b8.open(e.type,e.url,e.async)}if(e.xhrFields){for(b5 in e.xhrFields){b8[b5]=e.xhrFields[b5]}}if(e.mimeType&&b8.overrideMimeType){b8.overrideMimeType(e.mimeType)}if(!e.crossDomain&&!b9["X-Requested-With"]){b9["X-Requested-With"]="XMLHttpRequest"}try{for(b5 in b9){b8.setRequestHeader(b5,b9[b5])}}catch(b6){}b8.send((e.hasContent&&e.data)||null);b3=function(cc,cb){var ca,cd,cg,ce;try{if(b3&&(cb||b8.readyState===4)){b3=aE;if(b7){b8.onreadystatechange=bI.noop;if(aN){delete af[b7]}}if(cb){if(b8.readyState!==4){b8.abort()}}else{ce={};ca=b8.status;cd=b8.getAllResponseHeaders();if(typeof b8.responseText==="string"){ce.text=b8.responseText}try{cg=b8.statusText}catch(cf){cg=""}if(!ca&&e.isLocal&&!e.crossDomain){ca=ce.text?200:404}else{if(ca===1223){ca=204}}}}}catch(ch){if(!cb){b4(-1,ch)}}if(ce){b4(ca,cg,ce,cd)}};if(!e.async){b3()}else{if(b8.readyState===4){setTimeout(b3)}else{b7=++aw;if(aN){if(!af){af={};bI(a1).unload(aN)}af[b7]=b3}b8.onreadystatechange=b3}}},abort:function(){if(b3){b3(aE,true)}}}}})}var J,ab,bQ=/^(?:toggle|show|hide)$/,bJ=new RegExp("^(?:([+-])=|)("+bz+")([a-z%]*)$","i"),bP=/queueHooks$/,az=[h],aY={"*":[function(e,b8){var ca=this.createTween(e,b8),b6=ca.cur(),b5=bJ.exec(b8),b9=b5&&b5[3]||(bI.cssNumber[e]?"":"px"),b3=(bI.cssNumber[e]||b9!=="px"&&+b6)&&bJ.exec(bI.css(ca.elem,e)),b4=1,b7=20;if(b3&&b3[3]!==b9){b9=b9||b3[3];b5=b5||[];b3=+b6||1;do{b4=b4||".5";b3=b3/b4;bI.style(ca.elem,e,b3+b9)}while(b4!==(b4=ca.cur()/b6)&&b4!==1&&--b7)}if(b5){b3=ca.start=+b3||+b6||0;ca.unit=b9;ca.end=b5[1]?b3+(b5[1]+1)*b5[2]:+b5[2]}return ca}]};function bl(){setTimeout(function(){J=aE});return(J=bI.now())}function ba(b6,b8,b5){var b3,b7=(aY[b8]||[]).concat(aY["*"]),e=0,b4=b7.length;for(;e-1,cb={},ca={},b4,b6;if(cd){ca=b7.position();b4=ca.top;b6=ca.left}else{b4=parseFloat(e)||0;b6=parseFloat(cc)||0}if(bI.isFunction(ce)){ce=ce.call(b5,b8,b3)}if(ce.top!=null){cb.top=(ce.top-b3.top)+b4}if(ce.left!=null){cb.left=(ce.left-b3.left)+b6}if("using" in ce){ce.using.call(b5,cb)}else{b7.css(cb)}}};bI.fn.extend({position:function(){if(!this[0]){return}var b4,b5,e={top:0,left:0},b3=this[0];if(bI.css(b3,"position")==="fixed"){b5=b3.getBoundingClientRect()}else{b4=this.offsetParent();b5=this.offset();if(!bI.nodeName(b4[0],"html")){e=b4.offset()}e.top+=bI.css(b4[0],"borderTopWidth",true);e.left+=bI.css(b4[0],"borderLeftWidth",true)}return{top:b5.top-e.top-bI.css(b3,"marginTop",true),left:b5.left-e.left-bI.css(b3,"marginLeft",true)}},offsetParent:function(){return this.map(function(){var e=this.offsetParent||bV;while(e&&(!bI.nodeName(e,"html")&&bI.css(e,"position")==="static")){e=e.offsetParent}return e||bV})}});bI.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(b4,b3){var e=/Y/.test(b3);bI.fn[b4]=function(b5){return bI.access(this,function(b6,b9,b8){var b7=bo(b6);if(b8===aE){return b7?(b3 in b7)?b7[b3]:b7.document.documentElement[b9]:b6[b9]}if(b7){b7.scrollTo(!e?b8:bI(b7).scrollLeft(),e?b8:bI(b7).scrollTop())}else{b6[b9]=b8}},b4,b5,arguments.length,null)}});function bo(e){return bI.isWindow(e)?e:e.nodeType===9?e.defaultView||e.parentWindow:false}bI.each({Height:"height",Width:"width"},function(e,b3){bI.each({padding:"inner"+e,content:b3,"":"outer"+e},function(b4,b5){bI.fn[b5]=function(b9,b8){var b7=arguments.length&&(b4||typeof b9!=="boolean"),b6=b4||(b9===true||b8===true?"margin":"border");return bI.access(this,function(cb,ca,cc){var cd;if(bI.isWindow(cb)){return cb.document.documentElement["client"+e]}if(cb.nodeType===9){cd=cb.documentElement;return Math.max(cb.body["scroll"+e],cd["scroll"+e],cb.body["offset"+e],cd["offset"+e],cd["client"+e])}return cc===aE?bI.css(cb,ca,b6):bI.style(cb,ca,cc,b6)},b3,b7?b9:aE,b7,null)}})});bI.fn.size=function(){return this.length};bI.fn.andSelf=bI.fn.addBack;if(typeof module==="object"&&module&&typeof module.exports==="object"){module.exports=bI}else{a1.jQuery=a1.$=bI;if(typeof define==="function"&&define.amd){define("jquery",[],function(){return bI})}}})(window);
-/*! JsRender v1.0.0-beta: http://github.com/BorisMoore/jsrender and http://jsviews.com/jsviews */
-(function(a,w,p){if(w&&w.views||a.jsviews){return}var af="v1.0.0-beta",L,ab,s,x,n="{",m="{",g="}",f="}",X="^",y=/^(?:null|true|false|\d[\d.]*|([\w$]+|\.|~([\w$]+)|#(view|([\w$]+))?)([\w$.^]*?)(?:[.[^]([\w$]+)\]?)?)$/g,Y=/(\()(?=\s*\()|(?:([([])\s*)?(?:(\^?)([#~]?[\w$.^]+)?\s*((\+\+|--)|\+|-|&&|\|\||===|!==|==|!=|<=|>=|[<>%*!:?\/]|(=))\s*|([#~]?[\w$.^]+)([([])?)|(,\s*)|(\(?)\\?(?:(')|("))|(?:\s*(([)\]])(?=\s*\.|\s*\^)|[)\]])([([]?))|(\s+)/g,S=/[ \t]*(\r\n|\n|\r)/g,e=/\\(['"])/g,ad=/['"\\]/g,o=/\x08(~)?([^\x08]+)\x08/g,j=/^if\s/,r=/<(\w+)[>\s]/,N=/[\x00`><"'&]/g,I=N,ag=0,ae=0,K={"&":"&","<":"<",">":">","\x00":"","'":"'",'"':""","`":"`"},V="data-jsv-tmpl",O={},R={template:{compile:b},tag:{compile:q},helper:{},converter:{}},M={jsviews:af,render:O,settings:{delimiters:u,debugMode:true,tryCatch:true},sub:{View:ac,Error:D,tmplFn:B,parse:v,extend:t,error:F,syntaxError:Q},_cnvt:G,_tag:c,_err:function(ah){return T.debugMode?("Error: "+(ah.message||ah))+". ":""}};function D(ai,ah){if(ah&&ah.onError){if(ah.onError(ai)===false){return}}this.name="JsRender Error";this.message=ai||"JsRender error"}function t(aj,ai){var ah;aj=aj||{};for(ah in ai){aj[ah]=ai[ah]}return aj}(D.prototype=new Error()).constructor=D;function u(ah,aj,ai){if(!U.rTag||arguments.length){n=ah?ah.charAt(0):n;m=ah?ah.charAt(1):m;g=aj?aj.charAt(0):g;f=aj?aj.charAt(1):f;X=ai||X;ah="\\"+n+"(\\"+X+")?\\"+m;aj="\\"+g+"\\"+f;s="(?:(?:(\\w+(?=[\\/\\s\\"+g+"]))|(?:(\\w+)?(:)|(>)|!--((?:[^-]|-(?!-))*)--|(\\*)))\\s*((?:[^\\"+g+"]|\\"+g+"(?!\\"+f+"))*?)";U.rTag=s+")";s=new RegExp(ah+s+"(\\/)?|(?:\\/(\\w+)))"+aj,"g");x=new RegExp("<.*>|([^\\\\]|^)[{}]|"+ah+".*"+aj)}return[n,m,g,f,X]}function J(al,an){if(!an){an=al;al=p}var ak,am,aj,ao,ai=this,ah=!an||an==="root";if(al){ao=ai.type===an?ai:p;if(!ao){ak=ai.views;if(ai._.useKey){for(am in ak){if(ao=ak[am].get(al,an)){break}}}else{for(am=0,aj=ak.length;!ao&&am0){try{aj=aq.nodeType>0?aq:!x.test(aq)&&w&&w(a.document).find(aq)[0]}catch(ar){}if(aj){aq=aj.getAttribute(V);ah=ah||aq;aq=E[aq];if(!aq){ah=ah||"_"+ag++;aj.setAttribute(V,ah);aq=E[ah]=b(ah,aj.innerHTML,ao,am,ai,ap)}}return aq}}var al,aj;an=an||"";al=ak(an);ap=ap||(an.markup?an:{});ap.tmplName=ah;if(ao){ap._parentTmpl=ao}if(!al&&an.markup&&(al=ak(an.markup))){if(al.fn&&(al.debug!==an.debug||al.allowCode!==an.allowCode)){al=al.markup}}if(al!==p){if(ah&&!ao){O[ah]=function(){return an.render.apply(an,arguments)}}if(al.fn||an.fn){if(al.fn){if(ah&&ah!==al.tmplName){an=aa(ap,al)}else{an=al}}}else{an=h(al,ap);B(al,an)}k(ap);return an}}function h(ak,al){var ah,ai=T.wrapMap||{},aj=t({markup:ak,tmpls:[],links:{},tags:{},bnds:[],_is:"template",render:l},al);if(!al.htmlTag){ah=r.exec(ak);aj.htmlTag=ah?ah[1].toLowerCase():""}ah=ai[aj.htmlTag];if(ah&&ah!==ai.div){aj.markup=L.trim(aj.markup);aj._elCnt=true}return aj}function d(aj,ai){function ak(al,an,ar){var am,aq,ap,ao;if(al&&""+al!==al&&!al.nodeType&&!al.markup){for(ap in al){ak(ap,al[ap],an)}return M}if(an===p){an=al;al=p}if(al&&""+al!==al){ar=an;an=al;al=p}ao=ar?ar[ah]=ar[ah]||{}:ak;aq=ai.compile;if(am=U.onBeforeStoreItem){aq=am(ao,al,an,aq)||aq}if(!al){an=aq(p,an)}else{if(an===null){delete ao[al]}else{ao[al]=aq?(an=aq(al,an,ar,aj,ai)):an}}if(aq&&an){an._is=aj}if(am=U.onStoreItem){am(ao,al,an,aq)}return an}var ah=aj+"s";M[ah]=ak;R[aj]=ai}function l(aC,aj,ak,aD,am,al){var ax,aw,ao,az,aB,au,at,ap,ai,aA,ay,ah,an,av=this,ar=!av.attr||av.attr==="html",aq="";if(aD===true){at=true;aD=0}if(av.tag){ap=av;av=av.tag;aA=av._;ah=av.tagName;an=ap.tmpl;aj=aa(aj,av.ctx);ai=ap.content;if(ap.props.link===false){aj=aj||{};aj.link=false}ak=ak||ap.view;aC=aC===p?ak:aC}else{an=av.jquery&&(av[0]||F('Unknown template: "'+av.selector+'"'))||av}if(an){if(!ak&&aC&&aC._is==="view"){ak=aC}if(ak){ai=ai||ak.content;al=al||ak._.onRender;if(aC===ak){aC=ak.data;am=true}aj=aa(aj,ak.ctx)}if(!ak||ak.data===p){(aj=aj||{}).root=aC}if(!an.fn){an=E[an]||E(an)}if(an){al=(aj&&aj.link)!==false&&ar&&al;ay=al;if(al===true){ay=p;al=ak._.onRender}if(L.isArray(aC)&&!am){az=at?ak:(aD!==p&&ak)||ac(aj,"array",ak,aC,an,aD,ai,al);for(ax=0,aw=aC.length;ax":aM+ap}if(ah){ay="prm"+aG;ah="try{var "+ay+"=["+aL+"][0];}catch(e){"+ay+'="";}\n';aL=ay}}else{if(aC){ar=h(aI,aw);ar.tmplName=ai+"/"+ap;A(aC,ar);aq.push(ar)}if(!aH){aE=ap;al=ak;ak=""}at=av[aG+1];at=at&&at[0]==="else"}an+=",args:["+aL+"]}";if(aO&&aN||aM&&ap!==">"){aF=new Function("data,view,j,u"," // "+ai+" "+aK+" "+ap+"\n"+ah+"return {"+an+";");aF.paths=aN;aF._ctxs=ap;if(au){return aF}az=1}ak+=(aO?"\n"+(aN?"":ah)+(au?"return ":"ret+=")+(az?(az=0,ao=true,'c("'+aM+'",view,'+(aN?((aj[aK-1]=aF),aK):"{"+an)+");"):ap===">"?(am=true,"h("+aL+");"):(aD=true,"(v="+aL+")!="+(au?"=":"")+'u?v:"";')):(aP=true,"{tmpl:"+(aC?aq.length:"0")+","+an+","));if(aE&&!at){ak="["+ak.slice(0,-1)+"]";if(au||aN){ak=new Function("data,view,j,u"," // "+ai+" "+aK+" "+aE+"\nreturn "+ak+";");if(aN){(aj[aK-1]=ak).paths=aN}ak._ctxs=ap;if(au){return ak}}ak=al+'\nret+=t("'+aE+'",view,this,'+(aK||ak)+");";aN=0;aE=0}}}}ak="// "+ai+"\nvar j=j||"+(w?"jQuery.":"js")+"views"+(aD?",v":"")+(aP?",t=j._tag":"")+(ao?",c=j._cnvt":"")+(am?",h=j.converters.html":"")+(au?";\n":',ret="";\n')+(T.tryCatch?"try{\n":"")+(aw.debug?"debugger;":"")+ak+(au?"\n":"\nreturn ret;\n")+(T.tryCatch?"\n}catch(e){return j._err(e);}":"");try{ak=new Function("data,view,j,u",ak)}catch(aJ){Q("Compiled template code:\n\n"+ak,aJ)}if(ax){ax.fn=ak}return ak}function v(am,ai,ar){function at(ay,aP,aA,av,aH,aF,aB,aQ,aw,aG,aO,aN,aJ,aM,aD,aK,az,aL,aC,aE){var ax;aF=aF||"";aA=aA||aP||aN;aH=aH||aw;aG=aG||az||"";function aI(aU,aT,aW,aR,aY,aX,aV){if(aT){if(ai){if(an==="linkTo"){au=ai.to=ai.to||[];au.push(aH)}if(!an||ao){ai.push(aH)}}if(aT!=="."){var aS=(aW?'view.hlp("'+aW+'")':aR?"view":"data")+(aV?(aY?"."+aY:aW?"":(aR?"":"."+aT))+(aX||""):(aV=aW?"":aR?aY||"":aT,""));aS=aS+(aV?"."+aV:"");return aS.slice(0,9)==="view.data"?aS.slice(5):aS}}return aU}if(aB){Q(am)}else{if(ai&&aK&&!ap&&!al){if(!an||ao||au){ax=aq[aj];if(aE.length-2>aC-ax){ax=aE.slice(ax,aC+1);aK=m+":"+ax+g;aK=ak[aK]=ak[aK]||B(n+aK+f,ar,true);if(!aK.paths){v(ax,aK.paths=[],ar)}(au||ai).push({_jsvOb:aK})}}}return(ap?(ap=!aJ,(ap?ay:'"')):al?(al=!aM,(al?ay:'"')):((aA?(aj++,aq[aj]=aC++,aA):"")+(aL?(aj?"":an?(an=ao=au=false,"\b"):","):aQ?(aj&&Q(am),an=aH,ao=av,"\b"+aH+":"):aH?(aH.split("^").join(".").replace(y,aI)+(aG?(ah[++aj]=true,aH.charAt(0)!=="."&&(aq[aj]=aC),aG):aF)):aF?aF:aD?((ah[aj--]=false,aD)+(aG?(ah[++aj]=true,aG):"")):aO?(ah[aj]||Q(am),","):aP?"":(ap=aJ,al=aM,'"'))))}}var an,au,ao,ak=ar.links,ah={},aq={0:-1},aj=0,al=false,ap=false;return(am+" ").replace(Y,at)}function aa(ai,ah){return ai&&ai!==ah?(ah?t(t({},ah),ai):ai):ah&&t({},ah)}function i(ah){return K[ah]||(K[ah]=""+ah.charCodeAt(0)+";")}for(ab in R){d(ab,R[ab])}var E=M.templates,Z=M.converters,C=M.helpers,P=M.tags,U=M.sub,T=M.settings;if(w){L=w;L.fn.render=l}else{L=a.jsviews={};L.isArray=Array&&Array.isArray||function(ah){return Object.prototype.toString.call(ah)==="[object Array]"}}L.render=O;L.views=M;L.templates=E=M.templates;P({"else":function(){},"if":{render:function(aj){var ah=this,ai=(ah.rendering.done||!aj&&(arguments.length||!ah.tagCtx.index))?"":(ah.rendering.done=true,ah.selected=ah.tagCtx.index,ah.tagCtx.render());return ai},onUpdate:function(al,ak,aj){var ai,ah,am;for(ai=0;(ah=this.tagCtxs[ai])&&ah.args.length;ai++){ah=ah.args[0];am=!ah!==!aj[ai].args[0];if(!!ah||am){return am}}return false},flow:true},"for":{render:function(am){var ak=this,aj=ak.tagCtx,al=!arguments.length,ah="",ai=al||0;if(!ak.rendering.done){if(al){ah=p}else{if(am!==p){ah+=aj.render(am);ai+=L.isArray(am)?am.length:1}}if(ak.rendering.done=ai){ak.selected=aj.index}}return ah},onArrayChange:function(ak,ai){var aj,ah=this,al=ai.change;if(this.tagCtxs[1]&&(al==="insert"&&ak.target.length===ai.items.length||al==="remove"&&!ak.target.length||al==="refresh"&&!ai.oldItems.length!==!ak.target.length)){this.refresh()}else{for(aj in ah._.arrVws){aj=ah._.arrVws[aj];if(aj.data===ak.target){aj._.onArrayChange.apply(aj,arguments)}}}ak.done=true},flow:true},include:{flow:true},"*":{render:function(ah){return ah},flow:true}});Z({html:function(ah){return ah!=p?String(ah).replace(I,i):""},attr:function(ah){return ah!=p?String(ah).replace(N,i):ah===null?null:""},url:function(ah){return ah!=p?encodeURI(String(ah)):ah===null?null:""}});u()})(this,this.jQuery);
-/*! JsObservable v1.0.0-alpha: http://github.com/BorisMoore/jsviews and http://jsviews.com/jsviews */
-(function(y,f,k){if(!f){throw"requires jQuery or JsRender"}if(f.observable){return}var c="v1.0.0-alpha",w,n,H,t,A=f.event.special,s=f.views?f.views.sub:{},l=1,G=[].splice,j=[].concat,p=f.isArray,C=f.expando,q="object",E=s.propChng=s.propChng||"propertyChange",e=s.arrChng=s.arrChng||"arrayChange",x=s._cbBnds=s._cbBnds||{},g=E+".observe",u=f.isFunction,i=1,r=1,h=f.hasData;function v(I){return p(I)?new a(I):new b(I)}function b(I){this._data=I;return this}function a(I){this._data=I;return this}function B(I){return p(I)?[I]:I}function m(I){if(typeof I!=="number"){throw"Invalid index."}}function d(P,J){P=p(P)?P:[P];var N,O,L=J,M=L,I=P.length,K=[];for(N=0;N-1){ah=ah[L];continue}}if(L==="*"){if(u(ah)){if(O=ah.depends){o(O,M,I||P)}}else{S(ai,L)}break}else{if(L&&!(u(O=ah[L])&&O.depends)){S(ai+"."+L,T.join("."))}}}L=L?ah[L]:ah}if(u(L)){if(O=L.depends){o(ah,d(O,ah),M,N,I||B(P))}break}ah=L}}aa(ah,I);if(V){F(ae,V)}return{cbId:V,bnd:ae,leaf:ah}}function D(){[].push.call(arguments,true);return o.apply(this,arguments)}f.observable=v;v.Object=b;v.Array=a;v.observe=o;v.unobserve=D;b.prototype={_data:null,data:function(){return this._data},observe:function(I,J){return o(this._data,I,J)},unobserve:function(I,J){return D(this._data,I,J)},setProperty:function(Q,N,L){var M,O,J,K,P=this,I=P._data;Q=Q||"";if(I){if(p(Q)){O=Q.length;while(O--){J=Q[O];P.setProperty(J.name,J.value,L===k||L)}}else{if(""+Q!==Q){for(O in Q){P.setProperty(O,Q[O],N)}}else{if(Q.indexOf(C)<0){K=Q.split(".");while(I&&K.length>1){I=I[K.shift()]}P._setProperty(I,K.join("."),N,L)}}}}return P},_setProperty:function(J,N,M,L){var O,I,K=N?J[N]:J;if(u(K)){if(K.set){I=K;O=K.set===true?K:K.set;K=K.call(J)}}if(K!==M||L&&K!=M){if(!(K instanceof Date)||K>M||K1){J=p(J)?J:[J];if(J.length){this._insert(I,J)}}return this},_insert:function(I,J){t=this._data;H=t.length;G.apply(t,[I,0].concat(J));this._trigger({change:"insert",index:I,items:J})},remove:function(J,K){m(J);K=(K===k||K===null)?1:K;if(K&&J>-1){var I=this._data.slice(J,J+K);K=I.length;if(K){this._remove(J,K,I)}}return this},_remove:function(J,K,I){t=this._data;H=t.length;t.splice(J,K);this._trigger({change:"remove",index:J,items:I})},move:function(L,K,I){m(L);m(K);I=(I===k||I===null)?1:I;if(I){var J=this._data.slice(L,L+I);this._move(L,K,I,J)}return this},_move:function(L,K,I,J){t=this._data;H=t.length;t.splice(L,I);t.splice.apply(t,[K,0].concat(J));this._trigger({change:"move",oldIndex:L,index:K,items:J})},refresh:function(I){var J=this._data.slice(0);this._refresh(J,I);return this},_refresh:function(J,I){t=this._data;H=t.length;G.apply(t,[0,t.length].concat(I));this._trigger({change:"refresh",oldItems:J})},_trigger:function(I){var K=t.length,J=f([t]);J.triggerHandler(e,I);if(K!==H){J.triggerHandler(E,{path:"length",value:K,oldValue:H})}}};A[E]=A[e]={remove:function(I){if((I=I.data)&&(I.off=1,I=I.cb)){w=x[n=I._bnd]}},teardown:function(I){if(w){delete w[f.data(this,"obId")];F(w,n)}}}})(this,this.jQuery||this.jsviews);
-/*! JsViews v1.0.0-alpha: http://github.com/BorisMoore/jsviews and http://jsviews.com/jsviews */
-(function(r,az,u){if(!az){throw"requires jQuery"}if(!az.views){throw"requires JsRender"}if(!az.observable){throw"requires jquery.observable"}if(az.link){return}var g="v1.0.0-alpha",V,aG,aP,e,au,at,M,L,ax,b,Q=r.document,h=az.views,K=h.sub,m=h.settings,n=K.extend,O=K.View(u,"top"),d=az.isFunction,l=h.templates,S=az.observable,aO=S.observe,af="data-jsv",ab=m.linkAttr||"data-link",q=m.propChng=m.propChng||"propertyChange",Z=K.arrChng=K.arrChng||"arrayChange",aH=K._cbBnds=K._cbBnds||{},P="change.jsv",y="onBeforeChange",x="onAfterChange",aB="onAfterCreate",c='"><\/script>',aE='',
- openScript = ' - data-linked tag, close marker
- preceding = id
- ? (preceding + endOfElCnt + spaceBefore + openScript + id + closeScript + spaceAfter + tag)
- : endOfElCnt || all;
- }
- if (tag) {
- // If there are ids (markers since the last tag), move them to the defer string
- tagStack.unshift(parentTag);
- parentTag = tag.slice(1);
- if (tagStack[0] === badParent[parentTag]) {
- // TODO: move this to design-time validation check
- error('"' + parentTag + '" has incorrect parent tag');
- }
- if ((elCnt = elContent[parentTag]) && !prevElCnt) {
- deferStack.unshift(defer);
- defer = "";
- }
- prevElCnt = elCnt;
-//TODO Consider providing validation which throws if you place as child of
, etc. - since if not caught,
-//this can cause errors subsequently which are difficult to debug.
-// if (elContent[tagStack[0]]>2 && !elCnt) {
-// error(parentTag + " in " + tagStack[0]);
-// }
- if (defer && elCnt) {
- defer += "+"; // Will be used for stepping back through deferred tokens
- }
- }
- return preceding;
- }
-
- function processViewInfos(vwInfos, targetParent) {
- // If targetParent, we are processing viewInfos (which may include navigation through '+-' paths) and hooking up to the right parentElem etc.
- // (and elem may also be defined - the next node)
- // If no targetParent, then we are processing viewInfos on newly inserted content
- var deferPath, deferChar, bindChar, parentElem, id, onAftCr, deep,
- addedBindEls = [];
-
- // In elCnt context (element-only content model), prevNode is the first node after the open, nextNode is the first node after the close.
- // If both are null/undefined, then open and close are at end of parent content, so the view is empty, and its placeholder is the
- // 'lastChild' of the parentNode. If there is a prevNode, then it is either the first node in the view, or the view is empty and
- // its placeholder is the 'previousSibling' of the prevNode, which is also the nextNode.
- if (vwInfos) {
- //targetParent = targetParent || targetElem && targetElem.previousSibling;
- //targetParent = targetElem ? targetElem.previousSibling : targetParent;
- len = vwInfos.length;
- if (vwInfos.tokens.charAt(0) === "@") {
- // This is a special script element that was created in convertMarkers() to process deferred bindings, and inserted following the
- // target parent element - because no element tags were encountered to carry those binding tokens.
- targetParent = elem.previousSibling;
- elem.parentNode.removeChild(elem);
- elem = null;
- }
- len = vwInfos.length;
- while (len--) {
- vwInfo = vwInfos[len];
- //if (prevIds.indexOf(vwInfo.token) < 0) { // This token is a newly created view or tag binding
- bindChar = vwInfo.ch;
- if (deferPath = vwInfo.path) {
- // We have a 'deferred path'
- j = deferPath.length - 1;
- while (deferChar = deferPath.charAt(j--)) {
- // Use the "+" and"-" characters to navigate the path back to the original parent node where the deferred bindings ocurred
- if (deferChar === "+") {
- if (deferPath.charAt(j) === "-") {
- j--;
- targetParent = targetParent.previousSibling;
- } else {
- targetParent = targetParent.parentNode;
- }
- } else {
- targetParent = targetParent.lastChild;
- }
- // Note: Can use previousSibling and lastChild, not previousElementSibling and lastElementChild,
- // since we have removed white space within elCnt. Hence support IE < 9
- }
- }
- if (bindChar === "^") {
- if (tag = bindingStore[id = vwInfo.id]) {
- // The binding may have been deleted, for example in a different handler to an array collectionChange event
- // This is a tag binding
- deep = targetParent && (!elem || elem.parentNode !== targetParent);
- if (!elem || deep) {
- tag.parentElem = targetParent;
- }
- if (vwInfo.elCnt) {
- if (vwInfo.open) {
- if (targetParent) {
- // This is an 'open view' node (preceding script marker node,
- // or if elCnt, the first element in the view, with a data-jsv annotation) for binding
- targetParent._dfr = "#" + id + bindChar + (targetParent._dfr || "");
- }
- } else if (deep) {
- // There is no ._nxt so add token to _dfr. It is deferred.
- targetParent._dfr = "/" + id + bindChar + (targetParent._dfr || "");
- }
- }
- // This is an open or close marker for a data-linked tag {^{...}}. Add it to bindEls.
- addedBindEls.push([deep ? null : elem, vwInfo]);
- }
- } else if (view = viewStore[id = vwInfo.id]) {
- // The view may have been deleted, for example in a different handler to an array collectionChange event
- if (!view.link) {
- // If view is not already extended for JsViews, extend and initialize the view object created in JsRender, as a JsViews view
- view.parentElem = targetParent || elem && elem.parentNode || parentNode;
- $extend(view, LinkedView);
- view._.onRender = addBindingMarkers;
- view._.onArrayChange = arrayChangeHandler;
- setArrayChangeLink(view);
- }
- parentElem = view.parentElem;
- if (vwInfo.open) {
- // This is an 'open view' node (preceding script marker node,
- // or if elCnt, the first element in the view, with a data-jsv annotation) for binding
- view._elCnt = vwInfo.elCnt;
- if (targetParent) {
- targetParent._dfr = "#" + id + bindChar + (targetParent._dfr || "");
- } else {
- // No targetParent, so there is a ._nxt elem (and this is processing tokens on the elem)
- if (!view._prv) {
- parentElem._dfr = removeSubStr(parentElem._dfr, "#" + id + bindChar);
- }
- view._prv = elem;
- }
- } else {
- // This is a 'close view' marker node for binding
- if (targetParent && (!elem || elem.parentNode !== targetParent)) {
- // There is no ._nxt so add token to _dfr. It is deferred.
- targetParent._dfr = "/" + id + bindChar + (targetParent._dfr || "");
- view._nxt = undefined;
- } else if (elem) {
- // This view did not have a ._nxt, but has one now, so token may be in _dfr, and must be removed. (No longer deferred)
- if (!view._nxt) {
- parentElem._dfr = removeSubStr(parentElem._dfr, "/" + id + bindChar);
- }
- view._nxt = elem;
- }
- linkCtx = view.linkCtx;
- if (onAftCr = onAfterCreate || (view.ctx && view.ctx.onAfterCreate)) {
- onAftCr.call(linkCtx, view);
- }
- }
- //}
- }
- }
- len = addedBindEls.length;
- while (len--) {
- // These were added in reverse order to addedBindEls. We push them in BindEls in the correct order.
- bindEls.push(addedBindEls[len]);
- }
- }
- return !vwInfos || vwInfos.elCnt;
- }
-
- function getViewInfos(vwInfos) {
- // Used by view.childTags() and tag.childTags()
- // Similar to processViewInfos in how it steps through bindings to find tags. Only finds data-linked tags.
- var level, parentTag;
-
- if (len = vwInfos && vwInfos.length) {
- for (j = 0; j < len; j++) {
- vwInfo = vwInfos[j];
- if (get.id) {
- get.id = get.id !== vwInfo.id && get.id;
- } else {
- // This is an open marker for a data-linked tag {^{...}}, within the content of the tag whose id is get.id. Add it to bindEls.
- parentTag = tag = bindingStore[vwInfo.id].linkCtx.tag;
- if (!tag.flow) {
- if (!deep) {
- level = 1;
- while (parentTag = parentTag.parent) {
- level++;
- }
- tagDepth = tagDepth || level; // The level of the first tag encountered.
- }
- if ((deep || level === tagDepth) && (!tagName || tag.tagName === tagName)) {
- // Filter on top-level or tagName as appropriate
- tags.push(tag);
- }
- }
- }
- }
- }
- }
-
- function dataLink() {
- //================ Data-link and fixup of data-jsv annotations ================
- elems = qsa ? parentNode.querySelectorAll(linkViewsSel) : $(linkViewsSel, parentNode).get();
- l = elems.length;
-
- // The prevNode will be in the returned query, since we called markPrevOrNextNode() on it.
- // But it may have contained nodes that satisfy the selector also.
- if (prevNode) {
- // Find the last contained node of prevNode, to use as the prevNode - so we only link subsequent elems in the query
- prevNodes = qsa ? prevNode.querySelectorAll(linkViewsSel) : $(linkViewsSel, prevNode).get();
- prevNode = prevNodes.length ? prevNodes[prevNodes.length - 1] : prevNode;
- }
- tagDepth = 0;
- for (i = 0; i < l; i++) {
- elem = elems[i];
- if (prevNode && !found) {
- // If prevNode is set, not false, skip linking. If this element is the prevNode, set to false so subsequent elements will link.
- found = (elem === prevNode);
- } else if (nextNode && elem === nextNode) {
- // If nextNode is set then break when we get to nextNode
- break;
- } else if (elem.parentNode
- // elem has not been removed from DOM
- && processInfos(viewInfos(elem, undefined, tags && rOpenTagMarkers))
- // If a link() call, processViewInfos() adds bindings to bindEls, and returns true for non-script nodes, for adding data-link bindings
- // If a childTags() call getViewInfos adds tag bindings to tags array.
- && elem.getAttribute($viewsLinkAttr)) {
- bindEls.push([elem]);
- }
- }
-
- // Remove temporary marker script nodes they were added by markPrevOrNextNode
- unmarkPrevOrNextNode(prevNode, elCnt);
- unmarkPrevOrNextNode(nextNode, elCnt);
-
- if (get) {
- lazyLink && lazyLink.resolve();
- return;
- }
-
- if (elCnt && defer + ids) {
- // There are some views with elCnt, for which the open or close did not precede any HTML tag - so they have not been processed yet
- elem = nextNode;
- if (defer) {
- if (nextNode) {
- processViewInfos(viewInfos(defer + "+", true), nextNode);
- } else {
- processViewInfos(viewInfos(defer, true), parentNode);
- }
- }
- processViewInfos(viewInfos(ids, true), parentNode);
- // If there were any tokens on nextNode which have now been associated with inserted HTML tags, remove them from nextNode
- if (nextNode) {
- tokens = nextNode.getAttribute(jsvAttrStr);
- if (l = tokens.indexOf(prevIds) + 1) {
- tokens = tokens.slice(l + prevIds.length - 1);
- }
- nextNode.setAttribute(jsvAttrStr, ids + tokens);
- }
- }
-
- //================ Bind the data-linked elements and tags ================
- l = bindEls.length;
- for (i = 0; i < l; i++) {
- elem = bindEls[i];
- linkInfo = elem[1];
- elem = elem[0];
- if (linkInfo) {
- tag = bindingStore[linkInfo.id];
- linkCtx = tag.linkCtx;
- tag = linkCtx ? linkCtx.tag : tag;
- // The tag may have been stored temporarily on the bindingStore - or may have already been replaced by the actual binding
- if (linkInfo.open) {
- // This is an 'open linked tag' binding annotation for a data-linked tag {^{...}}
- if (elem) {
- tag.parentElem = elem.parentNode;
- tag._prv = elem;
- }
- tag._elCnt = linkInfo.elCnt;
- if (tag && (!tag.onBeforeLink || tag.onBeforeLink() !== false) && !tag._.bound) {
- // By default we data-link depth-first ("on the way in"), which is better for perf. But if a tag needs nested tags to be linked (refreshed)
- // first, before linking its content, then make onBeforeLink() return false. In that case we data-link depth-first, so nested tags will have already refreshed.
- tag._.bound = true;
- view = tag.tagCtx.view;
- addDataBinding(undefined, tag._prv, view, view.data||outerData, linkInfo.id);
- }
-
- tag._.linking = true;
- } else {
- tag._nxt = elem;
- if (tag._.linking) {
- // This is a 'close linked tag' binding annotation
- // Add data binding
- tagCtx = tag.tagCtx;
- view = tagCtx.view;
- tag.contents = getContents;
- tag.nodes = getNodes;
- tag.childTags = getChildTags;
-
- delete tag._.linking;
- callAfterLink(tag, tagCtx);
- if (!tag._.bound) {
- tag._.bound = true;
- addDataBinding(undefined, tag._prv, view, view.data||outerData, linkInfo.id);
- }
- }
- }
- } else {
- view = $view(elem);
- // Add data binding for a data-linked element (with data-link attribute)
- addDataBinding(elem.getAttribute($viewsLinkAttr), elem, view, view.data||outerData);
- }
- }
- lazyLink && lazyLink.resolve();
- }
- //==== /end of nested functions ====
-
- var linkCtx, tag, i, l, j, len, elems, elem, view, vwInfos, vwInfo, linkInfo, prevNodes, token, prevView, nextView, node, tags, deep, tagName, tagCtx, cvt,
- tagDepth, get, depth, fragment, copiedNode, firstTag, parentTag, wrapper, div, tokens, elCnt, prevElCnt, htmlTag, ids, prevIds, found, lazyLink, linkedElem,
- noDomLevel0 = $viewsSettings.noDomLevel0,
- self = this,
- thisId = self._.id + "_",
- defer = "",
- // The marker ids for which no tag was encountered (empty views or final closing markers) which we carry over to container tag
- bindEls = [],
- tagStack = [],
- deferStack = [],
- onAfterCreate = self.hlp(onAfterCreateStr),
- processInfos = processViewInfos;
-
- if (refresh) {
- lazyLink = refresh.lazyLink && $.Deferred();
- if (refresh.tmpl) {
- // refresh is the prevView, passed in from addViews()
- prevView = "/" + refresh._.id + "_";
- } else {
- get = refresh.get;
- if (refresh.tag) {
- thisId = refresh.tag + "^";
- refresh = true;
- }
- }
- refresh = refresh === true;
- }
-
- if (get) {
- processInfos = getViewInfos;
- tags = get.tags;
- deep = get.deep;
- tagName = get.name;
- }
-
- parentNode = parentNode
- ? ("" + parentNode === parentNode
- ? $(parentNode)[0] // It is a string, so treat as selector
- : parentNode.jquery
- ? parentNode[0] // A jQuery object - take first element.
- : parentNode)
- : (self.parentElem // view.link()
- || document.body); // link(null, data) to link the whole document
-
- parentTag = parentNode.tagName.toLowerCase();
- elCnt = !!elContent[parentTag];
-
- prevNode = prevNode && markPrevOrNextNode(prevNode, elCnt);
- nextNode = nextNode && markPrevOrNextNode(nextNode, elCnt) || null;
-
- if (html !== undefined) {
- //================ Insert html into DOM using documentFragments (and wrapping HTML appropriately). ================
- // Also convert markers to DOM annotations, based on content model.
- // Corresponds to nextNode ? $(nextNode).before(html) : $(parentNode).html(html);
- // but allows insertion to wrap correctly even with inserted script nodes. jQuery version will fail e.g. under tbody or select.
- // This version should also be slightly faster
- div = document.createElement("div");
- wrapper = div;
- prevIds = ids = "";
- htmlTag = parentNode.namespaceURI === "http://www.w3.org/2000/svg" ? "svg" : (firstTag = rFirstElem.exec(html)) && firstTag[1] || "";
- if (noDomLevel0 && firstTag && firstTag[2]) {
- error("Unsupported: " + firstTag[2]); // For security reasons, don't allow insertion of elements with onFoo attributes.
- }
- if (elCnt) {
- // Now look for following view, and find its tokens, or if not found, get the parentNode._dfr tokens
- node = nextNode;
- while (node && !(nextView = viewInfos(node))) {
- node = node.nextSibling;
- }
- if (tokens = nextView ? nextView.tokens : parentNode._dfr) {
- token = prevView || "";
- if (refresh || !prevView) {
- token += "#" + thisId;
- }
- j = tokens.indexOf(token);
- if (j + 1) {
- j += token.length;
- // Transfer the initial tokens to inserted nodes, by setting them as the ids variable, picked up in convertMarkers
- prevIds = ids = tokens.slice(0, j);
- tokens = tokens.slice(j);
- if (nextView) {
- node.setAttribute(jsvAttrStr, tokens);
- } else {
- parentNode._dfr = tokens;
- }
- }
- }
- }
-
- //================ Convert the markers to DOM annotations, based on content model. ================
-// oldElCnt = elCnt;
- html = ("" + html).replace(rConvertMarkers, convertMarkers);
-// if (!!oldElCnt !== !!elCnt) {
-// error("Parse: " + html); // Parse error. Content not well-formed?
-// }
- // Append wrapper element to doc fragment
- safeFragment.appendChild(div);
-
- // Go to html and back, then peel off extra wrappers
- // Corresponds to jQuery $(nextNode).before(html) or $(parentNode).html(html);
- // but supports svg elements, and other features missing from jQuery version (and this version should also be slightly faster)
- htmlTag = wrapMap[htmlTag] || wrapMap.div;
- depth = htmlTag[0];
- wrapper.innerHTML = htmlTag[1] + html + htmlTag[2];
- while (depth--) {
- wrapper = wrapper.lastChild;
- }
- safeFragment.removeChild(div);
- fragment = document.createDocumentFragment();
- while (copiedNode = wrapper.firstChild) {
- fragment.appendChild(copiedNode);
- }
- // Insert into the DOM
- parentNode.insertBefore(fragment, nextNode);
- }
-
- if (lazyLink) {
- setTimeout(dataLink, 0);
- } else {
- dataLink();
- }
-
- return lazyLink && lazyLink.promise();
- }
-
- function addDataBinding(linkMarkup, node, currentView, data, boundTagId) {
- // Add data binding for data-linked elements or {^{...}} data-linked tags
- var tmpl, tokens, attr, convertBack, params, trimLen, tagExpr, linkFn, linkCtx, tag, rTagIndex;
-
- if (boundTagId) {
- // {^{...}} data-linked tag. So only one linkTag in linkMarkup
- tag = bindingStore[boundTagId];
- tag = tag.linkCtx ? tag.linkCtx.tag : tag;
-
- linkCtx = tag.linkCtx || {
- data: data, // source
- elem: tag._elCnt ? tag.parentElem : node, // target
- view: currentView,
- ctx: currentView.ctx,
- attr: "html", // Script marker nodes are associated with {^{ and always target HTML.
- fn: tag._.bnd,
- tag: tag,
- // Pass the boundTagId in the linkCtx, so that it can be picked up in observeAndBind
- _bndId: boundTagId
- };
- bindDataLinkTarget(linkCtx, linkCtx.fn);
- } else if (linkMarkup && node) {
- // Compiled linkFn expressions could be stored in the tmpl.links array of the template
- // TODO - consider also caching globally so that if {{:foo}} or data-link="foo" occurs in different places,
- // the compiled template for this is cached and only compiled once...
- //links = currentView.links || currentView.tmpl.links;
-
- tmpl = currentView.tmpl;
-
-// if (!(linkTags = links[linkMarkup])) {
- // This is the first time this view template has been linked, so we compile the data-link expressions, and store them on the template.
-
- linkMarkup = normalizeLinkTag(linkMarkup, node);
- rTag.lastIndex = 0;
- while (tokens = rTag.exec(linkMarkup)) { // TODO require } to be followed by whitespace or $, and remove the \}(!\}) option.
- // Iterate over the data-link expressions, for different target attrs,
- // (only one if there is a boundTagId - the case of data-linked tag {^{...}})
- // e.g. )|!--((?:[^-]|-(?!-))*)--|(\*)))\s*((?:[^}]|}(?!}))*?))})
- return this;
- })();
-
- //===============
- // Public helpers
- //===============
-
- $viewsSub.viewInfos = viewInfos;
- // Expose viewInfos() as public helper method
-
- //====================================
- // Additional members for linked views
- //====================================
-
- function transferViewTokens(prevNode, nextNode, parentElem, id, viewOrTagChar, refresh) {
- // Transfer tokens on prevNode of viewToRemove/viewToRefresh to nextNode or parentElem._dfr
- var i, l, vwInfos, vwInfo, viewOrTag, viewId, tokens,
- precedingLength = 0,
- emptyView = prevNode === nextNode;
-
- if (prevNode) {
- // prevNode is either the first node in the viewOrTag, or has been replaced by the vwInfos tokens string
- vwInfos = viewInfos(prevNode) || [];
- for (i = 0, l = vwInfos.length; i < l; i++) {
- // Step through views or tags on the prevNode
- vwInfo = vwInfos[i];
- viewId = vwInfo.id;
- if (viewId === id && vwInfo.ch === viewOrTagChar) {
- if (refresh) {
- // This is viewOrTagToRefresh, this is the last viewOrTag to process...
- l = 0;
- } else {
- // This is viewOrTagToRemove, so we are done...
- break;
- }
- }
- if (!emptyView) {
- viewOrTag = vwInfo.ch === "_"
- ? viewStore[viewId]
- : bindingStore[viewId].linkCtx.tag;
- if (vwInfo.open) {
- // A "#m" token
- viewOrTag._prv = nextNode;
- } else if (vwInfo.close) {
- // A "/m" token
- viewOrTag._nxt = nextNode;
- }
- }
- precedingLength += viewId.length + 2;
- }
-
- if (precedingLength) {
- prevNode.setAttribute(jsvAttrStr, prevNode.getAttribute(jsvAttrStr).slice(precedingLength));
- }
- tokens = nextNode ? nextNode.getAttribute(jsvAttrStr) : parentElem._dfr;
- if (l = tokens.indexOf("/" + id + viewOrTagChar) + 1) {
- tokens = vwInfos.tokens.slice(0, precedingLength) + tokens.slice(l + (refresh ? -1 : id.length + 1));
- }
- if (tokens) {
- if (nextNode) {
- // If viewOrTagToRemove was an empty viewOrTag, we will remove both #n and /n
- // (and any intervening tokens) from the nextNode (=== prevNode)
- // If viewOrTagToRemove was not empty, we will take tokens preceding #n from prevNode,
- // and concatenate with tokens following /n on nextNode
- nextNode.setAttribute(jsvAttrStr, tokens);
- } else {
- parentElem._dfr = tokens;
- }
- }
- } else {
- // !prevNode, so there may be a deferred nodes token on the parentElem. Remove it.
- parentElem._dfr = removeSubStr(parentElem._dfr, "#" + id + viewOrTagChar);
- if (!refresh && !nextNode) {
- // If this viewOrTag is being removed, and there was no .nxt, remove closing token from deferred tokens
- parentElem._dfr = removeSubStr(parentElem._dfr, "/" + id + viewOrTagChar);
- }
- }
- }
-
- LinkedView = {
- // Note: a linked view will also, after linking have nodes[], _prv (prevNode), _nxt (nextNode) ...
- addViews: function(index, dataItems, tmpl) {
- // if view is not an array view, do nothing
- var i, viewsCount,
- self = this,
- itemsCount = dataItems.length,
- views = self.views;
-
- if (!self._.useKey && itemsCount && (tmpl = self.tmpl)) {
- // view is of type "array"
- // Use passed-in template if provided, since self added view may use a different template than the original one used to render the array.
- viewsCount = views.length + itemsCount;
-
- if (renderAndLink(self, index, tmpl, views, dataItems, self.ctx) !== false) {
- for (i = index + itemsCount; i < viewsCount; i++) {
- $observable(views[i]).setProperty("index", i);
- //This is fixing up index, but not key, and not index on child views. From child views, use view.get("item").index.
- }
- }
- }
- return self;
- },
-
- removeViews: function(index, itemsCount, keepNodes) {
- // view.removeViews() removes all the child views
- // view.removeViews( index ) removes the child view with specified index or key
- // view.removeViews( index, count ) removes the specified nummber of child views, starting with the specified index
- function removeView(index) {
- var id, bindId, parentElem, prevNode, nextNode, nodesToRemove,
- viewToRemove = views[index];
-
- if (viewToRemove && viewToRemove.link) {
- id = viewToRemove._.id;
- if (!keepNodes) {
- // Remove the HTML nodes from the DOM, unless they have already been removed, including nodes of child views
- nodesToRemove = viewToRemove.nodes();
- }
-
- // Remove child views, without removing nodes
- viewToRemove.removeViews(undefined, undefined, true);
-
- viewToRemove.data = undefined; // Set data to undefined: used as a flag that this view is being removed
- prevNode = viewToRemove._prv;
- nextNode = viewToRemove._nxt;
- parentElem = viewToRemove.parentElem;
- // If prevNode and nextNode are the same, the view is empty
- if (!keepNodes) {
- // Remove the HTML nodes from the DOM, unless they have already been removed, including nodes of child views
- if (viewToRemove._elCnt) {
- // if keepNodes is false (and transferring of tokens has not already been done at a higher level)
- // then transfer tokens from prevNode which is being removed, to nextNode.
- transferViewTokens(prevNode, nextNode, parentElem, id, "_");
- }
- $(nodesToRemove).remove();
- }
- if (!viewToRemove._elCnt) {
- prevNode.parentNode && parentElem.removeChild(prevNode);
- nextNode.parentNode && parentElem.removeChild(nextNode);
- }
- setArrayChangeLink(viewToRemove);
- for (bindId in viewToRemove._.bnds) {
- removeViewBinding(bindId);
- }
- delete viewStore[id];
- }
- }
-
- var current, view, viewsCount,
- self = this,
- isArray = !self._.useKey,
- views = self.views;
-
- if (isArray) {
- viewsCount = views.length;
- }
- if (index === undefined) {
- // Remove all child views
- if (isArray) {
- // views and data are arrays
- current = viewsCount;
- while (current--) {
- removeView(current);
- }
- self.views = [];
- } else {
- // views and data are objects
- for (view in views) {
- // Remove by key
- removeView(view);
- }
- self.views = {};
- }
- } else {
- if (itemsCount === undefined) {
- if (isArray) {
- // The parentView is data array view.
- // Set itemsCount to 1, to remove this item
- itemsCount = 1;
- } else {
- // Remove child view with key 'index'
- removeView(index);
- delete views[index];
- }
- }
- if (isArray && itemsCount) {
- current = index + itemsCount;
- // Remove indexed items (parentView is data array view);
- while (current-- > index) {
- removeView(current);
- }
- views.splice(index, itemsCount);
- if (viewsCount = views.length) {
- // Fixup index on following view items...
- while (index < viewsCount) {
- $observable(views[index]).setProperty("index", index++);
- }
- }
- }
- }
- return this;
- },
-
- refresh: function(context) {
- var self = this,
- parent = self.parent;
-
- if (parent) {
- renderAndLink(self, self.index, self.tmpl, parent.views, self.data, context, true);
- setArrayChangeLink(self);
- }
- return self;
- },
-
- nodes: getNodes,
- contents: getContents,
- childTags: getChildTags,
- link: viewLink
- };
-
- //=========================
- // Extend $.views.settings
- //=========================
-
- $viewsSettings.merge = {
- input: {
- from: { fromAttr: inputAttrib }, to: { toAttr: "value" }
- },
- textarea: valueBinding,
- select: valueBinding,
- optgroup: {
- from: { fromAttr: "label" }, to: { toAttr: "label" }
- }
- };
-
- if ($viewsSettings.debugMode) {
- // In debug mode create global for accessing views, etc
- validate = !$viewsSettings.noValidate;
- global._jsv = {
- views: viewStore,
- bindings: bindingStore
- };
- }
-
- //========================
- // Extend jQuery namespace
- //========================
-
- $extend($, {
-
- //=======================
- // jQuery $.view() plugin
- //=======================
-
- view: $views.view = $view = function(node, inner, type) {
- // $.view() returns top view
- // $.view(node) returns view that contains node
- // $.view(selector) returns view that contains first selected element
- // $.view(nodeOrSelector, type) returns nearest containing view of given type
- // $.view(nodeOrSelector, "root") returns root containing view (child of top view)
- // $.view(nodeOrSelector, true, type) returns nearest inner (contained) view of given type
-
- if (inner !== !!inner) {
- // inner not boolean, so this is view(nodeOrSelector, type)
- type = inner;
- inner = undefined;
- }
- var view, vwInfos, i, j, k, l, elem, elems,
- level = 0,
- body = document.body;
-
- if (node && node !== body && topView._.useKey > 1) {
- // Perf optimization for common cases
-
- node = "" + node === node
- ? $(node)[0]
- : node.jquery
- ? node[0]
- : node;
-
- if (node) {
- if (inner) {
- // Treat supplied node as a container element and return the first view encountered.
- elems = qsa ? node.querySelectorAll(bindElsSel) : $(bindElsSel, node).get();
- l = elems.length;
- for (i = 0; i < l; i++) {
- elem = elems[i];
- vwInfos = viewInfos(elem, undefined, rOpenViewMarkers);
-
- for (j = 0, k = vwInfos.length; j < k; j++) {
- view = vwInfos[j];
- if (view = viewStore[view.id]) {
- view = view && type ? view.get(true, type) : view;
- if (view) {
- return view;
- }
- }
- }
- }
- } else {
- while (node) {
- // Move back through siblings and up through parents to find preceding node which is a _prv (prevNode)
- // script marker node for a non-element-content view, or a _prv (first node) for an elCnt view
- if (vwInfos = viewInfos(node, undefined, rViewMarkers)) {
- l = vwInfos.length;
- while (l--) {
- view = vwInfos[l];
- if (view.open) {
- if (level < 1) {
- view = viewStore[view.id];
- return view && type ? view.get(type) : view || topView;
- }
- level--;
- } else {
- // level starts at zero. If we hit a view.close, then we move level to 1, and we don't return a view until
- // we are back at level zero (or a parent view with level < 0)
- level++;
- }
- }
- }
- node = node.previousSibling || node.parentNode;
- }
- }
- }
- }
- return inner ? undefined : topView;
- },
-
- link: $views.link = $link,
- unlink: $views.unlink = $unlink,
-
- //=====================
- // override $.cleanData
- //=====================
- cleanData: function(elems) {
- if (elems.length) {
- // Remove JsViews bindings. Also, remove from the DOM any corresponding script marker nodes
- clean(elems);
- // (elems HTMLCollection no longer includes removed script marker nodes)
- oldCleanData.call($, elems);
- }
- }
- });
-
- //===============================
- // Extend jQuery instance plugins
- //===============================
-
- $extend($.fn, {
- link: function(expr, from, context, parentView, prevNode, nextNode) {
- return $link(expr, this, from, context, parentView, prevNode, nextNode);
- },
- unlink: function(expr) {
- return $unlink(expr, this);
- },
- view: function(type) {
- return $view(this[0], type);
- }
- });
-
- //===============
- // Extend topView
- //===============
-
- $extend(topView, { tmpl: { links: {}, tags: {} }});
- $extend(topView, LinkedView);
- topView._.onRender = addBindingMarkers;
-
-})(this, this.jQuery);
diff --git a/nano/js/nano_base_helpers.js b/nano/js/nano_base_helpers.js
index 721795c09e4..53b539f75ac 100644
--- a/nano/js/nano_base_helpers.js
+++ b/nano/js/nano_base_helpers.js
@@ -14,14 +14,17 @@ NanoBaseHelpers = function ()
var initHelpers = function ()
{
- $.views.helpers({
+ $.views.helpers({
+
// Generate a Byond link
link: function( text, icon, parameters, status, elementClass, elementId) {
var iconHtml = '';
+ var iconClass = 'noIcon';
if (typeof icon != 'undefined' && icon)
{
- iconHtml = '';
+ iconHtml = '';
+ iconClass = 'hasIcon';
}
if (typeof elementClass == 'undefined' || !elementClass)
@@ -37,10 +40,10 @@ NanoBaseHelpers = function ()
if (typeof status != 'undefined' && status)
{
- return '
' + iconHtml + text + '
';
+ return '
' + iconHtml + text + '
';
}
- return '
' + iconHtml + text + '
';
+ return '
' + iconHtml + text + '
';
},
// Round a number to the nearest integer
round: function(number) {
@@ -114,9 +117,63 @@ NanoBaseHelpers = function ()
var percentage = Math.round((value - rangeMin) / (rangeMax - rangeMin) * 100);
return '
' + showText + '
';
+ },
+ // Display DNA Blocks (for the DNA Modifier UI)
+ displayDNABlocks: function(dnaString, selectedBlock, selectedSubblock, blockSize, paramKey) {
+ if (!dnaString)
+ {
+ return '
Please place a valid subject into the DNA modifier.
';
+ }
+
+ var characters = dnaString.split('');
+
+ var html = '
1
';
+ var block = 1;
+ var subblock = 1;
+ for (index in characters)
+ {
+ if (!characters.hasOwnProperty(index) || typeof characters[index] === 'object')
+ {
+ continue;
+ }
+
+ var parameters;
+ if (paramKey.toUpperCase() == 'UI')
+ {
+ parameters = { 'selectUIBlock' : block, 'selectUISubblock' : subblock };
+ }
+ else
+ {
+ parameters = { 'selectSEBlock' : block, 'selectSESubblock' : subblock };
+ }
+
+ var status = 'linkActive';
+ if (block == selectedBlock && subblock == selectedSubblock)
+ {
+ status = 'selected';
+ }
+
+ html += '
' + characters[index] + '
'
+
+ index++;
+ if (index % blockSize == 0 && index < characters.length)
+ {
+ block++;
+ subblock = 1;
+ html += '
';
+
+ return html;
}
});
- }
+ };
// generate a Byond href, combines _urlParameters with parameters
var generateHref = function (parameters)
@@ -147,7 +204,7 @@ NanoBaseHelpers = function ()
}
}
return queryString;
- }
+ };
return {
init: function ()
diff --git a/nano/js/nano_config.js b/nano/js/nano_config.js
index 7766b64a4b5..8352b197ccf 100644
--- a/nano/js/nano_config.js
+++ b/nano/js/nano_config.js
@@ -4,7 +4,12 @@ var NanoConfig = function ()
return {
init: function ()
{
-
+ if (typeof jQuery == 'undefined') {
+ alert('ERROR: jQuery failed to load!');
+ }
+ if (typeof $.views == 'undefined') {
+ alert('ERROR: JSRender failed to load!');
+ }
}
}
} ();
@@ -32,7 +37,7 @@ if (!Array.prototype.indexOf)
}
return -1;
};
-}
+};
if (!String.prototype.format)
{
@@ -54,7 +59,7 @@ if (!String.prototype.format)
});
};
String.prototype.format.regex = new RegExp("{-?[0-9]+}", "g");
-}
+};
Object.size = function(obj) {
var size = 0, key;
@@ -70,7 +75,7 @@ if(!window.console) {
return false;
}
};
-}
+};
String.prototype.toTitleCase = function () {
var smallWords = /^(a|an|and|as|at|but|by|en|for|if|in|of|on|or|the|to|vs?\.?|via)$/i;
diff --git a/nano/js/nano_update.js b/nano/js/nano_update.js
index 47f1ee6ee52..cb2eee021d7 100644
--- a/nano/js/nano_update.js
+++ b/nano/js/nano_update.js
@@ -23,6 +23,8 @@ NanoUpdate = function ()
// this function sets up the templates and base functionality
var init = function ()
{
+ $('#uiNoJavaScript').html('Loading...');
+
// this callback is triggered after new data is processed
// it updates the status/visibility icon and adds click event handling to buttons/links
NanoUpdate.addAfterUpdateCallback(function (updateData) {
@@ -71,9 +73,9 @@ NanoUpdate = function ()
// We store initialData and templateData in the body tag, it's as good a place as any
var body = $('body');
var templateData = body.data('templateData');
- _data = body.data('initialData');
+ var initialData = body.data('initialData');
- if (!templateData || !_data)
+ if (templateData == null || !initialData == null)
{
alert('Error: Initial data did not load correctly.');
}
@@ -88,44 +90,52 @@ NanoUpdate = function ()
}
}
- // load each template file and render it using _data
+ if (!templateCount)
+ {
+ alert('ERROR: No templates listed!');
+ }
+
+ // load markup for each template and register it
for (var key in templateData)
{
if (templateData.hasOwnProperty(key))
{
$.when($.get(templateData[key]))
- .done(function(templateData) {
+ .done(function(templateMarkup) {
if (_templates == null)
{
_templates = {};
}
- templateData += ''
+ templateMarkup = templateMarkup.replace(/ +\) *\}\}/g, ')}}');
+
+ templateMarkup += ''
try
{
- _templates[key] = $.templates(templateData);
- _templates[key].link( "#mainTemplate", _data ); // initial data gets applied first, before any updates
+ _templates[key] = $.templates(key, templateMarkup);
templateCount--;
if (templateCount <= 0)
{
+ if (_earlyUpdateData !== null) // Newer data has already arrived, so update
+ {
+ renderTemplates(_earlyUpdateData);
+ }
+ else
+ {
+ renderTemplates(initialData);
+ }
_isInitialised = true;
+ $('#uiNoJavaScript').hide();
}
-
- if (_earlyUpdateData !== null) // Newer data has already arrived, so update
- {
- observedDataUpdateRecursive(_earlyUpdateData, _data);
- }
executeCallbacks(_afterUpdateCallbacks, _data);
-
- //alert($("#mainTemplate").html());
}
catch(error)
{
- alert('An error occurred while loading the UI: ' + error.message);
+ alert('ERROR: An error occurred while loading the UI: ' + error.message);
return;
}
});
@@ -149,50 +159,43 @@ NanoUpdate = function ()
}
- if (_isInitialised) // templates have been loaded and are observing the data. We need to update it recursively
+ if (_isInitialised) // all templates have been registered, so render them
{
executeCallbacks(_beforeUpdateCallbacks, updateData);
- observedDataUpdateRecursive(updateData, _data);
+ renderTemplates(updateData);
executeCallbacks(_afterUpdateCallbacks, updateData);
}
else
{
- _earlyUpdateData = updateData; // templates have not been loaded, therefor they are not observing the data. We set _earlyUpdateData which will be applied after the template is loaded with the initial data
+ _earlyUpdateData = updateData; // all templates have not been registered. We set _earlyUpdateData which will be applied after the template is loaded with the initial data
}
- }
+ };
- // This function updates the observed data recursively
+ // This function renders the template with the latest data
// It has to be done recursively as each piece of data is observed individually and needs to be updated individually
- var observedDataUpdateRecursive = function (updateData, data, path)
+ var renderTemplates = function (data)
{
- if (path === null || typeof path === 'undefined')
+ if (!_templates.hasOwnProperty("main"))
{
- path = '';
+ alert('Error: Main template not found.');
}
- else
+
+ _data = data;
+
+ try
{
- path += '.';
+ $("#mainTemplate").html(_templates["main"].render(_data));
}
- for (var key in updateData)
+ catch(error)
{
- if (updateData.hasOwnProperty(key))
- {
- var currentPath = path + key;
- if (updateData[key] != null && typeof updateData[key] === 'object' && !$.isArray(updateData[key]))
- {
- observedDataUpdateRecursive(updateData[key], data, currentPath)
- }
- else
- {
- $.observable(data).setProperty(currentPath, updateData[key]);
- }
- }
- }
- }
+ alert('ERROR: An error occurred while rendering the UI: ' + error.message);
+ return;
+ }
+ };
- // execute all callbacks in the callbacks array/object provided, updateData is passed to them for processing
+ // Execute all callbacks in the callbacks array/object provided, updateData is passed to them for processing
var executeCallbacks = function (callbacks, updateData)
{
for (var index in callbacks)
@@ -201,7 +204,7 @@ NanoUpdate = function ()
}
return updateData;
- }
+ };
return {
init: function ()
diff --git a/nano/templates/TemplatesGuide.txt b/nano/templates/TemplatesGuide.txt
index 3cef9b91287..3b042edd0b0 100644
--- a/nano/templates/TemplatesGuide.txt
+++ b/nano/templates/TemplatesGuide.txt
@@ -7,7 +7,4 @@ to easily add conditionals (if statements), loops (for loops) and custom formatt
Templates are stored in the /nano/templates folder and the file extension is .tmpl.
-This guide is a work in progress. However the templating system is "JSRender", and documentation for it's
-markup syntax can be found here: http://www.jsviews.com/#jsrtags
-
-
+This guide is being replaced with a wiki entry, found here: http://baystation12.net/wiki/index.php?title=NanoUI
\ No newline at end of file
diff --git a/nano/templates/apc.tmpl b/nano/templates/apc.tmpl
index b57829f397f..ec7a576fc8e 100644
--- a/nano/templates/apc.tmpl
+++ b/nano/templates/apc.tmpl
@@ -1,14 +1,14 @@
{{else}}
- {^{if locked}}
+ {{if locked}}
Swipe an ID card to unlock this interface.
{{else}}
Swipe an ID card to lock this interface.
@@ -18,162 +18,162 @@
\ No newline at end of file
diff --git a/nano/templates/chem_dispenser.tmpl b/nano/templates/chem_dispenser.tmpl
index 9d9f6525fd4..10f1e1206f0 100644
--- a/nano/templates/chem_dispenser.tmpl
+++ b/nano/templates/chem_dispenser.tmpl
@@ -7,7 +7,7 @@ Used In File(s): \code\modules\reagents\Chemistry-Machinery.dm
Energy:
- {^{:~displayBar(energy, 0, maxEnergy, 'good', energy + ' Units')}}
+ {{:~displayBar(energy, 0, maxEnergy, 'good', energy + ' Units')}}
@@ -16,11 +16,11 @@ Used In File(s): \code\modules\reagents\Chemistry-Machinery.dm
Dispense: