From 2bbc0e4d13a9cd4a5dde6d47ed8e6b4bb3a931ee Mon Sep 17 00:00:00 2001 From: Ruben Date: Sat, 19 Feb 2011 16:21:29 +0100 Subject: [PATCH] added auth --- client/core/ide.js | 33 +- client/ext/auth/auth.js | 247 + client/ext/auth/auth.xml | 167 + client/ext/connect/connect.js | 46 + client/ext/filesystem/filesystem.js | 19 +- .../offline/.lib-offline.js.1297620930000.gz | Bin 1953 -> 0 bytes .../offline/.lib-offline.js.1297621845000.gz | Bin 1951 -> 0 bytes .../offline/.lib-offline.js.1297621931000.gz | Bin 1950 -> 0 bytes .../offline/.lib-offline.js.1297621971000.gz | Bin 1957 -> 0 bytes .../offline/.lib-offline.js.1297621979000.gz | Bin 1960 -> 0 bytes .../offline/.lib-offline.js.1297621999000.gz | Bin 1953 -> 0 bytes .../offline/.lib-offline.js.1297622147000.gz | Bin 1964 -> 0 bytes .../offline/.lib-offline.js.1297622343000.gz | Bin 1970 -> 0 bytes .../offline/.lib-offline.js.1297622362000.gz | Bin 1967 -> 0 bytes .../offline/.lib-offline.js.1297623084000.gz | Bin 1963 -> 0 bytes .../offline/.lib-offline.js.1297624401000.gz | Bin 1774 -> 0 bytes .../ext/offline/.lib-sync.js.1297621334000.gz | Bin 775 -> 0 bytes .../ext/offline/.lib-sync.js.1297621888000.gz | Bin 773 -> 0 bytes .../ext/offline/.lib-sync.js.1297622003000.gz | Bin 776 -> 0 bytes .../ext/offline/.lib-sync.js.1297622910000.gz | Bin 788 -> 0 bytes .../ext/offline/.lib-sync.js.1297623803000.gz | Bin 772 -> 0 bytes .../ext/offline/.lib-sync.js.1297623883000.gz | Bin 778 -> 0 bytes .../ext/offline/.lib-sync.js.1297623956000.gz | Bin 788 -> 0 bytes .../ext/offline/.lib-sync.js.1297624215000.gz | Bin 792 -> 0 bytes .../ext/offline/.lib-sync.js.1297624271000.gz | Bin 801 -> 0 bytes .../ext/offline/.lib-sync.js.1297624318000.gz | Bin 796 -> 0 bytes .../ext/offline/.offline.js.1297609329000.gz | Bin 5831 -> 0 bytes .../ext/offline/.offline.js.1297618511000.gz | Bin 682 -> 0 bytes .../ext/offline/.offline.js.1297621783000.gz | Bin 1337 -> 0 bytes .../ext/offline/.offline.js.1297621832000.gz | Bin 1337 -> 0 bytes .../ext/offline/.offline.js.1297621947000.gz | Bin 1344 -> 0 bytes .../ext/offline/.offline.js.1297622097000.gz | Bin 1325 -> 0 bytes .../ext/offline/.offline.js.1297622166000.gz | Bin 1332 -> 0 bytes .../ext/offline/.offline.js.1297622262000.gz | Bin 1332 -> 0 bytes .../ext/offline/.offline.js.1297622400000.gz | Bin 1338 -> 0 bytes .../ext/offline/.offline.js.1297622486000.gz | Bin 1345 -> 0 bytes .../ext/offline/.offline.js.1297623019000.gz | Bin 1396 -> 0 bytes .../ext/offline/.offline.js.1297623385000.gz | Bin 1417 -> 0 bytes .../ext/offline/.offline.js.1297623411000.gz | Bin 1437 -> 0 bytes .../ext/offline/.offline.js.1297623471000.gz | Bin 1445 -> 0 bytes .../ext/offline/.offline.js.1297623551000.gz | Bin 1437 -> 0 bytes .../ext/offline/.offline.js.1297671819000.gz | Bin 1459 -> 0 bytes client/ext/offline/lib-offline.js | 30 +- client/ext/offline/offline.js | 48 +- client/ext/settings/settings.js | 8 + client/include/windows.xml | 54 +- client/js/apf_debug.js | 135917 +++++++++++++++ server/cloud9/ide.js | 1 + 48 files changed, 136476 insertions(+), 94 deletions(-) create mode 100644 client/ext/auth/auth.js create mode 100644 client/ext/auth/auth.xml create mode 100644 client/ext/connect/connect.js delete mode 100644 client/ext/offline/.lib-offline.js.1297620930000.gz delete mode 100644 client/ext/offline/.lib-offline.js.1297621845000.gz delete mode 100644 client/ext/offline/.lib-offline.js.1297621931000.gz delete mode 100644 client/ext/offline/.lib-offline.js.1297621971000.gz delete mode 100644 client/ext/offline/.lib-offline.js.1297621979000.gz delete mode 100644 client/ext/offline/.lib-offline.js.1297621999000.gz delete mode 100644 client/ext/offline/.lib-offline.js.1297622147000.gz delete mode 100644 client/ext/offline/.lib-offline.js.1297622343000.gz delete mode 100644 client/ext/offline/.lib-offline.js.1297622362000.gz delete mode 100644 client/ext/offline/.lib-offline.js.1297623084000.gz delete mode 100644 client/ext/offline/.lib-offline.js.1297624401000.gz delete mode 100644 client/ext/offline/.lib-sync.js.1297621334000.gz delete mode 100644 client/ext/offline/.lib-sync.js.1297621888000.gz delete mode 100644 client/ext/offline/.lib-sync.js.1297622003000.gz delete mode 100644 client/ext/offline/.lib-sync.js.1297622910000.gz delete mode 100644 client/ext/offline/.lib-sync.js.1297623803000.gz delete mode 100644 client/ext/offline/.lib-sync.js.1297623883000.gz delete mode 100644 client/ext/offline/.lib-sync.js.1297623956000.gz delete mode 100644 client/ext/offline/.lib-sync.js.1297624215000.gz delete mode 100644 client/ext/offline/.lib-sync.js.1297624271000.gz delete mode 100644 client/ext/offline/.lib-sync.js.1297624318000.gz delete mode 100644 client/ext/offline/.offline.js.1297609329000.gz delete mode 100644 client/ext/offline/.offline.js.1297618511000.gz delete mode 100644 client/ext/offline/.offline.js.1297621783000.gz delete mode 100644 client/ext/offline/.offline.js.1297621832000.gz delete mode 100644 client/ext/offline/.offline.js.1297621947000.gz delete mode 100644 client/ext/offline/.offline.js.1297622097000.gz delete mode 100644 client/ext/offline/.offline.js.1297622166000.gz delete mode 100644 client/ext/offline/.offline.js.1297622262000.gz delete mode 100644 client/ext/offline/.offline.js.1297622400000.gz delete mode 100644 client/ext/offline/.offline.js.1297622486000.gz delete mode 100644 client/ext/offline/.offline.js.1297623019000.gz delete mode 100644 client/ext/offline/.offline.js.1297623385000.gz delete mode 100644 client/ext/offline/.offline.js.1297623411000.gz delete mode 100644 client/ext/offline/.offline.js.1297623471000.gz delete mode 100644 client/ext/offline/.offline.js.1297623551000.gz delete mode 100644 client/ext/offline/.offline.js.1297671819000.gz create mode 100644 client/js/apf_debug.js diff --git a/client/core/ide.js b/client/core/ide.js index 62771e834b8..163ab6075e8 100644 --- a/client/core/ide.js +++ b/client/core/ide.js @@ -30,6 +30,8 @@ require.def("core/ide", ["core/document", "/socket.io/socket.io.js"], this.sessionId = window.cloud9config.sessionId; this.workspaceId = window.cloud9config.workspaceId; + this.loggedIn = true; + this.dispatchEvent("load"); /**** Error Handling ****/ @@ -94,7 +96,8 @@ require.def("core/ide", ["core/document", "/socket.io/socket.io.js"], // fire up the socket connection: var options = { rememberTransport: false, - transports: ["websocket", "htmlfile", "xhr-polling", "jsonp-polling"], + transports: ["websocket", "htmlfile", "xhr-polling", "jsonp-polling"], + connectTimeout: 5000, transportOptions: { "xhr-polling": { timeout: 30000 @@ -106,6 +109,8 @@ require.def("core/ide", ["core/document", "/socket.io/socket.io.js"], }; ide.socketConnect = function() { + clearTimeout(ide.$retryTimer); + ide.socket.send(JSON.stringify({ command: "attach", sessionId: ide.sessionId, @@ -114,15 +119,18 @@ require.def("core/ide", ["core/document", "/socket.io/socket.io.js"], }; ide.socketDisconnect = function() { - ide.dispatchEvent("socketDisconnect"); - clearTimeout(ide.$retryTimer); + var retries = 0; ide.$retryTimer = setInterval(function() { - if (retries++ == 1) - winReconnect.show(); - ide.socket.connect(); - }, 2000); + if (++retries == 1) { + stServerConnected.deactivate(); + ide.dispatchEvent("socketDisconnect"); + } + + if (!ide.socket.connecting && !ide.testOffline && ide.loggedIn) + ide.socket.connect(); + }, 500); }; ide.socketMessage = function(message) { @@ -133,8 +141,6 @@ require.def("core/ide", ["core/document", "/socket.io/socket.io.js"], } if (message.type == "attached") { - clearTimeout(ide.$retryTimer); - winReconnect.hide(); stServerConnected.activate(); ide.dispatchEvent("socketConnect"); } @@ -142,10 +148,13 @@ require.def("core/ide", ["core/document", "/socket.io/socket.io.js"], ide.dispatchEvent("socketMessage", { message: message }); - if (message.type && message.type == "state") { - stProcessRunning.setProperty("active", message.processRunning); - } }; + + //@todo see if this can be moved to noderunner + ide.addEventListener("socketMessage", function(e){ + if (e.message.type && e.message.type == "state") + stProcessRunning.setProperty("active", e.message.processRunning); + }); ide.socket = new io.Socket(null, options); ide.socket.on("message", ide.socketMessage); diff --git a/client/ext/auth/auth.js b/client/ext/auth/auth.js new file mode 100644 index 00000000000..5ff57c7e298 --- /dev/null +++ b/client/ext/auth/auth.js @@ -0,0 +1,247 @@ +/** + * In App Authentication for Cloud9 + * + * @copyright 2010, Ajax.org B.V. + */ +define(function(require, exports, module) { + +var ide = require("core/ide"); +var ext = require("core/ext"); +var markup = require("text!ext/auth/auth.xml"); + +var ServiceLut = { + "github": { + url: "/auth/github", + width: 1000, + height: 650 + }, + "facebook": { + url: "/auth/facebook", + width: 800, + height: 460 + }, + "twitter": { + url: "/auth/twitter", + width: 800, + height: 460 + } +}, +def = "github", +crtOrigin, winSignin, winPayment, winListener, uid, username, context; + +function winCloseCheck() { + if (winSignin && !winSignin.closed) + return; + clearInterval(winListener); + if (stSignedIn.active) + return; + var pager = crtOrigin === 1 ? pgAuth : pgReserve; + pager.set(crtOrigin + 2); + self.focus(); +} + +return ext.register("ext/auth/auth", { + dev : "Ajax.org", + name : "Auth", + alone : true, + type : ext.GENERAL, + deps : [], + markup : markup, + + init : function(){ + auth.addEventListener("authrequired", function(e){ + ide.loggedIn = false; + ide.socket.disconnect(); + + ide.dispatchEvent("logout"); + }); + + //@todo fire this event; + ide.addEventListener("login", function(e){ + ide.loggedIn = true; + ide.socket.connect(); + }); + }, + + enable : function(){}, + disable : function(){}, + destroy : function(){}, + + register : function() { + var _self = this; + var data = "firstname=" + tbRgFirstName.getValue() + + "&lastname=" + tbRgLastName.getValue() + + "&email=" + tbRgEmail.getValue() + + "&username=" + tbRgUsername.getValue() + + "&password=" + tbRgPassword.getValue(); + comm.auth("create", + data, + function(data, state, extra) { + if (state !== apf.SUCCESS) { + //debugger; + return _self.showAlert("Registration failed", "Error:\n" + extra.data); + } + + data = JSON.parse(data)[0]; + _self.login(data.username, data.password); + } + ); + }, + + getAccount : function(callback) { + var _self = this; + comm.context("account", + null, + function(data, state, extra) { + if (state !== apf.SUCCESS) + return _self.showAlert("Cannot retrieve account data", "Error:\n" + extra.data); + + data = typeof data == "string" ? apf.getXml(data) : data; + mdlAccount.load(data); + if (callback) + callback(); + } + ); + }, + + signin : function(service, email) { + var origin = mnuAuth.visible ? 1 : 2, + pager = origin === 1 ? pgAuth : pgReserve, + sText = service.charAt(0).toUpperCase() + service.substr(1) + service = ServiceLut[service || def], + h4 = document.getElementById("statusMsg" + origin); + if (h4) { + if (!h4.origText) + h4.origText = h4.innerHTML; + h4.innerHTML = h4.origText.replace("%s", sText); + } + + pager.set(origin); + crtOrigin = origin; + + var screenHeight = screen.height; + var left = Math.round((screen.width / 2) - (service.width / 2)); + var top = 0; + if (screenHeight > service.height) + top = Math.round((screenHeight / 2) - (service.height / 2)) + + winSignin = window.open(apf.host + service.url + (email ? "/" + encodeURIComponent(email) : ""), "cloud9signin", + "left=" + left + ",top=" + top + ",width=" + service.width + ",height=" + service.height + + ",personalbar=0,toolbar=0,scrollbars=1,resizable=1" + ); + + winListener = setInterval(winCloseCheck, 1000); + if (winSignin) + winSignin.focus(); + }, + + login : function(username, password) { + // alert("todo normal signin: element?"); + var _self = this; + comm.auth("login", "username="+username+"&password="+password, + function(data, state, extra) { + if (state !== apf.SUCCESS) { + //debugger; + return _self.showAlert("Login failed", "Error:\n" + extra.data); + } + + mnuAuth.hide(); + self["stSignedIn"].activate(); + //document.getElementById("errorMsg1").innerHTML = ""; + /* + setTimeout(function(){ + pgAuth.set(3); + },500); + */ + data = JSON.parse(data); + _self.switchContext(data.activecontext); + _self.parseUserData(data.projects, data.blob, data.contexts, data.members); + } + ); + }, + + signout : function() { + comm.auth("signout", null, function(data, state, extra) { + try { + data = JSON.parse(data); + } + catch(ex) { + data = {result: true}; + } + + uid = username = context = githubprojects = null; + + //self[!data.result ? "stSignedIn" : "stIdle"].activate(); + //pgReserve.set(0); + //vboxRegister.show(); + + //pgAuth.set(0); + //if (data.result) + // username = null; + }); + }, + + signinCallback : function(service, success, projects, blob, activecontext, contexts, members, msg) { + clearInterval(winListener); + var origin = mnuAuth.visible ? 1 : 2, + pager = origin === 1 ? pgAuth : pgReserve; + if (success && pager === pgReserve) { + pager.set(3); + } + else { + if (success) + mnuReserve.hide(); + self[success ? "stSignedIn" : "stIdle"].activate(); + document.getElementById("errorMsg" + origin).innerHTML = msg || ""; + setTimeout(function(){ + pager.set(origin + 2); + },500); + } + if (success) { + this.switchContext(activecontext); + this.parseUserData(projects, blob, contexts, members); + + if (treePrj.length == 0)//this should be something like mdlOrganization.queryNode('context') + document.getElementById('biggreenarrow').style.display = 'block'; + } + }, + + signedIn : function() { + btnSign.setAttribute("onclick", "app.signout()"); + mnuAuth.hide(); + }, + + idle : function() { + btnSign.setAttribute("onclick", ""); + tbClone.clear(); + document.getElementById('helloUser').style.display='none'; + // reset pages to first page: + //if (typeof pgReserve != "undefined") + // pgReserve.set(0); + //if (typeof pgAuth != "undefined") + // pgAuth.set(0); + } +}); + + +/*var githubprojects; +this.parseUserData = function(projects, blob, contexts, members) { + if (blob && blob.user && blob.login) { + // save the username + username = blob.login; + uid = blob.uid; + //console.log("projects: ", projects, "blob", blob); + var helloUser = document.getElementById('helloUser'); + helloUser.innerHTML = 'Hi there ' + (blob.user.name || username) + '! Welcome to Cloud9.'; + helloUser.style.display = 'block'; + + if (blob.repos && blob.repos.length) + githubprojects = this.parseRepos(username, blob.repos); + } + + this.parseContexts(contexts); + this.parseProjects(projects); + this.parseMembers(members); +};*/ + +}); \ No newline at end of file diff --git a/client/ext/auth/auth.xml b/client/ext/auth/auth.xml new file mode 100644 index 00000000000..1d5d9072ac4 --- /dev/null +++ b/client/ext/auth/auth.xml @@ -0,0 +1,167 @@ + + + + + + + + + + + + + + + + + + + + + + + +

+ + E-mail + + + + Password + + +
+ + + + Not a member yet? Subscribe now! + + + Log in + +
+ + + + + + +

Registration

+ +
First name
+ +
+ +
Last name
+ +
+ +
Email
+ +
+ +
Username
+ +
+ +
Password
+ +
+ +
Confirm password
+ +
+ + Register + +
+
+ + + I am already a member + Set password + +
+
+
+
\ No newline at end of file diff --git a/client/ext/connect/connect.js b/client/ext/connect/connect.js new file mode 100644 index 00000000000..0989722508b --- /dev/null +++ b/client/ext/connect/connect.js @@ -0,0 +1,46 @@ +/** + * Connection Handling for Cloud9 + * + * @copyright 2010, Ajax.org B.V. + */ + +define(function(require, exports, module) { + +var ide = require("core/ide"); +var ext = require("core/ext"); + +return ext.register("ext/connect/connect", { + dev : "Ajax.org", + name : "Offline", + alone : true, + type : ext.GENERAL, + deps : [], + + init : function(){ + ide.addEventListener("socketConnect", function(e){ + ide.dispatchEvent("beforeonline"); + ide.dispatchEvent("afteronline"); + + winReconnect.hide(); + }); + + ide.addEventListener("socketDisconnect", function(e){ + ide.dispatchEvent("beforeoffline"); + ide.dispatchEvent("afteroffline"); + + winReconnect.show(); + }); + }, + + enable : function(){ + }, + + disable : function(){ + }, + + destroy : function(){ + //Remove all events + } +}); + +}); \ No newline at end of file diff --git a/client/ext/filesystem/filesystem.js b/client/ext/filesystem/filesystem.js index ba7293934ac..8efdafa79dd 100644 --- a/client/ext/filesystem/filesystem.js +++ b/client/ext/filesystem/filesystem.js @@ -1,12 +1,12 @@ /** - * Node Runner Module for the Cloud9 IDE + * File System Module for the Cloud9 IDE * * @copyright 2010, Ajax.org B.V. * @license GPLv3 */ define(function(require, exports, module) { - + var ide = require("core/ide"); var ext = require("core/ext"); var util = require("core/util"); @@ -193,8 +193,15 @@ return ext.register("ext/filesystem/filesystem", { init : function(amlNode){ this.model = new apf.model(); - this.model.load(""); - + + var _self = this; + ide.addEventListener("afteronline", function(){ + _self.model.load(""); + _self.setProjectName(ide.workspaceDir.split("/").pop()); + + ide.removeEventListener("afteronline", arguments.callee); + }); + var url; if (location.host) { var dav_url = location.href.replace(location.path + location.hash, "") + ide.davPrefix; @@ -272,9 +279,7 @@ return ext.register("ext/filesystem/filesystem", { ide.dispatchEvent("afterreload", {doc : doc, data : data}); } }); - }); - - fs.setProjectName(ide.workspaceDir.split("/").pop()); + }); }, setProjectName : function(name) { diff --git a/client/ext/offline/.lib-offline.js.1297620930000.gz b/client/ext/offline/.lib-offline.js.1297620930000.gz deleted file mode 100644 index e6c2703825c484256c9a06a5e48f60b5aeb4b6ef..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1953 zcmV;S2VVFeiwFq9A6QKS18iwxEpKLKY-w&~E^2cCwOCtk+qe>b_pe}m4p1rUO6e~4 zCE1)_(zGW)ngnpV`>;4DXlX2SE0bD^j%#%N-_Hz5T`lK=AVz@LynJ)}W;kSga4=#A z>=Q`PJZIM{U8oY+)qI`{32eHNiI5q)5Hrp72A9(6w{Q#6upCz=3obJ#8Dk%X#{V;z zD-FynfGLkr8g77zl4)3~TSy~ft=5BIAYh@B9?X^FN$0w+z2JvT&pF?AsvKy zNf?RJDWqNWsHK(DpqlPzHPeVgP<=6e^`kY*6ruUuEgksWfUZ<l7GA_qRP?mMJ)UY|o%q(z(HXCIyX&Z0?F5Adam9dCdfHs>3 zh(u&)W9gvkUtUMDIAZTA@EMpjfMkb^%apy2_JUSc-vPRBZ*5x3Ha}L|hJ>&nJ&gBC z<-q0EPEx2DTQ2}%25DL?S}#Nn6wikFdx4d_a4B#Xu*YCG#Mv4Izt4G&RW)5v&ejH4 zQHd%xT2RvbeE9ls!ruMGzeh@E?0xjRQw9Zq#ND5+FK&Nfzb%X@j^cQ|UPqZ+(bCwj zDC4ZiBlF$-2lvGz4S%nY#8C?KU<}_)?3s_tXRrMC@nl3M%~vuZ8-kGi7>#bZt`C5d zW=`@Ylm$;!TDbg4-Z>YwGlW~e|PiFl34Ovk#VE6mv4v3B?%S++O`%lRqLo_ z+$qg;K;9aa`qG7eQpr~W7*;#{x^C84#E7Tq85N)l%vU;wgK-_4#`)kqZMP{|qR*mS zWdTIw#n1`ghQ|*8m(Pcdqscd<`17En*QGDz&m*8JSI^`e8c&}GTdGx&;(7Kw=nh*0 zPdASi(BPJ9Uf4_t9wQsHR{CjXxnkmS>P9RnhYPMtIF}~KRl@TrW;@T|&t8T2iH)mm z2Dgp>7j|;|e9qQ@f4XT~uq`O5vDh>{A%4W11$`aD+6Ft5DPM3a-vFH#aePMqP>RtT zI{GqIH)y@xvf8{Ije@3}|7aipTA{TP|#e)jkE#UHObB z-&#?i;`p_Me2(jAdlUGsnsjnS8JK_wBPFREP$l-(@HF_W;`NTDG z6Naao=3#e_^L*es$rmS#gigPlo}Nvo?4N(w3tz_j<$eH#KGwcgm})m1W0W*deDKtR zH*a=!Z+Av0w)aSpJt6m?ZYQ0eB9quySD|J{NS_sSmn=jI7b^RV)ef2Om((k&^x5{a z2)(I|<4&6tEvFILT_DdWbN`F&Vh%b)FEy0oxveG4qCwhG41KxMa)3e)U>SvYe+6lA ztgTwxj(+sxm~vIEjBhir9go;0w!%t(W3I)UVS;QhFPr41-0B#(gQWHTA#JacOx746Z>6zm?<*JF=SFeI_5cpqW zj=eDZ`t`%v`!An5tf>wXA?GR>PaQ8AUm3N;4lnWHcTWXqt|Ner?zkljMSx7je2=#r z2r-ICSAseBZU}}Fsi_BdSht;Y9a`n>PTH#C&a6$a1=qDYeQjF;$yx3lXHS+5hlSVCUAmj-Ly4(NNju$Fhcm-X@LVsppw(7xQAU~zPX4bSAhMkjq2hXY52 z+wN6&f&T-jM!q!dLb?Gn!HV)S$HEC3Z~PQ@|K3u)w%BUB=ECf%TWi%X-#cFH1o8L| zg_}aVD=rn-f|_JulPnl_-ZaNyqexzdQ--(bN-AdKIdH1v(wzmn< zao3s`JB{y<|GLdN9VZ_!JV8iZA`J$;4(L%`d!=Jb$e{CwEu;KD?k(y?r~TSX6=>9p z2enps@|LTFfJZH{56CpRKhfkP^NjSkC(SM&oMrb=RX&ZWvQ5--q1M(hGvIhO>tgcCRFUxvqV6iETb=!2a5b_)2Gus?0_kJzvx7VtvnxM`JUzL=6^_y<#-h0;q zYY`S@W)JTYkFEA*M6NCip0sSrb_pe}m4p1rUO6e~4 zCE1)_(zGW)ngnpV`>;4DXlX2SE0bD^j%#%N-_Hz5T`lK=AVz@LynJ)}W;kSga4=#A z>=Q`PJZIM{U8oY+)qI`{32eHNiI5q)5Hrp72A9(6w{Q#6upCz=3obJ#8Dk%X#{V;z zD-FynfGLkr8g77zl4)3~TSy~ft=5BIAYh@B9?X^FN$0w+z2JvT&pF?AsvKy zNf?RJDWqNWsHK(DpqlPzHPeVgP<=6e^`kY*6ruUuEgksWfUZ<l7GA_qRP?mMJ)UY|o%q(z(HXCIyX&Z0?F5Adam9dCdfHs>3 zh(u&)W9gvkUtUMDIAZTA@EMpjfMkb^%apy2_JUSc-vPRBZ*5x3Ha}L|hJ>&nJ&gBC z<-q0EPEx2DTQ2}%25DL?S}#Nn6wikFdx4d_a4B#Xu*YCG#Mv4Izt4G&RW)5v&ejH4 zQHd%xT2RvbeE9ls!ruMGzeh@E?0xjRQw9Zq#ND5+FK&Nfzb%X@j^cQ|UPqZ+(bCwj zDC4ZiBlF$-2lvGz4S%nY#8C?KU<}_)?3s_tXRrMC@nl3M%~vuZ8-kGi7>#bZt`C5d zW=`@Ylm$;!TDbg4-Z>YwGlW~e|PiFl34Ovk#VE6mv4v3B?%S++O`%lRqLo_ z+$qg;K;9aa`qG7eQpr~W7*;#{x^C84#E7Tq85N)l%vU;wgK-_4#`)kqZMP{|qR*mS zWdTIw#n1`ghQ|*8m(Pcdqscd<`17En*QGDz&m*8JSI^`e8c&}GTdGx&;(7Kw=nh*0 zPdASi(BPJ9Uf4_t9wQsHR{CjXxnkmS>P9RnhYPMtIF}~KRl@TrW;@T|&t8T2iH)mm z2Dgp>7j|;|e9qQ@f4XT~uq`O5vDh>{A%4W11$`aD+6Ft5DPM3a-vFH#aePMqP>RtT zI{GqIH)y@xvf8{Ije@3}|7aipTA{TP|#e)jkE#UHObB z-&#?i;`p_Me2(jAdlUGsnsjnS8JK_wBPFREP$l-(@HF_W;`NTDG z6Naao=3#e_^L*es$rmS#gigPlo}Nvo?4N(w3tz_j<$eH#KGwcgm})m1W0W*deDKtR zH*a=!Z+Av0w)aSpJt6m?ZYQ0eB9quySD|J{NS_sSmn=jI7b^RV)ef2Om((k&^x5{a z2)(I|<4&6tEvFILT_DdWbN`F&Vh%b)FEy0oxveG4qCwhG41KxMa)3e)U>SvYe+6lA ztgTwxj(+sxm~vIEjBhir9go;0w!%t(W3I)UVS;QhFPr41-0B#(gQWHTA#JacOx746Z>6zm?<*JF=SFeI_5cpqW zj=eDZ`t`%v`!An5tf>wXA?GR>PaQ8AUm3N;4lnWHcTWXqt|Ner?zkljMSx7je2=#r z2r-ICSAseBZU}}Fsi_BdSht;Y9a`n>PTH#C&a6$a1=qDYeQjF;$yx3lXHS+5hlSVCUAmj-Ly4(NNju$Fhcm-X@LVsppw(7xQAU~zPX4bSAhMkjq2hXY52 z+wN6&f&T-jM!q!dLb?Gn!HV)S$HEC3Z~PQ@|K3u)w%BUB=ECf%TWi%X-#cFH1o8L| zg_}aVD=rn-f|_JulPnl_-ZaNyqexzdQ--(bN-AdKIdH1v(wzmn< zao3s`JB{y<|GLdN9VZ_!JV8iZA`J$;4(L%`d!=Jb$e{CwEu;KD?k(y?r~TSX6=>9p z2enps@|LTFfJZH{56CpRKhfkP^NjSkC(SM&oMrb=RX&ZWvQ5--q1M(hGvIhO>tgcCRFUxvqV6iETb=!2a5b_)2Gus?0_kJzvx7VtvnxM`JUzL=6^_y<#-h0;q zYY`S@W)JTYkF8008DKz`p^`37nMk-1O0t<&OOiu62=kmU z5~Wi}+vrh4E2lv@-O_5R5r?3IEYjC$Evfq$Q@sxGBLW89p9nMIja2SCbavp4$6I(H8FL4Uw zI;%e3Qp12J3pXbWED<14%RGaIDr))>UkW$hPqHOz%fY3Pm zJrhU`vp_*;X0p;6RmnCi*D8UcsIsMk%|Oo00!L`GQ3jK?1}EU6i5!&~i+DL`v#Ehd z#2jrb98~qo>qr(y>}?4?1+xZ_?2vJpvX{|b(9-H#K= zxZK)F3N>Tv1t3f>P0K~=g~)*7Su=kxu#)F41#ScO80?xjTY=#B8PBk)rYp+X$^a`W zQTav-N}5-PFAqoT?LYimr1YGva|};%RzH1?U{}m5$+HSOuqcK6p>tZAupCvnW&Z z03z~Y=!9>>@jbxh(_!Oi@)armH0bDc=}Y<32q??d6FG;*)2G3fYL%yWo;?k^!`8sl z&4UHhxW$_1HdBI!$Of&IewlVzc(~Kz)w}!-`l!Lm6?X)j<-ZH!SnX*4Hve>Y90gVuWZVb zuZ=KJb^KC5Hp6way^efaPdd7yIhcUwGx~p_EFZ9V2kp8Zj#!QE*x`g8)p{T$`N(x~ zBZkMD=3#q}bA8}i$!Eumgib!6oSaT3?7#omGhfHM^}Yv%KGwcQoGLerF-#gLKzQuI z>(@KGw>u*g+&iSmj*vT0x1-iiF_+j`m!W1y$e$Iomn=jI=W6~5%Ni&-XVg@=yFV&RenJp&FqCwhH41KxMvWG(VU>SvYe+g-D ztf^Ylj&Ahhn37ekjPEnBACK4t_QFbkWv<7YVuEZiGaE@+x&b0bzGFwe>$6X%m!Gdg zwj296J_ov-H1gaBkJ@eL65mAPw7dJ*7R%bU1Lrq9>ykDc$8pK2s!J&cUU#^=-C;w2 zMpGvRYil9>3>u5;tT*0`$L$WS2RDvSsEZ&6V&Plx8|u)d^vrRsa@j@gt5-qT3;cta zVKdCWe0hKR?(@eMYs!;E$e9X;6UR%&S4J(d#Y=qn-CF^g>j+?@J8Z~85g=1B+v6<< zLX0BP6=2T18-l(>s_Vfm)@?^^hn9J}leQ|qHER>>!F8ohSKEd_a+Z6@*^^~Gx&PQ; zs62mcFt+>7!HLb#Ph4UZT+%auI}U5toxMLl^@zvpkgB~vqw4wV%P*H7KAfLjoZ|dT zn(w>{H5Xpkb-3_VW1(!Xn_ORBU7cNgv;u#obrijvmbNx@Z(e6;EZ#?;z&jYHVzR(1 z3P{?~$(rUROUNtt)L^aK0ewd|*5Z~2vo2m$Y;HLonzy@SERL?Q;hEgk=(y|Ru;-|7 z+r8>8@P7i;$mgb6NH<_cSW%v5SU5rBm7n77A6&}k7F%uCT$o*TE3LZad&i5dAdYWQ zxGuE2;!=S3sYA*Y3{gIC+oZ2}0@;X)x$?Ko4r$OC1|RdY#{I8Rh@Dv#1lD=5sGqpg|`d z)LP-mTdWcS9yG+>Bh%#mM3WE9Gt%RZG`qNWmfb;B`8cM^CQ*xpT3g3Vf#cb*FJO#y z4H^`37nMk-1O0t<&OOiu62=kmU z5~Wi}+vrh4E2lv@-O_5R5r?3IEYjC$Evfq$Q@sxGBLW89p9nMIja2SCbavp4$6I(H8FL4Uw zI;%e3Qp12J3pXbWED<14%RGaIDr))>UkW$hPqHOz%fY3Pm zJrhU`vp_*;X0p;6RmnCi*D8UcsIsMk%|Oo00!L`GQ3jK?1}EU6i5!&~i+DL`v#Ehd z#2jrb98~qo>qr(y>}?4?1+xZ_?2vJpvX{|b(9-H#K= zxZK)F3N>Tv1t3f>P0K~=g~)*7Su=kxu#)F41#ScO80?xjTY=#B8PBk)rYp+X$^a`W zQTav-N}5-PFAqoT?LYimr1YGva|};%RzH1?U{}m5$+HSOuqcK6p>tZAupCvnW&Z z03z~Y=!9>>@jbxh(_!Oi@)armH0bDc=}Y<32q??d6FG;*)2G3fYL%yWo;?k^!`8sl z&4UHhxW$_1HdBI!$Of&IewlVzc(~Kz)w}!-`l!Lm6?X)j<-ZH!SnX*4Hve>Y90gVuWZVb zuZ=KJb^KC5Hp6way^efaPdd7yIhcUwGx~p_EFZ9V2kp8Zj#!QE*x`g8)p{T$`N(x~ zBZkMD=3#q}bA8}i$!Eumgib!6oSaT3?7#omGhfHM^}Yv%KGwcQoGLerF-#gLKzQuI z>(@KGw>u*g+&iSmj*vT0x1-iiF_+j`m!W1y$e$Iomn=jI=W6~5%Ni&-XVg@=yFV&RenJp&FqCwhH41KxMvWG(VU>SvYe+g-D ztf^Ylj&Ahhn37ekjPEnBACK4t_QFbkWv<7YVuEZiGaE@+x&b0bzGFwe>$6X%m!Gdg zwj296J_ov-H1gaBkJ@eL65mAPw7dJ*7R%bU1Lrq9>ykDc$8pK2s!J&cUU#^=-C;w2 zMpGvRYil9>3>u5;tT*0`$L$WS2RDvSsEZ&6V&Plx8|u)d^vrRsa@j@gt5-qT3;cta zVKdCWe0hKR?(@eMYs!;E$e9X;6UR%&S4J(d#Y=qn-CF^g>j+?@J8Z~85g=1B+v6<< zLX0BP6=2T18-l(>s_Vfm)@?^^hn9J}leQ|qHER>>!F8ohSKEd_a+Z6@*^^~Gx&PQ; zs62mcFt+>7!HLb#Ph4UZT+%auI}U5toxMLl^@zvpkgB~vqw4wV%P*H7KAfLjoZ|dT zn(w>{H5Xpkb-3_VW1(!Xn_ORBU7cNgv;u#obrijvmbNx@Z(e6;EZ#?;z&jYHVzR(1 z3P{?~$(rUROUNtt)L^aK0ewd|*5Z~2vo2m$Y;HLonzy@SERL?Q;hEgk=(y|Ru;-|7 z+r8>8@P7i;$mgb6NH<_cSW%v5SU5rBm7n77A6&}k7F%uCT$o*TE3LZad&i5dAdYWQ zxGuE2;!=S3sYA*Y3{gIC+oZ2}0@;X)x$?Ko4r$OC1|RdY#{I8Rh@Dv#1lD=5sGqpg|`d z)LP-mTdWcS9yG+>Bh%#mM3WE9Gt%RZG`qNWmfb;B`8cM^CQ*xpT3g3Vf#cb*FJO#y z4H^`37nMk-1O0t<&OOiu62=kmU z5~Wi}+vrh4E2lv@-O_5R5r?3IEYjC$Evfq$Q@sxGBLW89p9nMIja2SCbavp4$6I(H8FL4Uw zI;%e3Qp12J3pXbWED<14%RGaIDr))>UkW$hPqHOz%fY3Pm zJrhU`vp_*;X0p;6RmnCi*D8UcsIsMk%|Oo00!L`GQ3jK?1}EU6i5!&~i+DL`v#Ehd z#2jrb98~qo>qr(y>}?4?1+xZ_?2vJpvX{|b(9-H#K= zxZK)F3N>Tv1t3f>P0K~=g~)*7Su=kxu#)F41#ScO80?xjTY=#B8PBk)rYp+X$^a`W zQTav-N}5-PFAqoT?LYimr1YGva|};%RzH1?U{}m5$+HSOuqcK6p>tZAupCvnW&Z z03z~Y=!9>>@jbxh(_!Oi@)armH0bDc=}Y<32q??d6FG;*)2G3fYL%yWo;?k^!`8sl z&4UHhxW$_1HdBI!$Of&IewlVzc(~Kz)w}!-`l!Lm6?X)j<-ZH!SnX*4Hve>Y90gVuWZVb zuZ=KJb^KC5Hp6way^efaPdd7yIhcUwGx~p_EFZ9V2kp8Zj#!QE*x`g8)p{T$`N(x~ zBZkMD=3#q}bA8}i$!Eumgib!6oSaT3?7#omGhfHM^}Yv%KGwcQoGLerF-#gLKzQuI z>(@KGw>u*g+&iSmj*vT0x1-iiF_+j`m!W1y$e$Iomn=jI=W6~5%Ni&-XVg@=yFV&RenJp&FqCwhH41KxMvWG(VU>SvYe+g-D ztf^Ylj&Ahhn37ekjPEnBACK4t_QFbkWv<7YVuEZiGaE@+x&b0bzGFwe>$6X%m!Gdg zwj296J_ov-H1gaBkJ@eL65mAPw7dJ*7R%bU1Lrq9>ykDc$8pK2s!J&cUU#^=-C;w2 zMpGvRYil9>3>u5;tT*0`$L$WS2RDvSsEZ&6V&Plx8|u)d^vrRsa@j@gt5-qT3;cta zVKdCWe0hKR?(@eMYs!;E$e9X;6UR%&S4J(d#Y=qn-CF^g>j+?@J8Z~85g=1B+v6<< zLX0BP6=2T18-l(>s_Vfm)@?^^hn9J}leQ|qHER>>!F8ohSKEd_a+Z6@*^^~Gx&PQ; zs62mcFt+>7!HLb#Ph4UZT+%auI}U5toxMLl^@zvpkgB~vqw4wV%P*H7KAfLjoZ|dT zn(w>{H5Xpkb-3_VW1(!Xn_ORBU7cNgv;u#obrijvmbNx@Z(e6;EZ#?;z&jYHVzR(1 z3P{?~$(rUROUNtt)L^aK0ewd|*5Z~2vo2m$Y;HLonzy@SERL?Q;hEgk=(y|Ru;-|7 z+r8>8@P7i;$mgb6NH<_cSW%v5SU5rBm7n77A6&}k7F%uCT$o*TE3LZad&i5dAdYWQ zxGuE2;!=S3sYA*Y3{gIC+oZ2}0@;X)x$?Ko4r$OC1|RdY#{I8Rh@Dv#1lD=5sGqpg|`d z)LP-mTdWcS9yG+>Bh%#mM3WE9Gt%RZG`qNWmfb;B`8cM^CQ*xpT3g3Vf#cb*FJO#y z4H^`37nMk-1O0t<&OOiu62=kmU z5~Wi}+vrh4E2lv@-O_5R5r?3IEYjC$Evfq$Q@sxGBLW89p9nMIja2SCbavp4$6I(H8FL4Uw zI;%e3Qp12J3pXbWED<14%RGaIDr))>UkW$hPqHOz%fY3Pm zJrhU`vp_*;X0p;6RmnCi*D8UcsIsMk%|Oo00!L`GQ3jK?1}EU6i5!&~i+DL`v#Ehd z#2jrb98~qo>qr(y>}?4?1+xZ_?2vJpvX{|b(9-H#K= zxZK)F3N>Tv1t3f>P0K~=g~)*7Su=kxu#)F41#ScO80?xjTY=#B8PBk)rYp+X$^a`W zQTav-N}5-PFAqoT?LYimr1YGva|};%RzH1?U{}m5$+HSOuqcK6p>tZAupCvnW&Z z03z~Y=!9>>@jbxh(_!Oi@)armH0bDc=}Y<32q??d6FG;*)2G3fYL%yWo;?k^!`8sl z&4UHhxW$_1HdBI!$Of&IewlVzc(~Kz)w}!-`l!Lm6?X)j<-ZH!SnX*4Hve>Y90gVuWZVb zuZ=KJb^KC5Hp6way^efaPdd7yIhcUwGx~p_EFZ9V2kp8Zj#!QE*x`g8)p{T$`N(x~ zBZkMD=3#q}bA8}i$!Eumgib!6oSaT3?7#omGhfHM^}Yv%KGwcQoGLerF-#gLKzQuI z>(@KGw>u*g+&iSmj*vT0x1-iiF_+j`m!W1y$e$Iomn=jI=W6~5%Ni&-XVg@=yFV&RenJp&FqCwhH41KxMvWG(VU>SvYe+g-D ztf^Ylj&Ahhn37ekjPEnBACK4t_QFbkWv<7YVuEZiGaE@+x&b0bzGFwe>$6X%m!Gdg zwj296J_ov-H1gaBkJ@eL65mAPw7dJ*7R%bU1Lrq9>ykDc$8pK2s!J&cUU#^=-C;w2 zMpGvRYil9>3>u5;tT*0`$L$WS2RDvSsEZ&6V&Plx8|u)d^vrRsa@j@gt5-qT3;cta zVKdCWe0hKR?(@eMYs!;E$e9X;6UR%&S4J(d#Y=qn-CF^g>j+?@J8Z~85g=1B+v6<< zLX0BP6=2T18-l(>s_Vfm)@?^^hn9J}leQ|qHER>>!F8ohSKEd_a+Z6@*^^~Gx&PQ; zs62mcFt+>7!HLb#Ph4UZT+%auI}U5toxMLl^@zvpkgB~vqw4wV%P*H7KAfLjoZ|dT zn(w>{H5Xpkb-3_VW1(!Xn_ORBU7cNgv;u#obrijvmbNx@Z(e6;EZ#?;z&jYHVzR(1 z3P{?~$(rUROUNtt)L^aK0ewd|*5Z~2vo2m$Y;HLonzy@SERL?Q;hEgk=(y|Ru;-|7 z+r8>8@P7i;$mgb6NH<_cSW%v5SU5rBm7n77A6&}k7F%uCT$o*TE3LZad&i5dAdYWQ zxGuE2;!=S3sYA*Y3{gIC+oZ2}0@;X)x$?Ko4r$OC1|RdY#{I8Rh@Dv#1lD=5sGqpg|`d z)LP-mTdWcS9yG+>Bh%#mM3WE9Gt%RZG`qNWmfb;B`8cM^CQ*xpT3g3Vf#cb*FJO#y z4H{w3snNUoXv6}fn9H8D&(Ac;%gQP_L7H&ZrmLp}l;BpQnW9*~Q_z2U zlA^7Q41gY1M*fH{vR4%^fS2H5O=Q0!x#Ah?#tJP~dURq&8imUU9PoLvVvcPcL3;zI z#8_wb<25x5c)DeHWtd083PD0iyNJB)F9Jg4 z?0YUSG|U1Sp_=JRYZN8lutKX8%CgRu8a4+xHw#>$!$uiQ+6J6}%MdxPG8XX)&}P#B z5sNwMSURZsmzR+=j@i2kds+z4RXKMv4 zt5g*mEhuSzIedLMX7B#w-(#ic?0x*369yT8#M7Uy&ToHZ|5_MR93{zmy^iN{MWl(R zD3f`S$L72FH=aud8veJ!AdWMbMML;*5+C_UK7HlihvNaMG+W7(bcjamZZNpzx^4i+ z&7R~-C<~s#m}Ouv;=ky8bZ5^e)J5nyc!YH-%C}}yKx1LQoD5t5h%#mHaQ~l&hljydtpsk>?{42|l1P3l=G-Xl`P*eONs5Jlj;)2v)H)87 zJE5Hx$XlZ_U%K#5D)~wP!)k}G>xRxE#yrbTsQ{g0zET^GhP87VY0o~>)G>QOSLL8>}St|?yxoR zZ1ZFW4Q{#Sh0T=cDZD{zrT>;%+sR-MHI?nI;bkiU_J3`{`WE22^5e4bMl31o3$9Bz zlP1bl%JXZi4}1>)?bMW?*|0ih@YwK=zzFqAlAO)h8t_jy0}l=qC59@ezR9wH7kk62gj`!HiqEhFJ=U^gU%;^7xvf+faKWevy z(U{$}bgP&iHF97?^RXKO#|*op=A-r*XZy%aqA!jaF}?nBa&mfo&Hnm}z3^?I+b()g z=&||*PO8l?LH}qV&tTVtH*a=MZ+BM6s}FFI9VQQ;ZpW>kVlHtouUyTJFmP7TCRvCK z&ei-gR!htSPf}Ae2Onl2Ep)mGNT=j`K0Qz)@Q1Z_JH|Q%sNzrer4@$qW#? z;5&A~cXRgn^zzHii0%45NzQ@3ZGr^%&ZBAFnZ(baxa~fj?1W}@+kx{3UUY+YG)a;w zrs^T5T6oim-R({^^k*~;Vz9Ov($BDbHCb=G8IRioUT<|hp&^6}h!twAQVv)bS0QGZ-%HZk(zpNk9ON}+n~E;->0oA?#u-guHS$6;`_)(D(GgTi$cu*2Sxf%{}cQ{5U+u;^+z+ z_T;Wa$6XVTdRm1$?nQTw{~f4CzBFMW-GCWmMR}QH;RL-DlKk%PYpUBCTW!}|m|bye zsk-HR$BHc{PVN!7DYU!nGJylBNf$QBqG9V!bU13P_Gp8=Y-ZIl4XYR*nOORJCX9~G z=p1~xcckstw{g*=t<8&_(YMcj?NOcD$p>^#G@>by7NgDt^rRWSk}+V?Yy5uADE}uN zqKxy}$tUI+$?-s%T|7F=9tc(WW05MuKrI() zZ4EO8j=f=@!x-x-GU-Nj9b^wiStkuFHo37Ln{F0Deq(%UhePG=zeRm_TMboXlsVq(JJqourffi!qakS-vHQ~3Gzva yupPx@t;qL{7_DTKy;^Jr%afd!B|7RKLJAX&2--vXQ@DUr>i+?OK5&i=8vp=#_0PZn diff --git a/client/ext/offline/.lib-offline.js.1297622343000.gz b/client/ext/offline/.lib-offline.js.1297622343000.gz deleted file mode 100644 index e2340809d100bace0d5b100fbad0a8fee3d58430..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1970 zcmV;j2Tk}NiwFouC0I=Y18iwxEpKLKY-w&~E^2cCwOCtk+c*|}_pe|*3slayQaXcu zi8tFznsx)ENr0p?4~s!TOUE{+GO4BLxJJhRea|5&iIU}9ric(AGI{u&`$bF+4hHOi zeF6!Z=j>{w3snNUoXv6}fn9H8D&(Ac;%gQP_L7H&ZrmLp}l;BpQnW9*~Q_z2U zlA^7Q41gY1M*fH{vR4%^fS2H5O=Q0!x#Ah?#tJP~dURq&8imUU9PoLvVvcPcL3;zI z#8_wb<25x5c)DeHWtd083PD0iyNJB)F9Jg4 z?0YUSG|U1Sp_=JRYZN8lutKX8%CgRu8a4+xHw#>$!$uiQ+6J6}%MdxPG8XX)&}P#B z5sNwMSURZsmzR+=j@i2kds+z4RXKMv4 zt5g*mEhuSzIedLMX7B#w-(#ic?0x*369yT8#M7Uy&ToHZ|5_MR93{zmy^iN{MWl(R zD3f`S$L72FH=aud8veJ!AdWMbMML;*5+C_UK7HlihvNaMG+W7(bcjamZZNpzx^4i+ z&7R~-C<~s#m}Ouv;=ky8bZ5^e)J5nyc!YH-%C}}yKx1LQoD5t5h%#mHaQ~l&hljydtpsk>?{42|l1P3l=G-Xl`P*eONs5Jlj;)2v)H)87 zJE5Hx$XlZ_U%K#5D)~wP!)k}G>xRxE#yrbTsQ{g0zET^GhP87VY0o~>)G>QOSLL8>}St|?yxoR zZ1ZFW4Q{#Sh0T=cDZD{zrT>;%+sR-MHI?nI;bkiU_J3`{`WE22^5e4bMl31o3$9Bz zlP1bl%JXZi4}1>)?bMW?*|0ih@YwK=zzFqAlAO)h8t_jy0}l=qC5gM6d zsQS}#(s6(&ud%1UyXC@;aiJ^F!ugb^-vXCV1^!w>K0`V>zE3`CkMSH##ETjIzfd;j zut7xa-Z2`pyOseerbmq&81{VZCdDzs7OMHEea1N`a^vcYV@6D`znq+$USG4n{$ekD zujuxa9u#`4et{=zGfdEl8pwUvTH(!`ozvT$6>|LpTx5sI1E||^>!+AYoZc%}vm=bI z6|_kfB7<`^|BNLSlf{$N6jl0fCw-aeVrNf&QcgHWo>F=E56+}H=n-0}2|1qIg2gNv zWG%tamMblLLg*1uMlRl8rh*s?MGN)ldOt}hrPa##Sq5kRm|ft!t@JnM=Ex}~$OiMa zld@z6h+XgUXLZ|w^9NpZ zgLX7Yk}9U^NvK+Q(}~^fPBipqG)-%;wi?pUusk(cZ@d|g+f!g~2|l4og$#&galvot zD?mygIc^-UCbfO_D(bK8KZzL*-0bVu52x?Hd}>8a)vbh_sc3lZ;*#-|QA->nQ}2Fv zAA#z+0I=R22E333$V|-kSj&M>L{ZR{V9vZ5qP|3G>cKtQZO3hc?v{O@wyL-{Yg3%_ zbuCU;+JGS$%Uu@j$+Di@|50bCqWDo~?CpcsHbXyiiB)h(R}HQmHl{oKaDM6$kJ%wr zdx1(d`!|72K`FLfJv|M z`!%EdpLB>i-U+XisR9i;{-DtcJ8!v433w2&eMF|o{fQ=@m}ex%18H{g=q!66ROOFF zstg0QT&T4*%oI5GhJ6lWtgFbR8`X7?Js4%3G_cs@#(HeJSqS-!@u?jSmAfPt^#yM= zRE<&Ql&?xkzWU<0beGC&fwc&WGPBLQ#FqA;W;PgWD`nx(*_TJFyl=wF0L=C0I=Y18iwxEpKLKY-w&~E^2cCwOCtk+c*|}_pe|*3slayQaXcu zi8tFznsx)ENdTua4~s!TOUE*&GO4BLxJJhRea|7OtK~SEB0_-3%Hr>cr$dp}*ndW+fq(uD|?m!xrA!WSaGKGRM_EBj3KZCi_ zz{~=e@)(8T2AC+AhNZfLBqRvNVlHFI_$-4HcB3s~VbAeA&qU0PP?F8HS`r_UL710> z5i6ZQ(nb#(QaK6A?UqzCjW`6=W#g%zELo}u&7W?_z~=^ZDT0&1FYe%=U1nOX(uHYB z(N;z_fDS7oe?%AAtBM!EOK?~d*{?{Zc*44|LW`vyotTkE;W7d@_&ixL$F_=~y@69; zth4Izni>W?UbsDBV2J>+TILxvL{YOpAvP!O$Nkl6IRm|Am`B12K|)Eph`jAD0z&2N zdnPb6%mNvqn(<0&6eZiRT&oz0qRN&EHUpWO1+LIxqYNf(4NkyC6FDq17V&b>W>W(Z zixhP%98~qo%SalB>|F^y1G5H@?3i(xu-D;U&{FFMK=+-kRBPzw*DBpGAS_4+T=RCuznye^iD+Mg8 zSmhfnC~1BEGB)=0WH%fc{c9~2PV+4G<~Yz;iw zJefg_Tda9*GbMNmZ_rxlzopi8G8hDPWxKC=*-C)@Uz=uqYv8)_R#?xxC@?4ZFDsOv=0IsHS9MsKKrrm7nAdQfB+q-_;zwhy-GR(@<$z9W39=HbYw z`qOgMaeyeM*wf$Labd@}rYq3G*^I~E8ZMy<{I!5=j&yW3Tz`Oz>@Ycix*fNEid5qCUb>o{V05jZ zO|lRPT&nalmQ+j@Pf}5o>A#)yWu}XrJ^5MXg!AS()rkM#V48s*p{eS)M+ntc;Piq?4U~M&|pUoOor@rxKJZ+DHy>tlSAeXf zU1=K($yn~jXit{)CEYi;o>-gi;=|>+M?7W6RPqHX zRqx+kf4%INlHdK!PI<3mtL>T# zvny^TRkwWaSh3~A$pZq{g|?@I3LITcys$|Y3|qIS!%=OuM;qj2J=KnBti||9#M0kL zVRUqc?8Y~KN7{aUYZp!0+Pv5qlKbq}9_p!`d_eaEBN`QHG3bmzPnz~i85>M`jo+^s z<^QBZ)bUR9UYRP;pyLmkudwqLtC)ZX4YrTSG`T;~Y3HTf@wNV{h2!FvhxyOuA8B1=+zU>!g9jCO1}N)9pgYZ;a3EaH!l(xu|Y=%b{wF zGRJ&XQ1Vr`$A!CDUJI;6Sd^J<-UYU_gPPf3tgMtYht9q{T4j9`Rsv{Ncv=qa8vuJV zK|U!Fwxf`&75KgpqLqxYmyq>fd6w~_Ku7&UNP!=?1nnXHDO|uQ_5a>5=PfrI002iU B)n5Pr diff --git a/client/ext/offline/.lib-offline.js.1297623084000.gz b/client/ext/offline/.lib-offline.js.1297623084000.gz deleted file mode 100644 index 7405f81d62de88763ca894f535cb0c8cd48494df..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1963 zcmV;c2UPeUiwFoTC|FGb18iwxEpKLKY-w&~E^2cCwOCtk+cp$__pe~R0hKdXN;m8! zUe-&Rb^+2PfYa?^7z$cCwzRAokkr+39IuEFAToLQo%=EZ_lzVaO6N81bKUI=HvzBkCgb96iE11?6kA&Y-feUygb%07M>ECO>5_UXH4bl09ye z@3nob8=&tndT{^e+up;jLIu)@NMja3M{Q zs+gx^tPgw!f3|AM53FAtGkC23SIr3JOB7v9*$VJaR|5|Y6giC~>uQ?xkC?KcEzh9G zYLDHWDPOXK8c(3E6Gi9r4>=mWrUp7w<(SujBHJKMt5~zWvqiV`W3BQX;ZrsbM@H43 zmZO#fL_Wrz{^pJgJI2*rffi0DJpNL1302_FIiyphqvN~eqxKlizyv&-(*G-EV-6cc z(Ci(9A-iuFpkR7Z$$??dhi+0FGHjum51MD3g910MK09T^bo}Y;?0h_C|NhIK`CifP zDIF;ESp90AEX^=NCu$(~VQYm~ueMHawpPgXd$`CJlRc=LVdJNmNu1sbSF;n0t|c@{ z<|2VhHT#Gq6_dr2loUn!Z$@32>0)O`ewI1mym?MF;x`;jQ_ur6RTXzUwN;B*NJtve zp+T2gcEr&mLXG^qyH@QGt*%Tv1fq!YS}lzqXmIck*%c1mN`GN)n4DmitTB08c}uo{ z&_&>>i@@89kLTB)ZU<~T0HWv;=qspJ3cLqtu6HK!(<*Mek1RXOS>3ka{DEif$Q_KL zs0gfb94Z!GwE}pv6%yTPO(PqutcLWnUZbkiH{Oh=%~7zk4xiDuLI%XzIOlitDIlef z95<5}x)i4^ zZH*xr%iS0qmSx}U5tU`%>XDrtactA{BbQhY7j)_18e?U@i+7jj9`TeNQ{5M+RJDJ5 z{rUR+`^$@~bKHMP`>i{uI^mV2L&6_xgtAFDzP-M=xw!gZ8U93cUwoV@${at4K+=g;>eN5!guHPV8&wxLb~dP z-MKB%Y1_nuj(*{ed(oZa{{SkK&rQ9WuE7kktUOP#dV*dENq+a&JH_RWEx9W$%(l3t zRP7qRWyOXQM-K>GRojj>DsX-^@!V!w&~IF!_6L>K9&M24)o44U*%sp?5et7Uh0)QP zv}+&wEor;;tz0x}YV&MsQtq-}bF!zN@($e-3}{}Y#h^6{9W?S6GS--M8oygJ%KuS| zsO6pdxa>$piC@U{s#kXr;a=u000sd!M*?h diff --git a/client/ext/offline/.lib-offline.js.1297624401000.gz b/client/ext/offline/.lib-offline.js.1297624401000.gz deleted file mode 100644 index 476249b514547438553a65347e4b24592cc8dafc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1774 zcmVSN5~N`nu8e0~rcf}(eiIu1PhhGv zFf#+DJVs%-0VWEjVXl^tgapA@%w-H2pJZ^zK5C0t*nK?DGZAwmlw?z_<|K!75atCCjW zXd5FNK=&IXA7hK`HN`97HMrjr*>6auc*43|p~2FQ4rioMIE=szIXfHX#8wR2N}K|@ z&Z@^-Y8dc%=H`TfIReCLo@dZdMNK|KY!2OzoA-9HGrd84TJQoPdiaa#%(z;^m;tx&|T< zDcV>#sOp#3kt`0`>k@ndW(6SG0pl`ZFT$;$rPX(UZrfX#*09ZwRkk4^EJz3AM5zq8 z+}KGvYQ|PGK$u>dmWx(1kpZ1&&HSyvN?y1SxC_{0uq)zh1%mHWo?%u^78J9U0ajG3 z^0gKeG(R4^I2g0npZV8N>6D#>e>i1O07%^Z_T$C!C-%$CnEWt`R;yK*$^|Wr{E8w< z^DH!9&9AsG8ff@ufg}zSm}D`na$OaG<9bf= zITSgMVayUR$oOlWjc)Azh^h!Z`w#F=x$>1+=TLjt4@Uz>08xas%a7Uf7f1C(B_1cL z_r^Tt4bV$u56=I5aB$F=s*J#?>fOydOCrgaBIQPDFW(N6OJYm}v~4A1qE=zUxKo;G zfxI#*@u>^{q>|4CFwA!Nx~kV%#E>V+86}_#j8`hd!LT}<+WFu;ZMP{|pwGfgr2&NG z#gP-f867cb*;!lH)UY9gl0o~(Bk zP~#RWp4&(Xc99KQEB&9$+8hlAL7mxdYFRcDVExyonco_?F8#PDyb^PY`kdUmDxj&V+PoeV*#&8P#k%c{O}f=NHZtEnK2`B>%P9NP zaMU>fQCwn8f4$_wwsFm)Ko4gV9)D>Z31#3<1!PlPN87jQN98e0!2~>?(*O6$at;eb z(5@Y$F}rCwpcHyo>w&cAW7jE;8J1AZN9{fCfCAU9?j17{dU<_%dUkoqzI|hRzE*T= zN)HM>*1pC`R&F@LL)1XWhou!>zPz=2b8Cc-{vIi^Bjg^`&A9bbq!PRL@~GJ%vTFhD zB{Px0g-YLHO2uIDOR5!R_-~H-BGYkaOMF%_;ZF06a>Vc0m}a0y=&G7`JhNGgSx87) z+Mz=iTK4eh0aT-N-kz(rsMa*5OMxh&xK<0}8yalowy}m zK=<};{_gC<_0@>oCO{Nj0DT2DQh`4~>g%0Le7B0z?jy_ga@Mw6aDKyHm${>( zC@P6nwL|5=%MO7zwnfuedFDD+-?PX^YAIPE95}TjWfQaPXQ@C zb6ihewrksxR?wf|zY|mJy4j~sZ_ZAx-?ms&mMkHsDi~fmUNXKgYL2~R?2q4FN}#!p z0QPi;4Ou7xWFn?pyyZZMQAD}|%(-_%&=*a0QnV&=%i#x8Vx}Qo_~v&}55hhj_ipt`oebF1*HK1!gNG7~s9T`Hpi>EVYWT{$(-6{#p+P51+wN@M8WObz z9S2hN#JpN8V!9d{G!P#^9l4!3a!2Bj9{&e#VOURANHnUG%?pV(Th+{LA+F5Su+H%3Kbz9@*^)n#7cF8Wp?%c!z2n@bB!E_Z!+ z$VJvyy%IpfVK;JaE3UmflsnOIGYZM{z^9=QO*YD2@YF^7G~-2qUjJT5fgcY9?IHax Q9Ka6XUlq{#-;)>s0JVx=F#rGn diff --git a/client/ext/offline/.lib-sync.js.1297621334000.gz b/client/ext/offline/.lib-sync.js.1297621334000.gz deleted file mode 100644 index 910f410c21832e2e15ed32fef364e6a9fbcbcc9b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 775 zcmV+i1Ni(OiwFo-Ay`cU18iwxEpvHpV=iiQ0JT|nbaqso8h*tt2E6-|JjlAO=qxqRo?k?>>BBt!|M{tSNVrAX79 zaaiW;ZlRudJPSR7E8X*0Gr|3U96axVO0q>GIqwXd-h(P=48g(f&^xB0_9G(O=e4zW zAg1A7As`nq&9_=en!(Lr`w5vvCLD{MeY2a*dP``LMtiP6GMckr;J<(+XlYjDkOR;3 zZbi;cPs=*O()0W-aVM<|^hXPK3s3{hlA!mJJK04Qd&OIJ`7h`5YSWy&&w)xsXr7Z= z1CmfpquRdgyvGLljAxb-|CL1nr*}CdU5-v*Db1BDgAHZ*nx)tVxOBF0nU&uplSXT4x4Pl(b~1Bq$8gITb4sevaf?K|v?-`2mWzFjX66FE$zT_be# z!Km)mMw7&?B1=MI_%^u3sc1)Y!xH1A!K^1BwazCeozeR*xeTPc&>|58q!g0uPzh8d zJJM^H0u(WA#Pj=ZUk+x7lmXGDC@WnQEjQ+3R=$~C)?3x$1`($ zj2m*$ispD}s;o#|3_P5$iTpzuI`t019-GtH;TtfI}BP`avJ{93)UiJL>!x5m6B9o?JSERse2sT{% zzB5jXQU3+Y&OFCUGngP1ikU${(lJ-{GO??ox3PA(ehET5ioua-spWOl9VlxE{N`d9 z{*fpxiX#_n??CIht|nbaqw4?8+4<%CXwd}trA_X;&v*CT*-`Lg%M`>ZWWf}EnuW|V z&IBwtyPxZ4kxZir!If=AVwe;`NOqISBUNOLMsm>_1bqNq&;-Jr^DrBxrC`LK^1;Gng!iMIY>^O+r zY|hEq>FK_T5Pv5@o4J=)4(6l7`xU4Fb}2AhCA{?FlD;I~w*^?tW~I}NyyrlbCX{nh zi9k|nXk6JB?e2+19@v>>BzWaeAm}}Zv`x|rETdfeI=E04uUUqDK<@6YKD_&S-56|z z6DhRh5Hpd6_k4+ zj=e^-r%PR9O{!Ah*@P_=?DmmY?J(-FS*K;FO9>OL4r94G z7#h7p!W|mJfKuRq1^xr-UIG0+7U5`K)$|3!xLi!-J!k($13;l~Z65~q>nX;n8)=>kz(6Kd$ku>Q`*8Uik`IM^n+X5_ D&wX$q diff --git a/client/ext/offline/.lib-sync.js.1297622003000.gz b/client/ext/offline/.lib-sync.js.1297622003000.gz deleted file mode 100644 index 166bf09ce0c1a5d25e94204b2e1595eab3313dae..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 776 zcmV+j1NZzNiwFqwBUnuW18iwxEpvHpV=iiQ0JT=%Z__Xke(zsl$^%K4rrYbPi3x_L zN(d$uo+dPT$tAV$>|nbaqw4?8+4<%CXwd}trA_X;&v*CT*-`Lg%M`>ZWWf}EnuW|V z&IBwtyPxZ4kxZir!If=AVwe;`NOqISBUNOLMsm>_1bqNq&;-Jr^DrBxrC`LK^1;Gng!iMIY>^O+r zY|hEq>FK_T5Pv5@o4J=)4(6l7`xU4Fb}2AhCA{?FlD;I~w*^?tW~I}NyyrlbCX{nh zi9k|nXk6JB?e2+19@v>>BzWaeAm}}Zv`x|rETdfeI=E04uUUqDK<@6YKD_&S-56|z z6DhRh5Hpd6_k4+ zj=e^-r%PR9O{!Ah*@P_=?DmmY?J(-FS*K;FO9>OL4r94G z7#h7p!W|mJfKuRq1^xr-UIG0+7U5`K)$|3!xLi!-J!k($13;l~Z6C(i=sd8WP%)O?Nb_6(Ml-46wg!CKhs)po<}8Xmn%% diff --git a/client/ext/offline/.lib-sync.js.1297622910000.gz b/client/ext/offline/.lib-sync.js.1297622910000.gz deleted file mode 100644 index 0f79c6112d4ae9c158ef9ea12708a633ce8aeaa7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 788 zcmV+v1MB=BiwFpQCs<7a18iwxEpvHpV=iiQ0IgO{Pa821z2{fB>7lzJ*}#sFXG(^35rk@fe5 zi#H%<;6WpR%b4<&k&0$;+gW}@#*t0OVaGq+V*7j~G*6?A(4ZLQ>@NhbURPA@qE7U{|n`z^O zhCb}|-Nk5<_+4bllvutcF7Z0r(A=`b?oweE1CUbZqoXERwT{ay7_WX@U-+GQ)A~Ac zo*S7+0!m3m)~F7ulpXNjr36i^wZtYTG&P{e6MT3?IpqToDXhp9_B>bY5vA^X6CI$8 z-&MCWs6mUm>O?kV(T+LoiBt7mld=_fUZJgm?QZHR5Kdccc2Pa5J)PQEd^aU*VCOpx zJT2ERS`mRc?3u)>RJRxh=SImGG+OR$5W?K{4ED4kL+>PorKwPS|cz_QL4aSGMHfC5Fz6i|?jY$WlmG{8@IO%Z3h1w)3?uOk3Er2><||9eZK` diff --git a/client/ext/offline/.lib-sync.js.1297623803000.gz b/client/ext/offline/.lib-sync.js.1297623803000.gz deleted file mode 100644 index 5adabeb8b978ab56644d21c949d20f5b70f875a4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 772 zcmV+f1N;0RiwFq&Dp*Yd18iwxEpvHpV=iiQ0IgQZZrd;rz56Rz^-#%?<>VTpXwal6 z0u;R%JrqG}Rt_s>m9RuGy=FxDPJ3@XaRQv_cIbCwj75IemH0Qd?2*Ulbz6@80G8_gfEB^wP7yFo`WdN z_L5wjpC9T9@pBe-%spNOm@f|Rcc2E?M}gTX;n7Q~@KxYL*MQ}GUOCOlH3zCRp`4Ri z1d>rhliI%QvZoe#Bs0rN_{O0?(0dM9N75@Sr(F9wxKfsHS&nl+Hk+GIAHRKW4YtFn z6k2jfn8;-qxKf!Ydc{(a5pAdelG1bwLA3HigUDFkPuN$#6`*OqdtRU>#;}!klhB2a zMtyfSnq__$Sr!u;?-pIWj&@=0SZ1>{nAHTN*7@wLGd8W`vIv9q_pjG}XI_=xK+Y>8 zGf6-xsmK=9L6wRV^LHab6FXXBR}z{VP~-{TJ))fQ0f-b<Ve=%W$&9Ip_ z8hBp&uX+}NH5^&Sb|IbjG~em19I4Pvy3p&=`h9B!O-X(6z<>{ z7nA`9Z16u&_X_CGsR}1~)!P=1>vAw>yi`{6)*o zJjZ)8ng}X2vs-~d$FZs(6JAw#S8Ipsw;*<}a CJ!*0Q diff --git a/client/ext/offline/.lib-sync.js.1297623883000.gz b/client/ext/offline/.lib-sync.js.1297623883000.gz deleted file mode 100644 index a737a4fcea6ec44acc179e3d11331795f9bcdff1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 778 zcmV+l1NHnLiwFoyD_Bhe18iwxEpvHpV=iiQ0IgO{Z`&{oz2{eG-eGo{IPJB;uwv~n z6c~01b{L8wP;A-}rb?b9x9fuZ_fhhf{E-gDs!L+?@rmRkB`f$lJo-0DA&FYE|k?tG+Ua(Z8L>p>=BsAGW5UqV`5Gl+08}`+21!&6e)(g}`4jXCL2_1bj zs=Kq%H1(^<(gm@6x9H+kw4=FWsokZ)tS2C~&Sz(xu_+ywc^GWIf4%lA^Q!p`4f)YZTv3p*=392cGl(W1v_o!V&~N!7>Vy9Hr`$*?mI4?La#3>3pZBvRNtY$~*_P9%Xdv<*;1#?f=tNEqAoW9@xtOX;&;DJE0ELpd0<95H;Ee>g!i1qW zM9WeCMXSy{#|t!?AeEZg@j%isSM@OARnfaxJ6yj63paoRnWyeNPu+pC{$XTCT$SDw zTK2HHK!%?#+Q{OWi}>^@H`vmfeB9$0#n}5iw4Ycps$rygDga}c)u~tmJ{_a=U%4gM I3cv{f0MQS0Hvj+t diff --git a/client/ext/offline/.lib-sync.js.1297623956000.gz b/client/ext/offline/.lib-sync.js.1297623956000.gz deleted file mode 100644 index 3dea10cb1776f90ee7acf35c3f02d30efb9f4ebf..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 788 zcmV+v1MB=BiwFpmD_Bhe18iwxEpvHpV=iiQ0IgQZZrd;rz56Rz^-#%?<>ne8Xwal6 z0u;R%JrqF^Fw#gS%8)8aCru6idxz9UElv?Mx_Fs4Zx6)4{3*rUAr`U=%m%s>lY7Q)If&A1 z&&cWf_lG7!{9J^6C4(KCxY5}6eweB)3c=pBc=FVah_pj`VpxK!3}S%GsvZf~zXef;*hGuRGi zQfSE`VWN;>;8JCx93)FcPPCy0NJg_Q1X1c!gUDGi3^-K36QFIsdtRU>a@b7!N$BaL zN#C7~=DFWRmdC{MU89TF(Vpgp1^TzDC2k4 zMFuq*(Hd)u6Xo25&J<)1owoIwG~K`>3nwewA7W30C>}6cr*2y(QVp^AUrg9&Gi>Br z4ZLXl(}6``4o8-;TS(_UU+;7)C&^!X>8@7kFya3gppH9Aaa=YOwXPQ!2f#=o#CbaspoG{VrFjuQ>3c}9MNl@1r zM0V{B6FZnDP`XuIakkY7n*$xXR5F{)wiMj8czU~lBEKz z5m4b(1=qrap|@SD$+Sl6-n_({HJTxnn%TWU(kWN{W5%ndce(bsVGClnnWLeYy9~Wq z7|nryO4;34zMPglY;TtFJB>E7dgg{ce*BF>dKWduI7A6<3J>kOSAuF7X`U*;92QL} S)_}hbQTi95RG4Vl2><{R$$0hv diff --git a/client/ext/offline/.lib-sync.js.1297624215000.gz b/client/ext/offline/.lib-sync.js.1297624215000.gz deleted file mode 100644 index 32e33fa970fb756c44e6b19bcde2fcbbfa16e362..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 792 zcmV+z1Lyo7iwFppELcqf18iwxEpvHpV=iiQ0IgO_PuoBczUNm=-9zn=IB+FJMTM$D zs(Oifs8EF#d+e;b?7F)fD2?*pnYA5z{RjvZU-Fo5zIl8zyVm$;!!^b!W?_JT?LuXl z;1U;t-_FgWOoG`2=*c!Rv0TY8g6(8-&or#a2xqN9vO6>dOEB7&9(K)5-26$%=KHd= zw;*ou&JaMTgo%|^n&o)ib3cM0cIlMZ;8*GFo=?DvEZ#_inlr)wVfYG`sG&K9tw5RE z^(mYjAMff6>2ngcm3wvNXup+szXLVEJsRysORv1RqOXW|Z37n5Y2`G93xP}{ejf7U?CHF=2ci%2XEbc}8;pH#e7GKL7aI7+i-F zC5;jobD60yC{yJ!?wI+78uzUeDi4Fz&+iw0XWq2Fo}A}a zr3z4MrD09#kV^S8^LMGxfEz8jDS%}bH9X*ldy-S#At>oY-eb=T&F@L-;cU_bXzO>i zjEuTBq7~K^C-boh9Vy5iI{VfesJlT&7EM;T-OW4^qFIO0IW^llkgAKN|7O5@o8cng z7!+CUzwB5f<#1pbn}saB$Lk$#1%HDwyfD#_`B1axCtd7rSZhX$6FP}`3S72QjMeJ;La?(l z2&y`R$Zfvma)ZO9N>ZZoPLuL<86r~@NQ%6Gy4A>jkJU5Ug?c5!kt$qH)rIK3&IW)& zt6ZZo1SsgX!YgUR&|9$iU|^#~Yo5~O8jX-D!`rn+E^5a)*HSMRn}q{)*|p+!(8hXRhkQ=U^|ScVknGT@=&4h|s-+#iWT<_OU?9 WVN#dk4D@RkEuR4X9VNEn2><}Ue2fnO diff --git a/client/ext/offline/.lib-sync.js.1297624271000.gz b/client/ext/offline/.lib-sync.js.1297624271000.gz deleted file mode 100644 index ce3cabdd44b856293b7c3c52b691a105d9051fca..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 801 zcmV++1K#`}iwFqMELcqf18iwxEpvHpV=iiQ0IgO{Z`&{oz2{eG-eGc@IPJArv108o z6c~01b{LAGP;J@~rb?cqwCjTW_foQB%O7b`biU++k542YNm;|64cCxlkkbhM+J(w< z!6hsNznz;$nMSh-!6)Cy)N&&;5u*i3`VLqhH?HJ)a0G^JF6pXwC%x2lN#zaYJ)TwgO~f z*Qey<_;^=Gh@TVMW*+2Kfc@t2VFhY{doW|E(9=b2or+T zB9M$(melq|dwS}S2TtZWq3=8jB)b)mwMhnnT@m--yP|=p=nrH5mJ!UxqsdYX&YK{BS302T&_4E5hSegHtUr)|+ zt1?AEYo*B=)j^etXZr6_fg!H7;P@U zs``^b_gb{Vy5wXr)}bQ_xuvr&y&-iqaAjd<(d}*)h!D>@jLxZ<)`3)AEdDnmyf+!{ zBmGql4Ex-^wX-cety9BI|f_C(vx^-c}s#p03*zYWSF0HvAbavavUk@#OE2| zvK48p3a(-ZJ3Av$Re>mWJHc|ffniXd6sdweIKxT2KIX_pV2DR!%9c8 za4}U^rTaP?07|V24aOj##Cr^`q@^?%z4@S(<3($p;sf-DSY;nez!)ZVD$anvcJcBFN0rU%{0RU6M%sk0 diff --git a/client/ext/offline/.lib-sync.js.1297624318000.gz b/client/ext/offline/.lib-sync.js.1297624318000.gz deleted file mode 100644 index 52326810366dbadf899143cbc7dbe126cdc07c40..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 796 zcmV+%1LOQ3iwFq*ELcqf18iwxEpvHpV=iiQ0IgO_PuoBczUNm=-9zn=IB+FJMTM$D zs(Oifs8EF#d+e;b?7F)fD2?*pnYA5z{Rk8ZU-Fo5zIl8zyVm$;!!^b!W?_JT?LuXl z;1U;t-_FgWOoG`2=*c!Rv0TY8g6(8-&or#a2xqN9vO6>dOEB7&9(K)5-26$%=KHd= zw;*ou&JaMTgo%|^n&o)ib3cM0cIlMZ;8*GFo=?DvEZ#_inlr)wVfYG`sG&K9tw5RE z^(mYjAMff6>2ngcm3wvNXup+szXLVEJsRysORv1RqOXW|Z37n5Y2`G93xP}{ejf7U?CHF=2ci%2XEbc}8;pH#e7GKL7aI7+i-F zC5;jobD60yC{yJ!?wI+78uzUeDi4Fz&+iw0XWq2Fo}A}a zr3z4MrD09#kV^S8^LMGxfEz8jDS%}bH9X*ldy-S#At>oY-eb=T&F@L-;cU_bXzO>i zjEuTBq7~K^C-boh9Vy5iI{VfesJlT&7EM;T-OW4^qFIO0IW^llkgAKN|7O5@o8cng z7!+CUzwB5f<#1pbn}saB$Lk$#jKr=sm?pp~ zkm{kgv2{KDq~IeQyNjV;SC!B68~zq`cyXd3^`UmpPrBIMu-J@NCv+n76u4}~7%SE_ zhG1uB5LAT*k=uRCfb zoecnmR=GxF2vE>{g;&ysp|@i5!N^97);y)_H5wsRhP&k<(igafzV!&Yj9b@kBWC52 z_C{muUi2oSHx2%Q<&FXEi|W|J{Uy_Xy)jl5&s^4rPr_bE@6M(eyC|lG5utkxi%Ao! a>|=qH!=x_78R*w8T0Q~%7iWg#2><|`Zi|Be diff --git a/client/ext/offline/.offline.js.1297609329000.gz b/client/ext/offline/.offline.js.1297609329000.gz deleted file mode 100644 index 6d525fd3a86d89382a7c143168587efb7a5b88a8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5831 zcmV;&7C7l2iwFpD@>fj)18-(#Y-w&~E^2cC%{*&!+eVV#`70)jYem`=C?$1Qb)uDo zk|>8;ilh_ixKcisYX}TUSRlZ~LloEMf4}aY8DQ`v?f9yWwHr&oOixe0UsV6)%NBdd zE(1rfk>kce$ma0>cq!PW7bMPqvGLUiR^X!F>|nhO-NiCyKb@SO^w{tdf3|{f!7i*n zEn4BZw(ujt-rV0j{mfo3<9L16?{796YvCujw69h~{l(g|;^+7m*w=4`;$Mjy3d<4m zPTLMb(GTWx&-F#S$KJ~at|QtXdMyT@dE(o#8~7b~)@7ett$yF$@as_SlO9imAO*j@n~pC>Z0>r3IRG|vXGts^P?vZqHDVKlkE9H6x{GIU4*|G7 zJX?_>u}~~{h(f|L>j07?!f5HPEoC{TO1fuTc2@-XCnyjA%BF?zWC9kvWF#y8{XUwDP<@$_~ynXv1- z2R3B)!v~Ovw>QHFcK`O_{%$g|Qego?BaCKR&xvbrMMuQk^&)-$zktyZ;OIGQ$)5x; z+!pQ=V8}^o_A--B*8KDW5VlN4GjVd~V6@{N+k`I2Q5>XvCZR90y2r-8ZS~lHoWd&Z zKY}G@6WD+4&H?zf7X)FCT?A2#OaB-ml#`!MPfw0de?B>7Zzn^Nf=ChQdUDMoIQWbi zUT+@F(d#KX^wHt4AKf{)Iri}*a`WTe_4Up8c7(bqoh-ZqkHD0V!AC>KiS+L*O4jQj zjHOntJZ3nx$YSG|6bev=n}QkvrRC3vVHAUGB0xoMV{4BdfWiqc>X97BK#_=(VUbc9 z*me?zC{;_~Yz)!~N-v=iW+gB`5a z37UkCYY7UHLa_>-g?%3PH~A0VKmXV#!()9Rw^!5=)ywMk(a3FBcY>N>ITW zz>Y+aEhp|os3?V{2x$Xve6S&Kz86GreSA22=3q?38UFm*2Km5vjj`AK40cC?u!-a| z{J?W9sKB!)h3yuA=SjN0iUhEfUlw1J)YIcIGHl`m2XzZe1-eL>L@Ux;TpIL_=C zWe2Sv!8tvFkGTR2k+9?AB=potzKD?$7A*DoNNp3|4p!$z*(l zFd$e5-N{3@46Gyu`gIPgSqN|tRPh+K`l~Tp1PIMAr3wc=2kp`*NF3C|5*(n*EDRC~ zYYZ&%m+=ii`!D1FoxAvzS5XKk4EEo|uf7Pz`>ZG{Dha5P89!F3lX1>hUrZjGN>Huf zL|flx=||l(mfnEgf%9Df0JNT-Fi;6*pFp?IBK-zjgo=#QGnh`Lu}JcLo+ufNMD+e{kAh*1>v%@#UV(ms#w_ zo;Yt$0NfHp%>6f}g6zqS+!;Rqtn~rUgIBw@_5%RthR=leOKz+3ZPFm25%XwGwnyJ% z2C4fK_Y(6A{ce|Hc~G|D-PC(K&RXO<&vZk50yI_K%hKZYmR)r z1;SWMaAFAJclejRBgz!u;I9VV3I@4`gIf9;I9R(ml`%ue)U#B`Kr;zS5y(XVsUi1w zOmPi>K;S_cPldtIi!l5Us9Bi}s)sBqFsvD}0wSX~72j`Up4ltnQwMv(+uK;|ba{+K!dJ5IP)<Ze(NP0<4APNdtrw zhHG(AdrW|2c@;=AlA=fkU38SC1Zh4%OJW(WV`3#K39m5i3Jt&ohfL8~W|~369S^Y9 z`2c{|rxQSon1TIUOmM}suw~If2DmxrD6wN%2v&hOrUxn)P?k0Cju^(lnSgoVOFXMi zv&?uh?-o{&kh>601-?TWLFQsHYKkR4W}yEVl7aMu6i36ADFntm7cz1`Hu5EsLk9wn zFxXQ5!Xo};1Wqfk2OKOVA*(Y(o_O)cb?83HOs?ND3?2lDvi?JvBz|6|3Ruh@fkrBa zvGmg4uuzm~v3lI6e|5ftpEIeUy;j{>^~!$8QJL|;qmEBy#n((2&e(f1UugGOTPgtl zWXd}JPNIoF$lO5{kTJL)`|t>;p5BL%#04P`U7hYHo+qEXz8kCi=Qn`FOmzZs#{Lu2 zkCR@%KP2%uJpsA#LBb%X-e4svN>(!Vez>~O40h6Dj}ivqw9+&2FkRk*Y+n`l=nBAi zurGpQg$_zR7Wnkre*ZymN9G=jBvd(``nZ@?12C3BlfDET-wUiMoMUCdfTlMg+fe0j z$vKh~?Qoka@aEE8?l*zvTpeloz^Ew}BnxL7Xjuyp54CD`+9~JT-EL`1iguc8O5E#V zSP=+L+-ZMlcMBKHNwfI@c}+F0^sayCH8RW6co9RM&KX1@kr?~&UU*ek=~KBlD!stQ z2=6hs$It*OdZo578k9C*rK#h>!jl1W*=yQn$#4T6yn0o|OOjB84Qt=K9|n~Rh*BgH zw|pfmFrMQ1ZjL<>kSP$blWIDpv?`UvGhjVdyA?@OsKNbnax?un`g3$UoiuDijq_F( zrWX(lsl)!nNTA0)Q;KrNuqe=ze8c!pKG=1eka@{|L=|aR;)@Nt)>hz%PC5eaW#>1+ z|51QXnpX*`{&dUS+3nNkGRKiR;u-khkTL3^SQQ{=aCWApsxF9r9NypGj4y}N@!f69 zlwGarC|yRhZ)TuqImyE1P9Z{=2VY{pjKo`;eGNNjD)qxkd#QIyWYlk{tM_VGw!NUF z{eF7ytI@^VH)U&U!0%SlTQ_>JAJb_ohbElTb=Xmxy~=)j#o9;h0&f5tNuLT9Au>PY zusNI($O@Nx?!UpS6|eBCxw7>3tOTdi9RQx_yJfZRn{aI;dko3yhV*TO&9zSZPQ^`g z2z-ICkW_(1!-6ZfG5u%%)1Y}0BJ3#fqn7x&r?P%HIOvv^8{La=#eJ|1oo?~YNXUx* z;jNon zJ=)dKdLeVWcifF%y+Vks z{BOQ9gH?l&V0)#db!D~|t%dE*u|Jtb^||b_m5V&k=413B!y~;z1t9F0onln-i;PZ= zkBc&tYRdckeh5lFl#OcP3|SB#V_ed43skK1*)<1A>y^gW-Fi~%MC+Uahg?8k?Io{9 z)6wPh?ja9G+Q%TQKe%jiXqA#n3_;EKGp91(+|m&k3_;sHxz~rd3v7cey7^3GJjdi` zBf_kF>bnS(Wj2+DA`^&v@gF!b4!3B27eQJyuYlrfAgwC( zEEzlQv2Seom}ifRvq#1@A75QlKZ2M`)sW9q~Zya_tuW2z=*LjpNo8N|5=sXABRT{P&D zT9_Ke&O$J4v%h>%HdI!y&LM-PLXjbMdPzp~bEONJ*mNs=vjmj0s_z8WQIBG>Zwk|` zzyStv1Z2S>Dc8pusp?tjj+GoW;BH*j_Y|wj_};)-KF}gMXh66r{wo;QTwj<90E&JG z6XYoKFguD-QoS~i{k*G-C`Y5Ja7W8(Pn|euHD5jB)}SgVH+@$XDf3gG!%{W)4ZL(Wt3^zguK&pR3~iMY$eyj6PJUo{V8-po9voov zB%(&9o+#0ChS(HBxo~|DxyUSc70Ahomk;2qyIv zLC6A#^&$H6^vG{H(vyCmRBo%XrN##1P1D`wcWGV9A{s_qdvoA!I=To>cvjLlP!D&uZ;+p@g3tRVtL`>V1%3ED;32_cam+<(9-1EHH z;~V_XgZvJf(<#R>a)J#yMZ0Z~DX zRWhOlM)kU*C)6I1O)1g$4H$j*0#&P*E|(m&J;B4=mk2gGK?QW8IGb7G{uaL?k_$VE zwry|tk+sS9iCzA_x5Gb1lhMPUqlcV6xIOl%$G{&0+(;<~Wlv-osPztnZ_801Q|XGm zaope38^^<74Q#-brOtS2pi>`QkhLt_N`3lqIj8)kI7?7BZt+9@u)0oubLb_v=R&U< zID7kVbq&r95B*O29sC!t?6Ea)!Kb8yBnmQzFrlwtHMiq*u(b^m6mL19>`@=WoN?BZ zb?Atj0hUL<$Xv;ZSAZ*3!D@F0P2{L({pGL@1%Iptuxm98lB9Pkrb7+8L+Si&9y%A& z{GJ!(tzy)Kl;D$8f=8(Ymb$uDg-`WkBH8NqpFUV*M>q*E$| z6nC6yfDNnv#^JC|4uR@XipC+0kQCXtgIm+gYaYkg?M-$|UZQL+zN*_@*05q~y5v#b z3!wd{5kD?7nSK3l64oeVE=0%d|hMB(*hI-HJLb+Tp-A}XnFBc@* zsGPA+RIH6;vD)1|tM}EhN*RBHG}H{1eIx$(O~%x_TkMTd5mu8tbyyXi=60enl)F=q zdXl?(#-y@Iy{oIzVf#0fgWwu21{V4HoD8!MEY2NR0sE>3wM>x>9J8nSCmVCm*Nij5q`}^rp?HSCrCX*nz{|H3@PTeg z*XFwf%>r?de+vYHI{74HG4|x-Gd~w7S=m>w3g!e6%Xt)ImM0wqRGa&mU3htV za?&m46eUpEu#keVZ$~;(l%3SAK)6tl!o{%1_$~sGqF2Gl^b5FW8l_&9b(X&B%s``1 z+pQmRHZZTL=XO5JzuQ44$Zv`aY8Iv@tGw`gM$oqbxCYrMF)CTsLE*COQZ(-zA17&WOQuT+6`L$m$JfE^@Ub7Tp&fiwQFTX RH28oj{4bj)9mB;?002N7FV+A6 diff --git a/client/ext/offline/.offline.js.1297618511000.gz b/client/ext/offline/.offline.js.1297618511000.gz deleted file mode 100644 index f14b955fa66333781a73c3900af311ca4daa406a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 682 zcmV;b0#*GViwFo$7FbOJ18-(#Y-w&~E^2cCtyE2q(=ZUd=T{6qrK^xEaH0hStO`O} zv8#=M!)gz1?6hWGJ2+0$2=%`+c0QWy7A*&SNL|nKn>QbxW>7p@Wy|M7}{Ka_$AAZt(tj!ue#@}O)kRs!%Mna|2Y z8HG@57ZI5)f*?sgX;V|fV7cSUmO+P#nJ5ctvyDGS;z5j}TNJ@(tpt=dg%0*baPa&o zx1iOflUEpBaATXn31!S*x)d0<;wB^x61Q;FJvbeCE4jVyctSEDd&c&_Id{?h-OsTO zk9_11KFiD4a4LWB6{EY^knG8^OG+8ne!mDhbKRRd2Q3uU1mi*t1g%TtWH3J}!EA0K z$f%Snn%&Qi#kLz!#v7`bGPfTtliDShA-?p0F<8}|@ZZUzb@c#Yc z1o8Hs3G)xUp7SY699FcF_7vM!jSdws{mAxIN_|?#$sJ+GIX+P>D)I4#B-O?=5j48O z67XS$wS!yf8j#SbQ`!PPcO3IuuL|7br;ePAvuti~fn6hA0w{2H1yB zr>kGt?+a_ogE+41I?825s&T7f;=B}*y|sVhyx7z5y(%DNyFx|f&vMWDP z8sZ|Qmn|Jp%-?MEc0^_^lWsgK1emq4upB)4Uqg@Kl~ppag^~gK2s=1JHpBTL%glt` zc^Igzv}9;2(vTN8T|XA^oim!kignik^E=4=#--#-7)Z~%$xg_HlF$pSuAmvW>vAxO zLisQ~n_e89hONiaY{XZYY14DJLKYTB`&1`Qlyt4QW^MHB4<@1RAPD@#(cmGtWg&A~ zLVt3&WrP5y=f()Z*IO89_78FVUg?_alucJ4t?31=pC~O%$!)T5Vzk-^Pw0gV206GC z(+5*}y^Ztx(Ku#cnUbdjK&e#;+OBtk;c4oOp3>S&T76QDnrVp87_wbd2JENSZs7Mb zI?K(#Fvu~q$j)vIJVPRGsP||P84F%z2C2)%7nrAfHQAE8T`&|A+9s;q^P%@Zt?pD> z$IX;M_+uEKWUL0pH8`3egP5_ZbT82}NzMU}BzRet&R*+bH^)YbeuL-W?gEwoDYL5K zc2;O(L*xiJMmC2e%d}duL@g;NB`L<2>v#zP(Qyt>cQ67s6Ioa!Rgv>t!?LMDjgopcz`WJmT)2FJh>TJKf5SQwBRxo zFx6V=px1qXSh)3CYh2`AM=ezfzQD)T zXS#nO*l4Keu8wd z;2(^tH2amjAn|slAFq!-Uw-uHsN|JhAeSh|R3+6CCsX~K z3ZO^xZhgt~Gi2D=EFc|eBK*Vtl;-)9tGCE<*X+=HbBH$;rGW|kzNZ?_1lT>pgvzlo zrt)qVSl&Rd@FT_G3%5<-$DX+<)_v2GAUAR_&I$R5NY9+A9PPY*?ZS`HY!2y(#Epzt z(D19R@utbA5xQM8pFnA_0Z`4qK|;qBabrVG%tWuLx9;-3Jw&pRFjcu75n_|Ihcv-S>Wy$P)(em6n~pj}Y}MBL97Nq$SI-^3r{n0Lv2ZcrOnh@=odTalnq*`=St) z1U75xT4~D)rP(W?n*2AElEPn^syDhS7nc3<{O9K}d-0X86QxV`GWjQQ8luWT8enfe zoUMLkzb~wEla{qe?Arc`^U^@Wx2DpNV)W+%QZ%r-rDt7K(yv`=;7qoixaHQPqd{$LW?4uUAm91U)RTUWBA zB@8F0dqxOwdiF*LzTUz>vwuj_*Gjis=WMzHX-zNK`bqN2)ZAtZCq}D%@PuB>V331L zF~2pX*V{S28I5BGmMM8g0MuI5pzY=$7@p_O=oziOq}3hCz;@MRpEj-~|$KN4-0PC|LBQFi2f4KEpictI3|+?Si40&^Afyo=<~YYHg>o zb?m1M!XLxnkmMZjNP@>@>FjMi9Omey=r?)@?k->nkTa`1 zw~I;}8zV=+G14EBtk7!7GPR_fl%yCRmydjGIwjaMkfmKjlL`BNS5jQts5g~!%VP|F zZ6}8ly8T+=8HlS$x(Pj>j%udqbPZW!{lYQJ%{5zqhEe6wK?-9C_!#irwLNDGE)48T zZ~puKI6xXkpniCJw1cg&Q+)IEaO7Vd3HUres)orWWi-F zV5+s!QLlRov3T#b)wsxqjyzS?_j+^`Lv*)O#wkUz6L*hC<>ctZ-5PBWLj@Z{)ukDD8{5Hcg*D5mPwoIW1NRXAq(aLMmoTu5_V6I(E?+&+T77irs)MN z)e1|!5Da#W-AzE@2I(>W(x0jV7oA;QeSY`x?bVAdge@(Xy@ru7k+|fOUU0z+{lOp z4L{i%_Z>cs(Cxy1|K!mQKsEmg868)|jU6>H6TP9vz9U4?9h^780Q?bT)6~U_fH}9$ z&;(WgR`STQmSZP_An1m`KqM$h0zwtI6;_lsXJ)AsE{(<_i!BZg%&}x;N=k6+k1kwR zor^A4DwVE~qqi205J@j#s&P9a#t!QUX@Zl~GvQ!t7a&=w2E7#K1TwFRz)|GNfGE%| vfpZ!-j|W8`)|DOUtG2C$vvBJ}Ar#8Mc6DhJ60G=jO8@@?i6~CWB@X}qU`Lsu diff --git a/client/ext/offline/.offline.js.1297621947000.gz b/client/ext/offline/.offline.js.1297621947000.gz deleted file mode 100644 index 9fb7b44f7aabcd4885037e2679254ce5e5978785..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1344 zcmV-G1;6?qiwFq2BUnuW18-(#Y-w&~E^2cCy;p5-+cpsXu3y1wKqCjL)NP-ZY{S~l zeF1{Bh}-o;Pz1DelGV_pN>Y}MBL97Nq$SI-^3r{n0Lv2ZcrOnh@=odTalnq*`=St) z1U75xT4~D)rP(W?n*2AElEPn^syDhS7nc3<{O9K}d-0X86QxV`GWjQQ8luWT8enfe zoUMLkzb~wEla{qe?Arc`^U^@Wx2DpNEI$?1Wq>3BAzj z2K>0))PqSB%A4u=^y0-?y!BX{o%pga+Z^4kl9k2LKHrItlCBlkY#TlMgGp#R2%<1^ zyts{UUCENxG@P9786ni^*&8AFdJ6;1{vl0YE8TLPv*`+?HN9Z#C&?>QbDJ%k7_Iif z6M8X&K@Kj({MM9SZ|D4GG>#cqrsNp`P-|6#wwr@sc%D0>XSDW`R-aT8KMfHYL$-^` zfc>zw8-@Lh&axjE204Zn**T1X7f8e%_3jL!V9}GpAa%L;4D+0?CVO(X6^3F$+a#@f zJ`HZEwVle=v7a&se+=Vy1#5wE4UQ(rAZDzp-AnR7l5@Z_3LclGv$yqdn4_1X-{>K@ zyMQG?&aCp>E-G#Cn5Z>4M*35e6rk0eGk`&|P@{x~Crv!Tjvb2k6GGX8EN{VY6 z^`>%ed5poY?c{Jmw_htf2yqojH=*a#QOz`+t|4ozUpQvDxn>K{FseK{NMQ^C9|OL- zw&!fYg@JwP&41q?2S~#R)DLfucCa;est*upaJu$eYu`DV%7;fY9$<@@Wn73k&#p(- z&n_yHEV#@COtn@z>UD1+7Vo{b8W;J{k*CV~UXPAqi0*dEIHgE-;_mUNoE)9F`=gj} zFwn&pg}9WwE|S@&SFfhC8OC#Sf93-D@P8l-xKW0Fs9`I5ux2^+$sOQk z9NVohL#|i|PexVzW+iV(u}hyLbv#rBzl`tgIoi8Ey~pA5IO6MI_K%X0MI6`|uH`*!;Q}jr6wZ{^o0x4>I^tJ2}fe>SuYzQJnyF$7w<2|SRd>^-3)J6G`)bOT4AXdg2Aq_y9p@VAU(!k`cqZlqO+^3&+k6Iy?XKKH6CYP(%vZ3Df+PZonO-1@4I4YRM*eaDg^H%c(h3HeA!&zz|e?L2+z!cWj_ z3HdvT8yT^n;U|0JzQd;xx?T7WqCDCGsODcGqvMLWv7;ttqBqpocZ3MKgYzaBfIot4 zn!0!qFz418nxN|6N*-C(a_nRf1lmNWCLE0I0wgQdpqIj&K;~5u zIEq{u5Cz&La83i~@u29#y0RmE)wZ>87H)kgghCnEt}bmtf)zhk>HlBOd20SP4*&q7 C^PDCC diff --git a/client/ext/offline/.offline.js.1297622097000.gz b/client/ext/offline/.offline.js.1297622097000.gz deleted file mode 100644 index 2e90ec06b50f4f9ae564529ad347d1513c2df279..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1325 zcmV+|1=9K-iwFo&Bv?%X18-(#Y-w&~E^2cCy;t9F+cpq>*I&VEKw}50)NW5pwqb4O zwgG~4h}-ocC<0nK$!cg)B`M2Bk^g;nq$SI-oVt6M0NWJrc;Ef_NS>3^(}10__jxW# z32a(dmC}~wO0(BO)!DBoC51mzRjqYd%q{!*`HSZfd-;X0Vx_&Ev~K6x!uTPit1&4?jT)aEB* zQ#2B>+>F_`06n+0mJHQm4MmAI^q~m8Im;O=S^J!^u+3rUaV1|sPx`)H?1Wq>37ydD z8vMLnSG`5#%G=4+VlF5a>ClAa~kY~wxqok?ig6pX^u z@#0~It5OzJ({OygV}!C!V{e4g*IDRkc7r5&qjbY{#wJUU)^vgmPMnpd;x?T-F9;qp9c@rnpR~4?3e6l?Nza>M$UzSeyoRUBLOmIl_z0dYJ>Z^(%`7$k2jWU zwP2}QQ2CT3XO1hKJhpNL*h`RwosY(2_U%ypxVEFtRK_ij(D{`e?=NUKR*5GTW`lHN z8lMkpCdp(4X>C16ndRn=%|XMU^57)7(dX6>$J%FeAU>#&)Bf+=+{-i!r26~RY=%yg_!a5ZlKn7UYdB$WhP*vwbG+b_d#}} zo!3SqA@+ehtyYgBIyzz{HcN)-N3t{b9SzC}=*)c|#hSf|*1u7RW#?5MPd~nXJ(*6? zpZmun7s#FeBg253s*gjrFuJM>(?c8D?M;anky9TZNN&ophYC~ViiPlOP{nU-LPLt( z_!y~;p{wol@HsF;dw1uL050ybQvU#+!}?JuZ1H$4>|-cb3LTOxQwMd9E!pm|Gf>2y zjead3VG9?4=X5w#TCXFvR<*nR+dj#+$5J&64O0|SSyq9@`(oj|W(wapfrJCTcC44R z5T5sw>+AQ|L#z*WNB6y^4p}f0FwoZZm9Ql`MhlG9YJH$ZOpPwf?}1D`eQ! zbVNGR#Q1|9C#?&|H_wsft|Os$f7<)2$EOK#-_T{x1lSY9getJrr9QyU@g4-d#Qz}% zU%2%h0(QavR__|Q6uD7=aUg^dlb*R$1=@M~)VUv{*#fc)i5nSnLDNrm=6%~vGxT-s z|K(((Z35No3#4>h5jVEf#7Oj-y7iV2L3?oC2fgGEWHw2hznCxw>kN%i^)Dq)ENeJ+ zJ(vVtQ!+3U6eJ}=<+v4=6gFpOp%j)z6#5KheS%1V`#;OdVME~*xyjkRmb5Uqg| z%%qnvRl6M#VK;VyG{HscnQ&=r<{(+2dVLqh1Tri0z)|GZfXM0Q#pIj^4sowRfK_P+ jdaz9!;w(Jq{zIV*Y^qC}kkX3(LFxBjUV^BX3l9JQ5)qh9 diff --git a/client/ext/offline/.offline.js.1297622166000.gz b/client/ext/offline/.offline.js.1297622166000.gz deleted file mode 100644 index 47f239faed1b3e4d2905db6d7d4fd259b7dd11ad..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1332 zcmV-41*I&VEKw}50)NW5pwqb4O zwgG~4h}-ocC<0nK$!cg)B`M2Bk^g;nq$SI-oVt6M0Lv2Zct1aqpOVwlfSt1Uc`ix` zY+6^9(w5~)v)4k^*{>)ig+EhOt#w(~d{wH=CqD(;=U~m7v zT>ip-n_F9*B}vmXaUp9`O*R@PDJl`$mHh+fC4q*ob*UlFARmRWvWfD+wEtnmZo*V) zNKl9`{<+LZu%j=^*|Y7OJ&{?ne&0cLG1EP9XO=O|!!<+V(0spJSXBZiKmwmuP? zqLGN@X3V|?7`d&rWT+NvC`z=U4`uMpS3SMmk)r0?6wPRNCl&}@=ZccgTADf%7tGa4UAX+IL4BWjNeQ|6wGn0lgDmL3PGJmH7pCWJXtg&b9z;%ke4w}~M-COHh!qRr*`SJF*yIf% zcH?s-8be3h=iz-|hW75xA91+2%S!zN{+zELb%hj<*TO#L?nJUy1GVQ84Lkjkq)I5Nn5e zSqtHDKe@hse?5fyVE<{4DfvTXGxNSj`w%BH0RzOZuY{!O7%ebXtMvgAnIzY+P)p=U zLNM6!x%+?;3X)U&B|nup!h+r2e!BSZ?)K%!5AOonNqKGOh&HN1rqX(Wlj$g(2%tyv zYW;y1SBSx@>41yr*ykkW=m=-Gl7Gtq0}^DSh8c5A&4dc_~8Y?8Qmv0%=vGc-ok zzmz<&tl`Luun4-QVqhgGNh*ZOaj`5ZZO+U>DQu0#;}HoH4$SaSOO=%1`j0MLR;`OR z*N(WMw+2qIl3v18?bb|$eC-5bf|JxU;mX*|L9#;im>K2-GAr}ILFCqe$mwjx;+zN0 q;~s^8RcQxU+J>k&3lF{zLZJ+7x=YKH%8Fm9^#3mxs@J|P4*&qe>z_sd diff --git a/client/ext/offline/.offline.js.1297622262000.gz b/client/ext/offline/.offline.js.1297622262000.gz deleted file mode 100644 index 796a883f52e6cad6474088bb42f5eb0fd4bf3720..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1332 zcmV-41*I&VEKw}50)NW5pwqb4O zwgG~4h|~2UC<0nK$!cg)B`M2Bk^g;nq$SI-lDK=A0Lv2Zct1aqpOWL_fE}}sc`ix` zY+6^9(w5~)v$sOk*{>)ig+EhOt#w(~Z{wH=CqD(;=VDJAv zU;e^=n_F9*BuUdWaUp9`O*R@PDJl`$mHh+fC4q)-b*UlFARmRWvWfD+wEtnmuESJm zNKl9`{<+LZu%j=^*^BL*J&{?ne&0cLG1EP9XOmndL(<+V(0spJSXBZiKmwmuP? zqLGN@X3V|^7`d&rWT+NvC`z=UAIjjnvz)<_wbuy?+Zu-6ujC8pNk6ueosbJ9p%YqN zgCDo+syB&3c|WD?{ zT->j4Rmy^T8jeqQj8N6-*&CtqbryP>-9wVRQ@Y_gW0NIFYdXQkPn?ye;x?T-F zA_*ijz!e5tcnr0`&Cc1#+1q#+??~zLQuI6OXEc5wrTs{Fj;K8>kY~9;oOAIRW*J}Z zhq5E4)|j|)&!@pXwWd|sIQCO^wD!8#btC7(z!SN)&5-~Zv&y3|FSWr1U1@OG@%tOg zwOX)LEvSD=QZ$G4P9EC20_-`+!p=wIG5h{d1G%=N&Q!)Nk1+U^9q&(QS5}Ez7M6o_ zV|qRv)J&4e3ewtokTT27Et`XeLFK_g3ZoCMp~APZ z+^U`)h&tMFp8#EN&+OK~ZlLHk>u)j2#9@09#*SIJvumICpJ@t;YYF)caH|;y2exSH1M-mmLh!qRr$)Ji~*yIf% zcI|T{9z$o_m*IV2hW2hxA91+2%}V_P{+zELb%h*{*TO#Lim%PL$AzEOpR_g~?WRhIMLM@Rb z3Bh1X=zB9=9BH96vp?pq=N>UHCDYEg(CSxYV&0H2iF5 z+%tO`q1&bZ5Xwf|0;<_pNNGbO^lZV4ndmhU`W7-lyS3g0z2Xm4Hc4E(STN_-85*PN zUrHWX)^OxSSOi^DF|ZPpBo#vCxLB5yHfLs`6t+g=@rZ;82WEJvrAkV0{YMurtJX!E zYscKsTLVW}NiSilc55a=zIKE#!Aa_caAj=fAX%Y$%nWk^nU#6qAaY|s*I&VEKw}50)NW5pwqb4O zwgG~4h|~2UC<0nK$!cg)B`M2Bk^g;nq$SI-lDK=A0Lv2Zct1aqpOWL_fE}}sc`ix` zY+6^9(w5~)v$sOk*{>)ig+EhOt#w(~Z{wH=CqD(;=VDJAv zU;e^=n_F9*BuUdWaUp9`O*R@PDJl`$mHh+fC4q)-b*UlFARmRWvWfD+wEtnmuESJm zNKl9`{<+LZu%j=^*^BL*J&{?ne&0cLG1EP9XOmndL(<+V(0spJSXBZiKmwmuP? zqLGN@X3V|^7`d&rWT+NvC`z=UAIjjnvz)<_wbuy?+Zu-6ujC8pNk6ueosbJ9p%YqN zgCDo+syB&3c|WD?{ zT->j4Rmy^T8jeqQj8N6-*&CtqbryP>-9wVRQ@Y_gW0NIFYdXQkPn?ye;x?T-F zA_*ijz!e5tcnr0`&Cc1#+1q#+??~zLQuI6OXEc5wrTs{Fj;K8>kY~9;oOAIRW*J}Z zhq5E4)|j|)&!@pXwWd|sIQCO^wD!8#btC7(z!SN)&5-~Zv&y3|FSWr1U1@OG@%tOg zwOX)LEvSD=QZ$G4P9EC20_-`+!p=wIG5h{d1G%=N&Q!)Nk1+U^9q&(QS5}Ez7M6o_ zV|qRv)J&4e3ewtokTT27Et`XeLFK_g3ZoCMp~APZ z+^U`)h&tMFp8#EN&+OK~ZlLHk>u)j2#9@09#*SIJvumICpJ@t;YYF)caH|;vGh9JE5ITF*M)9=ghPBBA!x2KOdT-;`*{sDi^*N?hF7RYO1AMvhD|s&)jSrBa?jOVuzmOj$@}Sp^zTl!c3$DSYGP zBpisfL%pnp@VK8`UVgkBLVd9Rv`41=v9p&ZFfal3$S|P_ z>STh&`bQ7G5Er*XBkMPJu~RoV2bD|z&I!5BPKm_t_rmC{J9H1MzaNEXA+k> z)`Etg?TmZwPa|}@^q)%EXj?!v`wA&-XoQ|Ecrg>bCd%JJCTKU`yP#M6fyyR{ix&&# z+&V*JRQ*fIBg-0&%n6I2Ybpj-f|8^{s2mr|lG5hPER@35XgolXQsKZ1kG)h$39kR> z!e!OEXmjnj9eQiv2rKC&Ox149M9Aol5GFWDy%4U9%^V~vRFAu1P9U=~4;(~p42Yag wZ!FGf;5_b;3|N(RfX8j9jI(g>6CxDKz^1#jOsTB+Axr=N0t=uuc2Exh08fvgtpET3 diff --git a/client/ext/offline/.offline.js.1297622486000.gz b/client/ext/offline/.offline.js.1297622486000.gz deleted file mode 100644 index 79340136bb387e009a8cfde40dd50f26fe25d689..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1345 zcmV-H1-|+piwFqTC0I=Y18-(#Y-w&~E^2cCy;t9F+cpq>*I&VEKw}50)NW5pwqb4O zwgG~4h|~2UC<0nK$!cg)B`M2Bk^g;nq$SI-lDK=A0Lv2Zc;Ef_NS>18o4H6w;VQJbHL zP0>iiax-S%1N7Y1S~66NH54V<&<{oM-C53H$=d6Lg>4Q)k1P2CdeV>WWGCc8N$7-D z*WkzPy6R0LSKd!9CYP_zqYYwZw&LsDY+`h`QkE7+yLiXmOS+a^vyJ!ccP61}Q!ols z#|z4&vD}tv(vP?*WkID4$EQ2CDEaj4ZBZ6GLp{yzAxYjT-Ef_;$r7YBonV6#XQipQ zP3KOGR=XG|i4lWdC@#|M-jq&n3w}4?2hCIR+-Kp%t3glCa@GLj8eEVVKQmTU?j_za z#Iwv{pA%Cgab*U$&|u4wp-Q;fIU6~98-($WlrAqt+)+QG@dF|4d%|-h?_rKS%MH?< zi_b92_;TNs9Wj;0#EpAC4eqHmt;z=2PubDht72D;oC^a_#M%}|0%Xi8&%(Ua2G@0^ z!EwiLb1c_t!BVxL`YB1?99BDdXz2>D=O7C^AC1TC`$Glf+KxI?8Miz_=T~;TKcQV& zCGK394$_V3`E*b-NhT{uYwJnMEH}4o4jKlP2M5WGKDmYx-(H)I@m`6X_J1emUZ!Cn z)lYM?>G5)_dU_!1Xybi?biJ*!TLZg}B5c;*cX3;qM=a$+%y@b`P-{CcO+4o^6EM+Q z=~1V9FT2ssYon16`#_#nt49$X9kCLdDZ}(5*@?SHgK`2oarc8*vo}Bm?R*?WXr-eN ziNUKpo_>1!b~2r!$9IqCMj*TXj~W0jyFLz`*XXJ)Ob=~nKWIuktDO4yf%K;wyR9%q zHdzQy237phCp0ACwU3b+9y(w0C>@2;ky2EAF^O6zsR)~a?5LfcXKMp~+dp<#+bD$6R+c%m$v z*G%CXCy;Q!*N*kF7Q*v>a(Vgja)|Z8{?lGF<&T}s%)4HLh&-7I7-$juO4wH&q6Nlk zwSJ)8Op;4js3o>hLNM62e0Ko_6eP#^OMWVIqy@XV`FwWu;pX+Hs}BL~wY;`-WE*86 zQ)#`x$#mFG1kj^-v%ccR1#<9WIwBouV*JDImF9)x>zB9=Tw6o$ewXpbmrn=czN3bL z39v_o2~}X*OudAi<5>!NiQhB^U%2&M26oiGGw8a(6xmaNaUg^dlb$(O1=@N3+_@j4 z*#fdNiAx=GLBr2>#{DM%jnM7Ve=%jFZ35NoE2Olc5qq}e#Yps;I{%h3LA&|h1-;}C zWHw2hznCxw>kN%i^)DrlENeJ+PM8E;Q!+3U6eJ}=<+xau6gFpOp%j)z;{l2-6%Nes z*h`g^;OdVME~*xyjkW9T5UqhD%%qnvRl7A4VMlj_G{H&gg>Y$X<{(+2dc7OQ1Tri0 zz)|GJfXM0e#^js^4sox^fK_P+`nXLi<1F0!gb0N)u&FLBQ%Wm-&(iR}TOH D8km^b diff --git a/client/ext/offline/.offline.js.1297623019000.gz b/client/ext/offline/.offline.js.1297623019000.gz deleted file mode 100644 index f1ea09fb5b0a0e3e3e722f6154fc3a4de1488eab..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1396 zcmV-)1&jJ0iwFqoCs<7a18-(#Y-w&~E^2cCwO4I#+cpsXu3y1wKw}50)NY@aY{UBE zz5qco#OeAWC<0nK$!ciQBPq*9k^jCsQj+CZN!$T5V4LK<-#sLc$?^ckDPsnSwOHKKymI z`i1>Aw|04wBz0ZKg{+7*X*f($EJbYB_79Xx0uBFErG_|zd=$dkCdxb0{)Z8}4pXHe zK_X84xXg&K!!OF&i>=Ha$!uiWuIHryvoRJHySI00fP22<#8>^P@z(Q(A zcX+~1wj~iRKky1k-MJXr7U;+?s>f8nl(Du?>j1SDxq#GISNw; zZvup|+?Hw5FWj<}1tB!-IZUbvLo{wwZ>OZ1u+i!533h`dd9QTMb;c$ukk)jBHtMW2 zOK#J-Bcs+XLV`xb`lWGs$nJ@Bd|Tsp1AdU4l4m|CFJ29L`YC5MFs{MHjOk!he>aJD z6!9!`w$BLvl6Jrh@c4i&_J``!LdWTAEbIb^KAwtp`@p#yKM>NsCp^b3d6*;5a)Xm97!9@uph&1U^m8IPrT#8NKAjHkB)wRT77oXbqWL~Er- z9q+yDMmy75BOvw-d04F3slg~$3Y7%brd2n z_%e^DpWnWnOsDAa-Q&3tuwKal>EV-Fdo zh$ai+$smj0`eY43c^WOMY6@FiUJ3h{m2-s- zNtUSxc@8bv?!h!r#Ga0REgwM(7l0>uI8|D2BDPVLYY^H7%RdjKsu?P#D5SD%2^w#d zh4Y#zoN+P|4*1%kURFYQ+)pkqFD{2rAMB6znkj$n4787klbL{l2C~nDebpgc*g1-rTV^6Bd1&Fjxs9|JmTd1dE_Hp)V# z(rSUiblFY>(9pcuT=C)@F?c>55sx%6{$Vpqa^d*;CGG>)*3jIaGXDDV=|H>hsbOFO z?2%zY71&f$FJb3+mx5m5OU>YfTi<11zwJANt{Y4dJp~wNglxpbXU!POs4xTsnaZLD2yht_I1!c2M!Q)U6L-K2*rvpDKOf1D(ebgzj@f0J7P$a6 zt;$j=!&0f(+d@{!?>I{Wf5oy~t2~<<_Up;ZlYqVc##fEO-jVVu~sIKcU6BUU@jf9S}vIxz}{E2#zr{H^)D+m)vM?S1fBwaA=zaOw0Kb8t2 zoWut=&J&v0k}vYvi;d3ClwOOteV^t9=(RSm=neg^fyeNZRw6dJ6eE-j7&;2t`UGr> zMgo@VG5g_RKc~!ZYv?<_f-j&qd)X@6NnS_+-K0?! zxc6pV_LL};50i_@)$8-1@mT83^i8Up9Gxy`gKxWhr67l67poPR=Q?bX2(^*rJ1`KN z(fF(r$y3(qb?v$ z7TyF1ZMezfsNc9U7a1Yc?*&Yz2}3k)Om7~^G-0E&+f(cYQS@G_nyZ9OmLQDoCfcl% zT$kL$b32Vzy9@~$0qeKM)**SI(#dU{-w(t=dQzObqP%!H80n{!)xfv{n=_V!QUC2j zxMheZiS>O>_!qPSW`NrVY_LDnrxrTaUgKdKK=g65=(i7?`|+NXb|c{_F3H0Jd6sHi z#JuZloNS#S^)<%@F_zGtz-F&6UR8 zs#IXH<2G-WD!E{>Tu}eqDm|=s;?UM*V9r5gWf+iECtLXGQ;&9n}dQu;lY=b2!%2lvsdh7Ocq6gonwWR<)B7? zy$?Si=WS?5sdGH-toV3O`D2bO)Q=HO2K>{#i#UqN8-no0L>hJUq9;#Niwer^O%W7l^@&>4;>64)G7^FzNZ@ zn-g3ImKf2q+hyGG<;sD6-xK9v1(+j^$7P1JoY;t&<7o_PiFZAX58ODOf{fj9i;hpm zh@K3zbwWNulGEohLp#r(+wem)n?dqP;8Mq0(CcSg@7>ji-q6pLyGbUaO#$WX8(vuG z#)RnEfEP1SYq}A&kn!5h_ulIjf1t8aWaGtxS+~{@(S~E(jlu&INg2MF;jtG>A;9(@UD&Kz7j3Q`J4A0a9APEh43m{zGXZkK zBZLVm$rpt!V?77KGTCE)m=lO3Pdy8fTMb3pTxQNZ>v4x-z zZ3AS<5T`4Kpa^K`B&(rGm82{iMgIHls29tzoTdY2z&0h``#mHdMu&$UJ7kw>Dsl;I zS{8-UmZeIwQ=!V_SDYn*KVns^b)L;F`}yeQQNUh*;j2*TjJ*l}4xNO^V~_^e`@hbY zzpz(xYm4J3s;Vl?WJ#h?BVnSf5TRY!-%&5}G<+>{4PgT5$cL4UR2@wF?+5J0kClc9 zC-I>k=Lt=0$%}mUVxzM&W!5ro->10%vo;nMy`ld(@ECs5QpPq{a)gorLq|bdpMXu# zNWfAvX5T!F+?HB06bm(EIoiv$ z4&DR^W4X=asNc9Eml+|{?*&Yz2}3k)Om7~^G-0E&+f(cYQS?sfitB_;mLRR^Cfcl% z+!Wl#b2p7vy9@~$0qeKM)ggJH(#dU{-w(t=dP<(xMS1aZFw(P>Rlv9gmot`wQUBdT zxMheZiSvC<_?NT-W`NrVY_LDnrxrTSUgKdKK=i3+(Qh9(_v1Y&?MA{=T#`=<vaQafGjx{-2R7T?LWZH@#;m{m0>(_9rcD$?5 zF0>r?V=M>BhBQ7Il#HUu3gXh%AZ3=DJ2nRmgTk?rMhOUo%0~Y5pNms=c0KvCzfd1$ zNHLDa>=ip2lPj5EOPNrr7}V%5-0w6I2D!_dOax4{R(jOQKB#W6^;l`d z+ODG-v8zWtI;3MK8kJ#W)7-K94F>1Q(Xsn|5^B65&~`BnFM?)vB$Y z6eGTWyfz}=CJX;p4**wm-wzQS3{_{Q=WS@Ksd799o%nc9`D2ds)Q=HO7X0JAi#UqN z8-nnr&XE`*9ju>+cZwO>yE}Qr;o>gI^$+-SzJAmdvS(fj`>TCW4PR;A+}EqAML9#T~d4O13USyq6?!)oE8W(uD;IT_l} zJJickh#L2ktEWFu+awLP*)3B8B8!tMwk98%0;JP)lTJ zLNM5{$$O6y@}fihMnB~#!h+r2emcAUaQph>^#_mkT3*^YqK&GMsJL99GM(cS0W>sk z*4I3{Knz|?MU8X+1>T;mpcf>`Q0DEBYxXh5R6HT#m zJf=Y}@e*k8f!mtEAeFC4M@KYcL{A3BIUye*$(eJNp`GW?UHEi}XONspTrhsbp1#dERl|uAvz>AsaHC>fj$awAMd++s%KTz2ya`9rpoLgsz zXv1424=k%Vk}@oUE~yw;2}+U*fsA6#my|YVW}y_eM&kjBYz<$`@YqAzBEj_^UAU}T z7j3Q`ZA5Ps9AG8u8Kz3NW&$LO2M7~XQZIxnV>JiKGS#Dnm=nk(Pdx{bTLU6(ZaF8O r^SDDYV3peeir7#YXW_x^#23my(_LDoRMx}DqyK*aDOgPc18-(#Y-w&~E^2cCwO4I#+cpsXu3y1wKw}50ly0Avbi>-z zYXfA-5T`4Kpa^K`B&(50jif9aMgIHlNJ*AsIY|f1fNe><_j`_f6dfLV?2uiiX^{(H z)2b|`GAxyfoffi6e#TW2_$`*@TIJc?upf_J9|i2~SH23R%GkT`uh2>qc??1W`|#)a z@+bCYZcKR`MRi?=nW#uKY9w@&l|^V)=2z5i(bmYUzMA8M*{`&#D@nfkV z!bQAs*Lgw(U$a1uy_GR$RT25fe+Rrib+Zy`Luiy*ltzNduc99oSK(}aA z1@5_7mpvs4<-_D+a`pB+XgrpBvwV~4CP$|W+ThbJUn$7p*u`oE=D7}=BtmUu`5p|! zW;8zOMDmoidfl@f)l6Pmk-j1n8XnbfSFOr@FYa9ATXFXyUUJ168|;@YlXxqjZaX>h zV+(Hrgf`sdanx^InTw1N>h}UB(}W?qZcJ}($uwc3v)fbbE~4mnscNngHd%r&x?5)ChmefucyKbZ$$Kr`x+vW&>gc<2TndVC4 zZdEF<*m0XTOO;%(ST3l4Zc3lmJMl!tcmc|cUXM?P8=8SR2a%ciXgp@$_O+FxiOxa7 z4G%D-l^O3UwBwiKE{#PZ*^sVJ1|_3tvVyoW4!X>6eaGgYU{E-gLdyceLS`d>`uD{t zJG-9z;g5$+Q-&1dXw2TQqcOP>2{xJ)QkH`n{qY{!28TM`ewy>;0Vb^Uf1ie(*22Iv zJUt9`ZxA-3C(ndAonKF&ytk8gL$@6$x=s4W0PchHfW^EhW<0(dfRWv^=UgNOOq7yp z)X6@mZm@M*E5zEaBZt`4qaH2Nu@jBTFtTaw*!~8C>*VOz{yqye-VkWJ7>9?TSsnQZ z3|^+;^wa6-WIDx&?;o#Ck#AFo|EmXp6W#YivIT_l}JJgG+C>-u5S67!;L#X$5uXfm*JJAOCD&k~TzyM?I3n7tvjucXMCD(h{ zZxmg@LN1ZF6$OJ0$GrC_Aul?_Z}dZ+A}rYL?dP-WkGF3>U4QgwujQ4QBig76iHxfS zD$_YWDS$5ZyY)5CE)atk(-FxC9pWFdXVUY>H%B-JmPgUO+hyGG<;sD6e89-f-h+4HA4uMLPN!BYHB>)(QCtNlu^34DGypX~U;OJcHy+ z;HYCQ=<$oK=k864p3u*gd+|(0n*z$&SA4&r7c4~22E3SwTGI!QuIBa`T@h6AjmTVb-YGZP?%JV2PBl6+OzGS+htER#L5h&h2s^3<~s zxz$jl&AZQuXFcwa3|Qr6fH*c(##(sr&f*tRL(^RvQ!49W4grLP4 diff --git a/client/ext/offline/.offline.js.1297623551000.gz b/client/ext/offline/.offline.js.1297623551000.gz deleted file mode 100644 index 5fc532a33b6ee07647136f4fbe00d21b8ee8744c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1437 zcmV;O1!DRiiwFq+DOgPc18-(#Y-w&~E^2cCwO4I#+cpsXu3y1wKw}50ly0Avbi>-z zZ3AS<5T`4Kpa^K`B&(rGm82{iMgIHls29tzoTdY2z&0h``#mHdMu&$UJ7kw>Dsl;I zS{8-UmZeIwQ=!V_SDYn*KVns^b)L;F`}yeQQNUh*;j2*TjJ*l}4xNO^V~_^e`@hbY zzpz(xYm4J3s;Vl?WJ#h?BVnSf5TRY!-%&5}G<+>{4PgT5$cL4UR2@wF?+5J0kClc9 zC-I>k=Lt=0$%}mUVxzM&W!5ro->10%vo;nMy`ld(@ECs5QpPq{a)gorLq|bdpMXu# zNWfAvX5T!F+?HB06bm(EIoiv$ z4&DR^W4X=asNc9Eml+|{?*&Yz2}3k)Om7~^G-0E&+f(cYQS?sfitB_;mLRR^Cfcl% z+!Wl#b2p7vy9@~$0qeKM)ggJH(#dU{-w(t=dP<(xMS1aZFw(P>Rlv9gmot`wQUBdT zxMheZiSvC<_?NT-W`NrVY_LDnrxrTSUgKdKK=i3+(Qh9(_v1Y&?MA{=T#`=<vaQafGjx{-2R7T?LWZH@#;m{m0>(_9rcD$?5 zF0>r?V=M>BhBQ7Il#HUu3gXh%AZ3=DJ2nRmgTk?rMhOUo%0~Y5pNms=c0KvCzfd1$ zNHLDa>=ip2lPj5EOPNrr7}V%5-0w6I2D!_dOax4{R(jOQKB#W6^;l`d z+ODG-v8zWtI;3MK8kJ#W)7-K94F>1Q(Xsn|5^B65&~`BnFM?)vB$Y z6eGTWyfz}=CJX;p4**wm-wzQS3{_{Q=WS@Ksd799o%nc9`D2ds)Q=HO7X0JAi#UqN z8-nnr&XE`*9ju>+cZwO>yE}Qr;o>gI^$+-SzJAmdvS(fj`>TCW4PR;A+}EqAML9#T~d4O13USyq6?!)oE8W(uD;IT_l} zJJickh#L2ktEWFu+awLP*)3B8B8!tMwk98%0;JP)lTJ zLNM5{$$O6y@}fihMnB~#!h+r2emcAUaQph>^#_mkT3*^YqK&GMsJL99GM(cS0W>sk z*4I3{Knz|?MU8X+1>T;mpcf>`Q0DEBYxXh5R6HT#m zJf=Y}@e*k8f!mtEAeFC4M@KYcL{A3BIUye*$(eJNp`GW?UHEi}XONspTrhsbp1#dERl|uAvz>AsaHC>fj$awAMd++s%KTz2ya`9rpoLgsz zXv1424=k%Vk}@oUE~yw;2}+U*fsA6#my|YVW}y_eM&kjBYz<$`@YqAzBEj_^UAU}T z7j3Q`ZA5Ps9AG8u8Kz3NW&$LO2M7~XQZIxnV>JiKGS#Dnm=nk(Pdx{bTLU6(ZaF8O r^SDDYV3peeir7#YXW_x^#23my(_LDoRMx}DqyK*a-z zZ3AS<5T`4Kpa^J*lGV_pN>Y}M!vB4Dq$EqRoVo)hATeL={hlL_(ebfIj>&bJ@?0=7 zEsH`bLsF^8IhSSfE9@e{AF(XfD$nMI{Cx8ABp|Q9&{Zf^M&5*fhn9lpF%z1R_kUe3 ze<82t#uTSfR8>`&i4sYpnnFie!9%k$zXM<7DfYF@6$=xVj(oN&vjTk5o#lgx6H8E zjK*gzNuH91uU*?=&E&Nu=`u2|*^?Y@)Ua$icIP7R+1;^tNfoJOkl%VDVJcYFWOC%k z7TyR5ZK%oPsGGPV7a1bd?--1t5kokxMQ^7l8nMyJ?J%~3D0(MVMO8v3OD2qNHyW># zTo=^DbGr>vTMrQ$0qLg3#vysc($Z~}ZwBl@J}E9;P+q(owDck+6(dwJ>obIdVE*kS z>8Simv*AkjSNgig%vs6P7 zQ~rs~61vSsv)TrJC+{RP8Qp2q!h~HJH9r_5sW3oNC(O^R~k;M zLNSXScX*Rj$pwk!0`upp^swBCLsOSAbIC+z=A-eLeA|~ms?4Yrl~6+iFn(pmy9`aC z?7&O$D;kw5+C>YQBMPX6o`YR^MZ z8qTRmID`c!d+3IlAw1~dXiQ#_lQG&U62JrDQWk?8-BV!bv>XCN^VQeTBj{M_|6VjZ z2w(#c_7va86!ef6Y9TPBc+nq{~waPQRq4+i&9(Wz56xLcK_|OjVigXd61xsvNFc zOFrI{shC0=?8g8+!~D~|^&Um24e)yBe8f%^FYwR9C(R7xJ)AwEaPg4j>Id{$T|cS{ zx^!A{^OTk=2?hz0$h|a&rmTO44J5H6qnpbo&_baE44nT`sdYfsva~%;(<8bUj*t}r z!RUpMh7?S}rMR$G6A3elN`@|u7WJa!+~Iz5b8~$&gnDmxw9^l|n{%K82As?|8|Z<% zM9>-@LIsZiCD(iU=_tBk3%P_&kaNPeUF*g}hrH+*{-U4q6ktK_?>}AKez<@A@%Dp< z$1*L=9MFbQNMu|tfEoLR31>LeZ`QXoy8;YeO-D#0bO=Atk0YNyzB_?Bu$>vs-6`X4 zIu{Pm`wn{^!kIbJaIP3aN91eoLtZo^p3 znIM_$w4dM;6G@(W79#hW@w9$Toq1N{PCvp{xfy6h>#oO2c=QVLxzw!AE|w{V^*HkI N_g~HmqSWvZ001t~z{LOn diff --git a/client/ext/offline/lib-offline.js b/client/ext/offline/lib-offline.js index 5f7be8b14b2..763be6b98c0 100644 --- a/client/ext/offline/lib-offline.js +++ b/client/ext/offline/lib-offline.js @@ -45,40 +45,40 @@ var Offline = function(namespace, detectUrl){ cache.addEventListener("offline", function(e){ console.log(e.type); - }); + }, false); cache.addEventListener("online", function(e){ console.log(e.type); - }); + }, false); cache.addEventListener("checking", function(e){ console.log(e.type); - }); + }, false); cache.addEventListener("downloading", function(e){ console.log(e.type); - }); + }, false); cache.addEventListener("progress", function(e){ console.log(e.type); - }); + }, false); cache.addEventListener("cached", function(e){ console.log(e.type); - }); + }, false); cache.addEventListener("noupdate", function(e){ console.log(e.type); - }); + }, false); cache.addEventListener("updateready", function(e){ console.log(e.type); cache.swapCache(); - }); + }, false); cache.addEventListener("error", function(e){ console.log(e.type); - }); + }, false); }; (function(){ @@ -89,8 +89,8 @@ var Offline = function(namespace, detectUrl){ //If we were offline lets stay offline if (this.offlineTime) this.goOffline(); - else //Else we try to go online - this.goOnline(); + //else //Else we try to go online + //this.goOnline(); this.startDetect(); } @@ -100,6 +100,11 @@ var Offline = function(namespace, detectUrl){ this.isSiteAvailable = function(callback){ var _self = this; + if (!this.http) { + this.http = new apf.http(); + this.http.timeout = this.interval; + } + this.http.get(apf.getNoCacheUrl(this.detectUrl), { callback: function(data, state, extra){ if(state != apf.SUCCESS || !window.navigator.onLine){ @@ -128,9 +133,6 @@ var Offline = function(namespace, detectUrl){ } }); - this.http = new apf.http(); - this.http.timeout = this.interval; - //Check if we have connection right now this.isSiteAvailable(); diff --git a/client/ext/offline/offline.js b/client/ext/offline/offline.js index 433f3812cb4..3add695bd35 100644 --- a/client/ext/offline/offline.js +++ b/client/ext/offline/offline.js @@ -2,10 +2,15 @@ * Offline Support for Cloud9 * * @copyright 2010, Ajax.org B.V. - * @license GPLv3 */ -require.def("ext/offline/offline", ["core/ext", "core/ide", "ext/offline/lib-offline", "ext/offline/lib-sync", "ext/filesystem/filesystem"], - function(ext, ide, Offline, Sync, fs) { + +define(function(require, exports, module) { + +var ide = require("core/ide"); +var ext = require("core/ext"); +var Offline = require("ext/offline/lib-offline"); +var Sync = require("ext/offline/lib-sync"); +var fs = require("ext/filesystem/filesystem"); return ext.register("ext/offline/offline", { dev : "Ajax.org", @@ -15,17 +20,30 @@ return ext.register("ext/offline/offline", { deps : [fs], test : function(online){ - this.offline.stopDetect(); + ide.testOffline = true; if (online) - this.offline.goOnline(); + ide.socket.connect(); else - this.offline.goOffline(); + ide.socket.disconnect(); }, init : function(){ - var offline = this.offline = new Offline("cloud9", "static/ext/offline/ping.txt"); + var _self = this; + var offline = this.offline = new Offline("cloud9");//, "static/ext/offline/ping.txt"); var sync = this.sync = new Sync("cloud9"); + //Replace http checking because we already have a socket + offline.isSiteAvailable = function(){} + + //Set events necessary for checking online status using socket poll loop + ide.addEventListener("socketConnect", function(e){ + //offline.goOnline(); + }); + + ide.addEventListener("socketDisconnect", function(e){ + offline.goOffline(); + }); + //Forward Events offline.dispatchEvent = function(name, e){ ide.dispatchEvent(name, e); @@ -44,6 +62,7 @@ return ext.register("ext/offline/offline", { fs.realWebdav = fs.webdav; fs.webdav = offlineWebdav + _self.indicator.style.display = "block"; ide.onLine = false; }); @@ -54,7 +73,6 @@ return ext.register("ext/offline/offline", { var item = data.item; //Execute sync task here console.log("SYNC ITEM"); - item.handler(item); if (next() < 0) //End of loop offline.goOnline(); @@ -67,6 +85,7 @@ return ext.register("ext/offline/offline", { if (fs.realWebdav) fs.webdav = fs.realWebdav; + _self.indicator.style.display = "none"; ide.onLine = true; }); @@ -77,8 +96,7 @@ return ext.register("ext/offline/offline", { write : function(path, data, x, callback){ sync.add(path, { path: path, - data: data, - handler: function(){} + data: data }); if (callback) @@ -106,6 +124,16 @@ return ext.register("ext/offline/offline", { } } + this.indicator = document.body.appendChild(document.createElement("div")); + this.indicator.style.backgroundColor = "red"; + this.indicator.innerHTML = "OFFLINE"; + this.indicator.style.padding = "3px"; + this.indicator.style.position = "absolute" + this.indicator.style.zIndex = 10000; + this.indicator.style.right = "10px"; + this.indicator.style.top = "10px"; + this.indicator.style.display = "none"; + offline.start(); }, diff --git a/client/ext/settings/settings.js b/client/ext/settings/settings.js index d8fe1054274..b536374b472 100644 --- a/client/ext/settings/settings.js +++ b/client/ext/settings/settings.js @@ -103,6 +103,14 @@ return ext.register("ext/settings/settings", { }); });*/ + ide.addEventListener("afteronline", function(){ + _self.load(); + ide.removeEventListener("afteronline", arguments.callee); + }); + }, + + load : function(){ + var _self = this; this.model.load(this.convertOrBackup() ? template : ide.settings); ide.dispatchEvent("loadsettings", { diff --git a/client/include/windows.xml b/client/include/windows.xml index dfcb50dff3b..64b840e9ea8 100644 --- a/client/include/windows.xml +++ b/client/include/windows.xml @@ -1,51 +1,3 @@ - - - -

-
sadfasdf
+
[No Message]
@@ -99,7 +51,7 @@ skin = "change_photo">

-
sadfasdf
+
[No Message]
@@ -122,7 +74,7 @@ skin = "change_photo">

-
sadfasdf
+
[No Message]
diff --git a/client/js/apf_debug.js b/client/js/apf_debug.js new file mode 100644 index 00000000000..47e9900f044 --- /dev/null +++ b/client/js/apf_debug.js @@ -0,0 +1,135917 @@ + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/jpack_begin.js)SIZE(0)TIME(Mon, 10 Jan 2011 10:53:12 GMT)*/ + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/apf.js)SIZE(96988)TIME(Tue, 15 Feb 2011 08:59:13 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + + +/** + * Ajax.org Platform + * + * @author Ruben Daniels (ruben AT ajax DOT org) + * @version 3.0 + * @url http://www.ajax.org + * + * @event domready Fires when the browsers' dom is ready to be manipulated. + * @event movefocus Fires when the focus moves from one element to another. + * object: + * {AMLElement} toElement the element that will receive the focus. + * @event exit Fires when the application wants to exit. + * cancelable: Prevents the application from exiting. The returnValue of the + * event object is displayed in a popup which asks the user for permission. + * @event keyup Fires when the user stops pressing a key. + * cancelable: Prevents the behaviour. + * object: + * {Number} keyCode the char code of the pressed key. + * {Boolean} ctrlKey whether the ctrl key was pressed. + * {Boolean} shiftKey whether the shift key was pressed. + * {Boolean} altKey whether the alt key was pressed. + * {Object} htmlEvent the html event object. + * @event mousescroll Fires when the user scrolls the mouse + * cancelable: Prevents the container to scroll + * object: + * {Number} delta the scroll impulse. + * @event hotkey Fires when the user presses a hotkey + * bubbles: yes + * cancelable: Prevents the default hotkey behaviour. + * object: + * {Number} keyCode the char code of the pressed key. + * {Boolean} ctrlKey whether the ctrl key was pressed. + * {Boolean} shiftKey whether the shift key was pressed. + * {Boolean} altKey whether the alt key was pressed. + * {Object} htmlEvent the html event object. + * @event keydown Fires when the user presses a key + * bubbles: yes + * cancelable: Prevents the behaviour. + * object: + * {Number} keyCode the char code of the pressed key. + * {Boolean} ctrlKey whether the ctrl key was pressed. + * {Boolean} shiftKey whether the shift key was pressed. + * {Boolean} altKey whether the alt key was pressed. + * {Object} htmlEvent the html event object. + * @event mousedown Fires when the user presses a mouse button + * object: + * {Event} htmlEvent the char code of the pressed key. + * {AMLElement} amlNode the element on which is clicked. + * @event onbeforeprint Fires before the application will print. + * @event onafterprint Fires after the application has printed. + * @event load Fires after the application is loaded. + * @event error Fires when a communication error has occured while making a request for this element. + * cancelable: Prevents the error from being thrown. + * bubbles: + * object: + * {Error} error the error object that is thrown when the event callback doesn't return false. + * {Number} state the state of the call + * Possible values: + * apf.SUCCESS the request was successfull + * apf.TIMEOUT the request has timed out. + * apf.ERROR an error has occurred while making the request. + * apf.OFFLINE the request was made while the application was offline. + * {mixed} userdata data that the caller wanted to be available in the callback of the http request. + * {XMLHttpRequest} http the object that executed the actual http request. + * {String} url the url that was requested. + * {Http} tpModule the teleport module that is making the request. + * {Number} id the id of the request. + * {String} message the error message. + * @default_private + */ +var apf = { +VERSION:'3.0beta', + // Content Distribution Network URL: + + /** + * The url to the content delivery network. + * @type {String} + */ + CDN : "", + + + /** + * Boolean specifying whether apf is ready for dom operations. + * @type {Boolean} + */ + READY : false, + + //AML nodeFunc constants + /** + * Constant for a hidden aml element. + * @type {Number} + */ + NODE_HIDDEN : 101, + /** + * Constant for a visible aml element. + * @type {Number} + */ + NODE_VISIBLE : 102, + /** + * Constant for an o3 widget. + * @type {Number} + */ + NODE_O3 : 103, + + /** + * Constant for specifying that a widget is using only the keyboard to receive focus. + * @type {Number} + * @see baseclass.guielement.method.focus + */ + KEYBOARD : 2, + /** + * Constant for specifying that a widget is using the keyboard or the mouse to receive focus. + * @type {Boolean} + * @see baseclass.guielement.method.focus + */ + KEYBOARD_MOUSE : true, + + /** + * Constant for specifying success. + * @type {Number} + * @see element.teleport + */ + SUCCESS : 1, + /** + * Constant for specifying a timeout. + * @type {Number} + * @see element.teleport + */ + TIMEOUT : 2, + /** + * Constant for specifying an error. + * @type {Number} + * @see element.teleport + */ + ERROR : 3, + /** + * Constant for specifying the application is offline. + * @type {Number} + * @see element.teleport + */ + OFFLINE : 4, + + + debug : true, + debugType : "Memory", + debugFilter : "!teleport", + + + includeStack : [], + initialized : false, + AppModules : [], + + /** + * Boolean specifying whether apf tries to load a skin from skins.xml when no skin element is specified. + * @type {Boolean} + */ + autoLoadSkin : false, + /** + * Boolean specifying whether apf has started loading scripts and started the init process. + * @type {Boolean} + */ + started : false, + /** + * Namespace for all crypto libraries included with Ajax.org Platform. + */ + crypto : {}, //namespace + config : {}, + _GET : {}, + $asyncObjects : {"apf.oHttp" : 1, "apf.ajax": 1}, + + /** + * String specifying the basepath for loading apf from seperate files. + * @type {String} + */ + basePath : "", + + + /** + * {Object} contains several known and often used namespace URI's. + * @private + */ + ns : { + apf : "http://ajax.org/2005/aml", + aml : "http://ajax.org/2005/aml", + xsd : "http://www.w3.org/2001/XMLSchema", + xhtml : "http://www.w3.org/1999/xhtml", + xslt : "http://www.w3.org/1999/XSL/Transform", + xforms : "http://www.w3.org/2002/xforms", + ev : "http://www.w3.org/2001/xml-events" + }, + + + xPathAxis : {"self":1, "following-sibling":1, "ancestor":1}, //@todo finish list + + hasRequireJS : window.require && typeof require.def == "function", + + availHTTP : [], + /** + * @private + */ + releaseHTTP: function(http){ + if (apf.brokenHttpAbort) + return; + if (self.XMLHttpRequestUnSafe && http.constructor == XMLHttpRequestUnSafe) + return; + + http.onreadystatechange = function(){}; + + http.abort(); + this.availHTTP.push(http); + }, + + /** + * @private + */ + browserDetect : function(){ + if (this.$bdetect) + return; + this.$bdetect = true; + + // Browser Detection, using feature inference methods where possible: + // http://www.thespanner.co.uk/2009/01/29/detecting-browsers-javascript-hacks/ + // http://webreflection.blogspot.com/2009/01/32-bytes-to-know-if-your-browser-is-ie.html + // http://sla.ckers.org/forum/read.php?24,31765,33730 + var sAgent = navigator.userAgent.toLowerCase() || "", + // 1->IE, 0->FF, 2->GCrome, 3->Safari, 4->Opera, 5->Konqueror + b = (typeof/./)[0]=='f'?+'1\0'?3:2:+'1\0'?5:1-'\0'?1:+{valueOf:function(x){return!x}}?4:0; + + /* + * Fix for firefox older than 2 + * Older versions of firefox have (typeof/./) = function + * So firefox to be treated as Chrome, since the above expresion will return 2 + * Newer versions have (typeof/./) = object + * + */ + if((typeof/./)[0]=='f' && parseFloat((sAgent.match(/(?:firefox|minefield)\/([\d\.]+)/i) || {})[1]) <= 2) + b = 0; + + if (b == 2 && sAgent.indexOf("chrome") == -1) + b = 3; + + /** + * Specifies whether the application is running in the Opera browser. + * @type {Boolean} + */ + this.isOpera = b===4 || b===5;//(self.opera && Object.prototype.toString.call(self.opera) == "[object Opera]"); + //b = 5 for Opera 9 + + /** + * Specifies whether the application is running in the Konqueror browser. + * @type {Boolean} + */ + this.isKonqueror = b===5;//sAgent.indexOf("konqueror") != -1; + + /** + * Specifies whether the application is running in the Safari browser. + * @type {Boolean} + */ + this.isSafari = b===3;//a/.__proto__ == "//"; + + /** + * Specifies whether the application is running in the Safari browser version 2.4 or below. + * @type {Boolean} + */ + this.isSafariOld = false; + + /** + * Specifies whether the application is running on the Iphone. + * @type {Boolean} + */ + this.isIphone = sAgent.indexOf("iphone") != -1 || sAgent.indexOf("aspen simulator") != -1; + + /** + * Specifies whether the application is running in the Chrome browser. + * @type {Boolean} + */ + this.isChrome = b===2;//Boolean(/source/.test((/a/.toString + ""))) || sAgent.indexOf("chrome") != -1; + + /** + * Specifies whether the application is running in a Webkit-based browser + * @type {Boolean} + */ + this.isWebkit = this.isSafari || this.isChrome || this.isKonqueror; + + if (this.isWebkit) { + var matches = sAgent.match(/applewebkit\/(\d+)/); + if (matches) { + this.webkitRev = parseInt(matches[1]) + this.isSafariOld = parseInt(matches[1]) < 420; + } + } + + /** + * Specifies whether the application is running in the AIR runtime. + * @type {Boolean} + */ + this.isAIR = sAgent.indexOf("adobeair") != -1; + + /** + * Specifies whether the application is running in a Gecko based browser. + * @type {Boolean} + */ + this.isGecko = b===0;//(function(o) { o[o] = o + ""; return o[o] != o + ""; })(new String("__count__")); + + /** + * Specifies whether the application is running in the Firefox browser version 3. + * @type {Boolean} + */ + this.isGecko3 = this.isGecko;// && (function x(){})[-5] == "x"; + this.isGecko35 = this.isGecko && (/a/[-1] && Object.getPrototypeOf) ? true : false; + this.versionGecko = this.isGecko ? parseFloat(sAgent.match(/(?:gecko)\/([\d\.]+)/i)[1]) : -1; + var m = sAgent.match(/(?:firefox(-[\d.]+)?|minefield)\/([\d.]+)/i); + this.versionFF = this.isGecko && m && m.length ? parseFloat(m[2]) : 4.0; + this.versionSafari = this.isSafari && !this.isAIR ? parseFloat(sAgent.match(/(?:version)\/([\d\.]+)/i)[1]) : -1; + this.versionChrome = this.isChrome ? parseFloat(sAgent.match(/(?:chrome)\/([\d\.]+)/i)[1]) : -1; + this.versionOpera = this.isOpera + ? parseFloat(sAgent.match(b===4 + ? /(?:version)\/([\d\.]+)/i + : /(?:opera)\/([\d\.]+)/i)[1]) + : -1; + + var found; + /** + * Specifies whether the application is running in the Internet Explorer browser, any version. + * @type {Boolean} + */ + this.isIE = b===1;//! + "\v1"; + if (this.isIE) + this.isIE = parseFloat(sAgent.match(/msie ([\d\.]*)/)[1]); + + /** + * Specifies whether the application is running in the Internet Explorer browser version 8. + * @type {Boolean} + */ + this.isIE8 = this.isIE == 8 && (found = true); + + /** + * Specifies whether the application is running in the Internet Explorer browser version 7. + * @type {Boolean} + */ + this.isIE7 = !found && this.isIE == 7 && (found = true); + + //Mode detection + if (document.documentMode == 7) { //this.isIE == 7 && + apf.isIE7 = true; + apf.isIE8 = false; + apf.isIE7Emulate = true; + apf.isIE = 7; + } + + /** + * Specifies whether the application is running in the Internet Explorer browser version 6. + * @type {Boolean} + */ + this.isIE6 = !found && this.isIE == 6 && (found = true); + + var os = (navigator.platform.match(/mac|win|linux/i) || ["other"])[0].toLowerCase(); + /** + * Specifies whether the application is running on the Windows operating system. + * @type {Boolean} + */ + this.isWin = (os == "win"); + /** + * Specifies whether the application is running in the OSX operating system.. + * @type {Boolean} + */ + this.isMac = (os == "mac"); + /** + * Specifies whether the application is running in the OSX operating system.. + * @type {Boolean} + */ + this.isLinux = (os == "linux"); + + + + + try { + //this.isDeskrun = window.external.shell.runtime == 2; + } + catch(e) { + /** + * Specifies whether the application is running in the Deskrun runtime. + * @type {Boolean} + */ + this.isDeskrun = false; + } + + }, + + /** + * @private + */ + setCompatFlags : function(){ + //Set Compatibility + this.TAGNAME = apf.isIE ? "baseName" : "localName"; + this.styleSheetRules = apf.isIE ? "rules" : "cssRules"; + this.brokenHttpAbort = apf.isIE6; + this.canUseHtmlAsXml = apf.isIE; + this.supportNamespaces = !apf.isIE; + this.cannotSizeIframe = apf.isIE; + this.hasConditionCompilation = apf.isIE; + this.supportOverflowComponent = apf.isIE; + this.hasFlexibleBox = apf.versionGecko > 2 || apf.webkitRev > 2; //@todo check this + this.hasEventSrcElement = apf.isIE; + this.canHaveHtmlOverSelects = !apf.isIE6 && !apf.isIE5; + this.hasInnerText = apf.isIE; + this.hasMsRangeObject = apf.isIE; + this.descPropJs = apf.isIE; + this.hasClickFastBug = apf.isIE; + this.hasExecScript = window.execScript ? true : false; + this.canDisableKeyCodes = apf.isIE; + this.hasTextNodeWhiteSpaceBug = apf.isIE || apf.isIE >= 8; + this.hasCssUpdateScrollbarBug = apf.isIE; + this.canUseInnerHtmlWithTables = !apf.isIE; + this.hasSingleResizeEvent = !apf.isIE; + this.hasStyleFilters = apf.isIE; + this.supportOpacity = !apf.isIE; + this.supportPng24 = !apf.isIE6 && !apf.isIE5; + this.cantParseXmlDefinition = apf.isIE50; + this.hasDynamicItemList = !apf.isIE || apf.isIE >= 7; + this.canImportNode = apf.isIE; + this.hasSingleRszEvent = !apf.isIE; + this.hasXPathHtmlSupport = !apf.isIE; + this.hasFocusBug = apf.isIE; + this.hasHeightAutoDrawBug = apf.isIE && apf.isIE < 8; + //this.hasIndexOfNodeList = !apf.isIE; + this.hasReadyStateBug = apf.isIE50; + this.dateSeparator = apf.isIE ? "-" : "/"; + this.canCreateStyleNode = !apf.isIE; + this.supportFixedPosition = !apf.isIE || apf.isIE >= 7; + this.hasHtmlIdsInJs = apf.isIE && apf.isIE < 8 || apf.isWebkit; + this.needsCssPx = !apf.isIE; + this.hasCSSChildOfSelector = !apf.isIE || apf.isIE >= 8; + this.hasStyleAnchors = !apf.isIE || apf.isIE >= 8; + this.styleAttrIsObj = apf.isIE < 8; + this.hasAutocompleteXulBug = apf.isGecko; + this.loadsLocalFilesSync = apf.isIE || apf.isGecko; + this.mouseEventBuffer = apf.isIE ? 20 : 6; + this.hasComputedStyle = typeof document.defaultView != "undefined" + && typeof document.defaultView.getComputedStyle != "undefined"; + this.w3cRange = Boolean(window["getSelection"]); + this.locale = (apf.isIE + ? navigator.userLanguage + : navigator.language).toLowerCase(); + this.characterSet = document.characterSet || document.defaultCharset || "utf-8"; + var t = document.createElement("div"); + this.hasContentEditable = (typeof t.contentEditable == "string" + || typeof t.contentEditable == "boolean"); + apf.hasContentEditableContainerBug = apf.isWebkit; + // Try transform first for forward compatibility + var props = ["transform", "OTransform", "KhtmlTransform", "MozTransform", "WebkitTransform"], + prefixR = ["", "O", "Khtml", "Moz", "Webkit"], + prefixC = ["", "o-", "khtml-", "moz-", "webkit-"], + events = ["transitionend", "transitionend", "transitionend", "transitionend", "webkitTransitionEnd"], + i = 0, + l = 5; + this.supportCSSAnim = false; + this.supportCSSTransition = false; + for (; i < l && !this.supportCSSAnim; ++i) { + if (typeof t.style[props[i]] == "undefined") continue; + this.supportCSSAnim = props[i]; + this.runtimeStylePrefix = prefixR[i]; + this.classNamePrefix = prefixC[i]; + this.cssAnimEvent = events[i]; + } + t = null; + delete t; + + this.supportVML = apf.isIE; + this.supportSVG = !apf.isIE || apf.isIE > 8; + this.hasHtml5XDomain = apf.versionGecko >= 3.5; + this.supportCanvas = !!document.createElement("canvas").getContext; + this.supportCanvasText = !!(this.supportCanvas + && typeof document.createElement("canvas").getContext("2d").fillText == "function") + + this.hasVideo = !!document.createElement("video")["canPlayType"]; + this.hasAudio = !!document.createElement("audio")["canPlayType"]; + this.supportHashChange = ("onhashchange" in self) && (!apf.isIE || apf.isIE >= 8); + + if (self.XMLHttpRequest) { + var xhr = new XMLHttpRequest(); + this.hasXhrProgress = !!xhr.upload; + if (this.hasXhrBinary = !!(xhr.sendAsBinary || xhr.upload)) { + this.hasHtml5File = !!(File && File.prototype.getAsDataURL); + this.hasHtml5FileSlice = !!(File && File.prototype.slice); + } + } + else { + this.hasXhrProgress = this.hasXhrBinary = this.hasHtml5File + = this.hasHtml5FileSlice = false; + } + + this.windowHorBorder = + this.windowVerBorder = apf.isIE8 && (!self.frameElement + || parseInt(self.frameElement.frameBorder)) ? 4 : 0; + + + // Run through HTML5's new input types to see if the UA understands any. + // This is put behind the tests runloop because it doesn't return a + // true/false like all the other tests; instead, it returns an array + // containing properties that represent the 'supported' input types. + t = document.createElement("input"); + var _self = this; + (function(props) { + for (var i in props) { + t.setAttribute("type", i); + _self["hasInput" + i.charAt(0).toUpperCase() + + i.substr(1).replace("-l", "L")] = !!(t.type !== "text"); + } + })({"search":1, "tel":1, "url":1, "email":1, "datetime":1, "date":1, + "month":1, "week":1, "time":1, "datetime-local":1, "number":1, + "range":1, "color":1}); + t = null; + delete t; + + + this.enableAnim = !apf.isIE || apf.isIE > 8; + this.animSteps = apf.isIE ? 0.3 : 1; + this.animInterval = apf.isIE ? 7 : 1; + + this.CSSFLOAT = apf.isIE ? "styleFloat" : "cssFloat"; + this.CSSPREFIX = apf.isGecko ? "Moz" : (apf.isWebkit ? "webkit" : ""); + this.CSSPREFIX2 = apf.isGecko ? "-moz" : (apf.isWebkit ? "-webkit" : ""); + this.INLINE = apf.isIE && apf.isIE < 8 ? "inline" : "inline-block"; + this.needZoomForLayout = apf.isIE && apf.isIE < 8; + + //Other settings + this.maxHttpRetries = apf.isOpera ? 0 : 3; + + + this.percentageMatch = new RegExp(); + this.percentageMatch.compile("([\\-\\d\\.]+)\\%", "g"); + + + this.reMatchXpath = new RegExp(); + this.reMatchXpath.compile("(^|\\|)(?!\\@|[\\w-]+::)", "g"); + + + apf.isGears = !!apf.initGears() || 0; + + }, + + hasGeoLocation: function() { + + return typeof apf.geolocation != "undefined" && apf.geolocation.init(); + + }, + + + /** + * Restarts the application. + */ + reboot : function(){ + apf.console.info("Restarting application..."); + + location.href = location.href; + }, + + + /** + * Extends an object with one or more other objects by copying all their + * properties. + * @param {Object} dest the destination object. + * @param {Object} src the object that is copies from. + * @return {Object} the destination object. + */ + extend : function(dest, src){ + var prop, i, x = !dest.notNull; + if (arguments.length == 2) { + for (prop in src) { + if (x || src[prop]) + dest[prop] = src[prop]; + } + return dest; + } + + for (i = 1; i < arguments.length; i++) { + src = arguments[i]; + for (prop in src) { + if (x || src[prop]) + dest[prop] = src[prop]; + } + } + return dest; + }, + + $extend : function(dest, src){ + for (var prop in src) { + dest[prop] = src[prop]; + } + return dest; + }, + + + /** + * Sends and retrieves data from remote locations over http. + * Example: + * + * var content = apf.ajax("http://www.ajax.org", { + * method : "POST", + * data : "", + * async : false, + * callback : function( data, state ) { + * if (state == apf.SUCCESS) + * alert("Success"); + * else + * alert("Failure") + * } + * }); + * alert(content); + * + * + * @param {String} url the url that is accessed. + * @param {Object} options the options for the http request + * Properties: + * {Boolean} async whether the request is sent asynchronously. Defaults to true. + * {mixed} userdata custom data that is available to the callback function. + * {String} method the request method (POST|GET|PUT|DELETE). Defaults to GET. + * {Boolean} nocache whether browser caching is prevented. + * {String} data the data sent in the body of the message. + * {Boolean} useXML whether the result should be interpreted as xml. + * {Boolean} autoroute whether the request can fallback to a server proxy. + * {Boolean} caching whether the request should use internal caching. + * {Boolean} ignoreOffline whether to ignore offline catching. + * {String} contentType the mime type of the message + * {Function} callback the handler that gets called whenever the + * request completes succesfully or with an error, + * or when the request times out. + */ + ajax : (function(){ + var f = function(){ + return this.oHttp.get.apply(this.oHttp, arguments); + }; + + f.exec = function(method, args, callback, options){ + if (method == "ajax" && args[0]) { + var opt = args[1] || {}; + return this.oHttp.exec(opt.method || "GET", [args[0]], + opt.callback, apf.extend(options || {}, opt)); + } + }; + + return f; + })(), + + + /** + * Starts the application. + * @private + */ + start : function(){ + this.started = true; + var sHref = location.href.split("#")[0].split("?")[0]; + + //Set Variables + this.host = location.hostname && sHref.replace(/(\/\/[^\/]*)\/.*$/, "$1"); + this.hostPath = sHref.replace(/\/[^\/]*$/, "") + "/"; + + + apf.console.info("Starting Ajax.org Platform Application..."); + apf.console.warn("Debug build of Ajax.org Platform " + (apf.VERSION ? "version " + apf.VERSION : "")); + + + //mozilla root detection + //try{ISROOT = !window.opener || !window.opener.apf}catch(e){ISROOT = true} + + //Browser Specific Stuff + //this.browserDetect(); + this.setCompatFlags(); + + if (apf.onstart && apf.onstart() === false) + return false; + + + apf.$debugwin.start(); + + + //Load Browser Specific Code + + if (this.isIE) apf.runIE(); + //this.importClass(apf.runIE, true, self); + + + if (apf.isWebkit) apf.runWebkit(); + //this.importClass(apf.runSafari, true, self); + + + if (this.isOpera) apf.runOpera(); + //this.importClass(apf.runOpera, true, self); + + + if (this.isGecko || !this.isIE && !apf.isWebkit && !this.isOpera) + apf.runGecko(); + //this.importClass(apf.runGecko, true, self); + + + + for (var i, l2, a, m, n, o, v, p = location.href.split(/[?&]/), l = p.length, k = 1; k < l; k++) { + if (m = p[k].match(/(.*?)(\..*?|\[.*?\])?=([^#]*)/)) { + n = decodeURI(m[1]).toLowerCase(), o = this._GET; + if (m[2]) { + for (a = decodeURI(m[2]).replace(/\[\s*\]/g, "[-1]").split(/[\.\[\]]/), i = 0, l2 = a.length; i < l2; i++) { + v = a[i], + o = o[n] + ? o[n] + : o[n] = (parseInt(v) == v) + ? [] + : {}, + n = v.replace(/^["\'](.*)["\']$/, "$1"); + } + } + o[n != "-1" ? n : o.length] = unescape(decodeURI(m[3])); + } + } + + + + // Start HTTP object + this.oHttp = new this.http(); + + + + // Load user defined includes + this.Init.addConditional(this.parseAppMarkup, apf, ["body"]); + //@todo, as an experiment I removed 'HTTP' and 'Teleport' + + + //IE fix + try { + if (apf.isIE) + document.execCommand("BackgroundImageCache", false, true); + } + catch(e) {} + + + //apf.window.init(); + + + this.started = true; + + + // DOMReady already fired, so plz continue the loading and parsing + if (this.load_done) + this.execDeferred(); + + + //try{apf.root = !window.opener || !window.opener.apf;} + //catch(e){apf.root = false} + this.root = true; + + + for (var i = 0; i < apf.$required.length; i++) { + apf.include(apf.$required[i]); + } + apf.require = apf.include; + + + + + }, + + nsqueue : {}, + + + /** + * @private + */ + findPrefix : function(xmlNode, xmlns){ + var docEl; + if (xmlNode.nodeType == 9) { + if (!xmlNode.documentElement) + return false; + if (xmlNode.documentElement.namespaceURI == xmlns) + return xmlNode.prefix || xmlNode.scopeName; + docEl = xmlNode.documentElement; + } + else { + if (xmlNode.namespaceURI == xmlns) + return xmlNode.prefix || xmlNode.scopeName; + docEl = xmlNode.ownerDocument.documentElement; + if (docEl && docEl.namespaceURI == xmlns) + return xmlNode.prefix || xmlNode.scopeName; + + while (xmlNode.parentNode) { + xmlNode = xmlNode.parentNode; + if (xmlNode.namespaceURI == xmlns) + return xmlNode.prefix || xmlNode.scopeName; + } + } + + if (docEl) { + for (var i = 0; i < docEl.attributes.length; i++) { + if (docEl.attributes[i].nodeValue == xmlns) + return docEl.attributes[i][apf.TAGNAME] + } + } + + return false; + }, + + + /** + * @private + */ + importClass : function(ref, strip, win){ + if (!ref) + throw new Error(apf.formatErrorString(1018, null, + "importing class", + "Could not load reference. Reference is null")); + + if (!strip) + return apf.jsexec(ref.toString(), win); + + var q = ref.toString().replace(/^\s*function\s*\w*\s*\([^\)]*\)\s*\{/, "") + .replace(/\}\s*$/, ""); + + return apf.jsexec(q, win); + }, + + /** + * This method returns a string representation of the object + * @return {String} Returns a string representing the object. + */ + toString : function(){ + return "[Ajax.org Platform (apf)]"; + }, + + all : [], + + /** + * This method implements all properties and methods to this object from another class + * @param {Function} classRef Class reference + * @private + */ + implement : function(classRef) { + // for speed, we check for the most common case first + if (arguments.length == 1) { + + if (!classRef) { + throw new Error(apf.formatErrorString(0, this, + "Implementing class", + "Could not implement from '" + classRef[i] + "'", this)); + } + + classRef.call(this);//classRef + } + else { + for (var a, i = 0, l = arguments.length; i < l; i++) { + a = arguments[i]; + + if (!a) { + throw new Error(apf.formatErrorString(0, this, + "Implementing class", + "Could not implement from '" + arguments[i] + "'", this)); + } + + arguments[i].call(this);//classRef + } + } + + return this; + }, + + /** + * @private + */ + uniqueHtmlIds : 0, + + /** + * Adds a unique id attribute to an html element. + * @param {HTMLElement} oHtml the object getting the attribute. + */ + setUniqueHtmlId : function(oHtml){ + var id; + oHtml.setAttribute("id", id = "q" + this.uniqueHtmlIds++); + return id; + }, + + /** + * Retrieves a new unique id + */ + getUniqueId : function(){ + return this.uniqueHtmlIds++; + }, + + /** + * Finds a aml element based on it's uniqueId + */ + lookup : function(uniqueId){ + return this.all[uniqueId]; + }, + + /** + * Searches in the html tree from a certain point to find the + * aml element that is responsible for rendering the specified html + * element. + * @param {HTMLElement} oHtml the html context to start the search from. + */ + findHost : function(o){ + while (o && o.parentNode) { //!o.host && + try { + if ((o.host || o.host === false) && typeof o.host != "string") + return o.host; + } + catch(e){} + + o = o.parentNode; + } + + return null; + }, + + /** + * Sets a reference to an object by name in the global javascript space. + * @param {String} name the name of the reference. + * @param {mixed} o the reference to the object subject to the reference. + */ + setReference : function(name, o){ + return self[name] && self[name].hasFeature + ? 0 + : (self[name] = o); + }, + + /** + * The console outputs to the debug screen and offers differents ways to do + * this. + */ + console : { + + /** + * @private + */ + data : { + time : { + messages : {} + }, + + log : { + messages : {} + }, + + custom : { + messages : {} + }, + + warn : { + messages : {} + }, + + error : { + messages : {} + }, + + repeat : { + messages : {} + } + }, + + /** + * @private + */ + toggle : function(node, id){ + var sPath = apf.$debugwin ? apf.$debugwin.resPath : apf.basePath + "core/debug/resources/"; + if (node.style.display == "block") { + node.style.display = "none"; + node.parentNode.style.backgroundImage = "url(" + sPath + "splus.gif)"; + node.innerHTML = ""; + } + else { + node.style.display = "block"; + node.parentNode.style.backgroundImage = "url(" + sPath + "smin.gif)"; + node.innerHTML = this.cache[id] + .replace(/\&/g, "&") + .replace(/\t/g,"   ") + .replace(/ /g," ") + .replace(/\"); + + var p = node.parentNode.parentNode.parentNode, + el = node.parentNode.parentNode; + if(p.scrollTop + p.offsetHeight < el.offsetTop + el.offsetHeight) + p.scrollTop = el.offsetTop + el.offsetHeight - p.offsetHeight; + } + }, + + cache : [], + history : [], + typeLut : {time: "log", repeat: "log"}, + $lastmsg : "", + $lastmsgcount : 0, + + $detectSameMessage : function(){ + apf.console.$lastmsg = ""; + if (apf.console.$lastmsgcount) { + var msg = apf.console.$lastmsgcount + " times the same message"; + apf.console.$lastmsgcount = 0; + apf.console.write(msg, "repeat"); + clearTimeout(apf.console.$timer); + } + }, + + teleportList : [], + teleport : function(log){ + if (this.teleportModel) + log.setXml(this.teleportModel.data); + + this.teleportList.push(log); + }, + setTeleportModel : function(mdl){ + if (this.teleportModel == mdl) + return; + + this.teleportModel = mdl; + var xml = apf.getXml(""); + for (var i = 0; i < this.teleportList.length; i++) { + this.teleportList[i].setXml(xml); + } + + mdl.load(xml); + }, + + /** + * @private + * @event debug Fires when a message is sent to the console. + * object: + * {String} message the content of the message. + */ + write : function(msg, type, subtype, data, forceWin, nodate){ + clearTimeout(this.$timer); + if (msg == this.$lastmsg) { + this.$lastmsgcount++; + this.$timer = $setTimeout(this.$detectSameMessage, 1000); + return; + } + + this.$detectSameMessage(); + this.$lastmsg = msg; + this.$timer = $setTimeout(this.$detectSameMessage, 1000); + + //if (!apf.debug) return; + if (!Number.prototype.toPrettyDigit) { + Number.prototype.toPrettyDigit = function() { + var n = this.toString(); + return (n.length == 1) ? "0" + n : n; + } + } + + var dt = new Date(), + ms = String(dt.getMilliseconds()); + while (ms.length < 3) + ms += "0"; + var date = dt.getHours().toPrettyDigit() + ":" + + dt.getMinutes().toPrettyDigit() + ":" + + dt.getSeconds().toPrettyDigit() + "." + ms; + + msg = (!nodate ? "[" + date + "] " : "") + + String(msg) + .replace(/(<[^>]+>)| /g, function(m, tag, sp){ + if (tag) return tag; + return " "; + }) + //.replace(/\n/g, "\n
") + .replace(/\t/g,"   "); + var sPath = apf.$debugwin && apf.$debugwin.resPath + ? apf.$debugwin.resPath + : apf.basePath + "core/debug/resources/"; + + if (data) { + msg += "
" + + "More information" + + "
" + + "
"; + } + + msg = "
" + msg + "
"; //\n
+ + //deprecated + if (!subtype) + subtype = "default"; + + this.history.push([this.typeLut[type] || type, msg]); + + if (this.win && !this.win.closed) + this.showWindow(msg); + + //if (apf.debugFilter.match(new RegExp("!" + subtype + "(\||$)", "i"))) + // return; + + this.debugInfo.push(msg); + + if (self.console && (!document.all || apf.config.debug)) { + console[type == "warn" ? "warn" : + (type == "error" ? "error" : "log")] + (apf.html_entity_decode(msg.replace(/<[^>]*>/g, ""))); + } + + if (apf.dispatchEvent) + apf.dispatchEvent("debug", {message: msg, type: type}); + }, + + clear : function(){ + this.history = []; + }, + + getAll : function(err, wrn, log) { + var hash = {"error": err, "warn": wrn, "log": log, "custom": 1}; + var out = []; + for (var i = 0, l = this.history.length; i < l; i++) { + if (hash[this.history[i][0]]) + out.push(this.history[i][1]); + } + return out.join(""); + }, + + + /** + * Writes a message to the console. + * @param {String} msg the message to display in the console. + * @param {String} subtype the category for this message. This is used for filtering the messages. + * @param {String} data extra data that might help in debugging. + */ + debug : function(msg, subtype, data){ + + this.write(msg, "time", subtype, data); + + }, + + /** + * Writes a message to the console with the time icon next to it. + * @param {String} msg the message to display in the console. + * @param {String} subtype the category for this message. This is used for filtering the messages. + * @param {String} data extra data that might help in debugging. + */ + time : function(msg, subtype, data){ + + this.write(msg, "time", subtype, data); + + }, + + /** + * Writes a message to the console. + * @param {String} msg the message to display in the console. + * @param {String} subtype the category for this message. This is used for filtering the messages. + * @param {String} data extra data that might help in debugging. + */ + log : function(msg, subtype, data){ + + this.write(apf.htmlentities(msg).replace(/\n/g, "
"), "log", subtype, data); + + }, + + /** + * Writes a message to the console with the visual "info" icon and color + * coding. + * @param {String} msg the message to display in the console. + * @param {String} subtype the category for this message. This is used for filtering the messages. + * @param {String} data extra data that might help in debugging. + */ + info : function(msg, subtype, data){ + + this.log(apf.htmlentities(msg).replace(/\n/g, "
"), subtype, data); + + }, + + /** + * Writes a message to the console with the visual "warning" icon and + * color coding. + * @param {String} msg the message to display in the console. + * @param {String} subtype the category for this message. This is used for filtering the messages. + * @param {String} data extra data that might help in debugging. + */ + warn : function(msg, subtype, data){ + + this.write(apf.htmlentities(msg).replace(/\n/g, "
"), "warn", subtype, data); + + }, + + /** + * Writes a message to the console with the visual "error" icon and + * color coding. + * @param {String} msg the message to display in the console. + * @param {String} subtype the category for this message. This is used for filtering the messages. + * @param {String} data extra data that might help in debugging. + */ + error : function(msg, subtype, data){ + + this.write(msg.replace(/\n/g, "
"), "error", subtype, data); + + }, + + /** + * Prints a listing of all properties of the object. + * @param {mixed} obj the object for which the properties are displayed. + */ + dir : function(obj){ + var s = apf.$debugwin.$serializeObject(obj, "Inspected via apf.console.dir"); + if (typeof s == "string") { + this.write(s, "custom", null, null, null, true); + } + else { + this.write(obj + ? "Could not serialize object: " + s.message + : obj, "error", null, null, null, true); + } + + //this.info(apf.vardump(obj, null, false).replace(/ /g, " ").replace(/' + + ''); + } + if (!this.win) { + if (!this.haspopupkiller) + alert("Could not open debug window, please check your popupkiller"); + this.haspopupkiller = true; + } + else { + this.win.document.write((msg || this.debugInfo.join("")) + .replace(/\{imgpath\}/g, apf.debugwin + ? apf.debugwin.resPath + : apf.basePath + "core/debug/resources/")); + } + } + + + }, + + html_entity_decode : function(s){return s}, + htmlentities : function(s){return s}, + + /** + * Formats a Ajax.org Platform error message. + * @param {Number} number the number of the error. This can be used to look up more information about the error. + * @param {AMLElement} control the aml element that will throw the error. + * @param {String} process the action that was being executed. + * @param {String} message the actual error message. + * @param {XMLElement} amlContext the xml relevant to the error. For instance a piece of Ajax.org Markup Language xml. + */ + formatErrorString : function(number, control, process, message, amlContext, outputname, output){ + + var str = []; + if (amlContext && amlContext.ownerDocument) { + if (amlContext.nodeType == 9) + amlContext = amlContext.documentElement; + + //Determine file context + if (amlContext.ownerDocument.documentElement) { + var file = amlContext.ownerDocument.documentElement.getAttribute("filename"); + if (!file && amlContext.ownerDocument.documentElement.tagName == "html") + file = location.href; + file = file + ? apf.removePathContext(apf.hostPath, file) + : "Unkown filename"; + } + else file = "Unknown filename"; + + //Get serialized version of context + if (apf.$debugwin) + var amlStr = apf.$debugwin.$serializeObject(amlContext); + else + var amlStr = (amlContext.outerHTML || amlContext.xml || amlContext.serialize()) + .replace(/\<\?xml\:namespace prefix = j ns = "http\:\/\/ajax.org\/2005\/aml" \/\>/g, "") + .replace(/xmlns:a="[^"]*"\s*/g, ""); + + //Determine line number + var diff, linenr = 0, w = amlContext.previousSibling + || amlContext.parentNode && amlContext.parentNode.previousSibling; + while (w && w[apf.TAGNAME] != "body") { + diff = (w.outerHTML || w.xml || w.serialize()).split("\n").length; + linenr += diff - 1; + w = w.previousSibling || w.parentNode && w.parentNode.previousSibling; + } + if (w && w[apf.TAGNAME] != "body") + linenr = "unknown"; + else if(amlContext.ownerDocument + && amlContext.ownerDocument.documentElement.tagName == "html") + linenr += apf.lineBodyStart; + + //Grmbl line numbers are wrong when \n's in attribute space + + //Set file and line number + str.push("aml file: [line: " + linenr + "] " + file); + } + + if (control) + str.push("Element: " + + (apf.$debugwin && !apf.isDebugWindow + ? apf.$debugwin.$serializeObject(control) + : "'" + (control.name + || (control.$aml ? control.getAttribute("id") : null) + || "{Anonymous}") + + "' [" + control.tagName + "]")); + if (process) + str.push("Process: " + process.replace(/ +/g, " ")); + if (message) + str.push("Message: [" + number + "] " + message.replace(/ +/g, " ")); + if (outputname) + str.push(outputname + ": " + output); + if (amlContext && amlStr) + str.push("Related Markup: " + amlStr); + + return (apf.lastErrorMessage = str.join("\n")); + + }, + + /* Init */ + + /** + * Returns the directory portion of a url + * @param {String} url the url to retrieve from. + * @return {String} the directory portion of a url. + */ + getDirname : function(url){ + //(?:\w+\:\/\/)? + return ((url || "").match(/^([^#]*\/)[^\/]*(?:$|\#)/) || {})[1]; //Mike will check out how to optimize this line + }, + + /** + * Returns the file portion of a url + * @param {String} url the url to retrieve from. + * @return {String} the file portion of a url. + */ + getFilename : function(url){ + return ((url || "").split("?")[0].match(/(?:\/|^)([^\/]+)$/) || {})[1]; + }, + + /** + * Returns an absolute url based on url. + * @param {String} base the start of the url to which relative url's work. + * @param {String} url the url to transform. + * @return {String} the absolute url. + */ + getAbsolutePath : function(base, url){ + return url && url.charAt(0) == "/" + ? url + : (!url || !base || url.match(/^\w+\:\/\//) ? url : base.replace(/\/$/, "") + "/" + url.replace(/^\//, "")); + }, + + /** + * Loads javascript from a url. + * + * @param {String} sourceFile the url where the javascript is located. + * @param {Boolean} [doBase] check for basePath, otherwise prepend it + * @param {String} [type] set the type of a script tag, for later use + * @type {void} + */ + include : function(sourceFile, doBase, type, text, callback){ + + if (apf.started) + apf.console.info("including js file: " + sourceFile); + + + var sSrc = doBase ? apf.getAbsolutePath(apf.basePath || "", sourceFile) : sourceFile; + var head = document.getElementsByTagName("head")[0],//$("head")[0] + elScript = document.createElement("script"); + //elScript.defer = true; + if (type) + elScript.setAttribute("_apf_type", type); + if (text) { + if (apf.isIE) + window.execScript(text); + else + elScript.text = text; + } + else + elScript.src = sSrc; + head.appendChild(elScript); + + if (callback) + elScript[apf.isIE ? "onreadystatechange" : "onload"] = callback; + + return elScript; + }, + + $required : [], + require : function(){ + var dir = apf.getDirname(location.href), + i = 0, + l = arguments.length; + for (; i < l; i++) + this.$required.push(apf.getAbsolutePath(dir, arguments[i])); + }, + + /** + * @private + */ + Init : { + queue : [], + cond : { + combined : [] + }, + done : {}, + + add : function(func, o){ + if (this.inited) + func.call(o); + else if (func) + this.queue.push([func, o]); + }, + + addConditional : function(func, o, strObj){ + if (typeof strObj != "string") { + if (this.checkCombined(strObj)) + return func.call(o); + this.cond.combined.push([func, o, strObj]); + } + else if (self[strObj]) { + func.call(o); + } + else { + if (!this.cond[strObj]) + this.cond[strObj] = []; + this.cond[strObj].push([func, o]); + + this.checkAllCombined(); + } + }, + + checkAllCombined : function(){ + for (var i = 0; i < this.cond.combined.length; i++) { + if (!this.cond.combined[i]) continue; + + if (this.checkCombined(this.cond.combined[i][2])) { + this.cond.combined[i][0].call(this.cond.combined[i][1]) + this.cond.combined[i] = null; + } + } + }, + + checkCombined : function(arr){ + for (var i = 0; i < arr.length; i++) { + if (!this.done[arr[i]]) + return false; + } + + return true; + }, + + run : function(strObj){ + this.inited = this.done[strObj] = true; + + this.checkAllCombined(); + + var data = strObj ? this.cond[strObj] : this.queue; + if (!data) return; + for (var i = 0; i < data.length; i++) + data[i][0].call(data[i][1]); + } + }, + + + + + /** + * @private + */ + amlParts : [], + + + /** + * Determines the way apf tries to render this application. Set this value + * before apf is starts parsing. + * Possible values: + * 0 auto + * 1 partial + * 11 partial from a comment + * 2 full from serialized document or file fallback + * 21 full from file + * @type {Number} + */ + parseStrategy : 0, + + + /** + * @private + */ + parsePartialAml : function(docElement){ + + apf.console.warn("The aml namespace definition wasn't found " + + "on the root node of this document. We're assuming " + + "you want to load a partial piece of aml embedded " + + "in this document. Starting to search for it now."); + + + var findAml; + if (apf.isIE) { + findAml = function(htmlNode){ + + if (htmlNode.outerHTML.match(/\/>$/)) { + throw new Error("Cannot have self closing elements!\n" + + htmlNode.outerHTML); + } + + + try { + var tags = {"IMG":1,"LINK":1,"META":1,"INPUT":1,"BR":1,"HR":1,"AREA":1,"BASEFONT":1}, + regex = new RegExp(htmlNode.outerHTML.replace(/([\(\)\|\\\.\^\$\{\}\[\]])/g, "\\$1") + + ".*" + htmlNode.tagName), + match = htmlNode.parentNode.outerHTML.replace(/\n/g, "").match(regex), + strXml = match[0] + ">" + .replace(/(\w+)\s*=\s*([^\>="'\s ]+)( |\s|\>|\/\>)/g, "$1=\"$2\"$3") + .replace(/ disabled /g, " disabled='true' ") + .replace(/\]\]\>/g, "]]>") + .replace(/<(\w+)(\s[^>]*[^\/])?>/g, function(m, tag, c){ + if (tags[tag]) { + return "<" + tag + (c||"") + "/>"; + } + else { + return m; + } + }); + } + catch(e) { + + throw new Error(apf.formatErrorString(0, null, + "Parsing inline aml (without xmlns on root node)", + "Could not parse inline aml. This happens when the html" + + "is mangled too much by Internet Explorer. Either you " + + "are using a cdata section or javascript containing " + + "symbols that throw off the browser. Please put this aml " + + "in a seperate file and load it using an include element.")); + + + return; + } + + var xmlNode = apf.getAmlDocFromString("
" + + strXml + "
").documentElement; + + while (xmlNode.childNodes.length > 1) + xmlNode.removeChild(xmlNode.lastChild); + + apf.AppNode.appendChild(xmlNode); + } + } + else { + findAml = function(htmlNode){ + var strXml = htmlNode.outerHTML.replace(/ _moz-userdefined=""/g, ""), + xmlNode = apf.getAmlDocFromString("
" + + strXml + "
").documentElement; + + while (xmlNode.childNodes.length > 1) + xmlNode.removeChild(xmlNode.lastChild); + + if (apf.isWebkit) + xmlNode = apf.AppNode.ownerDocument.importNode(xmlNode, true); + + apf.AppNode.appendChild(xmlNode); + } + } + + var match = document.body.outerHTML + .match(/(\w+)\s*=\s*["']http:\/\/ajax\.org\/2005\/aml["']/); + if (!match) + return false; + + var strXmlns = "xmlns:" + match[0], + prefix = (RegExp.$1 || "").toUpperCase(); + if (apf.isOpera) + prefix = prefix.toLowerCase(); + if (!prefix) + return false; + + prefix += ":"; + + apf.AppNode = apf.getAmlDocFromString("<" + prefix.toLowerCase() + + "application " + strXmlns + " />").documentElement; + + var temp, loop, cnode, + isPrefix = false, + id = 0, + node = document.body; + while (node) { + isPrefix = node.nodeType == 1 + && node.tagName.substr(0,2) == prefix; + + if (isPrefix) { + findAml(cnode = node); + + if (apf.isIE) { + loop = node; + var count = 1, next = loop.nextSibling; + if (next) { + loop.parentNode.removeChild(loop); + + while (next && (next.nodeType != 1 || next.tagName.indexOf(prefix) > -1)){ + if (next.nodeType == 1) + count += next.tagName.charAt(0) == "/" ? -1 : 1; + + if (count == 0) { + if (temp) + temp.parentNode.removeChild(temp); + temp = next; + break; + } + + next = (loop = next).nextSibling; + if (!next) { + next = loop; + break; + } + if (loop.nodeType == 1) { + loop.parentNode.removeChild(loop); + if (temp) { + temp.parentNode.removeChild(temp); + temp = null; + } + } + else { + if (temp) + temp.parentNode.removeChild(temp); + + temp = loop; + } + } + + node = next; //@todo item should be deleted + //check here for one too far + } + else { + if (temp) + temp.parentNode.removeChild(temp); + temp = loop; + } + } + else { + if (temp) + temp.parentNode.removeChild(temp); + + temp = node; + //node = node.nextSibling; + } + + if (apf.amlParts.length + && apf.amlParts[apf.amlParts.length-1][1] == cnode) + apf.amlParts[apf.amlParts.length-1][1] = -1; + + apf.amlParts.push([node.parentNode, apf.isIE + ? node.nextSibling : node.nextSibling]); + } + else if (node.tagName == "SCRIPT" && node.getAttribute("src") + && (node.getAttribute("src").indexOf("ajax.org") > -1)) { + var strXml = node.outerHTML + .replace(/</g, "<") + .replace(/>/g, ">") + .replace(/&/g, "&") + .replace(/]*\>\s*<\!\[CDATA\[>?/i, "") + .replace(/]*\>(?:<\!\-\-)?/i, "") + .replace(/(\/\/)?\s*\&\#8211;>\s*<\/SCRIPT>/i, "") + .replace(/\-\->\s*<\/SCRIPT>/i, "") + .replace(/\]\](?:\>\;|>)\s*<\/SCRIPT>/i, "") + .replace(/<\/SCRIPT>$/mi, "") + .replace(/<\/?\s*(?:p|br)\s*\/?>/ig, "") + .replace(/<\!--\s*.*?\s*-->\s*" + + strXml + "").documentElement; + + if (apf.isWebkit) + xmlNode = apf.AppNode.ownerDocument.importNode(xmlNode, true); + + apf.AppNode.appendChild(xmlNode); + + apf.amlParts.push([node.parentNode, node.nextSibling]); + } + } + + //Walk entire html tree + if (!isPrefix && node.firstChild + || node.nextSibling) { + if (!isPrefix && node.firstChild) { + node = node.firstChild; + } + else { + node = node.nextSibling; + } + } + else { + do { + node = node.parentNode; + + if (node.tagName == "BODY") + node = null; + + } while (node && !node.nextSibling) + + if (node) { + node = node.nextSibling; + } + } + } + + if (temp) + temp.parentNode.removeChild(temp); + }, + + + /** + * @private + */ + parseAppMarkup : function(docElement){ + var isEmptyDocument = false; + + + if (this.parseStrategy == 1 || !this.parseStrategy && !docElement + && document.documentElement.outerHTML.split(">", 1)[0] + .indexOf(apf.ns.aml) == -1) { + this.parsePartialAml(docElement); + + if (this.parseStrategy == 1 || apf.amlParts.length) { + + if (apf.amlParts.length) + apf.console.warn("Aml found, parsing..."); + + + apf.isParsingPartial = true; + + apf.loadAmlIncludes(apf.AppNode); + + if (!self.ERROR_HAS_OCCURRED) { + apf.initialize(); + } + + return; + } + else { + + apf.console.warn("No aml found."); + + isEmptyDocument = true; + } + } + + + + + + + + if (isEmptyDocument && document.documentElement.outerHTML + .split(">", 1)[0] + .indexOf(apf.ns.aml) == -1) { + + apf.console.warn("The aml namespace declaration wasn't found. " + + "No aml elements were found in the body. Exiting"); + + return false; + } + + //Load current HTML document as 'second DOM' + if (this.parseStrategy == 21 || !this.parseStrategy && !docElement) { + return apf.oHttp.get((apf.alternativeAml + || document.body && document.body.getAttribute("xmlurl") + || location.href).split(/#/)[0], { + + type : "markup", + + callback: function(xmlString, state, extra){ + if (state != apf.SUCCESS) { + var oError = new Error(apf.formatErrorString(0, null, + "Loading XML application data", "Could not load " + + "XML from remote source: " + extra.message)); + + if (extra.tpModule.retryTimeout(extra, state, null, oError) === true) + return true; + + throw oError; + } + + + apf.lineBodyStart = (xmlString.replace(/\n/g, "\\n") + .match(/(.*)]*>/, "") + .replace(/^[\r\n\s]*/, ""); //.replace(/ /g, " ") //should be html2xmlentity conversion + if (!apf.supportNamespaces) + str = str.replace(/xmlns\=\"[^"]*\"/g, ""); + //var xmlNode = apf.getXmlDom(str);//apf.getAmlDocFromString(xmlString); + + if (self.ERROR_HAS_OCCURRED) + return; + + //Clear Body + if (apf.isIE) + document.body.innerHTML =""; + else { + var nodes = document.body.childNodes; + for (var i = nodes.length - 1; i >= 0; i--) + nodes[i].parentNode.removeChild(nodes[i]); + } + + + document.documentElement.style.display = "block"; + document.body.style.display = "block"; //might wanna make this variable based on layout loading... + + + apf.initialize(str); + + }, ignoreOffline: true}); + } + else { + + //might wanna make this variable based on layout loading... + document.body.style.display = "block"; + + + if (!self.ERROR_HAS_OCCURRED) + apf.initialize(docElement.outerHTML || docElement.xml); + } + + }, + + namespaces : {}, + setNamespace : function(namespaceURI, oNamespace){ + this.namespaces[namespaceURI] = oNamespace; + oNamespace.namespaceURI = namespaceURI; + }, + + /** + * @private + */ + initialize : function(xmlStr){ + + if (apf.initialized) return; + apf.initialized = true; + + + + + apf.console.info("Initializing..."); + clearInterval(apf.Init.interval); + + // Run Init + apf.Init.run(); //Process load dependencies + + + + var bodyMarginTop = parseFloat(apf.getStyle(document.body, "marginTop")); + apf.doesNotIncludeMarginInBodyOffset = (document.body.offsetTop !== bodyMarginTop); + + + if (apf.isParsingPartial) { + apf.config.setDefaults(); + apf.hasSingleRszEvent = true; + + var pHtmlNode = document.body; + var lastChild = pHtmlNode.lastChild; + apf.AmlParser.parseMoreAml(apf.AppNode, pHtmlNode, null, + true, false); + + var pNode, firstNode, next, info, + lastBefore = null, + loop = pHtmlNode.lastChild; + while (loop && lastChild != loop) { + info = apf.amlParts[loop.getAttribute("jid")]; + next = loop.previousSibling; + if (info) { + pNode = info[0]; + if ("P".indexOf(pNode.tagName) > -1) { + lastBefore = pNode.parentNode.insertBefore( + apf.getNode(loop, [0]), pNode); + } + else { + firstNode = apf.getNode(loop, [0]); + while(firstNode){ + if (firstNode) { + lastBefore = pNode.insertBefore(firstNode, + typeof info[1] == "number" ? lastBefore : info[1]); + } + else { + lastBefore = typeof info[1] == "number" + ? lastBefore + : info[1]; + } + firstNode = apf.getNode(loop, [0]); + } + } + + loop.parentNode.removeChild(loop); + } + loop = next; + } + + + $setTimeout("apf.layout.forceResize();"); + + } + else + + { + apf.window.init(xmlStr); + } + }, + + + /** + * @private + */ + execDeferred: function() { + // execute each function in the stack in the order they were added + var len = apf.load_events.length; + while (len--) + (apf.load_events.shift())(); + }, + + load_events: [], + load_timer : null, + load_done : false, + load_init : null, + + /** + * @private + */ + addDomLoadEvent: function(func) { + if (!this.$bdetect) + this.browserDetect(); + + if (apf.load_done) + return func(); + + // create event function stack + //apf.done = arguments.callee.done; + if (!apf.load_init) { + apf.load_init = function() { + if (apf.load_done) return; + // kill the timer + clearInterval(apf.load_timer); + apf.load_timer = null; + apf.load_done = true; + if (apf.started) + apf.execDeferred(); + }; + } + + apf.load_events.push(func); + + if (func && apf.load_events.length == 1) { + // Catch cases where addDomLoadEvent() is called after the browser + // event has already occurred. + var doc = document, UNDEF = "undefined"; + if ((typeof doc.readyState != UNDEF && doc.readyState == "complete") + || (doc.getElementsByTagName("body")[0] || doc.body)) + return apf.load_init(); + + // for Mozilla/Opera9. + // Mozilla, Opera (see further below for it) and webkit nightlies + // currently support this event + if (doc.addEventListener && !apf.isOpera) { + // We're using "window" and not "document" here, because it results + // in a memory leak, especially in FF 1.5: + // https://bugzilla.mozilla.org/show_bug.cgi?id=241518 + // See also: + // http://bitstructures.com/2007/11/javascript-method-callbacks + // http://www-128.ibm.com/developerworks/web/library/wa-memleak/ + window.addEventListener("DOMContentLoaded", apf.load_init, false); + } + // If IE is used and is not in a frame + else if (apf.isIE && window == top) { + apf.load_timer = setInterval(function() { + try { + // If IE is used, use the trick by Diego Perini + // http://javascript.nwbox.com/IEContentLoaded/ + doc.documentElement.doScroll("left"); + } + catch(ex) { + $setTimeout(arguments.callee, 0); + return; + } + // no exceptions anymore, so we can call the init! + apf.load_init(); + }, 10); + } + else if (apf.isOpera) { + doc.addEventListener("DOMContentLoaded", function() { + apf.load_timer = setInterval(function() { + for (var i = 0, l = doc.styleSheets.length; i < l; i++) { + if (doc.styleSheets[i].disabled) + return; + } + // all is fine, so we can call the init! + apf.load_init(); + }, 10); + }, false); + } + else if (apf.isWebkit && !apf.isIphone) { + var aSheets = doc.getElementsByTagName("link"), + i = aSheets.length, + iSheets; + for (; i >= 0; i++) { + if (!aSheets[i] || aSheets[i].getAttribute("rel") != "stylesheet") + aSheets.splice(i, 0); + } + iSheets = aSheets.length; + apf.load_timer = setInterval(function() { + if (/loaded|complete/.test(doc.readyState) + && doc.styleSheets.length == iSheets) + apf.load_init(); // call the onload handler + }, 10); + } + // for other browsers set the window.onload, but also execute the + // old window.onload + else { + var old_onload = window.onload; + window.onload = function () { + apf.load_init(); + if (old_onload) + old_onload(); + }; + } + } + }, + + + fireEvent : function(el, type, e, capture){ + if (el.dispatchEvent) + el.dispatchEvent(type, e, capture); + else + el.fireEvent("on" + type, e); + }, + + addListener : function(el, type, fn){ + if (el.addEventListener) + el.addEventListener(type, fn, false); + else if (el.attachEvent) + el.attachEvent("on" + type, fn); + return this; + }, + + removeListener : function(el, type, fn){ + if (el.removeEventListener) + el.removeEventListener(type, fn, false); + else if (el.detachEvent) + el.detachEvent("on" + type, fn); + return this; + }, + + stopEvent: function(e){ + this.stopPropagation(e).preventDefault(e); + return false; + }, + + stopPropagation: function(e){ + if (e.stopPropagation) + e.stopPropagation(); + else + e.cancelBubble = true; + return this; + }, + + preventDefault: function(e){ + if (e.preventDefault) + e.preventDefault(); + else + e.returnValue = false; + return this; + }, + + /* Destroy */ + + /** + * Unloads the aml application. + */ + unload : function(exclude){ + + apf.console.info("Initiating self destruct..."); + + + this.isDestroying = true; + + + this.popup.destroy(); + + + var node, + i = 0, + l = this.all.length; + for (; i < l; i++) { + node = this.all[i]; + if (node && node != exclude && node.destroy && !node.apf) + node.destroy(false); + } + + //this.dispatchEvent("DOMNodeRemovedFromDocument", {});//@todo apf3.0 + + for (i = 0, l = this.availHTTP.length; i < l; i++) + this.availHTTP[i] = null; + + this.availHTTP.length = 0; + + + if (apf.xmldb) + apf.xmldb.unbind(apf.window); + + + this.isDestroying = false; + } +}; + +/* + * Replacement for getElementsByTagNameNS because some browsers don't support + * this call yet. + */ +var $xmlns = function(xmlNode, tag, xmlns, prefix){ + if (!apf.supportNamespaces) { + if (!prefix) + prefix = apf.findPrefix(xmlNode, xmlns); + + if (xmlNode.style || xmlNode == document) + return xmlNode.getElementsByTagName(tag) + else { + if (prefix) + (xmlNode.nodeType == 9 ? xmlNode : xmlNode.ownerDocument) + .setProperty("SelectionNamespaces", + "xmlns:" + prefix + "='" + xmlns + "'"); + + return xmlNode.selectNodes(".//" + (prefix ? prefix + ":" : "") + tag); + } + } + return xmlNode.getElementsByTagNameNS(xmlns, tag); +}; + +var $setTimeout = setTimeout; +var $setInterval = setInterval; + +apf.setTimeout = function(f, t){ + apf.$eventDepth++; + return $setTimeout(function(){ + f(); + + if (--apf.$eventDepth == 0) + apf.queue.empty(); + }, t); +} + +/*$setTimeout = function(f, ms){ + setTimeout(function(){ + console.log(f.toString()); + if (typeof f == "string") eval(f) + else f(); + }, ms); +}*/ + +document.documentElement.className += " has_apf"; +document.documentElement.style.display = "none"; + +apf.browserDetect(); +apf.Init.run("apf"); + + + +apf.getShortestPath = function(p1, p2) { + if (p1.charAt(0) == "/") + return p1; + + var s = p1.split("/"); + var l = p2.split("/"); l.pop(); + + for (var i = 0; i < s.length; i++) { + if (s[0] == "..") { + l.pop(); + s.shift(); + } + else break; + } + + return l.join("/") + "/" + s.join("/"); +} +if (!apf.basePath) { + var snodes = document.getElementsByTagName("script"); + for (var src, i = 0; i < snodes.length; i++) { + src = snodes[i].getAttribute("src"); + if (src && src.match(/^(.*)apf(?:_debug|_release)?\.js$/)) { + apf.basePath = RegExp.$1; + break; + } + } + + apf.basePath = apf.basePath + ? apf.getShortestPath(apf.basePath, apf.getDirname(location.href)) + : "./"; +} + + + + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/core/class.js)SIZE(44597)TIME(Tue, 15 Feb 2011 08:59:13 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + + +/** + * @term propertybinding With property binding you can define the way a + * property is calculated. + * This statement is usually based on a javascript + * expression including one or more properties on other objects. The value of + * the property will always be kept up to date. This means that when one of the + * dependent properties changes, the property is recalculated. See the picture + * for a graphical explanation. + * Example: + * Let me give you an example to make it a bit straightforward. This example + * sets the visibility of the slider based on the state of the checkbox. + * + * + * Toggle this + * + * + * Expressions: + * The use of { and } tell Ajax.org Platform(APF) that the visible property will + * be bound. By specifying myCheckbox.value APF knows that the value of + * myCheckbox should be retrieved for this property. Whenever the checkbox + * changes, the slider will show or hide. + * + * Bidirectional: + * Sometimes it's necessary to make a binding from one property to another one, + * and vice versa. Think of a slider that is connected to the position property + * of a video element. When the video plays, the value of the slider should be + * updated. When the slider is dragged the video should be updated. This works + * in the same way as above, but instead of using curly braces + * you use brackets: [ and ]. The next example keeps the state of a dropdown in + * sync with the state of the tab page. + * + * + * + * + * + * + * Page 1 + * + * + * + * + * For more information visit {@link http://www.rubendaniels.com/2008/07/04/property-binding/ this blog article}.
+ * + * Internals: + * Property binding in apf is a flavor of a {@link http://en.wikipedia.org/wiki/Publish/subscribe publish/subscribe} + * system. When a binding is established the element that receives the value sets + * a listener on the property of another element. There can be any number of + * elements referenced in a single expression. When any of the properties that + * are listened to change, the subscriber gets notified to update the value + * of it's property. + */ + +/** + * @term baseclass A baseclass in Ajax.org Platform (apf) is a class that + * adds properties, methods, attributes, bindings and actions to the class that + * inherits from it. Javascript doesn't have most object oriented concepts like + * classes, class inheritance, interfaces, protected members and so on. When + * using apf you will find that some of these concepts have + * been implemented in a way that enables the core developers of apf to think in + * those concepts. The most important one is class inheritance. Because of the + * freedoms that javascript allows, it is possible to implement + * {@link http://en.wikipedia.org/wiki/Inheritance_(computer_science) inheritance} + * and even {@link http://en.wikipedia.org/wiki/Multiple_inheritance multiple inheritance}. + * + * Usage: + * In apf multiple inheritance is used on all elements to assign specific traits + * to aml elements. Check the list of baseclasses on the right to familiarize + * yourself with the traits that are available (i.e. dragdrop, rename, multiselect, + * databinding, alignment, etc). At the article of each element that inherits + * from a baseclass you will find an inheritance tree on the right. This tree + * will show you from which baseclasses that element has received traits. + * Compared to Java and other strict OOP languages, the inheritance tree is + * inverted. To give an example, in Java for instance, a Lamborghini inherits from + * Car which inherits from Vehicle. In apf Audi inherits from Engine, Wheels, + * Seats and Airco. So we can make the latest Lamborghini inherit from Airco too. + * + * Class: + * The apf.Class baseclass provides all basic features a apf element needs, such + * as event system, property binding and multiple inheritance with state defined + * by each baseclass. + * By setting the prototype of a function to an instance of apf.Class + * these traits are + * transferred to your class. + * + * API: + * The first method is the one that tells an object to implement traits from a + * baseclass. + * It works as follows: + * + * var myClass = function(){ + * this.$init(); + * } + * myClass.prototype = new apf.Class(); + * + * There is a class tree that you can use to create your own elements. For + * instance to create a visible element that uses skinning you can inherit from + * apf.Presentation: + * + * var myElement = function(){ + * this.$init(); + * } + * myElement.prototype = new apf.Presentation(); + * + * Please find a full description of the inheritance tree below. + * + * To check whether an object has inherited from baseclass use the following + * syntax: + * + * myObj.hasFeature(apf.__PRESENTATION__); + * + * Where the constant is the name of the baseclass in all caps. + * + * Apf supports multiple inheritance. Use the implement method to add a + * baseclass to your class that is not part of the inheritance tree: + * + * var myElement = function(){ + * this.$init(); + * + * this.implement(apf.Rename); + * } + * myElement.prototype = new apf.MultiSelect(); + * + * + * Inheritance Tree: + * + * - apf.Class + * - apf.AmlNode + * - apf.AmlElement + * - apf.Teleport + * - apf.GuiElement + * - apf.Presentation + * - apf.BaseTab + * - apf.DataBinding + * - apf.StandardBinding + * - apf.BaseButton + * - apf.BaseSimple + * - apf.Media + * - apf.MultiselectBinding + * - apf.MultiSelect + * - apf.BaseList + * + * Generally elements inherit from AmlElement, Presentation, StandardBinding, + * MultiselectBinding, or one of the leafs. + * + * The following classes are implemented using the implement method: + * + * - apf.Cache + * - apf.ChildValue + * - apf.LiveEdit + * - apf.DataAction + * - apf.Media + * - apf.MultiCheck + * - apf.Rename + * - apf.Xforms + * + * + * The following classes are automatically implemented when needed by apf.GuiElement. + * + * - apf.Anchoring + * - apf.DelayedRender + * - apf.DragDrop + * - apf.Focussable + * - apf.Interactive + * - apf.Transaction + * - apf.Validation + * + * + * The following class is automatically implemented by apf.MultiselectBinding + * + * - apf.VirtualViewport + * + */ + +/** + * All elements that implemented this {@link term.baseclass baseclass} have + * {@link term.propertybinding property binding}, + * event handling and constructor & destructor hooks. The event system is + * implemented following the W3C specification, similar to the + * {@link http://en.wikipedia.org/wiki/DOM_Events event system of the HTML DOM}. + * + * @constructor + * @baseclass + * + * @author Ruben Daniels (ruben AT ajax DOT org) + * @version %I%, %G% + * @since 0.8 + * + * @event propertychange Fires when a property changes. + * object: + * {String} name the name of the changed property + * {Mixed} originalvalue the value it had before the change + * {Mixed} value the value it has after the change + * + */ +apf.Class = function(){}; + +apf.Class.prototype = new (function(){ + // privates + var FUN = "function", + OBJ = "object", + UNDEF = "undefined", + SEL = "model", //selected|selection|properties| + PROP = "prop.", + MODEL = "model", + VALUE = "value"; + + this.$regbase = 0; + /** + * Tests whether this object has implemented a {@link term.baseclass baseclass}. + * @param {Number} test the unique number of the {@link term.baseclass baseclass}. + */ + this.hasFeature = function(test){ + return this.$regbase & test; + }; + + this.$initStack = []; + this.$bufferEvents = []; + this.$init = function(callback, nodeFunc, struct){ + if (typeof callback == FUN || callback === true) { + this.$bufferEvents = this.$bufferEvents.slice(); + + if (callback === true) + return this; + + this.$initStack = this.$initStack.slice(); //Our own private stack + this.$initStack.push(callback); + + return this; + } + + this.addEventListener = realAddEventListener; + //this.$removalQueue = []; + + if (this.nodeType != 2) //small little hack + this.$uniqueId = apf.all.push(this) - 1; + + this.$captureStack = {}; + this.$eventsStack = {}; + this.$funcHandlers = {}; + + var i = 0, l = this.$initStack.length; + for (; i < l; i++) + this.$initStack[i].apply(this, arguments); + + for (i = 0, l = this.$bufferEvents.length; i < l; i++) + this.addEventListener.apply(this, this.$bufferEvents[i]); + + delete realAddEventListener; + delete this.$initStack; + delete this.$bufferEvents; + + if (struct && (struct.htmlNode || this.nodeFunc == apf.NODE_HIDDEN)) { + this.$pHtmlNode = struct.htmlNode; + + + this.ownerDocument.$domParser.$continueParsing(this); + + + apf.queue.empty(); + + + } + + return this; + }; + + this.implement = apf.implement; + + /**** Property Binding ****/ + + this.$handlePropSet = function(prop, value){ + this[prop] = value; + }; + + + + /** + * Bind a property of another compontent to a property of this element. + * + * @param {String} myProp the name of the property of this element + * of which the value is communicated to + * bObject. + * @param {Class} bObject the object which will receive the property + * change message. + * @param {String} bProp the property of bObject which + * will be set using the value of + * myProp optionally + * processed using strDynamicProp. + * @param {String} [strDynamicProp] a javascript statement which contains the + * value of myProp. The string + * is used to calculate a new value. + * @private + */ + this.$bindProperty = function(myProp, bObject, bProp, fParsed, bRecip){ + if (!fParsed) + return bObject.$handlePropSet(bProp, this[myProp]); + + var eventName = PROP + myProp, eFunc, isBeingCalled, isLang; + (this.$eventsStack[eventName] || (this.$eventsStack[eventName] = [])).push(eFunc = function(e){ + if (isBeingCalled) //Prevent circular refs + return; + + + apf.$lm_has_lang = false; + + isBeingCalled = true; + + try { + if (fParsed.asyncs) { //if async + return fParsed.call(bObject, bObject.xmlRoot, function(value){ + bObject.setProperty(bProp, value, true, false, 10); + + + //@todo apf3.0 + if (apf.$lm_has_lang && !isLang) { + isLang = true; + //@todo should auto remove + apf.language.addProperty(bObject, bProp, fParsed); + } + + + isBeingCalled = false; + }); + } + else { + var value = fParsed.call(bObject, bObject.xmlRoot); + } + } + catch(e) { + apf.console.warn("[331] Could not execute binding for property " + + bProp + "\n\n" + e.message); + + isBeingCalled = false; + + return; + } + + //Can't do this when using xml nodes, doesnt seem needed anyway + //if (bObject[bProp] != value) + bObject.setProperty(bProp, value, true, false, 10);//e.initial ? 0 : + + + //@todo apf3.0 + if (apf.$lm_has_lang && !isLang) { + isLang = true; + //@todo should auto remove + apf.language.addProperty(bObject, bProp, fParsed); + } + + + isBeingCalled = false; + }); + + //Bi-directional property binding + if (bRecip) { + eventName = PROP + bProp; + var _self = this; + // add bidirectional binding to funcHandlers for visualconnect + + if (!this.$funcHandlers[bProp]) + this.$funcHandlers[bProp] = []; + + this.$funcHandlers[bProp].push({ + amlNode : bObject, + prop : bProp + }); + + + (bObject.$eventsStack[eventName] || (bObject.$eventsStack[eventName] = [])).push( + eFunc.recip = function(){ + if (isBeingCalled) //Prevent circular refs + return; + + isBeingCalled = true; + _self.setProperty(myProp, bObject[bProp], true, false, 10);//e.initial ? 0 : + isBeingCalled = false; + }); + }; + + //eFunc({initial: true}); + + return eFunc; + }; + + /** + * Sets a dynamic property from a string. + * The string used for this function is the same as used in AML to set a + * dynamic property: + * + * + * + * + * + * @param {String} prop the name of the property of this element to set + * using a dynamic rule. + * @param {String} pValue the dynamic property binding rule. + */ + this.$attrExcludePropBind = false; + this.$setDynamicProperty = function(prop, pValue){ + var exclNr = this.$attrExcludePropBind[prop], + options; + + //@todo apf3.0, please generalize this - cache objects, seems slow + if (SEL.indexOf(prop) > -1 || exclNr == 3) { + options = { + xpathmode : 2 + //parsecode : true //@todo is this also good for exclNr 3 ? + } + } + else if (exclNr == 2) { + options = {nostring : true}; + } + else if (exclNr === 0) { + options = { + parsecode : true + , nothrow : this.target.match(/-debug$/) ? true : false + }; + } + + if (this.liveedit) + (options || (options = {})).liveedit = true; + + + if (apf.config.debugLm) + (options || (options = {})).nothrow = true; + + + //Compile pValue through JSLT parser + + if (pValue && pValue.dataType == apf.FUNCTION) { + var fParsed = pValue; + pValue = ""; + } + else + + { + var fParsed = apf.lm.compile(pValue, options); + } + + //Special case for model due to needed extra signalling + if (prop == MODEL) + (this.$modelParsed = fParsed).instruction = pValue + + else if (exclNr === 0) + this.$lastFParsed = fParsed; + + + //if it's only text return setProperty() + if (fParsed.type == 2) { + this[prop] = !pValue; //@todo apf3.0 is this needed? + return this.setProperty(prop, fParsed.str, null, null, 10); //@todo is 10 here right? + } + + //if there's xpath: Add apf.DataBinding if not inherited. + //Add compiled binding rule. Load databinding if not loaded. + + var check = 1; + if (exclNr == 2 || fParsed.xpaths.length && exclNr != 1) { + if (!this.hasFeature(apf.__DATABINDING__)) { + this.implement(apf.StandardBinding); + if (this.$attrExcludePropBind[prop] == 1) + check = 0; + } + + if (check) + this.$addAttrBind(prop, fParsed, pValue); + } + + + //if there's prop binding: Add generated function to each obj/prop in the list + var matches = exclNr && exclNr != 3 && prop != MODEL ? {} : fParsed.props, //@todo apf3.0 sign of broken abstraction, please fix this with a bit mask + found = false, + _self = this, + o, node, bProp, p; + + for (p in matches) { + + if (typeof matches[p] == FUN) + continue; + + + o = p.split("."); + if (o.length > 2) { //apf.offline.syncing + bProp = o.pop(); + try{ + node = eval(o.join(".")); + } + catch(e){ + if (arguments[2]) { + apf.console.warn("[287] Could not execute binding test : " + + pValue.replace(/ -1) + return; + + s.unshift(callback); + + var f; + if (f = this.$eventsStack["$event." + eventName]) + f[0].call(this, callback); + }; + + /** + * Remove a function registered for an event. + * + * @param {String} eventName the name of the event for which to unregister + * a function. + * @param {function} callback the function to be removed from the event stack. + */ + this.removeEventListener = function(eventName, callback, useCapture){ + var stack = (useCapture ? this.$captureStack : this.$eventsStack)[eventName]; + + //@todo is this the best way? + if (stack) { + if (this.$eventDepth) + stack = (useCapture ? this.$captureStack : this.$eventsStack)[eventName] = stack.slice() + + stack.remove(callback); + if (!stack.length) + delete (useCapture ? this.$captureStack : this.$eventsStack)[eventName]; + } + }; + + /** + * Checks if there is an event listener specified for the event. + * + * @param {String} eventName the name of the event to check. + * @return {Boolean} whether the event has listeners + */ + this.hasEventListener = function(eventName){ + return (this.$eventsStack[eventName] && this.$eventsStack[eventName].length > 0); + }; + + /** + * Destructor of a Class. + * Calls all destructor functions and removes all mem leaking references. + * This function is called when exiting the application or closing the window. + * @param {Boolean} deep whether the children of this element should be destroyed. + * @method + */ + this.destroy = function(deep, clean){ + //Remove from apf.all + if (typeof this.$uniqueId == UNDEF && this.nodeType != 2) + return; + + this.$amlLoaded = false; + this.$amlDestroyed = true; + + if (this.$destroy) + this.$destroy(); + + this.dispatchEvent("DOMNodeRemoved", { + relatedNode : this.parentNode, + bubbles : !apf.isDestroying + }); + this.dispatchEvent("DOMNodeRemovedFromDocument"); + + apf.all[this.$uniqueId] = undefined; + + // != 2 && this.nodeType != 3 + if (!this.nodeFunc && !this.nodeType) { //If this is not a AmlNode, we're done. + //Remove id from global js space + try { + if (this.id || this.name) + self[this.id || this.name] = null; + } + catch (ex) {} + return; + } + + if (this.$ext && !this.$ext.isNative) { // && this.$ext.nodeType == 1 + if (this.nodeType == 1 && this.localName != "a") + this.$ext.oncontextmenu = this.$ext.host = null; + if (clean) { + if (this.localName != "collection") + this.$ext.parentNode.removeChild(this.$ext); + } + } + if (this.$int && !this.$int.isNative && this.$int.nodeType == 1 && this.localName != "a") + this.$int.host = null; + + //if (this.$aml && this.$aml.parentNode) + //this.$aml.parentNode.removeChild(this.$aml); + this.$aml = null; + + //Clear all children too + if (deep && this.childNodes) { + var nodes = this.childNodes; + for (i = nodes.length - 1; i >= 0; i--) { + if (nodes[i].destroy) + nodes[i].destroy(true, clean && this.localName == "collection"); + } + this.childNodes = null; + } + + //Remove from DOM tree if we are still connected + if (this.parentNode && this.removeNode) + this.removeNode(); + else if (this.ownerElement && !this.ownerElement.$amlDestroyed) + this.ownerElement.removeAttributeNode(this); + + //Remove from focus list - Should be in AmlNode + + if (this.$focussable && this.focussable) + apf.window.$removeFocus(this); + + + + //Remove dynamic properties + /*var f, i, l, h; + for (prop in this.$funcHandlers) { + h = this.$funcHandlers[prop]; + + //Remove any bounds if relevant + if (h && typeof h != FUN) { + for (i = 0, l = h.length; i < l; i++) { + (f = h[i]).amlNode.removeEventListener(PROP + f.prop, f.handler); + } + } + }*/ + + + if (this.attributes) { + var attr = this.attributes; + for (var i = attr.length - 1; i >= 0; i--) { + + this.$clearDynamicProperty(attr[i].nodeName); + + attr[i].destroy(); + } + } + + + if (deep !== false && this.childNodes && this.childNodes.length) { + apf.console.warn("You have destroyed an Aml Node without destroying " + + "it's children. Please be aware that if you don't " + + "maintain a reference, memory might leak"); + } + + + //Remove id from global js space + try { + if (this.id || this.name) + self[this.id || this.name] = null; + } + catch (ex) {} + + + apf.nameserver.remove(this.localName, this); + + }; +})(); + +apf.extend(apf, new apf.Class().$init()); +apf.Init.run("class"); + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/core/lib/util/color.js)SIZE(10887)TIME(Mon, 10 Jan 2011 10:53:11 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + + +apf.color = { +/* + colors: { + aliceblue:"#f0f8ff",antiquewhite:"#faebd7",aqua:"#00ffff", + aquamarine:"#7fffd4",azure:"#f0ffff",beige:"#f5f5dc",bisque:"#ffe4c4", + black:"#000000",blanchedalmond:"#ffebcd",blue:"#0000ff", + blueviolet:"#8a2be2",brown:"#a52a2a",burlywood:"#deb887", + cadetblue:"#5f9ea0",chartreuse:"#7fff00",chocolate:"#d2691e", + coral:"#ff7f50",cornflowerblue:"#6495ed",cornsilk:"#fff8dc", + crimson:"#dc143c",cyan:"#00ffff",darkblue:"#00008b",darkcyan:"#008b8b", + darkgoldenrod:"#b8860b",darkgray:"#a9a9a9",darkgrey:"#a9a9a9", + darkgreen:"#006400",darkkhaki:"#bdb76b",darkmagenta:"#8b008b", + darkolivegreen:"#556b2f",darkorange:"#ff8c00",darkorchid:"#9932cc", + darkred:"#8b0000",darksalmon:"#e9967a",darkseagreen:"#8fbc8f", + darkslateblue:"#483d8b",darkslategray:"#2f4f4f", + darkslategrey:"#2f4f4f",darkturquoise:"#00ced1",darkviolet:"#9400d3", + deeppink:"#ff1493",deepskyblue:"#00bfff",dimgray:"#696969", + dimgrey:"#696969",dodgerblue:"#1e90ff",firebrick:"#b22222", + floralwhite:"#fffaf0",forestgreen:"#228b22",fuchsia:"#ff00ff", + gainsboro:"#dcdcdc",ghostwhite:"#f8f8ff",gold:"#ffd700", + goldenrod:"#daa520",gray:"#808080",grey:"#808080",green:"#008000", + greenyellow:"#adff2f",honeydew:"#f0fff0",hotpink:"#ff69b4", + indianred:"#cd5c5c",indigo:"#4b0082",ivory:"#fffff0",khaki:"#f0e68c", + lavender:"#e6e6fa",lavenderblush:"#fff0f5",lawngreen:"#7cfc00", + lemonchiffon:"#fffacd",lightblue:"#add8e6",lightcoral:"#f08080", + lightcyan:"#e0ffff",lightgoldenrodyellow:"#fafad2",lightgray:"#d3d3d3", + lightgrey:"#d3d3d3",lightgreen:"#90ee90",lightpink:"#ffb6c1", + lightsalmon:"#ffa07a",lightseagreen:"#20b2aa",lightskyblue:"#87cefa", + lightslategray:"#778899",lightslategrey:"#778899", + lightsteelblue:"#b0c4de",lightyellow:"#ffffe0",lime:"#00ff00", + limegreen:"#32cd32",linen:"#faf0e6",magenta:"#ff00ff",maroon:"#800000", + mediumaquamarine:"#66cdaa",mediumblue:"#0000cd", + mediumorchid:"#ba55d3",mediumpurple:"#9370d8",mediumseagreen:"#3cb371", + mediumslateblue:"#7b68ee",mediumspringgreen:"#00fa9a", + mediumturquoise:"#48d1cc",mediumvioletred:"#c71585", + midnightblue:"#191970",mintcream:"#f5fffa",mistyrose:"#ffe4e1", + moccasin:"#ffe4b5",navajowhite:"#ffdead",navy:"#000080", + oldlace:"#fdf5e6",olive:"#808000",olivedrab:"#6b8e23",orange:"#ffa500", + orangered:"#ff4500",orchid:"#da70d6",palegoldenrod:"#eee8aa", + palegreen:"#98fb98",paleturquoise:"#afeeee",palevioletred:"#d87093", + papayawhip:"#ffefd5",peachpuff:"#ffdab9",peru:"#cd853f",pink:"#ffc0cb", + plum:"#dda0dd",powderblue:"#b0e0e6",purple:"#800080",red:"#ff0000", + rosybrown:"#bc8f8f",royalblue:"#4169e1",saddlebrown:"#8b4513", + salmon:"#fa8072",sandybrown:"#f4a460",seagreen:"#2e8b57", + seashell:"#fff5ee",sienna:"#a0522d",silver:"#c0c0c0",skyblue:"#87ceeb", + slateblue:"#6a5acd",slategray:"#708090",slategrey:"#708090", + snow:"#fffafa",springgreen:"#00ff7f",steelblue:"#4682b4",tan:"#d2b48c", + teal:"#008080",thistle:"#d8bfd8",tomato:"#ff6347",turquoise:"#40e0d0", + violet:"#ee82ee",wheat:"#f5deb3",white:"#ffffff",whitesmoke:"#f5f5f5", + yellow:"#ffff00",yellowgreen:"#9acd32" + },*/ + colorshex: { + aliceblue:0xf0f8ff,antiquewhite:0xfaebd7,aqua:0x00ffff, + aquamarine:0x7fffd4,azure:0xf0ffff,beige:0xf5f5dc,bisque:0xffe4c4, + black:0x000000,blanchedalmond:0xffebcd,blue:0x0000ff, + blueviolet:0x8a2be2,brown:0xa52a2a,burlywood:0xdeb887, + cadetblue:0x5f9ea0,chartreuse:0x7fff00,chocolate:0xd2691e, + coral:0xff7f50,cornflowerblue:0x6495ed,cornsilk:0xfff8dc, + crimson:0xdc143c,cyan:0x00ffff,darkblue:0x00008b,darkcyan:0x008b8b, + darkgoldenrod:0xb8860b,darkgray:0xa9a9a9,darkgrey:0xa9a9a9, + darkgreen:0x006400,darkkhaki:0xbdb76b,darkmagenta:0x8b008b, + darkolivegreen:0x556b2f,darkorange:0xff8c00,darkorchid:0x9932cc, + darkred:0x8b0000,darksalmon:0xe9967a,darkseagreen:0x8fbc8f, + darkslateblue:0x483d8b,darkslategray:0x2f4f4f, + darkslategrey:0x2f4f4f,darkturquoise:0x00ced1,darkviolet:0x9400d3, + deeppink:0xff1493,deepskyblue:0x00bfff,dimgray:0x696969, + dimgrey:0x696969,dodgerblue:0x1e90ff,firebrick:0xb22222, + floralwhite:0xfffaf0,forestgreen:0x228b22,fuchsia:0xff00ff, + gainsboro:0xdcdcdc,ghostwhite:0xf8f8ff,gold:0xffd700, + goldenrod:0xdaa520,gray:0x808080,grey:0x808080,green:0x008000, + greenyellow:0xadff2f,honeydew:0xf0fff0,hotpink:0xff69b4, + indianred:0xcd5c5c,indigo:0x4b0082,ivory:0xfffff0,khaki:0xf0e68c, + lavender:0xe6e6fa,lavenderblush:0xfff0f5,lawngreen:0x7cfc00, + lemonchiffon:0xfffacd,lightblue:0xadd8e6,lightcoral:0xf08080, + lightcyan:0xe0ffff,lightgoldenrodyellow:0xfafad2,lightgray:0xd3d3d3, + lightgrey:0xd3d3d3,lightgreen:0x90ee90,lightpink:0xffb6c1, + lightsalmon:0xffa07a,lightseagreen:0x20b2aa,lightskyblue:0x87cefa, + lightslategray:0x778899,lightslategrey:0x778899, + lightsteelblue:0xb0c4de,lightyellow:0xffffe0,lime:0x00ff00, + limegreen:0x32cd32,linen:0xfaf0e6,magenta:0xff00ff,maroon:0x800000, + mediumaquamarine:0x66cdaa,mediumblue:0x0000cd, + mediumorchid:0xba55d3,mediumpurple:0x9370d8,mediumseagreen:0x3cb371, + mediumslateblue:0x7b68ee,mediumspringgreen:0x00fa9a, + mediumturquoise:0x48d1cc,mediumvioletred:0xc71585, + midnightblue:0x191970,mintcream:0xf5fffa,mistyrose:0xffe4e1, + moccasin:0xffe4b5,navajowhite:0xffdead,navy:0x000080, + oldlace:0xfdf5e6,olive:0x808000,olivedrab:0x6b8e23,orange:0xffa500, + orangered:0xff4500,orchid:0xda70d6,palegoldenrod:0xeee8aa, + palegreen:0x98fb98,paleturquoise:0xafeeee,palevioletred:0xd87093, + papayawhip:0xffefd5,peachpuff:0xffdab9,peru:0xcd853f,pink:0xffc0cb, + plum:0xdda0dd,powderblue:0xb0e0e6,purple:0x800080,red:0xff0000, + rosybrown:0xbc8f8f,royalblue:0x4169e1,saddlebrown:0x8b4513, + salmon:0xfa8072,sandybrown:0xf4a460,seagreen:0x2e8b57, + seashell:0xfff5ee,sienna:0xa0522d,silver:0xc0c0c0,skyblue:0x87ceeb, + slateblue:0x6a5acd,slategray:0x708090,slategrey:0x708090, + snow:0xfffafa,springgreen:0x00ff7f,steelblue:0x4682b4,tan:0xd2b48c, + teal:0x008080,thistle:0xd8bfd8,tomato:0xff6347,turquoise:0x40e0d0, + violet:0xee82ee,wheat:0xf5deb3,white:0xffffff,whitesmoke:0xf5f5f5, + yellow:0xffff00,yellowgreen:0x9acd32 + }, + fixHSB: function (hsb) { + return { + h: Math.min(360, Math.max(0, hsb.h)), + s: Math.min(100, Math.max(0, hsb.s)), + b: Math.min(100, Math.max(0, hsb.b)) + }; + }, + + fixRGB: function (rgb) { + return { + r: Math.min(255, Math.max(0, rgb.r)), + g: Math.min(255, Math.max(0, rgb.g)), + b: Math.min(255, Math.max(0, rgb.b)) + }; + }, + + fixHex: function (hex) { + var len = 6 - hex.length; + if (len > 0) { + var o = [], i = 0; + for (; i < len; i++) + o.push("0"); + o.push(hex); + hex = o.join(""); + } + return hex; + }, + + hexToRGB: function (hex) { + hex = parseInt(((hex.indexOf("#") > -1) ? hex.substring(1) : hex), 16); + return {r: hex >> 16, g: (hex & 0x00FF00) >> 8, b: (hex & 0x0000FF)}; + }, + + hexToHSB: function (hex) { + return this.RGBToHSB(this.hexToRGB(hex)); + }, + + RGBToHSB: function (rgb) { + var hsb = { + h: 0, + s: 0, + b: 0 + }; + var min = Math.min(rgb.r, rgb.g, rgb.b), + max = Math.max(rgb.r, rgb.g, rgb.b), + delta = max - min; + hsb.b = max; + if (max != 0) { } + hsb.s = max != 0 ? 255 * delta / max : 0; + if (hsb.s != 0) { + if (rgb.r == max) + hsb.h = (rgb.g - rgb.b) / delta; + else if (rgb.g == max) + hsb.h = 2 + (rgb.b - rgb.r) / delta; + else + hsb.h = 4 + (rgb.r - rgb.g) / delta; + } + else + hsb.h = -1; + hsb.h *= 60; + if (hsb.h < 0) + hsb.h += 360; + hsb.s *= 100/255; + hsb.b *= 100/255; + return hsb; + }, + + HSBToRGB: function(hsb) { + var rgb = {}, + h = Math.round(hsb.h), + s = Math.round(hsb.s * 255 / 100), + v = Math.round(hsb.b * 255 / 100); + if (s == 0) + rgb.r = rgb.g = rgb.b = v; + else { + var t1 = v, + t2 = (255 - s) * v / 255, + t3 = (t1 - t2) * (h % 60)/60; + if (h == 360) + h = 0; + if (h < 60) + rgb.r = t1, rgb.b = t2, rgb.g = t2 + t3; + else if (h < 120) + rgb.g = t1, rgb.b = t2, rgb.r = t1 - t3; + else if (h < 180) + rgb.g = t1, rgb.r = t2, rgb.b = t2 + t3; + else if (h < 240) + rgb.b = t1, rgb.r = t2, rgb.g = t1 - t3; + else if (h < 300) + rgb.b = t1, rgb.g = t2, rgb.r = t2 + t3; + else if (h < 360) + rgb.r = t1, rgb.g = t2, rgb.b = t1 - t3; + else + rgb.r = 0, rgb.g = 0, rgb.b = 0; + } + return {r: Math.round(rgb.r), g: Math.round(rgb.g), b: Math.round(rgb.b)}; + }, + + RGBToHex: function(rgb) { + return ('00000'+(rgb.r<<16 | rgb.g<<8 | rgb.b).toString(16)).slice(-6); + }, + + HSBToHex: function(hsb) { + return this.RGBToHex(this.HSBToRGB(hsb)); + } +}; + + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/core/lib/util/abstractevent.js)SIZE(4316)TIME(Mon, 10 Jan 2011 10:53:11 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + + +/** + * @constructor + * @private + */ +apf.AbstractEvent = function(event, win) { + win = win || window; + var doc = win.document; + event = event || win.event; + if (event.$extended) return event; + this.$extended = true; + + this.event = event; + + this.type = event.type; + this.target = event.target || event.srcElement; + while (this.target && this.target.nodeType == 3) + this.target = this.target.parentNode; + + if (this.type.indexOf("key") != -1) { + this.code = event.which || event.keyCode; + /*this.key = apf.AbstractEvent.KEYS.fromCode(this.code); + if (this.type == 'keydown') { + var fKey = this.code - 111; + if (fKey > 0 && fKey < 13) + this.key = 'f' + fKey; + } + this.key = this.key || String.fromCharCode(this.code).toLowerCase();*/ + } + else if (this.type.match(/(click|mouse|menu)/i)) { + doc = (!doc.compatMode || doc.compatMode == 'CSS1Compat') ? doc.html : doc.body; + this.page = { + x: event.pageX || event.clientX + (doc ? doc.scrollLeft : 0), + y: event.pageY || event.clientY + (doc ? doc.scrollTop : 0) + }; + this.client = { + x: (event.pageX) ? event.pageX - win.pageXOffset : event.clientX, + y: (event.pageY) ? event.pageY - win.pageYOffset : event.clientY + }; + if (this.type.match(/DOMMouseScroll|mousewheel/)){ + this.wheel = (event.wheelDelta) ? event.wheelDelta / 120 : -(event.detail || 0) / 3; + } + this.rightClick = (event.which == 3) || (event.button == 2); + this.relatedTarget = null; + if (this.type.match(/over|out/)) { + if (this.type == "mouseover") + this.relatedTarget = event.relatedTarget || event.fromElement; + else if (this.type == "mouseout") + this.relatedTarget = event.relatedTarget || event.toElement; + else { + try { + while (this.relatedTarget && this.relatedTarget.nodeType == 3) + this.relatedTarget = this.relatedTarget.parentNode; + } + catch(e) {} + } + } + } + + this.shift = Boolean(event.shiftKey); + this.control = Boolean(event.ctrlKey); + this.alt = Boolean(event.altKey); + this.meta = Boolean(event.metaKey) + + this.stop = function(){ + return this.stopPropagation().preventDefault(); + }; + + this.stopPropagation = function(){ + if (this.event.stopPropagation) + this.event.stopPropagation(); + else + this.event.cancelBubble = true; + return this; + }; + + this.preventDefault = function(){ + if (this.event.preventDefault) + this.event.preventDefault(); + else + this.event.returnValue = false; + return this; + }; +}; + +apf.AbstractEvent.KEYS = { + 'enter' : 13, + 'up' : 38, + 'down' : 40, + 'left' : 37, + 'right' : 39, + 'esc' : 27, + 'space' : 32, + 'backspace': 8, + 'tab' : 9, + 'delete' : 46, + + fromCode: function(code) { + for (var i in this) { + if (this[i] == code) + return i; + return null; + } + } +}; + +apf.AbstractEvent.stop = function(event) { + return (new apf.AbstractEvent(event)).stop(); +}; + + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/core/lib/util/async.js)SIZE(4124)TIME(Mon, 10 Jan 2011 10:53:11 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + + + +/** + * @author Fabian Jakobs + * @version %I%, %G% + * @since 1.0 + * + * @namespace apf + * + */ + +/** + * Perform an async function in serial on each of the list items + * + * @param {Array} list + * @param {Function} async async function of the form function(item, callback) + * @param {Function} callback function of the form function(error), which is + * called after all items have been processed + */ +apf.asyncForEach = function(list, async, callback) { + var i = 0; + var len = list.length; + + if (!len) return callback(null, []); + + async(list[i], function handler(err) { + if (err) return callback(err); + i++; + + if (i < len) { + async(list[i], handler, i); + } else { + callback(null); + } + }, i); +}; + +/** + * Perform an async function in serial while the function 'condition' (first + * argument) evaluates to true. + * + * @param {Function} condition function that returns a Boolean, which determines + * if the loop should continue + * @param {Function} async async function of the form function(iteration_no, callback) + * @param {Function} callback function of the form function(error), which is + * called after all items have been processed + */ +apf.asyncWhile = function(condition, async, callback) { + var i = 0; + async(i, function handler(err) { + if (err) + return callback ? callback(err, i) : null; + + ++i; + if (condition(i)) + async(i, handler); + else + callback && callback(null, i); + }); +}; + +/** + * Map each element from the list to the result returned by the async mapper + * function. The mapper takes an element from the list and a callback as arguments. + * After completion the mapper has to call the callback with an (optional) error + * object as first and the result of the map as second argument. After all + * list elements have been processed the last callback is called with the mapped + * array as second argument. + * + * @param {Array} list + * @param {Function} mapper function of the form function(item, next) + * @param {Function} callback function of the form function(error, result) + */ +apf.asyncMap = function(list, mapper, callback) { + var i = 0; + var len = list.length; + + if (!len) return callback(null, []); + var map = []; + + async(list[i], function handler(err, value) { + if (err) return callback(err); + + map[i] = value; + i++; + + if (i < len) { + async(list[i], handler); + } else { + callback(null, map); + } + }); +}; + + +/** + * Chains an array of functions. Each of the functions except the last one must + * have excatly one 'callback' argument, which has to be called after the functions has + * finished. If the callback fails if has to pass a non null error object as + * first argument to the callback. + * + * @param {Array} funcs + */ +apf.asyncChain = function(funcs) { + var i = 0; + var len = funcs.length; + + function next() { + var f = funcs[i++]; + if (i == len) + f() + else + f(next) + } + + next(); +} + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/core/lib/util/cookie.js)SIZE(3073)TIME(Mon, 10 Jan 2011 10:53:11 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + + +/** + * Sets a name/value pair which is stored in the browser and sent to the server + * with every request. This is also known as a cookie. Be careful setting + * cookies, because they can take up a lot of bandwidth, especially for Ajax + * applications. + * + * @param {String} name cookie name + * @param {String} value cookie value + * @param {Date} expire expire date representing the number of milliseconds + * since 1 January 1970 00:00:00 UTC. + * @param {String} path path name + * @param {String} domain domain name + * @param {Boolean} secure cookie may benefit all the documents and CGI programs + * meet the requirements as to the path and domain + * compatibility + * Possible values: + * true may benefit + * false can not benefit + * + * @return {String} Returns a cookie name. + */ +apf.setcookie = function(name, value, expire, path, domain, secure) { + var ck = name + "=" + escape(value) + ";"; + if (expire) ck += "expires=" + new Date(expire + + new Date().getTimezoneOffset() * 60).toGMTString() + ";"; + if (path) ck += "path=" + path + ";"; + if (domain) ck += "domain=" + domain + ";"; + if (secure) ck += "secure"; + + document.cookie = ck; + return value +}; + +/** + * Gets the value of a stored name/value pair called a cookie. + * + * @param {String} name the name of the stored cookie. + * @return {String} Returns a value of the cookie or the empty string if it isn't found + */ +apf.getcookie = function(name) { + var aCookie = document.cookie.split("; "); + for (var i = 0; i < aCookie.length; i++) { + var aCrumb = aCookie[i].split("="); + if (name == aCrumb[0]) + return unescape(aCrumb[1]); + } + + return ""; +}; + +/** + * Deletes a stored name/value pair called a cookie. + * + * @param {String} name the name of the stored cookie + * @param {String} domain the name of the domain of stored cookie + */ +apf.delcookie = function (name, domain){ + document.cookie = name + "=blah; expires=Fri, 31 Dec 1999 23:59:59 GMT;" + + (domain ? 'domain='+domain : ''); +}; + + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/core/lib/util/ecmaext.js)SIZE(25941)TIME(Mon, 10 Jan 2011 10:53:11 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + + +// start closure: +//(function(){ + +if (typeof isFinite == "undefined") { + function isFinite(val){ + return val + 1 != val; + } +} + +apf.NUMBER = 1; +apf.BOOLEAN = 2; +apf.STRING = 3; +apf.ARRAY = 4; +apf.DATE = 5; +apf.REGEXP = 6; +apf.FUNCTION = 7; + +Array.prototype.dataType = apf.ARRAY; +Number.prototype.dataType = apf.NUMBER; +Date.prototype.dataType = apf.DATE; +Boolean.prototype.dataType = apf.BOOLEAN; +String.prototype.dataType = apf.STRING; +RegExp.prototype.dataType = apf.REGEXP; +Function.prototype.dataType = apf.FUNCTION; + +/** + * Converts a javascript object to a cgi string. + * @see core.convertXml + */ +apf.getCgiString = function(args, multicall, mcallname){ + var vars = []; + + function recur(o, stack) { + var prop; + if (apf.isArray(o)) { + for (var j = 0; j < o.length; j++) + recur(o[j], stack + "%5B%5D");//" + j + " + } + else if (typeof o == "object") { + for (prop in o) { + if (apf.isSafariOld && (!o[prop] || typeof p[prop] != "object")) + continue; + + if (typeof o[prop] == "function") + continue; + recur(o[prop], stack + "%5B" + encodeURIComponent(prop) + "%5D"); + } + } + else + vars.push(stack + "=" + encodeURIComponent(o)); + }; + + if (multicall) { + vars.push("func=" + mcallname); + for (var i = 0; i < args[0].length; i++) + recur(args[0][i], "f%5B" + i + "%5D"); + } else { + for (prop in args) { + if (apf.isSafariOld && (!args[prop] || typeof args[prop] == "function")) + continue; + + recur(args[prop], prop); + } + } + + return vars.join("&"); +} + +/** + * Converts a cgi string to a javascript object. + * @see core.convertXml + */ +apf.fromCgiString = function(args) { + if (!args) + return false; + + var obj = {}; + args = args.split("&"); + for (var data, i = 0; i < args.length; i++) { + data = args[i].split("="); + data[0] = decodeURIComponent(data[0]); + var path = data[0].replace(/\]/g, "").split("["); + + var spare = obj; + for (var j = 0; j < path.length; j++) { + if (spare[path[j]]) + spare = spare[path[j]]; + else if (path.length == j+1) { + if (path[j]) + spare[path[j]] = decodeURIComponent(data[1]); + else + spare.push(decodeURIComponent(data[1])); + break; //assuming last + } + else{ + spare[path[j]] = !path[j+1] ? [] : {}; + spare = spare[path[j]]; + } + } + } + + return obj; +} + + + + +/** + * Extends a Function object with properties from other objects, specified as + * arguments. + * + * @param {mixed} obj1, obj2, obj3, etc. + * @type Function + * @see apf.extend + */ +Function.prototype.extend = function() { + apf.extend.apply(this, [this].concat(Array.prototype.slice.call(arguments))); + return this; +}; + +/** + * Attach a Function object to an event as handler method. If apf.AbstractEvent + * is available, the active event is extended with convinience accessors as + * declared in apf.AbstractEvent + * + * @param {Object} The context the execute the Function within + * @param {Boolean} Whether the passed event object should be extended with AbstractEvent + * @param {mixed} param1, param2, param3, etc. + * @type Function + * @see apf.AbstractEvent + */ +Function.prototype.bindWithEvent = function() { + var __method = this, + args = Array.prototype.slice.call(arguments), + o = args.shift(), + ev = args.shift(); + return function(event) { + if (!event) + event = window.event; + + if (ev === true) + event = new apf.AbstractEvent(event, window); + + return __method.apply(o, [event].concat(args) + .concat(Array.prototype.slice.call(arguments))); + } +}; + +/** + * The bind function creates a new function (a bound function) that calls the + * function that is its this value (the bound function's target function) with + * a specified this parameter, which cannot be overridden. bind also accepts + * leading default arguments to provide to the target function when the bound + * function is called. A bound function may also be constructed using the new + * operator: doing so acts as though the target function had instead been + * constructed. The provided this value is ignored, while prepended arguments + * are provided to the emulated function. + * + * @param {Object} context The 'this' context of the bound function + * @type Function + */ +if (!Function.prototype.bind) + Function.prototype.bind = function(context /*, arg1, arg2... */) { + if (typeof this !== 'function') throw new TypeError(); + var _arguments = Array.prototype.slice.call(arguments, 1), + _this = this, + _concat = Array.prototype.concat, + _function = function() { + return _this.apply(this instanceof _dummy ? this : context, + _concat.apply(_arguments, arguments)); + }, + _dummy = function() {}; + _dummy.prototype = _this.prototype; + _function.prototype = new _dummy(); + return _function; +}; + +/** + * Copy an array, like this statement would: 'this.concat([])', but then do it + * recursively. + */ +Array.prototype.copy = function(){ + var ar = []; + for (var i = 0, j = this.length; i < j; i++) + ar[i] = this[i] && this[i].copy ? this[i].copy() : this[i]; + + return ar; +}; + +/** + * Concatenate the current Array instance with one (or more) other Arrays, like + * Array.concat(), but return the current Array instead of a new one that + * results from the merge. + * + * @param {Array} array1, array2, array3, etc. + * @type {Array} + */ +Array.prototype.merge = function(){ + for (var i = 0, k = arguments.length; i < k; i++) { + for (var j = 0, l = arguments[i].length; j < l; j++) { + this.push(arguments[i][j]); + } + } +}; + +/** + * Add the values of one or more arrays to the current instance by using the + * '+=' operand on each value. + * + * @param {Array} array1, array2, array3, etc. + * @type {Array} + * @see Array.copy + */ +Array.prototype.arrayAdd = function(){ + var s = this.copy(); + for (var i = 0, k = arguments.length; i < k; i++) { + for (var j = 0, l = s.length; j < l; j++) { + s[j] += arguments[i][j]; + } + } + + return s; +}; + +/** + * Check if an object is contained within the current Array instance. + * + * @param {mixed} obj The value to check for inside the Array + * @type {Boolean} + */ +Array.prototype.equals = function(obj){ + for (var i = 0, j = this.length; i < j; i++) + if (this[i] != obj[i]) + return false; + return true; +}; + +/** + * Make sure that an array instance contains only unique values (NO duplicates). + * + * @type {Array} + */ +Array.prototype.makeUnique = function(){ + var i, length, newArr = []; + for (i = 0, length = this.length; i < length; i++) + if (newArr.indexOf(this[i]) == -1) + newArr.push(this[i]); + + this.length = 0; + for (i = 0, length = newArr.length; i < length; i++) + this.push(newArr[i]); + + return this; +}; + +/** + * Check if this array instance contains a value 'obj'. + * + * @param {mixed} obj The value to check for inside the array + * @param {Number} [from] Left offset index to start the search from + * @type {Boolean} + * @see Array.indexOf + */ +Array.prototype.contains = function(obj, from){ + return this.indexOf(obj, from) != -1; +}; + +/** + * Search for the index of the first occurence of a value 'obj' inside an array + * instance. + * July 29, 2008: added 'from' argument support to indexOf() + * + * @param {mixed} obj The value to search for inside the array + * @param {Number} [from] Left offset index to start the search from + * @type {Number} + */ +Array.prototype.indexOf = Array.prototype.indexOf || function(obj, from){ + var len = this.length; + for (var i = (from < 0) ? Math.max(0, len + from) : from || 0; i < len; i++) { + if (this[i] === obj) + return i; + } + return -1; +}; + +/** + * Search for the index of the last occurence of a value 'obj' inside an array + * instance. + * + * @param {mixed} obj The value to search for inside the array + * @param {Number} [from] Left offset index to start the search from + * @type {Number} + */ +Array.prototype.lastIndexOf = Array.prototype.lastIndexOf || function(obj, from) { + //same as indexOf(), but in reverse loop, JS spec 1.6 + var len = this.length; + for (var i = (from >= len) ? len - 1 : (from < 0) ? from + len : len - 1; i >= 0; i--) { + if (this[i] === obj) + return i; + } + return -1; +}; + +/** + * Like Array.push, but only invoked when the value 'item' is already present + * inside the array instance. + * + * @param {mixed} item, item, ... + * @type {Array} + */ +Array.prototype.pushUnique = function(){ + var item, + i = 0, + l = arguments.length; + for (; i < l; ++i) { + item = arguments[i]; + if (this.indexOf(item) == -1) + this.push(item); + } + return this; +}; + +/** + * @todo: Ruben: could you please comment on this function? Seems to serve a very + * specific purpose... + * + * I also could not find an occurrence in our codebase. + */ +Array.prototype.search = function(){ + for (var i = 0, length = arguments.length; i < length; i++) { + if (typeof this[i] != "array") + continue; + for (var j = 0; j < length; j++) { + if (this[i][j] != arguments[j]) + break; + else if (j == (length - 1)) + return this[i]; + } + } +}; + +/** + * Iterate through each value of an array instance from left to right (front to + * back) and execute a callback Function for each value. + * + * @param {Function} fn + * @type {Array} + */ +Array.prototype.each = +Array.prototype.forEach = Array.prototype.forEach || function(fn) { + for (var i = 0, l = this.length; i < l; i++) + fn.call(this, this[i], i, this) === false + return this; +} + +/** + * Search for a value 'obj' inside an array instance and remove it when found. + * + * @type {mixed} obj + * @type {Array} + */ +Array.prototype.remove = function(obj){ + for (var i = this.length - 1; i >= 0; i--) { + if (this[i] != obj) + continue; + + this.splice(i, 1); + } + + return this; +}; + +/** + * Remove an item from an array instance which can be identified with key 'i' + * + * @param {Number} i + * @return {mixed} The removed item + */ +Array.prototype.removeIndex = function(i){ + if (!this.length) return null; + return this.splice(i, 1); +}; + +/** + * Insert a new value at a specific object; alias for Array.splice. + * + * @param {mixed} obj Value to insert + * @param {Number} i Index to insert 'obj' at + * @type {Number} + */ +Array.prototype.insertIndex = function(obj, i){ + this.splice(i, 0, obj); +}; + +/** + * Reverses the order of the elements of an array; the first becomes the last, + * and the last becomes the first. + * + * @type {Array} + */ +Array.prototype.invert = +Array.prototype.reverse = Array.prototype.reverse || function(){ + var l = this.length - 1; + for (var temp, i = 0; i < Math.ceil(0.5 * l); i++) { + temp = this[i]; + this[i] = this[l - i] + this[l - i] = temp; + } + + return this; +}; + + + +/* + * Attempt to fully comply (in terms of functionality) with the JS specification, + * up 'till version 1.7: + * @link http://developer.mozilla.org/en/docs/Core_JavaScript_1.5_Reference:Global_Objects:Array + */ + +/** + * Creates a new array with all of the elements of this array for which the + * provided filtering function returns true. + * + * @param {Function} fn Function to test each element of the array. + * @param {Object} bind Object to use as this when executing callback. + * @type {Array} + */ +Array.prototype.filter = Array.prototype.filter || function(fn, bind){ + var results = []; + for (var i = 0, l = this.length; i < l; i++) { + if (fn.call(bind, this[i], i, this)) + results.push(this[i]); + } + return results; +}; + +/** + * Returns true if every element in this array satisfies the provided testing + * function. + * + * @param {Function} fn Function to test for each element. + * @param {Object} bind Object to use as this when executing callback. + * @type {Boolean} + */ +Array.prototype.every = Array.prototype.every || function(fn, bind){ + for (var i = 0, l = this.length; i < l; i++) { + if (!fn.call(bind, this[i], i, this)) + return false; + } + return true; +}; + +/** + * Creates a new array with the results of calling a provided function on every + * element in this array. + * + * @param {Function} fn Function that produces an element of the new Array from an element of the current one. + * @param {Object} bind Object to use as this when executing callback. + * @type {Array} + */ +Array.prototype.map = Array.prototype.map || function(fn, bind){ + var results = []; + for (var i = 0, l = this.length; i < l; i++) + results[i] = fn.call(bind, this[i], i, this); + return results; +}; + +/** + * Tests whether some element in the array passes the test implemented by the + * provided function. + * + * @param {Function} fn Function to test for each element. + * @param {Object} bind Object to use as this when executing callback. + * @type {Boolean} + */ +Array.prototype.some = Array.prototype.some || function(fn, bind){ + for (var i = 0, l = this.length; i < l; i++) { + if (fn.call(bind, this[i], i, this)) + return true; + } + return false; +}; + + + +/** + * Transform a number to a string and pad it with a zero digit its length is one. + * + * @type {String} + */ +Number.prototype.toPrettyDigit = Number.prototype.toPrettyDigit || function() { + var n = this.toString(); + return (n.length == 1) ? "0" + n : n; +}; + +RegExp.prototype.getNativeFlags = function() { + return (this.global ? "g" : "") + + (this.ignoreCase ? "i" : "") + + (this.multiline ? "m" : "") + + (this.extended ? "x" : "") + + (this.sticky ? "y" : ""); +}; + +/** + * Accepts flags; returns a new XRegExp object generated by recompiling + * the regex with the additional flags (may include non-native flags). + * the original regex object is not altered. + */ +RegExp.prototype.addFlags = function(flags){ + return new RegExp(this.source, (flags || "") + this.getNativeFlags()); +}; + +/** + * Casts the first character in a string to uppercase. + * + * @type {String} + */ +String.prototype.uCaseFirst = function(){ + return this.substr(0, 1).toUpperCase() + this.substr(1) +}; + +/** + * Removes spaces and other space-like characters from the left and right ends + * of a string + * + * @type {String} + */ +String.prototype.trim = function(){ + return this.replace(/[\s\n\r]*$/, "").replace(/^[\s\n\r]*/, ""); +}; + +/** + * Concatenate a string with itself n-times. + * + * @param {Number} times Number of times to repeat the String concatenation + * @type {String} + */ +String.prototype.repeat = function(times){ + return Array(times + 1).join(this); +}; + +/** + * Count the number of occurences of substring 'str' inside a string + * + * @param {String} str + * @type {Number} + */ +String.prototype.count = function(str){ + return this.split(str).length - 1; +}; + +/** + * Remove HTML or any XML-like tags from a string + * + * @type {String} + */ +String.prototype.stripTags = function() { + return this.replace(/<\/?[^>]+>/gi, ""); +}; + +/** + * Wrapper for the global 'escape' function for strings + * + * @type {String} + */ +String.prototype.escape = function() { + return escape(this); +}; + +/** + * Returns an xml document + * @type {XMLElement} + */ +String.prototype.toXml = function(){ + var node = apf.getXml("" + this + ""); + if (node.childNodes.length == 1) { + return node.childNodes[0]; + } + else { + var docFrag = node.ownerDocument.createDocumentFragment(), + nodes = node.childNodes; + while (nodes.length) + docFrag.appendChild(nodes[0]); + return docFrag; + } +}; + + +if (typeof window != "undefined" && typeof window.document != "undefined" + && typeof window.document.createElement == "function") { + /** + * Encode HTML entities to its HTML equivalents, like '&' to '&amp;' + * and '<' to '&lt;'. + * + * @type {String} + * @todo is this fast? + */ + String.prototype.escapeHTML = function() { + this.escapeHTML.text.data = this; + return this.escapeHTML.div.innerHTML; + }; + + /** + * Decode HTML equivalent entities to characters, like '&amp;' to '&' + * and '&lt;' to '<'. + * + * @type {String} + */ + String.prototype.unescapeHTML = function() { + var div = document.createElement("div"); + div.innerHTML = this.stripTags(); + if (div.childNodes[0]) { + if (div.childNodes.length > 1) { + var out = []; + for (var i = 0; i < div.childNodes.length; i++) + out.push(div.childNodes[i].nodeValue); + return out.join(""); + } + else + return div.childNodes[0].nodeValue; + } + return ""; + }; + + String.prototype.escapeHTML.div = document.createElement("div"); + String.prototype.escapeHTML.text = document.createTextNode(""); + String.prototype.escapeHTML.div.appendChild(String.prototype.escapeHTML.text); + + if ("<\n>".escapeHTML() !== "<\n>") + String.prototype.escapeHTML = null; + + if ("<\n>".unescapeHTML() !== "<\n>") + String.prototype.unescapeHTML = null; +} + +if (!String.prototype.escapeHTML) { + String.prototype.escapeHTML = function() { + return this.replace(/&/g,"&").replace(//g,">"); + }; +} + +if (!String.prototype.unescapeHTML) { + String.prototype.unescapeHTML = function() { + return this.stripTags().replace(/</g,"<").replace(/>/g,">").replace(/&/g,"&"); + }; +} + +/** + * Trim a string down to a specific number of characters. Optionally, append an + * ellipsis ('...') as a suffix. + * + * @param {Number} nr + * @param {Boolean} [ellipsis] Append an ellipsis + * @type {String} + */ +String.prototype.truncate = function(nr, ellipsis){ + return this.length >= nr + ? this.substring(0, nr - (ellipsis ? 4 : 1)) + (ellipsis ? "..." : "") + : this; +}; + +/** + * Pad a string at the right or left end with a string 'pad' to a specific + * number of characters. Highly optimized version for speed, not readability. + * + * @param {Number} len Specifies the amount of characters required to pad to. + * @param {String} pad Specifies the character(s) to pad the string with + * @param {Boolean} [dir] Specifies at which end to append the 'pad' character (left or right). + * @type {String} + */ +String.prototype.pad = function(len, pad, dir) { + return dir ? (this + Array(len).join(pad)).slice(0, len) + : (Array(len).join(pad) + this).slice(-len); +}; + +apf.PAD_LEFT = false; +apf.PAD_RIGHT = true; + +/** + * Special String.split; optionally lowercase a string and trim all results from + * the left and right. + * + * @param {String} separator + * @param {Number} limit Maximum number of items to return + * @param {Boolean} bLowerCase Flag to lowercase the string prior to split + * @type {String} + */ +String.prototype.splitSafe = function(separator, limit, bLowerCase) { + return (bLowerCase && this.toLowerCase() || this) + .replace(/(?:^\s+|\n|\s+$)/g, "") + .split(new RegExp("[\\s ]*" + separator + "[\\s ]*", "g"), limit || 999); +}; + +/** + * Appends a random number with a specified length to this String instance. + * + * @see randomGenerator + * @param {Number} length + * @type {String} + */ +String.prototype.appendRandomNumber = function(length) { + for (var arr = [], i = 1; i <= length; i++) + arr.push(apf.randomGenerator.generate(1, 9)); + // Create a new string from the old one, don't just create a copy + return this.toString() + arr.join(""); +}; + +/** + * Prepends a random number with a specified length to this String instance. + * + * @see randomGenerator + * @param {Number} length + * @type {String} + */ +String.prototype.prependRandomNumber = function(length) { + for (var arr = [], i = 1; i <= length; i++) + arr.push(apf.randomGenerator.generate(1, 9)); + // Create a new string from the old one, don't just create a copy + return arr.join("") + this.toString(); +}; + +/** + * Returns a string produced according to the formatting string. It replaces + * all %s occurrences with the arguments provided. + * + * @link http://www.php.net/sprintf + * @type {String} + */ +String.prototype.sprintf = function() { + // Create a new string from the old one, don't just create a copy + var str = this.toString(), + i = 0, + inx = str.indexOf("%s"); + while (inx >= 0) { + var replacement = arguments[i++] || " "; + str = str.substr(0, inx) + replacement + str.substr(inx + 2); + inx = str.indexOf("%s"); + } + return str; +}; + +/** + * The now method returns the milliseconds elapsed since + * 1 January 1970 00:00:00 UTC up until now as a number. + * + * @type {Number} + */ +if (!Date.now) { + Date.now = function now() { + return +new Date(); + }; +} + +//})(); //end closure + + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/core/lib/util/flash.js)SIZE(22995)TIME(Mon, 10 Jan 2011 10:53:11 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + +/** + * Helper class that aids in creating and controlling Adobe Flash + * elements. + * + * @author Mike de Boer + * @version %I%, %G% + * @since 1.0 + * @namespace apf + * @private + */ +apf.flash = (function(){ + /** + * Flash Player Version Detection, version 1.7 + * Detect Client Browser type + * + * @type {String} + */ + function getControlVersion(){ + var version, axo; + + // NOTE : new ActiveXObject(strFoo) throws an exception if strFoo isn't in the registry + try { + // version will be set for 7.X or greater players + axo = new ActiveXObject("ShockwaveFlash.ShockwaveFlash.7"); + version = axo.GetVariable("$version"); + } + catch (e) {} + + if (!version) { + try { + // version will be set for 6.X players only + axo = new ActiveXObject("ShockwaveFlash.ShockwaveFlash.6"); + // installed player is some revision of 6.0 + // GetVariable("$version") crashes for versions 6.0.22 through 6.0.29, + // so we have to be careful. + // default to the first public version + version = "WIN 6,0,21,0"; + // throws if AllowScripAccess does not exist (introduced in 6.0r47) + axo.AllowScriptAccess = "always"; + // safe to call for 6.0r47 or greater + version = axo.GetVariable("$version"); + } + catch (e) {} + } + + if (!version) { + try { + // version will be set for 4.X or 5.X player + axo = new ActiveXObject("ShockwaveFlash.ShockwaveFlash.3"); + version = axo.GetVariable("$version"); + } + catch (e) {} + } + + if (!version) { + try { + // version will be set for 3.X player + axo = new ActiveXObject("ShockwaveFlash.ShockwaveFlash.3"); + version = "WIN 3,0,18,0"; + } + catch (e) {} + } + + if (!version) { + try { + // version will be set for 2.X player + axo = new ActiveXObject("ShockwaveFlash.ShockwaveFlash"); + version = "WIN 2,0,0,11"; + } + catch (e) { + version = -1; + } + } + + return version; + } + + /** + * JavaScript helper, required to detect Flash Player PlugIn version + * information. + * @see getControlVersion() for Internet Explorer (ActiveX detection) + * + * @type {String} + */ + function getSwfVersion(){ + // NS/Opera version >= 3 check for Flash plugin in plugin array + var flashVer = -1, + sAgent = navigator.userAgent.toLowerCase(); + + if (navigator.plugins != null && navigator.plugins.length > 0) { + if (navigator.plugins["Shockwave Flash 2.0"] || navigator.plugins["Shockwave Flash"]) { + var swVer2 = navigator.plugins["Shockwave Flash 2.0"] ? " 2.0" : "", + swfDescr = navigator.plugins["Shockwave Flash" + swVer2].description, + aDescr = swfDescr.split(" "), + aTempMaj = aDescr[2].split("."), + nMajor = aTempMaj[0], + nMinor = aTempMaj[1], + sRev = aDescr[3]; + if (sRev == "") + sRev = aDescr[4]; + if (sRev[0] == "d") { + sRev = sRev.substring(1); + } + else if (sRev[0] == "r") { + sRev = sRev.substring(1); + if (sRev.indexOf("d") > 0) + sRev = sRev.substring(0, sRev.indexOf("d")); + } + flashVer = nMajor + "." + nMinor + "." + sRev; + } + } + // MSN/WebTV 2.6 supports Flash 4 + else if (sAgent.indexOf("webtv/2.6") != -1) + flashVer = 4; + // WebTV 2.5 supports Flash 3 + else if (sAgent.indexOf("webtv/2.5") != -1) + flashVer = 3; + // older WebTV supports Flash 2 + else if (sAgent.indexOf("webtv") != -1) + flashVer = 2; + else if (apf.isIE && !apf.isOpera) + flashVer = getControlVersion(); + + return flashVer; + } + + /** + * When called with reqMajorVer, reqMinorVer, reqRevision returns true if + * that version or greater is available on the clients' system. + * + * @param {Number} reqMajorVer + * @param {Number} reqMinorVer + * @param {Number} reqRevision + * @type {Boolean} + */ + function detectFlashVersion(reqMajorVer, reqMinorVer, reqRevision){ + var versionStr = getSwfVersion(); + if (versionStr == -1) + return false; + if (versionStr != 0) { + var aVersions; + if (apf.isIE && !apf.isOpera) { + // Given "WIN 2,0,0,11" + var aTemp = versionStr.split(" "), // ["WIN", "2,0,0,11"] + sTemp = aTemp[1]; // "2,0,0,11" + aVersions = sTemp.split(","); // ['2', '0', '0', '11'] + } + else { + aVersions = versionStr.split("."); + } + var nMajor = aVersions[0], + nMinor = aVersions[1], + sRev = aVersions[2]; + + // is the major.revision >= requested major.revision AND the minor version >= requested minor + if (nMajor > parseFloat(reqMajorVer)) + return true; + if (nMajor == parseFloat(reqMajorVer)) { + if (nMinor > parseFloat(reqMinorVer)) + return true; + if (nMinor == parseFloat(reqMinorVer) + && sRev >= parseFloat(reqRevision)) { + return true; + } + } + return false; + } + } + + /** + * Generate ActiveContent for a Flash or Shockwave movie, while ensuring + * compatibility with the clients' browser. + * + * @param {Object} objAttrs + * @param {Object} params + * @param {Object} embedAttrs + * @param {Boolean} stdout If TRUE, the resulting string will be passed to the output buffer through document.write() + * @type {String} + */ + function generateObj(objAttrs, params, embedAttrs, stdout){ + if (stdout == "undefined") + stdout = false; + var i, str = []; + if (apf.isIE && !apf.isOpera) { + str.push(""); + for (i in params) + str.push(""); + str.push(""); + } + else { + str.push(""); + } + var sOut = str.join(""); + + if (stdout === true) + document.write(sOut); + + return sOut; + } + + /** + * Use this function to generate the HTML tags for a Flash movie object. + * It takes any number of arguments as parameters: + * arguments: 'name1', 'value1', 'name2', 'value2', etc. + * + * @type {String} + */ + function AC_FL_RunContent(options){ + var ret = AC_GetArgs(options, + "movie", "clsid:d27cdb6e-ae6d-11cf-96b8-444553540000", + "application/x-shockwave-flash"); + return generateObj(ret.objAttrs, ret.params, ret.embedAttrs); + } + + /** + * Generate the HTML for a Flash movie, with checks for general availability + * of a compatible Flash Player. If not, it will redirect to the installer + * (a seperate Flash Movie to upgrade) or diplay a link. + * + * @type {String} + */ + function buildContent(options) { + var v = isEightAvailable(); + if (isAvailable() && !v) + return buildInstaller(options || {}); + if (v) + return AC_FL_RunContent(options); + return 'This content requires the \ + Adobe Flash Player.'; + } + + function embed(options) { + var obj = options.context, + node = options.htmlNode, + prop = options.property || "$player"; + delete options.context, delete options.htmlNode, delete options.property; + + var content = buildContent(options), + // using timeouts INSIDE the callback, because I explicitly want to + // wait for APF to finish drawing the elements, i.e. wait for DOM + // elements to be drawn. + cb = function() { + $setTimeout(function() { + node.innerHTML = content; + obj[prop] = getElement(options.id); + //console.log("flash movie loaded: ", _self.player); + + $setTimeout(function() { + var fail = null; + if (!obj[prop]) { + fail = "The Flash movie failed to load. " + + "Please check if you're loading the movie on a " + + "website running through http://."; + } + else if (!obj[prop].parentNode) { + fail = "The movie has to be enabled " + + "manually because of Flashblock. No browser refresh is required."; + } + else if (obj[prop].style.display == "none") { + fail = "Adblock Plus blocks or hides the " + + "movie. Please enable it and refresh your browser."; + } + else if (!obj[prop].offsetWidth) { + fail = "The Flash movie failed to load. " + + "Please check if the file exists and the path is correct."; + } + + if (fail) { + + apf.console.error(fail, "flash"); + + if (options.onError) + options.onError({message: fail}); + else + obj.dispatchEvent("error", {message: fail}); + } + }, 1000); + }, 200); + }; + + return apf.loaded ? cb() : apf.addEventListener("load", cb); + } + + /** + * Build the tag that will load the Adobe installer for Flash + * upgrades. + */ + function buildInstaller(options) { + if (!options) + options = {}; + var ret = AC_GetArgs(options, + "movie", "clsid:d27cdb6e-ae6d-11cf-96b8-444553540000", + "application/x-shockwave-flash"), + MMPlayerType = (apf.isIE == true) ? "ActiveX" : "PlugIn", + MMredirectURL = window.location; + document.title = document.title.slice(0, 47) + " - Flash Player Installation"; + var MMdoctitle = document.title; + + return AC_FL_RunContent({ + src : "playerProductInstall", + FlashVars : "MMredirectURL=" + MMredirectURL + "&MMplayerType=" + + MMPlayerType + "&MMdoctitle=" + MMdoctitle + "", + width : "100%", + height : "100%", + align : "middle", + id : ret.embedAttrs["name"], + quality : "high", + bgcolor : "#000000", + name : ret.embedAttrs["name"], + allowScriptAccess: "always", + type : "application/x-shockwave-flash", + pluginspage : "http://www.adobe.com/go/getflashplayer" + }); + } + + var sSrc = "src|movie", + sObj = "onafterupdate|onbeforeupdate|onblur|oncellchange|onclick|ondblclick" + + "|ondrag|ondragend|ondragenter|ondragleave|ondragover|ondrop|onfinish" + + "|onfocus|onhelp|onmousedown|onmouseup|onmouseover|onmousemove" + + "|onmouseout|onkeypress|onkeydown|onkeyup|onload|onlosecapture" + + "|onpropertychange|onreadystatechange|onrowsdelete|onrowenter" + + "|onrowexit|onrowsinserted|onstart|onscroll|onbeforeeditfocus" + + "|onactivate|onbeforedeactivate|ondeactivate|type|codebase|id", + sEmb = "width|height|align|vspace|hspace|class|title|accesskey|name|tabindex"; + + /** + * Augments options from AC_FL_RunContent and AC_SW_RunContent to sane + * object that can be used to generate and tags (depending + * on the clients' browser, but this function will generate both) + * + * @param {Object} options + * @param {Object} srcParamName + * @param {Object} classid + * @param {Object} mimeType + * @type {Object} + */ + function AC_GetArgs(options, srcParamName, classid, mimeType){ + var i, name, + ret = { + embedAttrs: {}, + params : {}, + objAttrs : {} + }; + + for (i in options) { + name = i.toLowerCase(); + if (name == "classid") continue; + + if (name == "pluginspage") { + ret.embedAttrs[i] = options[i]; + } + else if (sSrc.indexOf(name) > -1) { + ret.embedAttrs["src"] = options[i]; + ret.params[srcParamName] = options[i]; + } + else if (sObj.indexOf(name) > -1) { + ret.objAttrs[i] = options[i]; + } + else if (sEmb.indexOf(name) > -1) { + ret.embedAttrs[i] = ret.objAttrs[i] = options[i]; + } + else { + ret.embedAttrs[i] = ret.params[i] = options[i]; + } + } + + ret.objAttrs["classid"] = classid; + if (mimeType) + ret.embedAttrs["type"] = mimeType; + return ret; + } + + /** + * Utility method; get an element from the browser's document object, by ID. + * + * @param {Object} id + * @type {HTMLDomElement} + */ + function getElement(id) { + var elem; + + if (typeof id == "object") + return id; + if (apf.isIE) { + return self[id]; + } + else { + elem = document[id] ? document[id] : document.getElementById(id); + if (!elem) + elem = apf.lookup(id); + return elem; + } + } + + var hash = {}, + uniqueID = 1; + + /** + * FAVideoManager: add a FAVideo instance to the stack for callbacks later on + * and return a unique Identifier for the FAVideo instance to remember. + * + * @param {Object} player + * @type {Number} + */ + function addPlayer(player) { + hash[++uniqueID] = player; + return uniqueID; + } + + /** + * FAVideoManager: retrieve the FAVideo instance that is paired to the + * unique identifier (id). + * + * @param {Object} id + * @type {FAVideo} + */ + function getPlayer(id) { + return hash[id]; + } + + /** + * Directs a call from embedded FAVideo SWFs to the appropriate FAVideo + * instance in Javascript + * + * @param {Object} id + * @param {Object} methodName + * @type {void} + */ + function callMethod(id, methodName) { + var player = hash[id]; + if (player == null) + throw new Error(apf.formatErrorString(0, this, "Player with id: " + id + " not found")); + if (player[methodName] == null) + throw new Error(apf.formatErrorString(0, this, "Method " + methodName + " Not found")); + + + apf.console.info("[FLASH] received method call for player '" + id + "', '" + methodName + "'"); + + + var args = [], + i = 2, + l = arguments.length; + for (; i < l; i++) + args.push(decode(arguments[i])); + player[methodName].apply(player, args); + } + + /** + * Directs a call from a JS object to an embedded SWF + * + * @param {mixed} o DOM reference of the Flash movie (or its ID as a string) + * @param {String} fn Name of the function to be called on the Flash movie, exposed by ExternalInterface + * @type {void} + */ + function remote(o, fn) { + if (typeof o == "string") + o = hash[o]; + var rs = o.CallFunction('' + + __flash__argumentsToXML(arguments, 2) + ''); + return eval(rs); + } + + /** + * Encodes our data to get around ExternalInterface bugs that are still + * present even in Flash 9. + * + * @param {Object} data + */ + function encode(data) { + if (!data || typeof data != "string") + return data; + // double encode all entity values, or they will be mis-decoded + // by Flash when returned + return data.replace(/\&([^;]*)\;/g, "&$1;") + // entity encode XML-ish characters, or Flash's broken XML + // serializer breaks + .replace(//g, ">") + // transforming \ into \\ doesn't work; just use a custom encoding + .replace("\\", "&custom_backslash;") + // null character + .replace(/\0/g, "\\0") + .replace(/\"/g, """); + } + + /** + * Decodes our data to get around ExternalInterface bugs that are still + * present even in Flash 9. + * + * @param {String} data + */ + function decode(data) { + // wierdly enough, Flash sometimes returns the result as an + // 'object' that is actually an array, rather than as a String; + // detect this by looking for a length property; for IE + // we also make sure that we aren't dealing with a typeof string + // since string objects have length property there + if (data && data.length && typeof data != "string") + data = data[0]; + if (!data || typeof data != "string") + return data; + + // certain XMLish characters break Flash's wire serialization for + // ExternalInterface; these are encoded into a custom encoding, rather + // than the standard entity encoding, because otherwise we won't be able + // to differentiate between our own encoding and any entity characters + // that are being used in the string itself + return data.replace(/\&custom_lt\;/g, "<") + .replace(/\&custom_gt\;/g, ">") + .replace(/\&custom_backslash\;/g, '\\') + // needed for IE; \0 is the NULL character + .replace(/\\0/g, "\0"); + } + + var aIsAvailable = {}; + /* + * Checks whether a valid version of Adobe Flash is available on the clients' + * system. Default version to check for is 6.0.65. + * + * @param {String} sVersion + * @type {Boolean} + */ + function isAvailable(sVersion) { + if (typeof sVersion != "string") + sVersion = "6.0.65"; + var aVersion = sVersion.split('.'); + while (aVersion.length < 3) + aVersion.push('0'); + if (typeof aIsAvailable[sVersion] == "undefined") + aIsAvailable[sVersion] = detectFlashVersion(parseInt(aVersion[0]), + parseInt(aVersion[1]), parseInt(aVersion[2])); + return aIsAvailable[sVersion]; + } + + /* + * Shorthand function to call and cache isAvailable() with version + * number 8.0.0 + * + * @type {Boolean} + */ + function isEightAvailable() { + return isAvailable("8.0.0"); + } + + var oSandboxTypes = { + remote : "remote (domain-based) rules", + localwithfile : "local with file access (no internet access)", + localwithnetwork: "local with network (internet access only, no local access)", + localtrusted : "local, trusted (local + internet access)" + }; + + function getSandbox(sType) { + var oSandbox = { + type : null, + description: null, + noRemote : false, + noLocal : false, + error : null + }; + oSandbox.type = sType.toLowerCase(); + oSandbox.description = oSandboxTypes[(typeof oSandboxTypes[oSandbox.type] != "undefined" + ? oSandbox.type + : "unknown")]; + if (oSandbox.type == "localwithfile") { + oSandbox.noRemote = true; + oSandbox.noLocal = false; + oSandbox.error = "Flash security note: Network/internet URLs will not \ + load due to security restrictions.\ + Access can be configured via Flash Player Global Security\ + Settings Page: \ + http://www.macromedia.com/support/documentation/en/flashplayer/help/settings_manager04.html"; + } + else if (oSandbox.type == "localwithnetwork") { + oSandbox.noRemote = false; + oSandbox.noLocal = true; + } + else if (oSandbox.type == "localtrusted") { + oSandbox.noRemote = false; + oSandbox.noLocal = false; + } + + return oSandbox; + } + + return { + isAvailable : isAvailable, + isEightAvailable: isEightAvailable, + buildContent : buildContent, + embed : embed, + encode : encode, + decode : decode, + getElement : getElement, + addPlayer : addPlayer, + getPlayer : getPlayer, + callMethod : callMethod, + getSandbox : getSandbox, + remote : remote + }; +})(); + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/core/lib/util/hook.js)SIZE(10100)TIME(Mon, 10 Jan 2011 10:53:11 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/core/lib/util/hotkey.js)SIZE(5600)TIME(Mon, 10 Jan 2011 10:53:11 GMT)*/ + + +//@todo maybe generalize this to pub/sub event system?? +/** + * @private + */ +apf.hotkeys = {}; +(function() { + /** + * @private + */ + var keyMods = {"ctrl": 1, "alt": 2, "option" : 2, "shift": 4, "meta": 8, "command": 8}; + + /** + * @private + */ + this.keyNames = { + "8" : "Backspace", + "9" : "Tab", + "13" : "Enter", + "27" : "Esc", + "32" : "Space", + "33" : "PageUp", + "34" : "PageDown", + "35" : "End", + "36" : "Home", + "37" : "Left", + "38" : "Up", + "39" : "Right", + "40" : "Down", + "45" : "Insert", + "46" : "Del", + "107": "+", + "112": "F1", + "113": "F2", + "114": "F3", + "115": "F4", + "116": "F5", + "117": "F6", + "118": "F7", + "119": "F8", + "120": "F9", + "121": "F10", + "122": "F11", + "123": "F12", + "188": ",", + "219": "[", + "221": "]" + }; + + var macUnicode = { + "meta" : "\u2318", // ⌘ + "command" : "\u2318", + "alt" : "\u2325", // ⌥ + "option" : "\u2325", + "shift" : "\u21E7", // ⇧ + "esc" : "\u238B", // ⎋ + "control" : "\u2303", // ⌃ + "backspace": "\u232B", // ⌫ + "del" : "\u2326", // ⌦ + "enter" : "\u21A9" // ↩ + }; + + var macUnicodeHtml = { + "meta" : "⌘", // ⌘ + "command" : "⌘", + "alt" : "⌥", // ⌥ + "option" : "⌥", + "shift" : "⇧", // ⇧ + "esc" : "⎋", // ⎋ + "control" : "ࣿ", // ⌃ TODO + "backspace": "èB;", // ⌫ TODO + "del" : "ख", // ⌦ TODO + "enter" : "A9;" // ↩ TODO + }; + + // hash to store the hotkeys in + this.$keys = {}; + + var _self = this; + + /** + * Registers a hotkey handler to a key combination. + * Example: + * + * apf.registerHotkey('Ctrl-Z', undoHandler); + * + * @param {String} hotkey the key combination to user. This is a + * combination of Ctrl, Alt, Shift and a normal key to press. Use + to + * seperate the keys. + * @param {Function} handler the code to be executed when the key + * combination is pressed. + */ + apf.registerHotkey = this.register = function(hotkey, handler){ + var key, + hashId = 0, + keys = hotkey.splitSafe("\\-", null, true), + i = 0, + l = keys.length; + + for (; i < l; ++i) { + if (keyMods[keys[i]]) + hashId = hashId | keyMods[keys[i]]; + else + key = keys[i] || "-"; //when empty, the splitSafe removed a '-' + } + + + if (!key) { + throw new Error("missing key for hotkey: " + hotkey); + } + + + (_self.$keys[hashId] || (_self.$keys[hashId] = {}))[key] = handler; + }; + + this.$exec = function(eInfo) { + var hashId = 0 | (eInfo.ctrlKey ? 1 : 0) | (eInfo.altKey ? 2 : 0) + | (eInfo.shiftKey ? 4 : 0) | (eInfo.metaKey ? 8 : 0); + + var key = _self.keyNames[eInfo.keyCode]; + if (!hashId && !key) //Hotkeys should always have one of the modifiers + return; + + var handler = (_self.$keys[hashId] || {})[(key + || String.fromCharCode(eInfo.keyCode)).toLowerCase()]; + if (handler) { + handler(); + eInfo.returnValue = false; + + apf.queue.empty(); + + } + + return eInfo.returnValue; + }; + + /** + * Removes a registered hotkey. + * @param {String} hotkey the hotkey combination. + */ + apf.removeHotkey = this.remove = this.unregister = function(hotkey) { + _self.register(hotkey, null); + }; + + this.toMacNotation = function(hotkey, bHtml) { + var t, + keys = hotkey.splitSafe("\\-"), + i = 0, + l = keys.length; + + for (; i < l; ++i) { + if (!keys[i]) continue; + if (t = (bHtml ? macUnicodeHtml : macUnicode)[keys[i].toLowerCase()]) + keys[i] = t; + } + return keys.join(" "); + }; + + apf.addEventListener("keydown", function(eInfo) { + var e = eInfo.htmlEvent; + //Hotkey + if (/*!eInfo.isTextInput && */_self.$exec(eInfo) === false + || eInfo.returnValue === false) { + apf.stopEvent(e); + if (apf.canDisableKeyCodes) { + try { + e.keyCode = 0; + } + catch(e) {} + } + return false; + } + + + var keys = []; + if (e.altKey) + keys.push("Alt"); + if (e.ctrlKey) + keys.push("Ctrl"); + if (e.shiftKey) + keys.push("Shift"); + if (e.metaKey) + keys.push("Meta"); + + if (_self.keyNames[e.keyCode]) + keys.push(_self.keyNames[e.keyCode]); + + if (keys.length) { + if (e.keyCode > 46 && !_self.keyNames[e.keyCode]) + keys.push(String.fromCharCode(e.keyCode)); + apf.setProperty("hotkey", keys.join("-")); + } + + }); +}).call(apf.hotkeys); + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/core/lib/util/iepngfix.js)SIZE(3570)TIME(Mon, 10 Jan 2011 10:53:11 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + + +/** + * @private + */ +apf.iepngfix = (function() { + var sNodes = null, + aNodes = null, + applyPositioning = true, + // Path to a transparent GIF image + shim, + + fnLoadPngs = function() { + if (!shim) + shim = apf.skins.skins["default"].mediaPath + '/blank.gif'; + + if (aNodes === null) { + if (sNodes) + aNodes = sNodes.splitSafe(','); + else + aNodes = [document]; + } + + function fixMe(obj) { + // background pngs + if (obj.currentStyle.backgroundImage.match(/\.png/i) !== null) + bg_fnFixPng(obj); + // image elements + if (obj.tagName == 'IMG' && obj.src.match(/\.png$/i) !== null) + el_fnFixPng(obj); + // apply position to 'active' elements + if (applyPositioning && (obj.tagName == 'A' || obj.tagName == 'INPUT') + && obj.style.position === '') { + obj.style.position = 'relative'; + } + } + + for (var j = 0, l = aNodes.length, node; j < l; j++) { + if (typeof aNodes[j] == "string") + aNodes[j] = document.getElementById(aNodes[j]); + node = aNodes[j]; + if (!node) continue; + + if (node != document) + fixMe(node); + + for (var i = node.all.length - 1, obj = null; (obj = node.all[i]); i--) + fixMe(obj); + } + }, + + bg_fnFixPng = function(obj) { + var mode = 'scale', + bg = obj.currentStyle.backgroundImage, + src = bg.substring(5, bg.length - 2); + + if (obj.currentStyle.backgroundRepeat == 'no-repeat') + mode = 'crop'; + obj.style.filter = "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='" + + src + "', sizingMethod='" + mode + "')"; + obj.style.backgroundImage = "url(" + shim + ")"; + }, + + el_fnFixPng = function(img) { + var src = img.src; + img.style.width = img.width + "px"; + img.style.height = img.height + "px"; + img.style.filter = "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='" + + src + "', sizingMethod='scale')"; + img.src = shim; + }; + + return { + limitTo: function(s) { + sNodes = s; + return this; + }, + run: fnLoadPngs + }; +})(); + + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/core/lib/util/json.js)SIZE(26243)TIME(Mon, 10 Jan 2011 10:53:11 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + +/** + * Creates xml nodes from an JSON string/ object recursively. + * + * @param {String} strJson the JSON definition. + * @param {Boolean} [noError] whether an exception should be thrown by the parser when the xml is not valid. + * @param {Boolean} [preserveWhiteSpace] whether whitespace that is present between XML elements should be preserved + * @return {XMLNode} the created xml document (NOT the root-node). + */ + +apf.json2xml_Obj = {}; +apf.json2xml_Attr = {}; +apf.json2xml_ObjByAttr = {}; + +apf.json2Xml = (function(){ + var jsonToXml = function (v, name, xml, notag) { + var i, n, m, t; + // do an apf warn + function cleanString(s){ + return s.replace(/&/g,"&").replace(/\/g,'>'); + } + if(!notag){ + if(name != (m=name.replace(/[^a-zA-Z0-9_-]/g, "_"))) + apf.console.warn("Json2XML, invalid characters found in JSON tagname '" + name, "json2Xml"); + name = m; + } + if (apf.isArray(v)) { + for (i = 0, n = v.length; i < n; i++) + jsonToXml(v[i],name,xml); + } + else if (typeof v == "object") { + var hasChild = false, objAttr = null; + + if(!notag)xml.push("<", name); + for (i in v) { + if ((n=apf.json2xml_Attr[i]) || i.charAt(0)=='@'){ + if(!n && !objAttr) objAttr = apf.json2xml_ObjByAttr[i.slice(1)]; + if(!notag)xml.push(" ", n?n:i.slice(1), "=\"", cleanString(v[i].toString()), "\""); + } else + hasChild = true; + } + if (hasChild) { + if(!notag)xml.push(">"); + if(t=(objAttr || apf.json2xml_Obj[name])){ + if(t==1) t = { child : name.replace(/(.*)s$/,"$1")||name, key : "name", value: "value"}; + for (i in v) { + if(i.charAt(0)!='@' && !apf.json2xml_Attr[i]){ + if( typeof(m = v[i]) =='object'){ + if(apf.json2xml_Obj[i]){ + jsonToXml(m,i,xml); + }else { + xml.push("<",t.child," ",t.key,"=\"",cleanString(i.toString()),"\" >"); + jsonToXml(m, i,xml,true); + xml.push("\n"); + } + } else { + xml.push("<",t.child," ",t.key,"=\"",i,"\" "); + if(t.value){ + if(t.value==1) + xml.push("/>"); + else + xml.push(t.value,"=\"",cleanString(v[i].toString()),"\"/>"); + }else + xml.push(">",cleanString(v[i].toString()),""); + } + } + } + if(!notag)xml.push("\n"); + }else{ + for (i in v) { + if (!apf.json2xml_Attr[i] && i.charAt(0)!='@'){ + if(i.match(/[^a-zA-Z0-9_-]/g)){ + apf.console.warn("Json2XML, invalid characters found in JSON tagname: '" + i, "json2Xml"); + }else + jsonToXml(v[i], i, xml,false); + } + } + if(!notag)xml.push(""); + } + }else if(!notag)xml.push("/>"); + } + else { + if(!notag)xml.push("<", name, ">", cleanString(v.toString()), ""); + else xml.push( cleanString(v.toString())); + } + + } + + return function(strJson, noError, preserveWhiteSpace) { + var o = (typeof strJson == "string" && apf.isJson(strJson)) + ? JSON.parse(strJson.replace(/""/g, '" "'))//eval("(" + strJson + ")") + : strJson, + xml = [], i; + jsonToXml(o,"jsondoc", xml, false); + + return apf.getXmlDom(xml.join("").replace(/\t|\n/g, ""), noError, true);//preserveWhiteSpace);//@todo apf3.0 + }; +})(); + +apf.xml2json = function (xml, noattrs) { + // alright! lets go and convert our xml back to json. + var filled, out = {}, o, nodes = xml.childNodes, cn, i,j, n,m, u,v,w, s,t,cn1,u1,v1,t1,name; + + if(!noattrs){ + if(m = (xml.attributes))for(u = 0,v = m.length; u < v; u++){ + t = apf.json2xml_Attr[w=m[u].nodeName] || ('@'+w); + if(t.indexOf('@a_')!=0)out[t] = m[u].nodeValue, filled = true; + } + } + + for (var i = 0, j = nodes.length;i -1) + o.content.style.display = ""; + + var x = options.x; + var y = options.y; + + var refNode = options.ref; + while (refNode && refNode.nodeType == 1) { + if (fixed = apf.getStyle(refNode, "position") == "fixed") + break; + refNode = refNode.parentNode || refNode.$parentNode; + } + + if (!fixed) { + if (refNode) { + var pos = apf.getAbsolutePosition(options.ref, + o.content.offsetParent || o.content.parentNode); + x = (x || 0) + pos[0]; + y = (y || 0) + pos[1]; + } + + if (options.width || o.width) + popup.style.width = ((options.width || o.width) - 3) + "px"; + + popup.style.position = "absolute"; + + var pOverflow = apf.getOverflowParent(o.content); + var edgeY = (pOverflow == document.documentElement + ? (apf.isIE + ? pOverflow.offsetHeight + : (window.innerHeight + window.pageYOffset)) + pOverflow.scrollTop + : pOverflow.offsetHeight + pOverflow.scrollTop); + moveUp = options.autoCorrect && (y + + (options.height || o.height || o.content.offsetHeight)) + > edgeY; + + if (moveUp) { + var value; + if (refNode) + value = (pos[1] - (options.height || o.height || o.content.offsetHeight)); + else + value = (edgeY - (options.height || o.height || o.content.offsetHeight)); + + popup.style.top = value < 0 ? y : value + "px"; + } + else { + popup.style.top = y + "px" + } + + if (!options.noleft) { + var edgeX = (pOverflow == document.documentElement + ? (apf.isIE + ? pOverflow.offsetWidth + : (window.innerWidth + window.pageXOffset)) + pOverflow.scrollLeft + : pOverflow.offsetWidth + pOverflow.scrollLeft); + moveLeft = options.autoCorrect && (x + + (options.width || o.width || o.content.offsetWidth)) + > edgeX; + + if (moveLeft) { + var value = (edgeX - (options.width || o.width || o.content.offsetWidth)); + popup.style.left = value < 0 ? x : value + "px"; + } + else { + popup.style.left = x + "px"; + } + } + } + else { + pos = apf.getAbsolutePosition(options.ref, refNode); + y = (y || 0) + pos[1] + refNode.offsetTop; + pos[0] += refNode.offsetLeft; + popup.style.position = "fixed"; + popup.style.top = y + "px"; + + if (!options.noleft) + popup.style.left = x + "px"; + } + + + // set a className that specifies the direction, to help skins with + // specific styling options. + apf.setStyleClass(popup, moveUp ? "upward" : "downward", [moveUp ? "downward" : "upward"]); + + + if (options.animate) { + if (options.animate == "fade") { + apf.tween.single(popup, { + type : 'fade', + from : 0, + to : 1, + anim : apf.tween.NORMAL, + steps : options.steps || 15 * apf.animSteps + }); + } + else { + var iVal, steps = apf.isIE8 ? 5 : 7, i = 0; + iVal = setInterval(function(){ + var value = ++i * ((options.height || o.height) / steps); + + popup.style.height = value + "px"; + if (moveUp) + popup.style.top = (y - value - (options.y || 0)) + "px"; + else + (options.container || popup).scrollTop = -1 * (i - steps) * ((options.height || o.height) / steps); + popup.style.display = "block"; + + if (i >= steps) { + clearInterval(iVal) + + if (options.callback) + options.callback(popup); + } + }, 10); + } + } + else { + if (!refNode) { + if (options.height || o.height) + popup.style.height = (options.height || o.height) + "px"; + popup.style.top = y + "px"; + } + popup.style.display = "block"; + + if (options.callback) + options.callback(popup); + } + + $setTimeout(function(){ + apf.popup.last = cacheId; + }); + + if (options.draggable) { + options.id = cacheId; + this.makeDraggable(options); + } + }, + + hide : function(){ + if (this.isDragging) return; + + var o = this.cache[this.last]; + if (o) { + if (o.content) + o.content.style.display = "none"; + + if (o.options && o.options.onclose) { + o.options.onclose(apf.extend(o.options, {htmlNode: o.content})); + o.options.onclose = false; + } + } + }, + + isShowing : function(cacheId){ + return this.last && this.last == cacheId + && this.cache[this.last] + && this.cache[this.last].content.style.display != "none"; + }, + + isDragging : false, + + makeDraggable: function(options) { + if (!apf.Interactive || this.cache[options.id].draggable) + return; + + var oHtml = this.cache[options.id].content; + this.cache[options.id].draggable = true; + var o = { + $propHandlers : {}, + minwidth : 10, + minheight : 10, + maxwidth : 10000, + maxheight : 10000, + dragOutline : false, + resizeOutline : false, + draggable : true, + resizable : options.resizable, + $ext : oHtml, + oDrag : oHtml.firstChild + }; + + oHtml.onmousedown = + oHtml.firstChild.onmousedown = function(e){ + if (!e) e = event; + + + if (apf.hasFocusBug + && !apf.popup.focusFix[(e.srcElement || e.target).tagName]) { + apf.window.$focusfix(); + } + + + (e || event).cancelBubble = true; + } + + apf.implement.call(o, apf.Interactive); + + o.$propHandlers["draggable"].call(o, true); + o.$propHandlers["resizable"].call(o, true); + }, + + forceHide : function(){ + if (this.last + + && !apf.plane.current + + && this.isShowing(this.last) + && this.cache[this.last] + && this.cache[this.last].options + && this.cache[this.last].options.autohide !== false) { + var o = apf.lookup(this.last); + if (!o) + this.last = null; + else if (o.dispatchEvent("popuphide") !== false) + this.hide(); + } + }, + + destroy : function(){ + for (var cacheId in this.cache) { + if (this.cache[cacheId]) { + this.cache[cacheId].content.onmousedown = null; + apf.destroyHtmlNode(this.cache[cacheId].content); + this.cache[cacheId].content = null; + this.cache[cacheId] = null; + } + } + + if (!this.popup) return; + //this.popup.document.body.c = null; + //this.popup.document.body.onmouseover = null; + } +} + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/core/lib/util/silverlight.js)SIZE(25659)TIME(Mon, 10 Jan 2011 10:53:11 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + +/** + * Helper class that aids in creating and controlling Microsoft Silverlight + * elements (XAML stuff). + * + * @author Mike de Boer + * @version %I%, %G% + * @since 1.0 + * @namespace apf + * @private + */ +apf.silverlight = (function() { + /** + * {Number} silverlightCount: + * + * Counter of globalized event handlers + */ + var silverlightCount = 0; + + /** + * {Boolean} __onSilverlightInstalledCalled: + * + * Prevents onSilverlightInstalled from being called multiple times + */ + var __onSilverlightInstalledCalled = false; + + /** + * {String} fwlinkRoot: + * + * Prefix for fwlink URL's + */ + var fwlinkRoot = 'http://go2.microsoft.com/fwlink/?LinkID='; + + /** + * {Boolean} __installationEventFired: + * + * Ensures that only one Installation State event is fired. + */ + var __installationEventFired = false; + + /** + * {Function} onGetSilverlight + * + * Called by Silverlight.GetSilverlight to notify the page that a user + * has requested the Silverlight installer + */ + var onGetSilverlight = null; + + /** + * Called by apf.silverlight.WaitForInstallCompletion when the page detects + * that Silverlight has been installed. The event handler is not called + * in upgrade scenarios. + * + * @type {void} + */ + function onSilverlightInstalled() { + window.location.reload(false); + } + + /** + * Checks to see if the correct version is installed + * + * @param {String} version + * @type {Boolean} + */ + function isInstalled(version){ + if (version == undefined) + version = null; + + var isVersionSupported = false; + var container = null; + + try { + var control = null; + var tryNS = false; + + if (window.ActiveXObject) { + try { + control = new ActiveXObject('AgControl.AgControl'); + if (version === null) { + isVersionSupported = true; + } + else if (control.IsVersionSupported(version)) { + isVersionSupported = true; + } + control = null; + } + catch (e) { + tryNS = true; + } + } + else { + tryNS = true; + } + if (tryNS) { + var plugin = navigator.plugins["Silverlight Plug-In"]; + if (plugin) { + if (version === null) { + isVersionSupported = true; + } + else { + var actualVer = plugin.description; + if (actualVer === "1.0.30226.2") + actualVer = "2.0.30226.2"; + var actualVerArray = actualVer.split("."); + while (actualVerArray.length > 3) { + actualVerArray.pop(); + } + while (actualVerArray.length < 4) { + actualVerArray.push(0); + } + var reqVerArray = version.split("."); + while (reqVerArray.length > 4) { + reqVerArray.pop(); + } + + var requiredVersionPart; + var actualVersionPart; + var index = 0; + do { + requiredVersionPart = parseInt(reqVerArray[index]); + actualVersionPart = parseInt(actualVerArray[index]); + index++; + } + while (index < reqVerArray.length && requiredVersionPart === actualVersionPart); + + if (requiredVersionPart <= actualVersionPart && !isNaN(requiredVersionPart)) { + isVersionSupported = true; + } + } + } + } + } + catch (e) { + isVersionSupported = false; + } + + return isVersionSupported; + } + + /** + * Occasionally checks for Silverlight installation status. If it + * detects that Silverlight has been installed then it calls + * apf.silverlight.onSilverlightInstalled();. This is only supported + * if Silverlight was not previously installed on this computer. + * + * @type {void} + */ + function WaitForInstallCompletion(){ + if (!apf.silverlight.isBrowserRestartRequired && onSilverlightInstalled) { + try { + navigator.plugins.refresh(); + } + catch(e) {} + if (isInstalled(null) && !__onSilverlightInstalledCalled) { + onSilverlightInstalled(); + __onSilverlightInstalledCalled = true; + } + else { + $setTimeout(WaitForInstallCompletion, 3000); + } + } + } + + /** + * Performs startup tasks + * + * @type {void} + */ + function startup() { + navigator.plugins.refresh(); + apf.silverlight.isBrowserRestartRequired = isInstalled(null); + if (!apf.silverlight.isBrowserRestartRequired) { + WaitForInstallCompletion(); + if (!__installationEventFired) { + onInstallRequired(); + __installationEventFired = true; + } + } + else if (window.navigator.mimeTypes) { + var mimeSL2 = navigator.mimeTypes["application/x-silverlight-2"]; + var mimeSL2b2 = navigator.mimeTypes["application/x-silverlight-2-b2"]; + var mimeSL2b1 = navigator.mimeTypes["application/x-silverlight-2-b1"]; + var mimeHighestBeta = mimeSL2b1; + if (mimeSL2b2) + mimeHighestBeta = mimeSL2b2; + + if (!mimeSL2 && (mimeSL2b1 || mimeSL2b2)) { + if (!__installationEventFired) { + onUpgradeRequired(); + __installationEventFired = true; + } + } + else if (mimeSL2 && mimeHighestBeta) { + if (mimeSL2.enabledPlugin && + mimeHighestBeta.enabledPlugin) { + if (mimeSL2.enabledPlugin.description != + mimeHighestBeta.enabledPlugin.description) { + if (!__installationEventFired) { + onRestartRequired(); + __installationEventFired = true; + } + } + } + } + } + } + + /** + * Inserts a Silverlight tag or installation experience into the HTML + * DOM based on the current installed state of Silverlight. + * + * @param {String} source + * @param {HTMLDomElement} parentElement + * @param {String} id + * @param {Object} properties + * @param {Object} events + * @param {Object} initParams + * @param {mixed} userContext + * @type {String} + */ + function createObject(source, parentElement, id, properties, events, initParams, userContext) { + var slPluginHelper = {}, + slProperties = properties, + slEvents = events; + + slPluginHelper.version = slProperties.version; + slProperties.source = source; + slPluginHelper.alt = slProperties.alt; + + //rename properties to their tag property names. For bacwards compatibility + //with Silverlight.js version 1.0 + if (initParams) + slProperties.initParams = initParams; + if (slProperties.isWindowless && !slProperties.windowless) + slProperties.windowless = slProperties.isWindowless; + if (slProperties.framerate && !slProperties.maxFramerate) + slProperties.maxFramerate = slProperties.framerate; + if (id && !slProperties.id) + slProperties.id = id; + + // remove elements which are not to be added to the instantiation tag + delete slProperties.ignoreBrowserVer; + delete slProperties.inplaceInstallPrompt; + delete slProperties.version; + delete slProperties.isWindowless; + delete slProperties.framerate; + delete slProperties.data; + delete slProperties.src; + delete slProperties.alt; + + // detect that the correct version of Silverlight is installed, else display install + var slPluginHTML; + if (isInstalled(slPluginHelper.version)) { + //move unknown events to the slProperties array + for (var name in slEvents) { + if (slEvents[name]) { + if (name == "onLoad" && typeof slEvents[name] == "function" + && slEvents[name].length != 1 ) { + var onLoadHandler = slEvents[name]; + slEvents[name] = function (sender) { + return onLoadHandler(document.getElementById(id), + userContext, sender) + }; + } + var handlerName = __getHandlerName(slEvents[name]); + if (handlerName != null) { + slProperties[name] = handlerName; + slEvents[name] = null; + } + else { + throw "typeof events."+name+" must be 'function' or 'string'"; + } + } + } + slPluginHTML = buildHTML(slProperties); + } + //The control could not be instantiated. Show the installation prompt + else { + slPluginHTML = buildPromptHTML(slPluginHelper); + } + + // insert or return the HTML + if (parentElement) + parentElement.innerHTML = slPluginHTML; + else + return slPluginHTML; + } + + /** + * create HTML that instantiates the control + * + * @param {Object} slProperties + * @type {String} + */ + function buildHTML(slProperties) { + var htmlBuilder = []; + htmlBuilder.push(''); + + delete slProperties.id; + delete slProperties.width; + delete slProperties.height; + + for (var name in slProperties) { + if (slProperties[name]) + htmlBuilder.push(''); + } + htmlBuilder.push('<\/object>'); + return htmlBuilder.join(''); + } + + /** + * takes a single parameter of all createObject + * parameters enclosed in {} + * + * @param {Object} params + * @type {String} + */ + function createObjectEx(params) { + var parameters = params; + var html = createObject(parameters.source, parameters.parentElement, + parameters.id, parameters.properties, parameters.events, + parameters.initParams, parameters.context); + if (parameters.parentElement == null) + return html; + } + + /** + * Builds the HTML to prompt the user to download and install Silverlight + * + * @param {Object} slPluginHelper + * @type {String} + */ + function buildPromptHTML(slPluginHelper) { + var slPluginHTML = ""; + var urlRoot = fwlinkRoot; + var version = slPluginHelper.version ; + if (slPluginHelper.alt) { + slPluginHTML = slPluginHelper.alt; + } + else { + if (!version) + version=""; + slPluginHTML = "Get Microsoft Silverlight"; + slPluginHTML = slPluginHTML.replace('{1}', version); + slPluginHTML = slPluginHTML.replace('{2}', urlRoot + '108181'); + } + + return slPluginHTML; + } + + /** + * Navigates the browser to the appropriate Silverlight installer + * + * @param {String} version + * @type {void} + */ + function getSilverlight(version) { + if (onGetSilverlight ) + onGetSilverlight(); + + var shortVer = ""; + var reqVerArray = String(version).split("."); + if (reqVerArray.length > 1) { + var majorNum = parseInt(reqVerArray[0]); + if (isNaN(majorNum) || majorNum < 2) + shortVer = "1.0"; + else + shortVer = reqVerArray[0]+'.'+reqVerArray[1]; + } + + var verArg = ""; + if (shortVer.match(/^\d+\056\d+$/) ) + verArg = "&v="+shortVer; + followFWLink("114576" + verArg); + } + + /** + * Navigates to a url based on fwlinkid + * + * @param {String} linkid + * @type {void} + */ + function followFWLink(linkid) { + top.location = fwlinkRoot + String(linkid); + } + + /** + * Encodes special characters in input strings as charcodes + * + * @param {String} strInput + * @type {String} + */ + function HtmlAttributeEncode(strInput) { + var c; + var retVal = ''; + + if (strInput == null) return null; + + for (var cnt = 0; cnt < strInput.length; cnt++) { + c = strInput.charCodeAt(cnt); + + if (((c > 96) && (c < 123)) || (( c > 64) && (c < 91)) + || ((c > 43) && (c < 58) && (c != 47)) || (c == 95)) + retVal = retVal + String.fromCharCode(c); + else + retVal = retVal + '&#' + c + ';'; + } + return retVal; + } + + /** + * Default error handling function + * + * @param {String} sender + * @param {Object} args + * @type {void} + */ + function default_error_handler(sender, args) { + var iErrorCode; + var errorType = args.ErrorType; + + iErrorCode = args.ErrorCode; + + var errMsg = ["\nSilverlight error message \n\ + ErrorCode: ", iErrorCode, "\n\ + ErrorType: ", errorType, " \n\ + Message: ", args.ErrorMessage, " \n"]; + + if (errorType == "ParserError") { + errMsg.push("XamlFile: ", args.xamlFile, " \n\ + Line: ", args.lineNumber, " \n\ + Position: ", args.charPosition, " \n"); + } + else if (errorType == "RuntimeError") { + if (args.lineNumber != 0) { + errMsg.push("Line: ", args.lineNumber, " \n\ + Position: ", args.charPosition, " \n"); + } + errMsg.push("MethodName: ", args.methodName, " \n"); + } + throw new Error(apf.formatErrorString(0, this, errMsg.join(''))); + } + + /** + * Releases event handler resources when the page is unloaded + * + * @type {void} + */ + function __cleanup() { + for (var i = silverlightCount - 1; i >= 0; i--) + window['__slEvent' + i] = null; + + silverlightCount = 0; + if (window.removeEventListener) + window.removeEventListener('unload', __cleanup , false); + else + window.detachEvent('onunload', __cleanup ); + } + + /** + * Generates named event handlers for delegates. + * + * @param {Function} handler + * @type {String} + */ + function __getHandlerName(handler) { + var handlerName = ""; + if (typeof handler == "string") { + handlerName = handler; + } + else if (typeof handler == "function" ) { + if (silverlightCount == 0) { + if (window.addEventListener) + window.addEventListener('onunload', __cleanup , false); + else + window.attachEvent('onunload', __cleanup); + } + var count = silverlightCount++; + handlerName = "__slEvent"+count; + + window[handlerName]=handler; + } + else { + handlerName = null; + } + return handlerName; + } + + /** + * onRequiredVersionAvailable: + * + * Called by version verification control to notify the page that + * an appropriate build of Silverlight is available. The page + * should respond by injecting the appropriate Silverlight control + */ + function onRequiredVersionAvailable() {}; + + /** + * onRestartRequired: + * + * Called by version verification control to notify the page that + * an appropriate build of Silverlight is installed but not loaded. + * The page should respond by injecting a clear and visible + * "Thanks for installing. Please restart your browser and return + * to mysite.com" or equivalent into the browser DOM + */ + function onRestartRequired() {}; + + /** + * onUpgradeRequired: + * + * Called by version verification control to notify the page that + * Silverlight must be upgraded. The page should respond by + * injecting a clear, visible, and actionable upgrade message into + * the DOM. The message must inform the user that they need to + * upgrade Silverlight to use the page. They are already somewhat + * familiar with the Silverlight product when they encounter this. + * Silverlight should be mentioned so the user expects to see that + * string in the installer UI. However, the Silverlight-powered + * application should be the focus of the solicitation. The user + * wants the app. Silverlight is a means to the app. + * + * The upgrade solicitation will have a button that directs + * the user to the Silverlight installer. Upon click the button + * should both kick off a download of the installer URL and replace + * the Upgrade text with "Thanks for downloading. When the upgarde + * is complete please restart your browser and return to + * mysite.com" or equivalent. + * + * Note: For a more interesting upgrade UX we can use Silverlight + * 1.0-style XAML for this upgrade experience. Contact PiotrP for + * details. + */ + function onUpgradeRequired() {}; + + /** + * onInstallRequired: + * + * Called by Silverlight.checkInstallStatus to notify the page + * that Silverlight has not been installed by this user. + * The page should respond by + * injecting a clear, visible, and actionable upgrade message into + * the DOM. The message must inform the user that they need to + * download and install components needed to use the page. + * Silverlight should be mentioned so the user expects to see that + * string in the installer UI. However, the Silverlight-powered + * application should be the focus of the solicitation. The user + * wants the app. Silverlight is a means to the app. + * + * The installation solicitation will have a button that directs + * the user to the Silverlight installer. Upon click the button + * should both kick off a download of the installer URL and replace + * the Upgrade text with "Thanks for downloading. When installation + * is complete you may need to refresh the page to view this + * content" or equivalent. + */ + function onInstallRequired() {}; + + /** + * IsVersionAvailableOnError: + * + * This function should be called at the beginning of a web page's + * Silverlight error handler. It will determine if the required + * version of Silverlight is installed and available in the + * current process. + * + * During its execution the function will trigger one of the + * Silverlight installation state events, if appropriate. + * + * Sender and Args should be passed through from the calling + * onError handler's parameters. + * + * The associated Sivlerlight tag must have + * minRuntimeVersion set and should have autoUpgrade set to false. + */ + function IsVersionAvailableOnError(sender, args) { + var retVal = false; + try { + if (args.ErrorCode == 8001 && !__installationEventFired) { + onUpgradeRequired(); + __installationEventFired = true; + } + else if (args.ErrorCode == 8002 && !__installationEventFired) { + onRestartRequired(); + __installationEventFired = true; + } + // this handles upgrades from 1.0. That control did not + // understand the minRuntimeVerison parameter. It also + // did not know how to parse XAP files, so would throw + // Parse Error (5014). A Beta 2 control may throw 2106 + else if (args.ErrorCode == 5014 || args.ErrorCode == 2106) { + if (__verifySilverlight2UpgradeSuccess(args.getHost())) + retVal = true; + } + else { + retVal = true; + } + } + catch (e) { } + return retVal; + }; + + /** + * IsVersionAvailableOnLoad: + * + * This function should be called at the beginning of a web page's + * Silverlight onLoad handler. It will determine if the required + * version of Silverlight is installed and available in the + * current process. + * + * During its execution the function will trigger one of the + * Silverlight installation state events, if appropriate. + * + * Sender should be passed through from the calling + * onError handler's parameters. + * + * The associated Sivlerlight tag must have + * minRuntimeVersion set and should have autoUpgrade set to false. + */ + function IsVersionAvailableOnLoad(sender) { + var retVal = false; + try { + if (__verifySilverlight2UpgradeSuccess(sender.getHost())) + retVal = true; + } + catch (e) {} + return retVal; + }; + + /** + * __verifySilverlight2UpgradeSuccess: + * + * This internal function helps identify installation state by + * taking advantage of behavioral differences between the + * 1.0 and 2.0 releases of Silverlight. + * + */ + function __verifySilverlight2UpgradeSuccess(host) { + var retVal = false, + version = "2.0.31005", + installationEvent = null; + + try { + if (host.IsVersionSupported(version + ".99")) { + installationEvent = onRequiredVersionAvailable; + retVal = true; + } + else if (host.IsVersionSupported(version + ".0")) { + installationEvent = onRestartRequired; + } + else { + installationEvent = onUpgradeRequired; + } + + if (installationEvent && !__installationEventFired) { + installationEvent(); + __installationEventFired = true; + } + } + catch (e) {} + return retVal; + }; + + var aIsAvailable = {}; + /* + * Checks whether a valid version of Silverlight is available on the clients' + * system. Default version to check for is 1.0. + * + * @param {String} sVersion Optional. + * @type {Boolean} + */ + function isAvailable(sVersion) { + if (typeof sVersion == "undefined") + sVersion = "1.0"; + if (typeof aIsAvailable[sVersion] == "undefined") + aIsAvailable[sVersion] = isInstalled(sVersion); + return aIsAvailable[sVersion]; + } + + return { + /** + * onGetSilverlight: + * + * Called by apf.silverlight.GetSilverlight to notify the page that a user + * has requested the Silverlight installer + */ + onGetSilverlight : null, + isBrowserRestartRequired: false, + startup : startup, + createObject : createObject, + createObjectEx : createObjectEx, + getSilverlight : getSilverlight, + default_error_handler : default_error_handler, + isAvailable : isAvailable + }; +})(); + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/core/lib/util/style.js)SIZE(17516)TIME(Mon, 10 Jan 2011 10:53:11 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + + +/** + * This method sets a single css rule + * @param {String} name the css name of the rule (i.e. '.cls' or '#id'). + * @param {String} type the css property to change. + * @param {String} value the css value of the property. + * @param {String} [stylesheet] the name of the stylesheet to change. + */ +apf.setStyleRule = function(name, type, value, stylesheet, win){ + name = name.toLowerCase(); + + if (!stylesheet) { + var sheets = (win || self).document.styleSheets; + for (var j = sheets.length - 1; j >= 0; j--) { + try { + var rules = sheets[j][apf.styleSheetRules]; + for (var i = 0; i < rules.length; i++) { + if (rules.item(i).selectorText.toLowerCase() == name) { + rules.item(i).style[type] = value; + return true; + } + } + } + catch(e){} + } + } + else { + var rules = (win || self).document.styleSheets[stylesheet || 0][apf.styleSheetRules]; + for (var i = 0; i < rules.length; i++) { + if (rules.item(i).selectorText.toLowerCase() == name) { + rules.item(i).style[type] = value; + return true; + } + } + } + + return false; +}; + +/** + * This method gets a single css rule + * @param {String} name the css name of the rule (i.e. '.cls' or '#id'). + * @param {String} type the css property to change. + * @param {String} [stylesheet] the name of the stylesheet to change. + */ +apf.getStyleRule = function(name, type, stylesheet, win){ + name = name.toLowerCase(); + + if (!stylesheet) { + var sheets = (win || self).document.styleSheets; + for (var j = sheets.length - 1; j >= 0; j--) { + try { + var rules = sheets[j][apf.styleSheetRules]; + for (var i = 0; i < rules.length; i++) { + if (rules.item(i).selectorText.toLowerCase() == name) { + return rules.item(i).style[type]; + } + } + } + catch(e){} + } + } + else { + var rules = (win || self).document.styleSheets[stylesheet || 0][apf.styleSheetRules]; + for (var i = 0; i < rules.length; i++) { + if (rules.item(i).selectorText.toLowerCase() == name) { + return rules.item(i).style[type]; + } + } + } + + return false; +} + +/** + * This method adds one class name to an HTMLElement and removes none or more. + * @param {HTMLElement} oHtml the HTMLElement to apply the css class to. + * @param {String} className the name of the css class to apply. + * @param {Array} [exclusion] a list of strings specifying names of css classes to remove. + * @returns {HTMLElement} + */ +apf.setStyleClass = function(oHtml, className, exclusion, userAction){ + if (!oHtml || userAction && this.disabled) + return; + + + if (oHtml.nodeFunc) { + throw new Error(apf.formatErrorString(0, this, + "Setting style class", + "Trying to set style class on aml node. Only xml or html nodes can \ + be passed to this function")); + } + + + if (className) { + if (exclusion) + exclusion[exclusion.length] = className; + else + exclusion = [className]; + } + + //Create regexp to remove classes + //var re = new RegExp("(?:(^| +)" + (exclusion ? exclusion.join("|") : "") + "($| +))", "gi"); + var re = new RegExp("(^| +)(?:" + (exclusion ? exclusion.join("|") : "") + ")", "gi"); + + //Set new class + oHtml.className != null + ? (oHtml.className = oHtml.className.replace(re, " ") + (className ? " " + className : "")) + : oHtml.setAttribute("class", (oHtml.getAttribute("class") || "") + .replace(re, " ") + (className ? " " + className : "")); + + return oHtml; +}; + +/** + * This method imports a css stylesheet from a string + * @param {String} cssString the css definition + * @param {Object} [doc] the reference to the document where the css is applied on + * @param {String} [media] the media to which this css applies (i.e. 'print' or 'screen') + */ +apf.importCssString = function(cssString, doc, media){ + doc = doc || document; + var htmlNode = doc.getElementsByTagName("head")[0];//doc.documentElement.getElementsByTagName("head")[0]; + + + if (!apf.supportOpacity) { + cssString = cssString.replace(/opacity[ \s]*\:[ \s]*([\d\.]+)/g, + function(m, m1){ + return "filter:progid:DXImageTransform.Microsoft.Alpha(opacity=" + (m1*100) + ")"; + }); + } + + + if (apf.canCreateStyleNode) { + //var head = document.getElementsByTagName("head")[0]; + var style = doc.createElement("style"); + style.appendChild(doc.createTextNode(cssString)); + if (media) + style.setAttribute('media', media); + htmlNode.appendChild(style); + } + else { + htmlNode.insertAdjacentHTML("beforeend", "."); + + /*if(document.body){ + document.body.style.height = "100%"; + $setTimeout('document.body.style.height = "auto"'); + }*/ + } +}; + +/** + * This method retrieves the current value of a property on a HTML element + * recursively. If the style isn't found on the element itself, it's parent is + * checked. + * @param {HTMLElement} el the element to read the property from + * @param {String} prop the property to read + * @returns {String} + */ +apf.getStyleRecur = function(el, prop) { + var value = apf.hasComputedStyle + ? document.defaultView.getComputedStyle(el,'').getPropertyValue( + prop.replace(/([A-Z])/g, function(m, m1){ + return "-" + m1.toLowerCase(); + })) + : el.currentStyle[prop] + + return ((!value || value == "transparent" || value == "inherit") + && el.parentNode && el.parentNode.nodeType == 1) + ? this.getStyleRecur(el.parentNode, prop) + : value; +}; + +/** + * This method imports a stylesheet defined in a multidimensional array + * @param {Array} def Required Multidimensional array specifying + * @param {Object} win Optional Reference to a window + * @method + * @deprecated + */ +apf.importStylesheet = function (def, win){ + for (var i = 0; i < def.length; i++) { + if (!def[i][1]) continue; + + if (apf.isIE) + (win || window).document.styleSheets[0].addRule(def[i][0], + def[i][1]); + else + (win || window).document.styleSheets[0].insertRule(def[i][0] + + " {" + def[i][1] + "}", 0); + } +} + +/** + * This method determines if specified coordinates are within the HTMLElement. + * @param {HTMLElement} el the element to check + * @param {Number} x the x coordinate in pixels + * @param {Number} y the y coordinate in pixels + * @returns {Boolean} + */ +apf.isInRect = function(oHtml, x, y){ + var pos = this.getAbsolutePosition(oHtml); + if (x < pos[0] || y < pos[1] || x > oHtml.offsetWidth + pos[0] - 10 + || y > oHtml.offsetHeight + pos[1] - 10) + return false; + return true; +}; + +/** + * Retrieves the parent which provides the rectangle to which the HTMLElement is + * bound and cannot escape. In css this is accomplished by having the overflow + * property set to hidden or auto. + * @param {HTMLElement} o the element to check + * @returns {HTMLElement} + */ +apf.getOverflowParent = function(o){ + //not sure if this is the correct way. should be tested + + o = o.offsetParent; + while (o && (this.getStyle(o, "overflow") != "hidden" + || "absolute|relative".indexOf(this.getStyle(o, "position")) == -1)) { + o = o.offsetParent; + } + return o || document.documentElement; +}; + +/** + * Retrieves the first parent element which has a position absolute or + * relative set. + * @param {HTMLElement} o the element to check + * @returns {HTMLElement} + */ +apf.getPositionedParent = function(o){ + o = o.offsetParent; + while (o && o.tagName.toLowerCase() != "body" + && "absolute|relative".indexOf(this.getStyle(o, "position")) == -1) { + o = o.offsetParent; + } + return o || document.documentElement; +}; + +/** + * Retrieves the absolute x and y coordinates, relative to the browsers + * drawing area or the specified refParent. + * @param {HTMLElement} o the element to check + * @param {HTMLElement} [refParent] the reference parent + * @param {Boolean} [inclSelf] whether to include the position of the element to check in the return value. + * @returns {Array} the x and y coordinate of oHtml. + */ +apf.getAbsolutePosition = function(o, refParent, inclSelf){ + if ("getBoundingClientRect" in document.documentElement) { + if (apf.doesNotIncludeMarginInBodyOffset && o == document.body) { + return [ + o.offsetLeft + (parseFloat(apf.getStyle(o, "marginLeft")) || 0), + + (o.scrollLeft || 0), + o.offsetTop + (parseFloat(apf.getStyle(o, "marginTop")) || 0) + + (o.scrollTop || 0) + ]; + } + + var box = o.getBoundingClientRect(), + top = box.top, + left = box.left, + corr = (apf.isIE && apf.isIE < 8); + + if (refParent && refParent != document.body) { + var pos = apf.getAbsolutePosition(refParent, null, true); + top -= pos[1]; + left -= pos[0]; + } + + if (!(apf.isIE && o == document.documentElement)) { + left += (refParent || document.body).scrollLeft || document.documentElement.scrollLeft || 0; + top += (refParent || document.body).scrollTop || document.documentElement.scrollTop || 0; + } + + if (inclSelf && !refParent) { + left += parseInt(apf.getStyle(o, "borderLeftWidth")) || 0 + top += parseInt(apf.getStyle(o, "borderTopWidth")) || 0; + } + + return [left - (corr ? 2 : 0), top - (corr ? 2 : 0)]; + } + + //@todo code below might be deprecated one day + var wt = inclSelf ? 0 : o.offsetLeft, ht = inclSelf ? 0 : o.offsetTop; + o = inclSelf ? o : o.offsetParent || o.parentNode ; + + if (apf.isIE8 && refParent) { + bw = this.getStyle(o, "borderLeftWidth"); + wt -= (apf.isIE && o.currentStyle.borderLeftStyle != "none" + && bw == "medium" ? 2 : parseInt(bw) || 0); + bh = this.getStyle(o, "borderTopWidth"); + ht -= (apf.isIE && o.currentStyle.borderTopStyle != "none" + && bh == "medium" ? 2 : parseInt(bh) || 0); + } + + var bw, bh, fl; + while (o && o != refParent) {//&& o.tagName.toLowerCase() != "html" + //Border - Left + bw = apf.isOpera || apf.isIE8 ? 0 : this.getStyle(o, "borderLeftWidth"); + + wt += (apf.isIE && o.currentStyle.borderLeftStyle != "none" && bw == "medium" + ? 2 + : parseInt(bw) || 0) + o.offsetLeft; + + if (apf.isIE && !apf.isIE8 && apf.getStyle(o, "styleFloat") == "none" + && apf.getStyle(o, "position") == "relative") { + var q = o.previousSibling; + while (q) { + if (q.nodeType == 1) { + fl = apf.getStyle(q, "styleFloat"); + if (fl == "left") { + wt -= parseInt(apf.getStyle(o, "marginLeft")) + || 0;//-1 * (o.parentNode.offsetWidth - o.offsetWidth)/2; //assuming auto + break; + } + else if (fl == "right") + break; + } + q = q.previousSibling; + } + } + + //Border - Top + bh = apf.isOpera || apf.isIE8 ? 0 : this.getStyle(o, "borderTopWidth"); + ht += (apf.isIE && o.currentStyle.borderTopStyle != "none" && bh == "medium" + ? 2 + : parseInt(bh) || 0) + o.offsetTop; + + //Scrolling + if (!apf.isGecko && o != refParent && (o.tagName != "HTML" || o.ownerDocument != document)) { + wt -= o.scrollLeft; + ht -= o.scrollTop; + } + + //Table support + if (o.tagName.toLowerCase() == "table") { + ht -= parseInt(o.border || 0) + parseInt(o.cellSpacing || 0); + wt -= parseInt(o.border || 0) + parseInt(o.cellSpacing || 0) * 2; + } + else if (o.tagName.toLowerCase() == "tr") { + var cp; + ht -= (cp = parseInt(o.parentNode.parentNode.cellSpacing)); + while (o.previousSibling) + ht -= (o = o.previousSibling).offsetHeight + cp; + } + + if (apf.isIE && !o.offsetParent && o.parentNode.nodeType == 1) { + wt -= o.parentNode.scrollLeft; + ht -= o.parentNode.scrollTop; + } + + o = o.offsetParent; + } + + return [wt, ht]; +}; + +//@todo its much faster to move these to browser specific files and eliminate apf.getStyle() +apf.getHorBorders = function(oHtml){ + return Math.max(0, + (parseInt(apf.getStyle(oHtml, "borderLeftWidth")) || 0) + + (parseInt(apf.getStyle(oHtml, "borderRightWidth")) || 0)); +}; + +apf.getVerBorders = function(oHtml){ + return Math.max(0, + (parseInt(apf.getStyle(oHtml, "borderTopWidth")) || 0) + + (parseInt(apf.getStyle(oHtml, "borderBottomWidth")) || 0)); +}; + +apf.getWidthDiff = function(oHtml){ + if (apf.hasFlexibleBox + && apf.getStyle(oHtml, apf.CSSPREFIX + "BoxSizing") != "content-box") + return 0; + + return Math.max(0, (parseInt(apf.getStyle(oHtml, "paddingLeft")) || 0) + + (parseInt(apf.getStyle(oHtml, "paddingRight")) || 0) + + (parseInt(apf.getStyle(oHtml, "borderLeftWidth")) || 0) + + (parseInt(apf.getStyle(oHtml, "borderRightWidth")) || 0)); +}; + +apf.getHeightDiff = function(oHtml){ + if (apf.hasFlexibleBox + && apf.getStyle(oHtml, apf.CSSPREFIX + "BoxSizing") != "content-box") + return 0; + + return Math.max(0, (parseInt(apf.getStyle(oHtml, "paddingTop")) || 0) + + (parseInt(apf.getStyle(oHtml, "paddingBottom")) || 0) + + (parseInt(apf.getStyle(oHtml, "borderTopWidth")) || 0) + + (parseInt(apf.getStyle(oHtml, "borderBottomWidth")) || 0)); +}; + +apf.getDiff = function(oHtml){ + if (apf.hasFlexibleBox + && apf.getStyle(oHtml, apf.CSSPREFIX + "BoxSizing") != "content-box") + return [0,0]; + + return [Math.max(0, (parseInt(apf.getStyle(oHtml, "paddingLeft")) || 0) + + (parseInt(apf.getStyle(oHtml, "paddingRight")) || 0) + + (parseInt(apf.getStyle(oHtml, "borderLeftWidth")) || 0) + + (parseInt(apf.getStyle(oHtml, "borderRightWidth")) || 0)), + Math.max(0, (parseInt(apf.getStyle(oHtml, "paddingTop")) || 0) + + (parseInt(apf.getStyle(oHtml, "paddingBottom")) || 0) + + (parseInt(apf.getStyle(oHtml, "borderTopWidth")) || 0) + + (parseInt(apf.getStyle(oHtml, "borderBottomWidth")) || 0))]; +}; + +apf.getMargin = function(oHtml) { + return [(parseInt(apf.getStyle(oHtml, "marginLeft")) || 0) + + (parseInt(apf.getStyle(oHtml, "marginRight")) || 0), + (parseInt(apf.getStyle(oHtml, "marginTop")) || 0) + + (parseInt(apf.getStyle(oHtml, "marginBottom")) || 0)] +}; + +apf.getHtmlInnerWidth = function(oHtml){ + return (oHtml.offsetWidth + - (parseInt(apf.getStyle(oHtml, "borderLeftWidth")) || 0) + - (parseInt(apf.getStyle(oHtml, "borderRightWidth")) || 0)); +}; + +apf.getHtmlInnerHeight = function(oHtml){ + return (oHtml.offsetHeight + - (parseInt(apf.getStyle(oHtml, "borderTopWidth")) || 0) + - (parseInt(apf.getStyle(oHtml, "borderBottomWidth")) || 0)); +}; + +/** + * Returns the viewport of the a window. + * + * @param {WindowImplementation} [win] The window to take the measurements of. + * @return {Object} Viewport object with fields x, y, w and h. + * @type {Object} + */ +apf.getViewPort = function(win) { + win = win || window; + var doc = (!win.document.compatMode + || win.document.compatMode == "CSS1Compat") + //documentElement for an iframe + ? win.document.html || win.document.documentElement + : win.document.body; + + // Returns viewport size excluding scrollbars + return { + x : win.pageXOffset || doc.scrollLeft, + y : win.pageYOffset || doc.scrollTop, + width : win.innerWidth || doc.clientWidth, + height: win.innerHeight || doc.clientHeight + }; +} + + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/core/lib/util/syntax.js)SIZE(12610)TIME(Mon, 10 Jan 2011 10:53:11 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + + +/** + * Syntax highlights a code string using html. + * @param {String} strCode the code to highlight. + * @return {String} the highlighted string. + */ +apf.highlightXml = +apf.highlightCode = function(strCode){ + var lines = strCode.split(/\n\r?/); + for (var min = 1000, i = 0, l = lines.length; i < l; i++){ + min = Math.min(min, lines[i].match(/^(\s+)?[^\s]/) && RegExp.$1.length || 1000); + if (!min) break; + } + + strCode = strCode.replace(new RegExp("^ {" + min + "}", "gm"), "") + .replace(/||<\?([\w\-]+)([\s\S]+?)\?>|<\!\[CDATA\[([\s\S]*?)\]\]>|<(\w+:)?script([\s\S]*?)>([\s\S]*?)<\/(?:\w+:)?script>|<([\/\w\:\-]+)([\s\S]*?)(\/?)>/g, + function(m, doctype, comment, ptarget, pcontents, cdata, sprefix, sattr, scontent, tagName, attrs, bl){ + if (doctype) { + return "<!doctype" + doctype.replace(/"; + } + else if (comment) { + return "<!--" + comment.replace(/"; + } + else if (ptarget) { + return "<?" + ptarget + "" + + apf.highlightJs(pcontents, true) + + "?>"; + } + else if (cdata) { + return "<![CDATA[" + cdata + "]]>"; + } + else if (sprefix) { + return "<" + sprefix + "script" + (attrs + ? "" + attrs.replace(/("[\s\S]*?")|([\w\-\:]+)/g, function(m, s, w){ + if (s) return s; + return "" + w + ""; + }) + ">" + : ">") + + apf.highlightJs(scontent, true) + + "</" + sprefix + "script>"; + } + else if (tagName) { + return "<" + + (tagName.substr(0, 2) == "a:" + ? "" + tagName + "" + : tagName) + + (attrs + ? "" + attrs.replace(/("[\s\S]*?")|([\w\-\:]+)/g, function(m, s, w){ + if (s) return s; + return "" + w + ""; + }) + "" + bl + ">" + : ">"); + } + }); + + return strCode;//.replace(/</g, "&lt;"); +} + +/** + * @private + */ +apf.removeStartWhitespaces = function(strCode){ + var lines = strCode.split(/\n\r?/); + for (var min = 1000, i = 0, l = lines.length; i < l; i++){ + min = Math.min(min, lines[i].match(/^(\s+)?[^\s]/) && RegExp.$1.length || 1000); + if (!min) break; + } + + return strCode.replace(new RegExp("^ {" + min + "}", "gm"), ""); +} + +/** + * @private + */ +apf.convertAmlToJson = function(strCode){ + var xml = apf.getXml("" + strCode + "", null, true); + + var script = [], bool = {"true":1, "false":1}; + var x = (function recur(nodes, depth, paout){ + var pre = new Array(depth+2).join(" "), output = []; + + for (var node, i = 0, l = nodes.length; i < l; i++) { + node = nodes[i]; + if (node.nodeType == 3 || node.nodeType == 4) { + if (node.nodeValue.replace(/[\s]*$/, "").replace(/^[\s]*/, "")) { + (paout || output).push("data", '"' + node.nodeValue.trim().replace(/"/g, "\\\"").replace(/\n/g, "\\\n") + '"'); + } + continue; + } + else if (node.nodeType == 8) { + output.push(pre + "//" + node.nodeValue); + continue; + } + else if (node.nodeType != 1) + continue; //ignore + else if (node[apf.TAGNAME] == "script") { + var s = node.childNodes; + for (var j = 0, jl = s.length; j < jl; j++) { + if (s[j].nodeValue.trim() != "//") + script.push(s[j].nodeValue.replace(/^ {16}/gm, "").replace(/\/\/$/, "") + "\n"); + } + continue; + } + + var attr, childLength = node.childNodes.length + (attr = node.attributes).length; + var max = 0, aout = []; + output.push(pre + "new apf." + node[apf.TAGNAME] + (childLength || !depth ? "({" : "()" + (depth == 0 ? ";" : (i == l - 1 ? "" : ",")))); + if (!depth) { + aout.push("htmlNode", "document.body"); + max = Math.max(8, max); + } + + for (var a, j = 0, jl = attr.length; j < jl; j++) { + aout.push((a = attr[j]).nodeName, a.nodeName.substr(0,2) == "on" + ? "function(){" + a.nodeValue + "}" + : (parseInt(a.nodeValue) == a.nodeValue || bool[a.nodeValue] + ? a.nodeValue + : '"' + a.nodeValue.replace(/"/g, "\\\"") + '"')); + max = Math.max(a.nodeName.length, max); + } + + var c = ""; + if (node[apf.TAGNAME] == "model" && node.childNodes.length) { + aout.push("data", '"' + apf.serializeChildren(node).trim().replace(/"/g, "\\\"").replace(/\r?\n/g, "\\\n") + '"'); + } + else if (childLength) + var c = recur(node.childNodes, depth+2, aout); + + max = Math.max(c ? 10 : 4, max); + + max++; + + for (j = 0, jl = aout.length; j < jl; j+=2) { + output.push(pre + " " + aout[j].pad(max, " ", apf.PAD_RIGHT) + + ": " + aout[j+1] + (j != jl - 2 || c ? "," : "")); + } + + if (c) { + output.push(pre + " " + "childNodes".pad(max, " ", apf.PAD_RIGHT) + ": ["); + output.push(c.substr(0, c.length - 1)); + output.push(pre + " ]"); + } + + if (childLength || !depth) + output.push(pre + "})" + (depth == 0 ? ";" : (i == l - 1 ? "" : ","))) + } + + return output.join("\n") + (depth == 0 ? "\n\n" + script.join("").trim() : ""); + })(xml.childNodes, 0); + + return x + "\n\n"; +} + +apf.highlightJs = function(x, notrim){ + if (!notrim) { + var lines = x.split(/\n\r?/); + for (var min = 1000, i = 0, l = lines.length; i < l; i++){ + min = Math.min(min, lines[i].match(/^(\s+)?[^\s]/) && RegExp.$1.length || 1000); + if (!min) break; + } + x = x.replace(new RegExp("^ {" + min + "}", "gm"), ""); + } + + return x.replace(/&/g, "&").replace(/' + f + '' + fws; + else if (co || colong) + return '' + (co || colong) + ''; + else if (str1 || str2) + return '' + (str1 || str2) + ''; + else if (nw) + return nw + '' + kw + '' + nw2; + }); +} + +/** + * Syntax highlights a code string using html. + * @param {String} strCode the code to highlight. + * @return {String} the highlighted string. + */ +apf.highlightCode2 = function(strCode){ + var comment=[],str=[]; + return strCode + .replace(/(\/\*[\s\S]*?\*\/|\/\/.*)/g, function(a){ comment.push(a); return '###n'+(comment.length-1)+'###';}) + .replace(/\"([\s\S]*?)\"/g, function(a,b){ str.push(b); return '###s'+(str.length-1)+'###';}) + .replace(/\'([\s\S]*?)\'/g, function(a,b){ str.push(b); return '###q'+(str.length-1)+'###';}) + .replace(/(\<)|(\>)/g,function(n,a,b){ return ""+(a?'@lt@':'@gt@')+""}) + .replace(/(\W)-?([\d\.]+)(\W)/g, "$1$2$3") + .replace(/([\|\&\=\;\,\:\?\+\*\-]+)/g, "$1") + .replace(/(\W)(break|continue|do|for|import|new|this|void|case|default|else|function|in|return|typeof|while|comment|delete|export|if|label|switch|var|with|abstract|implements|protected|boolean|instanceOf|public|byte|int|short|char|interface|static|double|long|synchronized|false|native|throws|final|null|transient|float|package|true|goto|private|catch|enum|throw|class|extends|try|const|finally|debugger|super)(\W)/g, + "$1$2$3") + .replace(/([\(\)\{\}\[\]])/g, "$1") + .replace(/###n(\d+)###/g,function(a,b){ return ""+comment[b].escapeHTML()+""; } ) + .replace(/###s(\d+)###/g,function(a,b){ return "\""+str[b].escapeHTML()+"\""; } ) + .replace(/###q(\d+)###/g,function(a,b){ return "'"+str[b].escapeHTML()+"'"; } ) + .replace(/stylecolor(.*?)\>/g,"style='color:$1'>") + .replace(/@(.*?)@/g,"&$1;"); +} + +/** + * Formats a javascript string with good indentation. Also known as pretty printing. + * @param {String} strJs the javascript to format. + * @return {String} the formatted string. + */ +apf.formatJS = function(strJs){ + var d = 0, r = 0; + var comment=[],str=[]; + return strJs + .replace(/(\/\*[\s\S]*?\*\/|\/\/.*)/g, function(a){ comment.push(a); return '###n'+(comment.length-1)+'###';}) + .replace(/\"([\s\S]*?)\"/g, function(a,b){ str.push(b); return '###s'+(str.length-1)+'###';}) + .replace(/\'([\s\S]*?)\'/g, function(a,b){ str.push(b); return '###q'+(str.length-1)+'###';}) + .replace(/;+/g, ';').replace(/{;/g, '{').replace(/({)|(})|(\()|(\))|(;)/g, + function(m, co, cc, ro, rc, e){ + if (co) d++; + if (cc) d--; + if (ro){ r++; return ro;} + if (rc){ r--; return rc;} + + var o = ''; + for (var i = 0; i < d; i++) + o += '\t'; + if (co) return '{\n' + o; + if (cc) return '\n' + o + '}'; + if (e) return (r>0)?e:(';\n' + o); + }).replace(/;\s*\n\s*\n/g, ';\n').replace(/}var/g, '}\nvar') + .replace(/([\n\s]*)###n(\d+)###[\n\s]*/g,function(a,b,c){ return b+comment[c]+b; } ) + .replace(/###s(\d+)###/g,function(a,b,c){ return "\""+str[b]+"\""; } ) + .replace(/###q(\d+)###/g,function(a,b,c){ return "'"+str[b]+"'"; } ); +}; + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/core/lib/util/textdiff.js)SIZE(89290)TIME(Mon, 10 Jan 2011 10:53:11 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + + +/** + * @fileoverview Computes the difference between two texts to create a patch. + * Applies the patch onto another text, allowing for errors. + * @author fraser AT google DOT com (Neil Fraser) + */ + +/** + * Class containing the diff, match and patch methods. + * @constructor + */ +apf.diff_match_patch = new (function() { + + // Defaults. + // Redefine these in your program to override the defaults. + + // Number of seconds to map a diff before giving up (0 for infinity). + this.diffTimeout = 1.0; + // Cost of an empty edit operation in terms of edit characters. + this.diffEditCost = 4; + // The size beyond which the double-ended diff activates. + // Double-ending is twice as fast, but less accurate. + this.diffDualThreshold = 32; + // At what point is no match declared (0.0 = perfection, 1.0 = very loose). + this.matchThreshold = 0.5; + // How far to search for a match (0 = exact location, 1000+ = broad match). + // A match this many characters away from the expected location will add + // 1.0 to the score (0.0 is a perfect match). + this.matchDistance = 1000; + // When deleting a large block of text (over ~64 characters), how close does + // the contents have to match the expected contents. (0.0 = perfection, + // 1.0 = very loose). Note that matchThreshold controls how closely the + // end points of a delete need to match. + this.patchDeleteThreshold = 0.5; + // Chunk size for context length. + this.patchMargin = 4; + + /** + * The data structure representing a diff is an array of tuples: + * [[DIFF_DELETE, "Hello"], [DIFF_INSERT, "Goodbye"], [DIFF_EQUAL, " world."]] + * which means: delete 'Hello', add 'Goodbye' and keep ' world.' + */ + var DIFF_DELETE = -1, + DIFF_INSERT = 1, + DIFF_EQUAL = 0, + /* + * Compute the number of bits in an int. + * The normal answer for JavaScript is 32. + * @return {number} Max bits + */ + getMaxBits = function() { + var maxbits = 0, + oldi = 1, + newi = 2; + while (oldi != newi) { + maxbits++; + oldi = newi; + newi = newi << 1; + } + return maxbits; + }, + /** + * Class representing one patch operation. + * @constructor + */ + patch_obj = function () { + /** @type {Array.>} */ + this.diffs = []; + /** @type {number?} */ + this.start1 = null; + /** @type {number?} */ + this.start2 = null; + /** @type {number} */ + this.length1 = 0; + /** @type {number} */ + this.length2 = 0; + }, + opMap = []; + opMap[DIFF_INSERT] = "+"; + opMap[DIFF_DELETE] = "-"; + opMap[DIFF_EQUAL] = " "; + + /** + * Emmulate GNU diff's format. + * Header: @@ -382,8 +481,9 @@ + * Indicies are printed as 1-based, not 0-based. + * @return {string} The GNU diff string. + */ + patch_obj.prototype.toString = function() { + var coords1 = (this.length1 === 0) + ? this.start1 + ",0" + : (this.length1 == 1) + ? this.start1 + 1 + : (this.start1 + 1) + "," + this.length1, + + coords2 = (this.length2 === 0) + ? this.start2 + ",0" + : (this.length2 == 1) + ? this.start2 + 1 + : (this.start2 + 1) + "," + this.length2, + + text = ["@@ -" + coords1 + " +" + coords2 + " @@\n"], + x = 0, + l = this.diffs.length; + // Escape the body of the patch with %xx notation. + for (; x < l; x++) + text[x + 1] = opMap[this.diffs[x][0]] + encodeURI(this.diffs[x][1]) + "\n"; + // Opera doesn't know how to encode char 0. + return text.join("").replace(/\x00/g, "%00").replace(/%20/g, " "); + }; + + // How many bits in a number? + this.matchMaxBits = getMaxBits(); + + + /** + * Find the differences between two texts. Simplifies the problem by stripping + * any common prefix or suffix off the texts before diffing. + * @param {string} text1 Old string to be diffed. + * @param {string} text2 New string to be diffed. + * @param {boolean} opt_checklines Optional speedup flag. If present and false, + * then don't run a line-level diff first to identify the changed areas. + * Defaults to true, which does a faster, slightly less optimal diff + * @return {Array.>} Array of diff tuples. + */ + this.diff_main = function(text1, text2, opt_checklines) { + // Check for equality (speedup) + if (text1 == text2) + return [[DIFF_EQUAL, text1]]; + + if (typeof opt_checklines == "undefined") + opt_checklines = true; + + var checklines = opt_checklines, + // Trim off common prefix (speedup) + commonlength = this.diff_commonPrefix(text1, text2), + commonprefix = text1.substring(0, commonlength); + + text1 = text1.substring(commonlength); + text2 = text2.substring(commonlength); + + // Trim off common suffix (speedup) + commonlength = this.diff_commonSuffix(text1, text2); + var commonsuffix = text1.substring(text1.length - commonlength); + text1 = text1.substring(0, text1.length - commonlength); + text2 = text2.substring(0, text2.length - commonlength); + + // Compute the diff on the middle block + var diffs = diff_compute.call(this, text1, text2, checklines); + + // Restore the prefix and suffix + if (commonprefix) + diffs.unshift([DIFF_EQUAL, commonprefix]); + if (commonsuffix) + diffs.push([DIFF_EQUAL, commonsuffix]); + this.diff_cleanupMerge(diffs); + return diffs; + }; + + + /** + * Find the differences between two texts. Assumes that the texts do not + * have any common prefix or suffix. + * @param {string} text1 Old string to be diffed. + * @param {string} text2 New string to be diffed. + * @param {boolean} checklines Speedup flag. If false, then don't run a + * line-level diff first to identify the changed areas. + * If true, then run a faster, slightly less optimal diff + * @return {Array.>} Array of diff tuples. + * @private + */ + var diff_compute = function(text1, text2, checklines) { + var diffs; + + if (!text1) // Just add some text (speedup) + return [[DIFF_INSERT, text2]]; + + if (!text2) // Just delete some text (speedup) + return [[DIFF_DELETE, text1]]; + + var longtext = text1.length > text2.length ? text1 : text2, + shorttext = text1.length > text2.length ? text2 : text1, + i = longtext.indexOf(shorttext); + if (i != -1) { + // Shorter text is inside the longer text (speedup) + diffs = [[DIFF_INSERT, longtext.substring(0, i)], + [DIFF_EQUAL, shorttext], + [DIFF_INSERT, longtext.substring(i + shorttext.length)]]; + // Swap insertions for deletions if diff is reversed. + if (text1.length > text2.length) + diffs[0][0] = diffs[2][0] = DIFF_DELETE; + return diffs; + } + longtext = shorttext = null; // Garbage collect + + // Check to see if the problem can be split in two. + var a, linearray, + hm = this.diff_halfMatch(text1, text2); + if (hm) { + // A half-match was found, sort out the return data. + //text1_a = hm[0], + //text1_b = hm[1], + //text2_a = hm[2], + //text2_b = hm[3], + //mid_common = hm[4], + // Send both pairs off for separate processing. + //var diffs_a = this.diff_main(hm[0], hm[2], checklines), + // diffs_b = this.diff_main(hm[1], hm[3], checklines); + // Merge the results. + return this.diff_main(hm[0], hm[2], checklines) + .concat([[DIFF_EQUAL, hm[4]]], this.diff_main(hm[1], hm[3], checklines)); + } + + // Perform a real diff. + if (checklines && (text1.length < 100 || text2.length < 100)) // Too trivial for the overhead. + checklines = false; + + if (checklines) { + // Scan the text on a line-by-line basis first. + a = this.diff_linesToChars(text1, text2); + text1 = a[0]; + text2 = a[1]; + linearray = a[2]; + } + diffs = this.diff_map(text1, text2); + if (!diffs) // No acceptable result. + diffs = [[DIFF_DELETE, text1], [DIFF_INSERT, text2]]; + if (checklines) { + // Convert the diff back to original text. + this.diff_charsToLines(diffs, linearray); + // Eliminate freak matches (e.g. blank lines) + this.diff_cleanupSemantic(diffs); + + // Rediff any replacement blocks, this time character-by-character. + // Add a dummy entry at the end. + diffs.push([DIFF_EQUAL, ""]); + var pointer = 0, + count_delete = 0, + count_insert = 0, + text_delete = "", + text_insert = ""; + while (pointer < diffs.length) { + switch (diffs[pointer][0]) { + case DIFF_INSERT: + count_insert++; + text_insert += diffs[pointer][1]; + break; + case DIFF_DELETE: + count_delete++; + text_delete += diffs[pointer][1]; + break; + case DIFF_EQUAL: + // Upon reaching an equality, check for prior redundancies. + if (count_delete >= 1 && count_insert >= 1) { + // Delete the offending records and add the merged ones. + a = this.diff_main(text_delete, text_insert, false); + diffs.splice(pointer - count_delete - count_insert, + count_delete + count_insert); + pointer = pointer - count_delete - count_insert; + for (var j = a.length - 1; j >= 0; j--) { + diffs.splice(pointer, 0, a[j]); + } + pointer = pointer + a.length; + } + count_insert = 0; + count_delete = 0; + text_delete = ""; + text_insert = ""; + break; + } + pointer++; + } + diffs.pop(); // Remove the dummy entry at the end. + } + return diffs; + }; + + /** + * Split two texts into an array of strings. Reduce the texts to a string of + * hashes where each Unicode character represents one line. + * @param {string} text1 First string. + * @param {string} text2 Second string. + * @return {Array.>} Three element Array, containing the + * encoded text1, the encoded text2 and the array of unique strings. The + * zeroth element of the array of unique strings is intentionally blank. + * @private + */ + this.diff_linesToChars = function(text1, text2) { + // '\x00' is a valid character, but various debuggers don't like it. + // So we'll insert a junk entry to avoid generating a null character. + var lineArray = [""], // e.g. lineArray[4] == "Hello\n" + lineHash = {}; // e.g. lineHash["Hello\n"] == 4 + + /** + * Split a text into an array of strings. Reduce the texts to a string of + * hashes where each Unicode character represents one line. + * Modifies linearray and linehash through being a closure. + * @param {string} text String to encode + * @return {string} Encoded string + * @private + */ + function diff_linesToCharsMunge(text) { + var chars = "", + // Walk the text, pulling out a substring for each line. + // text.split("\n") would would temporarily double our memory footprint. + // Modifying text would create many large strings to garbage collect. + lineStart = 0, + lineEnd = -1, + // Keeping our own length variable is faster than looking it up. + lineArrayLength = lineArray.length; + while (lineEnd < text.length - 1) { + lineEnd = text.indexOf("\n", lineStart); + if (lineEnd == -1) + lineEnd = text.length - 1; + var line = text.substring(lineStart, lineEnd + 1); + lineStart = lineEnd + 1; + + if (lineHash.hasOwnProperty + ? lineHash.hasOwnProperty(line) + : (lineHash[line] !== undefined)) { + chars += String.fromCharCode(lineHash[line]); + } + else { + chars += String.fromCharCode(lineArrayLength); + lineHash[line] = lineArrayLength; + lineArray[lineArrayLength++] = line; + } + } + return chars; + } + + return [diff_linesToCharsMunge(text1), diff_linesToCharsMunge(text2), + lineArray]; + }; + + /** + * Rehydrate the text in a diff from a string of line hashes to real lines of + * text. + * @param {Array.>} diffs Array of diff tuples. + * @param {Array.} lineArray Array of unique strings. + * @private + */ + this.diff_charsToLines = function(diffs, lineArray) { + var x = 0, + chars, text, y; + for (; x < diffs.length; x++) { + chars = diffs[x][1]; + text = []; + for (y = 0; y < chars.length; y++) + text[y] = lineArray[chars.charCodeAt(y)]; + diffs[x][1] = text.join(""); + } + }; + + /** + * Explore the intersection points between the two texts. + * @param {string} text1 Old string to be diffed. + * @param {string} text2 New string to be diffed. + * @return {Array.>?} Array of diff tuples or null if no + * diff available. + * @private + */ + this.diff_map = function(text1, text2) { + // Don't run for too long. + var ms_end = (new Date()).getTime() + this.diffTimeout * 1000, + // Cache the text lengths to prevent multiple calls. + text1_length = text1.length, + text2_length = text2.length, + d = 0, + max_d = text1_length + text2_length - 1, + doubleEnd = this.diffDualThreshold * 2 < max_d, + v_map1 = [], + v_map2 = [], + v1 = {}, + v2 = {}, + footsteps = {}, + done = false, + // Safari 1.x doesn't have hasOwnProperty + hasOwnProperty = !!(footsteps.hasOwnProperty), + // If the total number of characters is odd, then the front path will collide + // with the reverse path. + front = (text1_length + text2_length) % 2, + x, y, k, a, footstep; // Used to track overlapping paths. + v1[1] = 0; + v2[1] = 0; + for (; d < max_d; d++) { + // Bail out if timeout reached. + if (this.diffTimeout > 0 && (new Date()).getTime() > ms_end) + return null; + + // Walk the front path one step. + v_map1[d] = {}; + for (k = -d; k <= d; k += 2) { + if (k == -d || k != d && v1[k - 1] < v1[k + 1]) + x = v1[k + 1]; + else + x = v1[k - 1] + 1; + y = x - k; + if (doubleEnd) { + footstep = x + "," + y; + if (front && (hasOwnProperty + ? footsteps.hasOwnProperty(footstep) + : (footsteps[footstep] !== undefined))) { + done = true; + } + if (!front) + footsteps[footstep] = d; + } + while (!done && x < text1_length && y < text2_length && + text1.charAt(x) == text2.charAt(y)) { + x++; + y++; + if (doubleEnd) { + footstep = x + "," + y; + if (front && (hasOwnProperty + ? footsteps.hasOwnProperty(footstep) + : (footsteps[footstep] !== undefined))) { + done = true; + } + if (!front) + footsteps[footstep] = d; + } + } + v1[k] = x; + v_map1[d][x + "," + y] = true; + if (x == text1_length && y == text2_length) { + // Reached the end in single-path mode. + return this.diff_path1(v_map1, text1, text2); + } + else if (done) { + // Front path ran over reverse path. + v_map2 = v_map2.slice(0, footsteps[footstep] + 1); + a = this.diff_path1(v_map1, text1.substring(0, x), + text2.substring(0, y)); + return a.concat(this.diff_path2(v_map2, text1.substring(x), + text2.substring(y))); + } + } + + if (doubleEnd) { + // Walk the reverse path one step. + v_map2[d] = {}; + for (k = -d; k <= d; k += 2) { + x = (k == -d || k != d && v2[k - 1] < v2[k + 1]) + ? v2[k + 1] + : v2[k - 1] + 1; + y = x - k; + footstep = (text1_length - x) + "," + (text2_length - y); + if (!front && (hasOwnProperty + ? footsteps.hasOwnProperty(footstep) + : (footsteps[footstep] !== undefined))) { + done = true; + } + if (front) + footsteps[footstep] = d; + while (!done && x < text1_length && y < text2_length + && text1.charAt(text1_length - x - 1) == text2.charAt(text2_length - y - 1)) { + x++; + y++; + footstep = (text1_length - x) + "," + (text2_length - y); + if (!front && (hasOwnProperty + ? footsteps.hasOwnProperty(footstep) + : (footsteps[footstep] !== undefined))) { + done = true; + } + if (front) + footsteps[footstep] = d; + } + v2[k] = x; + v_map2[d][x + "," + y] = true; + if (done) { + // Reverse path ran over front path. + v_map1 = v_map1.slice(0, footsteps[footstep] + 1); + a = this.diff_path1(v_map1, text1.substring(0, text1_length - x), + text2.substring(0, text2_length - y)); + return a.concat(this.diff_path2(v_map2, + text1.substring(text1_length - x), + text2.substring(text2_length - y))); + } + } + } + } + // Number of diffs equals number of characters, no commonality at all. + return null; + }; + + /** + * Work from the middle back to the start to determine the path. + * @param {Array.} v_map Array of paths. + * @param {string} text1 Old string fragment to be diffed. + * @param {string} text2 New string fragment to be diffed. + * @return {Array.>} Array of diff tuples. + * @private + */ + this.diff_path1 = function(v_map, text1, text2) { + var path = [], + x = text1.length, + y = text2.length, + /** @type {number?} */ + last_op = null, + d = v_map.length - 2; + for (; d >= 0; d--) { + while (1) { + if (v_map[d].hasOwnProperty + ? v_map[d].hasOwnProperty((x - 1) + "," + y) + : (v_map[d][(x - 1) + "," + y] !== undefined)) { + x--; + if (last_op === DIFF_DELETE) + path[0][1] = text1.charAt(x) + path[0][1]; + else + path.unshift([DIFF_DELETE, text1.charAt(x)]); + last_op = DIFF_DELETE; + break; + } + else if (v_map[d].hasOwnProperty + ? v_map[d].hasOwnProperty(x + "," + (y - 1)) + : (v_map[d][x + "," + (y - 1)] !== undefined)) { + y--; + if (last_op === DIFF_INSERT) + path[0][1] = text2.charAt(y) + path[0][1]; + else + path.unshift([DIFF_INSERT, text2.charAt(y)]); + last_op = DIFF_INSERT; + break; + } + else { + x--; + y--; + //if (text1.charAt(x) != text2.charAt(y)) { + // throw new Error("No diagonal. Can't happen. (diff_path1)"); + //} + if (last_op === DIFF_EQUAL) + path[0][1] = text1.charAt(x) + path[0][1]; + else + path.unshift([DIFF_EQUAL, text1.charAt(x)]); + last_op = DIFF_EQUAL; + } + } + } + return path; + }; + + /** + * Work from the middle back to the end to determine the path. + * @param {Array.} v_map Array of paths. + * @param {string} text1 Old string fragment to be diffed. + * @param {string} text2 New string fragment to be diffed. + * @return {Array.>} Array of diff tuples. + * @private + */ + this.diff_path2 = function(v_map, text1, text2) { + var path = [], + pathLength = 0, + x = text1.length, + y = text2.length, + /** @type {number?} */ + last_op = null, + d = v_map.length - 2; + for (; d >= 0; d--) { + while (1) { + if (v_map[d].hasOwnProperty + ? v_map[d].hasOwnProperty((x - 1) + "," + y) + : (v_map[d][(x - 1) + "," + y] !== undefined)) { + x--; + if (last_op === DIFF_DELETE) { + path[pathLength - 1][1] += text1.charAt(text1.length - x - 1); + } + else { + path[pathLength++] = + [DIFF_DELETE, text1.charAt(text1.length - x - 1)]; + } + last_op = DIFF_DELETE; + break; + } + else if (v_map[d].hasOwnProperty + ? v_map[d].hasOwnProperty(x + "," + (y - 1)) + : (v_map[d][x + "," + (y - 1)] !== undefined)) { + y--; + if (last_op === DIFF_INSERT) { + path[pathLength - 1][1] += text2.charAt(text2.length - y - 1); + } + else { + path[pathLength++] = + [DIFF_INSERT, text2.charAt(text2.length - y - 1)]; + } + last_op = DIFF_INSERT; + break; + } + else { + x--; + y--; + //if (text1.charAt(text1.length - x - 1) != + // text2.charAt(text2.length - y - 1)) { + // throw new Error("No diagonal. Can't happen. (diff_path2)"); + //} + if (last_op === DIFF_EQUAL) { + path[pathLength - 1][1] += text1.charAt(text1.length - x - 1); + } + else { + path[pathLength++] = + [DIFF_EQUAL, text1.charAt(text1.length - x - 1)]; + } + last_op = DIFF_EQUAL; + } + } + } + return path; + }; + + /** + * Determine the common prefix of two strings + * @param {string} text1 First string. + * @param {string} text2 Second string. + * @return {number} The number of characters common to the start of each + * string. + */ + this.diff_commonPrefix = function(text1, text2) { + // Quick check for common null cases. + if (!text1 || !text2 || text1.charCodeAt(0) !== text2.charCodeAt(0)) + return 0; + // Binary search. + // Performance analysis: http://neil.fraser.name/news/2007/10/09/ + var pointermin = 0, + pointermax = Math.min(text1.length, text2.length), + pointermid = pointermax, + pointerstart = 0; + while (pointermin < pointermid) { + if (text1.substring(pointerstart, pointermid) + == text2.substring(pointerstart, pointermid)) { + pointermin = pointermid; + pointerstart = pointermin; + } + else { + pointermax = pointermid; + } + pointermid = Math.floor((pointermax - pointermin) / 2 + pointermin); + } + return pointermid; + }; + + /** + * Determine the common suffix of two strings + * @param {string} text1 First string. + * @param {string} text2 Second string. + * @return {number} The number of characters common to the end of each string. + */ + this.diff_commonSuffix = function(text1, text2) { + // Quick check for common null cases. + if (!text1 || !text2 || text1.charCodeAt(text1.length - 1) + !== text2.charCodeAt(text2.length - 1)) { + return 0; + } + // Binary search. + // Performance analysis: http://neil.fraser.name/news/2007/10/09/ + var pointermin = 0, + pointermax = Math.min(text1.length, text2.length), + pointermid = pointermax, + pointerend = 0; + while (pointermin < pointermid) { + if (text1.substring(text1.length - pointermid, text1.length - pointerend) + == text2.substring(text2.length - pointermid, text2.length - pointerend)) { + pointermin = pointermid; + pointerend = pointermin; + } + else { + pointermax = pointermid; + } + pointermid = Math.floor((pointermax - pointermin) / 2 + pointermin); + } + return pointermid; + }; + + /** + * Do the two texts share a substring which is at least half the length of the + * longer text? + * @param {string} text1 First string. + * @param {string} text2 Second string. + * @return {Array.?} Five element Array, containing the prefix of + * text1, the suffix of text1, the prefix of text2, the suffix of + * text2 and the common middle. Or null if there was no match. + */ + this.diff_halfMatch = function(text1, text2) { + var longtext = text1.length > text2.length ? text1 : text2, + shorttext = text1.length > text2.length ? text2 : text1, + _self = this; + if (longtext.length < 10 || shorttext.length < 1) + return null; // Pointless. + + /** + * Does a substring of shorttext exist within longtext such that the substring + * is at least half the length of longtext? + * Closure, but does not reference any external variables. + * @param {string} longtext Longer string. + * @param {string} shorttext Shorter string. + * @param {number} i Start index of quarter length substring within longtext + * @return {Array.?} Five element Array, containing the prefix of + * longtext, the suffix of longtext, the prefix of shorttext, the suffix + * of shorttext and the common middle. Or null if there was no match. + * @private + */ + function diff_halfMatchI(longtext, shorttext, i) { + // Start with a 1/4 length substring at position i as a seed. + var seed = longtext.substring(i, i + Math.floor(longtext.length / 4)), + j = -1, + best_common = "", + best_longtext_a, best_longtext_b, best_shorttext_a, best_shorttext_b, + prefixLength, suffixLength; + while ((j = shorttext.indexOf(seed, j + 1)) != -1) { + prefixLength = _self.diff_commonPrefix(longtext.substring(i), + shorttext.substring(j)); + suffixLength = _self.diff_commonSuffix(longtext.substring(0, i), + shorttext.substring(0, j)); + if (best_common.length < suffixLength + prefixLength) { + best_common = shorttext.substring(j - suffixLength, j) + + shorttext.substring(j, j + prefixLength); + best_longtext_a = longtext.substring(0, i - suffixLength); + best_longtext_b = longtext.substring(i + prefixLength); + best_shorttext_a = shorttext.substring(0, j - suffixLength); + best_shorttext_b = shorttext.substring(j + prefixLength); + } + } + if (best_common.length >= longtext.length / 2) { + return [best_longtext_a, best_longtext_b, best_shorttext_a, + best_shorttext_b, best_common]; + } + else { + return null; + } + } + + // First check if the second quarter is the seed for a half-match. + var hm1 = diff_halfMatchI(longtext, shorttext, Math.ceil(longtext.length / 4)), + // Check again based on the third quarter. + hm2 = diff_halfMatchI(longtext, shorttext, Math.ceil(longtext.length / 2)), + hm; + if (!hm1 && !hm2) + return null; + else if (!hm2) + hm = hm1; + else if (!hm1) + hm = hm2; + else // Both matched. Select the longest. + hm = hm1[4].length > hm2[4].length ? hm1 : hm2; + + // A half-match was found, sort out the return data. + var text1_a, text1_b, text2_a, text2_b; + if (text1.length > text2.length) { + text1_a = hm[0]; + text1_b = hm[1]; + text2_a = hm[2]; + text2_b = hm[3]; + } + else { + text2_a = hm[0]; + text2_b = hm[1]; + text1_a = hm[2]; + text1_b = hm[3]; + } + //var mid_common = hm[4]; + return [text1_a, text1_b, text2_a, text2_b, hm[4]]; + }; + + /** + * Reduce the number of edits by eliminating semantically trivial equalities. + * @param {Array.>} diffs Array of diff tuples. + */ + this.diff_cleanupSemantic = function(diffs) { + var changes = false, + equalities = [], // Stack of indices where equalities are found. + equalitiesLength = 0, // Keeping our own length var is faster in JS. + lastequality = null, // Always equal to equalities[equalitiesLength-1][1] + pointer = 0, // Index of current position. + // Number of characters that changed prior to the equality. + length_changes1 = 0, + // Number of characters that changed after the equality. + length_changes2 = 0; + while (pointer < diffs.length) { + if (diffs[pointer][0] == DIFF_EQUAL) { // equality found + equalities[equalitiesLength++] = pointer; + length_changes1 = length_changes2; + length_changes2 = 0; + lastequality = diffs[pointer][1]; + } + else { // an insertion or deletion + length_changes2 += diffs[pointer][1].length; + if (lastequality !== null && (lastequality.length <= length_changes1) + && (lastequality.length <= length_changes2)) { + // Duplicate record + diffs.splice(equalities[equalitiesLength - 1], 0, + [DIFF_DELETE, lastequality]); + // Change second copy to insert. + diffs[equalities[equalitiesLength - 1] + 1][0] = DIFF_INSERT; + // Throw away the equality we just deleted. + equalitiesLength--; + // Throw away the previous equality (it needs to be reevaluated). + equalitiesLength--; + pointer = equalitiesLength > 0 ? equalities[equalitiesLength - 1] : -1; + length_changes1 = 0; // Reset the counters. + length_changes2 = 0; + lastequality = null; + changes = true; + } + } + pointer++; + } + if (changes) + this.diff_cleanupMerge(diffs); + this.diff_cleanupSemanticLossless(diffs); + }; + + /** + * Look for single edits surrounded on both sides by equalities + * which can be shifted sideways to align the edit to a word boundary. + * e.g: The cat came. -> The cat came. + * @param {Array.>} diffs Array of diff tuples. + */ + this.diff_cleanupSemanticLossless = function(diffs) { + // Define some regex patterns for matching boundaries. + var punctuation = /[^a-zA-Z0-9]/, + whitespace = /\s/, + linebreak = /[\r\n]/, + blanklineEnd = /\n\r?\n$/, + blanklineStart = /^\r?\n\r?\n/; + + /** + * Given two strings, compute a score representing whether the internal + * boundary falls on logical boundaries. + * Scores range from 5 (best) to 0 (worst). + * Closure, makes reference to regex patterns defined above. + * @param {string} one First string + * @param {string} two Second string + * @return {number} The score. + */ + function diff_cleanupSemanticScore(one, two) { + if (!one || !two) // Edges are the best. + return 5; + + // Each port of this function behaves slightly differently due to + // subtle differences in each language's definition of things like + // 'whitespace'. Since this function's purpose is largely cosmetic, + // the choice has been made to use each language's native features + // rather than force total conformity. + var score = 0; + // One point for non-alphanumeric. + if (one.charAt(one.length - 1).match(punctuation) + || two.charAt(0).match(punctuation)) { + score++; + // Two points for whitespace. + if (one.charAt(one.length - 1).match(whitespace) + || two.charAt(0).match(whitespace)) { + score++; + // Three points for line breaks. + if (one.charAt(one.length - 1).match(linebreak) + || two.charAt(0).match(linebreak)) { + score++; + // Four points for blank lines. + if (one.match(blanklineEnd) || two.match(blanklineStart)) + score++; + } + } + } + return score; + } + + var pointer = 1, + equality1, edit, equality2, commonOffset, commonString, bestEquality1, + bestEdit, bestEquality2, bestScore, score; + // Intentionally ignore the first and last element (don't need checking). + while (pointer < diffs.length - 1) { + if (diffs[pointer - 1][0] == DIFF_EQUAL && diffs[pointer + 1][0] == DIFF_EQUAL) { + // This is a single edit surrounded by equalities. + equality1 = diffs[pointer - 1][1]; + edit = diffs[pointer][1]; + equality2 = diffs[pointer + 1][1]; + + // First, shift the edit as far left as possible. + commonOffset = this.diff_commonSuffix(equality1, edit); + if (commonOffset) { + commonString = edit.substring(edit.length - commonOffset); + equality1 = equality1.substring(0, equality1.length - commonOffset); + edit = commonString + edit.substring(0, edit.length - commonOffset); + equality2 = commonString + equality2; + } + + // Second, step character by character right, looking for the best fit. + bestEquality1 = equality1; + bestEdit = edit; + bestEquality2 = equality2; + bestScore = diff_cleanupSemanticScore(equality1, edit) + + diff_cleanupSemanticScore(edit, equality2); + while (edit.charAt(0) === equality2.charAt(0)) { + equality1 += edit.charAt(0); + edit = edit.substring(1) + equality2.charAt(0); + equality2 = equality2.substring(1); + score = diff_cleanupSemanticScore(equality1, edit) + + diff_cleanupSemanticScore(edit, equality2); + // The >= encourages trailing rather than leading whitespace on edits. + if (score >= bestScore) { + bestScore = score; + bestEquality1 = equality1; + bestEdit = edit; + bestEquality2 = equality2; + } + } + + if (diffs[pointer - 1][1] != bestEquality1) { + // We have an improvement, save it back to the diff. + if (bestEquality1) { + diffs[pointer - 1][1] = bestEquality1; + } + else { + diffs.splice(pointer - 1, 1); + pointer--; + } + diffs[pointer][1] = bestEdit; + if (bestEquality2) { + diffs[pointer + 1][1] = bestEquality2; + } + else { + diffs.splice(pointer + 1, 1); + pointer--; + } + } + } + pointer++; + } + }; + + /** + * Reduce the number of edits by eliminating operationally trivial equalities. + * @param {Array.>} diffs Array of diff tuples. + */ + this.diff_cleanupEfficiency = function(diffs) { + var changes = false, + equalities = [], // Stack of indices where equalities are found. + equalitiesLength = 0, // Keeping our own length var is faster in JS. + lastequality = "", // Always equal to equalities[equalitiesLength-1][1] + pointer = 0, // Index of current position. + // Is there an insertion operation before the last equality. + pre_ins = false, + // Is there a deletion operation before the last equality. + pre_del = false, + // Is there an insertion operation after the last equality. + post_ins = false, + // Is there a deletion operation after the last equality. + post_del = false; + while (pointer < diffs.length) { + if (diffs[pointer][0] == DIFF_EQUAL) { // equality found + if (diffs[pointer][1].length < this.diffEditCost + && (post_ins || post_del)) { + // Candidate found. + equalities[equalitiesLength++] = pointer; + pre_ins = post_ins; + pre_del = post_del; + lastequality = diffs[pointer][1]; + } + else { + // Not a candidate, and can never become one. + equalitiesLength = 0; + lastequality = ""; + } + post_ins = post_del = false; + } + else { // an insertion or deletion + if (diffs[pointer][0] == DIFF_DELETE) + post_del = true; + else + post_ins = true; + /* + * Five types to be split: + * ABXYCD + * AXCD + * ABXC + * AXCD + * ABXC + */ + if (lastequality && ((pre_ins && pre_del && post_ins && post_del) + || ((lastequality.length < this.diffEditCost / 2) + && (pre_ins + pre_del + post_ins + post_del) == 3))) { + // Duplicate record + diffs.splice(equalities[equalitiesLength - 1], 0, + [DIFF_DELETE, lastequality]); + // Change second copy to insert. + diffs[equalities[equalitiesLength - 1] + 1][0] = DIFF_INSERT; + equalitiesLength--; // Throw away the equality we just deleted; + lastequality = ""; + if (pre_ins && pre_del) { + // No changes made which could affect previous entry, keep going. + post_ins = post_del = true; + equalitiesLength = 0; + } + else { + equalitiesLength--; // Throw away the previous equality; + pointer = equalitiesLength > 0 + ? equalities[equalitiesLength - 1] + : -1; + post_ins = post_del = false; + } + changes = true; + } + } + pointer++; + } + + if (changes) + this.diff_cleanupMerge(diffs); + }; + + /** + * Reorder and merge like edit sections. Merge equalities. + * Any edit section can move as long as it doesn't cross an equality. + * @param {Array.>} diffs Array of diff tuples. + */ + this.diff_cleanupMerge = function(diffs) { + diffs.push([DIFF_EQUAL, ""]); // Add a dummy entry at the end. + var pointer = 0, + count_delete = 0, + count_insert = 0, + text_delete = "", + text_insert = "", + commonlength; + while (pointer < diffs.length) { + switch (diffs[pointer][0]) { + case DIFF_INSERT: + count_insert++; + text_insert += diffs[pointer][1]; + pointer++; + break; + case DIFF_DELETE: + count_delete++; + text_delete += diffs[pointer][1]; + pointer++; + break; + case DIFF_EQUAL: + // Upon reaching an equality, check for prior redundancies. + if (count_delete !== 0 || count_insert !== 0) { + if (count_delete !== 0 && count_insert !== 0) { + // Factor out any common prefixies. + commonlength = this.diff_commonPrefix(text_insert, text_delete); + if (commonlength !== 0) { + if ((pointer - count_delete - count_insert) > 0 + && diffs[pointer - count_delete - count_insert - 1][0] + == DIFF_EQUAL) { + diffs[pointer - count_delete - count_insert - 1][1] += + text_insert.substring(0, commonlength); + } + else { + diffs.splice(0, 0, [DIFF_EQUAL, + text_insert.substring(0, commonlength)]); + pointer++; + } + text_insert = text_insert.substring(commonlength); + text_delete = text_delete.substring(commonlength); + } + // Factor out any common suffixies. + commonlength = this.diff_commonSuffix(text_insert, text_delete); + if (commonlength !== 0) { + diffs[pointer][1] = text_insert.substring(text_insert.length + - commonlength) + diffs[pointer][1]; + text_insert = text_insert.substring(0, text_insert.length + - commonlength); + text_delete = text_delete.substring(0, text_delete.length + - commonlength); + } + } + // Delete the offending records and add the merged ones. + if (count_delete === 0) { + diffs.splice(pointer - count_delete - count_insert, + count_delete + count_insert, [DIFF_INSERT, text_insert]); + } + else if (count_insert === 0) { + diffs.splice(pointer - count_delete - count_insert, + count_delete + count_insert, [DIFF_DELETE, text_delete]); + } + else { + diffs.splice(pointer - count_delete - count_insert, + count_delete + count_insert, [DIFF_DELETE, text_delete], + [DIFF_INSERT, text_insert]); + } + pointer = pointer - count_delete - count_insert + + (count_delete ? 1 : 0) + (count_insert ? 1 : 0) + 1; + } + else if (pointer !== 0 && diffs[pointer - 1][0] == DIFF_EQUAL) { + // Merge this equality with the previous one. + diffs[pointer - 1][1] += diffs[pointer][1]; + diffs.splice(pointer, 1); + } + else { + pointer++; + } + count_insert = 0; + count_delete = 0; + text_delete = ""; + text_insert = ""; + break; + } + } + if (diffs[diffs.length - 1][1] === "") + diffs.pop(); // Remove the dummy entry at the end. + + // Second pass: look for single edits surrounded on both sides by equalities + // which can be shifted sideways to eliminate an equality. + // e.g: ABAC -> ABAC + var changes = false; + pointer = 1; + // Intentionally ignore the first and last element (don't need checking). + while (pointer < diffs.length - 1) { + if (diffs[pointer - 1][0] == DIFF_EQUAL + && diffs[pointer + 1][0] == DIFF_EQUAL) { + // This is a single edit surrounded by equalities. + if (diffs[pointer][1].substring(diffs[pointer][1].length + - diffs[pointer - 1][1].length) == diffs[pointer - 1][1]) { + // Shift the edit over the previous equality. + diffs[pointer][1] = diffs[pointer - 1][1] + + diffs[pointer][1].substring(0, diffs[pointer][1].length + - diffs[pointer - 1][1].length); + diffs[pointer + 1][1] = diffs[pointer - 1][1] + diffs[pointer + 1][1]; + diffs.splice(pointer - 1, 1); + changes = true; + } + else if (diffs[pointer][1].substring(0, diffs[pointer + 1][1].length) + == diffs[pointer + 1][1]) { + // Shift the edit over the next equality. + diffs[pointer - 1][1] += diffs[pointer + 1][1]; + diffs[pointer][1] = diffs[pointer][1] + .substring(diffs[pointer + 1][1].length) + diffs[pointer + 1][1]; + diffs.splice(pointer + 1, 1); + changes = true; + } + } + pointer++; + } + // If shifts were made, the diff needs reordering and another shift sweep. + if (changes) + this.diff_cleanupMerge(diffs); + }; + + /** + * loc is a location in text1, compute and return the equivalent location in + * text2. + * e.g. 'The cat' vs 'The big cat', 1->1, 5->8 + * @param {Array.>} diffs Array of diff tuples. + * @param {number} loc Location within text1. + * @return {number} Location within text2. + */ + this.diff_xIndex = function(diffs, loc) { + var chars1 = 0, + chars2 = 0, + last_chars1 = 0, + last_chars2 = 0, + x = 0, + l = diffs.length; + for (; x < l; x++) { + if (diffs[x][0] !== DIFF_INSERT) // Equality or deletion. + chars1 += diffs[x][1].length; + if (diffs[x][0] !== DIFF_DELETE) // Equality or insertion. + chars2 += diffs[x][1].length; + if (chars1 > loc) // Overshot the location. + break; + last_chars1 = chars1; + last_chars2 = chars2; + } + // Was the location was deleted? + if (diffs.length != x && diffs[x][0] === DIFF_DELETE) + return last_chars2; + // Add the remaining character length. + return last_chars2 + (loc - last_chars1); + }; + + /** + * Convert a diff array into a pretty HTML report. + * @param {Array.>} diffs Array of diff tuples. + * @return {string} HTML representation. + */ + this.diff_prettyHtml = function(diffs) { + var html = [], + i = 0, + x = 0, + l = diffs.length, + op, data, text; + for (; x < l; x++) { + op = diffs[x][0]; // Operation (insert, delete, equal) + data = diffs[x][1]; // Text of change. + text = data.replace(/&/g, "&").replace(//g, ">").replace(/\n/g, "¶
"); + switch (op) { + case DIFF_INSERT: + html[x] = "" + text + ""; + break; + case DIFF_DELETE: + html[x] = "" + text + ""; + break; + case DIFF_EQUAL: + html[x] = "" + text + ""; + break; + } + if (op !== DIFF_DELETE) + i += data.length; + } + return html.join(""); + }; + + /** + * Compute and return the source text (all equalities and deletions). + * @param {Array.>} diffs Array of diff tuples. + * @return {string} Source text. + */ + this.diff_text1 = function(diffs) { + var text = [], + x = 0, + l = diffs.length; + for (; x < l; x++) { + if (diffs[x][0] !== DIFF_INSERT) + text[x] = diffs[x][1]; + } + return text.join(""); + }; + + /** + * Compute and return the destination text (all equalities and insertions). + * @param {Array.>} diffs Array of diff tuples. + * @return {string} Destination text. + */ + this.diff_text2 = function(diffs) { + var text = [], + x = 0, + l = diffs.length; + for (; x < l; x++) { + if (diffs[x][0] !== DIFF_DELETE) + text[x] = diffs[x][1]; + } + return text.join(""); + }; + + /** + * Compute the Levenshtein distance; the number of inserted, deleted or + * substituted characters. + * @param {Array.>} diffs Array of diff tuples. + * @return {number} Number of changes. + */ + this.diff_levenshtein = function(diffs) { + var levenshtein = 0, + insertions = 0, + deletions = 0, + x = 0, + l = diffs.length, + op, data; + for (; x < l; x++) { + op = diffs[x][0]; + data = diffs[x][1]; + switch (op) { + case DIFF_INSERT: + insertions += data.length; + break; + case DIFF_DELETE: + deletions += data.length; + break; + case DIFF_EQUAL: + // A deletion and an insertion is one substitution. + levenshtein += Math.max(insertions, deletions); + insertions = 0; + deletions = 0; + break; + } + } + return levenshtein + Math.max(insertions, deletions); + }; + + /** + * Crush the diff into an encoded string which describes the operations + * required to transform text1 into text2. + * E.g. =3\t-2\t+ing -> Keep 3 chars, delete 2 chars, insert 'ing'. + * Operations are tab-separated. Inserted text is escaped using %xx notation. + * @param {Array.>} diffs Array of diff tuples. + * @return {string} Delta text. + */ + this.diff_toDelta = function(diffs) { + var text = [], + x = 0, + l = diffs.length; + for (; x < l; x++) { + switch (diffs[x][0]) { + case DIFF_INSERT: + text[x] = "+" + encodeURI(diffs[x][1]); + break; + case DIFF_DELETE: + text[x] = "-" + diffs[x][1].length; + break; + case DIFF_EQUAL: + text[x] = "=" + diffs[x][1].length; + break; + } + } + // Opera doesn't know how to encode char 0. + return text.join("\t").replace(/\x00/g, "%00").replace(/%20/g, " "); + }; + + + /** + * Given the original text1, and an encoded string which describes the + * operations required to transform text1 into text2, compute the full diff. + * @param {string} text1 Source string for the diff. + * @param {string} delta Delta text. + * @return {Array.>} Array of diff tuples. + * @throws {Error} If invalid input. + */ + this.diff_fromDelta = function(text1, delta) { + var diffs = [], + diffsLength = 0, // Keeping our own length var is faster in JS. + pointer = 0; // Cursor in text1 + // Opera doesn't know how to decode char 0. + delta = delta.replace(/%00/g, "\0"); + var tokens = delta.split(/\t/g), + x = 0, + l = tokens.length, + param, n, text; + for (; x < l; x++) { + // Each token begins with a one character parameter which specifies the + // operation of this token (delete, insert, equality). + param = tokens[x].substring(1); + switch (tokens[x].charAt(0)) { + case "+": + try { + diffs[diffsLength++] = [DIFF_INSERT, decodeURI(param)]; + } + catch (ex) { + // Malformed URI sequence. + throw new Error("Illegal escape in diff_fromDelta: " + param); + } + break; + case "-": + // Fall through. + case "=": + n = parseInt(param, 10); + if (isNaN(n) || n < 0) + throw new Error("Invalid number in diff_fromDelta: " + param); + + text = text1.substring(pointer, pointer += n); + if (tokens[x].charAt(0) == "=") + diffs[diffsLength++] = [DIFF_EQUAL, text]; + else + diffs[diffsLength++] = [DIFF_DELETE, text]; + break; + default: + // Blank tokens are ok (from a trailing \t). + // Anything else is an error. + if (tokens[x]) { + throw new Error("Invalid diff operation in diff_fromDelta: " + + tokens[x]); + } + } + } + if (pointer != text1.length) { + throw new Error("Delta length (" + pointer + + ") does not equal source text length (" + text1.length + ")."); + } + return diffs; + }; + + + // MATCH FUNCTIONS + + /** + * Locate the best instance of 'pattern' in 'text' near 'loc'. + * @param {string} text The text to search. + * @param {string} pattern The pattern to search for. + * @param {number} loc The location to search around. + * @return {number} Best match index or -1. + */ + this.match_main = function(text, pattern, loc) { + loc = Math.max(0, Math.min(loc, text.length)); + // Shortcut (potentially not guaranteed by the algorithm) + if (text == pattern) + return 0; + else if (!text.length) // Nothing to match. + return -1; + // Perfect match at the perfect spot! (Includes case of null pattern) + else if (text.substring(loc, loc + pattern.length) == pattern) + return loc; + else // Do a fuzzy compare. + return this.match_bitap(text, pattern, loc); + }; + + /** + * Locate the best instance of 'pattern' in 'text' near 'loc' using the + * Bitap algorithm. + * @param {string} text The text to search. + * @param {string} pattern The pattern to search for. + * @param {number} loc The location to search around. + * @return {number} Best match index or -1. + * @private + */ + this.match_bitap = function(text, pattern, loc) { + if (pattern.length > this.matchMaxBits) + throw new Error("Pattern too long for this browser."); + + // Initialise the alphabet. + var s = this.match_alphabet(pattern), + _self = this; + + /* + * Compute and return the score for a match with e errors and x location. + * Accesses loc and pattern through being a closure. + * @param {number} e Number of errors in match. + * @param {number} x Location of match. + * @return {number} Overall score for match (0.0 = good, 1.0 = bad). + * @private + */ + function match_bitapScore(e, x) { + var accuracy = e / pattern.length, + proximity = Math.abs(loc - x); + if (!_self.matchDistance) // Dodge divide by zero error. + return proximity ? 1.0 : accuracy; + return accuracy + (proximity / _self.matchDistance); + } + + // Highest score beyond which we give up. + var score_threshold = this.matchThreshold, + // Is there a nearby exact match? (speedup) + best_loc = text.indexOf(pattern, loc); + if (best_loc != -1) + score_threshold = Math.min(match_bitapScore(0, best_loc), score_threshold); + // What about in the other direction? (speedup) + best_loc = text.lastIndexOf(pattern, loc + pattern.length); + if (best_loc != -1) + score_threshold = Math.min(match_bitapScore(0, best_loc), score_threshold); + best_loc = -1; + + // Initialise the bit arrays. + var matchmask = 1 << (pattern.length - 1), + bin_max = pattern.length + text.length, + d = 0, + l = pattern.length, + bin_min, bin_mid, last_rd, start, finish, rd, j, charMatch, score; + for (; d < l; d++) { + // Scan for the best match; each iteration allows for one more error. + // Run a binary search to determine how far from 'loc' we can stray at this + // error level. + bin_min = 0; + bin_mid = bin_max; + while (bin_min < bin_mid) { + if (match_bitapScore(d, loc + bin_mid) <= score_threshold) + bin_min = bin_mid; + else + bin_max = bin_mid; + bin_mid = Math.floor((bin_max - bin_min) / 2 + bin_min); + } + // Use the result from this iteration as the maximum for the next. + bin_max = bin_mid; + start = Math.max(1, loc - bin_mid + 1); + finish = Math.min(loc + bin_mid, text.length) + pattern.length; + + rd = Array(finish + 2); + rd[finish + 1] = (1 << d) - 1; + for (j = finish; j >= start; j--) { + // The alphabet (s) is a sparse hash, so the following line generates + // warnings. + charMatch = s[text.charAt(j - 1)]; + if (d === 0) { // First pass: exact match. + rd[j] = ((rd[j + 1] << 1) | 1) & charMatch; + } + else { // Subsequent passes: fuzzy match. + rd[j] = ((rd[j + 1] << 1) | 1) & charMatch + | (((last_rd[j + 1] | last_rd[j]) << 1) | 1) + | last_rd[j + 1]; + } + if (rd[j] & matchmask) { + score = match_bitapScore(d, j - 1); + // This match will almost certainly be better than any existing match. + // But check anyway. + if (score <= score_threshold) { + // Told you so. + score_threshold = score; + best_loc = j - 1; + // When passing loc, don't exceed our current distance from loc. + if (best_loc > loc) + start = Math.max(1, 2 * loc - best_loc); + else // Already passed loc, downhill from here on in. + break; + } + } + } + // No hope for a (better) match at greater error levels. + if (match_bitapScore(d + 1, loc) > score_threshold) + break; + last_rd = rd; + } + return best_loc; + }; + + /** + * Initialise the alphabet for the Bitap algorithm. + * @param {string} pattern The text to encode. + * @return {Object} Hash of character locations. + * @private + */ + this.match_alphabet = function(pattern) { + var s = {}, + i = 0, + l = pattern.length; + for (; i < l; i++) + s[pattern.charAt(i)] = 0; + for (i = 0; i < pattern.length; i++) + s[pattern.charAt(i)] |= 1 << (pattern.length - i - 1); + return s; + }; + + // PATCH FUNCTIONS + + /** + * Increase the context until it is unique, + * but don't let the pattern expand beyond matchMaxBits. + * @param {patch_obj} patch The patch to grow. + * @param {string} text Source text. + * @private + */ + this.patch_addContext = function(patch, text) { + var pattern = text.substring(patch.start2, patch.start2 + patch.length1), + padding = 0; + while (text.indexOf(pattern) != text.lastIndexOf(pattern) + && pattern.length < this.matchMaxBits - this.patchMargin + - this.patchMargin) { + padding += this.patchMargin; + pattern = text.substring(patch.start2 - padding, + patch.start2 + patch.length1 + padding); + } + // Add one chunk for good luck. + padding += this.patchMargin; + // Add the prefix. + var prefix = text.substring(patch.start2 - padding, patch.start2); + if (prefix) + patch.diffs.unshift([DIFF_EQUAL, prefix]); + // Add the suffix. + var suffix = text.substring(patch.start2 + patch.length1, + patch.start2 + patch.length1 + padding); + if (suffix) + patch.diffs.push([DIFF_EQUAL, suffix]); + + // Roll back the start points. + patch.start1 -= prefix.length; + patch.start2 -= prefix.length; + // Extend the lengths. + patch.length1 += prefix.length + suffix.length; + patch.length2 += prefix.length + suffix.length; + }; + + /** + * Compute a list of patches to turn text1 into text2. + * Use diffs if provided, otherwise compute it ourselves. + * There are four ways to call this function, depending on what data is + * available to the caller: + * Method 1: + * a = text1, b = text2 + * Method 2: + * a = diffs + * Method 3 (optimal): + * a = text1, b = diffs + * Method 4 (deprecated, use method 3): + * a = text1, b = text2, c = diffs + * + * @param {string|Array.>} a text1 (methods 1,3,4) or + * Array of diff tuples for text1 to text2 (method 2). + * @param {string|Array.>} opt_b text2 (methods 1,4) or + * Array of diff tuples for text1 to text2 (method 3) or undefined (method 2). + * @param {string|Array.>} opt_c Array of diff tuples for + * text1 to text2 (method 4) or undefined (methods 1,2,3). + * @return {Array.} Array of patch objects. + */ + this.patch_make = function(a, opt_b, opt_c) { + var text1, diffs; + if (typeof a == "string" && typeof opt_b == "string" + && typeof opt_c == "undefined") { + // Method 1: text1, text2 + // Compute diffs from text1 and text2. + text1 = a; + diffs = this.diff_main(text1, opt_b, true); + if (diffs.length > 2) { + this.diff_cleanupSemantic(diffs); + this.diff_cleanupEfficiency(diffs); + } + } + else if (typeof a == "object" && typeof opt_b == "undefined" + && typeof opt_c == "undefined") { + // Method 2: diffs + // Compute text1 from diffs. + diffs = a; + text1 = this.diff_text1(diffs); + } + else if (typeof a == "string" && typeof opt_b == "object" + && typeof opt_c == "undefined") { + // Method 3: text1, diffs + text1 = a; + diffs = opt_b; + } + else if (typeof a == "string" && typeof opt_b == "string" + && typeof opt_c == "object") { + // Method 4: text1, text2, diffs + // text2 is not used. + text1 = a; + diffs = opt_c; + } + else { + throw new Error("Unknown call format to patch_make."); + } + + if (diffs.length === 0) + return []; // Get rid of the null case. + + var patches = [], + patch = new patch_obj(), + patchDiffLength = 0, // Keeping our own length var is faster in JS. + char_count1 = 0, // Number of characters into the text1 string. + char_count2 = 0, // Number of characters into the text2 string. + // Start with text1 (prepatch_text) and apply the diffs until we arrive at + // text2 (postpatch_text). We recreate the patches one by one to determine + // context info. + prepatch_text = text1, + postpatch_text = text1, + x = 0, + l = diffs.length, + diff_type, diff_text; + for (; x < l; x++) { + diff_type = diffs[x][0]; + diff_text = diffs[x][1]; + + if (!patchDiffLength && diff_type !== DIFF_EQUAL) { + // A new patch starts here. + patch.start1 = char_count1; + patch.start2 = char_count2; + } + + switch (diff_type) { + case DIFF_INSERT: + patch.diffs[patchDiffLength++] = diffs[x]; + patch.length2 += diff_text.length; + postpatch_text = postpatch_text.substring(0, char_count2) + + diff_text + + postpatch_text.substring(char_count2); + break; + case DIFF_DELETE: + patch.length1 += diff_text.length; + patch.diffs[patchDiffLength++] = diffs[x]; + postpatch_text = postpatch_text.substring(0, char_count2) + + postpatch_text.substring(char_count2 + + diff_text.length); + break; + case DIFF_EQUAL: + if (diff_text.length <= 2 * this.patchMargin + && patchDiffLength && l != x + 1) { + // Small equality inside a patch. + patch.diffs[patchDiffLength++] = diffs[x]; + patch.length1 += diff_text.length; + patch.length2 += diff_text.length; + } + else if (diff_text.length >= 2 * this.patchMargin) { + // Time for a new patch. + if (patchDiffLength) { + this.patch_addContext(patch, prepatch_text); + patches.push(patch); + patch = new patch_obj(); + patchDiffLength = 0; + // Unlike Unidiff, our patch lists have a rolling context. + // http://code.google.com/p/google-diff-match-patch/wiki/Unidiff + // Update prepatch text & pos to reflect the application of the + // just completed patch. + prepatch_text = postpatch_text; + char_count1 = char_count2; + } + } + break; + } + + // Update the current character count. + if (diff_type !== DIFF_INSERT) + char_count1 += diff_text.length; + if (diff_type !== DIFF_DELETE) + char_count2 += diff_text.length; + } + // Pick up the leftover patch if not empty. + if (patchDiffLength) { + this.patch_addContext(patch, prepatch_text); + patches.push(patch); + } + + return patches; + }; + + /** + * Given an array of patches, return another array that is identical. + * @param {Array.} patches Array of patch objects. + * @return {Array.} Array of patch objects. + * @private + */ + var patch_deepCopy = function(patches) { + // Making deep copies is hard in JavaScript. + var patchesCopy = [], + x = 0, + l = patches.length, + patch, patchCopy, y; + for (; x < l; x++) { + patch = patches[x]; + patchCopy = new patch_obj(); + patchCopy.diffs = []; + for (y = 0; y < patch.diffs.length; y++) + patchCopy.diffs[y] = patch.diffs[y].slice(); + patchCopy.start1 = patch.start1; + patchCopy.start2 = patch.start2; + patchCopy.length1 = patch.length1; + patchCopy.length2 = patch.length2; + patchesCopy[x] = patchCopy; + } + return patchesCopy; + }; + + /** + * Merge a set of patches onto the text. Return a patched text, as well + * as a list of true/false values indicating which patches were applied. + * @param {Array.} patches Array of patch objects. + * @param {string} text Old text. + * @return {Array.>} Two element Array, containing the + * new text and an array of boolean values. + */ + this.patch_apply = function(patches, text) { + if (patches.length == 0) + return [text, []]; + + // Deep copy the patches so that no changes are made to originals. + patches = patch_deepCopy(patches); + + var nullPadding = this.patch_addPadding(patches); + text = nullPadding + text + nullPadding; + + this.patch_splitMax(patches); + // delta keeps track of the offset between the expected and actual location + // of the previous patch. If there are patches expected at positions 10 and + // 20, but the first patch was found at 12, delta is 2 and the second patch + // has an effective expected position of 22. + var delta = 0, + results = [], + x = 0, + l = patches.length, + patch, expected_loc, text1, start_loc, end_loc, text2, diffs, index1, + index2, y, mod, l2; + for (; x < l; x++) { + patch = patches[x]; + expected_loc = patch.start2 + delta; + text1 = this.diff_text1(patch.diffs); + end_loc = -1; + if (text1.length > this.matchMaxBits) { + // patch_splitMax will only provide an oversized pattern in the case of + // a monster delete. + start_loc = this.match_main(text, text1.substring(0, this.matchMaxBits), + expected_loc); + if (start_loc != -1) { + end_loc = this.match_main(text, + text1.substring(text1.length - this.matchMaxBits), + expected_loc + text1.length - this.matchMaxBits); + // Can't find valid trailing context. Drop this patch. + if (end_loc == -1 || start_loc >= end_loc) + start_loc = -1; + } + } + else { + start_loc = this.match_main(text, text1, expected_loc); + } + + if (start_loc == -1) { + // No match found. :( + results[x] = false; + // Subtract the delta for this failed patch from subsequent patches. + delta -= patch.length2 - patch.length1; + } + else { + // Found a match. :) + results[x] = true; + delta = start_loc - expected_loc; + if (end_loc == -1) + text2 = text.substring(start_loc, start_loc + text1.length); + else + text2 = text.substring(start_loc, end_loc + this.matchMaxBits); + + if (text1 == text2) { + // Perfect match, just shove the replacement text in. + text = text.substring(0, start_loc) + + this.diff_text2(patch.diffs) + + text.substring(start_loc + text1.length); + } + else { + // Imperfect match. Run a diff to get a framework of equivalent + // indices. + diffs = this.diff_main(text1, text2, false); + if (text1.length > this.matchMaxBits + && this.diff_levenshtein(diffs) / text1.length + > this.patchDeleteThreshold) { + // The end points match, but the content is unacceptably bad. + results[x] = false; + } + else { + this.diff_cleanupSemanticLossless(diffs); + index1 = 0; + for (y = 0, l2 = patch.diffs.length; y < l2; y++) { + mod = patch.diffs[y]; + if (mod[0] !== DIFF_EQUAL) + index2 = this.diff_xIndex(diffs, index1); + if (mod[0] === DIFF_INSERT) { // Insertion + text = text.substring(0, start_loc + index2) + + mod[1] + text.substring(start_loc + index2); + } + else if (mod[0] === DIFF_DELETE) { // Deletion + text = text.substring(0, start_loc + index2) + + text.substring(start_loc + + this.diff_xIndex(diffs, index1 + mod[1].length)); + } + if (mod[0] !== DIFF_DELETE) + index1 += mod[1].length; + } + } + } + } + } + // Strip the padding off. + text = text.substring(nullPadding.length, text.length - nullPadding.length); + return [text, results]; + }; + + /** + * Add some padding on text start and end so that edges can match something. + * Intended to be called only from within patch_apply. + * @param {Array.} patches Array of patch objects. + * @return {string} The padding string added to each side. + */ + this.patch_addPadding = function(patches) { + var paddingLength = this.patchMargin, + nullPadding = "", + x = 1, + l = patches.length; + for (; x <= paddingLength; x++) + nullPadding += String.fromCharCode(x); + + // Bump all the patches forward. + for (x = 0; x < l; x++) { + patches[x].start1 += paddingLength; + patches[x].start2 += paddingLength; + } + + // Add some padding on start of first diff. + var patch = patches[0], + diffs = patch.diffs, + extraLength; + if (diffs.length == 0 || diffs[0][0] != DIFF_EQUAL) { + // Add nullPadding equality. + diffs.unshift([DIFF_EQUAL, nullPadding]); + patch.start1 -= paddingLength; // Should be 0. + patch.start2 -= paddingLength; // Should be 0. + patch.length1 += paddingLength; + patch.length2 += paddingLength; + } + else if (paddingLength > diffs[0][1].length) { + // Grow first equality. + extraLength = paddingLength - diffs[0][1].length; + diffs[0][1] = nullPadding.substring(diffs[0][1].length) + diffs[0][1]; + patch.start1 -= extraLength; + patch.start2 -= extraLength; + patch.length1 += extraLength; + patch.length2 += extraLength; + } + + // Add some padding on end of last diff. + patch = patches[patches.length - 1]; + diffs = patch.diffs; + if (diffs.length == 0 || diffs[diffs.length - 1][0] != DIFF_EQUAL) { + // Add nullPadding equality. + diffs.push([DIFF_EQUAL, nullPadding]); + patch.length1 += paddingLength; + patch.length2 += paddingLength; + } + else if (paddingLength > diffs[diffs.length - 1][1].length) { + // Grow last equality. + extraLength = paddingLength - diffs[diffs.length - 1][1].length; + diffs[diffs.length - 1][1] += nullPadding.substring(0, extraLength); + patch.length1 += extraLength; + patch.length2 += extraLength; + } + + return nullPadding; + }; + + /** + * Look through the patches and break up any which are longer than the maximum + * limit of the match algorithm. + * @param {Array.} patches Array of patch objects. + */ + this.patch_splitMax = function(patches) { + var x = 0, + bigpatch, patch_size, start1, start2, precontext, patch, empty, + diff_type, diff_text, postcontext; + for (; x < patches.length; x++) { + if (patches[x].length1 <= this.matchMaxBits) continue; + + bigpatch = patches[x]; + // Remove the big old patch. + patches.splice(x--, 1); + patch_size = this.matchMaxBits; + start1 = bigpatch.start1; + start2 = bigpatch.start2; + precontext = ""; + while (bigpatch.diffs.length !== 0) { + // Create one of several smaller patches. + patch = new patch_obj(); + empty = true; + patch.start1 = start1 - precontext.length; + patch.start2 = start2 - precontext.length; + if (precontext !== "") { + patch.length1 = patch.length2 = precontext.length; + patch.diffs.push([DIFF_EQUAL, precontext]); + } + while (bigpatch.diffs.length !== 0 + && patch.length1 < patch_size - this.patchMargin) { + diff_type = bigpatch.diffs[0][0]; + diff_text = bigpatch.diffs[0][1]; + if (diff_type === DIFF_INSERT) { + // Insertions are harmless. + patch.length2 += diff_text.length; + start2 += diff_text.length; + patch.diffs.push(bigpatch.diffs.shift()); + empty = false; + } + else if (diff_type === DIFF_DELETE + && patch.diffs.length == 1 + && patch.diffs[0][0] == DIFF_EQUAL + && diff_text.length > 2 * patch_size) { + // This is a large deletion. Let it pass in one chunk. + patch.length1 += diff_text.length; + start1 += diff_text.length; + empty = false; + patch.diffs.push([diff_type, diff_text]); + bigpatch.diffs.shift(); + } + else { + // Deletion or equality. Only take as much as we can stomach. + diff_text = diff_text.substring(0, patch_size + - patch.length1 - this.patchMargin); + patch.length1 += diff_text.length; + start1 += diff_text.length; + if (diff_type === DIFF_EQUAL) { + patch.length2 += diff_text.length; + start2 += diff_text.length; + } + else { + empty = false; + } + patch.diffs.push([diff_type, diff_text]); + if (diff_text == bigpatch.diffs[0][1]) { + bigpatch.diffs.shift(); + } + else { + bigpatch.diffs[0][1] = + bigpatch.diffs[0][1].substring(diff_text.length); + } + } + } + // Compute the head context for the next patch. + precontext = this.diff_text2(patch.diffs) + .substring(precontext.length - this.patchMargin); + // Append the end context for this patch. + postcontext = this.diff_text1(bigpatch.diffs) + .substring(0, this.patchMargin); + if (postcontext !== "") { + patch.length1 += postcontext.length; + patch.length2 += postcontext.length; + if (patch.diffs.length !== 0 && + patch.diffs[patch.diffs.length - 1][0] === DIFF_EQUAL) { + patch.diffs[patch.diffs.length - 1][1] += postcontext; + } + else { + patch.diffs.push([DIFF_EQUAL, postcontext]); + } + } + if (!empty) + patches.splice(++x, 0, patch); + } + } + }; + + /** + * Take a list of patches and return a textual representation. + * @param {Array.} patches Array of patch objects. + * @return {string} Text representation of patches. + */ + this.patch_toText = function(patches) { + var text = [], + x = 0, + l = patches.length; + for (; x < l; x++) + text[x] = patches[x]; + return text.join(""); + }; + + /** + * Parse a textual representation of patches and return a list of patch objects. + * @param {string} textline Text representation of patches. + * @return {Array.} Array of patch objects. + * @throws {Error} If invalid input. + */ + this.patch_fromText = function(textline) { + var patches = []; + if (!textline) + return patches; + // Opera doesn"t know how to decode char 0. + textline = textline.replace(/%00/g, "\0"); + var text = textline.split("\n"), + textPointer = 0, + len = text.length, + re = /^@@ -(\d+),?(\d*) \+(\d+),?(\d*) @@$/, + m, patch, sign, line; + while (textPointer < len) { + m = text[textPointer].match(re); + if (!m) + throw new Error("Invalid patch string: " + text[textPointer]); + + patch = new patch_obj(); + patches.push(patch); + patch.start1 = parseInt(m[1], 10); + if (m[2] === "") { + patch.start1--; + patch.length1 = 1; + } + else if (m[2] == "0") { + patch.length1 = 0; + } + else { + patch.start1--; + patch.length1 = parseInt(m[2], 10); + } + + patch.start2 = parseInt(m[3], 10); + if (m[4] === "") { + patch.start2--; + patch.length2 = 1; + } + else if (m[4] == "0") { + patch.length2 = 0; + } + else { + patch.start2--; + patch.length2 = parseInt(m[4], 10); + } + textPointer++; + + while (textPointer < len) { + sign = text[textPointer].charAt(0); + try { + line = decodeURI(text[textPointer].substring(1)); + } catch (ex) { + // Malformed URI sequence. + throw new Error("Illegal escape in patch_fromText: " + line); + } + if (sign == "-") // Deletion. + patch.diffs.push([DIFF_DELETE, line]); + else if (sign == "+") // Insertion. + patch.diffs.push([DIFF_INSERT, line]); + else if (sign == " ") // Minor equality. + patch.diffs.push([DIFF_EQUAL, line]); + else if (sign == "@") // Start of next patch. + break; + else if (sign === "") + ;// Blank line? Whatever. + else // WTF? + throw new Error("Invalid patch mode '" + sign + "' in: " + line); + textPointer++; + } + } + return patches; + }; +})(); + + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/core/lib/util/utilities.js)SIZE(19540)TIME(Mon, 10 Jan 2011 10:53:11 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + + +/** + * Opens a window with the string in it + * @param {String} str the html string displayed in the new window. + */ +apf.pasteWindow = function(str){ + var win = window.open("about:blank"); + win.document.write(str); +}; + + + +/** + * @private + */ +apf.xmlEntityMap = { + "quot": "34", "amp": "38", "apos": "39", "lt": "60", "gt": "62", + "nbsp": "160", "iexcl": "161", "cent": "162", "pound": "163", "curren": "164", + "yen": "165", "brvbar": "166", "sect": "167", "uml": "168", "copy": "169", + "ordf": "170", "laquo": "171", "not": "172", "shy": "173", "reg": "174", + "macr": "175", "deg": "176", "plusmn": "177", "sup2": "178", "sup3": "179", + "acute": "180", "micro": "181", "para": "182", "middot": "183", "cedil": "184", + "sup1": "185", "ordm": "186", "raquo": "187", "frac14": "188", "frac12": "189", + "frac34": "190", "iquest": "191", "agrave": "192", "aacute": "193", + "acirc": "194", "atilde": "195", "auml": "196", "aring": "197", "aelig": "198", + "ccedil": "199", "egrave": "200", "eacute": "201", "ecirc": "202", + "euml": "203", "igrave": "204", "iacute": "205", "icirc": "206", "iuml": "207", + "eth": "208", "ntilde": "209", "ograve": "210", "oacute": "211", "ocirc": "212", + "otilde": "213", "ouml": "214", "times": "215", "oslash": "216", "ugrave": "217", + "uacute": "218", "ucirc": "219", "uuml": "220", "yacute": "221", "thorn": "222", + "szlig": "223", "agrave": "224", "aacute": "225", "acirc": "226", "atilde": "227", + "auml": "228", "aring": "229", "aelig": "230", "ccedil": "231", "egrave": "232", + "eacute": "233", "ecirc": "234", "euml": "235", "igrave": "236", "iacute": "237", + "icirc": "238", "iuml": "239", "eth": "240", "ntilde": "241", "ograve": "242", + "oacute": "243", "ocirc": "244", "otilde": "245", "ouml": "246", "divide": "247", + "oslash": "248", "ugrave": "249", "uacute": "250", "ucirc": "251", "uuml": "252", + "yacute": "253", "thorn": "254", "yuml": "255", "oelig": "338", "oelig": "339", + "scaron": "352", "scaron": "353", "yuml": "376", "fnof": "402", "circ": "710", + "tilde": "732", "alpha": "913", "beta": "914", "gamma": "915", "delta": "916", + "epsilon": "917", "zeta": "918", "eta": "919", "theta": "920", "iota": "921", + "kappa": "922", "lambda": "923", "mu": "924", "nu": "925", "xi": "926", + "omicron": "927", "pi": "928", "rho": "929", "sigma": "931", "tau": "932", + "upsilon": "933", "phi": "934", "chi": "935", "psi": "936", "omega": "937", + "alpha": "945", "beta": "946", "gamma": "947", "delta": "948", "epsilon": "949", + "zeta": "950", "eta": "951", "theta": "952", "iota": "953", "kappa": "954", + "lambda": "955", "mu": "956", "nu": "957", "xi": "958", "omicron": "959", + "pi": "960", "rho": "961", "sigmaf": "962", "sigma": "963", "tau": "964", + "upsilon": "965", "phi": "966", "chi": "967", "psi": "968", "omega": "969", + "thetasym": "977", "upsih": "978", "piv": "982", "ensp": "8194", "emsp": "8195", + "thinsp": "8201", "zwnj": "8204", "zwj": "8205", "lrm": "8206", "rlm": "8207", + "ndash": "8211", "mdash": "8212", "lsquo": "8216", "rsquo": "8217", + "sbquo": "8218", "ldquo": "8220", "rdquo": "8221", "bdquo": "8222", + "dagger": "8224", "dagger": "8225", "bull": "8226", "hellip": "8230", + "permil": "8240", "prime": "8242", "prime": "8243", "lsaquo": "8249", + "rsaquo": "8250", "oline": "8254", "frasl": "8260", "euro": "8364", + "image": "8465", "weierp": "8472", "real": "8476", "trade": "8482", + "alefsym": "8501", "larr": "8592", "uarr": "8593", "rarr": "8594", + "darr": "8595", "harr": "8596", "crarr": "8629", "larr": "8656", "uarr": "8657", + "rarr": "8658", "darr": "8659", "harr": "8660", "forall": "8704", "part": "8706", + "exist": "8707", "empty": "8709", "nabla": "8711", "isin": "8712", + "notin": "8713", "ni": "8715", "prod": "8719", "sum": "8721", "minus": "8722", + "lowast": "8727", "radic": "8730", "prop": "8733", "infin": "8734", + "ang": "8736", "and": "8743", "or": "8744", "cap": "8745", "cup": "8746", + "int": "8747", "there4": "8756", "sim": "8764", "cong": "8773", "asymp": "8776", + "ne": "8800", "equiv": "8801", "le": "8804", "ge": "8805", "sub": "8834", + "sup": "8835", "nsub": "8836", "sube": "8838", "supe": "8839", "oplus": "8853", + "otimes": "8855", "perp": "8869", "sdot": "8901", "lceil": "8968", + "rceil": "8969", "lfloor": "8970", "rfloor": "8971", "lang": "9001", + "rang": "9002", "loz": "9674", "spades": "9824", "clubs": "9827", + "hearts": "9829", "diams": "9830" +}; + +/** + * see string#escapeHTML + * @param {String} str the html to be escaped. + * @return {String} the escaped string. + */ +apf.htmlentities = function(str){ + return str.escapeHTML(); +}; + +/** + * Escape an xml string making it ascii compatible. + * @param {String} str the xml string to escape. + * @return {String} the escaped string. + * + * @todo This function does something completely different from htmlentities, + * the name is confusing and misleading. + */ +apf.xmlentities = function(str) { + return str.replace(/&([a-z]+);/gi, function(a, m) { + if (apf.xmlEntityMap[m = m.toLowerCase()]) + return '&#' + apf.xmlEntityMap[m] + ';'; + return a; + }); +}; + +/** + * Unescape an html string. + * @param {String} str the string to unescape. + * @return {String} the unescaped string. + */ +apf.html_entity_decode = function(str){ + return (str || "").replace(/\&\#38;/g, "&").replace(/</g, "<") + .replace(/>/g, ">").replace(/&/g, "&").replace(/ /g, " "); +}; + + + +/** + * Determines whether the keyboard input was a character that can influence + * the value of an element (like a textbox). + * @param {Number} charCode The ascii character code. + */ +apf.isCharacter = function(charCode){ + return (charCode < 112 || charCode > 122) + && (charCode == 32 || charCode > 42 || charCode == 8); +}; + +/** + * This random number generator has been added to provide a more robust and + * reliable random number spitter than the native Ecmascript Math.random() + * function. + * is an implementation of the Park-Miller algorithm. (See 'Random Number + * Generators: Good Ones Are Hard to Find', by Stephen K. Park and Keith W. + * Miller, Communications of the ACM, 31(10):1192-1201, 1988.) + * @author David N. Smith of IBM's T. J. Watson Research Center. + * @author Mike de Boer (mike AT javeline DOT com) + * @class randomGenerator + */ +apf.randomGenerator = { + d: new Date(), + seed: null, + A: 48271, + M: 2147483647, + Q: null, + R: null, + oneOverM: null, + + /** + * Generates a random Number between a lower and upper boundary. + * The algorithm uses the system time, in minutes and seconds, to 'seed' + * itself, that is, to create the initial values from which it will generate + * a sequence of numbers. If you are familiar with random number generators, + * you might have reason to use some other value for the seed. Otherwise, + * you should probably not change it. + * @param {Number} lnr Lower boundary + * @param {Number} unr Upper boundary + * @result A random number between lnr and unr + * @type Number + */ + generate: function(lnr, unr) { + if (this.seed == null) + this.seed = 2345678901 + (this.d.getSeconds() * 0xFFFFFF) + (this.d.getMinutes() * 0xFFFF); + this.Q = this.M / this.A; + this.R = this.M % this.A; + this.oneOverM = 1.0 / this.M; + return Math.floor((unr - lnr + 1) * this.next() + lnr); + }, + + /** + * Returns a new random number, based on the 'seed', generated by the + * generate method. + * @type Number + */ + next: function() { + var hi = this.seed / this.Q; + var lo = this.seed % this.Q; + var test = this.A * lo - this.R * hi; + if (test > 0) + this.seed = test; + else + this.seed = test + this.M; + return (this.seed * this.oneOverM); + } +}; + +/** + * Adds a time stamp to the url to prevent the browser from caching it. + * @param {String} url the url to add the timestamp to. + * @return {String} the url with timestamp. + */ +apf.getNoCacheUrl = function(url){ + return url + + (url.indexOf("?") == -1 ? "?" : "&") + + "nocache=" + new Date().getTime(); +}; + +/** + * Checks if the string contains curly braces at the start and end. If so it's + * processed as javascript, else the original string is returned. + * @param {String} str the string to parse. + * @return {String} the result of the parsing. + */ +apf.parseExpression = function(str){ + if (!apf.parseExpression.regexp.test(str)) + return str; + + + try { + + return eval(RegExp.$1); + + } + catch(e) { + throw new Error(apf.formatErrorString(0, null, + "Parsing Expression", + "Invalid expression given '" + str + "'")); + } + +}; +apf.parseExpression.regexp = /^\{([\s\S]*)\}$/; + +/** + * @private + */ +apf.formatNumber = function(num, prefix){ + var nr = parseFloat(num); + if (!nr) return num; + + var str = new String(Math.round(nr * 100) / 100).replace(/(\.\d?\d?)$/, function(m1){ + return m1.pad(3, "0", apf.PAD_RIGHT); + }); + if (str.indexOf(".") == -1) + str += ".00"; + + return prefix + str; +}; + +/** + * Execute a script in the global scope. + * + * @param {String} str the javascript code to execute. + * @return {String} the javascript code executed. + */ +apf.jsexec = function(str, win){ + if (!str) + return str; + if (!win) + win = self; + + if (apf.isO3) + eval(str, self); + else if (apf.hasExecScript) { + win.execScript(str); + } + else { + var head = win.document.getElementsByTagName("head")[0]; + if (head) { + var script = win.document.createElement('script'); + script.setAttribute('type', 'text/javascript'); + script.text = str; + head.appendChild(script); + head.removeChild(script); + } else + eval(str, win); + } + + return str; +}; + +/** + * Shorthand for an empty function. + */ +apf.K = function(){}; + + + +/** + * Reliably determines whether a variable is a Number. + * + * @param {mixed} value The variable to check + * @type {Boolean} + */ +apf.isNumber = function(value){ + return parseFloat(value) == value; +}; + +/** + * Reliably determines whether a variable is an array. + * @see http://thinkweb2.com/projects/prototype/instanceof-considered-harmful-or-how-to-write-a-robust-isarray/ + * + * @param {mixed} value The variable to check + * @type {Boolean} + */ +apf.isArray = function(value) { + return Object.prototype.toString.call(value) === "[object Array]"; +}; + +/** + * Determines whether a string is true in the html attribute sense. + * @param {mixed} value the variable to check + * Possible values: + * true The function returns true. + * 'true' The function returns true. + * 'on' The function returns true. + * 1 The function returns true. + * '1' The function returns true. + * @return {Boolean} whether the string is considered to imply truth. + */ +apf.isTrue = function(c){ + return (c === true || c === "true" || c === "on" || typeof c == "number" && c > 0 || c === "1"); +}; + +/** + * Determines whether a string is false in the html attribute sense. + * @param {mixed} value the variable to check + * Possible values: + * false The function returns true. + * 'false' The function returns true. + * 'off' The function returns true. + * 0 The function returns true. + * '0' The function returns true. + * @return {Boolean} whether the string is considered to imply untruth. + */ +apf.isFalse = function(c){ + return (c === false || c === "false" || c === "off" || c === 0 || c === "0"); +}; + +/** + * Determines whether a value should be considered false. This excludes amongst + * others the number 0. + * @param {mixed} value the variable to check + * @return {Boolean} whether the variable is considered false. + */ +apf.isNot = function(c){ + // a var that is null, false, undefined, Infinity, NaN and c isn't a string + return (!c && typeof c != "string" && c !== 0 || (typeof c == "number" && !isFinite(c))); +}; + +/** + * Creates a relative url based on an absolute url. + * @param {String} base the start of the url to which relative url's work. + * @param {String} url the url to transform. + * @return {String} the relative url. + */ +apf.removePathContext = function(base, url){ + if (!url) return ""; + + if (url.indexOf(base) > -1) + return url.substr(base.length); + + return url; +}; + +/** + * @private + * @todo why is this done like this? + */ +apf.cancelBubble = function(e, o, noPropagate){ + if (e.stopPropagation) + e.stopPropagation() + else + e.cancelBubble = true; + + //if (o.$focussable && !o.disabled) + //apf.window.$focus(o); + + + /*if (apf.isIE) + o.$ext.fireEvent("on" + e.type, e); + else + o.$ext.dispatchEvent(e.name, e);*/ + + if (!noPropagate) { + if (o && o.$ext && o.$ext["on" + (e.type || e.name)]) + o.$ext["on" + (e.type || e.name)](e); + apf.window.$mousedown(e); + } + //if (apf.isGecko) + //apf.window.$mousedown(e); + + +}; + + + +/** + * Attempt to fix memory leaks + * @private + */ +apf.destroyHtmlNode = function (element) { + if (!element) return; + + if (!apf.isIE || element.ownerDocument != document) { + if (element.parentNode) + element.parentNode.removeChild(element); + return; + } + + var garbageBin = document.getElementById('IELeakGarbageBin'); + if (!garbageBin) { + garbageBin = document.createElement('DIV'); + garbageBin.id = 'IELeakGarbageBin'; + garbageBin.style.display = 'none'; + document.body.appendChild(garbageBin); + } + + // move the element to the garbage bin + garbageBin.appendChild(element); + garbageBin.innerHTML = ''; +}; + + +/** + * @private + */ +apf.getRules = function(node){ + var rules = {}; + + for (var w = node.firstChild; w; w = w.nextSibling){ + if (w.nodeType != 1) + continue; + else { + if (!rules[w[apf.TAGNAME]]) + rules[w[apf.TAGNAME]] = []; + rules[w[apf.TAGNAME]].push(w); + } + } + + return rules; +}; + + +apf.isCoord = function (n){ + return n || n === 0; +} + +apf.getCoord = function (n, other){ + return n || n === 0 ? n : other; +} + +/** + * @private + */ +apf.getBox = function(value, base){ + if (!base) base = 0; + + if (value == null || (!parseInt(value) && parseInt(value) != 0)) + return [0, 0, 0, 0]; + + var x = String(value).splitSafe(" "); + for (var i = 0; i < x.length; i++) + x[i] = parseInt(x[i]) || 0; + switch (x.length) { + case 1: + x[1] = x[0]; + x[2] = x[0]; + x[3] = x[0]; + break; + case 2: + x[2] = x[0]; + x[3] = x[1]; + break; + case 3: + x[3] = x[1]; + break; + } + + return x; +}; + +/** + * @private + */ +apf.getNode = function(data, tree){ + var nc = 0;//nodeCount + //node = 1 + if (data != null) { + for (var i = 0; i < data.childNodes.length; i++) { + if (data.childNodes[i].nodeType == 1) { + if (nc == tree[0]) { + data = data.childNodes[i]; + if (tree.length > 1) { + tree.shift(); + data = this.getNode(data, tree); + } + return data; + } + nc++ + } + } + } + + return null; +}; + +/** + * Retrieves the first xml node with nodeType 1 from the children of an xml element. + * @param {XMLElement} xmlNode the xml element that is the parent of the element to select. + * @return {XMLElement} the first child element of the xml parent. + * @throw error when no child element is found. + */ +apf.getFirstElement = function(xmlNode){ + + try { + xmlNode.firstChild.nodeType == 1 + ? xmlNode.firstChild + : xmlNode.firstChild.nextSibling + } + catch (e) { + throw new Error(apf.formatErrorString(1052, null, + "Xml Selection", + "Could not find element:\n" + + (xmlNode ? xmlNode.xml : "null"))); + } + + + return xmlNode.firstChild.nodeType == 1 + ? xmlNode.firstChild + : xmlNode.firstChild.nextSibling; +}; + +/** + * Retrieves the last xml node with nodeType 1 from the children of an xml element. + * @param {XMLElement} xmlNode the xml element that is the parent of the element to select. + * @return {XMLElement} the last child element of the xml parent. + * @throw error when no child element is found. + */ +apf.getLastElement = function(xmlNode){ + + try { + xmlNode.lastChild.nodeType == 1 + ? xmlNode.lastChild + : xmlNode.lastChild.nextSibling + } + catch (e) { + throw new Error(apf.formatErrorString(1053, null, + "Xml Selection", + "Could not find last element:\n" + + (xmlNode ? xmlNode.xml : "null"))); + } + + + return xmlNode.lastChild.nodeType == 1 + ? xmlNode.lastChild + : xmlNode.lastChild.previousSibling; +}; + +/** + * Selects the content of an html element. Currently only works in + * internet explorer. + * @param {HTMLElement} oHtml the container in which the content receives the selection. + */ +apf.selectTextHtml = function(oHtml){ + if (!apf.hasMsRangeObject) return;// oHtml.focus(); + + var r = document.selection.createRange(); + try {r.moveToElementText(oHtml);} catch(e){} + r.select(); +}; + + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/core/lib/util/visibilitymanager.js)SIZE(3663)TIME(Mon, 10 Jan 2011 10:53:11 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + +/** + * Manages visibility hooks for elements that need to be visible to set their + * layout. + * + * @private + */ +apf.visibilitymanager = function(){ + var tree = {}; + var _self = this; + var inited = false; + + this.check = function(amlNode, type, callback) { + if (amlNode.$ext.offsetHeight || amlNode.$ext.offsetWidth) + return true; + + if (amlNode.$visibleCheck) { + if (amlNode.$visibleCheck[type]) + return; + } + else + amlNode.$visibleCheck = {}; + + function cleanup(setInsertion){ + var p = amlNode; + while (p) { + p.removeEventListener("prop.visible", check); + p.removeEventListener("DOMNodeRemoved", remove); + p.removeEventListener("DOMNodeRemovedFromDocument", remove); + if (setInsertion) + p.addEventListener("DOMNodeInserted", add); + p = p.parentNode || p.$parentNode; + } + + delete amlNode.$visibleCheck[type]; + } + + function check(e){ + //apf.isTrue(e.value) + if (!amlNode.$ext.offsetHeight && !amlNode.$ext.offsetWidth) + return; + + callback.call(amlNode); + cleanup(); + } + + function remove(e){ + if (e.currentTarget != this) + return; + + cleanup(e.name == "DOMNodeRemoved"); + } + + function add(){ + //Set events on the parent tree + var p = amlNode; + while (p) { + p.addEventListener("prop.visible", check); + p.addEventListener("DOMNodeRemoved", remove); + p.addEventListener("DOMNodeRemovedFromDocument", remove); + p.removeEventListener("DOMNodeInserted", add); + p = p.parentNode || p.$parentNode; + } + + amlNode.$visibleCheck[type] = true; + } + + add(); + + return false; + } + + this.permanent = function(amlNode, show, hide){ + var state = amlNode.$ext.offsetHeight && amlNode.$ext.offsetWidth; + function check(e){ + var newState = amlNode.$ext.offsetHeight && amlNode.$ext.offsetWidth; + if (newState == state) + return; + + if (newState) show(); + else hide(); + + state = newState; + } + + //Set events on the parent tree + var p = amlNode; + while (p) { + p.addEventListener("prop.visible", check); + p = p.parentNode || p.$parentNode; + } + + return state; + } +}; + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/core/lib/util/xml.js)SIZE(45361)TIME(Mon, 10 Jan 2011 10:53:11 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + +/** + * Determines whether a node is a child of another node. + * + * @param {DOMNode} pNode the potential parent element. + * @param {DOMNode} childnode the potential child node. + * @param {Boolean} [orItself] whether the method also returns true when pNode is the childnode. + * @return {Number} the child position of the node. Or false if it's not a child. + */ +apf.isChildOf = function(pNode, childnode, orItself){ + if (!pNode || !childnode) + return false; + + if (childnode.nodeType == 2) + childnode = childnode.ownerElement || childnode.selectSingleNode(".."); + + if (orItself && pNode == childnode) + return true; + + var loopnode = childnode.parentNode; + while(loopnode){ + if(loopnode == pNode) + return true; + loopnode = loopnode.parentNode; + } + + return false; +}; + +/** + * Escapes "&", greater than and less than signs and quotation marks into + * the proper XML entities. + * + * @param {String} str The string to escape + * @returns {String} The escaped string + */ +apf.escapeXML = function(str) { + return ((str || "") + .replace(/&/g, "&") + .replace(/"/g, """) + .replace(//g, ">") + .replace(/'/g, "'") + ); +}; + +/** + * Determines whether a node is it's parent's only child. + * @param {DOMNode} node the potential only child. + * @param {Array} nodeType list of the node types that this child can be. + * @returns {Boolean} whether the node is only child and optionally of one of the specified nodeTypes. + */ +apf.isOnlyChild = function(node, nodeType){ + if (!node || !node.parentNode || nodeType && nodeType.indexOf(node.nodeType) == -1) + return false; + + var i, l, cnode, nodes = node.parentNode.childNodes; + for (i = 0, l = nodes.length; i < l; i++) { + cnode = nodes[i]; + if (cnode.nodeType == 1 && cnode != node) + return false; + if (cnode.nodeType == 3 && !cnode.nodeValue.trim()) + return false; + } + + return true; +}; + +/** + * Gets the position of a dom node within the list of child nodes of it's + * parent. + * + * @param {DOMNode} node the node for which the child position is determined. + * @return {Number} the child position of the node. + */ +apf.getChildNumber = function(node, fromList){ + if (!node) return -1; + + var p = node.parentNode, j = 0; + if (!p) return 0; + if (!fromList) + fromList = p.childNodes; + + if (fromList.indexOf) { + var idx = fromList.indexOf(node); + return idx == -1 ? fromList.length : idx; + } + + for (var i = 0, l = fromList.length; i < l; i++) { + if (fromList[i] == node) + return j; + j++; + } + return j; +}; + +/** + * Integrates nodes as children of a parent. Optionally attributes are + * copied as well. + * + * @param {XMLNode} xmlNode the data to merge. + * @param {XMLNode} parent the node to merge on. + * @param {Object} options + * Properties: + * {Boolean} [copyAttributes] whether the attributes of xmlNode are copied as well. + * {Boolean} [clearContents] whether the contents of parent is cleared. + * {Number} [start] This feature is used for the virtual viewport. More information will follow. + * {Number} [length] This feature is used for the virtual viewport. More information will follow. + * {Number} [documentId] This feature is used for the virtual viewport. More information will follow. + * {XMLElement} [marker] This feature is used for the virtual viewport. More information will follow. + * @return {XMLNode} the created xml node + */ +apf.mergeXml = function(XMLRoot, parentNode, options){ + if (typeof parentNode != "object") + parentNode = getElementById(parentNode); + + if (options && options.clearContents) { + //Signal listening elements + var node, j, i, + nodes = parentNode.selectNodes("descendant::node()[@" + apf.xmldb.xmlListenTag + "]"); + for (i = nodes.length - 1; i >= 0; i--) { + var s = nodes[i].getAttribute(apf.xmldb.xmlListenTag).split(";"); + for (j = s.length - 1; j >= 0; j--) { + node = apf.all[s[j]]; + if (!node) continue; + if (node.dataParent && node.dataParent.xpath) + node.dataParent.parent.signalXmlUpdate[node.$uniqueId] = true; + else if (node.$model) + node.$model.$waitForXml(node); + } + } + + //clean parent + nodes = parentNode.childNodes; + for (i = nodes.length - 1; i >= 0; i--) + parentNode.removeChild(nodes[i]); + } + + + if (options && options.start) { //Assuming each node is in count + var reserved, beforeNode, nodes, doc, i, l, marker = options.marker; + if (!marker){ + //optionally find marker + } + + //This code assumes that the dataset fits inside this marker + + //Start of marker + if (marker.getAttribute("start") - options.start == 0) { + marker.setAttribute("start", options.start + options.length); + reserved = parseInt(marker.getAttribute("reserved")); + marker.setAttribute("reserved", reserved + options.length); + beforeNode = marker; + } + //End of marker + else if (options.start + options.length == marker.getAttribute("end")) { + marker.setAttribute("end", options.start + options.length); + beforeNode = marker.nextSibling; + reserved = parseInt(marker.getAttribute("reserved")) + + parseInt(marker.getAttribute("end")) - options.length; + } + //Middle of marker + else { + var m2 = marker.parentNode.insertBefore(marker.cloneNode(true), marker); + m2.setAttribute("end", options.start - 1); + marker.setAttribute("start", options.start + options.length); + reserved = parseInt(marker.getAttribute("reserved")); + marker.setAttribute("reserved", reserved + options.length); + beforeNode = marker; + } + + nodes = XMLRoot.childNodes; + + if (parentNode.ownerDocument.importNode) { + doc = parentNode.ownerDocument; + for (i = 0, l = nodes.length; i < l; i++) { + parentNode.insertBefore(doc.importNode(nodes[i], true), beforeNode) + .setAttribute(apf.xmldb.xmlIdTag, options.documentId + "|" + (reserved + i)); + } + } + else { + for (i = nodes.length - 1; i >= 0; i--) { + parentNode.insertBefore(nodes[0], beforeNode) + .setAttribute(apf.xmldb.xmlIdTag, options.documentId + "|" + (reserved + i)); + } + } + } + else + + { + beforeNode = options && options.beforeNode ? options.beforeNode : apf.getNode(parentNode, [0]); + nodes = XMLRoot.childNodes; + + if (parentNode.ownerDocument.importNode) { + doc = parentNode.ownerDocument; + for (i = 0, l = nodes.length; i < l; i++) + parentNode.insertBefore(doc.importNode(nodes[i], true), beforeNode); + } + else + for (i = nodes.length - 1; i >= 0; i--) + parentNode.insertBefore(nodes[0], beforeNode); + } + + if (options && options.copyAttributes) { + var attr = XMLRoot.attributes; + for (i = 0; i < attr.length; i++) + if (attr[i].nodeName != apf.xmldb.xmlIdTag) + parentNode.setAttribute(attr[i].nodeName, attr[i].nodeValue); + } + + return parentNode; +}; + +/** + * Sets the nodeValue of a dom node. + * + * @param {XMLElement} xmlNode the xml node that should receive the nodeValue. + * When an element node is passed the first text node is set. + * @param {String} nodeValue the value to set. + * @param {Boolean} applyChanges whether the changes are propagated to the databound elements. + * @param {UndoObj} undoObj the undo object that is responsible for archiving the changes. + */ +apf.setNodeValue = function(xmlNode, nodeValue, applyChanges, options){ + if (!xmlNode) + return; + + var undoObj, xpath, newNodes; + if (options) { + undoObj = options.undoObj; + xpath = options.xpath; + newNodes = options.newNodes; + + undoObj.extra.oldValue = options.forceNew + ? "" + : apf.queryValue(xmlNode, xpath); + + undoObj.xmlNode = xmlNode; + if (xpath) { + xmlNode = apf.createNodeFromXpath(xmlNode, xpath, newNodes, options.forceNew); + } + + undoObj.extra.appliedNode = xmlNode; + } + + if (xmlNode.nodeType == 1) { + if (!xmlNode.firstChild) + xmlNode.appendChild(xmlNode.ownerDocument.createTextNode("")); + + xmlNode.firstChild.nodeValue = apf.isNot(nodeValue) ? "" : nodeValue; + + if (applyChanges) + apf.xmldb.applyChanges("text", xmlNode, undoObj); + } + else { + // @todo: this should be fixed in libxml + if (apf.isO3 && xmlNode.nodeType == 2) + nodeValue = nodeValue.replace(/&/g, "&"); + + var oldValue = xmlNode.nodeValue; + xmlNode.nodeValue = apf.isNot(nodeValue) ? "" : nodeValue; + + if (undoObj) { + undoObj.name = xmlNode.nodeName; + } + + //AML support - getters/setters would be awesome + if (xmlNode.$triggerUpdate) + xmlNode.$triggerUpdate(null, oldValue); + + if (applyChanges) + apf.xmldb.applyChanges(xmlNode.nodeType == 2 ? "attribute" : "text", xmlNode.parentNode + || xmlNode.ownerElement || xmlNode.selectSingleNode(".."), + undoObj); + } + + + if (applyChanges) { + var node; + if (xpath) { + var node = undoObj.xmlNode;//.selectSingleNode(newNodes.foundpath); + if (node.nodeType == 9) { + node = node.documentElement; + xpath = xpath.replace(/^[^\/]*\//, "");//xpath.substr(newNodes.foundpath.length); + } + } + else + node = xmlNode; + + apf.xmldb.applyRDB(["setValueByXpath", node, nodeValue, xpath, + options && options.forceNew], + undoObj || {xmlNode: xmlNode} + ); + } + +}; + +/** + * Sets a value of an XMLNode based on an xpath statement executed on a reference XMLNode. + * + * @param {XMLNode} xmlNode the reference xml node. + * @param {String} xpath the xpath used to select a XMLNode. + * @param {String} value the value to set. + * @param {Boolean} local whether the call updates databound UI. + * @return {XMLNode} the changed XMLNode + */ +apf.setQueryValue = function(xmlNode, xpath, value, local){ + var node = apf.createNodeFromXpath(xmlNode, xpath); + if (!node) + return null; + + apf.setNodeValue(node, value, !local); + //apf.xmldb.setTextNode(node, value); + return node; +}; + +/** + * Removed an XMLNode based on an xpath statement executed on a reference XMLNode. + * + * @param {XMLNode} xmlNode the reference xml node. + * @param {String} xpath the xpath used to select a XMLNode. + * @return {XMLNode} the changed XMLNode + */ +apf.removeQueryNode = function(xmlNode, xpath, local){ + var node = apf.queryNode(xmlNode, xpath); + if (!node) + return false; + + if (local) + node.parentNode.removeChild(node); + else + apf.xmldb.removeNode(node); + + return node; +}; + +/** + * Queries an xml node using xpath for a string value. + * @param {XMLElement} xmlNode the xml element to query. + * @param {String} xpath the xpath query. + * @return {String} the value of the query result or empty string. + */ +apf.queryValue = function (xmlNode, xpath){ + if (!xmlNode) + return ""; + if (xmlNode.nodeType == 2) + return xmlNode.nodeValue; + + if (xpath) { + xmlNode = xmlNode.selectSingleNode(xpath); + if (!xmlNode) + return ""; + } + return xmlNode.nodeType == 1 + ? (!xmlNode.firstChild ? "" : xmlNode.firstChild.nodeValue) + : xmlNode.nodeValue; +}; + +/** + * Queries an xml node using xpath for a string value. + * @param {XMLElement} xmlNode the xml element to query. + * @param {String} xpath the xpath query. + * @return {Arary} list of values which are a result of the query. + */ +apf.queryValues = function(xmlNode, xpath){ + var out = []; + if (!xmlNode) return out; + + var nodes = xmlNode.selectNodes(xpath); + if (!nodes.length) return out; + + for (var i = 0; i < nodes.length; i++) { + var n = nodes[i]; + if (n.nodeType == 1) + n = n.firstChild; + out.push(n.nodeValue || ""); + } + return out; +}; + +/** + * Executes an xpath expression on any dom node. This is especially useful + * for dom nodes that don't have a good native xpath processor such as html + * in some versions of internet explorer and xml in webkit. + * + * @param {DOMNode} contextNode the xml node that is subject to the query. + * @param {String} sExpr the xpath expression. + * @returns {Array} list of xml nodes found. The list can be empty. + */ +apf.queryNodes = function(contextNode, sExpr){ + if (contextNode && (apf.hasXPathHtmlSupport && contextNode.selectSingleNode || !contextNode.style)) + return contextNode.selectNodes(sExpr); //IE55 + //if (contextNode.ownerDocument != document) + // return contextNode.selectNodes(sExpr); + + return apf.XPath.selectNodes(sExpr, contextNode) +}; + +/** + * Executes an xpath expression on any dom node. This is especially useful + * for dom nodes that don't have a good native xpath processor such as html + * in some versions of internet explorer and xml in webkit. This function + * Only returns the first node found. + * + * @param {DOMNode} contextNode the dom node that is subject to the query. + * @param {String} sExpr the xpath expression. + * @returns {XMLNode} the dom node found or null if none was found. + */ +apf.queryNode = function(contextNode, sExpr){ + if (contextNode && (apf.hasXPathHtmlSupport && contextNode.selectSingleNode || !contextNode.style)) + return contextNode.selectSingleNode(sExpr); //IE55 + //if (contextNode.ownerDocument != document) + // return contextNode.selectSingleNode(sExpr); + + var nodeList = apf.queryNodes(contextNode ? contextNode : null, + sExpr + (apf.isIE ? "" : "[1]")); + return nodeList.length > 0 ? nodeList[0] : null; +}; + +/** + * Retrieves the attribute of an xml node or the first parent node that has + * that attribute set. If no attribute is set the value is looked for on + * the appsettings element. + * + * @param {XMLElement} xml the xml node that is the starting point of the search. + * @param {String} attr the name of the attribute. + * @param {Function} [func] callback that is run for every node that is searched. + * @return {String} the found value, or empty string if none was found. + */ +apf.getInheritedAttribute = function(xml, attr, func){ + var result, avalue; + + //@todo optimize this and below + if (xml.nodeType != 1) + xml = xml.parentNode; + + while (xml && (xml.nodeType != 1 || !(result = attr + && ((avalue = xml.getAttribute(attr)) || typeof avalue == "string") + || func && func(xml)))) { + xml = xml.parentNode; + } + if (avalue == "") + return ""; + + return !result && attr && apf.config + ? apf.config[attr] + : result; +}; + +/** + * Creates an xml node based on an xpath statement. + * + * @param {DOMNode} contextNode the dom node that is subject to the query. + * @param {String} xPath the xpath query. + * @param {Array} [addedNodes] this array is filled with the nodes added. + * @param {Boolean} [forceNew] whether a new node is always created. + * @return {DOMNode} the last element found. + * @todo generalize this to include attributes in if format [] + */ +apf.createNodeFromXpath = function(contextNode, xPath, addedNodes, forceNew){ + var xmlNode, foundpath = "", paths = xPath.replace(/('.*?')|(".*?")|\|/g, function(m, m1, m2){ + if (m1 || m2) return m1 || m2; + return "-%-|-%-"; + }).split("-%-|-%-")[0].split(/\/(?!\/)/);//.split("/"); + if (!forceNew && (xmlNode = contextNode.selectSingleNode(xPath))) + return xmlNode; + + var len = paths.length - 1; + if (forceNew) { + if (paths[len].trim().match(/^\@(.*)$|^text\(\)$/)) + len--; + } + + //Directly forwarding to the document element because of a bug in the o3 xml lib + if (!paths[0]) { + contextNode = contextNode.ownerDocument.documentElement; + paths.shift();paths.shift(); + len--;len--; + } + + for (var addedNode, isAdding = false, i = 0; i < len; i++) { + if (!isAdding && contextNode.selectSingleNode(foundpath + + (i != 0 ? "/" : "") + paths[i])) { + foundpath += (i != 0 ? "/" : "") + paths[i];// + "/"; + continue; + } + + //Temp hack + var isAddId = paths[i].match(/(\w+)\[@([\w-]+)=(\w+)\]/); + + if (!isAddId && paths[i].match(/\@|\[.*\]|\(.*\)/)) { + throw new Error(apf.formatErrorString(1041, this, + "Select via xPath", + "Could not use xPath to create xmlNode: " + xPath)); + } + if (!isAddId && paths[i].match(/\/\//)) { + throw new Error(apf.formatErrorString(1041, this, + "Select via xPath", + "Could not use xPath to create xmlNode: " + xPath)); + } + + + if (isAddId) + paths[i] = isAddId[1]; + + isAdding = true; + addedNode = contextNode.selectSingleNode(foundpath || ".") + .appendChild(contextNode.ownerDocument.createElement(paths[i])); + + if (isAddId) { + addedNode.setAttribute(isAddId[2], isAddId[3]); + foundpath += (foundpath ? "/" : "") + isAddId[0];// + "/"; + } + else + foundpath += (foundpath ? "/" : "") + paths[i];// + "/"; + + if (addedNodes) + addedNodes.push(addedNode); + } + + if (!foundpath) + foundpath = "."; + if (addedNodes) + addedNodes.foundpath = foundpath; + + var newNode, lastpath = paths[len], + doc = contextNode.nodeType == 9 ? contextNode : contextNode.ownerDocument; + do { + if (lastpath.match(/^\@(.*)$/)) { + (newNode || contextNode.selectSingleNode(foundpath)) + .setAttributeNode(newNode = doc.createAttribute(RegExp.$1)); + } + else if (lastpath.trim() == "text()") { + newNode = (newNode || contextNode.selectSingleNode(foundpath)) + .appendChild(doc.createTextNode("")); + } + else { + var hasId = lastpath.match(/(\w+)\[@([\w-]+)=(\w+)\]/); + if (hasId) lastpath = hasId[1]; + newNode = (newNode || contextNode.selectSingleNode(foundpath)) + .appendChild(doc.createElement(lastpath)); + if (hasId) + newNode.setAttribute(hasId[2], hasId[3]); + } + + if (addedNodes) + addedNodes.push(newNode); + + foundpath += (foundpath ? "/" : "") + paths[len]; + } while((lastpath = paths[++len])); + + return newNode; +}; + +/** + * @private + */ +apf.convertMethods = { + /** + * Gets a JSON object containing all the name/value pairs of the elements + * using this element as it's validation group. + * + * @return {String} the string representation of a the json object + */ + "json": function(xml){ + return JSON.stringify(apf.xml2json(xml)); + /* + var result = {}, filled = false, nodes = xml.childNodes; + for (var i = 0; i < nodes.length; i++) { + if (nodes[i].nodeType != 1) + continue; + var name = nodes[i].tagName; + filled = true; + + //array + var sameNodes = xml.selectNodes(x); + if (sameNodes.length > 1) { + var z = []; + for (var j = 0; j < sameNodes.length; j++) { + z.push(this.json(sameNodes[j], result)); + } + result[name] = z; + } + else //single value + result[name] = this.json(sameNodes[j], result); + } + + return filled ? result : apf.queryValue(xml, "text()");*/ + }, + + "cgivars": function(xml, basename){ + if (!basename) + basename = ""; + + var value, name, sameNodes, j, l2, + str = [], + nodes = xml.childNodes, + done = {}, + i = 0, + l = nodes.length; + for (; i < l; ++i) { + if (nodes[i].nodeType != 1) + continue; + name = nodes[i].tagName; + if (done[name]) + continue; + + //array + sameNodes = xml.selectNodes(name); + if (sameNodes.length > 1) { + done[name] = true; + for (j = 0, l2 = sameNodes.length; j < l2; j++) { + value = this.cgivars(sameNodes[j], basename + name + "[" + j + "]"); + if (value) + str.push(value); + } + } + else { //single value + value = this.cgivars(nodes[i], basename + name); + if (value) + str.push(value); + } + } + + var attr = xml.attributes; + for (i = 0, l = attr.length; i < l; i++) { + if (attr[i].nodeValue) { + if (basename) + str.push(basename + "[" + attr[i].nodeName + "]=" + + escape(attr[i].nodeValue)); + else + str.push(attr[i].nodeName + "=" + escape(attr[i].nodeValue)); + } + } + + if (str.length) + return str.join("&"); + + value = apf.queryValue(xml, "text()"); + if (basename && value) + return basename + "=" + escape(value); + + return ""; + }, + + "cgiobjects": function(xml, basename, isSub, includeEmpty){ + if (!basename) + basename = ""; + + var node, name, value, a, i, j, attr, attr_len, isOnly, + nodes = xml.childNodes, + output = [], + tagNames = {}, + nm = ""; + + for (i = 0; i < nodes.length; i++) { + node = nodes[i]; + + if (node.nodeType == 1) { + name = node.tagName; + + isOnly = node.parentNode.selectNodes(name).length == 1 + ? true + : false; + + if (typeof tagNames[name] == "undefined") { + tagNames[name] = 0; + } + + nm = basename + + (isSub ? "[" : "") + name + (isSub ? "]" : "") + + (isOnly ? "" : "[" + tagNames[name] + "]"); + + attr = node.attributes; + attr_len = node.attributes.length; + + if (attr_len > 0) { + for (j = 0; j < attr_len; j++) { + if (!(a = attr[j]).nodeValue) + continue; + + output.push(nm + "[_" + a.nodeName + "]=" + + escape(a.nodeValue.trim())); + } + } + + value = this.cgiobjects(node, nm, true); + + if (value.dataType !== 1) { + output.push(value); + } + else { + if (node.firstChild && node.firstChild.nodeValue.trim()) { + output.push(nm + (attr_len > 0 ? "[_]=" : "=") + + escape(node.firstChild.nodeValue.trim())); + } + else { + if (attr_len == 0) { + if (includeEmpty) { + output.push(nm); + } + } + } + } + + tagNames[name]++; + } + //@todo, that's that ?? + //handle node values (for form submission) + else if (node.nodeType == 3 && isSub) { + var nval = node.nodeValue; + + if (nval && nval.trim() !== "") { + output.push(basename + "=" + escape(nval)); + } + + //was: output = node.nodeType; + } + } + + if (!isSub && xml.getAttribute("id")) + output.push("id=" + escape(xml.getAttribute("id"))); + + if (output.length) + return output.join("&"); + + return output; + } +}; + +/** + * Converts xml to another format. + * + * @param {XMLElement} xml the {@link term.datanode data node} to convert. + * @param {String} to the format to convert the xml to. + * Possible values: + * json converts to a json string + * cgivars converts to cgi string. + * cgiobjects converts to cgi objects + * @return {String} the result of the conversion. + */ +apf.convertXml = function(xml, to){ + return apf.convertMethods[to](xml); +}; + +/** + * Returns the first text or cdata child of an {@link term.datanode data node}. + * + * @param {XMLElement} x the xml node to search. + * @return {XMLNode} the found xml node, or null. + */ +apf.getTextNode = function(x){ + for (var i = 0, l = x.childNodes.length; i < l; ++i) { + if (x.childNodes[i].nodeType == 3 || x.childNodes[i].nodeType == 4) + return x.childNodes[i]; + } + return false; +}; + +/** + * @private + */ +apf.getBoundValue = function(amlNode, xmlRoot, applyChanges){ + if (!xmlRoot && !amlNode.xmlRoot) + return ""; + + var xmlNode = amlNode.$getDataNode("value", amlNode.xmlRoot); + + return xmlNode ? apf.queryValue(xmlNode) : ""; +}; + +/** + * @private + */ +apf.getArrayFromNodelist = function(nodelist){ + for (var nodes = [], j = 0, l = nodelist.length; j < l; ++j) + nodes.push(nodelist[j]); + return nodes; +}; + +apf.serializeChildren = function(xmlNode){ + var node, + s = [], + nodes = xmlNode.childNodes, + i = 0, + l = nodes.length; + for (; i < l; ++i) { + s[i] = (node = nodes[i]).nodeType == 1 + ? node.xml || node.serialize() + : (node.nodeType == 8 ? "" : node.nodeValue); + } + return s.join(""); +} + +/** + * Returns a string version of the {@link term.datanode data node}. + * + * @param {XMLElement} xmlNode the {@link term.datanode data node} to serialize. + * @return {String} the serilized version of the {@link term.datanode data node}. + */ +apf.getXmlString = function(xmlNode){ + var xml = apf.xmldb.cleanNode(xmlNode.cloneNode(true)); + return xml.xml || xml.serialize(); +}; + +/** + * Creates xml nodes from an xml string recursively. + * + * @param {String} strXml the xml definition. + * @param {Boolean} [noError] whether an exception should be thrown by the parser + * when the xml is not valid. + * @param {Boolean} [preserveWhiteSpace] whether whitespace that is present between + * XML elements should be preserved + * @return {XMLNode} the created xml node. + */ +apf.getXml = function(strXml, noError, preserveWhiteSpace){ + return apf.getXmlDom(strXml, noError, preserveWhiteSpace).documentElement; +}; + +/** + * Formats an xml string with good indentation. Also known as pretty printing. + * @param {String} strXml the xml to format. + * @return {String} the formatted string. + */ +apf.formatXml = function(strXml){ + strXml = strXml.trim(); + + var lines = strXml.split("\n"), + depth = 0, + i = 0, + l = lines.length; + for (; i < l; ++i) + lines[i] = lines[i].trim(); + lines = lines.join("\n").replace(/\>\n/g, ">").replace(/\>/g, ">\n") + .replace(/\n\]+[^\/]\>/) ? depth++ : depth))) + lines[i]; + if (!strXml) + return ""; + + return lines.join("\n"); +}; + +//@todo this function needs to be 100% proof, it's the core of the system +//for RDB: xmlNode --> Xpath statement +apf.xmlToXpath = function(xmlNode, xmlContext, useAID){ + if (!xmlNode) //@todo apf3.0 + return ""; + + if (useAID === true && xmlNode.nodeType == 1 && xmlNode.getAttribute(apf.xmldb.xmlIdTag)) { + return "//node()[@" + apf.xmldb.xmlIdTag + "='" + + xmlNode.getAttribute(apf.xmldb.xmlIdTag) + "']"; + } + + if (apf != this && this.lookup && this.select) { + var unique, def = this.lookup[xmlNode.tagName]; + if (def) { + //unique should not have ' in it... -- can be fixed... + unique = xmlNode.selectSingleNode(def).nodeValue; + return "//" + xmlNode.tagName + "[" + def + "='" + unique + "']"; + } + + for (var i = 0, l = this.select.length; i < l; i++) { + if (xmlNode.selectSingleNode(this.select[i][0])) { + unique = xmlNode.selectSingleNode(this.select[i][1]).nodeValue; + return "//" + this.select[i][0] + "[" + this.select[i][1] + + "='" + unique + "']"; + } + } + } + + if (xmlNode == xmlContext) + return "."; + + if (xmlNode.nodeType != 2 && !xmlNode.parentNode && !xmlNode.ownerElement) { + + throw new Error(apf.formatErrorString(0, null, + "Converting XML to Xpath", + "Error xml node without parent and non matching context cannot\ + be converted to xml.", xmlNode)); + + + return false; + } + + var str = [], lNode = xmlNode; + if (lNode.nodeType == 2) { + str.push("@" + lNode.nodeName); + lNode = lNode.ownerElement || xmlNode.selectSingleNode(".."); + } + + var id;//, pfx = ""; + while(lNode && lNode.nodeType == 1) { + if (lNode == xmlContext) { + //str.unshift("/");//pfx = "//"; + break; + } + str.unshift((lNode.nodeType == 1 ? lNode.tagName : "text()") + + "[" + (useAID && (id = lNode.nodeType == 1 && lNode.getAttribute(apf.xmldb.xmlIdTag)) + ? "@" + apf.xmldb.xmlIdTag + "='" + id + "'" + : (apf.getChildNumber(lNode, lNode.parentNode.selectNodes(lNode.nodeType == 1 ? lNode.tagName : "text()")) + 1)) + + "]"); + lNode = lNode.parentNode; + }; + + return (str[0] == "/" || xmlContext && xmlContext.nodeType == 1 ? "" : "/") + str.join("/"); //pfx + +}; + +//for RDB: Xpath statement --> xmlNode +apf.xpathToXml = function(xpath, xmlNode){ + if (!xmlNode) { + + throw new Error(apf.formatErrorString(0, null, + "Converting Xpath to XML", + "Error context xml node is empty, thus xml node cannot \ + be found for '" + xpath + "'")); + + + return false; + } + + return xmlNode.selectSingleNode(xpath); +}; + + +apf.n = function(xml, xpath){ + return new apf.xmlset(xml, xpath, true); +} +apf.b = function(xml, xpath){ + return new apf.xmlset(xml, xpath); +} +apf.b.$queue = []; +apf.b.$state = 0; +/** + * Naive jQuery like set implementation + * @todo add dirty flags + * @todo add query queue + * @todo rewrite to use arrays + */ +apf.xmlset = function(xml, xpath, local, previous){ + if (typeof(xml) == "string") + xml = apf.getXml(xml); + + this.$xml = xml; + if (xml) + this.$nodes = xml.dataType == apf.ARRAY ? xml : (xpath ? xml.selectNodes(xpath) : [xml]); + this.$xpath = xpath || "." + this.$local = local; + this.$previous = previous; +}; + +(function(){ + this.add = function(){} //@todo not implemented + + this.begin = function(){ + apf.b.$state = 1; + return this; + } + this.commit = function(at, rmt, uri){ + if (apf.b.$queue.length) { + if (rmt) { + var _self = this; + rmt.addEventListener("rdbsend", function(e){ + if (!uri || e.uri == uri) { + _self.rdbstack = e.message; + rmt.removeEventListener("rdbsend", arguments.callee); + } + }); + } + + at.execute({ + action : 'multicall', + args : apf.b.$queue + }); + + if (rmt) + rmt.$processQueue(rmt); + } + + apf.b.$queue = []; + apf.b.$state = 0; + return this; + } + this.rollback = function(){ + apf.b.$queue = []; + apf.b.$state = 0; + return this; + } + this.getRDBMessage = function(){ + return this.rdbstack || []; + } + + this.before = function(el){ + el = typeof el == "function" ? el(i) : el; + + for (var node, i = 0, l = this.$nodes.length; i < l; i++) { + node = this.$nodes[i]; + if (this.$local) + node.parentNode.insertBefore(el, node); + else + apf.xmldb.appendChild(node.parentNode, el, node); + } + return this; + } + + this.after = function(el){ + el = typeof el == "function" ? el(i) : el; + + for (var node, i = 0, l = this.$nodes.length; i < l; i++) { + node = this.$nodes[i]; + if (this.$local) + node.parentNode.insertBefore(el, node.nextSibling); + else + apf.xmldb.appendChild(node.parentNode, el, node.nextSibling); + } + + return this; + } + + this.andSelf = function(){} + + this.append = function(el){ + for (var node, child, i = 0, l = this.$nodes.length; i < l; i++) { + node = this.$nodes[i]; + child = typeof el == "function" ? el(i, node) : el; + + if (apf.b.$state) + apf.b.$queue.push({ + action : 'appendChild', + args : [node, child] + }); + else if (this.$local) + node.appendChild(child); + else + apf.xmldb.appendChild(node, child); + + } + + return this; + } + this.appendTo = function(target){ + for (var i = 0, l = this.$nodes.length; i < l; i++) { + target.appendChild(this.$nodes[i]); + } + return this; + } + this.prepend = function(content){ + for (var node, i = 0, l = this.$nodes.length; i < l; i++) { + node = this.$nodes[i]; + node.insertBefore(typeof el == "function" ? el(i, node) : el, node.firstChild); + } + + return this; + } + this.prependTo = function(content){ + for (var i = 0, l = this.$nodes.length; i < l; i++) { + target.insertBefore(this.$nodes[i], target.firstChild); + } + return this; + } + + this.attr = function(attrName, value){ + if (value === undefined) + return this.$nodes && this.$nodes[0] && this.$nodes[0].getAttribute(attrName) || ""; + else { + for (var i = 0, l = this.$nodes.length; i < l; i++) { + if (apf.b.$state) + apf.b.$queue.push({ + action : 'setAttribute', + args : [this.$nodes[i], attrName, value] + }); + else if (this.$local) + this.$nodes[i].setAttribute(attrName, value); + else + apf.xmldb.setAttribute(this.$nodes[i], attrName, value); + } + } + + return this; + } + + this.removeAttr = function(attrName){ + for (var i = 0, l = this.$nodes.length; i < l; i++) { + if (apf.b.$state) + apf.b.$queue.push({ + action : 'removeAttribute', + args : [this.$nodes[i], attrName] + }); + else if (this.$local) + this.$nodes[i].removeAttribute(attrName); + else + apf.xmldb.removeAttribute(this.$nodes[i], attrName); + } + + return this; + } + + this.xml = function(){ + var str = []; + for (var i = 0, l = this.$nodes.length; i < l; i++) { + str.push(this.$nodes[i].xml); + } + return str.join("\n"); + } + + this.get = + this.index = function(idx){ + if (idx == undefined) + return apf.getChildNumber(this.$nodes[0], this.$nodes[0].parentNode.getElementsByTagName("*")) + } + + this.eq = function(index){ + return index < 0 ? this.$nodes[this.$nodes.length - index] : this.$nodes[index]; + } + + this.size = + this.length = function(){ + return this.$nodes.length; + } + this.load = function(url){ + + } + + this.next = function(selector){ + if (!selector) selector = "node()[local-name()]"; + return new apf.xmlset(this.$xml, "((following-sibling::" + (this.$xpath == "." ? "node()" : this.$xpath) + ")[1])[self::" + selector.split("|").join("|self::") + "]", this.$local, this); + } + + this.nextAll = function(selector){ + if (!selector) selector = "node()[local-name()]"; + return new apf.xmlset(this.$xml, "(following-sibling::" + (this.$xpath == "." ? "node()" : this.$xpath) + ")[self::" + selector.split("|").join("|self::") + "]", this.$local, this); + } + + this.nextUntil = function(){} + + this.prev = function(selector){ + if (!selector) selector = "node()[local-name()]"; + return new apf.xmlset(this.$xml, "((preceding-sibling::" + (this.$xpath == "." ? "node()" : this.$xpath) + ")[1])[self::" + selector.split("|").join("|self::") + "]", this.$local, this); + } + this.prevAll = function(selector){ + if (!selector) selector = "node()[local-name()]"; + return new apf.xmlset(this.$xml, "(preceding-sibling::" + (this.$xpath == "." ? "node()" : this.$xpath) + ")[self::" + selector.split("|").join("|self::") + "]", this.$local, this); + } + + this.not = function(){} + + this.parent = function(selector){ + return new apf.xmlset(this.$xml.parentNode, this.$local, this); + } + + this.parents = function(selector){} + this.pushStack = function(){} + this.replaceAll = function(){} + this.replaceWith = function(el){ + for (var node, child, i = 0, l = this.$nodes.length; i < l; i++) { + node = this.$nodes[i]; + child = typeof el == "function" ? el(i, node) : el; + + if (apf.b.$state) + apf.b.$queue.push({ + action : 'replaceNode', + args : [child, node] + }); + else if (this.$local) + node.parentNode.replaceChild(child, node); + else + apf.xmldb.replaceNode(child, node); + + } + + return this; + } + + this.siblings = function(selector){ + //preceding-sibling:: + //return new apf.xmlset(this.$xml, "(" + this.$xpath + ")/node()[self::" + selector.split("|").join("|self::") + "]"); + } + + this.text = function(){ + + } + + this.toArray = function(){ + var arr = []; + for (var i = 0, l = this.$nodes.length; i < l; i++) { + arr.push(this.$nodes[i]); + } + return arr; + } + + this.detach = function(selector){ + var items = []; + + for (var node, i = 0, l = this.$nodes.length; i < l; i++) { + node = this.$nodes[i]; + if (!node.selectSingleNode("self::node()[" + selector + "]")) + continue; + + if (apf.b.$state) + apf.b.$queue.push({ + action : 'removeNode', + args : [node] + }); + else if (this.$local) + node.parentNode.removeChild(node); + else + apf.xmldb.removeNode(node); + + items.push(node); + } + + return new apf.xmlset(items, "", this.$local, this); + } + + this.remove = function(selector){ + for (var node, n = this.$nodes, i = n.length - 1; i >= 0; i--) { + node = n[i]; + if (selector && !node.selectSingleNode("self::node()[" + selector + "]")) + continue; + + if (apf.b.$state) + apf.b.$queue.push({ + action : 'removeNode', + args : [node] + }); + else if (this.$local) + node.parentNode.removeChild(node); + else + apf.xmldb.removeNode(node); + } + + return this; + } + + this.children = function(selector){ + var nodes = []; + for (var node, child, i = 0, l = this.$nodes.length; i < l; i++) { + var list = (node = this.$nodes[i]).selectNodes(selector); + for (var j = 0, jl = list.length; j < jl; j++) { + nodes.push(list[j]); + } + } + return new apf.xmlset(nodes, null, this.$local, this); + } + + this.children2 = function(selector){ + return new apf.xmlset(this.$xml, "(" + this.$xpath + ")/node()[self::" + selector.split("|").join("|self::") + "]", this.$local, this); + } + + this.has = + this.find = function(path){ + return new apf.xmlset(this.$xml, "(" + this.$xpath + ")//" + path.split("|").join("|self::"), this.$local, this); + } + + this.query = function(path){ + return new apf.xmlset(this.$xml, "(" + this.$xpath + ")/" + path.split("|").join("|(" + this.$xpath + ")/"), this.$local, this); + } + + this.filter = function(filter){ + var newList = []; + for (var i = 0, l = this.$nodes.length; i < l; i++) { + if (this.$nodes[i].selectSingleNode("self::node()[" + filter + "]")) + newList.push(this.$nodes[i]); + } + return new apf.xmlset(newList, null, this.$local, this); + } + + this.end = function(){ + return this.$previous || this; + } + + this.is = function(selector) { + return this.filter(selector) ? true : false; + } + + this.contents = function(){ + return this.children("node()"); + } + + this.has = function(){ + //return this.children( + } + + this.val = function(value){ + if (value !== undefined) { + apf.setQueryValue(this.$xml, this.$xpath, value); + return this; + } + else + return apf.queryValue(this.$xml, this.$xpath); + } + + this.vals = function(){ + return apf.queryValues(this.$xml, this.$xpath); + } + + this.node = function(){ + return apf.queryNode(this.$xml, this.$xpath); + } + + this.nodes = function(){ + return apf.queryNodes(this.$xml, this.$xpath); + } + + this.clone = function(deep){ + if (this.$nodes.length == 1) + return new apf.xmlset(this.$nodes[0].cloneNode(true), "", this.$local, this); + + var nodes = []; + for (var i = 0, l = this.$nodes.length; i < l; i++) { + nodes.push(this.$nodes[i].cloneNode(deep == undefined ? true : deep)); + } + + return new apf.xmlset(nodes, "", this.$local, this); + } + + this.context = function(){ + return this.$xml; + } + + this.data = function(data){ + for (var i = 0, l = this.$nodes.length; i < l; i++) { + apf.setQueryValue(this.$nodes[i], ".", data); + } + return this; + } + + this.each = function(func){ + for (var i = 0, l = this.$nodes.length; i < l; i++) { + func.call(this.$nodes[i], i); + } + return this; + } + + this.eachrev = function(func){ + for (var i = this.$nodes.length - 1; i >= 0; i--) { + func.call(this.$nodes[i], i); + } + return this; + } + + this.map = function(callback){ + var values = []; + for (var i = 0, l = this.$nodes.length; i < l; i++) { + values.push(callback(this.$nodes[i])); + } + return new apf.xmlset(values, "", this.$local, this); //blrghhh + } + + this.empty = function(){ + this.children().detach(); + return this; + } + + this.first = function(){ + return new apf.xmlset(this.$xml, "(" + this.$xpath + ")[1]", this.$local, this); + } + + this.last = function(){ + return new apf.xmlset(this.$xml, "(" + this.$xpath + ")[last()]", this.$local, this); + } +}).call(apf.xmlset.prototype); + + + + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/core/lib/util/xmldiff.js)SIZE(36580)TIME(Mon, 10 Jan 2011 10:53:11 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + + +apf.xmlDiff = function (doc1, doc2){ + /*var domParser = new apf.DOMParser(); + domParser.allowAnyElement = true; + domParser.preserveWhiteSpace = true; + apf.compareDoc = domParser.parseFromXml(apf.getCleanCopy(mdlTest.data)).documentElement; + + var doc1 = apf.compareDoc, + doc2 = apf.getCleanCopy(mdlTest2.data),*/ + + var debug = true, + hash = {}, + rules = [], + appendRules = [], + reorderRules = [], + preserveWhiteSpace = doc1.ownerDocument.$domParser.preserveWhiteSpace, + + + dt = new Date().getTime(), + + + APPEND = 1, + UPDATE = 2, + REMOVE = 3, + SETATTRIBUTE = 4, + + notFoundEl = {}, notFoundAttr = {}, foundEl = {}, + foundTxt = {}, foundAttr = {}, curNode = {include: {}}, + + arr, arrPath, l, lPath, found, path, p, t, j, i, a, item, subitem, + arrIndex, lastRule, prop, + emptyArr = [], node; + + (function createHash(el, curPath, hash){ + var i, nodes, attr, l, a, e, p; + + curPath += "/" + el.tagName; //or mypath = ? + if (l = (nodes = el.childNodes).length){ + for (i = 0; i < l; i++) { + if ((e = nodes[i]).nodeType == 1) { + createHash(nodes[i], curPath, hash); + nodes[i].$childNumber = i; + } + else { + p = curPath + "/" + e.nodeType, + (hash[p] || (hash[p] = [])).push(e), + e.$isValid = false; + e.$childNumber = i; + } + } + } + if (l = (attr = el.attributes).length){ + for (i = 0; i < l; i++) { + p = curPath + "/@" + (a = attr[i]).nodeName, + (hash[p] || (hash[p] = [])).push(a), + a.$isValid = false; + } + } + + (hash[curPath] || (hash[curPath] = [])).push(el); + el.$isValid = false; + if (el.$amlList) + delete el.$amlList; + //el.$validChildren = 0; + })(doc1, "", hash); + + curNode.include[doc1.parentNode.$uniqueId] = doc1; + (function match(el, curPath, hash, pNode, curParentNode, childNumber){ + var total, first, v, jl, item, include, i, nodes, attr, l, a, e, p, s, sh, + tn, curNode, pcur, pcurl, id, count, node, + max = 0, + found = [], + pInclude = {length: 0}, + oldInclude = curParentNode.include; + + curPath += "/" + el.tagName; //or mypath = ? + if (!(s = hash[curPath])) { + //rules.push([APPEND, el, curParentNode]); + (notFoundEl[curPath] || (notFoundEl[curPath] = [])).push({ + arr : null, + node : el, + curNode : curParentNode + }); + return false; + } + else { + //@todo optimize this + include = (curNode = {}).include = {}; + for (i = 0, l = s.length; i < l; i++) { + if ((item = s[i])) + include[item.$uniqueId] = item; + } + include.length = l; + } + + if (l = (nodes = el.childNodes).length){ + for (i = 0; i < l; i++) { + if ((e = nodes[i]).nodeType == 1) { + match(e, curPath, hash, pNode, curNode, i); + } + else { + if ((sh = hash[p = curPath + "/" + e.nodeType])) { + //Ignore the same node + for (total = 0, first = null, v = e.nodeValue, j = 0, jl = sh.length; j < jl; j++) { + //possible extra heuristic && tn.parentNode.childNodes.length == e.parentNode.childNodes.length + if ((tn = sh[j]) && tn.nodeValue == v) { + if (!first) { + first = { + node : e, + //node2 : tn, + index : j, + //v : v, + arr : sh, + curNode : curNode, + pcur : [tn.parentNode.$uniqueId], + found : [j], + childNumber : i + }; + } + else { + first.found.push(j), + first.pcur.push(tn.parentNode.$uniqueId); + } + total++; + } + } + + if (total) { + /*if (total == 1) { + (tn = first.node2).$isValid = true; //@todo might be removed + (tn = tn.ownerElement).$validChildren++; + }*/ + + pcur = first.pcur, pcurl = pcur.length; + for (j = 0; j < pcurl; j++) { + if (curNode[pcur[j]]) + curNode[pcur[j]] += (1/total); + else + curNode[pcur[j]] = (1/total); + } + + //delete sh[j]; + (foundTxt[p] || (foundTxt[p] = [])).push(first); + + continue; + } + + //Update or New + (notFoundEl[p] || (notFoundEl[p] = [])).push({ + arr : sh, + node : e, + curNode : curNode + }); + + continue; + } + + //New node + //rules.push([APPEND, e, curNode]); + (notFoundEl[p] || (notFoundEl[p] = [])).push({ + isNew : true, + arr : null, + node : e, + curNode : curNode + }); + } + } + } + if (l = (attr = el.attributes).length){ + for (i = 0; i < l; i++) { + pattr = curPath + "/@" + (a = attr[i]).nodeName; + if (sh = hash[pattr]) { + //Ignore the same node + for (total = 0, first = null, v = a.nodeValue, j = 0, jl = sh.length; j < jl; j++) { + //possible extra heuristic && tn.parentNode.childNodes.length == e.parentNode.childNodes.length + if ((tn = sh[j]) && tn.nodeValue == v) { + if (!first) { + first = { + node : a, + //node2 : tn, + index : j, + //v : v, + arr : sh, + curNode : curNode, + pcur : [tn.ownerElement.$uniqueId], + found : [j] + } + } + else { + first.found.push(j), + first.pcur.push(tn.ownerElement.$uniqueId); + } + total++; + } + } + + if (total) { + /*if (total == 1) { + (tn = first.node2).$isValid = true; //@todo might be removed + (tn = tn.ownerElement).$validChildren++; + }*/ + + pcur = first.pcur, pcurl = pcur.length; + for (j = 0; j < pcurl; j++) { + if (curNode[pcur[j]]) + curNode[pcur[j]] += (1 / total); + else + curNode[pcur[j]] = (1 / total); + } + + //delete sh[j]; + (foundAttr[pattr] || (foundAttr[pattr] = [])).push(first); + + continue; + } + + //Update or New + (notFoundAttr[pattr] || (notFoundAttr[pattr] = [])).push({ + arr : sh, + node : a, + curNode : curNode + }); + + continue; + } + + //New node + //rules.push([SETATTRIBUTE, a, curNode]); + (notFoundAttr[pattr] || (notFoundAttr[pattr] = [])).push({ + isNew : true, + arr : null, + node : a, + curNode : curNode + }); + } + } + + /*for (i = 0, l = s.length; i < l; i++) { + //@todo only one? break here?, cache sum? + if ((tn = s[i]) && tn.$validChildren == tn.childNodes.length + tn.attributes.length) { + tn.$isValid = true; + (tn = tn.parentNode).$validChildren++; //@todo -1 not enough? + + delete s[i]; + } + }*/ + + //if (curPath == "/a:application/div/div/div") debugger; + + for (id in curNode) { + if (curNode[id] > max) { + //if (curNode.include[id]) { + max = curNode[id]; + //me = id; + count = 1; + //} + } + else if (curNode[id] == max) { + count++; + } + } + + //We have no idea who el is + if (!max) { + count = s.length; + for (i = count; i >= 0; i--){ + if (!s[i]) + continue; + + pNode = (node = s[i]).parentNode; + + if (node.$isValid || !include[node.$uniqueId] || !oldInclude[pNode.$uniqueId]) { + delete include[node.$uniqueId],//@todo is this needed? + count--; + continue; + } + + //Nodes could have same parent + if (!pInclude[pNode.$uniqueId]) { + pInclude[pNode.$uniqueId] = pNode, + pInclude.length++; + } + found.push(node); + } + } + //We have some hints who el is + else { + for (id in curNode) { + if (curNode[id] != max) + continue; + + pNode = (node = apf.all[id]).parentNode; + + if (node.$isValid || !include[node.$uniqueId] || !oldInclude[pNode.$uniqueId]) { + delete include[node.$uniqueId]; //@todo is this needed? + count--; + continue; + } + + //Nodes could have same parent + if (!pInclude[pNode.$uniqueId]) { + pInclude[pNode.$uniqueId] = pNode, + pInclude.length++; + } + found.push(node); + } + } + + if (found.length) //@experimental - when a new node is found, dont determine the parent. + curParentNode.include = pInclude; + + for (first = null, i = 0, l = found.length; i < l; i++) { //@todo l == count + pNode = (item = found[i]).parentNode; + + //guessing parentNode + if (curParentNode[pNode.$uniqueId]) + curParentNode[pNode.$uniqueId] += 1 / count; + else + curParentNode[pNode.$uniqueId] = 1 / count; + + if (count > 1) { + if (!first) { + first = { + node : el, + //node2 : found[0], + arr : s, + curNode : curNode, + curParentNode : curParentNode, + found : found, + childNumber : childNumber + }; + + //foundEl should be here with found array filled by this loop - we trust our scheme + (foundEl[curPath] || (foundEl[curPath] = [])).push(first); + } + + if (count > (item.$matchedNodes || 0)) + item.$matchedNodes = count; + } + else { //Code below is probably not necessary and can be removed. + item.$isValid = true; + + //Clean up hash + for (var j = s.length - 1; j >= 0; j--) { + if (s[j] == item) { + delete s[j];//this.splice(i, 1); + break; + } + } + } + } + + if (!found.length) { //was !count + //There might be information in the parent about who we are so + //we'll continue the search in recurEl() + var found = []; + for (var prop in curNode.include) { + if (prop == "length") + continue; + found.push(curNode.include[prop]); + } + + (foundEl[curPath] || (foundEl[curPath] = [])).push({ + arr : s, + node : el, + curParentNode : curParentNode, + curNode : curNode, + found : found, + childNumber : childNumber + }); + + //Update or New + /*(notFoundEl[curPath] || (notFoundEl[curPath] = [])).push({ + arr : s, + node : el, + curNode : curParentNode, + me : curNode + });*/ + } + + curNode.curNode = found[0], + curNode.parent = curParentNode, + curNode.curPath = curPath; + })(doc2, "", hash, doc1, curNode); + + //if (doc1.$isValid) //docs match completely + //return true; + + /* + indexOf (nodes in arrPath) to find out if node still exists. + do remove node from .arr to say its determined + */ + + function checkOrder(node1, node2) { + if (node1.$childNumber != node2.childNumber) + reorderRules.push([node1, node2.childNumber]); + } + + //Process all conflicting nodes on this path + //@todo this should be optimized a lot + function recurEl(path){ + var pathCollision = foundEl[path]; + if (!pathCollision) + return; + + var curItem, curMatch, l, count, nMatch, j, i, potentialMatches, + leastMatchNodes, pl, pNode; + for (i = 0, pl = pathCollision.length; i < pl; i++) { + //Make sure to do cleanup! + //Strategy!: the parent knows best! + + //if (pathCollision[i].node2.$isValid) + //continue; + + potentialMatches = (curItem = pathCollision[i]).found, + leastMatchNodes = 100000; + for (count = j = 0, l = potentialMatches.length; j < l; j++) { + if ((curMatch = potentialMatches[j]).$isValid) + continue; + + nMatch = curMatch.$matchedNodes; + if (nMatch < leastMatchNodes) { + leastMatchNodes = nMatch, + count = 1; + } + else if (nMatch == leastMatchNodes) { + count++; + } + } + + //Found match + if (count == 1) { + for (j = 0, l = potentialMatches.length; j < l; j++) { + if (!(curMatch = potentialMatches[j]).$isValid + && curMatch.$matchedNodes == leastMatchNodes) { + curMatch.$isValid = true, + //curMatch.parentNode.$validChildren++; + curItem.curNode.curNode = curMatch;//set who is me + checkOrder(curMatch, curItem); + break; + } + } + } + else if (count) { + //recursion.. with pathCollion[i].curNode.parent.found -> potentialMatches for the parent; + //if parent found and it was doubting, delete entry from foundEl[path][i].node == pathCollion[i].node.parentNode + var include = curItem.curParentNode.include; + //There's only one parent + if (include.length == 1) { + pNode = curItem.curParentNode.curNode;//apf.all[include[0]]; + for (j = 0, l = potentialMatches.length; j < l; j++) { + if (!(curMatch = potentialMatches[j]).$isValid + && curMatch.$matchedNodes == leastMatchNodes + && curMatch.parentNode == pNode) { + curMatch.$isValid = true, + //curMatch.parentNode.$validChildren++; + curItem.curNode.curNode = curMatch;//set who is me + checkOrder(curMatch, curItem); + break; + } + } + //@todo check here if found + continue; + } + + //Determine who's who for the parents + recurEl(curItem.curParentNode.curPath); + + pNode = curItem.curParentNode.curNode; + for (j = 0, l = potentialMatches.length; j < l; j++) { + if (!(curMatch = potentialMatches[j]).$isValid + && curMatch.$matchedNodes == leastMatchNodes + && curMatch.parentNode == pNode) { + curMatch.$isValid = true, + //curMatch.parentNode.$validChildren++; + curItem.curNode.curNode = curMatch;//set who is me + checkOrder(curMatch, curItem); + break; + } + } + + /*for (var j = 0; j < potentialMatches.length; j++) { + if (!potentialMatches[j].$isValid && potentialMatches[j].$matchedNodes == leastMatchNodes) { + recur + } + }*/ + } + //Add when node is same as others + else { + //This case is for when there is a node that almost looks the + //same and was discarded but was actually needed + var nodes = hash[path], foundLast = false; + for (var k = 0; k < nodes.length; k++) { + if (nodes[k] && !nodes[k].$isValid) { + nodes[k].$isValid = true; + curItem.curNode.curNode = nodes[k]; + checkOrder(nodes[k], curItem); + foundLast = true; + break; + } + } + + //if (curItem.curParentNode.include.length == 1) { + //This case is for when a node looks the same as other nodes + if (!foundLast) { + curItem.curNode.isAdding = true; + + if (!curItem.curParentNode.isAdding) { + (notFoundEl[path] || (notFoundEl[path] = [])).push({ + isNew : true, + arr : null, + node : curItem.node, + curNode : curItem.curParentNode + }); + } + } + } + } + + delete foundEl[path];//Will this mess with the iterator? + } + + //Process conflicting element nodes + for (path in foundEl) { + recurEl(path); + } + + //Process conflicting text nodes + //debugger; + for (path in foundTxt) { + arr = foundTxt[path], //found text nodes with this path + l = arr.length, + arrPath = arr[0].arr; + + var lost = []; + for (i = 0; i < l; i++) { + t = arr[i], + arrIndex = t.found; + + //Find the right node + if ((p = t.curNode) && (p = p.curNode)) { + if (arrPath[t.index] && p == arrPath[t.index].parentNode) { + checkOrder(arrPath[t.index], t); + delete arrPath[t.index]; + //p.$validChildren++; + } + else { + //cleanup hash + for (found = false, j = 0; j < arrIndex.length; j++) { + if (arrPath[arrIndex[j]] && arrPath[arrIndex[j]].parentNode == p) { + checkOrder(arrPath[arrIndex[j]], t); + delete arrPath[arrIndex[j]], + //p.$validChildren++; + found = true; + break; + } + } + + //if not found, what does it mean? + if (!found && !t.curNode.isAdding) + (notFoundEl[path] || (notFoundEl[path] = [])).push(t); + } + } + else { + //throw new Error("hmm, new?"); + //part of a new chain? + /*if (!t.curNode.isAdding) { + + }*/ + //lost.push(t); + + if (!t.curNode.isAdding) + (notFoundEl[path] || (notFoundEl[path] = [])).push(t); + } + } + + /*for (i = 0; i < lost.length; i++) { + for (var j = 0; j < arrPath.length; j++) { + if (!arrPath[j]) + continue; + alert(1); + (notFoundEl[path] || (notFoundEl[path] = [])).push(lost[i]); + break; + } + }*/ + } + + //Process conflicting attr nodes + for (path in foundAttr) { + arr = foundAttr[path], //found attributes with this path + l = arr.length, + arrPath = arr[0].arr; + + for (i = 0; i < l; i++) { + t = arr[i], + arrIndex = t.found; + + //Find the right node + if ((p = t.curNode) && (p = p.curNode)) { + if (arrPath[t.index] && p == arrPath[t.index].ownerElement) { + delete arrPath[t.index]; + //p.$validChildren++; + } + else { + //cleanup hash + for (found = false, j = 0; j < arrIndex.length; j++) { + if (arrPath[arrIndex[j]] && arrPath[arrIndex[j]].ownerElement == p) { + delete arrPath[arrIndex[j]], + //p.$validChildren++; + found = true; + break; + } + } + + //if not found, what does it mean? + if (!found && !t.curNode.isAdding) + (notFoundAttr[path] || (notFoundAttr[path] = [])).push(t); + } + } + else { + //throw new Error("hmm, new?"); + //part of a new chain? + + if (!t.curNode.isAdding) + (notFoundAttr[path] || (notFoundAttr[path] = [])).push(t); + } + } + } + + //Process not found attribute + /* + arr : sh, + node : a, + curNode : curNode + + is it update or new? + - get the parent node from curNode.curNode; + - check if there is a parent node in the list that matches + - remove entry in arrPath + - else + - parent is new node + */ + for (path in notFoundAttr) { + arr = notFoundAttr[path], //not found attributes with this path + l = arr.length, + arrPath = arr[0].arr || emptyArr, + lPath = arrPath.length; + + //should there be a check for isAdded here? + + for (i = 0; i < l; i++) { + a = arr[i]; + + if ((p = t.curNode) && p.isAdding) { + //Ignore, parent is new, so this will be added automatically + continue; + } + + //Found parent + if ((p = a.curNode) && (p = p.curNode)) { + if (a.node.nodeName == "id") + p.setAttribute("id", a.node.nodeValue); + else + rules.push([SETATTRIBUTE, p, a.node]); + //p.$validChildren++; + + //cleanup hash + for (j = 0; j < lPath; j++) { + if (arrPath[j] && arrPath[j].ownerElement == p) { + delete arrPath[j]; + break; + } + } + } + else { + //Ignore, parent is new, so this will be added automatically (i think :S) + } + } + } + //Process not found nodes (all but attribute) + for (path in notFoundEl) { + arr = notFoundEl[path], //not found attributes with this path + l = arr.length, + arrPath = arr[0].arr || emptyArr, + lPath = arrPath.length; + +//if (path == "/a:application/div/div/div/div/a/3") debugger; + + for (i = 0; i < l; i++) { + t = arr[i]; + + if ((p = t.curNode) && p.isAdding) { + //Ignore, parent is new, so this will be added automatically + continue; + } + + //Found parent + if (p = p.curNode) { + appendRules.push(lastRule = [APPEND, p, t.node]); + //p.$validChildren++; + //cleanup hash + for (j = 0; j < lPath; j++) { + if (arrPath[j] && arrPath[j].parentNode == p) { + if (t.node.nodeType != 1) { + lastRule[0] = UPDATE; + lastRule[1] = arrPath[j]; + } + else { + appendRules.length--; + } + + delete arrPath[j]; + break; + } + } + } + else { + + } + } + } + + //Process remaining hash + for (prop in hash){ + item = hash[prop]; + for (i = 0, l = item.length; i < l; i++) { + subitem = item[i]; + //@todo check if the last check is actually needed: + // && subitem.$validChildren != subitem.childNodes.length + subitem.attributes.length) { + if (subitem && !subitem.$isValid && (subitem.parentNode || subitem.ownerElement).$isValid) { + rules.push([REMOVE, subitem]); + } + } + } + + var dt = new Date().getTime(); + //This loop could be optimized away + var q = {}, doc = doc1.ownerDocument; + for (i = 0, l = appendRules.length; i < l; i++) { + switch((item = appendRules[i])[0]) {//@todo optimize + case UPDATE: + + if (debug) { + apf.console.log("XmlDiff: UPDATE " + + item[1].nodeValue + " with " + + item[2].nodeValue); + } + + + //item[1].nodeValue = item[2].nodeValue; + item[1].$setValue(item[2].nodeValue); + if (item[1].nodeType != 2) { //@todo apf3.0 optimize this + var childNr1 = apf.getChildNumber(item[1]), + childNr2 = apf.getChildNumber(item[2]); + if (childNr1 != childNr2) + item[1].parentNode.insertBefore(item[1], item[1].parentNode.childNodes[childNr2]); + } + //@todo need trigger for aml node + break; + case APPEND: + + if (debug) { + apf.console.log("XmlDiff: APPEND " + + (item[2].tagName ? "<" + item[2].tagName + ">" : item[2].nodeValue) + " to " + + "<" + item[1].localName + "> [" + item[1].$uniqueId + "]"); + } + + + if (!item[1].canHaveChildren) { + item[1].$aml = item[2].parentNode; + if (item[1].$redraw) + item[1].$redraw(); + continue; + } + + if (item[1].render != "runtime") { + var xml = item[1].parentNode; + while (xml && xml.nodeType == 1 && !xml.getAttribute("render")) + xml = xml.parentNode; + if (xml && xml.render) {// && !xml.visible) { //@todo apf3.0 add case for page + if (xml.$amlList) + xml.$amlList.push(item); + else { + xml.$amlList = [item]; + xml.addEventListener("beforerender", function(e){ + var nodes = this.$amlList; + this.$amlList = null; + + for (var item, i = 0, l = nodes.length; i < l; i++) { + item = nodes[i]; + var childNr = apf.getChildNumber(node = item[2]); + item[1].insertBefore(doc.importNode(node, true), item[1].childNodes[childNr]); + } + + //@todo call afterrender + this.$rendered = true; + this.removeEventListener("beforerender", arguments.callee); + + return false; + }); + xml.$rendered = false; + } + } + else { + //!preserveWhiteSpace + /*var list = item[2].parentNode.selectNodes("node()[local-name() or string-length(normalize-space())]"); + var idx = apf.getChildNumber(item[2], list); + if (idx < list.length) {*/ + + (item[1].$amlList || (item[1].$amlList = []))[apf.getChildNumber(item[2])] = item; + q[item[1].$uniqueId] = item[1]; + } + } + else { + item[1].$aml = item[2].parentNode; + item[1].$rendered = false; + } + break; + } + } + + //@todo apf3.0 optimize this + var list, newNode; + for (var id in q) { + list = q[id].$amlList; + for (var item, i = 0; i < list.length; i++) { + item = list[i]; + if (!item) continue; + /*if (item[2].nodeType == 1) { + newNode = doc.createElementNS(item[2].namespaceURI || apf.ns.xhtml, item[2][apf.TAGNAME]); + item[1].insertBefore(newNode, item[1].childNodes[i]); + } + else {*/ + newNode = doc.importNode(item[2], true); + if (newNode) + item[1].insertBefore(newNode, item[1].childNodes[i]); + //} + } + } + + for (i = 0, l = rules.length; i < l; i++) { + switch((item = rules[i])[0]) { + case REMOVE: + + if (debug) { + apf.console.log("XmlDiff: REMOVE " + + (item[1].localName ? "<" + item[1].localName + ">" : item[1].nodeValue) + + " [" + item[1].$uniqueId + "] "); + } + + if ((node = item[1]).destroy) { + node.destroy(true, true); + } + else if (node.parentNode) + node.parentNode.removeChild(node); + else + node.ownerElement.removeAttributeNode(node); + break; + case SETATTRIBUTE: + + if (debug) { + apf.console.log("XmlDiff: ATTRIBUTE " + + "<" + item[1].localName + "> [" + item[1].$uniqueId + "] " + + item[2].nodeName + "=\"" + item[2].nodeValue + "\""); + } + + + item[1].setAttribute((item = item[2]).nodeName, item.nodeValue); + break; + } + } + + for (var node, pnode, next, nr, tonr, i = 0;i < reorderRules.length; i++) { + node = reorderRules[i][0]; + nr = node.$childNumber; + tonr = reorderRules[i][1]; + pnode = node.parentNode; + next = pnode.childNodes[tonr + (nr < tonr ? 1 : 0)]; + if (node.nextSibling != next && node != next) + pnode.insertBefore(node, next); + } + + apf.queue.empty(); + + + if (debug) + apf.console.time("Diff time:" + (time = (new Date() - dt))); + + /*var res1 = (apf.formatXml(doc2.xml)); + var res2 = (apf.formatXml(doc1.serialize())); + + if(res1 != res2) { + throw new Error("A potentially serious xml diff problem was detected. \ + Please contact the author of this library:\n" + + res1 + "\n\n" + res2); //@todo make this into a proper apf3.0 error + }*/ + + +} + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/core/lib/util/zmanager.js)SIZE(2540)TIME(Mon, 10 Jan 2011 10:53:11 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + +/** + * Manages the z-index of all elements in the UI. It takes care of different + * regions in the z dimension preserved for certain common UI scenarios. + * + * Remarks: + * The following regions are defined: + * From: To: For: + * 10 100000 Common Elements (each element a unique z index) + * 1000000 1100000 Plane (Modal Windows / Maximized Panels) (latest shown highest) + * 2000000 2100000 Popup (Menus / Dropdown Containers) (latest shown highest) + * 3000000 3100000 Notifiers + * 4000000 4100000 Drag Indicators + * 10000000 11000000 Print + * + * @private + */ +apf.zmanager = function(){ + var count = { + "default" : { + level : 10 + }, + "plane" : { + level : 1000000 + }, + "popup" : { + level : 2000000 + }, + "notifier" : { + level : 3000000 + }, + "drag" : { + level : 4000000 + }, + "print" : { + level : 10000000 + } + }; + + this.set = function(type, main, companion){ + main.style.zIndex = count[type].level++; + if (companion) { + if (companion.$storedZ == undefined) + companion.$storedZ = companion.style.zIndex; + companion.style.zIndex = count[type].level++ + } + } + + this.clear = function(main, companion){ + if (companion.$storedZ == main.style.zIndex + 1) { + companion.style.zIndex = companion.$storedZ; + companion.$storedZ = undefined; + } + } +}; + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/core/lib/history.js)SIZE(9968)TIME(Mon, 10 Jan 2011 10:53:11 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + + +/** + * Implementation of hash change listener. The 'hash' is the part of the + * location string in your browser that takes care of pointing to a section + * within the current application. + * Example: + * + * www.example.com/index.php#products + * + * Remarks: + * In future browsers (> 2009) the location hash can be set by script and + * {@link element.history.event.hashchange} is called when it's changed by using the back or forward + * button of the browsers. In most of the current (2009) browsers this is not the case. + * This object handles that logic for those browsers in such a way that the user + * of the application can use the back and forward buttons in an intuitive manner. + * + * Note on Internet Explorer 8. When switching between the IE7 compatibility mode + * and IE8 mode the history navigation will break. A browser restart is then + * required to fix it. Individually history navigation works fine in each mode. + * + * @event hashchange Fires when the hash changes. This can be either by setting + * a new hash value or when a user uses the back or forward button. Typing a + * new hash value in the location bar will also trigger this function. + * Example: + * + * apf.addEventListener("hashchange", function(e){ + * var info = e.page.split(":"); + * + * switch(info[0]) { + * case "product": //hash is for instance 'product:23849' + * //Sets the application state to display product info + * //For more information see {@link element.state} + * stProduct.activate(); + * //Loads a product by id + * loadProduct(info[1]); + * break; + * case "news": + * stNews.activate(); + * break; + * } + * }); + * + * + * @default_private + */ +apf.history = { + inited: false, + page : null, + past : [], + future: [], + delay : 1, + + init : function(defName, getVar, delay){ + if (this.inited) + return; + + if (delay) + this.delay = delay; + + this.inited = true; + + var name, _self = this; + function preInit() { + name = apf.dispatchEvent("hashinit") + || location.href.match(/#(.*)$/) && decodeURI(RegExp.$1) + || apf._GET[getVar || -1] || defName; + + + location.hash = name; + _self.hasChanged(name || null); + } + + if (apf.supportHashChange) { + $setTimeout(function() { + preInit(); + + window.onhashchange = function(){ + var page = location.hash.replace("#", ""); + apf.history.hasChanged(decodeURI(page)); + }; + }); + } + else if (apf.isIE) { + preInit(); + var str = + "\ + \ +

0

\ + \ + "; + + if (top == self) { + document.body.insertAdjacentHTML("beforeend", + ""); + document.frames["nav"].document.open(); + document.frames["nav"].document.write(str); + document.frames["nav"].document.close(); + } + + this.iframe = document.frames["nav"];// : document.getElementById("nav").contentWindow; + //Check to see if url has been manually changed + this.timer2 = setInterval(function(){ + if (!apf.history.changingHash && location.hash != "#" + apf.history.page) { + var name = location.hash.replace(/^#/, ""); + var page = apf.history.page; + apf.history.setHash(name, true, true); + apf.history.page = page; + apf.history.hasChanged(name); + } + }, apf.history.delay || 1); + } + else { + preInit(); + apf.history.lastUrl = location.href.toString(); + this.timer2 = setInterval(function(){ + if (apf.history.lastUrl == location.href.toString()) + return; + + apf.history.lastUrl = location.href.toString(); + //var page = location.href.replace(/^.*#(.*)$/, "$1") + var page = location.hash.replace("#", "");//.replace(/^.*#(.*)$/,"$1"); + apf.history.hasChanged(decodeURI(page)); + }, 20); + } + }, + to_name : null, + + /** + * Sets the hash value of the location bar in the browser. This is used + * to represent the state of the application for use by the back and forward + * buttons as well as for use when bookmarking or sharing url's. + * @param {String} name the new hash value. + * @param {Boolean} timed whether to add a delay to setting the value. + */ + setHash : function(name, timed, force){ + if (this.changing || this.page == name || !force + && decodeURIComponent(location.hash) == "#" + decodeURIComponent(name)) { + this.to_name = name; + return; + } + + if (!apf.supportHashChange && apf.isIE && !timed) { + this.to_name = name; + return $setTimeout(function(){ + apf.history.setHash(apf.history.to_name, true, force); + }, 200); + } + + this.changePage(name); + if (!this.inited) + return this.init(name); + + if (!apf.supportHashChange && apf.isIE) { + var h = this.iframe.document.body + .appendChild(this.iframe.document.createElement('h1')); + h.id = name; + h.innerHTML = "1"; + } + + (!apf.supportHashChange && apf.isIE ? this.iframe : window).location.href = "#" + name; + + if (!apf.isIE && !apf.isGecko && !apf.isIphone) + apf.history.lastUrl = location.href.toString(); + //else if (apf.isIE8) + }, + + timer : null, + changePage: function(page, force){ + if (!apf.supportHashChange && apf.isIE) { + this.page = page; + this.changingHash = true; + clearTimeout(this.timer); + this.timer = $setTimeout(function(){ + location.hash = page; + apf.history.changingHash = false; + }, 1); + } + }, + + update: function(page) { + var i, l, idx = 0; + + // check past: + for (i = 0, l = this.past.length; i < l && idx === 0; i++) { + if (this.past[i] == page) + idx = i + 1; + } + if (idx > 0) { + // part of past up till page (Array.slice), EXCLUDING page + this.future = this.past.slice(idx, this.past.length - 1) + .concat(this.future).makeUnique(); + this.past.splice(idx, this.past.length - (idx)); + idx = -idx; + } + else { + // check future: + for (i = 0, l = this.future.length; i < l && idx === 0; i++) { + if (this.future[i] == page) { + idx = i + 1; + // current past + part of the future up till page + // (Array.splice), INCLUDING page + this.past = this.past.concat(this.future + .splice(0, this.future.length - idx)).makeUnique(); + } + } + if (idx === 0) { + this.past.push(page); + idx = 1; + } + } + + return idx; + }, + + hasChanged: function(page, force){ + if (page == this.page && !force) + return; + this.changePage(page, force); + + this.changing = true; + if (apf.dispatchEvent("hashchange", { + oldURL : this.page, + newURL : page, + page : page, + index : this.update(page) + }) === false) { + page = location.hash = this.page; + }; + this.changing = false; + + this.page = page; + } +}; + + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/core/lib/config.js)SIZE(8175)TIME(Mon, 10 Jan 2011 10:53:11 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + + +apf.config = new apf.Class().$init(); +apf.extend(apf.config, { + //Defaults + disableRightClick : false, + allowSelect : false, + allowBlur : true, + autoDisableActions : true, + autoDisable : false, /** @todo fix this to only autodisable when createmodel is not true */ + disableF5 : true, + autoHideLoading : true, + disableSpace : true, + defaultPage : "home", + disableBackspace : true, + undokeys : false, + outline : false, + dragOutline : false, + resizeOutline : false, + autoDisableNavKeys : true, + disableTabbing : false, + resourcePath : null, + initdelay : true, + liveText : false, + + iePngFix : false, + + + iphoneFullscreen : true, + iphoneStatusbar : 'default', //other options: black-translucent, black + iphoneIcon : null, + iphoneIconIsGlossy : false, + iphoneFixedViewport: true, + + + x : "", + y : "", + w : "", + h : "", + + snapcontainer : true, + snapelement : true, + snapguide : true, + snapgrid : false, + showgrid : false, + gridsize : 10, + + $setGridSize : function(value){ + if (value) + this.gridsize = value; + + if (this.showgrid) { + app.$int.style.backgroundImage = "url(images/grid/" + this.gridSize + ".gif)"; + app.$int.style.backgroundRepeat = "repeat"; + } + else { + app.$int.style.backgroundImage = ""; + app.$int.style.backgroundRepeat = ""; + } + }, + + skinset : "default", + name : apf.isO3 ? "o3App" : self.window && window.location.href.replace(/[^0-9A-Za-z_]/g, "_"), + + tags : {}, + defaults : {}, + baseurl : "", + + "model" : "@default", + "empty-message" : "No items", + "loading-message" : "Loading...", + "offline-message" : "You are currently offline.", + + setDefaults : function(){ + + if (apf.isParsingPartial) { + this.disableRightClick = false; + this.allowSelect = true; + this.autoDisableActions = true; + this.autoDisable = false; + this.disableF5 = false; + this.autoHideLoading = true; + this.disableSpace = false; + this.disableBackspace = false; + this.undokeys = false; + this.disableTabbing = true; + this.allowBlur = true; + } + + }, + + getDefault : function(type, prop){ + var d = this.defaults[type]; + if (!d) + return; + + for (var i = d.length - 1; i >= 0; i--) { + if (d[i][0] == prop) + return d[i][1]; + } + }, + + $handlePropSet : function(name, value){ + //this[name] = value; + //@todo I dont want to go through all the code again, maybe later + this[name.replace(/-(\w)/g, function(m, m1){ + return m1.toUpperCase() + })] = this[name] = value; + + (this.$propHandlers && this.$propHandlers[name] + || apf.GuiElement.propHandlers[name] || apf.K).call(this, value); + }, + + $inheritProperties : {}, + + $propHandlers : { + "baseurl" : function(value){ + this.baseurl = apf.parseExpression(value); + }, + "language" : function(value){ + + apf.addEventListener("load", function(){ + apf.setModel(apf.config.language, apf.language); + }); + + }, + "resource-path" : function(value){ + this.resourcePath = apf.parseExpression(value || "") + .replace(/resources\/?|\/$/g, ''); + }, + + "iepngfix" : function(value, x){ + this.iePngFix = (!apf.supportPng24 + && (apf.isTrue(value) + || x.getAttribute("iepngfix-elements"))); + + if (this.iePngFix) { + // run after the init() has finished, otherwise the body of the + // document will still be empty, thus no elements found. + $setTimeout(function() { + apf.iepngfix.limitTo(x.getAttribute("iepngfix-elements") || "").run(); + }); + } + }, + + + "skinset" : function(value) { + if (this.$amlLoaded) + apf.skins.changeSkinset(value); + }, + + + "outline" : function(value) { + this.dragOutline = + this.resizeOutline = + this.outline = apf.isTrue(value); + }, + "drag-outline" : function(value){ + this.dragOutline = value + ? apf.isTrue(value) + : false; + }, + "resize-outline" : function(value){ + this.resizeOutline = value + ? !apf.isFalse(value) + : false; + }, + + + "login" : function(value, x) { + apf.auth.init(x); + }, + + + "storage" : function(value) { + if (value) + apf.storage.init(value); + }, + + + "offline" : function(value){ + if (value && typeof apf.offline != "undefined") + apf.offline.init(value); + }, + + + "disable-f5" : function(value){ + if (apf.isDeskrun && value) + shell.norefresh = true; + }, + + + "showgrid": function(value){ + this.$setGridSize(); + }, + "gridsize": function(value){ + this.$setGridSize(value); + }, + + "debug" : function(value) { + + if (value) { + apf.$debugwin.activate(); + apf.addEventListener("load", function(){ + //$setTimeout("apf.$debugwin.activate();", 200) //@todo has a bug in gecko, chrome + apf.removeEventListener("load", arguments.callee); + }); + } + + apf.debug = value; + } + } +}); + + +if (apf.history) + apf.addEventListener("load", function(){ + apf.history.init(apf.config.defaultPage, "page"); + }); + + + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/core/lib/offline.js)SIZE(20264)TIME(Mon, 10 Jan 2011 10:53:11 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + + +/** + * @define offline + * Adds offline support for aml applications. It can store and restore the state + * of the application, the models, any transaction that occurred whilst being + * offline, queuing actions (ActionTracker state) and state of the runtime + * application itself (all properties of each element). This allows the + * application to return to the exact state the user left it, when needed. This + * means that when enabled you can at any moment turn of your computer (i.e. + * remove the battery) and when your computer starts up whilst sitting in the + * train start the application and continue working as if the application + * was never closed. + * Example: + * + * + * + * + * + * + * @event losechanges Fires before the offline state is removed. + * cancelable: Prevents the application from losing it's recorded offline state. + * @event beforeoffline Fires before bringing the application offline. + * cancelable: Prevents the application from going offline + * @event afteroffline Firest after the application is brought offline. + * @event beforeonline Fires before bringing the application online. + * cancelable: Prevents the application from going online + * @event afteronline Fires after the application is brought online. + * @event beforeload Fires before loading the offline state into this application. + * cancelable: Prevents the application from reloading it's offline state. + * @event sync Fires at each sync item's completion. + * object: + * {Number} position the number of the item in the list that is currently processed. + * {Number} length the total number of items in the list. + * + * @inherits apf.Class + * + * @attribute {Number} progress the progress of the sync. A number between 0 and 1. + * Example: + * + * + * Synchronizing your changes + * + * Cancel + * Hide Window + * + * + * @attribute {Number} position the progress of the sync. + * @attribute {Number} length the total length of items to sync. + * @attribute {Boolean} syncing whether the application is syncing while coming online. + * @attribute {Boolean} onLine whether the application is online. This property is false during sync. + * @attribute {String} resources the resources that should be + * kept offline and synced later. This is a pipe '|' seperated list. + * Possible values: + * application deals with making the actual application offline avaible. + * models takes care of having the data of the models offline available. + * transactions records the state of the actiontrackers so that these are available offline. + * queue handles queuing of actions that can only be executed whilst online. + * state records the state of all elements in this application on a property level. + * @attribute {Number} rdb-timeout the number of milliseconds + * after the remote databindings server considers a client + * offline and destroys all saved offline messages. + * + * @default_private + */ +apf.offline = { + /** + * whether offline support is enabled. + * @type {Boolean} + */ + enabled : false, + + /** + * whether the application is online. + * @type {Boolean} + */ + onLine : -1, + resources : ["application", "models", "transactions", "queue", "state"], + autoInstall : false, + storage : null, + inited : false, + rdbTimeout : 600000,//After 10 minutes, we assume the RDB messaged will be destroyed + + init : function(aml){ + apf.makeClass(this); + + //Read configuration + if (aml) { + this.$aml = aml; + + if (typeof aml == "string") { + + } + else if (aml.nodeType) { + if (aml.getAttribute("resources")) + this.providers = aml.getAttribute("resources").split("|"); + + /** + * @private + */ + if (aml.getAttribute("rdb-timeout")) + this.rdbTimeout = parseInt(aml.getAttribute("rdb-timeout")); + + //Events + var a, i, attr = aml.attributes; + for (i = 0; i < attr.length; i++) { + a = attr[i]; + if (a.nodeName.indexOf("on") == 0) + this.addEventListener(a.nodeName, + + apf.lm.compile(a.nodeValue, {event: true, parsecode: true}) + + ); + } + } + else { + apf.extend(this, aml); + } + } + + + var provider = apf.offline.application.init(aml) + + + //Check for storage provider + if (provider) { + this.storage = apf.storage.getProvider(provider); + + + if (this.storage) + apf.console.info("Installed storage provider '" + provider + "'"); + + } + + if (!this.storage) { + this.storage = apf.storage.initialized + ? apf.storage + : apf.storage.init(); //autodetect + } + + if (!this.storage) { + + throw new Error("Offline failed to attain access \ + to a storage provider"); + + + return; + } + + if (!this.storage.isPermanent()) { + apf.addEventListener("exit", function(){ + return apf.offline.dispatchEvent("losechanges"); + }); + } + + if (this.storage.asyncInit) { + apf.document.$domParser.$shouldWait++; //@todo apf3.0 make this work again + this.storage.ready(function(){ + apf.offline.storage.onready = null; //Prevent being called twice + apf.offline.continueInit(); + apf.document.$domParser.$continueParsing(apf.document.documentElement); + }); + + return; + } + + this.continueInit(); + }, + + continueInit : function(){ + // Check if all specified resources are available + for (var i = this.resources.length - 1; i >= 0; i--) { + if (!this[this.resources[i]]) + this.resources.removeIndex(i); + else + this[this.resources[i]].init(this.$aml); + } + + this.enabled = true; + + + this.detector.init(this.$aml); + + + this.offlineTime = parseInt(this.storage.get("offlinetime", this.namespace)); + + //If we were offline lets stay offline + if (this.offlineTime) + this.goOffline(); + else //Else we try to go online + this.goOnline(); + + apf.offline.dispatchEvent("load"); + }, + + $destroy : function(){ + + apf.console.info("Cleaning offline"); + + + if (this.provider && this.provider.destroy) + this.provider.destroy(); + + if (this.storage && this.storage.destroy) + this.storage.destroy(); + + for (var i = this.resources.length - 1; i >= 0; i--) { + if (this[this.resources[i]] && this[this.resources[i]].destroy) + this[this.resources[i]].destroy(); + } + }, + + IDLE : 0, //idle + TO_OFFLINE : 1, //going offline + TO_ONLINE : 2, //going online + STOPPING : 3, //stopping going online + + /** + * Indicates what's happening right now + */ + inProcess : 0, + + $supportedProperties : ["syncing", "position", "length", "progress", "onLine"], + handlePropSet : function(prop, value, force){ + this[prop] = value; + //All read-only properties + }, + + /** + * Brings the application offline. + */ + goOffline : function(){ + if (!this.enabled || this.onLine === false + || this.inProcess == this.TO_OFFLINE) + return false; + + //We can't go offline yet, we'll terminate current process and wait + if (this.inProcess) { + this.inProcess = this.STOPPING; + return false; + } + + if (this.dispatchEvent("beforeoffline") === false) + return false; + + //We're offline, let's dim the light + this.setProperty("onLine", false); + this.inProcess = this.TO_OFFLINE; + + if (!this.offlineTime) { + this.offlineTime = new Date().getTime(); + this.storage.put("offlinetime", this.offlineTime, this.namespace); + } + + + //if (apf.auth.retry) //Don't want to ruin the chances of having a smooth ride on a bad connection + // apf.auth.loggedIn = false; //we're logged out now, we'll auto-login when going online + + + + //Turn off detection if needed + if (this.detector.enabled && this.detector.detection != "manual") + this.detector.start(); + + + + if (!this.initial) { + /** + * @private + */ + this.initial = { + disableRDB : apf.xmldb.disableRDB //@todo record this in storage + } + } + apf.xmldb.disableRDB = true; + + + this.inProcess = this.IDLE; + + this.dispatchEvent("afteroffline"); + + + apf.console.info("The application is now working offline.") + + + return true;//success + }, + + /** + * Brings the application online. + */ + goOnline : function(){ + if (!this.enabled || this.onLine === true + || this.inProcess == this.TO_ONLINE) + return false; + + if (this.dispatchEvent("beforeonline") === false) + return false; + + //We're online, let's show the beacon + this.setProperty("onLine", true); //@todo Think about doing this in the callback, because of processes that will now intersect + this.inProcess = this.TO_ONLINE; + this.onlineTime = new Date().getTime(); + this.reloading = false; + + + apf.console.info("Trying to go online.") + + + + //Turn off detection if needed + if (this.detector.enabled && this.detector.detection == "error") + this.detector.stop(); + + + + //Check if we have to reload all models + this.$checkRsbTimeout(); + + //Reset RDB in original state + if (this.initial) + apf.xmldb.disableRDB = this.initial.disableRDB; + + + var callback = function(){ + /* + @todo syncing should probably be patched to take a random + time before starting to prevent blasting a server during a + glitch, of course decent loadbalancing would solve this as well. + */ + this.startSync(); + + this.dispatchEvent("afteronline"); + } + + + var auth = apf.document.getElementsByTagNameNS(apf.ns.apf, "auth")[0]; + if (!auth) + return; + //First let's log in to the services that need it before syncing changes + if (auth.needsLogin && auth.loggedIn) { // && !auth.loggedIn + auth.authRequired({ + object : this, + retry : callback + }); + } + else + + { + callback.call(this); + } + + return true;//success + }, + + + /** + * If we've been offline for a long time, + * let's clear the models, we can't trust the data anymore + */ + $checkRsbTimeout : function(){ + if (!this.rdbTimeout) + return; + + var i, j, k, rdbs = apf.nameserver.getAll("remote"); + for (i = 0; i < rdbs.length; i++) { + var rdb = rdbs[i]; + if (this.reloading + || this.onlineTime - this.offlineTime > this.rdbTimeout) { + if (!this.reloading) { + if (this.dispatchEvent("beforereload") === false) { + + apf.console.warn("Warning, potential data corruption\ + because you've cancelled reloading the data of all \ + remote databinding synchronized models."); + + + break; + } + + this.reloading = true; + } + + rdb.discardBefore = this.onlineTime; + + for (j = 0; k < rdb.models.length; j++) { + rdb.models[j].clear(); + + + apf.offline.models.addToInitQueue(rdb.models[j]) + + } + } + } + + if (this.reloading) { + + apf.console.warn("The application has been offline longer than the \ + server timeout. To maintain consistency the models\ + are reloaded. All undo stacks will be purged."); + + + + apf.offline.transactions.clear("undo|redo"); + + + + var ats = apf.nameserver.getAll("actiontracker"); + for (var i = 0; i < ats.length; i++) { + ats[i].reset(); + } + + } + }, + + + $goOnlineDone : function(success){ + //this.reloading = true; + this.inProcess = this.IDLE; //We're done + this.setProperty("syncing", false); + + if (success) { + this.offlineTime = null; + this.initial = null; + this.storage.remove("offlinetime", this.namespace); + + + apf.console.info("Syncing done.") + apf.console.info("The application is now working online.") + + } + else { + + apf.console.info("Syncing was cancelled. Going online failed") + + + //Going online has failed. Going offline again + this.goOffline(); + } + }, + + /** + * Clears all offline data. + */ + clear : function(){ + if (!this.enabled) + return false; + + + apf.console.info("Clearing all offline and state cache"); + + + for (var i = this.resources.length - 1; i >= 0; i--) { + if (this[this.resources[i]].clear) + this[this.resources[i]].clear(); + } + }, + + /** + * Does cleanup after we've come online + * @private + */ + startSync : function(){ + if (this.syncing) + return; + + this.setProperty("syncing", true); + + + apf.console.info("Start syncing offline changes.") + + + var syncResources = [], + syncLength = 0, + syncPos = 0, + syncRes = null, + len, i; + + //Start finding all resources to sync + for (i = this.resources.length - 1; i >= 0; i--) { + if (this[this.resources[i]].sync) { + len = this[this.resources[i]].getSyncLength(); + + if (len) { + syncResources.push(this[this.resources[i]]); + syncLength += len; + } + } + } + + var fln = apf.offline; + var callback = function(extra){ + if (fln.inProcess == fln.STOPPING) { + if (!extra.finished && extra.length - 1 != extra.position) { + syncRes.stopSync(function(){ //@todo if(syncRes) ?? + fln.$goOnlineDone(false); + }); + } + else { + fln.$goOnlineDone(false); + } + + return; + } + + if (extra.finished) { + if (syncResources.length) { + syncRes = syncResources.pop(); + syncRes.sync(callback); + } + else { + //@todo check if we need to sync more.. + + fln.$goOnlineDone(true); + } + + return; + } + + if (!extra.start) + syncPos++; + + fln.setProperty("progress", parseInt(syncPos/syncLength*100)); + fln.setProperty("position", syncPos); + fln.setProperty("length", syncLength); + + fln.dispatchEvent("sync", apf.extend(extra, { + position : syncPos, + length : syncLength + })); + } + + if (syncLength) { + callback({start : true}); + callback({finished : true}); + } + else { + + apf.console.info("Nothing to synchronize.") + + + this.$goOnlineDone(true); + } + + + /* + When going online check $loadedWhenOffline of + the multiselect widgets and reload() them + */ + var nodes = apf.all; //@todo maintaining a list is more efficient, is it necesary?? + for (i = 0; i < nodes.length; i++) { + if (nodes[i].$loadedWhenOffline) + nodes[i].reload(); + } + + }, + + stopSync : function(){ + if (this.syncing) + this.inProcess = this.STOPPING; + } +}; + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/core/lib/offline/application.js)SIZE(11733)TIME(Mon, 10 Jan 2011 10:53:11 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + + +/** + * Object handling the offline state of the application resources. This includes + * the files that contain application logic themselve. In most cases the + * functionality of this object will be managed from within the offline + * element in AML. + * Example: + * + * + * + * + * @define offline + * @event beforeinstall Fires before installation of an offline provider + * cancelable: Cancels the installation of the offline provider + * @event afterinstall Fires after installation of an offline provider + * + * @attribute {String} [version-get] a datainstruction for getting a version number of the current application + * @attribute {String} [providers] a pipe seperated list of possible providers. + * Possible values: + * gears Uses the Google Gears plugin for storage of application files + * @attribute {Boolean} [auto-install] whether the required plugin is installed when it's not installed yet. + * + * @default_private + * @todo a later version should also clear models and thus undo state + */ +apf.offline.application = { + enabled : false, + urls : [], + providers : ["deskrun", "gears"], + + init : function(aml){ + if (this.enabled) + return; + + this.namespace = apf.config.name + ".apf.offline.application"; + + if (typeof aml == "string") { + this.providers = aml.split("|"); + } + else if (aml.nodeType) { + if (aml.getAttribute("version-get")) + this.application.versionGet = aml.getAttribute("version-get"); + + if (aml.getAttribute("providers")) + this.providers = aml.getAttribute("providers").split("|"); + + if (aml.getAttribute("auto-install")) + this.autoInstall = apf.isTrue(aml.getAttribute("auto-install")); + } + + //Check for an available offline provider + for (var i = 0; i < this.providers.length; i++) { + if (!this[this.providers[i]]) { + + apf.console.warn("Module not loaded for offline provider: " + + this.providers[i]); + + continue; + } + + if (this[this.providers[i]].isAvailable()) { + this.provider = this[this.providers[i]].init(this.storeName); + + if (this.provider !== false) { + this.provider.name = this.providers[i]; + break; + } + } + } + + //@todo if online please check if the latest version is loaded here + + if (!this.provider) { + if (this.autoInstall) { + if (this.install() === false) { + + apf.console.warn("Could not install any of the preferred \ + offline providers:" + + this.providers.join(", ")); + + + apf.offline.application = null; //Can't put the app offline + return this.providers[0]; + } + } + else { + + apf.console.warn("Could not find any of the specified \ + offline providers:" + + this.providers.join(", ")); + + + apf.offline.application = null; //Can't put the app offline + return this.providers[0]; + } + } + + if (!apf.loaded) { //@todo you might want to consider creating single run events + apf.addEventListener("load", function(){ + if (apf.offline.application.enabled) + apf.offline.application.save(); + apf.removeEventListener("load", arguments.callee); + }); + } + else { + apf.offline.addEventListener("load", function(){ + apf.offline.application.save(); + }); + } + + this.enabled = true; + + return this.provider.name; + }, + + install : function(){ + if (apf.offline.dispatchEvent("beforeinstall") === false) { + + apf.console.warn("Installation cancelled"); + + return false; + } + + for (var i = 0; i < this.providers.length; i++) { + if (!this[this.providers[i]]) + continue; + + if (this[this.providers[i]].install()) { + this.provider = this[this.providers[i]].init(this.storeName); + + if (this.provider !== false) + break; + } + } + + apf.offline.dispatchEvent("afterinstall"); + + if (!this.provider) + return false; + }, + + clear : function(){ + if (this.provider) + this.provider.clear(); + }, + + cache : function(url){ + //if(!new apf.url(url).isSameLocation()) + //return; + if (url.indexOf(":") > -1 && url.indexOf("http://" + location.host) == -1) + return; + + this.urls.pushUnique(url.replace(/\#.*$/, "")); + }, + + remove : function(url){ + this.urls.remove(url) + }, + + refresh : function(callback){ + var storage = apf.offline.storage; + + if(this.versionGet){ + var oldVersion = storage.get("oldVersion", this.namespace); + var newVersion = null; + var _self = this; + + apf.getData(this.versionGet, {callback: + function(newVersion, state, extra){ + if (state == apf.TIMEOUT) + return extra.tpModule.retryTimeout(extra, state, apf.offline); + + if (state == apf.OFFLINE) + return; + + if (state == apf.ERROR) + storage.remove("oldVersion", _self.namespace); + + if (apf.debug || !newVersion || !oldVersion + || oldVersion != newVersion){ + + + apf.console.info("Refreshing offline file list"); + + + + if (apf.offline.state.enabled) { + apf.offline.state.clear(); + + if (apf.offline.state.realtime) + apf.offline.state.search(); + } + + + _self.search(); + _self.provider.store(_self.urls, + callback, newVersion); + } + else{ + + apf.console.info("No need to refresh offline file list"); + + + callback({ + finished : true + }); + } + } + }); + } + else{ + + apf.console.info("Refreshing offline file list"); + + + this.search(); + this.provider.store(this.urls, callback); + } + }, + + //forEach??? + search : function(){ + //Html based sources + this.cache(window.location.href); + + var i, nodes = document.getElementsByTagName("script"); + for (i = 0; i < nodes.length; i++) + this.cache(nodes[i].getAttribute("src")); + + nodes = document.getElementsByTagName("link"); + for (i = 0; i < nodes.length; i++){ + if((nodes[i].getAttribute("rel") || "").toLowerCase() == "stylesheet") + continue; + + this.cache(nodes[i].getAttribute("href")); + } + + nodes = document.getElementsByTagName("img"); + for (i = 0; i < nodes.length; i++) + this.cache(nodes[i].getAttribute("src")); + + nodes = document.getElementsByTagName("a"); + for (i = 0; i < nodes.length; i++) + this.cache(nodes[i].getAttribute("href")); + + // @todo handle 'object' and 'embed' tag + + // parse our style sheets for inline URLs and imports + var _self = this, j, rule, sheet, sheets = document.styleSheets; + for (i = 0; i < sheets.length; i++) { + sheet = sheets[i]; + if (apf.isIE) { //@todo multibrowser test this + if (sheet.readOnly) { + sheet.cssText.replace(/url\(\s*([^\) ]*)\s*\)/gi, function(m, url){ + _self.cache(url); + return ""; + }); + } + } + else { + if (sheet.ownerNode.tagName == "STYLE") + continue; + + for (j = 0; j < sheet.cssRules.length; j++) { + rule = sheet.cssRules[j].cssText; + if(!rule) + continue; + + rule.replace(/url\(\s*([^\) ]*)\s*\)/gi, function(m, url){ + _self.cache(url); + return ""; + }); + } + } + } + + //Cache Skin CSS + apf.skins.loadedCss.replace(/url\(\s*([^\) ]*)\s*\)/gi, function(m, url){ + _self.cache(url); + return ""; + }); + + //Aml based sources + //@todo apf3.x this needs to be rewritten + /*if (apf.AmlParser.$aml) { + function callback(item){ + if(!item.nodeType) return; + + var nodes = item.selectNodes("//include/@src|//skin/@src"); + for (var i = 0; i < nodes.length; i++) { + _self.cache(nodes[i].nodeValue); + } + } + + callback(apf.AmlParser.$aml); + apf.includeStack.forEach(callback); + }*/ + + //Cached resources?? + }, + + save : function(callback){ + if (!apf.offline.onLine) { + var func = function(){ + apf.offline.application.save(); + apf.offline.removeEventListener("afteronline", func) + } + apf.offline.addEventListener("afteronline", func); + + return; + } + + this.refresh(callback); + } +}; + + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/core/lib/offline/gears.js)SIZE(4771)TIME(Mon, 10 Jan 2011 10:53:11 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + + +/** + * Offline provider that uses Google gears. + * @default_private + */ +apf.offline.application.gears = { + localServer : null, + lastStore : null, + cancelID : null, + refreshing : false, + fileIndex : 0, + + init : function(){ + + // clip at 64 characters, the max length of a resource store name + this.name = this.storeName.truncate(64); + this.storeName = apf.config.name + ".apf.offline"; + + try{ + this.localServer = apf.nameserver.get("google", "gears").create("beta.localserver", "1.0"); + } + catch(e){ + apf.console.warn("Error loading gears: " + e.message); + return false; + } + + return this; + }, + + install : function(){ + //@todo make a script to install gears here + + apf.isGears = true; + }, + + isAvailable : function(){ + return apf.isGears && location.protocol != "file:"; + }, + + clear : function(){ + this.localServer.removeStore(this.name); + }, + + store : function(listOfURLs, callback, newVersion){ + // refresh everything by simply removing + // any older stores + this.localServer.removeStore(this.name); + + // open/create the resource store + this.localServer.openStore(this.name); + + try { + var store = this.lastStore = this.localServer.createStore(this.name); + } + catch(e) { + + apf.console.warn("Gears failed to start local storage: " + e.message); + + + return false; + } + + // add our list of files to capture + var _self = this; + this.refreshing = true; + this.fileIndex = 0; + this.cancelID = store.capture(listOfURLs, + function(url, success, captureId){ + if (!success && _self.refreshing) { + _self.cancelID = null; + _self.refreshing = false; + + if (callback) { + callback({ + error : true, + message : "Unable to capture " + url + }); + } + + return; + } + else if (success) { + _self.fileIndex++; + + if (callback) { + callback({ + position : _self.fileIndex, + length : listOfURLS.length // @fixme: where is listOfURLS ??? + }); + } + } + + if (success && _self.fileIndex >= listOfURLs.length) { + _self.cancelID = null; + _self.refreshing = false; + + if(newVersion) + apf.storage.put("oldVersion", newVersion, null, + apf.offline.application.storeName); + + if (callback) { + callback({ + finished : true + }); + } + } + }); + }, + + abort: function(){ + // summary: + // For advanced usage; most developers can ignore this. + // Aborts and cancels a refresh. + if (!this.refreshing) + return; + + this.lastStore.abortCapture(this.cancelID); + this.refreshing = false; + } + +}; + + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/core/lib/offline/detector.js)SIZE(4827)TIME(Mon, 10 Jan 2011 10:53:11 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + + +/** + * Object detecting if the application has network, the detection moments can + * be manual, automatic or only when a communication error occurs. In most + * cases the functionality of this object will be managed from within the + * offline element in AML. + * Example: + * + * + * + * + * @define offline + * @attribute {String} [detect-url] a datainstruction for getting a version number of the current application + * @attribute {String} [detection] a pipe seperated list of possible providers. + * Possible values: + * auto Automatically detect whether the network is available by retrieving the file specified in the detect-url attribute + * manual Disable automatic or error based detection + * error Only detect network state when a communication timeout occurs. + * @attribute {Boolean} [interval] whether the required plugin is installed when it's not installed yet. + * + * @default_private + */ +apf.offline.detector = { + + detectUrl : apf.basePath + "core/lib/offline/network_check.txt", + + detection : "auto", //manual|auto|error + interval : 5000, + + init : function(aml){ + if (aml.nodeType) { + if (aml.getAttribute("detect-url")) + this.detectUrl = aml.getAttribute("detect-url"); + + else + this.detectUrl = (apf.config.resourcePath || apf.basePath) + + "resources/network_check.txt"; + + + this.detection = apf.isTrue(aml.getAttribute("detection")) + ? "auto" + : aml.getAttribute("detection") || "auto"; + + if (aml.getAttribute("interval")) + this.interval = parseInt(aml.getAttribute("interval")); + } + + if ("error|auto".indexOf(this.detection) > -1) { + apf.addEventListener("error", function(e){ + //Timeout detected.. Network is probably gone + if (e.state == apf.TIMEOUT) { + //Let's try to go offline and return false to cancel the error + return !apf.offline.goOffline();//callback //@todo callback??? + } + }); + } + + this.oHttp = new apf.http(); + this.oHttp.timeout = this.interval; + + //Check if we have connection right now + this.isSiteAvailable(); + + if (this.detection == "auto") + this.start(); + }, + + isSiteAvailable : function(callback){ + this.oHttp.get(apf.getNoCacheUrl(this.detectUrl), { + callback: function(data, state, extra){ + if(state != apf.SUCCESS || !window.navigator.onLine){ + apf.offline.goOffline(callback); //retry here?? + } + else{ + apf.offline.goOnline(callback); + } + }, + ignoreOffline : true, + hideLogMessage : true + }); + }, + + /** + * Start automatic network availability detection + */ + start : function(){ + clearInterval(this.timer); + + + apf.console.info("Automatic detection of network state is activated"); + + + var _self = this; + this.timer = setInterval(function(){ + _self.isSiteAvailable(); + }, this.interval); + }, + + /** + * Stop automatic network availability detection + */ + stop : function(){ + clearInterval(this.timer); + + + apf.console.info("Detection of network state is deactivated"); + + } +}; + + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/core/lib/offline/models.js)SIZE(5471)TIME(Mon, 10 Jan 2011 10:53:11 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + + +/** + * Object dealing with the storing the state of models for use offline. In + * most cases the functionality of this object will be managed from within the + * offline element in AML. + * Example: + * + * + * + * + * @define offline + * @attribute {Boolean} [realtime] whether changes are stored realtime. + * + * @default_private + */ +apf.offline.models = { + enabled : false, + timer : null, + models : {}, + initQueue : [], + realtime : true, + + init : function(aml){ + this.namespace = apf.config.name + ".apf.offline.models"; + + if (aml.nodeType && aml.getAttribute("realtime")) + this.realtime = !apf.isFalse(aml.getAttribute("realtime")); + + if (!this.realtime) { + apf.addEventListener("exit", function(){ + apf.offline.models.search(); + }); + } + + //@todo what to do if we're not realtime + + this.enabled = true; + }, + + markForUpdate : function(model){ + this.models[model.$uniqueId] = model; + + if(!this.timer){ + var _self = this; + this.timer = $setTimeout(function(){ + _self.timer = null; + var models = _self.models; + + for (var mId in models) { + _self.updateModel(models[mId]); + } + + _self.models = {}; + }, 2000); + } + }, + + clear : function(){ + apf.offline.storage.clear(this.namespace); + }, + + removeModel : function(model){ + var name = model.name || model.$uniqueId + ".model"; + + //Remove recorded data of this model + apf.offline.storage.remove(name, this.namespace); + + //Remove the model from the init queue + this.initQueue.remove(model); + }, + + updateModel : function(model){ + var name = model.name || model.$uniqueId + ".model"; + + + apf.console.info("Updating model '" + name + "'"); + + + /* + This could be optimized by only recording the changes to the + data. At load/exit these could be purged. + */ + + var docId = model.data.getAttribute(apf.xmldb.xmlDocTag); + model.data.setAttribute(apf.xmldb.xmlDocTag + "_length", + apf.xmldb.nodeCount[docId]); + + apf.offline.storage.put(name, model.data.xml || model.data.serialize(), this.namespace); + }, + + loadModel : function(model){ + var name = model.name || model.$uniqueId + ".model"; + + var data = apf.offline.storage.get(name, this.namespace); + if (!data) return false; + + + apf.console.info("Loading model '" + name + "' from local storage"); + + + var xmlNode = apf.getXmlDom(data).documentElement; + var docId = xmlNode.getAttribute(apf.xmldb.xmlDocTag); + apf.xmldb.nodeCount[docId] + = parseInt(xmlNode.getAttribute(apf.xmldb.xmlDocTag + "_length")); + + model.load(xmlNode); + return true; + }, + + search : function(){ + //Save all the models + + var done = {}, models = apf.nameserver.getAll("model"); + for (var i = 0; i < models.length; i++) { + if (done[models[i].$uniqueId]) + continue; + + done[models[i].$uniqueId] = true; + this.updateModel(models[i]); + } + + + return true; + }, + + addToInitQueue : function(model){ + this.initQueue.pushUnique(model); + model.session = false; + }, + + stopSync : function(callback){ + //No stopping here.. the queue will fill itself automatically + callback(); + }, + + getSyncLength : function(){ + return this.initQueue.length; + }, + + sync : function(callback){ + //We assume we're online now, but just in case we clear the queue + var queue = this.initQueue.slice(); + this.initQueue.length = 0; + + var qNr = 0, len = queue.length; + for (var i = 0; i < queue.length; i++) { + queue[i].init(function(){ + callback({ + position : ++qNr, + length : len + }); + + if(qNr == len - 1) + callback({finished: true}); + }); + } + } +}; + + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/core/lib/offline/queue.js)SIZE(7008)TIME(Mon, 10 Jan 2011 10:53:11 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + + +/** + * Object handling queuing of actions that can only be executed whilst online. + * These actions are stored in the queue and executed in serie when the + * application comes online again. This is done after apf.auth has logged the + * user into the application again, if necesary. This object is used for HTTP + * XMPP and Webdav, but is general purpose and can be used to store any + * action that should only be executed while online. + * + * @default_private + */ +apf.offline.queue = { + enabled : false, + stack : [], + + init : function(){ + this.namespace = apf.config.name + ".apf.offline.queue"; + this.enabled = true; + }, + + add : function(commInfo){ + var namespace = this.namespace; + var storage = apf.offline.storage; + var len = parseInt(storage.get("length", namespace)) || 0; + + //Add the commInfo to the stack + this.stack[len] = commInfo; //retry this on sync + + //Check here for xml nodes in storeInfo?? + + //Store http info + storage.put(len, apf.serialize(commInfo), namespace); + storage.put("length", ++len, namespace); + + /* + If there's a callback, we'll inform it that we're not + executing the call because we're offline. + */ + var callback = commInfo.callback; + if (!commInfo.undoObj && callback) { + + var strWarn = "The application is currently offline. Your \ + request will be retried when the application \ + goes online again. Please be aware that when the\ + request is finally made, this callback might\ + not be available anymore. Therefore the state of\ + the data should already represent the state of\ + the application that a succesfull execution of\ + the request communicates. You might want to look\ + at using an actiontracker."; + + apf.console.warn(strWarn); + + + callback(null, apf.OFFLINE, apf.extend({ + offline : true + + , message : strWarn + + }, commInfo)); + } + }, + + stopSync : function(callback){ + this.stop = callback; + }, + + getSyncLength : function(){ + return parseInt(apf.offline.storage.get("length", this.namespace)) || 0; + }, + + //Sync all transactions, let offline decide when + sync : function(callback, isStarted){ + if (this.stop) { + this.stop(); + this.stop = null; + return + } + + var namespace = this.namespace; + var storage = apf.offline.storage; + var len = parseInt(storage.get("length", namespace)) || 0; + var start = parseInt(storage.get("start", namespace)) || 0; + var commInfo; + + if (this.stack[start]) { + commInfo = this.stack[start]; + } + else { + commInfo = this.$getCommInfo(storage.get(start, namespace)); + if (!commInfo) { + + apf.console.error("Error syncing queue items. This is a serious\ + error. The queue stack has become corrupted. It will now be \ + cleared and the queued offline messages will be lost!"); //@todo + + + this.clear(); + apf.offline.stopSync(); + + return callback({finished: true}); + } + + this.stack[start] = commInfo; + } + + if (!commInfo.callback2) { + commInfo.callback2 = commInfo.callback; + + commInfo.callback = function(data, state, extra){ + //We'll let the main callback decide if this one should be retries + if (commInfo.callback2 && + commInfo.callback2.apply(window, arguments) === true) { + //@todo: Warning here?? + + return true; + } + + // We're done with this one + storage.remove(start, namespace); + storage.put("start", start+1, namespace); + apf.offline.queue.stack[start] = null; + + callback({ + position : start, + length : len, + info : commInfo + }); + + if (start == len - 1) { + //Sync is completely done + storage.clear(namespace); + + callback({ + finished : true + }); + } + else { + //Next! + apf.offline.queue.sync(callback, true); + } + } + } + + this.stack[start].retry(); + }, + + clear : function(){ + apf.offline.storage.clear(this.namespace); + }, + + $getCommInfo : function(strCommItem){ + if (!strCommItem) + return false; + + var commObject, commInfo = apf.unserialize(strCommItem); + for (var i = 0; i < commInfo.$object.length; i++) { + commObject = self[commInfo.$object[i]] || eval(commInfo.$object[i]); + if (commObject) + break; + } + + + if (!commObject) { + //@todo + } + + + commInfo.object = commObject; + commInfo.retry = new Function(commInfo.$retry); + + return commInfo; + } +}; + + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/core/lib/offline/state.js)SIZE(7978)TIME(Mon, 10 Jan 2011 10:53:11 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + + +/** + * Object recording the state of all elements. If the realtime attribute is + * set the state of the elements is recorded realtime. Otherwise it is + * recorded only when the application exits. During startup the state of the + * application can be restored by cancelling the 'restore' event. In most cases + * the functionality of this object will be managed from within the offline + * element in AML. + * Example: + * + * + * + * + * @event restore Fires before restoring the application to the predefined state. + * cancelable: Loads the stored state into the applicaton. + * + * @define offline + * @attribute {String} [set] a datainstruction that stores the state of the application to an external data store. + * + * @default_private + * @todo optimize by not getting the default values from the aml + */ +apf.offline.state = { + enabled : false, + states : {}, + realtime : true, + lookup : {}, + + init : function(aml){ + this.namespace = apf.config.name + ".apf.offline.state"; + + if (aml.nodeType) { + if (aml.getAttribute("realtime")) + this.realtime = !apf.isFalse(aml.getAttribute("realtime")); + + if (aml.getAttribute("set")) + this.setInstruction = aml.getAttribute("set"); + } + + apf.addEventListener("exit", function(){ + if (!apf.offline.state.realtime) { + //apf.offline.state.search(); + var lookup = apf.offline.state.lookup; + var storage = apf.offline.storage; + var ns = apf.offline.state.namespace; + + for (var key in lookup) { + var ns = apf.offline.state.namespace; + storage.put(key, lookup[key], ns); + } + } + + if (apf.offline.state.setInstruction) + apf.offline.state.send(); + }); + + + var registry = apf.extend({}, apf.offline.storage || apf.storage); + registry.namespace = apf.config.name + ".apf.registry"; + apf.registry.$export(registry); + apf.registry = registry; + + + //@todo This could be optimized if needed + if (apf.offline.storage.getAllPairs(this.namespace, this.lookup)) { + /* + This is the moment the developer should do something like: + return confirm("Would you like to continue your previous session?"); + */ + if (apf.offline.dispatchEvent("restore") === false) { + this.clear(); + this.lookup = {}; + + + apf.offline.transactions.clear("undo|redo"); + + } + } + + + + apf.offline.transactions.doStateSync = true; + + + this.enabled = true; + }, + + warned : false, + timeout : {}, + set : function(obj, key, value){ + + if (!obj.name && !this.warned) { + this.warned = true; + apf.console.warn("Components found without name. This means that \ + when the application changes the state \ + serialization can break."); + } + + + if (!obj.tagName) + return; + + var name = obj.name || obj.$uniqueId + "_" + obj.tagName; + var storage = apf.offline.storage; + + + if (!name || !storage.isValidKey(name)) { //@todo + throw new Error("invalid") + } + + if (!storage.isValidKey(key)) { //@todo + throw new Error("invalid") + } + + + /* + Using a timeout here, is an optimization for fast changing + properties such as slider values. + */ + key = name + "." + key; + this.lookup[key] = value; + + if (!this.realtime) + return; + + var ns = this.namespace; + clearTimeout(this.timeout[key]); + this.timeout[key] = $setTimeout(function(){ + storage.put(key, value, ns); + }, 200); + }, + + get : function(obj, key, value){ + return this.lookup[(obj.name || obj.$uniqueId + "_" + obj.tagName) + "." + key]; + + /*return apf.offline.storage.get( + (obj.name || obj.$uniqueId + "." + obj.tagName) + "." + key, + this.namespace);*/ + }, + + //blrgh.. unoptimized + getAll : function(obj) { + var prop, res = {}, x, + name = obj.name || obj.$uniqueId + "_" + obj.tagName; + for (prop in this.lookup) { + x = prop.split("."); + if (x[0] == name) + res[x[1]] = this.lookup[prop]; + } + + return res; + }, + + clear : function(){ + apf.offline.storage.clear(this.namespace); + + var ns = apf.registry.getNamespaces(); + for (var i = 0; i < ns.length; i++) { + apf.registry.clear(ns[i]); + } + + + apf.offline.transactions.clear("undo|redo"); + + }, + + search : function(){ + var storage = apf.offline.storage; + + //Search for dynamic properties + var props, i, j, nodes = apf.all; + for (i = 0; i < nodes.length; i++) { + if (nodes[i].name && nodes[i].getAvailableProperties) { + props = nodes[i].getAvailableProperties(); + for (j = 0; j < props.length; j++) { + if (nodes[i][props[j]]) + this.set(nodes[i], props[j], nodes[i][props[j]]); + } + } + } + + //@todo Search for actiontracker stacks + + //@todo Search for selection states + }, + + send : function(){ + var storage = apf.offline.storage; + + var data = {}; + var keys = storage.getKeys(this.namespace); + + for (var i = 0; i < keys.length; i++) { + data[keys[i]] = storage.get(keys[i], this.namespace); + } + + apf.saveData(this.setInstruction, { + ignoreOffline : true, + data : apf.serialize(data), + callback : function(data, state, extra){ + if (extra.tpModule.retryTimeout(extra, state, apf.offline) === true) + return true; + } + }); + } +}; + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/core/lib/offline/transactions.js)SIZE(9780)TIME(Mon, 10 Jan 2011 10:53:11 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + + +/** + * Object recording the state of actiontrackers. When an application goes + * offline, the state is maintained such that it can be synced at a later date. + * The actiontracker state can be maintained even when the application restarts. + * In most cases the functionality of this object will be managed from within + * the offline element in AML. + * Example: + * + * + * + * + * @define offline + * @event transactioncancel Fires before installation of an offline provider + * enableable Cancels the installation of the offline provider + * + * @default_private + * @todo remove serialize here + */ +apf.offline.transactions = { + enabled : false, + + init : function(){ + this.namespace = apf.config.name + ".apf.offline.transactions"; + this.enabled = true; + + + apf.addEventListener("load", function(){ + apf.offline.transactions.rebuildActionQueues(); + apf.removeEventListener("load", arguments.callee); + }); + + }, + + /* + * data GET requests aren't synced but disallowed, + * such as load/insert bindings and model load="" + * This function will sent the ontransactioncancel event which + * can be used to notify the user that we're offline. + */ + actionNotAllowed : function(){ + apf.offline.dispatchEvent("transactioncancel", { + message : "Transaction is not allowed", + bubbles : true + }); + + return; + }, + + //@todo you might want to error on dotts in the at name + addAction : function(at, qItem, type){ + + if (!at.name || !apf.storage.base.isValidKey(at.name)) { + //@todo + throw new Error("Invalid or missing name for actiontracker \ + used for offline transactions '" + at.name + "'."); + } + + + var namespace = this.namespace + "." + at.name + "." + type; + var storage = apf.offline.storage; + var len = parseInt(storage.get("length", namespace)) || 0; + + storage.put(len, apf.serialize(type == "queue" + ? { + undo : qItem.undo, + undoObj : qItem.undoObj.$export() + } + : qItem.$export()), namespace); + storage.put("length", ++len, namespace); + }, + + removeAction : function(at, fromTop, type){ + var namespace = this.namespace + "." + at.name + "." + type; + var storage = apf.offline.storage; + + //@todo add checks for stack sanity + if (fromTop) { + var len = parseInt(storage.get("length", namespace)) - 1; + var start = parseInt(storage.get("start", namespace)) || 0; + + + if (len < 0) {//@todo + throw new Error("something went terribly wrong"); + } + + + if (start == len || len < 0) { + storage.clear(namespace); + return; + } + + storage.remove(len, namespace); + storage.put("length", len, namespace); + } + else { + var start = parseInt(storage.get("start", namespace)) || 0; + var len = parseInt(storage.get("length", namespace)) || 0; + + if (start + 1 == len) { + storage.clear(namespace); + return; + } + + storage.remove(start, namespace) + storage.put("start", ++start, namespace); + } + }, + + rebuildActionQueues : function(){ + var storage = apf.offline.storage; + var namespaces = storage.getNamespaces(); + if (!namespaces) return; + var lookup, re = new RegExp(this.namespace + "\\.([^\\.]*)\\.([^\\.]*)"); + + for (var ats = [], i = 0; i < namespaces.length; i++) { + if (namespaces[i].match(re)) + ats.push([RegExp.$1, RegExp.$2]); + } + + var i, j, qItem, stack, namespace, at, start, len, type; + + for (i = 0; i < ats.length; i++) { + at = apf.nameserver.get("actiontracker", ats[i][0]); + type = ats[i][1]; + + + if (!at) { //@todo + throw new Error(apf.formatErrorString(0, null, + "Rebuilding Action Queue", + "An actiontracker could not be found by the name of '" + + ats[i][0] + "'")); + } + + + lookup = {}; + namespace = this.namespace + "." + at.name + "." + type; + storage.getAllPairs(namespace, lookup); + + start = parseInt(lookup["start"]) || 0; + len = parseInt(lookup["length"]) || 0; + stack = []; + + + apf.console.info("Restoring " + type + " stack for " + + (at.name == "default" + ? "the default actiontracker" + : "the '" + at.name + "' actiontracker") + + " of " + len + " items."); + + + if (type == "queue") { + for (j = len - 1; j >= start; j--) { + qItem = apf.unserialize(lookup[j]); + qItem.undoObj = new apf.UndoData(qItem.undoObj, at).$import(); + stack.unshift(qItem); + } + } + else { + for (j = len - 1; j >= start; j--) { + qItem = apf.unserialize(lookup[j]); + stack.unshift(new apf.UndoData(qItem, at).$import()); + } + } + + at.$loadQueue(stack, type); + + apf.offline.sLookup = null; + } + + }, + + clearActions : function(at, type){ + apf.offline.storage.clear(this.namespace + "." + at.name + "." + type); + }, + + clear : function(queues){ + if (!queues) + queues = "undo|redo|queue"; + + var storage = apf.offline.storage; + var namespaces = storage.getNamespaces(); + var re = new RegExp(this.namespace + "\\.([^\\.]*)\\.(" + queues + ")"); + + for (var i = 0; i < namespaces.length; i++) { + if (namespaces[i].match(re)) + storage.clear(namespaces[i]); + } + }, + + stopSync : function(callback){ + //No stopping here.. the queue will fill itself automatically + callback(); + }, + + getSyncLength : function(){ + + var ats = apf.nameserver.getAll("actiontracker"); + + var len = 0; + for (var i = 0; i < ats.length; i++) + len += ats[i].$getQueueLength(); + + return len; + + }, + + sync : function(callback){ + + var ats = apf.nameserver.getAll("actiontracker"); + + var qNr = 0, len = 0; + for (var i = 0; i < ats.length; i++) { + if (ats[i].$getQueueLength()) { + len += ats[i].$getQueueLength(); + ats[i].$startQueue(function(last){ + if (qNr >= len - 1) + return false; //silently ignore later changes... (might be wrong) + + if (last) + qNr = len; + + callback({ + position : ++qNr, + length : len + }); + + if(qNr >= len - 1) + callback({finished: true}); + }); + } + } + + } +}; + +/** + * Determines whether it's possible to start a new action. + * @private + * @method + */ +apf.offline.canTransact = function(){ + if(!apf.offline.enabled || this.onLine || this.transactions.enabled) + return true; + + //Transactions can be enabled from this event + if(this.dispatchEvent("transactioncancel", { + message : "Could not execute transaction whilst being offline,\ + silently doing nothing", + bubbles : true + }) === true) + return true; + + return false; +}; + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/core/lib/data.js)SIZE(16420)TIME(Mon, 10 Jan 2011 10:53:11 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + + +/** + * @term datainstruction Data instructions offer a single and consistent way for + * storing and retrieving + * data from different data sources. For instance from a webserver using REST + * or RPC, or from local data sources such as gears, air, o3, html5, as well as + * from in memory sources from javascript or cookies. There is often an xml + * element which is relevant to storing information. This element can be + * accessed using xpath statements in the data instruction using curly braces. + * + * - for complex model expr. replace model. + * - use property binding for selection, instead of setConnections + * + * + * + * + * + * + * + * - create exec function for async objects + * - have list of async objects + * + * Syntax: + * Using data instructions to retrieve data + * + * model="name_of_model" + * model="[name_of_model::xpath]" + * model="{element.selected}" + * model="[local(element.selected) {xpath}]" + * model="{element.choose}" + * model="[local(element.choose) {xpath}]" + * model="[local(element.root) {xpath}]" + * load="[comm.doCall([@id], test(), 5+10).xml]" + * get="example.jsp" + * get="http://www.bla.nl?blah=10&foo=[@bar]&example=[10+5]" + * get="{comm.submit('abc', [@bar])}" + * get="[local(comm.submit('abc', [@bar])) return [xpath]]" + * get="[submit('abc', [@bar])]" + * get="{xmpp.login(username, password)}" + * get="{webdav.getRoot()}" + * get="[10+5]" + * + * + * Syntax: + * Using data instructions to store data + * + * set="http://www.bla.nl?blah=10&foo={/bar}&example=[10+5]" + * set="post http://www.bla.nl?blah=10&foo={/bar}&example=[10+5]" + * + * set="[submit('abc', {/bar})]" + * set="[example=[@name]]" + * set="[apf.setcookie('something', [.])]" + * set="[o3.fs.get('/var/test.xml').data = [.]]" + * + * + * [ + * function test(){ + * var blah = comm.blah(); + * return blah; + * } + * ] + * + * + * See: + *
    + *
  • {@link teleport.cgi the cgi teleport module}
  • + *
  • {@link teleport.rpc the rpc teleport module}
  • + *
  • {@link teleport.webdav the webdav teleport module}
  • + *
  • {@link teleport.xmpp the xmpp teleport module}
  • + *
+ */ + +/** + * Stores data using a {@link term.datainstruction data instruction}. + * + * @param {String} instruction the {@link term.datainstruction data instruction} to be used to store the data. + * @param {Object} [options] the options for this instruction + * Properties: + * {Boolean} multicall whether this call should not be executed immediately but saved for later sending using the purge() command. + * {mixed} userdata any data that is useful to access in the callback function. + * {Array} args the arguments of the call, overriding any specified in the data instruction. + * {XMLElement} [xmlContext] the subject of the xpath queries + * {Function} [callback] the code that is executed when the call returns, either successfully or not. + */ +apf.saveData = + +/** + * Retrieves data using a {@link term.datainstruction data instruction}. + * Example: + * Several uses for a data instruction + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * @param {String} instruction the {@link term.datainstruction data instruction} to be used to retrieve the data. + * @param {XMLElement} [xmlContext] the subject of the xpath queries + * @param {Object} [options] the options for this instruction + * Properties: + * {Boolean} multicall whether this call should not be executed immediately but saved for later sending using the purge() command. + * {mixed} userdata any data that is useful to access in the callback function. + * {mixed} data data to use in the call + * {Array} args the arguments of the call, overriding any specified in the data instruction. + * @param {Function} [callback] the code that is executed when the call returns, either successfully or not. + */ +apf.getData = function(instruction, options){ + if (!instruction) return false; + + //Instruction type detection + var result, chr = instruction.charAt(0), callback = options.callback; + + + var gCallback = function(data, state, extra){ + var _self = this; + $setTimeout(function(){ + s2.call(_self, data, state, extra); + }); + } + + var s2 = + + + function(data, state, extra){ + var callback = options.callback + + if (state != apf.SUCCESS) + return callback(data, state, extra || {}); + + //Change this to warning? + /*if (!data) { + throw new Error(apf.formatErrorString(0, null, + "Loading new data", "Could not load data. \n\ + Data instruction: '" + instruction + "'")); + }*/ + + return callback(data, state, extra || {}); + } + + if (!options) options = {}; //@todo optimize? + var fParsed = options.fParsed || (instruction.indexOf("{") > -1 || instruction.indexOf("[") > -1 + ? apf.lm.compile(instruction, { + withopt : true, + precall : options.precall, + alwayscb : true, + simplexpath : true + }) + : {str: instruction, type: 2}); + + if (options.precall && !options._pc) { + options.asyncs = fParsed.asyncs; + options._pc = 1; + } + + //@todo hack because we're not using compileNode.. an imperfection.. + if (fParsed.type == 3){ + if (fParsed.xpaths[0]) { + var model = fParsed.xpaths[0], xpath = fParsed.xpaths[1]; + + //@todo can this be async? + if (model == "#" || xpath == "#") { //When there is a set model and not a generated xpath + var m = (apf.lm.compile(instruction, { + xpathmode: 5 + }))(); + + //@todo apf3 this needs to be fixed in live markup + if (typeof m != "string") { + model = m.model && m.model.$isModel && m.model; + if (model) + xpath = m.xpath; + else if (m.model) { + model = apf.xmldb.findModel(m.model); + xpath = apf.xmlToXpath(m.model, model.data) + (m.xpath ? "/" + m.xpath : ""); //@todo make this better + } + else { + //Model is not yet available. When it comes available we will be recalled (at least for prop binds) + return; + } + } + else model = null; + } + else { + + model = apf.nameserver.get("model", model) + + } + + + if (!model) { + throw new Error("Could not find model '" + model + "' in " + instruction); //@todo apf3.0 make proper error + } + + + return gCallback(model.data.selectSingleNode(xpath), apf.SUCCESS, {}); + } + else { + + if (!options.xmlNode) { + return apf.console.error(apf.formatErrorString(0, null, + "Loading data", + "Xpath found without model and no xmlNode specified" + + instruction)); + } + + + return gCallback(options.xmlNode.data.selectSingleNode(fParsed.xpaths[1]), apf.SUCCESS, {}); + } + } + + //xml generation + if (chr == "<") { + //string only + if (fParsed.type == 2) + result = fParsed.str; + else + return fParsed(options.xmlNode, gCallback, options); + } + //jslt fetching data + else if ((chr == "[" || chr == "{")) { //(fParsed.asyncs || fParsed.models) && + return fParsed(options.xmlNode, gCallback, options); + } + //url + else { + if (fParsed.type == 1 || fParsed.type == 3) { + var callback2 = callback; + callback = options.callback = function(data, state, extra){ + if (options._pc === true) + return; + + if (state != apf.SUCCESS) + return callback2.apply(this, arguments); + + var url = data.split(" "), method = "get"; + if (url.length > 1 && url[0].length < 10) { + method = url.shift(); + url = url.join(" "); + } + else url = data; + + callback = options.callback = callback2; + apf.oHttp.exec(method, [url], gCallback, options); + } + fParsed(options.xmlNode, gCallback, options); + } + else { + if (options._pc === true) + return; + + var url = instruction.split(" "), method = "get"; + if (url.length > 1 && url[0].length < 10) { + method = url.shift(); + url = url.join(" "); + } + else { + url = instruction; + } + + apf.oHttp.exec(method, [url.replace(/\\/g, "")], gCallback, options); + } + } + + if (result) { + if (callback) + gCallback(result, apf.SUCCESS, {}); + else { + //apf.console.warn("Returning data directly in apf.getData(). \ + //This means that all callback communication ends in void!"); + return result; + } + } +}; + + +/** + * Creates a model object based on a {@link term.datainstruction data instruction}. + * + * @param {String} instruction the {@link term.datainstruction data instruction} to be used to retrieve the data for the model. + * @param {AmlNode} amlNode the element the model is added to. + */ +apf.setModel = function(instruction, amlNode){ + if (!instruction) return; + + //Find existing model + var fParsed = instruction.indexOf("{") > -1 || instruction.indexOf("[") > -1 + ? apf.lm.compile(instruction, { + //precall : false, + alwayscb : true + }) + : { + type: 2, + str : instruction + }; + + if (instruction == "@default" || fParsed.type == 2) { + + var model = apf.nameserver.get("model", instruction); + if (model) + return model.register(amlNode); + else + + if (instruction == "@default") + return; + + //@todo apf3.0 check here if string is valid url (relative or absolute) + if (instruction.indexOf(".") == -1 && instruction.indexOf("/") == -1) { + + apf.console.warn("Could not find model '" + instruction + "'"); + + return; + } + } + + //Just an xpath doesnt work. We don't have context + //var l, x; + if (fParsed.type == 3) {//This won't work for complex xpaths + if (fParsed.models) { //check for # in xpaths[i] to determine if its calculated + if (fParsed.xpaths.length == 2 && fParsed.xpaths[0] != '#' && fParsed.xpaths [1] != '#') { + + + if (!apf.nameserver.get("model", fParsed.xpaths[0])) { + throw new Error("Could not find model '" + fParsed.xpaths[0] + "' in " + instruction); //@todo apf3.0 make proper error + } + + + apf.nameserver.get("model", fParsed.xpaths[0]).register(amlNode, fParsed.xpaths[1]); + + return; + } + } + + else { + //throw new Error(apf.formatErrorString(0, amlNode, + apf.console.warn("Xpath found without model. This might fail if no\ + context is specified using local(): " + instruction); + } + + } + + if (amlNode.clear) + amlNode.clear("loading"); + + //Complex data fetch (possibly async) - data is loaded only once. + //Potential property binding has to take of the rest + apf.getData(instruction, { + parsed : fParsed, + xmlNode : amlNode && amlNode.xmlRoot, + callback : function(data, state, extra){ + //@todo apf3.0 call onerror on amlNode + if (state != apf.SUCCESS) { + throw new Error(apf.formatErrorString(0, null, + "Loading new data", "Could not load data into model. \ + \nMessage: " + extra.message + "\ + \nInstruction: '" + instruction + "'")); + } + + if (!data) + return amlNode.clear && amlNode.clear(); + + if (typeof data == "string") { + if (data.charAt(0) == "<") + data = apf.getXml(data); + else { + //Assuming web service returned url + if (data.indexOf("http://") == 0) + return apf.setModel(data, amlNode); + else { + throw new Error("Invalid data from server");//@todo apf3.0 make proper apf error handling. apf.onerror + } + } + } + + if (data.nodeFunc) { //Assuming a model was passed -- data.localName == "model" && + data.register(amlNode); + return; + } + + var model = apf.xmldb.findModel(data); //See if data is already loaded into a model + if (model) + model.register(amlNode, apf.xmlToXpath(data, model.data)); //@todo move function to xml library + else + new apf.model().register(amlNode).load(data); + }}); +}; + + + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/core/lib/date.js)SIZE(31241)TIME(Mon, 10 Jan 2011 10:53:11 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + +/** + * @term dateformat Format a date based on small strings of characters representing + * a variable. + * Syntax: + * + * d day of the month as digits, no leading zero for single-digit days + * dd day of the month as digits, leading zero for single-digit days + * ddd day of the week as a three-letter abbreviation + * dddd day of the week as its full name + * m month as digits, no leading zero for single-digit months + * mm month as digits, leading zero for single-digit months + * mmm month as a three-letter abbreviation + * mmmm month as its full name + * yy year as last two digits, leading zero for years less than 2010 + * yyyy year represented by four digits + * h hours, no leading zero for single-digit hours (12-hour clock) + * hh hours, leading zero for single-digit hours (12-hour clock) + * H hours, no leading zero for single-digit hours (24-hour clock) + * HH hours, leading zero for single-digit hours (24-hour clock) + * M minutes, no leading zero for single-digit minutes + * MM minutes, leading zero for single-digit minutes + * s seconds, no leading zero for single-digit seconds + * ss seconds, leading zero for single-digit seconds + * + */ + + +// Some common format strings +/** + * @private + */ +apf.date = (function() { + +return { + masks : { + "default": "ddd mmm dd yyyy HH:MM:ss", + shortDate: "m/d/yy", + mediumDate: "mmm d, yyyy", + longDate: "mmmm d, yyyy", + fullDate: "dddd, mmmm d, yyyy", + shortTime: "h:MM TT", + mediumTime: "h:MM:ss TT", + longTime: "h:MM:ss TT Z", + isoDate: "yyyy-mm-dd", + isoTime: "HH:MM:ss", + isoDateTime: "yyyy-mm-dd'T'HH:MM:ss", + isoUtcDateTime: "UTC:yyyy-mm-dd'T'HH:MM:ss'Z'" + }, + + // Internationalization strings + i18n : { + /** + * Defines what day starts the week + * + * Monday (1) is the international standard. + * Redefine this to 0 if you want weeks to begin on Sunday. + */ + beginWeekday : 1, + dayNames : [ + "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", + "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", + "Friday", "Saturday" + ], + + dayNumbers : { + "Sun" : 0, "Mon" : 1, "Tue" : 2, "Wed" : 3, "Thu" : 4, "Fri" : 5, + "Sat" : 6, "Sunday" : 0, "Monday" : 1, "Tuesday" : 2, + "Wednesday" : 3, "Thursday" : 4, "Friday" : 5, "Saturday" : 6 + }, + monthNames : [ + "Jan", "Feb", "Mar", "Apr", "May", "Jun", + "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", + "January", "February", "March", "April", "May", "June", + "July", "August", "September", "October", "November", "December" + ], + monthNumbers : { + "Jan" : 0, "Feb" : 1, "Mar" : 2, "Apr" : 3, "May" : 4, "Jun" : 5, + "Jul" : 6, "Aug" : 7, "Sep" : 8, "Oct" : 9, "Nov" : 10, "Dec" : 11 + } + } + + , + + span: function(seconds) { + if (seconds < 0) return; + + var sec = parseInt(seconds), + min = Math.floor(sec / 60), + hour = Math.floor(min / 60), + day = parseInt(Math.floor(hour / 24)); + this.seconds = sec % 60; + this.minutes = min % 60; + this.hours = hour % 24; + this.days = day; + }, + + dateToDays: function(day, month, year) { + var y = year + ""; + var century = parseInt(y.substr(0, 2)); + year = parseInt(y.substr(2, 2), 10); //do base 10, because of weird JS issue + if (month > 2) { + month -= 3; + } + else { + month += 9; + if (year) { + year--; + } + else { + year = 99; + century--; + } + } + + return (Math.floor((146097 * century) / 4 ) + + Math.floor((1461 * year) / 4 ) + + Math.floor((153 * month + 2) / 5 ) + + day + 1721119); + }, + + daysToDate: function(days) { + days -= 1721119; + var century = Math.floor((4 * days - 1) / 146097); + days = Math.floor(4 * days - 1 - 146097 * century); + var day = Math.floor(days / 4); + + var year = Math.floor((4 * day + 3) / 1461); + day = Math.floor(4 * day + 3 - 1461 * year); + day = Math.floor((day + 4) / 4); + + var month = Math.floor((5 * day - 3) / 153); + day = Math.floor(5 * day - 3 - 153 * month); + day = Math.floor((day + 5) / 5); + + if (month < 10) { + month += 3; + } + else { + month -= 9; + if (year++ == 99) { + year = 0; + century++; + } + } + + return { + day : day + "", + month: month + "", + year : (century + "").pad(2, "0") + (year + "").pad(2, "0") + }; + }, + + nextDay: function(day, month, year) { + var now = new Date(); + return this.daysToDate(this.dateToDays(day || now.getDate(), month + || now.getMonth(), year || now.getFullYear()) + 1); + }, + + prevDay: function(day, month, year) { + var now = new Date(); + return this.daysToDate(this.dateToDays(day || now.getDate(), month + || now.getMonth(), year || now.getFullYear()) - 1); + }, + + isLeapYear: function(year) { + if (typeof year != "number") + return false; + if (year < 1582) // pre Gregorio XIII - 1582 + return (year % 4 == 0); + else // post Gregorio XIII - 1582 + return ((year % 4 == 0) && (year % 100 != 0)) || (year % 400 == 0); + }, + + julianDate: function(day, month, year) { + var now = new Date(); + if (!month) + month = now.getMonth(); + + var days = [0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334], + julian = (days[month - 1] + day || now.getDate()); + if (month > 2 && this.isLeapYear(year || now.getFullYear())) + julian++; + return julian; + }, + + compare: function(d1, d2) { + var days1 = this.dateToDays(d1.getUTCDate(), d1.getUTCMonth(), d1.getUTCYear()), + days2 = this.dateToDays(d2.getUTCDate(), d2.getUTCMonth(), d2.getUTCYear()); + if (days1 < days2) + return -1; + if (days1 > days2) + return 1; + if (d1.getUTCHour() < d2.getUTCHour()) + return -1; + if (d1.getUTCHour() > d2.getUTCHour()) + return 1; + if (d1.getUTCMinutes() < d2.getUTCMinutes()) + return -1; + if (d1.getUTCMinutes() > d2.getUTCMinutes()) + return 1; + if (d1.getUTCSeconds() < d2.getUTCSeconds()) + return -1; + if (d1.getUTCSeconds() > d2.getUTCSeconds()) + return 1; + return 0; + }, + + gregorianToISO: function(day, month, year) { + var mnth = [0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334], + y_isleap = this.isLeapYear(year), + y_1_isleap = this.isLeapYear(year - 1), + day_of_year_number = day + mnth[month - 1]; + if (y_isleap && month > 2) + day_of_year_number++; + // find Jan 1 weekday (monday = 1, sunday = 7) + var yy = (year - 1) % 100, + jan1_weekday = 1 + parseInt(((((((year - 1) - yy) / 100) % 4) * 5) + + (yy + parseInt(yy / 4))) % 7), + // weekday for year-month-day + weekday = 1 + parseInt(((day_of_year_number + (jan1_weekday - 1)) - 1) % 7), + yearnumber, weeknumber + // find if Y M D falls in YearNumber Y-1, WeekNumber 52 or + if (day_of_year_number <= (8 - jan1_weekday) && jan1_weekday > 4){ + yearnumber = year - 1; + weeknumber = (jan1_weekday == 5 || (jan1_weekday == 6 && y_1_isleap)) + ? 53 + : 52; + } + else { + yearnumber = year; + } + // find if Y M D falls in YearNumber Y+1, WeekNumber 1 + if (yearnumber == year) { + if (((y_isleap ? 366 : 365) - day_of_year_number) < (4 - weekday)) { + yearnumber++; + weeknumber = 1; + } + } + // find if Y M D falls in YearNumber Y, WeekNumber 1 through 53 + if (yearnumber == year) { + weeknumber = parseInt((day_of_year_number + (7 - weekday) + + (jan1_weekday - 1)) / 7); + if (jan1_weekday > 4) + weeknumber--; + } + // put it all together + if (weeknumber < 10) + weeknumber = "0" + weeknumber; + return yearnumber + "-" + weeknumber + "-" + weekday; + }, + + weekOfYear: function(day, month, year) { + var now = new Date(); + return parseInt(this.gregorianToISO(day || now.getDate(), + month || now.getMonth(), year || now.getFullYear()).split("-")[1]); + }, + + quarterOfYear: function(month) { + var now = new Date(); + return parseInt((month || now.getMonth() - 1) / 3 + 1); + }, + + daysInMonth: function(month, year) { + var now = new Date(); + if (!year) + year = now.getFullYear(); + if (!month) + month = now.getMonth(); + + if (year == 1582 && month == 10) + return 21; // October 1582 only had 1st-4th and 15th-31st + + if (month == 2) + return this.isLeapYear(year) ? 29 : 28; + else if (month == 4 || month == 6 || month == 9 || month == 11) + return 30; + else + return 31; + }, + + dayOfWeek: function(day, month, year) { + var now = new Date(); + if (!year) + year = now.getFullYear(); + if (!month) + month = now.getMonth(); + if (!day) + day = now.getDate(); + return new Date(year, month, day).getDay(); + }, + + beginOfWeek: function(day, month, year, epoch) { + var now = new Date(); + if (!year) + year = now.getFullYear(); + if (!month) + month = now.getMonth(); + if (!day) + day = now.getDate(); + var this_weekday = this.dayOfWeek(day, month, year); + var interval = (7 - this.i18n.beginWeekday + this_weekday) % 7; + var d = this.dateToDays(day, month, year) - interval; + return epoch ? d : this.daysToDate(d); + }, + + weeksInMonth: function(month, year) { + var now = new Date(); + if (!year) + year = now.getFullYear(); + if (!month) + month = now.getMonth(); + + var FDOM = this.dayOfWeek(1, month, year), //firstOfMonthWeekday + beginWeekday = this.i18n.beginWeekday, + first_week_days, weeks; + if (beginWeekday == 1 && FDOM == 0) { + first_week_days = 7 - FDOM + beginWeekday; + weeks = 1; + } + else if (beginWeekday == 0 && FDOM == 6) { + first_week_days = 7 - FDOM + beginWeekday; + weeks = 1; + } + else { + first_week_days = beginWeekday - FDOM; + weeks = 0; + } + first_week_days %= 7; + return Math.ceil((this.daysInMonth(month, year) - first_week_days) / 7) + + weeks; + }, + + getCalendarWeek: function(day, month, year) { + var now = new Date(), + week_array = []; + // date for the column of week + var curr_day = this.beginOfWeek(day || now.getDate(), + month || now.getMonth(), year || now.getFullYear(), true); + + for (var i = 0; i <= 6; i++) { + week_array[i] = this.daysToDate(curr_day); + curr_day++; + } + return week_array; + }, + + getCalendarMonth: function(month, year) { + var now = new Date(), + month_array = []; + if (!year) + year = now.getFullYear(); + if (!month) + month = now.getMonth(); + + // date for the first row, first column of calendar month + var curr_day = (this.i18n.beginWeekday == 1) + ? (this.dayOfWeek(1, month, year) == 0) + ? this.dateToDays(1, month, year) - 6 + : this.dateToDays(1, month, year) - this.dayOfWeek(1, month, year) + 1 + : (this.dateToDays(1, month, year) - this.dayOfWeek(1, month, year)); + + // number of days in this month + var weeksInMonth = this.weeksInMonth(month, year); + for (var rows = 0; rows < weeksInMonth; rows++) { + month_array[rows] = []; + for (var cols = 0; cols <= 6; cols++) { + month_array[rows][cols] = this.daysToDate(curr_day); + curr_day++; + } + } + + return month_array; + }, + + getCalendarYear: function(year) { + if (!year) + year = new Date().getFullYear(); + var year_array = []; + + for (var curr_month = 0; curr_month <= 11; curr_month++) + year_array[curr_month] = this.getCalendarMonth(curr_month + 1, year); + + return year_array; + } + +}; + +})(); + +apf.date.dateFormat = (function () { + var token = /d{1,4}|m{1,4}|yy(?:yy)?|([HhMsTt])\1?|[LloSZ]|"[^"]*"|'[^']*'/g, + timezone = /\b(?:[PMCEA][SDP]T|(?:Pacific|Mountain|Central|Eastern|Atlantic) (?:Standard|Daylight|Prevailing) Time|(?:GMT|UTC)(?:[-+]\d{4})?)\b/g, + timezoneClip = /[^-+\dA-Z]/g, + pad = function (val, len) { + val = String(val); + len = len || 2; + while (val.length < len) val = "0" + val; + return val; + }; + + // Regexes and supporting functions are cached through closure + return function (date, mask, utc) { + var dF = apf.date; + + // You can't provide utc if you skip other args (use the "UTC:" mask prefix) + if (arguments.length == 1 && (typeof date == "string" + || date instanceof String) && !/\d/.test(date)) { + mask = date; + date = undefined; + } + + // Passing date through Date applies apf.date.getDateTime, if necessary + date = date ? new Date(date) : new Date(); + + if (isNaN(date)) return "NaN";//throw new SyntaxError("invalid date"); + + mask = String(dF.masks[mask] || mask || dF.masks["default"]); + + // Allow setting the utc argument via the mask + if (mask.slice(0, 4) == "UTC:") { + mask = mask.slice(4); + utc = true; + } + + var _ = utc ? "getUTC" : "get", + d = date[_ + "Date"](), + D = date[_ + "Day"](), + m = date[_ + "Month"](), + y = date[_ + "FullYear"](), + H = date[_ + "Hours"](), + M = date[_ + "Minutes"](), + s = date[_ + "Seconds"](), + L = date[_ + "Milliseconds"](), + o = utc ? 0 : date.getTimezoneOffset(), + flags = { + d : d, + dd : pad(d), + ddd : dF.i18n.dayNames[D], + dddd: dF.i18n.dayNames[D + 7], + m : m + 1, + mm : pad(m + 1), + mmm : dF.i18n.monthNames[m], + mmmm: dF.i18n.monthNames[m + 12], + yy : String(y).slice(2), + yyyy: y, + h : H % 12 || 12, + hh : pad(H % 12 || 12), + H : H, + HH : pad(H), + M : M, + MM : pad(M), + s : s, + ss : pad(s), + l : pad(L, 3), + L : pad(L > 99 ? Math.round(L / 10) : L), + t : H < 12 ? "a" : "p", + tt : H < 12 ? "am" : "pm", + T : H < 12 ? "A" : "P", + TT : H < 12 ? "AM" : "PM", + Z : utc + ? "UTC" + : (String(date).match(timezone) + || [""]).pop().replace(timezoneClip, ""), + o : (o > 0 ? "-" : "+") + + pad(Math.floor(Math.abs(o) / 60) * 100 + + Math.abs(o) % 60, 4), + S : ["th", "st", "nd", "rd"] + [d % 10 > 3 ? 0 : (d % 100 - d % 10 != 10) * d % 10] + }; + + return mask.replace(token, function ($0) { + return $0 in flags ? flags[$0] : $0.slice(1, $0.length - 1); + }); + }; +})(); + + +/** + * Create a object representation of date from datetime string parsing it with + * datetime format string + * + * @param {String} datetime the date and time wrote in allowed format + * @param {String} format style of displaying date, created using various + * masks + * Possible masks: + * d day of the month as digits, no leading zero for single-digit days + * dd day of the month as digits, leading zero for single-digit days + * ddd day of the week as a three-letter abbreviation + * dddd day of the week as its full name + * m month as digits, no leading zero for single-digit months + * mm month as digits, leading zero for single-digit months + * mmm month as a three-letter abbreviation + * mmmm month as its full name + * yy year as last two digits, leading zero for years less than 2010 + * yyyy year represented by four digits + * h hours, no leading zero for single-digit hours (12-hour clock) + * hh hours, leading zero for single-digit hours (12-hour clock) + * H hours, no leading zero for single-digit hours (24-hour clock) + * HH hours, leading zero for single-digit hours (24-hour clock) + * M minutes, no leading zero for single-digit minutes + * MM minutes, leading zero for single-digit minutes + * s seconds, no leading zero for single-digit seconds + * ss seconds, leading zero for single-digit seconds + */ +apf.date.getDateTime = function(datetime, format) { + var token = /d{1,4}|m{1,4}|yy(?:yy)?|([HhMsTt])\1?|[LloSZ]|"[^"]*"|'[^']*'/g, + timezone = /\b(?:[PMCEA][SDP]T|(?:Pacific|Mountain|Central|Eastern|Atlantic) (?:Standard|Daylight|Prevailing) Time|(?:GMT|UTC:)(?:[-+]\d{4})?)\b/g, + alter = 0, + y = new Date().getFullYear(), + m = 1, + d = 1, + h = 12, + M = 0, + s = 0, + i18n = apf.date.i18n; + + if (!format) { + throw new Error(apf.formatErrorString(0, null, + "date-format", "Date format is null")); + } + + format = format.replace(timezone, ""); + + var str = format.replace(token, function(str, offset, p) { + var part = datetime.substring(p + alter, p + alter + str.length); + + switch (str) { + case "d": + case "m": + case "h": + case "H": + case "M": + case "s": + if (!/[\/, :\-](d|m|h|H|M|s)$|^(d|m|h|H|M|s)[\/, :\-]|[\/, :\-](d|m|h|H|M|s)[\/, :\-]/.test(format)) { + throw new Error(apf.formatErrorString(0, null, + "date-format", "Dates without leading zero needs separators")); + } + + var value = parseInt(datetime.substring(p + alter, + p + alter + 2)); + + if (value.toString().length == 2) + alter++; + + return str == "d" + ? d = value + : (str == "m" + ? m = value + : (str == "M" + ? M = value + : (str == "s" + ? s = value + : h = value))); + case "dd": + return d = part; //01-31 + case "dddd": + //changeing alteration because "dddd" have no information about day number + alter += i18n.dayNames[i18n.dayNumbers[part.substring(0,3)] + 7].length - 4; + break; + case "mm": + return m = part; //01 - 11 + case "mmm": + return m = i18n.monthNumbers[part] + 1; + case "mmmm": + var monthNumber = i18n.monthNumbers[part.substring(0, 3)]; + alter += i18n.monthNames[monthNumber + 12].length - 4; + return m = monthNumber + 1; + case "yy": + return y = parseInt(part) < 70 ? "20" + part : part; + case "yyyy": + return y = part; + case "hh": + return h = part; + case "HH": + return h = part; + case "MM": + return M = part; + case "ss": + return s = part; + case "T": + case "Z": + //because in date we have only T + alter -= 2; + break; + } + }); + + return new Date(y, m-1, d, h, M, s); +}; + +// For convenience... +Date.prototype.format = function (mask, utc) { + return apf.date.dateFormat(this, mask, utc); +}; + + +Date.prototype.copy = function() { + return new Date(this.getTime()); +}; +Date.prototype.inDaylightTime = function() { + // Calculate Daylight Saving Time + var dst = 0, + jan1 = new Date(this.getFullYear(), 0, 1, 0, 0, 0, 0), // jan 1st + june1 = new Date(this.getFullYear(), 6, 1, 0, 0, 0, 0), // june 1st + temp = jan1.toUTCString(), + jan2 = new Date(temp.slice(0, temp.lastIndexOf(" ")-1)); + temp = june1.toUTCString(); + var june2 = new Date(temp.slice(0, temp.lastIndexOf(" ")-1)); + var std_time_offset = (jan1 - jan2) / (1000 * 60 * 60), + daylight_time_offset = (june1 - june2) / (1000 * 60 * 60); + + if (std_time_offset === daylight_time_offset) { + dst = 0; // daylight savings time is NOT observed + } + else { + // positive is southern, negative is northern hemisphere + var hemisphere = std_time_offset - daylight_time_offset; + if (hemisphere >= 0) + std_time_offset = daylight_time_offset; + dst = 1; // daylight savings time is observed + } + return dst; +}; +Date.prototype.addSeconds = function(sec) { + sec = parseInt(sec); + // Negative value given. + if (sec < 0) + return this.subtractSeconds(Math.abs(sec)); + + return this.addSpan(new apf.date.span(sec)); +}; +Date.prototype.addSpan = function(span) { + if (!(span instanceof apf.date.span)) return this; + var d; + + this.setSeconds(this.getSeconds() + span.seconds); + if (this.getSeconds() >= 60) { + this.setMinutes(this.getMinutes() + 1); + this.setSeconds(this.getSeconds() - 60); + } + + this.setMinutes(this.getMinutes() + span.minutes); + if (this.getMinutes() >= 60) { + this.setHours(this.getHours() + 1); + if (this.getHours() >= 24) { + d = apf.date.nextDay(this.getDate(), this.getMonth(), this.getFullYear()); + this.setFullYear(parseInt(d.year), parseInt(d.month), parseInt(d.day)); + this.setHours(this.getHours() - 24); + } + this.setMinutes(this.getMinutes() - 60); + } + + this.setHours(this.getHours() + span.hours); + if (this.getHours() >= 24) { + d = apf.date.nextDay(this.getDate(), this.getMonth(), this.getFullYear()); + this.setFullYear(parseInt(d.year), parseInt(d.month), parseInt(d.day)); + this.setHours(this.getHours() - 24); + } + + d = apf.date.dateToDays(this.getDate(), this.getMonth(), this.getFullYear()); + d += span.days; + var d2 = apf.date.daysToDate(d); + + this.setFullYear(parseInt(d2.year), parseInt(d2.month), parseInt(d2.day)); + return this; +}; +Date.prototype.subtractSeconds = function(sec) { + sec = parseInt(sec); + + // Negative value given. + if (sec < 0) + return this.addSeconds(Math.abs(sec)); + + return this.subtractSpan(new apf.date.span(sec)); +}; +Date.prototype.subtractSpan = function(span) { + if (!(span instanceof apf.date.span)) return this; + var d; + + this.setSeconds(this.getSeconds() - span.seconds); + if (this.getSeconds() < 0) { + this.setMinutes(this.getMinutes() - 1); + this.setSeconds(this.getSeconds() + 60); + } + + this.setMinutes(this.getMinutes() - span.minutes); + if (this.getMinutes() < 0) { + this.setHours(this.getHours() - 1); + if (this.getHours() < 0) { + d = apf.date.prevDay(this.getDate(), this.getMonth(), this.getFullYear()); + this.setFullYear(parseInt(d.year), parseInt(d.month), parseInt(d.day)); + this.setHours(this.getHours() + 24); + } + this.setMinutes(this.getMinutes() + 60); + } + + this.setHours(this.getHours() - span.hours); + if (this.getHours() < 0) { + d = apf.date.prevDay(this.getDate(), this.getMonth(), this.getFullYear()); + this.setFullYear(parseInt(d.year), parseInt(d.month), parseInt(d.day)); + this.setHours(this.getHours() + 24); + } + + d = apf.date.dateToDays(this.getDate(), this.getMonth(), this.getFullYear()); + d -= span.days; + var d2 = apf.date.daysToDate(d); + + this.setFullYear(parseInt(d2.year), parseInt(d2.month), parseInt(d2.day)); + return this; +}; +Date.prototype.before = function(when) { + return (apf.date.compare(this, when) == -1); +}; +Date.prototype.after = function(when) { + return (apf.date.compare(this, when) == 1); +}; +Date.prototype.equals = function(when) { + return (apf.date.compare(this, when) === 0); +}; +Date.prototype.isFuture = function() { + return this.after(new Date()); +}; +Date.prototype.isPast = function() { + return this.before(new Date()); +}; +Date.prototype.isLeapYear = function() { + return apf.date.isLeapYear(this.getFullYear()); +}; +Date.prototype.getJulianDate = function() { + return apf.date.julianDate(this.getDate(), this.getMonth(), this.getFullYear()); +}; +Date.prototype.getWeekOfYear = function() { + return apf.date.weekOfYear(this.getDate(), this.getMonth(), this.getFullYear()); +}; +Date.prototype.getQuarterOfYear = function() { + return apf.date.quarterOfYear(this.getMonth()); +}; +Date.prototype.getDaysInMonth = function() { + return apf.date.daysInMonth(this.getMonth(), this.getFullYear()); +}; +Date.prototype.getWeeksInMonth = function() { + return apf.date.weeksInMonth(this.getMonth(), this.getFullYear()); +}; +Date.prototype.getNextDay = +Date.prototype.getNextWeekday = function() { + var d = apf.date.nextDay(this.getDate(), this.getMonth(), this.getFullYear()); + return this.copy().setFullYear(d.year, d.month, d.day); +}; +Date.prototype.getPrevDay = +Date.prototype.getPrevWeekday = function() { + var d = apf.date.prevDay(this.getDate(), this.getMonth(), this.getFullYear()); + return this.copy().setFullYear(d.year, d.month, d.day); +}; +Date.prototype.getCalendarWeek = function() { + return apf.date.getCalendarWeek(this.getDate(), this.getMonth(), this.getFullYear()); +}; +Date.prototype.getCalendarMonth = function() { + return apf.date.getCalendarMonth(this.getMonth(), this.getFullYear()); +}; +Date.prototype.getCalendarYear = function() { + return apf.date.getCalendarYear(this.getFullYear()); +}; +Date.prototype.fromISO8601 = function(formattedString) { + var match = formattedString.match(/^(?:(\d{4})(?:-(\d{2})(?:-(\d{2}))?)?)?(?:T(\d{2}):(\d{2})(?::(\d{2})(.\d+)?)?((?:[+-](\d{2}):(\d{2}))|Z)?)?$/), + result = null; + + if (match) { + match.shift(); + if (match[1]) + match[1]--; // Javascript Date months are 0-based + if (match[6]) + match[6] *= 1000; // Javascript Date expects fractional seconds as milliseconds + + result = new Date(match[0]||1970, match[1]||0, match[2]||1, match[3]||0, match[4]||0, match[5]||0, match[6]||0); //TODO: UTC defaults + if (match[0] < 100) + result.setFullYear(match[0] || 1970); + + var offset = 0, + zoneSign = match[7] && match[7].charAt(0); + if (zoneSign != "Z"){ + offset = ((match[8] || 0) * 60) + (Number(match[9]) || 0); + if(zoneSign != "-") + offset *= -1; + } + if (zoneSign) + offset -= result.getTimezoneOffset(); + if (offset) + result.setTime(result.getTime() + offset * 60000); + } + else + return new Date(formattedString); + + return result; // Date or null +} + +Date.prototype.getUTCTime = function() { + //Date.UTC(year, month[, date[, hrs[, min[, sec[, ms]]]]]) + return Date.UTC(this.getUTCFullYear(), this.getUTCMonth(), this.getUTCDate(), + this.getUTCHours(), this.getUTCMinutes(), this.getUTCSeconds(), + this.getUTCMilliseconds()); +}; + +Date.prototype.toISO8601 = function() { + var pad = function (amount, width) { + var padding = ""; + while (padding.length < width - 1 && amount < Math.pow(10, width - padding.length - 1)) + padding += "0"; + return padding + amount.toString(); + } + + var offset = this.getTimezoneOffset(); + return pad(this.getFullYear(), 4) + + "-" + pad(this.getMonth() + 1, 2) + + "-" + pad(this.getDate(), 2) + + "T" + pad(this.getHours(), 2) + + ":" + pad(this.getMinutes(), 2) + + ":" + pad(this.getUTCSeconds(), 2) + + (offset > 0 ? "-" : "+") + + pad(Math.floor(Math.abs(offset) / 60), 2) + + ":" + pad(Math.abs(offset) % 60, 2); +}; + + + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/core/lib/draw.js)SIZE(66997)TIME(Mon, 10 Jan 2011 10:53:11 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + +/** + * @private + */ +apf.draw = { + + initDriver : function(){ + // initialize by copying either canvas of VML into my object. + if(!this.initLayer){ + var k,o=apf.supportVML?apf.draw.vml:apf.draw.canvas; + for(k in o){ + this[k]=o[k]; + } + } + }, + + //---------------------------------------------------------------------- + + // vars + + //---------------------------------------------------------------------- + + basevars : function(){ + return ["var _math_,v,t=0,n=(new Date()).getTime()*0.001", + ",e=Math.E, p=Math.PI, p2=2*p, p12=0.5*p", + ",x, y, z, _x,_y,_z, zt, i, j, k, _opt_;"].join(''); + }, + + vars : function(ml,mt,mr,mb){ + return ["var _math_,vx1 = v.vx1, vy1 = v.vy1,_rseed=1", + ",vx2 = v.vx2, vy2 = v.vy2, vw = vx2-vx1, vh = vy1-vy2", + ",vz2 = v.vz2, vz1 = v.vz1, vd = vz2-vz1", + ",zoom = 1/v.zoom", + ",a=v.a,b=v.b,c=v.c,d=v.d", + ",dw = l.dw",ml?"-"+(ml+mr):"", + ",dh = l.dh",mt?"-"+(mt+mb):"", + ",dw12 = dw*0.5, dh12 = dh*0.5", + ",dzw = dw/v.zoomx, dzh = -dh/v.zoomy", + ",dx = ",ml?ml:0, + ",dy = ",mt?mt:0, + ",mx = m&&m.x, my = m&&m.y", + ",db = dy+dh, dr = dx+dw", + ",tw = dw/vw, th = dh/vh, ty = -vy2*th+dy, tx = -vx1*tw+dx", + ",v,t=0,nt=0,n=(new Date()).getTime()*0.001, dt=-(l._n?l._n:n)+(l._n=n)", + ",e=Math.E, p=Math.PI, p2=2*p, p12=0.5*p", + ",x, y = 0, z = 0, _x,_y,_z, zt, i, j, k, _opt_, _anim = 0,", + "_storelut,_storelist,_translut,_speedlut,_overlaylut;"].join(''); + }, + defCamVec : function(){ + // lets do a proper 3x3 inverse and mul it with our camera pos + return "var inv=1/m00*(m11*m22-m12*m21)-m01*(m10*m22-m12*m20)+m02*(m10*m21-m11*m20);"+ + "var mcx = inv*(m11*m22-m12*m21)*m03 + inv*(m02*m21-m01*m22)*m13 + inv*(m01*m12-m02*m11)*m23,"+ + " mcy = inv*(m12*m20-m10*m22)*m03 + inv*(m00*m22-m02*m20)*m13 + inv*(m02*m10-m00*m12)*m23,"+ + " mcz = inv*(m10*m21-m11*m20)*m03 + inv*(m01*m20-m00*m21)*m13 + inv*(m00*m11-m01*m10)*m23;" + // " mcz m10*m13+m20*m23,mcy=m01*m03+m11*m13+m21*m23,mcz=m02*m03+m12*m13+m22*m23;"; + }, + setMatrix3D : function(m){ + + var l = this.l; + var s = ["var m00=",m[0],",m01=",m[1],",m02=",m[2], ",m03=",m[3], + ",m10=",m[4],",m11=",m[5],",m12=",m[6], ",m13=",m[7], + ",m20=",m[8],",m21=",m[9],",m22=",m[10],",m23=",m[11],";"]; + if(l.p3d<0){ // we have ortho perspective + this.ortho = 1; + s.push("var persp = dw / v.p3d/-v.tz, perspd = persp / ",l.ds,";"); + } else { + this.ortho = 0; + s.push("var persp = dw / v.p3d, perspd = persp / ",l.ds,";"); + } + return s.join(''); + }, + + sincos3 : function(pre,rx,ry,rz){ + return[ "var ",pre,"cx = __cos(",rx,"),",pre,"sx = __sin(",rx,"),", + pre,"cy = __cos(",ry,"),",pre,"sy = __sin(",ry,"),", + pre,"cz = __cos(",rz,"),",pre,"sz = __sin(",rz,");" ].join(''); + }, + matrix4S : function(sx,sy,sz){ + return [ sx,0,0,0, + 0,sy,0,0, + 0,0,sz,0, + 0,0,0,1 ]; + }, + matrix4T : function(tx,ty,tz){ + return [ 1,0,0,tx, + 0,1,0,ty, + 0,0,1,tz, + 0,0,0,1 ]; + }, + matrix4RP : function(pre){ + return this.matrix4R(pre+'cx',pre+'sx',pre+'cy',pre+'sy',pre+'cz',pre+'sz'); + }, + matrix4R : function(cx,sx,cy,sy,cz,sz){ + return [ [cy,'*',cz].join(''), + ['(-',sz,'*',cy,')'].join(''), + sy, + 0, + ['(',cz,'*',sx,'*',sy,'+',sz,'*',cx,')'].join(''), + ['(-',sx,'*',sy,'*',sz,'+',cx,'*',cz,')'].join(''), + ['(-',sx,'*',cy,')'].join(''), + 0, + ['(-',cx,'*',sy,'*',cz,'+',sx,'*',sz,')'].join(''), + ['(',cx,'*',sy,'*',sz,'+',cz,'*',sx,')'].join(''), + ['(',cx,'*',cy,')'].join(''), + 0, + 0,0,0,1]; + }, + matrixMul : function(){ + // lets multiply matrices on our arglist with conceptually ordered transform + var m = arguments[arguments.length-1]; + for(var i = arguments.length-2;i>=0;i--) + m = this.matrixAB(m,arguments[i]); + return m; + }, + matrixAB : function(a,b){ + var out = [], x, y, i, j, t, v; + for(y = 0;y<16;y+=4){ + for(x = 0;x<4;x++){ + v = []; + if((i=a[y]) &&(j=b[x]) ) v[v.length] = i==1?j:(j==1?i:(i+'*'+j)); + if((i=a[y+1])&&(j=b[x+4]) ) v[v.length] = i==1?j:(j==1?i:(i+'*'+j)); + if((i=a[y+2])&&(j=b[x+8]) ) v[v.length] = i==1?j:(j==1?i:(i+'*'+j)); + if((i=a[y+3])&&(j=b[x+12])) v[v.length] = i==1?j:(j==1?i:(i+'*'+j)); + out[out.length] = v.length?((v.length>1)?'('+v.join('+')+')':v[0]):0; + } + } + return out; + }, + + // check for backface for a certain plane + backface3D : function(pts,cm,zmode){ + var a = pts[0], b = pts[1], c = pts[2], x = 0, y = 1, z = 2; + if(cm) x = cm[0], y = cm[1], z = cm[2]; + return this.ortho?[ + "-((m00*",b[x],"+m01*",b[y],"+m02*",b[z],"+m03)-(__ax=m00*",a[x],"+m01*",a[y],"+m02*",a[z],"+m03))*", + "((m10*",c[x],"+m11*",c[y],"+m12*",c[z],"+m13)-(__ay=m10*",a[x],"+m11*",a[y],"+m12*",a[z],"+m13))+", + "((m10*",b[x],"+m11*",b[y],"+m12*",b[z],"+m13)-__ay)*((m00*",c[x],"+m01*",c[y],"+m02*",c[z],"+m03)-__ax)"].join(''): + [ + "(((__by=m10*",b[x],"+m11*",b[y],"+m12*",b[z],"+m13) - (__ay=m10*",a[x],"+m11*",a[y],"+m12*",a[z],"+m13)) *", + "((__cz=m20*",c[x],"+m21*",c[y],"+m22*",c[z],"+m23) - (__az=m20*",a[x],"+m21*",a[y],"+m22*",a[z],"+m23)) -", + "((__bz=m20*",b[x],"+m21*",b[y],"+m22*",b[z],"+m23) - __az) * ((__cy=m10*",c[x],"+m11*",c[y],"+m12*",c[z],"+m13) - __ay) ) * ", + "(__ax=m00*",a[x],"+m01*",a[y],"+m02*",a[z],"+m03) + ", + "((__bz - __az) * ((__cx=m00*",c[x],"+m01*",c[y],"+m02*",c[z],"+m03) - __ax) -", + "((__bx=m00*",b[x],"+m01*",b[y],"+m02*",b[z],"+m03) - __ax) * (__cz - __az) ) * __ay + ", + "((__bx - __ax) * (__cy - __ay) - (__by - __ay) * (__cx - __ax) ) * __az "].join(''); + }, + + //---------------------------------------------------------------------- + + text3D : function( p, cm, zc, text) { + var i = 0, d, pt, q, s = ["__n=0;"], x = 0, y = 1, z = 2, sx, sy, vx, vy, vxi, vyi; + if(cm) x = cm[0], y = cm[1], z = cm[2]; + + // lets project, clip and print + return [ + "if((__z = m20*",p[x],"+m21*",p[y],"+m22*",p[z],"+m23) < ",zc,"){", + this.text(["dw12+(m00*",p[x],"+m01*",p[y],"+m02*",p[z],"+m03)*",this.ortho?"persp":"(persp/__z)"].join(''), + ["dh12+(m10*",p[x],"+m11*",p[y],"+m12*",p[z],"+m13)*",this.ortho?"persp":"(persp/__z)"].join(''),text), + "}" + ].join(''); + }, + + // 3D API + // draw a 3D polygon clipped against a z-plane + poly3DClip : function(idx,pt,cm,zc,open){ + var i = 0, d, p, pt, q, s = ["__n=0;"], x = 0, y = 1, z = 2, sx, sy, vx, vy, vxi, vyi; + if(cm) x = cm[0], y = cm[1], z = cm[2]; + + //calculate z-clipping info for each vertex + for(var i = 0;i=0) cc[d]++; + } + for( i = 0;i1)cc[i] = j++; + else cc[i]=0; + } + for(var i = 0;i=0){ + pt = pts[d]; + q=[this.ortho?"": + "zt = persp / ((zt=(m20*"+pt[f0]+"+m21*"+pt[f1]+"+m22*"+pt[f2]+"+m23)<-0.01)?zt:-0.01);", + "dw12+(m00*"+pt[f0]+"+m01*"+pt[f1]+"+m02*"+pt[f2]+"+m03)*"+ + (this.ortho?"persp":"zt"), + "dh12+(m10*"+pt[f0]+"+m11*"+pt[f1]+"+m12*"+pt[f2]+"+m13)*"+ + (this.ortho?"persp":"zt")]; + d = f?0:i; + if(cc[d])q[1]= "__t"+cc[d]+(cf[d]?"":"="+q[1]), + q[2]= "__t"+cc[d]+(cf[d]++?"":"="+q[2]); + }; + switch(d){ + case -1: f=1;s.push( this.close() );break; + case 0: f=0;s.push( q[0], this.moveTo(q[1],q[2]) ); break; + case indices.length-1: s.push( q[0], this.lineTo(q[1],q[2]), + this.close() );break; + default: s.push( q[0], this.lineTo(q[1],q[2]) ); break; + } + } + return s.join('').replace(/m\d\d\*\(?0\)?\+/g,""); + }, + lineTo3D : function(x,y,z,sx,sy,fl){ + return this.$do3D("lineTo",x,y,z,sx,sy,fl); + }, + moveTo3D : function(x,y,z,sx,sy,fl){ + return this.$do3D("moveTo",x,y,z,sx,sy,fl); + }, + + $store3D : function(x,y){ + return x+";"+y+";"; + }, + store3D : function(x,y,z,sx,sy,fl){ + return this.$do3D("$store3D",x,y,z,sx,sy,fl); + }, + $do3D : function(f,x,y,z,sx,sy,fl){ + var _x,_y,_z; + if(typeof x == 'string' && x.match(/[\[\]\*\+\-\/]/))x="(_x="+x+")",_x="_x"; + else x="("+x+")",_x=x; + if(typeof y == 'string' && y.match(/[\[\]\*\+\-\/]/))y="(_y="+y+")",_y="_y"; + else y="("+y+")",_y=y; + if(typeof z == 'string' && z.match(/[\[\]\*\+\-\/]/))z="(_z="+z+")",_z="_z"; + else z="("+z+")",_z=z; + if(fl){ + var v = [x,y,z], _v = [_x,_y,_z]; + x = v[_x=fl[0]], y = v[_y=fl[1]], z = v[_z=fl[2]]; + _x = _v[_x], _y = _v[_y], _z = _v[_z]; + } + var r = []; + if(!this.ortho)r.push("zt =persp/((zt=m20*"+x+"+m21*"+y+"+m22*"+z+"+m23)<-0.01?zt:-0.01);"); + r.push(this[f]( (sx===undefined?"":sx)+ + "dw12+(m00*"+_x+"+m01*"+_y+"+m02*"+_z+"+m03)*"+(this.ortho?"persp":"zt"), + (sy===undefined?"":sy)+ + "dh12+(m10*"+_x+"+m11*"+_y+"+m12*"+_z+"+m13)*"+(this.ortho?"persp":"zt") ) ); + return r.join('').replace(/m\d\d\*\(?0\)?\+/g,""); + }, + + //---------------------------------------------------------------------- + // Style parsing + //---------------------------------------------------------------------- + + parseStyle : function( def, ovl, err ) { + var style = {}, o, v, k, s, t, i, j, n, m; + + //var o = {}, k1, v1, k2, v2, t, s, i, j, len, _self = this; + //var k, v, n ,m, w, p, h, q, u, o, x, y, z, r, g; + + var _self = this; + + // initialise transition table + if(!(t=_self.stateTransition)[0x40001]){ + s = {}; + for(v in t)for(i = 0;i<32;i++)s[v|i]=t[v]|i; + _self.stateTransition = s; + // + // alert( (n[p]|i).toString(16) ); + } + + // parse styles + ovl = ovl?this.parseJSS(ovl.item?ovl.join(''):ovl,err):{}; + // logw(apf.dump(ovl)); + // we need to integrate style into o. + function stylecopy(dst, src, key){ + // copy into the destination + var t,k,v,o; + for(k in src)if(dst[k] === undefined) + dst[k] = _self.isDynamic(v=src[k])?_self.parseJSS(v):v; + if(t=src.inherit) stylecopy( dst, def[t]||_self['$'+t], t ); + if(o = ovl[key])for(k in o)dst[k] = o[k]; + } + + for(k in def){ + if( typeof(v=def[k]) == 'object' && v!==null && v['$']==1){ + stylecopy(style[k] = {}, v, k); + } + } + function styleinherit(dst,b,c,s){ + // we should walk up the state-list + var o,v,k; + do{ + if(!c && !s) o = style[b]; + else o = ovl[b+(c?"."+c:"")+(s?":"+s:"")]; + if(typeof(o)=='object') + for(k in o) if( k.indexOf('$')==-1 && + dst[k] === undefined ) dst[k] = o[k]; + if(s)s = _self.$stateInherit[v]; + }while(s); + } + + // now lets run through all objects with a class and / or state: + for(k in ovl) if(typeof(v=ovl[k]) == 'object'){ + t = k.match(/([\w\_-]+)\.?([\w\_-]+)?\:?([\w\_-]+)?/); + var base = t[1], cls = t[2], state = t[3]; + if(cls || state){ + o = style[k] = {}; + if(state){ + if(cls)styleinherit( o, base, cls, state ); + styleinherit( o, base, 0, state ); + } + if(cls)styleinherit( o, base , cls, 0 ); + styleinherit( o, base, 0, 0 ); + o.$cls = cls?cls:'', o.$state = state?state:''; + o.$base = s = style[base]; + + if(!s) { + alert("ERROR, baseless style found: "+base); + return; + } + (s.$stylelist?s.$stylelist:(s.$stylelist=[])).push(o); + if(!cls) + (s.$baselist?s.$baselist:(s.$baselist={}))[state]=1; + else + (s.$clslist?s.$clslist:(s.$clsc = 1,s.$clslist={}))[cls]=s.$clsc++; + } + } + + function initShape(s){ + if(s.stroke === null || s.stroke=='null' || s.stroke==0) delete s.stroke; + if(s.fill === null || s.fill=='null' || s.fill==0) delete s.fill; + if(s.family === null || s.family=='null' || s.family==0) delete s.family; + + if( (s.isshape && s.fill === undefined && + s.stroke === undefined && s.tile === undefined) || + (s.isfont && s.family === undefined) ) return false; + if(s.isshape){ + s.opacity = s.opacity!==undefined ? s.opacity : 1; + s.fillopacity = s.fillopacity!==undefined ? s.fillopacity:s.opacity; + s.gradopacity = s.gradopacity!==undefined ? s.gradopacity:s.fillopacity; + s.strokeopacity = s.strokeopacity!==undefined ? s.strokeopacity:s.opacity; + s.angle = s.angle!==undefined ? s.angle : 0; + s.weight = s.weight!==undefined ? s.weight : 1 + } + return true; + } + // generate all required tables and luts + for(k in style) if(typeof(s=style[k]) == 'object'){ + // add missing class states automatically + + if(s.$baselist && s.$clslist){ + delete s.$clsc; + for(i in s.$clslist){ + for(j in s.$baselist){ + if(!style[t = k+'.'+i+':'+j]){ + // if this is an overlay, dont autogen + if((m = style[k+':'+j]) && m.overlay){ + (m.$clsovl?m.$clsovl:m.$clsovl=[]).push(i); + }else{ + style[t] = o = {}; + for(v in (t=style[k+'.'+i]))o[v] = t[v]; + for(v in (t=ovl[k+':'+j]))o[v] = t[v]; + o.$cls = i, o.$state = j, o.$base = s; + initShape(o); + s.$stylelist.push(o); + } + } + } + } + } + if(s.$stylelist){ // lets go create our style luts + s.$storelut = {}; + s.$speedlut = {}; + //s.$storelist = []; + s.$overlaylut = {}; + var cls, state, ovl, idx; + j = s.$stylelist; + for(i = 0;i=0;m--){ + cls = s.$clslist?(s.$clslist[o.$clsovl[m]]||0):0; + s.$storelut[ state|cls ] = i;//n; + s.$speedlut[ state|cls ] = o.speed || 1; + if(ovl)s.$overlaylut[ state|cls ] = (ovl==0x10000000)?cls:ovl; + } + // check if we have an overlay, ifso decode our overlay state|cls + } + } + if(!initShape(s)){ + delete style[k]; + } + } + return style; + }, + stateBit : { + 0 : 0, + 'init' : 0x01000000, // 0x00ff0000 == statetype + 'hidden' : 0x00010000, // 0x0f000000 == dynamic type 0 = no dyn, 1 = in, 2 = out + 'deinit' : 0x02000000, + 'hover' : 0x00020000, + 'hoverin' : 0x01020000, + 'hoverout' : 0x02020000, + 'select' : 0x00030000, + 'selectin' : 0x01030000, + 'selectout' : 0x02030000, + 'selecthover' : 0x00040000, + 'selecthoverin' : 0x01040000, + 'selecthoverout' : 0x02040000, + 'animating' : 0x03050000 + }, + + stateTransition : { + 0x01000000 : 0, + 0x02000000 : 0x00010000, + 0x01020000 : 0x00020000, + 0x02020000 : 0, + 0x01030000 : 0x00030000, + 0x02030000 : 0, + 0x01040000 : 0x00040000, + 0x02040000 : 0x00030000, + 0x01050000 : 0x01050000 + }, + + stateMask : { + 'selected' : 0x01000000|0x00800000|0x00200000|0x00100000|0x00080000, + 'normal' : 0x20000000|0x08000000|0x04000000|0x02000000|0x00040000, + 'dynamic' : 0x20000000|0x10000000|0x04000000|0x02000000|0x00800000| + 0x00400000|0x00100000|0x00080000|0x00040000, + 'hover' : 0x08000000|0x04000000|0x00200000|0x00100000 + }, + + $stateInherit : { + 'hidden' : 0, + 'init' : 0, + 'deinit' : 0, + 'hover' : 0, + 'hoverin' : 'hover', + 'hoverout' : 'hover', + 'select' : 0, + 'selectin' : 'select', + 'selectout' : 'select', + 'selecthover' : 'hover', + 'selecthoverin' : 'selecthover', + 'selecthoverout' : 'selecthover', + 'animating' : 0 + }, + + $stateFallback : { + 'init' : 1, + 'hover' : 1, + 'hoverin' : 'hover', + 'hoverout' : 1, + 'select' : 1, + 'selectin' : 'select', + 'selectout' : 1, + 'selecthover' : 'hover', + 'selecthoverin' : 'selecthover', + 'selecthoverout' : 'select', + 'hidden' : 1 + }, + + getXYWH : function( m, p, noflatten ){ + var t; + if(!( (t=this.$getXYWH_NT[p]) || (p=this.$getXYWH_TN[t=p]) ))return '0'; + if(m==null)return '0'; + if(typeof(m)=='object'){ + if(m.sort) return --p>=m.length?'0':( (t=m[p]) && t.sort && !noflatten ? t.join(''): t); + return (t=m[t])===undefined||p>1?'0':(t && t.sort && !noflatten ? t.join('') : t); + } + return p==1?m:'0'; + }, + $getXYWH_NT : {1:'x',2:'y',3:'z',4:'w'}, + $getXYWH_TN : {'x':1,'y':2,'z':3,'w':4}, + + getTRBL : function( m, p, noflatten ){ + var t; + if(!( (t=this.$getTRBL_NT[p]) || (p=this.$getTRBL_TN[t=p]) ))return '0'; + if(m==null)return '0'; + if(typeof(m)=='object'){ + if(m.sort) return --p>=m.length?'0':( (t=m[p]) && t.sort && !noflatten ? t.join(''): t); + return (t=m[t])===undefined||p>1?'0':(t && t.sort && !noflatten ? t.join('') : t); + } + return p==1?m:'0'; + }, + $getTRBL_NT : {1:'t',2:'r',3:'b',4:'l'}, + $getTRBL_TN : {'t':1,'y1':1,'r':2,'x2':2,'b':3,'y2':3,'l':4,'x1':4}, + + getFlat : function( m ){ + if(typeof(m)=='object' && m.sort) return m.join(''); + return m; + }, + + getColor : function (a) { + if(a.match(/\(/)) return a; + if(a.match(/^#/)) return "'"+a+"'"; + var b = a.toLowerCase(); + return (this.colors[b])?"'"+this.colors[b]+"'":a; + }, + getX : function( s, pre, val, post, def){ + var v; return (typeof(v=s[val+'-x'])=='undefined' && + (typeof(v=s[val])!='object' || typeof(v=v[0])=='undefined'))? + (typeof(def)!='undefined'?def:''):(pre+v+post); + }, + getY : function( s, pre, val, post, def, ovl){ + var v; return (typeof(v=s[val+'-y'])=='undefined' && + (typeof(v=s[val])!='object' || typeof(v=v[1])=='undefined'))? + (typeof(def)!='undefined'?def:''):(pre+v+post); + }, + checkX : function( s, val, ovl, no){ + var v; return (typeof(v=s[val+'-x'])=='undefined' && + (typeof(v=s[val])!='object' || typeof(v=v[0])=='undefined'))? + (typeof(no)=='undefined'?'':no):ovl; + }, + checkY : function( s, val, ovl, no){ + var v; return (typeof(v=s[val+'-y'])=='undefined' && + (typeof(v=s[val])!='object' || typeof(v=v[1])=='undefined'))? + (typeof(no)=='undefined'?'':no):ovl; + }, + + isDynamic : function( a ) { + // check if we have a dynamic property.. how? + return a && typeof(a)=='string' && + !(a.indexOf('.')!=-1 && a.match(/^[\s:a-zA-Z0-9\/\\\._-]+$/)) && + a.match(/[\(+*\/-]/)!=null; + }, + + baseMacro : function ( code ) { + // lets compile our macros + for(var j = code.length,i=0,v;i=0;i--) + if(typeof(c2=code[i]) == 'object'){ + for(var j=c2.length-1;j>=0;j--) + if(typeof(c3=c2[j]) == 'object') + c2[j] = c3.join(''); + code[i] = c2.join(''); + }*/ + } + // find used math functions and create local var + code.replace(/\_\_(\w+)/g,function(m,a){ + if(!cnt[a]) { + if(a.length<=2)s.push("__"+a); + else s.push("__"+a+"=Math."+a); + cnt[a]=1; + } + }); + + // optimize out const parseInt and const math-operations + code = code.replace(/(__(\w+))\((\-?\d+\.?\d*)\)/g, + function(m,a,b,c){ + if(a=='__round')return Math.round(c); + return Math[b](c); + }); + + + //code = code.replace(/__round\((_d[xy])\)/g,"$1"); + //code = code.replace(/\(0\)\+/g,""); + + //TODO pull out 0 multiplication + //code = code.replace(/\+0\s*([\;\,\)])/g,"$1"); + + if(code.match('_rndtab'))s.push('_rndtab=apf.draw.$rndtab'); + //code = code.replace(/\(([a-z0-9\_]+)\)/g,"$1"); + + code = s.length ? code.replace(/\_math\_/,s.join(',')): code; + + cnt = {},n = 0, s=[]; + /* + code = code.replace(/(m\d\d\*)\(?(\-?\d+(?:\.\d+))?\)/g,function(m,a,b){ + var t = a+b; + if(cnt[t] === undefined){ + s.push("_mo"+n+"="+t); + return cnt[t]="_mo"+(n++); + } + return cnt[t]; + }); + */ + code = s.length ? code.replace(/\_opt\_/,s.join(',')): code; + code = code.replace(/__round\((d[wh])\)/g,"$1"); + + return code; + }, + + parseJSS : function(s,err,inobj,debug){ + if(!s)return{}; + var mulinsert = true; + var lp = 0, sm = 0, t, i, len, fn = 0, sfn = [], arg = [], sarg = [], + ac = [], sac = [], sn=[], obj = inobj||{}, prop = 0, sobj = [], + _self = this, mn={1:'}',2:')',3:']',4:')',5:'}'}, rn={'{':1,'(':2,'[':3}, ln=6; + try{ + s=s.replace(/\/\*[\S\s]*?\*\/|\/\/.*?;/g,''); + s.replace(/(["'])|(\^)|([\w\.\_]+\:?[\w\_\s-]*)\s*\{\s*|([\w\_]+)\s*[:]+\s*|([\w\.\_]+)\s*\(\s*|(\#[0-9a-zA-Z]+|0x[0-9a-zA-Z]+)|(\d+\.?\d*[\_a-zA-Z]+)|([({\[])|([)}\]])|(\\["'{}\[\](),;\:]|\s*[\<\>\=*+\%@&\/]\s*|\s*\-\s+)|([,\s]+)|(;)|$/g, + function(m,str,exp,openobj,openval,openmac,hexobj,numobj,open,close,skip,sep,split,pos){ + /*logw( ln+' - '+(str?' str:'+str:'')+(openobj?' openobj:'+openobj:'')+(openval?' openval:'+openval:'')+ + (openmac?' openmac:'+openmac:'')+(numobj?' numobj:'+numobj:'')+(hexobj?' hexobj:'+hexobj:'')+(open?' open:'+open:'')+(close?' close:'+close:'')+(skip?' skip:##'+skip+'#':'')+(sep?' sep:##'+sep+'#':'')+ + (split?' split:'+split:'')+' pos:'+pos+'\n');*/ + if(skip||hexobj)return m; + if(sm || str) { + if(str && !sm)sm = str; + else if(sm==str)sm = 0; + return m; + } + if( sep ){ + ac.push(s.slice(lp,pos));arg.push(ac.join(''));lp=pos+sep.length,ac=[]; + return m; + } + if( openval ){ + if(ln>=5){ + ln = 6, prop = openval, lp = pos+m.length;arg=[],ac=[]; + } + return m; + } + if(numobj){ + ac.push(s.slice(lp,pos)); + t = m.match(/(\d\.?\d*)(.+)/); + ac.push(t[1],'*',t[2]); + lp = pos+m.length; + return m; + } + if( openmac){ + sn.push(ln=4); + if(pos>lp)ac.push( s.slice(lp,pos) ); + if(ac.length && ac[ac.length-1].match(/\)\s*$/))ac.push('*'); + if(t = openmac.match(/(\d\.?\d*)(.+)/)){ + ac.push(t[1]+'*'); + openmac = t[2]; + } + // check if our macro starts with numbers push up a number* + // if its only a number inject * and fall through to normal () + // + sac.push(ac); sarg.push(arg); + sfn.push(fn); fn = openmac; + + arg = [], ac = [], lp = pos+m.length; + return m; + } + if(openobj){ + if(ln<5)throw({t:"JSS Error - object scope found inside macro",p:pos}); + lp = pos+m.length; sn.push(ln=5); + + //logw(apf.dump(openobj)+openobj.split(' ').length); + (openobj = openobj.replace(/^\s*/,'').replace(/\s*$/,'').split(' ')).length>1?(openobj=openobj[1]+'@'+openobj[0]):openobj=openobj[0]; + sobj.push(obj); obj = (typeof(t=obj[openobj])=='object')?t:(obj[openobj]={}); + return m; + } + if( open ){ + sn.push(ln=rn[open]); + if(ln==1 && prop){ + sn.pop(); + lp = pos+m.length; sn.push(ln=5); + sobj.push(obj); obj = (typeof(t=obj[prop])=='object')?t:(obj[prop]={}); + }else if(ln==3){ + if(pos>lp)ac.push( s.slice(lp,pos) ); + sac.push(ac); sarg.push(arg); + arg = [], ac = [], lp = pos+open.length; + } + return m; + } + if( close ){ + if( !sn.length || mn[ln=sn.pop()] != close){ + throw({t:"JSS Error - closed "+ln+" with "+close,p:pos}); + log(); + } + switch(ln){ + case 3: // closed an array + ac.push(s.slice(lp,pos));arg.push(ac.join('')); + if(sarg.length!=1){ // append as string + (ac=sac.pop()).push( '[',arg.join(','),']' ); + arg = sarg.pop(); + } + else { // append as array + sac.pop();t = sarg.pop();ac=[]; + for(i = 0,len=arg.length;i1?arg:arg[0]; + arg=[], prop=null, obj = sobj.pop(); + break; + } + if(!sarg.length)ln=6; + return m; + } + if( ln>=5 ){ + ac.push(s.slice(lp,pos)); + if((t=ac.join('')).length)arg.push(t); + lp = pos+m.length, ac = []; + if(prop)obj[prop] = arg.length>1?arg:arg[0]; + else if(t && sn.length==0)obj = arg.length>1?arg:arg[0]; + arg=[],prop=null; + } + return m; + }); + if(sm)throw({t:"JSS Error - Unclosed string found "+sm,p:lp}); + if(sn.length>0)throw({t:"JSS Error - Unclosed object found "+sn[sn.length-1],p:lp}); + }catch(e){ + apf.alert_r(e); + if(err)err.v = e.p>=0 ? e.t+" at: "+e.p+" ->"+s.slice((t=e.p-4)<0?0:t,7)+"<-" : e.t; + return null; + } + return obj; + }, + + sin : function(a){return "__sin("+a+")";}, + cos : function(a){return "__cos("+a+")";}, + tan : function(a){return "__tan("+a+")";}, + asin : function(a){return "__asin("+a+")";}, + acos : function(a){return "__acos("+a+")";}, + atan : function(a){return "__atan("+a+")";}, + atan2 : function(a){return "__atan2("+a+")";}, + floor : function(a){return "__floor("+a+")";}, + exp : function(a){return "__exp("+a+")";}, + log : function(a){return "__log("+a+")";}, + pow : function(a,b){return "__pow("+a+","+b+")";}, + random : function(a){return "__random("+a+")";}, + round : function(a){return "__round("+a+")";}, + sqrt : function(a){return "__sqrt("+a+")";}, + $pal : function(imode,n){ + // alright this is a color interpolation function, we got n arguments + // which are string colors, hexcolors or otherwise and we need to write an interpolator + var s=[ + "'#'+('000000'+(__round(", + "((__a=parseInt((__t=["]; + for(var i = 2, len=arguments.length;i2?",":""); + if(apf.draw.colors[t]) + s.push( "'", apf.draw.colors[t], "'" ); + else if(t.match(/\(/)) + s.push(t); + else if(t.match(/^#/)) + s.push( "'", t, "'" ); + else + s.push(t); + } + if(imode&2) s.push( + "])[ (__g=__floor( __c=(__f=(",n,")",(imode&1)?"*"+(len-3):"", + ")<0?0:__f))>",len-3,"?",len-3,":__g].slice(1),16))&0xff)", + "*(__d=1-(__c-__floor(__c)))", + "+((__b=parseInt(__t[ (__g=__ceil(__c))>",len-3,"?",len-3,":__g", + "].slice(1),16))&0xff)*(__e=1-__d) )", + "+(__round(__d*(__a&0xff00)+__e*(__b&0xff00))&0xff00)", + "+(__round(__d*(__a&0xff0000)+__e*(__b&0xff0000))&0xff0000)", + ").toString(16)).slice(-6)"); + else s.push( + "])[ __floor( __c=(__f=(",n,")",imode?"*"+(len-3):"", + ")<0?-__f:__f)%",len-2,"].slice(1),16))&0xff)", + "*(__d=1-(__c-__floor(__c)))", + "+((__b=parseInt(__t[ __ceil(__c)%",len-2, + "].slice(1),16))&0xff)*(__e=1-__d) )", + "+(__round(__d*(__a&0xff00)+__e*(__b&0xff00))&0xff00)", + "+(__round(__d*(__a&0xff0000)+__e*(__b&0xff0000))&0xff0000)", + ").toString(16)).slice(-6)"); + return s.join(''); + }, + $lut : function(imode,n){ + var s=["(["],a, i = 2, len = arguments.length; + for(;i2?",":""); + if(typeof(a)=='string' && a.match(/\(/) || a.match(/^['"]/)) + s.push(a); + else s.push("'",a,"'"); + } + s.push("])[__floor((__b=((",n,")",imode?"*"+(len-3):"", + ")%",len-2,")<0?-__b:__b)]"); + return s.join(''); + }, + + $lin : function(imode,n){ + var s=["((__t=["],a, i = 2,len=arguments.length; + for(;i2?",":""); + if(typeof(a)=='string' && a.match(/\(/) || a.match(/^['"]/)) + s.push(a); + else s.push("'",a,"'"); + } + s.push("])[__floor( __c=(__f=(",n,")",imode?"*"+(len-3):"", + ")<0?-__f:__f)%",len-2,"]", + "*(__d=1-(__c-__floor(__c)))", + "+__t[ __ceil(__c)%",len-2, + "]*(__e=1-__d) )"); + return s.join(''); + }, + + fixed : function(a,v,nz){ + v = "parseFloat(("+a+").toFixed("+v+"))"; + return parseInt(nz)?this.nozero(a,v):v; + }, + padded : function(a,v,nz){ + v = "("+a+").toFixed("+v+")"; + return parseInt(nz)?this.nozero(a,v):v; + }, + abs : function(a){ + if(parseFloat(a)==a)return Math.abs(a); + if(typeof(a) == 'number' || a.match(/$[a-z0-9_]+^/)) + return "("+a+"<0?-"+a+":"+a+")"; + return "((__t="+a+")<0?-__t:__t)"; + }, + min : function(a,b){ + if(b===null)return a; + if(parseFloat(a)==a && parseFloat(b)==b)return Math.min(a,b); + var a1=a,b1=b,a2=a,b2=b; + if(typeof(a) == 'string' && !a.match(/$-?[a-z0-9\_]+^/))a1="(__a="+a+")", a2="__a"; + if(typeof(b) == 'string' && !b.match(/$-?[a-z0-9\_]+^/))b1="(__b="+b+")", b2="__b"; + return "(("+a1+")<("+b1+")?"+a2+":"+b2+")"; + }, + max : function(a,b){ + if(b===null)return a; + if(parseFloat(a)==a && parseFloat(b)==b)return Math.max(a,b); + var a1=a,b1=b,a2=a,b2=b; + if(typeof(a) == 'string' && !a.match(/$-?[a-z0-9\_]+^/))a1="(__c="+a+")", a2="__c"; + if(typeof(b) == 'string' && !b.match(/$-?[a-z0-9\_]+^/))b1="(__d="+b+")", b2="__d"; + return "(("+a1+")>("+b1+")?"+a2+":"+b2+")"; + }, + clamp : function(a,b,c){ + if(b===null||c==null)return a; + return this.max(this.min(a,c),b); + }, + pal : function(){ + var arg = Array.prototype.slice.call(arguments,0);arg.unshift(1); + return this.$pal.apply(this,arg); + }, + pali : function(){ + var arg = Array.prototype.slice.call(arguments,0);arg.unshift(0); + return this.$pal.apply(this,arg); + }, + palc : function(){ + var arg = Array.prototype.slice.call(arguments,0);arg.unshift(3); + return this.$pal.apply(this,arg); + }, + palci : function(){ + var arg = Array.prototype.slice.call(arguments,0);arg.unshift(2); + return this.$pal.apply(this,arg); + }, + lin : function(){ + var arg = Array.prototype.slice.call(arguments,0);arg.unshift(1); + return this.$lin.apply(this,arg); + }, + lini : function(){ + var arg = Array.prototype.slice.call(arguments,0);arg.unshift(0); + return this.$lin.apply(this,arg); + }, + lut : function(){ + var arg = Array.prototype.slice.call(arguments,0);arg.unshift(1); + return this.$lut.apply(this,arg); + }, + luti : function(){ + var arg = Array.prototype.slice.call(arguments,0);arg.unshift(0); + return this.$lut.apply(this,arg); + }, + $rgbpack : function( r,g,b){ + return a + }, + rgb : function(r,g,b){ + if(parseFloat(r)==r && parseFloat(g)==g && parseFloat(b)==b) + return this.$rgbpack(r,g,b); + return ["('#'+('000000'+(", + (parseFloat(r)==r?((r<0?0:(r>255?255:parseInt(r)))<<16): + "(((__t="+r+")<0?0:(__t>255?255:parseInt(__t)))<<16)"),"+", + (parseFloat(g)==g?((g<0?0:(g>255?255:parseInt(g)))<<8): + "(((__t="+g+")<0?0:(__t>255?255:parseInt(__t)))<<8)+"),"+", + (parseFloat(b)==b?((b<0?0:(b>255?255:parseInt(b)))): + "(((__t="+b+")<0?0:(__t>255?255:parseInt(__t))))"), + ").toString(16)).slice(-6))"].join(''); + }, + $hsvpack : function(h,s,v){ + var i, m=v*(1-s), + n=v*(1-s*((i=Math.floor(((h<0?-h:h)%1)*6))?h-i:1-(h-i))); + + switch (i) + { + case 6: + case 0: return this.$rgbpack(v, n, m); + case 1: return this.$rgbpack(n, v, m); + case 2: return this.$rgbpack(m, v, n); + case 3: return this.$rgbpack(m, n, v); + case 4: return this.$rgbpack(n, m, v); + default: + case 5: return this.$rgbpack.rgb(v, m, n); + } + }, + hsv : function(h,s,v){ + if(parseFloat(r)==r && parseFloat(g)==g && parseFloat(b)==b) + return this.$hsvpack(r,g,b); + return "apf.draw.$hsvpack("+h+","+s+","+v+");"; + }, + rgbf : function(r,g,b){ + return this.rgb(parseFloat(r)==r?r*255:"255*("+r+")", + parseFloat(g)==g?g*255:"255*("+g+")", + parseFloat(b)==b?b*255:"255*("+b+")"); + }, + nozero : function(a,v,z){ + return "(("+a+")>-0.0000000001 && ("+a+")<0.0000000001)?"+ + (z!==undefined?z:"''")+":("+(v!==undefined?v:a)+")"; + }, + $rndtab : null, + rnd : function(a){ + if(a){ + if( !this.$rndtab ){ + var i, t = this.$rndtab = Array( 256 ); + for(i = -256;i<256;i++)t[i] = Math.random(); + } + return "_rndtab[__round(("+a+")*255)%255]"; + } + return "((_rseed=(_rseed * 16807)%2147483647)/2147483647)" + }, + ang : function(a){ + if(a == parseFloat(a))return a * (Math.PI / 180); + //alert("("+a+"(p/180))"); + return "(("+a+")*p/180)"; + }, + snap : function(a,b){ + return "(__round(("+a+")/(__t=("+b+")))*__t)"; + }, + rnds : function(a,b){ + return this.rnd(this.snap(a,b)); + }, + tsin : function(a){ + return "(0.5+0.5*__sin("+a+"))"; + }, + tcos : function(a){ + return "(0.5+0.5*__cos("+a+"))"; + }, + usin : function(a){ + return "(0.5-0.5*__sin("+a+"))"; + }, + ucos : function(a){ + return "(0.5-0.5*__cos("+a+"))"; + }, + two : function(a){ + return "(0.5+0.5*("+a+"))"; + }, + easein : function(t,pow){ + if(!pow)pow = '3'; + return ['__pow(',t,',',pow,')'].join(''); + }, + easeout : function(t,pow){ + if(!pow)pow = '3'; + return ['(1-__pow(1-(',t,'),',pow,'))'].join(''); + }, + ease : function(t,powin,powout){ + if(!powin)powin = '3'; + if(!powout)powout = powin; + return ["(0.5*((__a=2*(",t,"))<1?", + "__pow(__a,",powin,"):", + "(2-__pow(2-__a,",powout,"))))"].join(''); + }, + fontz : function(a,b) { + // we multiply b*l.ds and then + return (-a/b)+(this.ortho?"*perspd":"*(perspd/__z)"); + }, + + $canJoin : function( a, b){ + if(a.isfont && b.isfont) + return a.join!=null && b.join!=null && + a.family === b.family && + a.join === b.join && + a.height == b.height && + a.width == b.width && + a.align === b.align && + a.color === b.color && + a.size === b.size && + a.style === b.style; + if(a.isshape && b.isshape) + return a.join!=null && b.join!=null && + a.stroke === b.stroke && + a.join === b.join && + a.weight == b.weight && + a.fill === b.fill && + a.fillopacity === b.fillopacity && + a.strokeopacity === b.strokeopacity && + a.angle === b.angle; + return false; + }, + + $shape : { + isshape : true, + stroke : null, + fill : null, + tilex:'(this.$tilex)', + tiley:'(this.$tiley)' + }, + + $font : { + isfont : true, + height : 12, + family : "verdana", + weight : "normal", + color : "#00000", + size : 10 + }, + + //---------------------------------------------------------------------- + + // Generic rendering + + //---------------------------------------------------------------------- + + draw3D : function(x,y,z,w,h,d){ + return ''; + }, + + //---------------------------------------------------------------------- + + beginMouseState : function( style, sthis, func, nargs ){ + var s = [], l = this.l; + + this.mousestyle = style; + this.mousethis = sthis; + this.mousefunc = func; + this.mousestates = []; + var v = style.$statelist, i, j, t, u; + if(!v || !v.length) return ''; + + v = this.mousestates = v.slice(0); + if(v[0]!=style) + v.unshift(style); + + if(!l._mousestyles)l._mousestyles = []; + for(i = 0, j = v.length;i", + "((_y2=(",rw,")%p2)<0?(_y2=p2-_y2):_y2) ){", + "if(_x1 >= _x2 || _x1<=_y2 )return x;", + "}else{", + "if(_x1 >= _x2 && _x1<=_y2 )return x;", + "}", + "}" + ].join(''); + }else{ + return [ + this.moveTo(x,y), + this.ellipse('_x6','_y6',w,h,rs,rw,1), + this.close()].join(''); + } + /* + if(gx(t,'','scale','','1')!='1'){ + rs=['_x5=(',gx(t,'(','offset',')+'),'(',rs,')','+', + gx(t,'(','center',')','0.5'),'*(_x3=',rw,')', + gx(t,'*(1-(_x4=','scale','))'), + gx(t,'+_x3*(','move',')'),')*p2'].join(''); + rw='_x5+(_x3*_x4)*p2'; + }else{ + rs = ['_x5=(',gx(t,'(','offset',')+'),'(',rs,')', + gx(t,'+(_x3='+rw+')*(','move',')'),')*p2'].join(''); + rw = '_x5+('+cx(t,'move','_x3',rw)+')*p2'; + } + if(gy(t,'','scale','','1')!='1'){ + ds=[gy(t,'(','offset',')+'),gy(t,'(','center',')','0.5'), + gy(t,'*(1-(_y4=','scale','))'),gy(t,'+(','move',')')].join(''); + dw='_y4'; + }else{ + ds = [gy(t,'(','offset',')','0'),gy(t,'+(','move',')')].join(''); + dw = '1'; + } + // lets draw an ellipse with rs and rw phases + // now we have an offset y and a size y how do we deal with that? + if(ds!='0'){ + return [ +this.moveTo("_x6=__cos(_y8=((_x9="+rs+")+(_y9="+rw+"))*0.5)*(_x8="+ds+")*(_x7="+w+")+("+x+")", + "_y6=__sin(_y8)*_x8*(_y7="+h+")+("+y+")"), + this.ellipse( '_x6','_y6','_x7*(_x3='+dw+')','_y7*_x3','_x9','_y9',1), + this.close()].join(''); + }else{ + return [ + this.moveTo("_x6=("+x+")","_y6=("+y+")"), + this.ellipse( '_x6','_y6',dw=='1'?'('+w+')':'('+w+')*(_x3='+dw+')', + dw=='1'?'('+h+')':'('+h+')*_x3',rs,rw,1), + this.close()].join(''); + }*/ + } + }, + + //---------------------------------------------------------------------- + + shapedRect : function(x,y,w,h,m){ + // css stylable drawing + var t = this.style; + var gx = this.getX, gy = this.getY, cx = this.checkX, cy = this.checkY; + function rect(){ + if(gx(t,'','scale','','1')!='1'){ + x=[gx(t,'dzw*(','offset',')+'),'(',x,')','+',gx(t,'(','center',')','0.5'),'*(_x3=',w,')', + gx(t,'*(1-(_x4=','scale','))'),gx(t,'+_x3*(','move',')')].join(''); + w='_x3*_x4'; + }else{ + x = [gx(t,'dzw*(','offset',')+'),'(',x,')',gx(t,'+(_x3='+w+')*(','move',')')].join(''); + w = cx(t,'move','_x3',w); + } + if(gy(t,'','scale','','1')!='1'){ + y=[gy(t,'dzh*(','offset',')+'),'(',y,')','+',gy(t,'(','center',')','0.5'),'*(_y3=',h,')', + gy(t,'*(1-(_y4=','scale','))'),gy(t,'+_y3*(','move',')')].join(''); + h='_y3*_y4'; + }else{ + y = [gy(t,'dzh*(','offset',')+'),'(',y,')',gy(t,'+(_y3='+h+')*(','move',')')].join(''); + h = cy(t,'move','_y3',h); + } + } + switch(t.shape){ + case 'rect': + default: + if(!t.rotate){ + rect(); + if(m){ + return [ + "if( (_x9 = mx - (",x,"))>=0 && _x9<(",w,")&&", + " (_y9 = my - (",y,"))>=0 && _y9<(",h,")) return x;", + ].join(''); + } + return this.rect(x,y,w,h); + }else{ + return [ + '_x9=(_x8=(_x6=',gx(t,'(','center',')','0.5'),'*(_x3=',w,'))*(',gx(t, + '(1-(_x4=','scale','))','0'),'-1))+_x3',cx(t,'scale','*_x4'),';', + '_y9=(_y8=(_y6=',gy(t,'(','center',')','0.5'),'*(_y3=',h,'))*(',gy(t, + '(1-(_y4=','scale','))','0'),'-1))+_y3',cy(t,'scale','*_y4'),';', + this.moveTo('(_cr=__cos(_t='+t.rotate+'))*_x8-(_sr=__sin(_t))*_y8+(_x5='+ + gx(t,'(','offset',')+')+x+'+_x6)','_sr*_x8+_cr*_y8+(_y5='+ + gy(t,'(','offset',')+')+y+'+_y6)'), + this.lineTo('_cr*_x9-_sr*_y8+_x5','_sr*_x9+_cr*_y8+_y5'), + this.lineTo('_cr*_x9-_sr*_y9+_x5','_sr*_x9+_cr*_y9+_y5'), + this.lineTo('_cr*_x8-_sr*_y9+_x5','_sr*_x8+_cr*_y9+_y5'), + this.close() + ].join(''); + } + case 'circle':{ + rect(); + if(m){ + return [ + "if( (_x9 = mx - (",x,"))>=0 && _x9<(",w,")&&", + " (_y9 = my - (",y,"))>=0 && _y9<(",h,")) return x;", + ].join(''); + } + return t.pie?[ + this.moveTo('_x6='+x+'+'+'(_x5=0.5*('+w+'))','_y6='+y+'+'+'(_y5=0.5*('+h+'))'), + this.ellipse('_x6','_y6','_x5','_y5',gx(t,'','range','' ),gy(t,'','range','' ) ), + this.close()].join(''):[ + this.ellipse(x+'+'+'(_x5=0.5*('+w+'))',y+'+'+'(_y5=0.5*('+h+'))', + '_x5','_y5',gx(t,'','range','' ),gy(t,'','range','' ) ), + this.close()].join(''); + }break; + case 'polygon':{ + if(t.frames){ + + }else{ + return [ + "_x3=((_x2=",gy(t,"","range","","2*p"), + ")-(_x1=",gx(t,'','range','','0'),"))/(",t.steps||10,");v=_x1;", + this.moveTo("(_x4="+x+")+__sin(_x1)*(_x5="+w+")", + "(_y4="+y+")+__cos(_x1)*(_y5="+h+")"), + "for(v=_x1+_x3;v<_x2;v+=_x3){", + this.lineTo("_x4+__sin(_x1)*_x5", + "_y4+__cos(_x1)*_y5"), + "}", + this.close() + ].join(''); + } + }break; + case 'math':{ + if(t.frames){ + // expand shape + }else{ + rect(); + return [ + "_x7=((_x6=",gy(t,"","range","","2*p"), + ")-(_x5=",gx(t,'','range','','0'),"))/(",t.steps||10,");v=_x5;", + this.moveTo("(_x8="+x+")+"+gx(t,"(","path",")","0")+"*(_x9="+w+")", + "(_y8="+y+")+"+gy(t,"(","path",")","0")+"*(_y9="+h+")"), + "for(v=_x5+_x7;v<_x6;v+=_x7){", + this.lineTo("_x8"+gx(t,"+(","path",")*_x9"), + "_y8"+gy(t,"+(","path",")*_y9")), + "}", + this.close() + ].join(''); + } + }break; + } + return ''; + }, + + $endDraw : function() { + if(this.mousemode){ + return this.$endMouse(); + } + if(this.statemode){ + return this.$endState(); + } + var t = this.style; + if(t){ + if(t.isshape) + return this.$endShape(); + if(t.isfont) + return this.$endFont(); + } + return ''; + }, + + //---------------------------------------------------------------------- + + // State rendering + + //---------------------------------------------------------------------- + + serializeStyleState : function(style){ + var s = [] ,h,v,k,f; + if(!style.$stylelist)return ""; + s.push("$storelist:_n=[", Array(style.$stylelist.length).join("[],"),"[]]"); + s.push(",$storelut:{"); + h = style.$storelut;f = 1; + for(k in h){ + s.push(--f?",":"","0x",parseInt(k).toString(16),":_n[",h[k],"]"); + } + s.push("},$speedlut:{"); + h = style.$speedlut;f = 1; + for(k in h){ + s.push(--f?",":"","0x",parseInt(k).toString(16),":",h[k]); + } + s.push("},$overlaylut:{"); + h = style.$overlaylut;f = 1; + for(k in h){ + s.push(--f?",":"","0x",parseInt(k).toString(16),":0x",parseInt(h[k]).toString(16)); + } + s.push("}"); + return s.join(''); + }, + + // state based drawing + beginState : function( style, sthis, func, nargs, dyns ){ + var s = [this.beginShape(style.$shadow || style)]; + + this.statemode = 1; + this.statethis = sthis; + this.stateargs = nargs; + this.statefunc = func; + this.statedyns = dyns || []; + this.statedyns.push("t","x"); + + var v = style.$stylelist, i, n; + if(!v || !v.length) return s.join(''); + + s.push("_storelut = _s.$storelut, _storelist = _s.$storelist,_overlaylut = _s.$overlaylut,", + "_translut = apf.draw.stateTransition, _speedlut = _s.$speedlut ;\n"); + + for(i = 0, n = v.length;i1){", + "_t=",state,"=_translut[_t],",time,"=n,t=0;", + "}", + "if(_t&0x0f000000)_anim=1,nt=1-t;", + "}"]; + for(i = 2, j = arguments.length;i0){", + "_sv=0,",d,";", + this.beginShape(style), + "for(_sv=0;_sv<_st;_sv+=",n,"){", + d,",nt = 1-t;", + this.statefunc.apply(this.statethis,a), + "}", + this.$endDraw(), + "}else _styles[",style._id,"]._path=[];\n" + ].join(''); + } + return s.join(''); + }, + + //---------------------------------------------------------------------- + + // HTML Text output + + //---------------------------------------------------------------------- + + + // generic htmlText + beginFont: function( style, needed, ml,mt,mr,mb ) { + if(!style || needed===undefined)return "document.title='beginFont Failed';"; + var l = this.l, html = l._htmljoin, s=[this.$endDraw()]; + this.style = style; + style._id = l._styles.push(style)-1; + + ml = ml!==undefined?ml/l.ds:0; + mt = mt!==undefined?mt/l.ds:0; + mr = mr!==undefined?mr/l.ds:0; + mb = mb!==undefined?mb/l.ds:0; + + + if(parseInt(style.left)!=style.left) this.mx = "+("+style.left+")"+(ml?"-"+ml:""); + else this.mx = "+"+(style.left-ml); + if(parseInt(style.top)!=style.top) this.my = "+("+style.top+")"+(mt?"-"+mt:""); + else this.my = "+"+(style.top-mt); + // find a suitable same-styled other text so we minimize the textdivs + /* + for(i = l._styles.length-2;i>=0;i--){ + if(!l._styles[i]._prev && + apf.draw.equalStyle( l._styles[i], style )){ + style._prev = i; + break; + } + } + s + if(style._prev===undefined){*/ + this.dynsize = (parseInt(style.size)!=style.size); + style._txtdiv = ["
-
"].join(''); + html.push("
"); + + s.push( "_s=_styles[",style._id,"],_tn=_s._txtnodes,_tc = 0;\n"); + /*} else { + if(this.last !== style._prev) + s.push("_s=_styles[",style._prev, + "],_tn=_s._txtnodes,_tc = _s._txtcount;\n"); + }*/ + s.push("if((_l=(",needed, + ")) > _tn.length-_tc)apf.draw.$allocText(_s,_l);"); + return s.join(''); + }, + + text : function( x, y, text) { + var t = ((this.l.ds>1)?"/"+this.l.ds:""); + return ["if( (_t=_tn[_tc++]).s!=(_v=",text,") )_t.v.nodeValue=_t.s=_v;", + "if(_t.x!=(_v=__round(",x,")))_t.n.style.left=_t.x=(_v",t,this.mx,")+'px';", + "if(_t.y!=(_v=__round(",y,")))_t.n.style.top=_t.y=(_v",t,this.my,")+'px';", + this.dynsize?[ + "if(_t.sz!=(_v=__round(",this.style.size,"))&&_v>0)_t.n.style.fontSize=_t.sz=_v+'px';" + ].join(''):""].join(''); + + }, + + $allocText : function(style, needed){ + var t, tn = style._domnode, ts = style._txtnodes; + if(!ts.length)tn.innerHTML = Array(needed+1).join(style._txtdiv); + else tn.insertAdjacentHTML('beforeend',Array(needed+1). + join(style._txtdiv)); + while(needed-->0){ + t=tn.childNodes[ts.length]; + ts.push({ x:-10000000000,y:-10000000000, n: t, v: t.firstChild,sz:-1,s:null}); + } + }, + + $endFont : function(){ + this.last = this.style._id; + this.style = 0; + this.mx="",this.my=""; + return "_s._txtcount = _tc;"; + }, + + $finalizeFont : function(style) { + var s=["if((_lc=(_s=_styles[",style._id,"])._txtused)>", + "(_tc=_s._txtcount)){_tn=_s._txtnodes;", + "for(;_lc>_tc;)_tn[--_lc].n.style.display='none';", + "_s._txtused=_tc;", + "} else if(_lc<_tc) {_tn=_s._txtnodes;", + "for(;_lc<_tc;)_tn[_lc++].n.style.display='block';", + "_s._txtused=_tc;", + "}\n"]; + //var v = style._txtnodes = []; + //style._txtused = 0; + //style._txtcount = 0; + return s.join(''); + }, + + + colors : apf.color.colors +}; + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/core/lib/flow.js)SIZE(71086)TIME(Mon, 10 Jan 2011 10:53:11 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + + +/** + * This abstraction is used for creating block elements which can be moved by + * mouse or keyboard, rotated with 90 degreed steps, flipped horizontally and + * vertically and resized on the fly using the mouse. Each block can have inputs + * defined in a template file. Inputs allow creating stiff connections between + * block elements. If a block does not have inputs, the connection is created in + * the most optimal way. + * + * @event onmousedown Fires when mouse button is pressed on document body + * @event onmousemove Fires when mouse pointer is moving over document body + * @event onmouseup Fires when mouse button is released while the pointer is + * over the document body + * + * @attribute {Boolean} isMoved the state of movement. When connection + * is moving, this attribute is set to true. + * It gives information to other methods + * what happens with the connection element. + * Possible values: + * true block moves + * false block don't move + * @attribute {Object} objCanvases storage workareas objects, it allows + * an easy access to them if need + * @attribute {Object} connectionsTemp keeps information about source block + * and its input in moment of connection + * creation. (mode must be set to + * "connection-add") + * @attribute {Object} connectionsManager manage of entire connection creation + * process + * @attribute {Number} sSize connection line width + * @attribute {Number} fsSize define size of first and last connection + * segment + * + * @private + * @default_private + * @constructor + * + * @author Lukasz Lipinski + * @version %I%, %G% + * @since 1.0 + * @namespace apf + */ + +apf.flow = { + isMoved : false, + objCanvases : {}, + connectionsTemp : null, + connectionsManager : null, + + sSize : 7, + fsSize : 15, + + init : function() { + //apf.flow.connectionsManager = new apf.flow.connectionsManager; + + document.body.onmousedown = function(e) { + e = (e || event); + + /* Looking for Block element */ + var target = e.target || e.srcElement, + isDragged = false; + + if (target.tagName == 'HTML') + return; + while (target != document.body && !apf.flow.findBlock(target.id)) { + target = target.parentNode || target.parentElement; + } + /* Looking for Block element - End*/ + + var objBlock = apf.flow.isBlock(target); + + if (!objBlock) + return; + if (!objBlock.draggable) + return; + + var sx = e.clientX, sy = e.clientY, + dx = 0, dy = 0, + l = parseInt(target.style.left), + t = parseInt(target.style.top), + newTop, newLeft; + //Snap + var snap = objBlock.canvas.snap, + gridW = objBlock.canvas.gridW, + gridH = objBlock.canvas.gridH; + + //objBlock.canvas.htmlElement.scrollLeft = Math.round(l+dx+target.offsetWidth); + + if (e.preventDefault) + e.preventDefault(); + + document.body.onmousemove = function(e) { + e = (e || event); + + isDragged = true; + + dx = e.clientX - sx; + dy = e.clientY - sy; + + newTop = t + dy; + newLeft = l + dx; + + if (snap) { + target.style.left = Math.round(newLeft / gridW) * gridW + "px"; + target.style.top = Math.round(newTop / gridH) * gridH + "px"; + } + else { + target.style.left = newLeft + "px"; + target.style.top = newTop + "px"; + } + + + objBlock.onMove(); + apf.flow.onblockmove(); + + return false; + }; + + document.body.onmouseup = function(e) { + document.body.onmousemove = null; + + if (apf.flow.onaftermove && isDragged) { + apf.flow.onaftermove(dy, dx); + + isDragged = false; + } + }; + }; + } +}; + +/** + * Workarea where blocks and connections its placed + * + * @param {HTMLElement} htmlElement the html representation of a workarea + * @constructor + */ +apf.flow.canvas = function(htmlElement) { + if (!htmlElement.getAttribute("id")) { + apf.setUniqueHtmlId(htmlElement); + } + + this.id = htmlElement.getAttribute("id"); + this.htmlElement = htmlElement; + + this.htmlBlocks = {}; + this.htmlConnectors = {}; + + this.scrollPointer = null; + this.lastTop = 0; + this.lastLeft = 0; + + this.mode = "normal"; + this.disableremove = false; + this.snap = false; + this.gridW = 48; + this.gridH = 48; + + this.initCanvas = function() { + apf.flow.objCanvases[this.htmlElement.getAttribute("id")] = this; + }; + + this.removeConnector = function(id) { + var c = this.htmlConnectors[id]; + c.htmlElement.parentNode.removeChild(c.htmlElement); + this.htmlConnectors[id] = c = null; + delete this.htmlConnectors[id]; + }; + + this.deselectConnections = function() { + for (var id in this.htmlConnectors) { + var con = this.htmlConnectors[id]; + if (con.selected) { + con.deselect("selected"); + con.deselectInputs("Selected"); + con.selected = false; + } + } + }; + + this.setMode = function(mode) { + this.mode = mode; + }; + + this.getMode = function() { + return this.mode; + }; + + this.getWindowScrollLeft = function() { + return document.documentElement.scrollLeft || document.body.scrollLeft; + }; + + this.getWindowScrollTop = function() { + return document.documentElement.scrollTop || document.body.scrollTop; + }; + + this.scrollLeft = function() { + this.htmlElement.scrollLeft = 9999; + }; + + this.scrollTop = function() { + this.htmlElement.scrollTop = 9999; + }; + + this.getScrollLeft = function() { + return this.htmlElement.scrollLeft; + }; + + this.getScrollTop = function() { + return this.htmlElement.scrollTop; + }; + + this.addScrollPointer = function() { + this.scrollPointer = this.htmlElement.appendChild(document.createElement("div")); + this.scrollPointer.className = "scrollPointer"; + }; + + this.moveLeftScrollPointer = function(left) { + var value = parseInt(left) + 150; + this.scrollPointer.style.left = value + "px"; + this.lastLeft = parseInt(left); + }; + + this.moveTopScrollPointer = function(top) { + var value = parseInt(top) + 150; + this.scrollPointer.style.top = value + "px"; + this.lastTop = parseInt(top); + }; + + this.getWidth = function() { + return this.htmlElement.offsetWidth; + }; + + this.getHeight = function() { + return this.htmlElement.offsetHeight; + }; +}; + +/** + * Creates a new block object which can be moved by mouse or keyboard, rotated + * with 90 degrees steps, flipped horizontally and vertically and resized on the fly + * using the mouse. Each block can have inputs, background picture and minimal + * dimension defined in the template file. It is possible to create connections + * between blocks. + * + * @param {HTMLElement} htmlElement the html representation of block + * @param {Object} objCanvas object representation of workarea (canvas) element + * @param {Object} other the properties of the block element + * Properties: + * {Boolean} lock prohibit block move, default is false + * Possible values: + * false block element is unlocled + * true block element is locked + * {Boolean} flipv whether to mirror the block over the vertical axis, + * background image is flipped automaticly, default is false + * Possible values: + * true block element is flipped + * false block element is not flipped + * {Boolean} fliph whether to mirror the block over the horizontal axis, + * background image is flipped automaticly. default is false + * Possible values: + * true block element is flipped + * false block element is not flipped + * {Number} rotation the rotation in degrees clockwise, background image + * is rotated automaticly, default is 0 + * Possible values: + * 0 0 degrees rotation + * 90 90 degrees rotation + * 180 180 degrees rotation + * 270 270 degrees rotation + * {Object} inputList Block's inputs list, block could haven't any inputs + * Properties: + * {Number} x position in pixels relative to Block's horizontal + * dimension + * {Number} y position in pixels relative to Block's vertical + * dimension + * {String} position edge where input is placed + * Possible values: + * top input is placed on top edge of block + * right input is placed on right edge of block + * bottom input is placed on bottom edge of block + * left input is placed on left edge of block + * {String} type name of block with special abilities, which + * could set in template file + * {String} picture Path to image file. + * {Number} dwidth the minimal horizontal size of Block element + * {Number} dheight the minimal vertical size of Block element + * {Boolean} scalex resizing in horizontal plane + * {Boolean} scaley resizing in vertical plane + * {Boolean} scaleratio resizing in horizontal or vertical plane only + * is not allowed. Resizing in two dimensions + * plane at the same time is allowed. + * {XMLElement} xmlNode the xml representation of block from model + * {String} caption description placed under block element + * @constructor + */ +apf.flow.block = function(htmlElement, objCanvas, other) { + this.canvas = objCanvas; + this.htmlElement = htmlElement; + this.id = htmlElement.getAttribute("id"); + this.moveListeners = new Array(); + this.draggable = true; + this.htmlOutputs = {}; + + this.image = null; + this.other = other; + + var _self = this; + + this.destroy = function() { + //removing events + this.htmlElement.onmouseover = + this.htmlElement.onmouseout = + this.htmlElement.onclick = null; + + //removing connections + for (var i = this.moveListeners.length-1; i >= 0; i--) { + this.moveListeners[i].destroy(); + this.moveListeners.removeIndex(i); + } + //removing objBlock from canvas + delete this.canvas.htmlBlocks[this.id]; + } + + this.initBlock = function() { + this.canvas.htmlBlocks[this.id] = this; + + var tag, dChilds, j, l2, + bChilds = this.htmlElement.childNodes, + i = 0, + l = bChilds.length + for (; i < l; i++) { + tag = bChilds[i].tagName; + if (tag && (tag = tag.toLowerCase())) { + if (tag == "div") { + dChilds = bChilds[i].childNodes; + + for (j = 0, l2 = dChilds.length; j < l2; j++) { + if (dChilds[j].tagName && dChilds[j].tagName.toLowerCase() == "img") { + this.imageContainer = bChilds[i]; + this.image = dChilds[j]; + } + } + } + else if (tag == "blockquote") { + this.caption = bChilds[i]; + } + } + } + + if (!this.other.type) { + apf.setStyleClass(this.htmlElement, "empty"); + this.image.style.display = "none"; + } + else { + if (this.other.picture == null) { + this.image.style.display = "none"; + } + else { + this.image.style.display = "block"; + this.image.src = this.other.picture; + this.image.onload = function() { + _self.changeRotation(_self.other.rotation, + _self.other.fliph, _self.other.flipv, true); + } + } + } + /* Set last scale ratio */ + this.other.ratio = this.other.dwidth / this.other.dheight; + + this.changeRotation(_self.other.rotation, + _self.other.fliph, _self.other.flipv, true); + this.setCaption(this.other.caption); + this.setLock(this.other.lock, true) + + this.updateOutputs(); + }; + + this.updateOutputs = function() { + var id, input, pos, _x, _y, + inp = this.other.inputList; + + if (this.other.lock) + return; + + for (id in inp) { + input = this.htmlOutputs[id] + ? this.htmlOutputs[id] + : new apf.flow.input(this, id); + + if (!this.htmlOutputs[id]) + this.htmlOutputs[id] = input; + pos = this.updateInputPos(inp[id]); + + _x = pos[0] - (pos[2] == "left" || pos[2] == "right" + ? Math.ceil(parseInt(apf.getStyle(input.htmlElement, "width"))/2) + : Math.ceil(apf.flow.sSize/2)); + _y = pos[1] - (pos[2] == "top" || pos[2] == "bottom" + ? Math.ceil(parseInt(apf.getStyle(input.htmlElement, "height"))/2) + : Math.ceil(apf.flow.sSize/2)); + + input.lastUpdate = pos; + input.moveTo(_x, _y); + } + }; + + this.outputsVisibility = function(visible) { + var inp = this.htmlOutputs; + for (var id in inp) { + var input = inp[id]; + if (visible) + input.show(); + else + input.hide(); + } + }; + + /** + * Immobilise block element on workarea + * + * @param {Number} lock prohibit block move, default is false + * Possible values: + * true block is locked + * false block is unlocked + */ + this.setLock = function(lock, init) { + if (this.other.lock !== lock || init) { + this.draggable = !lock; + this.other.lock = lock; + this.outputsVisibility(!lock); + + if (lock) + apf.setStyleClass(this.htmlElement, "locked"); + else + apf.setStyleClass(this.htmlElement, "", ["locked"]); + } + }; + + /** + * Sets new description which is placed under block element + * + * @param {String} caption block description + */ + this.setCaption = function(caption) { + var c = this.caption; + if (!c || c.nodeType != 1) return; + c.innerHTML = caption; + if (this.other.capPos == "inside") { + if (c.offsetWidth !== 0 && c.offsetHeight !== 0) { + c.style.marginLeft = + "-" + (Math.ceil(c.offsetWidth / 2)) + "px"; + c.style.marginTop = + "-" + (Math.ceil(c.offsetHeight / 2)) + "px"; + } + } + }; + + /** + * Moves block to new x, y position + * + * @param {Number} top vertical coordinate + * @param {Number} left horizontal coordinate + */ + this.moveTo = function(top, left) { + var t = parseInt(this.htmlElement.style.top), + l = parseInt(this.htmlElement.style.left); + + if (t !== top || l !== left) { + this.htmlElement.style.top = top + "px"; + this.htmlElement.style.left = left + "px"; + + //var st = this.canvas.getScrollTop(); + //var sl = this.canvas.getScrollLeft(); + + if (this.canvas.lastTop < top || top > this.canvas.getHeight() - 100) { + this.canvas.moveTopScrollPointer(top); + this.canvas.scrollTop(); + } + if (this.canvas.lastLeft < left && left > this.canvas.getWidth() - 100) { + this.canvas.moveLeftScrollPointer(left); + this.canvas.scrollLeft(); + } + } + } + + /** + * Resize block element + * + * @param {Number} width new vertical block size + * @param {Number} height new horizontal block size + */ + this.resize = function(width, height) { + var w = parseInt(this.htmlElement.style.width), + h = parseInt(this.htmlElement.style.height); + + if (w !== width || h !== height) { + this.htmlElement.style.width = this.imageContainer.style.width + = width + "px"; + this.htmlElement.style.height = this.imageContainer.style.height + = height + "px"; + this.image.style.height = height + "px"; + this.image.style.width = width + "px"; + this.image.style.filter = ""; + } + } + + /** + * Set rotation and flip and call to redraw image function. + * + * @param {Number} rotation the rotation in degrees clockwise, background image is rotated automaticly, Default is 0. + * Possible values: + * 0 0 degrees rotation + * 90 90 degrees rotation + * 180 180 degrees rotation + * 270 270 degrees rotation + * @param {Boolean} fliph whether to mirror the block over the vertical axis, background image is flipped automaticly. Default is false. + * Possible values: + * true block element is flipped + * false block element is not flipped + * @param {Boolean} flipv whether to mirror the block over the horizontal axis, background image is flipped automaticly. Default is false. + * Possible values: + * true block element is flipped + * false block element is not flipped + * @param {Boolean} init + */ + this.changeRotation = function(rotation, fliph, flipv, init) { + var o = this.other, prev = [o.rotation, o.fliph, o.flipv]; + if (!o.type) + return; + + o.rotation = parseInt(rotation) % 360 || 0; + o.fliph = String(fliph) == "true" ? true : false; + o.flipv = String(flipv) == "true" ? true : false; + + var flip = (o.fliph && !o.flipv + ? "horizontal" + : (!o.fliph && o.flipv + ? "vertical" + : "none")); + + //if (init || prev[0] != o.rotation || prev[1] != o.fliph || prev[2] != o.flipv) { + this.repaintImage(flip, o.rotation, 'rel'); + //} + }; + + /** + * Function repaints default block's image with new rotation and flip. + * + * @param {String} flip whether to mirror the image over the vertical or horizontal axis + * Possible values: + * none image is not flipped + * horizontal image is flipped horizontal + * vertical image is flipped vertical + * @param {Number} angle degrees angle + * Possible values: + * 0 0 degrees angle + * 90 90 degrees angle + * 180 180 degrees angle + * 270 270 degrees amgle + * @param {String} whence + */ + this.repaintImage = function(flip, angle, whence) { + var p = this.image; + if (p.style.display == "none") + return; + p.style.display = "block"; + + p.angle = !whence + ? ((p.angle == undefined ? 0 : p.angle) + angle) % 360 + : angle; + + var canvas, + rotation = Math.PI *(p.angle >= 0 ? p.angle : 360 + p.angle) / 180, + costheta = Math.cos(rotation), + sintheta = Math.sin(rotation); + + if (document.all && !window.opera) { + canvas = document.createElement('img'); + canvas.src = p.src; + canvas.style.height = p.height + "px"; + canvas.style.width = p.width + "px"; + + canvas.style.filter = "progid:DXImageTransform.Microsoft.Matrix(M11=" + + costheta + ",M12=" + (-sintheta) + + ",M21=" + sintheta + + ",M22=" + costheta + + ",SizingMethod='auto expand')"; + + if (flip !== "none") { + canvas.style.filter += "progid:DXImageTransform.Microsoft.BasicImage(" + +(flip == "horizontal" + ? "mirror=1" + : "rotation=2, mirror=1") + +")"; + } + } + else { + canvas = document.createElement('canvas'); + if (!p.oImage) { + canvas.oImage = new Image(); + canvas.oImage.src = p.src; + } + else { + canvas.oImage = p.oImage; + } + + canvas.style.width = canvas.width + = Math.abs(costheta * canvas.oImage.width) + + Math.abs(sintheta * canvas.oImage.height); + canvas.style.height = canvas.height + = Math.abs(costheta * canvas.oImage.height) + + Math.abs(sintheta * canvas.oImage.width); + + var context = canvas.getContext('2d'); + context.save(); + + switch (flip) { + case "vertical": + context.translate(0, canvas.oImage.height); + context.scale(1, -1); + break; + case "horizontal": + context.translate(canvas.oImage.height, 0); + context.scale(-1, 1); + break; + } + + if (rotation <= Math.PI / 2) + context.translate(sintheta * canvas.oImage.height, 0); + else if (rotation <= Math.PI) + context.translate(canvas.width, -costheta * canvas.oImage.height); + else if (rotation <= 1.5 * Math.PI) + context.translate(-costheta * canvas.oImage.width, canvas.height); + else + context.translate(0, -sintheta * canvas.oImage.width); + context.rotate(rotation); + + try { + context.drawImage(canvas.oImage, 0, 0, canvas.oImage.width, + canvas.oImage.height); + context.restore(); + } + catch (e) {} + } + canvas.angle = p.angle; + this.imageContainer.replaceChild(canvas, p); + this.image = canvas; + }; + + /** + * When Block change his position notify other elements about that fact + * (actualy notify only connections, but it's not important what type + * element have. Notified object must have onMove function). + */ + this.onMove = function() { + for (var i = 0, ml = this.moveListeners, l = ml.length; i < l; i++) + ml[i].onMove(); + }; + + /** + * Calculates the new input position if block is resized, flipped or rotated. + * Base on default informations about block element from template. + * + * @param {Object} input object representation of input element + * Properties: + * {Number} x x position in pixels based on Block's dimensions + * {Number} y y position in pixels based on Block's dimensions + * {String} position input orientation + * Possible values: + * top input is placed on top edge of block + * right input is placed on right edge of block + * bottom input is placed on bottom edge of block + * left input is placed on left edge of block + * @param {Object} [dPos] destination block position + * Properties: + * dPos[0] destination block horizontal coordinate + * dPos[1] destination block vertical coordinate + * dPos[2] horizontal size of destination block element + * dPos[3] vertical size of destination block element + * @return {Object} new input position + */ + this.updateInputPos = function(input, dPos) { + var b = this.htmlElement, + o = this.other, + w = parseInt(b.style.width), + h = parseInt(b.style.height), + ior = input ? input.position : "auto", + x = input ? input.x : w / 2, + y = input ? input.y : h / 2, + dw = o.dwidth, + dh = o.dheight, + fv = o.flipv, + fh = o.fliph, + r = o.rotation, + positions = {0 : "top", 1 : "right", 2 : "bottom", 3 : "left", + "top" : 0, "right" : 1, "bottom" : 2, "left" : 3}, + sSize = apf.flow.sSize, + hSize = Math.floor(apf.flow.sSize / 2); + + /* Changing input floating */ + ior = (ior == "auto") + ? "auto" + : positions[(positions[ior] + parseInt(r) / 90)%4]; + + if (ior !== "auto") { + if (fv) + ior = ior == "top" ? "bottom" : (ior == "bottom" ? "top" : ior); + if (fh) + ior = ior == "left" ? "right" : (ior == "right" ? "left" : ior); + + /* If block is resized, block keep proportion */ + x = (r == 90 || r == 270) ? x*h / dh : x*w / dw; + y = (r == 90 || r == 270) ? y*w / dw : y*h / dh; + + /* If rotate, change inputs coordinates */ + var _x = x, _y = y; + + _x = (r == 90) ? w - y - 1 : (r == 180 ? w - x - 1 : (r == 270 ? y : x)); + _y = (r == 90) ? x : (r == 180 ? h - y - 1 : (r == 270 ? h - x - 1 : y)); + + /* Flip Vertical and Horizontal */ + _x = fh ? w - _x : _x; + _y = fv ? h - _y : _y; + + _x = fh ? (ior == "top" || ior == "bottom" ? _x - 1 : _x) : _x + _y = fv ? (ior == "left" || ior == "right" ? _y - 1 : _y) : _y + + _x = ior == "top" || ior == "bottom" + ? _x - (sSize/2) + (apf.isIE || apf.isOpera || apf.isChrome ? 1 : 0) + : _x; + _y = ior == "left" || ior == "right" + ? _y - (sSize/2) + (apf.isIE || apf.isOpera || apf.isChrome ? 1 : 0) + : _y; + } + else { + var st = parseInt(b.style.top), + sl = parseInt(b.style.left), + dt = dPos[1], dl = dPos[0]; + dw = dPos[2], dh = dPos[3]; + + if (st + h * 1.5 < dt) { + ior = "bottom"; + } + else if (st > dt + dh * 1.5) { + ior = "top"; + } + else { + if (sl > dl + dw / 2) + ior = "left"; + else if (sl < dl) + ior = "right"; + else + ior = "left"; + } + + _x = (ior == "top" || ior == "bottom") + ? w/2 - hSize + : ior == "right" ? w : 0; + _y = (ior == "left" || ior == "right") + ? h/2 - hSize + : ior == "bottom" ? h : 0; + } + return [_x, _y, ior]; + }; + + this.htmlElement.onmouseup = function(e) { + if (!_self.other.type && _self.canvas.mode == "connection-add") + apf.flow.connectionsManager.addBlock(_self, 0); + }; +}; + +/** + * Creates object representation for input elements. Each block can have + * unlimited inputs. + * + * @param {Object} objBlock object representation of block element + * @param {Number} number unique input number for block element + * @constructor + */ +apf.flow.input = function(objBlock, number) { + this.objBlock = objBlock; + this.htmlElement = objBlock.htmlElement.appendChild(document.createElement("div")); + this.number = number; + this.lastUpdate = null; + + var _self = this; + + apf.setStyleClass(this.htmlElement, "input"); + + /** + * Hides inpiut + */ + this.hide = function() { + this.htmlElement.style.display = "none"; + }; + + /** + * Shows input + */ + this.show = function() { + this.htmlElement.style.display = "block"; + }; + + /** + * Moves input to new position + * + * @param {Number} x new horizontal position + * @param {Number} y new vertical position + */ + this.moveTo = function(x, y) { + this.htmlElement.style.left = x + "px"; + this.htmlElement.style.top = y + "px"; + }; + + var connection; + var vMB; + this.htmlElement.onmousedown = function(e) { + e = (e || event); + e.cancelBubble = true; + apf.flow.isMoved = true; + + var canvas = _self.objBlock.canvas, + pn = _self.htmlElement.parentNode, + mode = canvas.mode; + + if (e.preventDefault) + e.preventDefault(); + + vMB = new apf.flow.virtualMouseBlock(canvas, e); + + var con = apf.flow.findConnector(_self.objBlock, _self.number); + if (con) { + var source = con.source + ? con.connector.objDestination + : con.connector.objSource, + destination = con.source + ? con.connector.objSource + : con.connector.objDestination, + sourceInput = con.source + ? con.connector.other.input + : con.connector.other.output, + destinationInput = con.source + ? con.connector.other.output + : con.connector.other.input; + /* Temporary connection must keeping output direction */ + vMB.other.inputList[1].position = destination.updateInputPos( + destination.other.inputList[destinationInput])[2]; + + _self.objBlock.onremoveconnection([con.connector.other.xmlNode]); + apf.flow.removeConnector(con.connector.htmlElement); + + connection = new apf.flow.addConnector(canvas , source, vMB, { + output : sourceInput, input : 1 + }); + apf.flow.connectionsManager.addBlock(source, sourceInput); + canvas.setMode("connection-change"); + } + else { + connection = new apf.flow.addConnector(canvas, _self.objBlock, vMB, { + output : _self.number + }); + apf.flow.connectionsManager.addBlock(_self.objBlock, _self.number); + canvas.setMode("connection-add"); + } + connection.newConnector.virtualSegment = true; + vMB.onMove(e); + + document.body.onmousemove = function(e) { + e = (e || event); + if (vMB) + vMB.onMove(e); + }; + + document.body.onmouseup = function(e) { + e = (e || event); + var t = e.target || e.srcElement; + document.body.onmousemove = null; + apf.flow.isMoved = false; + + if (t && canvas.mode == "connection-change") { + if ((t.className || "").indexOf("input") == -1) + apf.flow.connectionsManager.addBlock(destination, destinationInput); + } + apf.flow.connectionsManager.clear(); + + if (connection) + apf.flow.removeConnector(connection.newConnector.htmlElement); + if (vMB) { + vMB.onMove(e); + vMB.destroy(); + vMB = null; + _self.objBlock.canvas.setMode("normal"); + } + }; + }; + + this.htmlElement.onmouseup = function(e) { + apf.flow.connectionsManager.addBlock(_self.objBlock, _self.number); + }; + + this.htmlElement.onmouseover = function(e) { + var mode = _self.objBlock.canvas.mode; + if (mode == "connection-add" || mode == "connection-change") { + apf.setStyleClass(_self.htmlElement, "inputHover"); + } + }; + + this.htmlElement.onmouseout = function(e) { + apf.setStyleClass(_self.htmlElement, "", ["inputHover"]); + }; +}; + +/** + * Manage informations about clicked blocks and/or inputs. If mode + * connection-add is active and if two blocks or inputs has clicked, new + * connection will be created. + * @constructor + */ +apf.flow.connectionsManager = new (function() { + this.addBlock = function(objBlock, inputNumber) { + if (objBlock && (inputNumber || inputNumber == 0)) { + var s = apf.flow.connectionsTemp; + + if (!s) { + apf.flow.connectionsTemp = { + objBlock : objBlock, + inputNumber : inputNumber + }; + } + else { + if (s.objBlock.id !== objBlock.id || s.inputNumber !== inputNumber) { + objBlock.oncreateconnection(s.objBlock.other.xmlNode, + s.inputNumber, objBlock.other.xmlNode, inputNumber); + objBlock.canvas.setMode("normal"); + } + this.clear(); + } + } + }; + + this.clear = function() { + apf.flow.connectionsTemp = null; + }; +})(); + +/** + * Simulate block element to create temporary connection between source + * block and mouse cursor, until destination block is not clicked. + * + * @param {Object} canvas object representation of canvas element + * @constructor + */ +apf.flow.virtualMouseBlock = function(canvas) { + var hook = [0, 0, "virtual"]; + this.canvas = canvas; + this.htmlElement = document.createElement('div'); + + this.canvas.htmlElement.appendChild(this.htmlElement); + + this.htmlElement.style.display = "block"; + this.moveListeners = new Array(); + this.draggable = 0; + this.htmlOutputs = {}; + this.htmlOutputs[1] = { + htmlElement : this.htmlElement.appendChild(document.createElement("div")), + number : 1, + lastUpdate : hook + }; + this.other = {}; + this.other.inputList = {}; + this.other.inputList[1] = {x : hook[0], y : hook[1], position : hook[2]}; + + apf.setStyleClass(this.htmlElement, "vMB"); + + var sPos = apf.getAbsolutePosition(this.canvas.htmlElement); + + this.onMove = function(e) { + //@todo apf3.x see why this is twice (2 * this.canvas.getWindowScrollLeft() - for Top either) + this.htmlElement.style.left = (e.clientX + 2 + this.canvas.getWindowScrollLeft() + + this.canvas.getScrollLeft() - sPos[0]) + "px"; + this.htmlElement.style.top = (e.clientY + 2 + this.canvas.getWindowScrollTop() + + this.canvas.getScrollTop() - sPos[1]) + "px"; + + for (var i = 0, l = this.moveListeners.length; i < l; i++) + this.moveListeners[i].onMove(); + }; + + this.destroy = function() { + this.htmlElement.parentNode.removeChild(this.htmlElement); + }; + + this.updateInputPos = function(input) { + return hook; + }; +}; + + +/** + * Creates connection between two block elements. To remove connection is needed + * select it by mouse and press delete button. + * + * @param {HTMLElement} htmlElement html representation of connector element + * @param {Object} objCanvas object representation of connector element + * @param {Object} objSource object representation of source block element + * @param {Object} objDestination object representation of destination block element + * @param {Object} other connection properties + * Properties: + * {Number} output source block input number + * {Number} input destination block input number + * {XMLElement} xmlNode xml representation of connection element + * @constructor + */ +apf.flow.connector = function(htmlElement, objCanvas, objSource, objDestination, other) { + /** + * Connection segments + */ + this.htmlSegments = []; + + /** + * Array used when connection is repainting + */ + var htmlSegmentsTemp = []; + + /** + * Connection label - text defined by user in the middle of connection + */ + this.htmlLabel = null; + + /** + * Connector-start object, it could be an arrow + */ + this.htmlStart = null; + + /** + * Connector-end object, it could be an arrow + */ + this.htmlEnd = null; + + /** + * Object of source block + */ + this.objSource = objSource; + + /** + * Object of destination block + */ + this.objDestination = objDestination; + this.other = other; + + this.selected = false; + + this.htmlElement = htmlElement; + this.virtualSegment = null; + + var sSize = apf.flow.sSize, //Segment size + fsSize = apf.flow.fsSize, //First segment size + hSize = Math.floor(sSize / 2), + + sourceHtml = this.objSource.htmlElement, + destinationHtml = this.objDestination.htmlElement, + + _self = this; + + this.initConnector = function() { + if (!htmlElement.getAttribute("id")) + apf.setUniqueHtmlId(htmlElement); + objCanvas.htmlConnectors[htmlElement.getAttribute("id")] = this; + + this.objSource.moveListeners.push(this); + this.objDestination.moveListeners.push(this); + this.activateInputs(); + this.onMove(); + }; + + this.activateInputs = function() { + this.i1 = other.output && this.objSource.other.inputList[other.output] + ? this.objSource.other.inputList[other.output] + : {x : 0, y : 0, position : "auto"}; + + this.i2 = other.input && this.objDestination.other.inputList[other.input] + ? this.objDestination.other.inputList[other.input] + : {x : 0, y : 0, position : "auto"}; + }; + + this.destroy = function() { + this.deselectInputs("Selected"); + var sl = this.objSource.moveListeners, + i = 0, + l = sl.length; + for (; i < l; i++) { + if (sl[i] == this) + this.objSource.moveListeners.removeIndex(i); + } + + var dl = this.objDestination.moveListeners; + for (i = 0, l = dl.length; i < l; i++) { + if (dl[i] == this) + this.objDestination.moveListeners.removeIndex(i); + } + objCanvas.removeConnector(this.htmlElement.getAttribute("id")); + }; + + this.onMove = function() { + this.draw(); + if (this.selected) { + this.deselectInputs("Hover"); + this.deselect("selected"); + this.deselectInputs("Selected"); + this.selected = false; + } + }; + + this.draw = function() { + var sIPos, dIPos, + l = [], + s = [parseInt(sourceHtml.style.left), + parseInt(sourceHtml.style.top), + parseInt(sourceHtml.style.width), + parseInt(sourceHtml.style.height)], + d = [parseInt(destinationHtml.style.left), + parseInt(destinationHtml.style.top), + parseInt(destinationHtml.style.width), + parseInt(destinationHtml.style.height)]; + + htmlSegmentsTemp = this.htmlSegments; + /*for (var i = 0, l = this.htmlSegments.length; i < l; i++) { + htmlSegmentsTemp.push(this.htmlSegments[i]); + }*/ + this.htmlSegments = []; + + if (this.i1.position == "auto" || this.i2.position == "auto") { + sIPos = this.objSource.updateInputPos(this.i1, d); + dIPos = this.objDestination.updateInputPos(this.i2, s); + } + else { + sIPos = this.objSource.htmlOutputs[other.output].lastUpdate; + dIPos = this.objDestination.htmlOutputs[other.input].lastUpdate; + } + + var sO = sIPos[2]; + var dO = dIPos[2]; + + s[0] += sIPos[0]; + s[1] += sIPos[1]; + + d[0] += dIPos[0]; + d[1] += dIPos[1]; + + if (sO !== "virtual") + s = this.createSegment(s, [fsSize, sO], true); + + if (dO !== "virtual") + d = this.createSegment(d, [fsSize, dO], true); + + l = s; + var position = s[0] > d[0] + ? (s[1] > d[1] ? "TL" : (s[1] < d[1] ? "BL" : "ML")) + : (s[0] < d[0] + ? (s[1] > d[1] ? "TR" : (s[1] < d[1] ? "BR" : "MR")) + : (s[1] > d[1] ? "TM" : (s[1] < d[1] ? "MM" : "BM"))), + + condition = position + + (sO == "left" ? 1 : (sO == "right" ? 2 : sO == "top" ? 4 : 8)) + + (dO == "left" ? 1 : (dO == "right" ? 2 : dO == "top" ? 4 : 8)); + //rot.setValue(condition) + + switch (condition) { + case "TR41": + l = this.createSegment(l, [apf.isGecko + ? s[1] - d[1] + : Math.ceil(s[1] - d[1]), "top"]); + l = this.createSegment(l, [apf.isGecko + ? d[0] - s[0] + : Math.ceil(d[0] - s[0]), "right"]); + break; + case "TR44": + case "TR14": + case "TR11": + l = this.createSegment(l, [apf.isGecko + ? s[1] - d[1] + : Math.ceil(s[1] - d[1]), "top"]); + l = this.createSegment(l, [apf.isGecko + ? d[0] - s[0] + : Math.floor(d[0] - s[0]), "right"]); + break; + case "BR22": + case "BR24": + case "BR42": + case "BR44": + l = this.createSegment(l, [apf.isGecko + ? d[0] - s[0] + : Math.floor(d[0] - s[0]), "right"]); + l = this.createSegment(l, [Math.ceil(Math.abs(d[1] - s[1])), "bottom"]); + break; + case "BR41": + l = this.createSegment(l, [apf.isGecko + ? (d[0] - s[0]) / 2 + : (d[0] - s[0]) / 2, "right"]); + l = this.createSegment(l, [d[1] - s[1], "bottom"]); + l = this.createSegment(l, [apf.isGecko + ? (d[0] - s[0]) / 2 + : Math.ceil((d[0] - s[0]) / 2), "right"]); + break; + case "BR48": + case "BR28": + l = this.createSegment(l, [apf.isGecko + ? (d[0] - s[0]) / 2 + : (d[0] - s[0]) / 2, "right"]); + l = this.createSegment(l, [apf.isGecko + ? d[1] - s[1] : Math.ceil(d[1] - s[1]), "bottom"]); + l = this.createSegment(l, [apf.isGecko + ? (d[0] - s[0]) / 2 + : (d[0] - s[0]) / 2, "right"]); + break; + case "BR21": + l = this.createSegment(l, [apf.isGecko + ? (d[0] - s[0]) / 2 + : (d[0] - s[0]) / 2, "right"]); + l = this.createSegment(l, [apf.isGecko + ? d[1] - s[1] + : Math.ceil(d[1] - s[1]), "bottom"]); + l = this.createSegment(l, [parseInt((d[0] - s[0]) / 2)+1, "right"]); + break; + case "TL44": + case "TL42": + case "TL24": + case "TL22": + l = this.createSegment(l, [apf.isGecko + ? s[1] - d[1] + : Math.ceil(s[1] - d[1]), "top"]); + l = this.createSegment(l, [apf.isGecko + ? s[0] - d[0] + : Math.ceil(s[0] - d[0]), "left"]); + break; + case "TR21": + case "TR24": + case "TR81": + case "TR84": + case "TR21": + case "TR24": + case "TR81": + case "TR84": + l = this.createSegment(l, [apf.isGecko + ? (d[0] - s[0]) / 2 + : (d[0] - s[0]) / 2, "right"]); + l = this.createSegment(l, [apf.isGecko + ? s[1] - d[1] + : Math.ceil(s[1] - d[1]), "top"]); + l = this.createSegment(l, [apf.isGecko + ? (d[0] - s[0]) / 2 + : (d[0] - s[0]) / 2, "right"]); + break; + case "BR18": + case "BR88": + case "BR81": + case "BR11": + l = this.createSegment(l, [d[1] - s[1], "bottom"]); + l = this.createSegment(l, [apf.isGecko + ? d[0] - s[0] + : Math.ceil(d[0] - s[0]), "right"]); + break; + case "BR14": + l = this.createSegment(l, [apf.isGecko + ? (d[1] - s[1]) / 2 + : Math.ceil((d[1] - s[1]) / 2) , "bottom"]); + l = this.createSegment(l, [apf.isGecko + ? d[0] - s[0] + : Math.floor(d[0] - s[0]), "right"]); + l = this.createSegment(l, [Math.ceil((d[1] - s[1]) / 2), "bottom"]); + break; + case "BR84": + case "BR82": + case "BR12": + l = this.createSegment(l, [apf.isGecko + ? (d[1] - s[1]) / 2 + : Math.ceil((d[1] - s[1]) / 2) , "bottom"]); + l = this.createSegment(l, [apf.isGecko + ? d[0] - s[0] + : Math.floor(d[0] - s[0]), "right"]); + l = this.createSegment(l, [(d[1] - s[1]) / 2, "bottom"]); + break; + case "BL84": + case "BL24": + case "BL21": + l = this.createSegment(l, [apf.isGecko + ? (d[1] - s[1]) / 2 + : Math.ceil((d[1] - s[1]) / 2), "bottom"]); + l = this.createSegment(l, [apf.isGecko + ? s[0] - d[0] + : Math.ceil(s[0] - d[0]), "left"]); + l = this.createSegment(l, [apf.isGecko + ? (d[1] - s[1]) / 2 + : Math.ceil((d[1] - s[1]) / 2), "bottom"]); + break; + case "BL11": + case "BL14": + case "BL41": + case "BL44": + case "BL81": + l = this.createSegment(l, [apf.isGecko + ? s[0] - d[0] + : Math.ceil(s[0] - d[0]), "left"]); + l = this.createSegment(l, [apf.isGecko + ? d[1] - s[1] + : Math.ceil(d[1] - s[1]), "bottom"]); + break; + case "BL12": + case "BL18": + case "BL42": + case "BL48": + l = this.createSegment(l, [apf.isGecko + ? (s[0] - d[0]) / 2 + : (s[0] - d[0]) / 2, "left"]); + l = this.createSegment(l, [apf.isGecko + ? d[1] - s[1] + : Math.ceil(d[1] - s[1]), "bottom"]); + l = this.createSegment(l, [apf.isGecko + ? (s[0] - d[0]) / 2 + : (s[0] - d[0]) / 2, "left"]); + break; + case "BL88": + case "BL82": + case "BL28": + case "BL22": + l = this.createSegment(l, [apf.isGecko + ? d[1] - s[1] + : Math.ceil(d[1] - s[1]), "bottom"]); + l = this.createSegment(l, [apf.isGecko + ? s[0] - d[0] + : Math.ceil(s[0] - d[0]), "left"]); + break; + case "TL88": + case "TL81": + case "TL18": + case "TL11": + l = this.createSegment(l, [apf.isGecko + ? s[0] - d[0] + : Math.ceil(s[0] - d[0]), "left"]); + l = this.createSegment(l, [apf.isGecko + ? s[1] - d[1] + : Math.ceil(s[1] - d[1]), "top"]); + break; + case "TL41": + l = this.createSegment(l, [apf.isGecko + ? (s[1] - d[1]) / 2 + : Math.floor((s[1] - d[1]) / 2), "top"]); + l = this.createSegment(l, [apf.isGecko + ? s[0] - d[0] : Math.ceil(s[0] - d[0]), "left"]); + l = this.createSegment(l, [apf.isGecko + ? (s[1] - d[1]) / 2 + : Math.ceil((s[1] - d[1]) / 2), "top"]); + break; + case "TL48": + case "TL28": + case "TL21": + l = this.createSegment(l, [apf.isGecko + ? (s[1] - d[1]) / 2 + : Math.floor((s[1] - d[1]) / 2), "top"]); + l = this.createSegment(l, [apf.isGecko + ? s[0] - d[0] : Math.ceil(s[0] - d[0]), "left"]); + l = this.createSegment(l, [apf.isGecko + ? (s[1] - d[1]) / 2 + : Math.floor((s[1] - d[1]) / 2), "top"]); + break; + case "TL12": + case "TL14": + case "TL82": + case "TL84": + l = this.createSegment(l, [apf.isGecko + ? (s[0] - d[0]) / 2 + : (s[0] - d[0]) / 2, "left"]); + l = this.createSegment(l, [apf.isGecko + ? s[1] - d[1] + : Math.ceil(s[1] - d[1]), "top"]); + l = this.createSegment(l, [apf.isGecko + ? (s[0] - d[0]) / 2 + : (s[0] - d[0]) / 2, "left"]); + break; + case "TR12": + case "TR18": + case "TR42": + case "TR48": + l = this.createSegment(l, [apf.isGecko + ? (s[1] - d[1]) / 2 + : Math.floor((s[1] - d[1]) / 2), "top"]); + l = this.createSegment(l, [apf.isGecko + ? d[0] - s[0] + : Math.floor(d[0] - s[0]), "right"]); + l = this.createSegment(l, [apf.isGecko + ? (s[1] - d[1]) / 2 + : Math.floor((s[1] - d[1]) / 2), "top"]); + break; + case "TR22": + case "TR28": + case "TR82": + case "TR88": + l = this.createSegment(l, [apf.isGecko + ? d[0] - s[0] + : Math.floor(d[0] - s[0]), "right"]); + l = this.createSegment(l, [apf.isGecko + ? s[1] - d[1] + : Math.ceil(s[1] - d[1]), "top"]); + break; + default: + switch (position) { + case "ML": + l = this.createSegment(l, [apf.isGecko + ? s[0] - d[0] + : Math.ceil(s[0] - d[0]), "left"]); + break; + case "MM": + l = this.createSegment(l, [apf.isGecko + ? s[0] - d[0] + : Math.ceil(s[0] - d[0]), "left"]); + l = this.createSegment(l, [apf.isGecko + ? d[1] - s[1] + : Math.ceil(d[1] - s[1]), "bottom"]); + break; + case "TM": + l = this.createSegment(l, [apf.isGecko + ? s[1] - d[1] + : Math.ceil(s[1] - d[1]), "top"]); + l = this.createSegment(l, [apf.isGecko + ? s[0] - d[0] + : Math.ceil(s[0] - d[0]), "left"]); + break; + case "MR": + // This part is not checked, MR41 needs only "right" + // line, else need them both + l = this.createSegment(l, [apf.isGecko + ? d[0] - s[0] + : Math.floor(d[0] - s[0]), "right"]); + if (condition.substring(2,4) == "41") + break; + l = this.createSegment(l, [Math.abs(d[1] - s[1]), + "bottom"]); + break; + } + break; + } + + for (var i = htmlSegmentsTemp.length - 1; i >= 0; i--) + htmlSegmentsTemp[i][0].style.display = "none"; + + if (this.other.label) + this.htmlLabel = apf.flow.label(this); + + if (this.other.type) { + var _type = this.other.type.split("-"); + + if (_type[0] !== "none") + this.htmlStart = apf.flow.connectorsEnds(this, "start", _type[0]); + if (_type[1] !== "none") + this.htmlEnd = apf.flow.connectorsEnds(this, "end", _type[1]); + } + }; + + this.createSegment = function(coor, lines, startSeg) { + var or = lines[1], + l = lines[0], + sX = coor[0] || 0, + sY = coor[1] || 0, + _temp = htmlSegmentsTemp.shift(), + _self = this, + segment = _temp ? _temp[0] : null, + plane = (or == "top" || or == "bottom") ? "ver" : "hor"; + + if (!segment) { + segment = htmlElement.appendChild(document.createElement("div")); + + apf.setUniqueHtmlId(segment); + apf.setStyleClass(segment, "segment"); + + if (_self.selected) + _self.select("selected"); + + var canvas = this.objSource.canvas; + /* Segment events */ + segment.onmouseover = function(e) { + if (!apf.flow.isMoved && ((canvas.mode == "connection-change" + && _self.selected) || canvas.mode == "connection-add")) { + _self.select("hover"); + } + }; + + segment.onmouseout = function(e) { + _self.deselect("hover"); + }; + + segment.onmousedown = function(e) { + e = e || event; + e.cancelBubble = true; + _self.deselect("selected"); + _self.select("clicked"); + }; + + segment.onmouseup = function(e) { + e = (e || event); + e.cancelBubble = true; + var ctrlKey = e.ctrlKey, + temp = _self.selected; + + if (!ctrlKey) + _self.objSource.canvas.deselectConnections(); + + _self.selected = temp ? false : _self.selected ? false : true; + + if (_self.selected) { + _self.selectInputs("Selected"); + _self.deselect("clicked"); + _self.select("selected"); + canvas.setMode("connection-change"); + } + else { + _self.deselectInputs("Selected"); + _self.deselect("clicked"); + _self.deselect("selected"); + canvas.setMode("normal"); + } + }; + } + + segment.plane = plane; + + var w = plane == "ver" ? sSize : l, + h = plane == "ver" ? l : sSize, + className = "segment "+"seg_" + plane; + + if (_self.virtualSegment) + className += " seg_"+plane+"_virtual"; + + segment.className = className; + + if (or == "top") + sY -= l; + if (or == "left") + sX -=l; + + segment.style.display = "block"; + segment.style.left = sX + + (plane == "hor" && !startSeg || (or =="left" && startSeg) ? 3 : 0) + + "px"; + segment.style.top = sY + (plane == "ver" ? 3 : 0) + "px"; + segment.style.width = w + (or =="left" && startSeg ? -3 : (or == "right" && startSeg ? 3 : 0)) + "px"; + segment.style.height = h + "px"; + + /* Define the connection end point */ + if (or == "bottom") + sY += h; + if (or == "right") + sX += w; + + this.htmlSegments.push([segment, or]); + + return [sX, sY]; + }; + + this.deselect = function(type) { + var segments = this.htmlElement.childNodes, + i = 0, + l = segments.length; + + for (; i < l; i++) { + if ((segments[i].className || "").indexOf("segment") != -1) { + apf.setStyleClass(segments[i], "", + ["seg_" + segments[i].plane + "_" + type]); + } + } + if (!this.selected) + this.deselectInputs("Selected"); + + if (this.htmlLabel && type == "selected") + this.htmlLabel.className = "label"; + }; + + this.select = function(type) { + var segments = this.htmlElement.childNodes, + i = 0, + l = segments.length; + + for (; i < l; i++) { + if ((segments[i].className || "").indexOf("segment") != -1) { + apf.setStyleClass(segments[i], + "seg_" + segments[i].plane + "_" + type); + } + } + this.selectInputs(); + if (this.htmlLabel && type == "selected") + this.htmlLabel.className = "label labelSelected"; + }; + + this.selectInputs = function(type) { + if (this.other.output && this.objSource.htmlOutputs[other.output]) { + var output = this.objSource.htmlOutputs[other.output].htmlElement; + apf.setStyleClass(output, "input" + type); + } + if (this.other.input && this.objDestination.htmlOutputs[other.input]) { + var input = this.objDestination.htmlOutputs[other.input].htmlElement; + apf.setStyleClass(input, "input" + type); + } + }; + + this.deselectInputs = function(type) { + if (this.other.output && this.objSource.htmlOutputs[this.other.output]) { + var output = this.objSource.htmlOutputs[this.other.output].htmlElement; + apf.setStyleClass(output, "", ["input" + type]); + } + if (this.other.input && this.objDestination.htmlOutputs[this.other.input]) { + var input = this.objDestination.htmlOutputs[this.other.input].htmlElement; + apf.setStyleClass(input, "", ["input" + type]); + } + }; +}; + +apf.flow.connectorsEnds = function(connector, place, type) { + var conEnd = (place == "start") ? connector.htmlStart : connector.htmlEnd, + segment = connector.htmlSegments[place == "start" ? 0 : 1], + + l = parseInt(segment[0].style.left), + t = parseInt(segment[0].style.top), + + htmlElement = conEnd ? conEnd : connector.htmlElement.appendChild(document.createElement("div")); + + t += (segment[1] == "top") ? parseInt(segment[0].style.height) - 14 : 0; + l += (segment[1] == "left") + ? parseInt(segment[0].style.width) - 11 + : (segment[1] == "right" ? 3 : 0); + + htmlElement.style.left = l + "px"; + htmlElement.style.top = t + "px"; + htmlElement.className = "connector-end " + type + " or"+segment[1]; + + return htmlElement; +}; + +apf.flow.label = function(connector, number) { + number = number || Math.ceil(connector.htmlSegments.length / 2); + var htmlElement, + segment = connector.htmlSegments[number], + l = parseInt(segment[0].style.left), + t = parseInt(segment[0].style.top); + + if (connector.htmlLabel) { + htmlElement = connector.htmlLabel; + } + else { + htmlElement = connector.htmlElement.appendChild(document.createElement("span")); + apf.setStyleClass(htmlElement, "label"); + } + + l += segment[1] == "top" || segment[1] == "bottom" + ? segment[0].offsetWidth + 3 + : (segment[0].offsetWidth - htmlElement.offsetWidth) / 2; + t += segment[1] == "top" || segment[1] == "bottom" + ? (segment[0].offsetHeight - htmlElement.offsetHeight) / 2 + : segment[0].offsetHeight - 2; + + htmlElement.style.left = l + "px"; + htmlElement.style.top = t + "px"; + htmlElement.innerHTML = connector.other.label; + + return htmlElement; +} + +/* + * Utility functions + */ + +/** + * Looking for object representation of block element with given id. + * + * @param {String} blockId html representation of block element id + * @return {Object} object representation of block element + */ +apf.flow.findBlock = function(blockId) { + var c = apf.flow.objCanvases; + + for (var id in c) { + if (c[id].htmlBlocks[blockId]) { + return c[id].htmlBlocks[blockId]; + } + } +}; + +/** + * Looking for object representation of block element with given HTMLElement. + * + * @param {HTMLElement} htmlNode html representation of block element + * @return {Object} object representation of block element + */ +apf.flow.isBlock = function(htmlNode) { + if(!htmlNode) + return; + + var id, block, + c = apf.flow.objCanvases; + + for (id in c) { + block = c[id].htmlBlocks[htmlNode.getAttribute("id")]; + if (block) + return block; + } +}; + +/** + * Looking for object representation of canvas element with given HTMLElement. + * + * @param {HTMLElement} htmlNode html representation of canvas element + * @return {Object} object representation of canvas element + */ +apf.flow.isCanvas = function(htmlNode) { + if (htmlNode) + return apf.flow.objCanvases[htmlNode.id]; +}; + +/** + * Looking for object representation of connector element with given object + * representation of source and destination block element and with source and + * destination input number. + * + * @param {Object} objBlock object representation of block element + * @param {Number} iNumber block bnput number + * @param {Object} objBlock2 object representation of block element + * @param {Number} iNumber2 block input number + * + * @return {Object} object representation of connector element and source description + * Properties: + * {Object} connector object representation of connector element + * {Boolean} source infortmation about which block is a source block + * Possible values: + * true when objBlock is a source block + * false when objBlock isn't a source block + */ +apf.flow.findConnector = function(objBlock, iNumber, objBlock2, iNumber2) { + var id, id2, cobjS, cobjD, co, ci, connectors, + c = apf.flow.objCanvases; + + for (id in c) { + connectors = c[id].htmlConnectors; + for (id2 in connectors) { + if (connectors[id2]) { + cobjS = connectors[id2].objSource, + cobjD = connectors[id2].objDestination, + co = connectors[id2].other.output, + ci = connectors[id2].other.input; + + if (objBlock2 && iNumber2) { + if (cobjS.id == objBlock.id && co == iNumber + && cobjD.id == objBlock2.id && ci == iNumber2) { + return {connector : connectors[id2], source : true}; + } + else if (cobjD.id == objBlock.id && ci == iNumber + && cobjS.id == objBlock2.id && co == iNumber2) { + return {connector : connectors[id2], source : false}; + } + } + else { + if (cobjS == objBlock && co == iNumber) + return {connector : connectors[id2], source : true}; + else if (cobjD == objBlock && ci == iNumber) + return {connector : connectors[id2], source : false}; + } + } + } + } +}; + +/** + * Looking for object representation of connector element with given HTMLElement. + * + * @param {HTMLElement} htmlNode html representation of connector element + * @return {Object} object representation of connector element + */ +apf.flow.isConnector = function(htmlNode) { + var c = apf.flow.objCanvases; + for (var id in c) { + if (c[id].htmlConnectors[htmlNode.id]) + return c[id].htmlConnectors[htmlNode.id]; + } +}; + +/** + * Creates object representation of canvas element + * + * @param {HTMLElement} htmlNode html representation of canvas element + * @return {Object} newCanvas object representation of canvas element + */ +apf.flow.getCanvas = function(htmlNode) { + var newCanvas = apf.flow.isCanvas(htmlNode); + + if (!newCanvas) { + newCanvas = new apf.flow.canvas(htmlNode); + newCanvas.initCanvas(); + } + return newCanvas; +}; + +/** + * Removes html representation of canvas element with his all elements. + * + * @param {HTMLElement} htmlNode html representation of canvas element + * + */ +apf.flow.removeCanvas = function(htmlNode) { + var canvas = apf.flow.isCanvas(htmlNode); + canvas.destroy(); +}; + +/** + * Creates object representation of block + * + * @param {HTMLElement} htmlElement html representation of block element + * @param {Object} objCanvas object representation of Canvas element + * @param {Object} other block properties + * Properties: + * {Boolean} lock prohibit block move. Default is false. + * Possible values: + * false block element is unlocled + * true block element is locked + * {Boolean} flipv whether to mirror the block over the vertical axis, + * background image is flipped automaticly. Default is false. + * Possible values: + * true block element is flipped + * false block element is not flipped + * {Boolean} fliph whether to mirror the block over the horizontal axis, + * background image is flipped automaticly. Default is false. + * Possible values: + * true block element is flipped + * false block element is not flipped + * {Number} rotation the rotation in degrees clockwise, background image + * is rotated automaticly, Default is 0. + * Possible values: + * 0 0 degrees rotation + * 90 90 degrees rotation + * 180 180 degrees rotation + * 270 270 degrees rotation + * {Object} inputList Block's inputs list, block could haven't any inputs + * Properties: + * {Number} x position in pixels relative to Block's horizontal + * dimension + * {Number} y position in pixels relative to Block's vertical + * dimension + * {String} position edge where input is placed + * Possible values: + * top input is placed on top edge of block + * right input is placed on right edge of block + * bottom input is placed on bottom edge of block + * left input is placed on left edge of block + * {String} type name of block with special abilities, which + * could set in template file + * {String} picture Path to image file. + * {Number} dwidth the minimal horizontal size of Block element + * {Number} dheight the minimal vertical size of Block element + * {Boolean} scalex Allows only horizontal resizing + * {Boolean} scaley Allows only vertical resizing + * {Boolean} scaleratio Vertical or horiznotal resizing only is not + * allowed. It's possible to resizing in two + * dimensions plane at the same time. + * {XMLElement} xmlNode xml representation of block from model + * {String} caption description placed under block element + * @return {Object} object representation of block element + */ +apf.flow.addBlock = function(htmlElement, objCanvas, other) { + if (htmlElement && !apf.flow.isBlock(htmlElement)) { + if (!htmlElement.getAttribute("id")) { + apf.setUniqueHtmlId(htmlElement); + } + var newBlock = new apf.flow.block(htmlElement, objCanvas, other); + newBlock.initBlock(); + return newBlock; + } +}; + +/** + * Removes html representation of block element with his all connections elements. + * + * @param {HTMLElement} htmlElement html representation of block element + * + */ +apf.flow.removeBlock = function(htmlElement) { + var block = apf.flow.isBlock(htmlElement); + block.destroy(); +}; + +/** + * Creates html representation of connector element between two blocks + * + * @param {Object} c object representation of canvas element + * @param {Object} s object representation of source block element + * @param {Object} d object representation of destination block element + * @param {Object} o connector properties + * Properties: + * {Number} output source block input number + * {Number} input destination block input number + * {XMLElement} xmlNode xml representation of connection element + */ +apf.flow.addConnector = function(c, s, d, o) { + var htmlElement = c.htmlElement.appendChild(document.createElement("div")); + + this.newConnector = new apf.flow.connector(htmlElement, c, s, d, o); + this.newConnector.initConnector(); +}; + +/** + * Removes html representation of connector element from canvas. + * + * @param {HTMLElement} htmlElement html representation of connector element + */ +apf.flow.removeConnector = function(htmlElement) { + var connector = apf.flow.isConnector(htmlElement); + if (connector) { + connector.destroy(); + } + delete connector; +}; + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/core/lib/flow2.js)SIZE(70664)TIME(Mon, 10 Jan 2011 10:53:11 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/core/lib/geolocation.js)SIZE(11303)TIME(Mon, 10 Jan 2011 10:53:11 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: socket://www.fsf.org. + * + */ + + + +/** + * + * + * @author Mike de Boer (mike AT ajax DOT org) + * @version %I%, %G% + * @since 3.0 + */ +apf.geolocation = (function() { + var bb_successCallback, + bb_errorCallback, + bb_blackberryTimeout_id = -1, + pub = {}, + provider = null, + cache = null, + UNDEF = "undefined", + UNAVAILABLE = "Position unavailable"; + + function handleBlackBerryLocationTimeout() { + if (bb_blackberryTimeout_id!=-1) + bb_errorCallback({message:"Timeout error", code:3}); + } + + function handleBlackBerryLocation() { + clearTimeout(bb_blackberryTimeout_id); + bb_blackberryTimeout_id = -1; + if (bb_successCallback && bb_errorCallback) { + if (blackberry.location.latitude == 0 && blackberry.location.longitude == 0) { + //http://dev.w3.org/geo/api/spec-source.html#position_unavailable_error + //POSITION_UNAVAILABLE (numeric value 2) + bb_errorCallback({message: UNAVAILABLE, code: 2}); + } + else { + var timestamp = null; + //only available with 4.6 and later + //http://na.blackberry.com/eng/deliverables/8861/blackberry_location_568404_11.jsp + if (blackberry.location.timestamp) + timestamp = new Date(blackberry.location.timestamp); + bb_successCallback({ + timestamp: timestamp, + coords : { + latitude : blackberry.location.latitude, + longitude: blackberry.location.longitude + } + }); + } + //since blackberry.location.removeLocationUpdate(); + //is not working as described + //http://na.blackberry.com/eng/deliverables/8861/blackberry_location_removeLocationUpdate_568409_11.jsp + //the callback are set to null to indicate that the job is done + bb_successCallback = bb_errorCallback = null; + } + } + + pub.getCurrentPosition = function(successCallback,errorCallback, options) { + provider.getCurrentPosition(successCallback, errorCallback, options); + }; + + pub.init = function() { + if (provider) return true; // already ran 'init' before + try { + if (typeof(geo_position_js_simulator) != UNDEF) { + provider = geo_position_js_simulator; + } + else if (typeof bondi != UNDEF && typeof bondi.geolocation != UNDEF) { + provider = bondi.geolocation; + } + else if (!!navigator.geolocation) { + provider = navigator.geolocation; + pub.getCurrentPosition = function(successCallback, errorCallback, options) { + function _successCallback(p) { + //for mozilla geode, it returns the coordinates slightly differently + if (typeof p.latitude != UNDEF) { + successCallback({ + timestamp: p.timestamp, + coords : { + latitude : p.latitude, + longitude: p.longitude + } + }); + } + else { + successCallback(p); + } + } + provider.getCurrentPosition(_successCallback, errorCallback, options); + if (provider.watchCurrentPosition) + provider.watchCurrentPosition(_successCallback, errorCallback, options) + }; + } + else if (typeof window.google != UNDEF && typeof google.gears != UNDEF) { + provider = google.gears.factory.create('beta.geolocation'); + } + else if (typeof Mojo != UNDEF && typeof Mojo.Service.Request != "Mojo.Service.Request") { + provider = true; + pub.getCurrentPosition = function(successCallback, errorCallback, options) { + var parameters = {}; + if (options) { + //http://developer.palm.com/index.php?option=com_content&view=article&id=1673#GPS-getCurrentPosition + if (options.enableHighAccuracy && options.enableHighAccuracy == true) + parameters.accuracy = 1; + if (options.maximumAge) + parameters.maximumAge = options.maximumAge; + if (options.responseTime) { + if (options.responseTime < 5) + parameters.responseTime = 1; + else if (options.responseTime < 20) + parameters.responseTime = 2; + else + parameters.timeout = 3; + } + } + + new Mojo.Service.Request('palm://com.palm.location', { + method : "getCurrentPosition", + parameters: parameters, + onSuccess : function(p){ + successCallback({ + timestamp: p.timestamp, + coords : { + latitude : p.latitude, + longitude: p.longitude, + heading : p.heading + } + }); + }, + onFailure: function(e){ + if (e.errorCode == 1) + errorCallback({code: 3, message: "Timeout"}); + else if (e.errorCode == 2) + errorCallback({code: 2, message: UNAVAILABLE}); + else + errorCallback({code: 0, message: "Unknown Error: webOS-code" + e.errorCode}); + } + }); + }; + } + else if (typeof device != UNDEF && typeof device.getServiceObject != UNDEF) { + provider = device.getServiceObject("Service.Location", "ILocation"); + + //override default method implementation + pub.getCurrentPosition = function(successCallback, errorCallback, options) { + function callback(transId, eventCode, result) { + if (eventCode == 4) { + errorCallback({message: UNAVAILABLE, code: 2}); + } + else { + //no timestamp of location given? + successCallback({ + timestamp: null, + coords : { + latitude : result.ReturnValue.Latitude, + longitude: result.ReturnValue.Longitude, + altitude : result.ReturnValue.Altitude, + heading : result.ReturnValue.Heading + } + }); + } + } + //make the call + provider.ILocation.GetLocation({ + LocationInformationClass: "BasicLocationInformation" + }, callback); + }; + } + else if (typeof window.blackberry != UNDEF && blackberry.location.GPSSupported) { + // set to autonomous mode + if (typeof blackberry.location.setAidMode == UNDEF) + return false; + blackberry.location.setAidMode(2); + //override default method implementation + pub.getCurrentPosition = function(successCallback, errorCallback, options) { + //passing over callbacks as parameter didn't work consistently + //in the onLocationUpdate method, thats why they have to be set + //outside + bb_successCallback = successCallback; + bb_errorCallback = errorCallback; + //function needs to be a string according to + //http://www.tonybunce.com/2008/05/08/Blackberry-Browser-Amp-GPS.aspx + bb_blackberryTimeout_id = setTimeout("handleBlackBerryLocationTimeout()", options['timeout'] || 60000); + blackberry.location.onLocationUpdate("handleBlackBerryLocation()"); + blackberry.location.refreshLocation(); + }; + provider = blackberry.location; + } + } + catch (e){ + + apf.console.error("GeoLocation error: " + e.toString()); + + + // fallback to Google API: + provider = "google"; + pub.getCurrentPosition = function(successCallback, errorCallback, options) { + if (cache) + return successCallback(cache); + apf.include((document.location.protocol == "https:" ? "https:" : "http:") + + "//www.google.com/jsapi", false, null, null, function() { + if (typeof google == UNDEF + || typeof google.loader == UNDEF + || typeof google.loader.ClientLocation == UNDEF) + return errorCallback({message: UNAVAILABLE}); + successCallback(cache = { + timestamp: null, + coords : { + latitude : google.loader.ClientLocation.latitude, + longitude: google.loader.ClientLocation.longitude, + address : google.loader.ClientLocation.address + } + }); + }); + }; + } + return provider != null; + }; + + return pub; +})(); + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/core/lib/html.js)SIZE(15348)TIME(Mon, 10 Jan 2011 10:53:11 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + +/** + * The parser of the HyperText Markup Language. + * @private + */ +apf.htmlCleaner = (function() { + var prepareRE = null, exportRE = null, + noMarginTags = {"table": 1, "TABLE": 1}, + selfClosing = {"br": 1, "img": 1, "input": 1, "hr": 1}; + + return { + /** + * Processes, sanitizes and cleanses a string of raw html that originates + * from outside a contentEditable area, so that the inner workings of the + * editor are less likely to be affected. + * + * @param {String} html + * @return The sanitized string, valid to store and use in the editor + * @type {String} + */ + prepare: function(html, bNoEnclosing) { + if (!prepareRE) { + // compile 'em regezz + prepareRE = [ + /<(\/?)strong>|]+)>/gi, + /<(\/?)em>|]+)>/gi, + /'/g, + /* + Ruben: due to a bug in IE and FF this regexp won't fly: + /((?:[^<]*|<(?:span|strong|em|u|i|b)[^<]*))]*?>/gi, //@todo Ruben: add here more inline html tag names + */ + /(<(\/?)(span|strong|em|u|i|b|a|strike|sup|sub|font|img)(?:\s+[\s\S]*?)?>)|()|(<(\/?)([\w\-]+)(?:\s+[\s\S]*?)?>)|([^<>]*)/gi, //expensive work around + /(]*href=)([^\s^>]+)*([^>]*>)/gi, + /

<\/p>/gi, + /]+)\/>|/gi + ]; + } + + // Convert strong and em to b and i in FF since it can't handle them + if (apf.isGecko) {//@todo what about the other browsers? + html = html.replace(prepareRE[0], "<$1b$2>") + .replace(prepareRE[1], "<$1i$2>"); + } + else if (apf.isIE) { + html = html.replace(prepareRE[2], "'") // IE can't handle apos + .replace(prepareRE[4], "$1$2 _apf_href=$2$3"); + //.replace(prepareRE[5], "

 

"); + + //
's need to be replaced to be properly handled as + // block elements by IE - because they're not converted + // when an editor command is executed + var str = [], capture = false, strP = [], depth = [], bdepth = [], + lastBlockClosed = false; + html.replace(prepareRE[3], + function(m, inline, close, tag, br, block, bclose, btag, any){ + if (inline) { + var id = strP.push(inline); + + tag = tag.toLowerCase(); + if (!selfClosing[tag]) { + if (close) { + if (!depth[depth.length-1] + || depth[depth.length-1][0] != tag) { + strP.length--; //ignore non matching tag + } + else { + depth.length--; + } + } + else { + depth.push([tag, id]); + } + } + + capture = true; + } + else if (any) { + strP.push(any); + capture = true; + } + else if (br) { + if (capture) { + if (depth.length) { + strP.push(br); + } + else { + str.push("

", strP.join(""), "

"); + strP = []; + } + + if (!depth.length) + capture = false; + } + else { + if ((bdepth.length || lastBlockClosed) + && br.indexOf("_apf_marker") > -1) { + //donothing + } + else + str.push("

 

"); + } + } + else if (block){ + if (bclose) { + if (bdepth[bdepth.length-1] != btag.toLowerCase()) { + return; + } + else { + bdepth.length--; + } + + //Never put P's inside block elements + if (strP.length) { + str.push(strP.join("")); + strP = []; + } + + lastBlockClosed = 2; + } + else { + var useStrP = strP.length && strP.join("").trim(); + var last = useStrP ? strP : str; + if (!noMarginTags[btag]) { + if (last[last.length - 1] == "

 

") + last[last.length - 1] = "";//

"; //maybe make this a setting + else if(useStrP && !bdepth.length) + last.push("

"); + } + + if (strP.length) { + //Never put P's inside block elements + if (!useStrP || bdepth.length) { + str.push(strP.join("")); + strP = []; + } + else { + str.push("

", strP.join(""), "

"); + strP = []; + } + } + + bdepth.push(btag.toLowerCase()); + } + + str.push(block); + capture = false; + } + + lastBlockClosed = lastBlockClosed == 2 ? 1 : false; + }); + var s; + if ((s = strP.join("")).trim()) + str.push(bNoEnclosing + ? s + : "

" + s + "

"); + html = str.join(""); + } + + // Fix some issues + html = (apf.xmlentities ? apf.xmlentities(html) : html) + .replace(prepareRE[6], ""); + + return html; + }, + + /** + * Return a string of html, but then formatted in such a way that it can + * embedded. + * + * @param {String} html + * @param {Boolean} noEntities + * @type {String} + */ + parse: function(html, noEntities, noParagraph) { + if (!exportRE) { + // compile 'em regezz + exportRE = [ + /]*><\/li>/gi, + /]*_apf_placeholder="1"\/?>/gi, + /<(a|span|div|h1|h2|h3|h4|h5|h6|pre|address)>[\s\n\r\t]*<\/(a|span|div|h1|h2|h3|h4|h5|h6|pre|address)>/gi, + /<(tr|td)>[\s\n\r\t]*<\/(tr|td)>/gi, + /[\s]*_apf_href="?[^\s^>]+"?/gi, + /(".*?"|'.*?')|(\w)=([^'"\s>]+)/gi, + /<((?:br|input|hr|img)(?:[^>]*[^\/]|))>/ig, // NO! do
see selfClosing + /

 $/mig, + /(]*?>(?:[\r\n\s]| )*]*?>)|(<(\/?)(span|strong|em|u|i|b|a|br|strike|sup|sub|font|img)(?:\s+.*?)?>)|(<(\/?)([\w\-]+)(?:\s+.*?)?>)|([^<>]*)/gi, + /<\/p>/gi, //

 <\/p>| + /

/gi, + /<\s*\/?\s*(?:\w+:\s*)?[\w-]*[\s>\/]/g + ]; + } + + if (apf.isIE) { + html = html.replace(exportRE[7], "

") + .replace(exportRE[9], "
") + .replace(exportRE[10], "") + } + else if (html == "
") + html = ""; + + html = (!noEntities && apf.xmlentities ? apf.xmlentities(html) : html) + .replace(exportRE[0], "") + .replace(exportRE[1], "") + .replace(exportRE[2], "") + .replace(exportRE[3], "<$1> ") + .replace(exportRE[4], "") + .replace(exportRE[6], "<$1 />") + .replace(exportRE[11], function(m){ + return m.toLowerCase(); + }); + + //@todo: Ruben: Maybe make this a setting (paragraphs="true") + //@todo might be able to unify this function with the one above. + if (apf.isIE && !noParagraph) { + var str = [], capture = true, strP = [], depth = [], bdepth = []; + html.replace(exportRE[8], + function(m, br, inline, close, tag, block, bclose, btag, any){ + if (inline) { + if (apf.isIE) { + inline = inline.replace(exportRE[5], + function(m1, str, m2, v){ + return str || m2 + "=\"" + v + "\""; + });//'$2="$3"') //quote un-quoted attributes + } + + var id = strP.push(inline); + + if (!selfClosing[tag]) { + if (close) { + if (!depth[depth.length-1] + || depth[depth.length-1][0] != tag) { + strP.length--; //ignore non matching tag + } + else { + depth.length--; + } + } + else { + depth.push([tag, id]); + } + } + + capture = true; + } + else if (any) { + strP.push(any); + capture = true; + } + else if (br) { + if (capture) { + if (depth.length) { + strP.push(br); + } + else { + str.push("

", strP.join("").trim() + || " ", "

"); + strP = []; + capture = false; + } + } + else + str.push("

 

"); + } + else if (block){ + if (bclose) { + if (bdepth[bdepth.length-1] != btag) { + return; + } + else { + bdepth.length--; + } + + //Never put P's inside block elements + if (strP.length) { + str.push(strP.join("")); + strP = []; + } + } + else { + if (apf.isIE) { + block = block.replace(exportRE[5], + function(m1, str, m2, v){ + return str || m2 + "=\"" + v + "\""; + });//'$2="$3"') //quote un-quoted attributes + } + + //@todo this section can be make similar to the one + // in the above function and vice verse + var last = strP.length ? strP : str; + if (last[last.length - 1] == "

 

") + last.length--; + + if (strP.length) { + var s; + //Never put P's inside block elements + if (bdepth.length || (s = strP.join("").trim()) + .replace(/<.*?>/g,"").trim() == "") { + str.push(s || strP.join("")); + strP = []; + } + else { + str.push("

", + (s || strP.join("").trim() || " ") + .replace(/
[\s\r\n]*$/, ""), + "

"); + strP = []; + } + } + + bdepth.push(btag); + } + + str.push(block); + capture = false; + } + }); + + if (strP.length) { + str.push("

" + strP.join("") + .replace(/
[\s\r\n]*$/, "") + "

"); + } + html = str.join(""); + } + else { + html = html.replace(/]*_apf_marker="1"[^>]*>/gi, "
"); + } + + + // check for VALID XHTML in DEBUG mode... + try { + apf.getXml("" + html.replace(/&.{3,5};/g, "") + + ""); + } + catch(ex) { + apf.console.error(ex.message + "\n" + html.escapeHTML()); + } + + + return html; + } + }; +})(); + + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/core/lib/language.js)SIZE(8586)TIME(Mon, 10 Jan 2011 10:53:11 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + + +/** + * Adds multilingual support for aml applications. Reads language symbols from + * an xml file and distributes them among elements containing text elements + * or images. When EditMode is turned on, it can subtract all text elements + * necesary for translation and export them in an xml file. This file can be + * sent to a translator to translate and then loaded back into the application. + * Examples: + * This examples shows a small language file. For example purpose it's loaded + * inline in a model. Normally this file would be loaded from a web server. + * There is a simple window and a couple of buttons that receive symbols from + * the language file. Two buttons provide a means to switch the language of the + * application, using the language symbols from the model. + * + * + * + * + * + * + * Textuele + * Arte + * Bonjour + * Adresse de courrier electronique * + * ... + * + * + * + * + * + * + * Text + * Art + * Hello + * E-mail * + * ... + * + * + * + * + * + * + * + * + * + * + * $[sub/main/1]$ + * + * $[sub/main/2]$ + * + * + * + * + * + * + * + * + * English + * + * + * French + * + * + * + * @default_private + * @todo get appsettings to understand language + */ +apf.language = { + /** + * Boolean specifying whether read strings are tried to match themselves if no key + * was gives. + */ + automatch : false, + + loaded : false, + + /** + * String setting the prefix to the set of language symbols. This is a tree path + * using a dott (.) as a seperation symbol. + */ + prefix : "sub.main.", + words : {}, + texts : {}, + elements : {}, + bindings : {}, + count : 0, + + /** + * Loads the symbol list from an xml node. + * @param {XMLElement} xmlNode the root of the symbol tree for the choosen language. + * @param {String} [prefix] the prefix that overrides the default prefix. + */ + load : function(xmlNode, prefix){ + if (!xmlNode) + return; + + if (typeof xmlNode == "string") { + if (xmlNode.charAt(0) == "<") + xmlNode = apf.getXmlDom(xmlNode).documentElement; + else + return this.loadFrom(xmlNode); //assuming data instruction + } + + this.parseSection(xmlNode, prefix); + this.redraw(); + this.loaded = true; + }, + + /** + * Loads the symbol list using a {@link term.datainstruction data instruction} + * @param {String} instruction the {@link term.datainstruction data instruction} to load the symbol xml from. + */ + loadFrom : function(instruction) { + apf.getData(instruction, {callback: function(xmlNode){ + if (!xmlNode) + return; + + + if (!xmlNode) { + throw new Error(apf.formatErrorString(0, null, + "Loading language", + "Could not find language symbols using processing \ + instruction: '" + instruction + "'")); + + return; + } + + + apf.language.load(xmlNode); + }}); + }, + + parseSection: function(xmlNode, prefix){ + if (!prefix) + prefix = xmlNode.getAttribute("id") || ""; + + if (xmlNode.tagName == "key") { + prefix += "/" + xmlNode.getAttribute("id"); + this.words[prefix] = xmlNode.firstChild ? xmlNode.firstChild.nodeValue : ""; + +/* @todo apf3.0 MIKE I think this is something you did + var val = xmlNode.firstChild ? xmlNode.firstChild.nodeValue : "", + aliases = xmlNode.getAttribute("aliases"); + this.update(prefix + "/" + xmlNode.getAttribute("id"), val); + if (aliases) { + aliases = aliases.splitSafe(","); + for (var i = 0, l = aliases.length; i < l; i++) + this.update(prefix + "/" + aliases[i], val); + } +*/ + return; + } + + //if(xmlNode.tagName == "lang") prefix = xmlNode.getAttribute("id"); + if (xmlNode.tagName == "group") + prefix += (prefix ? "/" : "") + xmlNode.getAttribute("id"); + + var nodes = xmlNode.childNodes; + for (var i = 0; i < nodes.length; i++) + if (nodes[i].nodeType == 1) + this.parseSection(nodes[i], prefix); + }, + + redraw : function(){ + var id, fParsed, prop, props, els = this.elements, amlNode; + for (id in els) { + props = els[id], amlNode = apf.all[id]; + for (prop in props) { + fParsed = props[prop]; + try { + if (fParsed.asyncs) { //if async + return fParsed.call(this, amlNode.xmlRoot, function(value){ + amlNode.setProperty(prop, value, true); + }); + } + else { + var value = fParsed.call(amlNode, amlNode.xmlRoot); + } + } + catch(e){ + apf.console.warn("[275] Could not execute language update for " + + prop + "\n\n" + e.message); + continue; + //return; + } + + amlNode.setProperty(prop, value, true); + } + } + + var sel, amlNode, bds = this.bindings; + for (id in bds) { + amlNode = apf.all[id]; + if (amlNode.selection) { + sel = amlNode.getSelection(); + amlNode.reload(); + amlNode.selectList(sel); + } + else amlNode.reload(); + } + }, + + getWord : function(symbol) { + return this.words[symbol]; + }, + + addProperty : function(amlNode, prop, func){ + (this.elements[amlNode.$uniqueId] || (this.elements[amlNode.$uniqueId] = {}))[prop] = func; + }, + + removeProperty : function(amlNode, prop){ + delete (this.elements[amlNode.$uniqueId] || false)[prop]; + }, + + addBinding : function(amlNode){ + this.bindings[amlNode.$uniqueId] = true; + }, + + removeBinding : function(amlNode){ + delete this.bindings[amlNode.$uniqueId]; + } +}; + + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/core/lib/layout.js)SIZE(14004)TIME(Mon, 10 Jan 2011 10:53:11 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + + +/** + * Object dealing with layout updates + */ +apf.layout = { + compile : function(oHtml){ + var l = this.layouts[oHtml.getAttribute("id")]; + if (!l) return false; + + var root = l.root.copy();//is there a point to copying? + + l.layout.compile(root); + l.layout.reset(); + }, + + removeAll : function(aData) { + aData.children.length = null + + var htmlId = this.getHtmlId(aData.pHtml); + if (!this.rules[htmlId]) + delete this.qlist[htmlId]; + }, + + timer : null, + qlist : {}, + dlist : [], + $hasQueue : false, + + queue : function(oHtml, obj, compile, q){ + if (!q) { + this.$hasQueue = true; + q = this.qlist; + } + + var id; + if (!(id = this.getHtmlId(oHtml))) + id = apf.setUniqueHtmlId(oHtml); + + if (q[id]) { + if (obj) + q[id][2].push(obj); + if (compile) + q[id][1] = compile; + return; + } + + q[id] = [oHtml, compile, [obj]]; + + if (!this.timer) + this.timer = apf.setZeroTimeout(function(){ + apf.layout.processQueue(); + }); + }, + + processQueue : function(){ + var i, id, l, qItem, list; + + for (i = 0; i < this.dlist.length; i++) { + if (this.dlist[i].hidden) + this.dlist[i].hide(); + else + this.dlist[i].show(); + } + + do { + var newq = {}; + var qlist = this.qlist; + this.qlist = {}; + + this.$hasQueue = false; + + for (id in qlist) { + qItem = qlist[id]; + + if (qItem[1]) + apf.layout.compileAlignment(qItem[1]); + + list = qItem[2]; + for (i = 0, l = list.length; i < l; i++) { + if (list[i]) { + if (list[i].$amlDestroyed) + continue; + //if (list[i].$amlLoaded) + list[i].$updateLayout(); + /*else + this.queue(qItem[0], list[i], null, newq);*/ + } + } + + apf.layout.activateRules(qItem[0]); + } + } while (this.$hasQueue); + + if (apf.hasSingleRszEvent) + apf.layout.forceResize(); + + this.dlist = []; + + clearTimeout(this.timer); + this.timer = null; + }, + + rules : {}, + onresize : {}, + + getHtmlId : function(oHtml){ + return oHtml.getAttribute ? oHtml.getAttribute("id") : 1; + }, + + /** + * Adds layout rules to the resize event of the browser. Use this instead + * of onresize events to add rules that specify determine the layout. + * @param {HTMLElement} oHtml the element that triggers the execution of the rules. + * @param {String} id the identifier for the rules within the resize function of this element. Use this to easily update or remove the rules added. + * @param {String} rules the javascript code that is executed when the html element resizes. + * @param {Boolean} [overwrite] whether the rules are added to the resize function or overwrite the previous set rules with the specified id. + */ + setRules : function(oHtml, id, rules, overwrite){ + if (!this.getHtmlId(oHtml)) + apf.setUniqueHtmlId(oHtml); + if (!this.rules[this.getHtmlId(oHtml)]) + this.rules[this.getHtmlId(oHtml)] = {}; + + var ruleset = this.rules[this.getHtmlId(oHtml)][id]; + if (!overwrite && ruleset) { + this.rules[this.getHtmlId(oHtml)][id] = rules + "\n" + ruleset; + } + else + this.rules[this.getHtmlId(oHtml)][id] = rules; + }, + + /** + * Retrieves the rules set for the resize event of an html element specified by an identifier + * @param {HTMLElement} oHtml the element that triggers the execution of the rules. + * @param {String} id the identifier for the rules within the resize function of this element. + */ + getRules : function(oHtml, id){ + return id + ? this.rules[this.getHtmlId(oHtml)][id] + : this.rules[this.getHtmlId(oHtml)]; + }, + + /** + * Removes the rules set for the resize event of an html element specified by an identifier + * @param {HTMLElement} oHtml the element that triggers the execution of the rules. + * @param {String} id the identifier for the rules within the resize function of this element. + */ + removeRule : function(oHtml, id){ + var htmlId = this.getHtmlId(oHtml); + if (!this.rules[htmlId]) + return; + + var ret = this.rules[htmlId][id] || false; + delete this.rules[htmlId][id]; + + var prop; + for (prop in this.rules[htmlId]) { + + } + if (!prop) + delete this.rules[htmlId] + + if (apf.hasSingleRszEvent) { + if (this.onresize[htmlId]) + this.onresize[htmlId] = null; + else { + var p = oHtml.parentNode; + while (p && p.nodeType == 1 && !this.onresize[p.getAttribute("id")]) { + p = p.parentNode; + } + + if (p && p.nodeType == 1) { + var x = this.onresize[p.getAttribute("id")]; + if (x.children) + delete x.children[htmlId] + } + } + } + + return ret; + }, + + /** + * Activates the rules set for an html element + * @param {HTMLElement} oHtml the element that triggers the execution of the rules. + */ + activateRules : function(oHtml, no_exec){ + if (!oHtml) { //!apf.hasSingleRszEvent && + var prop, obj; + for(prop in this.rules) { + obj = document.getElementById(prop); + if (!obj || obj.onresize) // || this.onresize[prop] + continue; + this.activateRules(obj); + } + + if (apf.hasSingleRszEvent && window.onresize) + window.onresize(); + return; + } + + var rsz, id, rule, rules, strRules = []; + if (!apf.hasSingleRszEvent) { + rules = this.rules[this.getHtmlId(oHtml)]; + if (!rules){ + oHtml.onresize = null; + return false; + } + + for (id in rules) { //might need optimization using join() + if (typeof rules[id] != "string") + continue; + strRules.push(rules[id]); + } + + //apf.console.info(strRules.join("\n")); + rsz = apf.needsCssPx + ? new Function(strRules.join("\n")) + : new Function(strRules.join("\n").replace(/ \+ 'px'|try\{\}catch\(e\)\{\}\n/g,"")) + + oHtml.onresize = rsz; + if (!no_exec) + rsz(); + } + else { + var htmlId = this.getHtmlId(oHtml); + rules = this.rules[htmlId]; + if (!rules){ + //@todo keep .children + //delete this.onresize[htmlId]; + return false; + } + + for (id in rules) { //might need optimization using join() + if (typeof rules[id] != "string") + continue; + strRules.push(rules[id]); + } + + var p = oHtml.parentNode; + while (p && p.nodeType == 1 && !this.onresize[p.getAttribute("id")]) { + p = p.parentNode; + } + + var f = new Function(strRules.join("\n"));//.replace(/try\{/g, "").replace(/}catch\(e\)\{\s*\}/g, "\n") + if (this.onresize[htmlId]) + f.children = this.onresize[htmlId].children; + + if (p && p.nodeType == 1) { + var x = this.onresize[p.getAttribute("id")]; + this.onresize[htmlId] = (x.children || (x.children = {}))[htmlId] = f; + } + else { + this.onresize[htmlId] = f; + } + if (!no_exec) + f(); + + if (!window.onresize) { + /*var f = apf.layout.onresize; + window.onresize = function(){ + var s = []; + for (var name in f) + s.unshift(f[name]); + for (var i = 0; i < s.length; i++) + s[i](); + }*/ + + var rsz = function(f){ + //@todo fix this + try{ + var c = []; + for (var name in f) + if (f[name]) + c.unshift(f[name]); + for (var i = 0; i < c.length; i++){ + c[i](); + if (c[i].children) { + rsz(c[i].children); + } + } + } + catch(e){ + + } + } + + window.onresize = function(){ + rsz(apf.layout.onresize); + } + } + } + }, + + /** + * Forces calling the resize rules for an html element + * @param {HTMLElement} oHtml the element for which the rules are executed. + */ + forceResize : function(oHtml){ + if (apf.hasSingleRszEvent) + return window.onresize && window.onresize(); + + /* @todo this should be done recursive, old way for now + apf.hasSingleRszEvent + ? this.onresize[this.getHtmlId(oHtml)] + : + */ + + var rsz = oHtml.onresize; + if (rsz) + rsz(); + + var els = oHtml.getElementsByTagName("*"); + for (var i = 0, l = els.length; i < l; i++) { + if (els[i] && els[i].onresize) + els[i].onresize(); + } + }, + + paused : {}, + + /** + * Disables the resize rules for the html element temporarily. + * @param {HTMLElement} oHtml the element for which the rules are paused. + * @param {Function} func the resize code that is used temporarily for resize of the html element. + */ + pause : function(oHtml, replaceFunc){ + if (apf.hasSingleRszEvent) { + var htmlId = this.getHtmlId(oHtml); + this.paused[htmlId] = this.onresize[htmlId] || true; + + if (replaceFunc) { + this.onresize[htmlId] = replaceFunc; + this.onresize[htmlId].children = this.paused[htmlId].children; + replaceFunc(); + } + else + delete this.onresize[htmlId]; + } + else { + this.paused[this.getHtmlId(oHtml)] = oHtml.onresize || true; + + if (replaceFunc) { + oHtml.onresize = replaceFunc; + replaceFunc(); + } + else + oHtml.onresize = null; + } + }, + + /** + * Enables paused resize rules for the html element + * @param {HTMLElement} oHtml the element for which the rules have been paused. + */ + play : function(oHtml){ + if (!this.paused[this.getHtmlId(oHtml)]) + return; + + if (apf.hasSingleRszEvent) { + var htmlId = this.getHtmlId(oHtml); + var oldFunc = this.paused[htmlId]; + if (typeof oldFunc == "function") { + this.onresize[htmlId] = oldFunc; + //oldFunc(); + } + else + delete this.onresize[htmlId]; + + if (window.onresize) + window.onresize(); + + this.paused[this.getHtmlId(oHtml)] = null; + } + else { + var oldFunc = this.paused[this.getHtmlId(oHtml)]; + if (typeof oldFunc == "function") { + oHtml.onresize = oldFunc; + oldFunc(); + } + else + oHtml.onresize = null; + + this.paused[this.getHtmlId(oHtml)] = null; + } + } +}; + + +/** + * @private + */ +apf.getWindowWidth = function(){ + return apf.isIE ? document.documentElement.offsetWidth - apf.windowHorBorder : window.innerWidth; +} +/** + * @private + */ +apf.getWindowHeight = function(){ + return apf.isIE ? document.documentElement.offsetHeight - apf.windowVerBorder : window.innerHeight; +} + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/core/lib/printer.js)SIZE(5120)TIME(Mon, 10 Jan 2011 10:53:11 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + + +/** + * @private + */ +apf.printer = { + tagName : "printer", + nodeFunc : apf.NODE_HIDDEN, + lastContent : "", + inited : false, + panel : null, + + init : function(aml){ + if (this.inited) return this; + + this.inited = true; + this.$aml = aml; + + this.panel = document.body.appendChild(document.createElement("div")); + this.panel.setAttribute("id", "print_content"); + this.panel.onmousedown = function(){ + apf.printer.hide(); + }; + with (this.panel.style) { + position = "absolute"; + top = "0"; + left = "0"; + width = "100%"; + height = "100%"; + backgroundColor = "white"; + } + apf.window.zManager.set("print", this.panel); + + apf.importCssString("#print_content{display:none}"); + apf.importCssString(apf.hasCSSChildOfSelector && !apf.isIE + ? "body #print_content{display:block} body>*{display:none}" + : "body #print_content, body #print_content *{display:block} body *{display:none}", + document, "print"); + + //body #print_content, body #print_content *{display:block} + + if (aml) { + //Events + var a, i, attr = aml.attributes; + for (i = 0; i < attr.length; i++) { + a = attr[i]; + if (a.nodeName.indexOf("on") == 0) + apf.addEventListener(a.nodeName, + + apf.lm.compile(a.nodeValue, {event: true, parsecode: true}) + + ); + } + } + + + function printPNGFix(disable) { + if (apf.supportPng24) return; + + if (!apf.config.iePngFix) return; + + for (var e, i = 0, j = document.all.length; i < j; i++) { + e = document.all[i]; + if (e.filters['DXImageTransform.Microsoft.AlphaImageLoader'] || e._png_print) { + if (disable) { + e._png_print = e.style.filter; + e.style.filter = ''; + } + else { + e.style.filter = e._png_print; + e._png_print = ''; + } + } + } + } + + + window.onbeforeprint = function(){ + + printPNGFix(true); + + apf.dispatchEvent("beforeprint"); + }; + + window.onafterprint = function(){ + + printPNGFix(false); + + apf.dispatchEvent("afterprint"); + }; + + return this; + }, + + preview : function(strHtml, show){ + this.init(); + + if (typeof strHtml != "string") + strHtml = strHtml.outerHTML || strHtml.xml || strHtml.serialize(); + + this.lastContent = strHtml; + this.panel.innerHTML = strHtml; + + if (show) + this.show(); + + return this; + }, + + show : function() { + if (!this.inited) return this; + this.panel.style.display = "block"; + return this; + }, + + hide : function() { + if (!this.inited) return this; + this.panel.style.display = "none"; + this.panel.style.height = document.body.scrollHeight + "px"; + this.panel.style.width = document.body.scrollWidth + "px"; + return this; + } +}; + +/** + * Sents html to a printer in formatted form. + * @param {String} strHtml the html to be printed. + */ +apf.print = function(strHtml){ + apf.printer.init().preview(strHtml); + window.print(); +}; + + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/core/lib/queue.js)SIZE(3021)TIME(Fri, 11 Feb 2011 16:49:57 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + + +// Only add setZeroTimeout to the window object, and hide everything +// else in a closure. +apf.setZeroTimeout = apf.isIE || apf.isO3 || apf.isAIR + ? setTimeout + : (function() { + var timeouts = []; + var messageName = "zero-timeout-message"; + + // Like setTimeout, but only takes a function argument. There's + // no time argument (always zero) and no arguments (you have to + // use a closure). + function setZeroTimeout(fn) { + timeouts.push(fn); + window.postMessage(messageName, "*"); + } + + function handleMessage(e) { + if (!e) e = event; + if (e.source == window && e.data == messageName) { + apf.stopPropagation(e); + if (timeouts.length> 0) { + var fn = timeouts.shift(); + fn(); + } + } + } + + apf.addListener(window, "message", handleMessage, true); + + // Add the one thing we want added to the window object. + return setZeroTimeout; + })(); + + + + +/** + * + */ +apf.queue = { + //@todo apf3.0 + q : {}, + + timer : null, + add : function(id, f){ + this.q[id] = f; + if (!this.timer) + this.timer = apf.setZeroTimeout(function(){ + apf.queue.empty(); + }); + }, + + remove : function(id){ + delete this.q[id]; + }, + + empty : function(prop){ + clearTimeout(this.timer); + this.timer = null; + + + if (apf.layout && apf.layout.$hasQueue) + apf.layout.processQueue(); + + + if (apf.xmldb && apf.xmldb.$hasQueue) + apf.xmldb.notifyQueued(); + + + var q = this.q; + this.q = {}; + for (var prop in q){ + var f = q[prop]; + if (f) { + delete q[prop]; + f(); + } + } + } +}; + + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/core/lib/resize.js)SIZE(13139)TIME(Mon, 10 Jan 2011 10:53:11 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + + +/** + * This abstraction is using for resizing block elements. Resizing is allowed + * with square elements in vertical, horizontal or both planes. Symmetric + * resizing is possible with SHIFT button. + * + * @private + * @default_private + * @constructor + * + * @author Lukasz Lipinski + * @version %I%, %G% + * @since 1.0 + * @namespace apf + */ + +apf.resize = function() { + /** + * {Boolean} scalex resizing in horizontal plane, default is true + * Possible values: + * true resizing in horizontal plane is allowed + * false resizing in horizontal plane is not allowed + * {Boolean} scaley resizing in vertical plane, default is true + * Possible values: + * true resizing in vertical plane is allowed + * false resizing in vertical plane is not allowed + * {Boolean} scaleratio resizing in horizontal or vertical plane only is not allowed. Resizing in two dimensions plane at the same time is allowed. + * Possible values: + * true resizing in two dimensions plane at the same time is allowed + * false Resizing in two dimensions plane at the same time is not allowed + * {Number} dwidth the minimal horizontal size of Block element, default is 56 pixels + * {Number} dheight the minimal vertical size of Block element, default is 56 pixels + */ + this.scales = { + scalex : false, + scaley : false, + scaleratio: false, + dwidth : 0, + dheight : 0, + snap : false, + gridW : 48, + gridH : 48 + }; + + /** + * html representation of resized block element + */ + this.htmlElement; + + /** + * store object representations of inputs elements + */ + var squares = []; + + this.init = function() { + squares = [ + new apf.resize.square("top", "left", this), + new apf.resize.square("top", "middle", this), + new apf.resize.square("top", "right", this), + new apf.resize.square("middle", "left", this), + new apf.resize.square("middle", "right", this), + new apf.resize.square("bottom", "left", this), + new apf.resize.square("bottom", "middle", this), + new apf.resize.square("bottom", "right", this)]; + }; + + /** + * Links block element with resize feature + * + * @param {HTMLElement} oHtml html representation of block element + * @param {Object} scales blocks scale settings + */ + this.grab = function(oHtml, scales) { + this.htmlElement = oHtml; + this.scales = scales; + + if (!squares.length) + this.init(); + this.show(); + }; + + /** + * Hides all block squares + */ + this.hide = function() { + for (var i = 0, l = squares.length; i < l; i++) { + squares[i].visible = false; + squares[i].repaint(); + } + }; + + /** + * Shows all block squares + */ + this.show = function() { + var sx = this.scales.scalex; + var sy = this.scales.scaley; + var sr = this.scales.scaleratio; + + for (var i = 0, l = squares.length, s; i < l; i++) { + s = squares[i]; + s.visible = sx && sy + ? true + : (sy && !sx + ? (s.posX == "middle" + ? true + : false) + : (sx && !sy + ? (s.posY == "middle" + ? true + : false) + : (sr + ? ((s.posY == "top" || s.posY == "bottom") + && s.posX !== "middle" + ? true + : false) + : false))); + + s.repaint(); + } + }; + + /** + * Destroys all block squares + */ + this.destroy = function(){ + for (var i = 0; i < squares.length; i++) { + squares[i].destroy(); + } + }; +}; + +/** + * Creates html and object representation for square element. Square is used for + * resizing block elements. + * + * @param {String} posY square vertical align relative to resized block element + * Possible values: + * top square is on top of resized block element + * middle square is in the middle of the resized block element + * bottom square is on the bottom of resized block element + * @param {String} posX square vertical align relative to resized block element + * Possible values: + * left square is on the left of resized block element + * middle square is in the middle of the resized block element + * right square is on the right of resized block element + * @param {Object} objResize object of resize class + * @constructor + */ +apf.resize.square = function(posY, posX, objResize) { + /** + * Square visibility + */ + this.visible = true; + /** + * square vertical align relative to resized block element + */ + this.posX = posX; + /** + * square vertical align relative to resized block element + */ + this.posY = posY; + + var margin = 0; + var _self = this; + + /** + * html represenation of square element + */ + this.htmlElement = objResize.htmlElement.parentNode.appendChild(document.createElement('div')); + apf.setStyleClass(this.htmlElement, "square"); + + /** + * Repaints square + */ + this.repaint = function() { + if (this.visible) { + var block = objResize.htmlElement; + this.htmlElement.style.display = "block"; + + var bw = parseInt(block.style.width) + apf.getDiff(block)[0]; + var bh = parseInt(block.style.height) + apf.getDiff(block)[1]; + var bt = parseInt(block.style.top); + var bl = parseInt(block.style.left); + + var sw = this.htmlElement.offsetWidth; + var sh = this.htmlElement.offsetHeight; + + var t = posY == "top" + ? bt - margin - sh + : posY == "middle" + ? bt + bh/2 - sh/2 + : bt + bh + margin; + var l = posX == "left" + ? bl - margin - sw + : posX == "middle" + ? bl + bw/2 - sw/2 + : bl + bw + margin; + + var c = (posY == "middle" + ? "w-resize" + : (posX == "middle" + ? "n-resize" + : (posY + posX == "topleft" + || posY + posX == "bottomright") + ? "nw-resize" + : "ne-resize")); + + this.htmlElement.style.top = (t - 1) + "px"; + this.htmlElement.style.left = (l - 1) + "px"; + this.htmlElement.style.cursor = c; + } + else { + //IE bug + var sw = this.htmlElement.offsetWidth; + this.htmlElement.style.display = 'none'; + } + }; + + this.destroy = function(){ + apf.destroyHtmlNode(this.htmlElement); + }; + + /* Events */ + this.htmlElement.onmouseover = function(e) { + apf.setStyleClass(_self.htmlElement, "squareHover"); + }; + + this.htmlElement.onmouseout = function(e) { + apf.setStyleClass(_self.htmlElement, "", ["squareHover"]); + }; + + this.htmlElement.onmousedown = function(e) { + e = (e || event); + + var block = objResize.htmlElement, + + sx = e.clientX, + sy = e.clientY, + + pt = block.parentNode.offsetTop, + pl = block.parentNode.offsetLeft, + + dw = objResize.scales.dwidth, + dh = objResize.scales.dheight, + + snap = objResize.scales.snap, + gridH = objResize.scales.gridH, + gridW = objResize.scales.gridW, + + objBlock = apf.flow.isBlock(block), + r = objBlock.other.ratio, + + posX = _self.posX, + posY = _self.posY, + + width, height, top, left, dx, dy, + prev_w, prev_h, + + l = parseInt(block.style.left), + t = parseInt(block.style.top), + w = parseInt(block.style.width), + h = parseInt(block.style.height), + resized = false; + + objResize.onresizedone(w, h, t, l); + + if (e.preventDefault) { + e.preventDefault(); + } + + document.onmousemove = function(e) { + e = (e || event); + + dx = e.clientX - sx; + dy = e.clientY - sy; + var shiftKey = e.shiftKey, + proportion = r; + + if (shiftKey) { + if (posX == "right" && posY == "bottom") { + width = w + dx; + height = width/proportion; + left = l; + top = t; + } + else if (posX == "right" && posY == "top") { + width = w + dx; + height = width/proportion; + left = l; + top = t - dx/proportion; + } + else if (posX == "left" && posY == "bottom") { + width = w - dx; + height = width/proportion; + left = l + dx; + top = t; + } + else if (posX == "left" && posY == "top") { + width = w - dx; + height = width/proportion; + left = l + dx; + top = t + dx/proportion; + } + + /* Keep minimal size */ + if(width >= dw && height >= dh) { + width = prev_w = Math.max(dw, width); + height = prev_h = Math.max(dh, height); + } + else { + width = prev_w; + height = prev_h; + return false; + } + } + else { + width = posX == "right" + ? w + dx + : (posX == "left" + ? w - dx + : w); + height = posY == "bottom" + ? h + dy + : (posY == "top" + ? h - dy + : h); + left = posX == "right" + ? l + : (posX == "left" + ? Math.min(l + w - dw, l + dx) + : l); + top = posY == "bottom" + ? t + : (posY == "top" + ? Math.min(t + h - dh, t + dy) + : t); + + /* Keep minimal size */ + width = Math.max(dw, width); + height = Math.max(dh, height); + } + + if (snap) { + left = Math.floor(left / gridW) * gridW; + top = Math.floor(top / gridH) * gridH; + width = Math.ceil(width / gridW) * gridW; + height = Math.ceil(height / gridH) * gridH; + } + + if (objResize.onresize) { + objResize.onresize(block, top, left, width, height); + } + + objResize.show(); + + resized = true; + }; + + document.onmouseup = function(e) { + document.onmousemove = null; + if (objResize.onresizedone && resized) { + objResize.onresizedone(width, height, top, left); + objBlock.other.ratio = width / height; + resized = false; + } + }; + }; +} + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/core/lib/resize2.js)SIZE(10417)TIME(Mon, 10 Jan 2011 10:53:11 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/core/lib/selection.js)SIZE(32184)TIME(Mon, 10 Jan 2011 10:53:11 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + + +/** + * @class apf.selection + * @constructor + * @author Mike de Boer (mike AT javeline DOT com) + */ +apf.selection = function(oWin, oDoc, editor) { + /* + * Initialize the apf.selection class. + * + * @type apf.selection + */ + oWin = oWin || window; + oDoc = oDoc || window.document; + this.current = null; + + var FUNC = "function", + UNDEF = "undefined", + CHAR = "character", + TEXT = "Text", + CTRL = "Control", + NONE = "None", + csLock, + vfocus = (editor && typeof editor.$visualFocus == FUNC), + _self = this; + + /** + * Get the selection of the editable area + * + * @type {Selection} + */ + this.get = function() { + return apf.w3cRange ? oWin.getSelection() : oDoc.selection; + }; + + /** + * Set or move the current selection to the cached one. + * At the moment, this function is very IE specific and is used to make sure + * that there's a correct selection object available at all times. + * @see apf.selection.cache + * + * @type {Range} + */ + this.set = function() { + if (!this.current) return null; + if (apf.w3cRange) { + this.moveToBookmark(this.current); + return this.current; + } + + try { + if (vfocus) + editor.$visualFocus(); + else + oWin.focus(); + this.current.select(); + } + catch (ex) {} + + if (vfocus) + editor.$visualFocus(); + else + oWin.focus(); + + return this.current; + }; + + /** + * Save the selection object/ current range into a global variable to cache + * it for later use. + * At the moment, this function is very IE specific and is used to make sure + * that there's a correct selection object available at all times. + * + * @param {Boolean} [w3cToo] Also cache the selection for browsers that support + * the w3c range spec + * @type {void} + */ + this.cache = function(w3cToo) { + if (apf.w3cRange) { + if (w3cToo) + this.current = this.getBookmark(); + return this; + } + + var oSel = oDoc.selection; + this.current = oSel.createRange(); + this.current.type = oSel.type; + + if (this.current.type == TEXT && this.current.text == "" && !csLock) { + csLock = $setTimeout(this.cache, 0); + } + else { + clearTimeout(csLock); + csLock = null; + } + + return this; + }; + + /** + * Retrieve the active Range object of the current document selection. + * Internet Explorer returns a controlRange when a control (e.g. image, + * object tag) is selected or a textRange when a set of characters is + * selected. + * + * @type {Range} + */ + this.getRange = function() { + var oSel = this.get(), range; + + try { + if (oSel) + range = oSel.rangeCount > 0 + ? oSel.getRangeAt(0) + : (oSel.createRange + ? oSel.createRange() + : oDoc.createRange()); + } + // IE throws unspecified error here if we're placed in a frame/iframe + catch (ex) {} + + // No range found then create an empty one + // This can occur when the editor is placed in a hidden container + // element on Gecko. Or on IE when there was an exception + if (!range) + range = apf.w3cRange + ? oDoc.createRange() + : oDoc.body.createTextRange(); + + return range; + }; + + /** + * Set the active range of the current selection inside the editable + * document to a specified range. + * + * @param {Range} range + * @type {void} + */ + this.setRange = function(range) { + if (apf.w3cRange) { + var oSel = this.get(); + + if (oSel) { + oSel.removeAllRanges(); + oSel.addRange(range); + } + } + else { + try { + range.select(); + } + // Needed for some odd IE bug + catch (ex) {} + } + + return this; + }; + + var activeEl, + _inline = "BR|IMG|HR|INPUT", + _block = "P|BUTTON|TEXTAREA|SELECT|DIV|H[1-6]|ADDRESS|PRE|OL|UL|LI|TABLE|TBODY|TR|DT|DE|TD|SUB|SUP", + _form = "SELECT|BUTTON|TEXTAREA", + reBlock = new RegExp("^(?:" + _block + ")$", "i"), + reForm = new RegExp("^(?:" + _form + ")$", "i"), + reInline = new RegExp("^(?:" + _inline + ")$", "i"); + + this.inline = _inline; + this.block = _block; + this.form = _form; + + function trimNl(str) { + return (apf.isOpera || apf.isIE) ? str : str.replace(/\r\n/g, " "); + } + + function getText(node) { + return String(apf.isIE ? node.nodeType == 3 ? node.nodeValue : node.innerText : node.textContent); + } + + function getHtml(node) { + return String(node.nodeType == 3 ? apf.isIE ? node.nodeValue : node.textContent : node.innerHTML); + } + + function textContent(node, o) { + var cn, + sp = 0, + str = "", + i = 0, + l = node.childNodes.length; + for (; i < l; i++) { + cn = node.childNodes[i]; + if (reForm.test(cn.nodeName)) { + str += "<->" + (apf.isIE ? "" : trimNl(getText(cn)).replace(/./gi, " ")) + ""; + } + else { + if (reBlock.test(cn.nodeName)) { + str += "<+>" + (apf.isIE ? "" : trimNl(getText(cn)).replace(/./gi, " ")) + ""; + } + else { + if (reInline.test(cn.nodeName)) { + str += "<>"; + } + else { + if (cn.nodeName == "SPAN" && (sp = _4.attr(cn, "scaytid"))) + str += "<" + sp + ">" + trimNl(getHtml(cn)) + ""; + else + str += trimNl(getHtml(cn)); + } + } + } + } + return str; + } + + function getParent(node, eltype, attr, root) { + var k; + root = root || this.containerNode; + eltype = eltype ? new RegExp("^(?:" + eltype + ")$") : null; + while (node && node != root) { + if (eltype && eltype.test(node.nodeName.toUpperCase())) { + return node; + } + if (attr) { + for (k in attr) { + if (node.getAttribute(k) !== null && attr[k] === null) + return node; + if ((attr[k] !== null && node.getAttribute(k) !== null + && !(node.getAttribute(k) === false)) ? !attr[k] : attr[k]) { + return node; + } + } + } + node = node.parentNode; + } + return false; + } + + /** + * Returns a bookmark location for the current selection. This bookmark + * object can then be used to restore the selection after some content + * modification to the document. + * + * @param {Boolean} [type] State if the bookmark should be simple or not. + * Default is complex. + * @param {Function} [callback] function used to retrieve a custom reference node + * @return {Object} Bookmark object, use moveToBookmark with this + * object to restore the selection. + */ + this.getBookmark = function(type, callback) { + var ch = -16777215, + range = this.getRange(), + vp = apf.getViewPort(oWin), + c = oDoc.body, + o = { + scrollX : vp.x, + scrollY : vp.y, + collapse: 0, + start : 0 + }, + sel = this.get(); + + activeEl = null; + if (type == "simple") { + o.rng = range; + return o; + } + if (!apf.w3cRange) { + if (range.item) { + var e = range.item(0), + n = c.getElementsByTagName(e.nodeName), + i = 0, + l = n.length; + for (; i < l; i++) { + if (e == n[i]) + return !(sp = i); + } + return apf.extend(o, { + tag : e.nodeName, + index: sp + }); + } + var tr, bp, sp, tr1; + tr = range.duplicate(); + tr.moveToElementText(c); + tr.collapse(true); + bp = Math.abs(tr.move(CHAR, ch)); + tr = range.duplicate(); + tr.collapse(true); + sp = Math.abs(tr.move(CHAR, ch)); + tr = range.duplicate(); + tr.collapse(false); + var offset = 0; + tr1 = tr.duplicate(); + tr1.moveEnd(CHAR, 1); + tr1.collapse(false); + var parN = tr1.parentElement(); + if (reBlock.test(parN.nodeName)) { + if (getParent(tr.parentElement(), _block, null, c) != parN) + activeEl = parN; + } + return apf.extend(o, { + start : sp - bp - offset, + length: Math.abs(tr.move(CHAR, ch)) - sp + }); + } + var p = sel.anchorNode; //getParentElement() + while (p && (p.nodeType != 1)) + p = p.parentNode; + if (p && p.nodeName == "IMG") { + return o; + } + if (!sel) + return null; + + var w, + sc = range.startContainer, + an = sel.anchorNode, + custom = callback ? callback(an) : null; + if (sel.isCollapsed && an) { + o.collapse = 1; + p = getParent(an, _block) || c; + if (an.nodeType == 3) { + w = oDoc.createTreeWalker(p, NodeFilter.SHOW_TEXT, null, false); + while (n = w.nextNode()) { + if (n == an) { + o.start = o.start + sel.anchorOffset; + break; + } + o.start += trimNl(n.nodeValue || "").length; + } + } + else { + if (an != p) { + w = oDoc.createTreeWalker(p, NodeFilter.SHOW_ALL, null, false); + while (n = w.nextNode()) { + if (n == an) + break; + o.start += trimNl(n.nodeValue || "").length; + } + } + for (i = 0, l = range.startOffset; i < l; i++) + o.start += parseInt(String(sc.childNodes[i].textContent).length); + } + o.end = o.start; + if (!custom) { + o.content = sc.textContent || sc.innerHTML; + try { + if (range.startOffset == 0 && sc.previousSibling + && (/IMG|BR|INPUT/.test(sc.previousSibling.nodeName))) { + o.br = sc.previousSibling; + } + if (sc.childNodes[range.startOffset - 1] + && (/IMG|BR|INPUT/.test(sc.childNodes[range.startOffset - 1].nodeName))) { + o.br = sc.childNodes[range.startOffset - 1]; + } + } + catch(e) {} + } + if (custom && range.startOffset == 0) { + n = custom.previousSibling; + while (n && ((n.nodeType == 3 && n.textContent == "") + || (n.nodeType != 3 && n.innerHTML == ""))) { + if (n && (/IMG|BR|INPUT/.test(n.nodeName))) { + o.br = n; + o.br2 = n.nextSibling; + break; + } + n = n.previousSibling; + } + } + apf.extend(o, { + block : p, + node : sc, + offset: range.startOffset + }); + return o; + } + var s = []; + p = 0; + w = oDoc.createTreeWalker(c, NodeFilter.SHOW_TEXT, null, false); + while ((n = w.nextNode()) != null) { + if (n == sc) + s[0] = p; + if (n == range.endContainer) { + s[1] = p; + break; + } + p += trimNl(n.nodeValue || "").length; + } + apf.extend(o, { + start: s[0] + range.startOffset, + end : s[1] + range.endOffset, + block: c + }); + return o; + }; + + /** + * Restores the selection to the specified bookmark. + * + * @param {Object} b Bookmark to restore selection from. + * @return {Boolean} true/false if it was successful or not. + */ + this.moveToBookmark = function(b) { + var crt, + sel = this.get(), + c = oDoc.body, + rng = this.getRange(); + + function getPos(sp, ep) { + var n, par, nv, nvl, o, + p = 0, + d = {}, + k = -1, + w = oDoc.createTreeWalker(b.block, NodeFilter.SHOW_TEXT, null, false); + while (n = w.nextNode()) { + nv = n.nodeValue || ""; + nvl = trimNl(nv).length; + p += nvl; + if (b.collapse) { + if (p >= sp) + par = getParent(n, _block) || c; + if (p == sp) + k = par == b.block ? 1 : 0; + if (k == -1 && p > sp || k == 1) { + d.endNode = d.startNode = n; + d.endOffset = d.startOffset = sp - (p - nvl); + return d; + } + } + else { + if (p >= sp && !d.startNode) { + o = sp - (p - nvl); + d.startNode = n; + d.startOffset = sp - (p - nvl); + } + if (p >= ep) { + d.endNode = n; + d.endOffset = ep - (p - nvl); + return d; + } + } + } + return null; + } + + if (!b) + return false; + + if (!apf.w3cRange) { + oDoc.body.setActive(); + if (crt = b.rng) { + try { + crt.select(); + } + catch(ex) {} + return true; + } + if (b.tag) { + crt = c.createControlRange(); + var n = oDoc.getElementsByTagName(b.tag), + i = 0, + l = n.length; + for (; i < l; i++) { + if (i == b.index) + crt.addElement(n[i]); + } + } + else { + try { + if (b.start < 0) { + return true; + } + crt = sel.createRange(); + if (activeEl) { + crt.moveToElementText(activeEl); + crt.moveStart(CHAR, -2); + crt.expand(word); + crt.collapse(false); + } + else { + crt.moveToElementText(c); + crt.collapse(true); + crt.moveStart(CHAR, b.start); + crt.moveEnd(CHAR, b.length); + } + } + catch(e) { + return true; + } + } + try { + crt.select(); + } + catch(ex) {} + return true; + } + if (!sel) + return false; + + crt = rng.cloneRange(); + if (b.rng) { + sel.removeAllRanges(); + sel.addRange(b.rng); + } + else { + if (typeof b.node != UNDEF) { + var a = false; + if ((b.node.nodeType == 3 && b.node.parentNode != null + && b.node.textContent == b.content) || (b.node.nodeType != 3 + && b.node.innerHTML == b.content)) { + crt.setStart(b.node, b.offset); + crt.collapse(true); + a = true; + } + if (typeof b.br != UNDEF && (/IMG|BR|INPUT/.test(b.br.nodeName))) { + if (b.br.nextSibling) { + crt.selectNode(b.br.nextSibling); + crt.collapse(true); + } + else { + crt.selectNode(b.br); + crt.collapse(false); + } + a = true; + } + if (a) { + if (!apf.isOpera) + sel.removeAllRanges(); + sel.addRange(crt); + c.focus(); + oWin.scrollTo(b.scrollX, b.scrollY); + return; + } + } + if (typeof b.start != UNDEF && typeof b.end != UNDEF) { + try { + var sd = getPos(b.start, b.end); + if (sd) { + crt.setStart(sd.startNode, sd.startOffset); + crt.setEnd(sd.endNode, sd.endOffset); + oWin.scrollTo(b.scrollX, b.scrollY); + if (!apf.isOpera) + sel.removeAllRanges(); + sel.addRange(crt); + } + } + catch(ex) { + apf.console.error(ex); + } + } + return; + } + }; + + /** + * Retrieve the contents of the currently active selection/ range as a + * string of HTML. + * + * @type {String} + */ + this.getContent = function(retType) { + if (typeof retType != "string") + retType = "html" + var range = this.getRange(), oSel = this.get(), prefix, suffix, n, + oNode = oDoc.body; + + if (retType == "text") + return this.isCollapsed() ? '' : (range.text || (oSel.toString ? oSel.toString() : '')); + + if (this.isCollapsed()) + return ""; + + if (typeof range.htmlText != UNDEF) + return range.htmlText; + + + var pNode, n = range.cloneContents(); + if (!n.childNodes.length) + return ""; + + pNode = n.childNodes[0].ownerDocument.createElement("div"); + pNode.appendChild(n); + return pNode.innerHTML; + + /* + prefix = suffix = ''; + //if (editor && editor.output == 'text') + //return this.isCollapsed() ? '' : (range.htmlText || (range.item + // && range.item(0).outerHTML) || ''); + + if (range.cloneContents) { + n = range.cloneContents(); + if (n) + oNode.appendChild(n); + } + else if (typeof range.item != UNDEF || typeof range.htmlText != UNDEF) + oNode.innerHTML = range.item ? range.item(0).outerHTML : range.htmlText; + else + oNode.innerHTML = range.toString(); + + // Keep whitespace before and after + if (/^\s/.test(oNode.innerHTML)) + prefix = ' '; + if (/\s+$/.test(oNode.innerHTML)) + suffix = ' '; + + // Note: TinyMCE uses a serializer here, I don't. + // prefix + this.serializer.serialize(oNode, options) + suffix; + return this.isCollapsed() ? '' : prefix + oNode.outerHTML + suffix;*/ + } + + /** + * Alter the content of the current selection/ active range by setting its + * contents with some other specified HTML + * + * @param {String} html + * @type {void} + * @return A reference to the HTML node which has been inserted or its direct parent + */ + this.setContent = function(html, bNoPrepare) { + var range = this.getRange(); + + + if (!bNoPrepare) + html = apf.htmlCleaner.prepare(html, true); + + + if (range.insertNode) { + // Make caret marker since insertNode places the caret in the + // beginning of text after insert + html += '_'; + + // Delete and insert new node + range.deleteContents(); + range.insertNode(this.getRange().createContextualFragment(html)); + + // Move to caret marker + var oCaret = oDoc.getElementById('__caret'); + var htmlNode = oCaret.previousSibling; + + // Make sure we wrap it completely, Opera fails with a simple + // select call + range = oDoc.createRange(); + range.setStartBefore(oCaret); + range.setEndAfter(oCaret); + this.setRange(range); + + // Delete the marker, and hopefully the caret gets placed in the + // right location + oDoc.execCommand('Delete', false, null); + + // In case it's still there + if (oCaret && oCaret.parentNode) + oCaret.parentNode.removeChild(oCaret); + + return htmlNode; + } + else { + if (range.item) { + // Delete content and get caret text selection + this.remove(); + range = this.getRange(); + } + + html = html.replace(/^<(\w+)/, '<$1 id="__caret"'); + range.pasteHTML(html); + var htmlNode = oDoc.getElementById('__caret'); + if (htmlNode) { + htmlNode.removeAttribute("id"); + return htmlNode; + } + } + } + + var styleObjNodes = { + img : 1, + hr : 1, + li : 1, + table : 1, + tr : 1, + td : 1, + embed : 1, + object: 1, + ol : 1, + ul : 1 + }; + + /** + * Get the type of selection of the editable area + * + * @type String + */ + this.getType = function() { + var oSel = this.get(); + if (apf.isIE) { + return oSel.type; + } + else { + // By default set the type to "Text". + var type = 'Text' ; + // Check if the actual selection is a Control (IMG, TABLE, HR, etc...). + if (oSel && oSel.rangeCount == 1) { + var range = oSel.getRangeAt(0); + if (range.startContainer == range.endContainer + && (range.endOffset - range.startOffset) == 1 + && range.startContainer.nodeType == 1 + && styleObjNodes[range.startContainer + .childNodes[range.startOffset].nodeName.toLowerCase()]) { + type = CTRL; + } + } + return type; + } + }; + + /** + * Retrieve the currently selected element from the editable area + * + * @return {DOMObject} Currently selected element or common ancestor element + */ + this.getSelectedNode = function() { + var range = this.getRange(); + + if (!apf.isIE) { + // Range maybe lost after the editor is made visible again + if (!range) + return oDoc; + + var oSel = this.get(), oNode = range.commonAncestorContainer; + + // Handle selection as image or other control like element such + // as anchors + if (!range.collapsed) { + // If the anchor node is an element instead of a text node then + // return this element + if (apf.isWebkit && oSel.anchorNode && oSel.anchorNode.nodeType == 1) + return oSel.anchorNode.childNodes[oSel.anchorOffset]; + + if (range.startContainer == range.endContainer) { + if (range.startOffset - range.endOffset < 2) { + if (range.startContainer.hasChildNodes()) + oNode = range.startContainer.childNodes[range.startOffset]; + } + } + } + + //oNode = oNode.parentNode; + //while (oNode && oNode.parentNode && oNode.nodeType != 1) + // oNode = oNode.parentNode; + return oNode; + } + + return range.item ? range.item(0) : range.parentElement(); + }; + + /** + * Retrieve the parent node of the currently selected element from the + * editable area + * + * @type DOMObject + */ + this.getParentNode = function() { + switch (this.getType()) { + case CTRL : + if (apf.isIE) + return this.getSelectedNode().parentElement; + else + return this.getSelectedNode().parentNode; + case NONE : + return; + default : + var oSel = this.get(); + if (apf.isIE) { + return oSel.createRange().parentElement(); + } + else { + if (oSel) { + var oNode = oSel.anchorNode; + while (oNode && oNode.nodeType != 1) + oNode = oNode.parentNode; + return oNode; + } + } + break; + } + }; + + /** + * Select a specific node inside the editable area + * + * @param {DOMObject} node + * @type void + */ + this.selectNode = function(node) { + var oSel, range; + + //@todo Mike please check this! + while (node.nodeType == 1 && node.firstChild) + node = node.firstChild; + + if (apf.isIE) { + oSel = this.get(); + + if (!node) + node = oSel.createRange().parentElement(); + + try{ + oSel.empty(); + }catch(e){} + + try { + // Try to select the node as a control. + range = oDoc.body.createControlRange(); + range.addElement(node); + } + catch (e) { + // If failed, select it as a text range. + range = oDoc.body.createTextRange(); + try { + range.moveToElementText(node.nodeType != 1 + ? node.parentNode + : node); + } + catch (e2) { + if (node.nodeValue) + range.findText(node.nodeValue); + } + } + try{ + range.select(); + }catch(e){} + } + else { + range = this.getRange(); + if (node) + range.selectNode(node); + oSel = this.get(); + oSel.removeAllRanges(); + oSel.addRange(range); + } + + return this; + }; + + /** + * Collapse the selection to start or end of range. + * + * @param {Boolean} [toEnd] Boolean state if to collapse to end or + * not. Defaults to start. + * @type {void} + */ + this.collapse = function(toEnd) { + var range = this.getRange(), n; + + // 'Control' range on IE + if (range.item) { + n = range.item(0); + range = oDoc.body.createTextRange(); + range.moveToElementText(n); + } + + range.collapse(!!toEnd); + this.setRange(range); + + return this; + }; + + /** + * Checks if the active range is in a collapsed state or not. + * + * @type {Boolean} + */ + this.isCollapsed = function() { + var range = this.getRange(), oSel = this.get(); + + if (!range || range.item) + return false; + + return !oSel || range.boundingWidth == 0 || range.collapsed; + }; + + /** + * Check if the currently selected element has any parent node(s) with the + * specified tagname + * + * @param {String} nodeTagName + * @type {Boolean} + */ + this.hasAncestorNode = function(nodeTagName) { + var oContainer, range = this.getRange(); + if (this.getType() == CTRL || !apf.isIE) { + oContainer = this.getSelectedNode(); + if (!oContainer && !apf.isIE) { + try { + oContainer = range.startContainer; + } + catch(e){} + } + } + else { + oContainer = range.parentElement(); + } + while (oContainer) { + if (apf.isIE) + if (oContainer.tagName == nodeTagName) + return true; + else if (oContainer.nodeType == 1 + && oContainer.tagName == nodeTagName) + return true; + oContainer = oContainer.parentNode; + } + return false ; + }; + + /** + * Move the selection to a parent element of the currently selected node + * with the specified tagname + * + * @param {String} nodeTagName + * @type {void} + */ + this.moveToAncestorNode = function(nodeTagName) { + var oNode, i, range = this.getRange(); + nodeTagName = nodeTagName.toUpperCase(); + if (apf.isIE) { + if (this.getType() == CTRL) { + for (i = 0; i < range.length; i++) { + if (range(i).parentNode) { + oNode = range(i).parentNode; + break; + } + } + } + else { + oNode = range.parentElement(); + } + while (oNode && oNode.nodeName != nodeTagName) + oNode = oNode.parentNode; + return oNode; + } + else { + var oContainer = this.getSelectedNode(); + if (!oContainer) + oContainer = oWin.getSelection().getRangeAt(0).startContainer + while (oContainer) { + if (oContainer.tagName == nodeTagName) + return oContainer; + oContainer = oContainer.parentNode; + } + return null ; + } + }; + + /** + * Remove the currently selected contents from the editable area + * + * @type {Selection} + */ + this.remove = function() { + var oSel = this.get(), i; + if (apf.isIE) { + if (oSel.type != NONE) + oSel.clear(); + } + else if (oSel) { + for (i = 0; i < oSel.rangeCount; i++) + oSel.getRangeAt(i).deleteContents(); + } + return this; + }; + + this.$destroy = function() { + oWin = oDoc = editor = this.current = _self = null; + delete oWin; + delete oDoc; + delete editor; + delete this.current; + delete _self; + }; +}; + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/core/lib/skins.js)SIZE(12705)TIME(Mon, 10 Jan 2011 10:53:11 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + + +/** + * @private + */ +apf.skins = { + skins : {}, + css : [], + events : ["onmousemove", "onmousedown", "onmouseup", "onmouseout", + "onclick", "ondragcopy", "ondragstart", "ondblclick"], + + /* *********** + Init + ************/ + Init: function(xmlNode, refNode, path){ + /* + get data from refNode || xmlNode + - name + - icon-path + - media-path + + all paths of the xmlNode are relative to the src attribute of refNode + all paths of the refNode are relative to the index.html + images/ is replaced if there is a refNode to the relative path from index to the skin + /images/ + */ + var name = (refNode ? refNode.getAttribute("id") : null) + || xmlNode.getAttribute("id"); + var base = (refNode ? refNode.getAttribute("src").match(/\//) || path : "") + ? (path || refNode.getAttribute("src")).replace(/\/[^\/]*$/, "") + "/" + : ""; //@todo make this absolute? + + var mediaPath = null, iconPath = null; + mediaPath = xmlNode.getAttribute("media-path"); + if (mediaPath !== null) + mediaPath = apf.getAbsolutePath(base || apf.hostPath, mediaPath); + else if (refNode) { + mediaPath = refNode.getAttribute("media-path"); + if (mediaPath !== null) + mediaPath = apf.getAbsolutePath(apf.hostPath, mediaPath); + else + mediaPath = apf.getAbsolutePath(base || apf.hostPath, "images/"); + } + + iconPath = xmlNode.getAttribute("icon-path"); + if (iconPath !== null) + iconPath = apf.getAbsolutePath(base || apf.hostPath, iconPath); + else if (refNode) { + iconPath = refNode.getAttribute("icon-path"); + if (iconPath !== null) + iconPath = apf.getAbsolutePath(apf.hostPath, iconPath); + else + iconPath = apf.getAbsolutePath(base || apf.hostPath, "icons/"); + } + + if (!name) + name = "default"; + + if (xmlNode.getAttribute("id")) + document.body.className += " " + xmlNode.getAttribute("id"); + + var names = name.split("|"); + name = names[0]; + + if (!this.skins[name] || name == "default") { + this.skins[name] = { + base : base, + name : name, + iconPath : iconPath, + mediaPath: mediaPath, + templates: {}, + originals: {}, + xml : xmlNode + } + + if (names.length > 1) { + for (var i = 0; i < names.length; i++) + this.skins[names[i]] = this.skins[name]; + } + } + + if (!this.skins["default"] && this.$first == refNode) + this.skins["default"] = this.skins[name]; + + var nodes = xmlNode.childNodes; + for (var i = nodes.length - 1; i >= 0; i--) { + if (nodes[i].nodeType != 1) + continue; + + //this.templates[nodes[i].tagName] = nodes[i]; + this.skins[name].templates[nodes[i].getAttribute("name")] = nodes[i]; + if (nodes[i].ownerDocument) + this.importSkinDef(nodes[i], base, name); + } + + this.purgeCss(mediaPath, iconPath); + + if (this.queue[name]) { + for (var prop in this.queue[name]) { + this.queue[name][prop](); + } + } + }, + + /** + * This method loads a stylesheet from a url + * @param {String} filename Required The url to load the stylesheet from + * @param {String} title Optional Title of the stylesheet to load + * @method + */ + loadStylesheet: function(filename, title){ + var o; + with (o = document.getElementsByTagName("head")[0].appendChild(document.createElement("LINK"))) { + rel = "stylesheet"; + type = "text/css"; + href = filename; + title = title; + } + + return o; + }, + + /* *********** + Import + ************/ + importSkinDef: function(xmlNode, basepath, name){ + var i, l, nodes = $xmlns(xmlNode, "style", apf.ns.aml), tnode, node; + for (i = 0, l = nodes.length; i < l; i++) { + node = nodes[i]; + + if (node.getAttribute("src")) + this.loadStylesheet(apf.getAbsolutePath(basepath, node.getAttribute("src"))); + else { + var test = true; + if (node.getAttribute("condition")) { + try { + test = eval(node.getAttribute("condition")); + } + catch (e) { + test = false; + } + } + + if (test) { + //#-ifndef __PROCESSED + tnode = node.firstChild; + while (tnode) { + this.css.push(tnode.nodeValue); + tnode = tnode.nextSibling; + } + /*#-else + this.css.push(nodes[i].firstChild.nodeValue); + #-endif*/ + } + } + } + + nodes = $xmlns(xmlNode, "alias", apf.ns.apf); + var t = this.skins[name].templates; + for (i = 0; i < nodes.length; i++) { + if (!nodes[i].firstChild) + continue; + t[nodes[i].firstChild.nodeValue.toLowerCase()] = xmlNode; + } + }, + + loadedCss : "", + purgeCss: function(imagepath, iconpath){ + if (!this.css.length) + return; + + var cssString = this.css.join("\n").replace(/images\//g, imagepath).replace(/icons\//g, iconpath); + apf.importCssString(cssString); + + + this.loadedCss += cssString; + + + this.css = []; + }, + + loadCssInWindow : function(skinName, win, imagepath, iconpath){ + this.css = []; + var name = skinName.split(":"); + var skin = this.skins[name[0]]; + var template = skin.templates[name[1]]; + this.importSkinDef(template, skin.base, skin.name); + var cssString = this.css.join("\n").replace(/images\//g, imagepath).replace(/icons\//g, iconpath); + apf.importCssString(cssString); + + this.css = []; + }, + + /* *********** + Retrieve + ************/ + setSkinPaths: function(skinName, amlNode){ + skinName = skinName.split(":"); + var name = skinName[0]; + var type = skinName[1]; + + + if (!this.skins[name]) { + throw new Error(apf.formatErrorString(1076, null, + "Retrieving Skin", + "Could not find skin '" + name + "'", amlNode.$aml)); + } + + + amlNode.iconPath = this.skins[name].iconPath; + amlNode.mediaPath = this.skins[name].mediaPath; + }, + + getTemplate: function(skinName, noError){ + skinName = skinName.split(":"); + var name = skinName[0]; + var type = skinName[1]; + + if (!this.skins[name]) { + if (noError) + return false; + + + throw new Error(apf.formatErrorString(1077, null, + "Retrieving Template", + "Could not find skin '" + name + "'")); + + + return false; + } + + if (!this.skins[name].templates[type]) + return false; + + var skin = this.skins[name].templates[type]; + var originals = this.skins[name].originals[type]; + if (!originals) { + originals = this.skins[name].originals[type] = {}; + + + if (!$xmlns(skin, "presentation", apf.ns.aml)[0]) { + throw new Error(apf.formatErrorString(1078, null, + "Retrieving Template", + "Missing presentation tag in '" + name + "'")); + } + + + var nodes = $xmlns(skin, "presentation", apf.ns.aml)[0].childNodes; + for (var i = 0; i < nodes.length; i++) { + if (nodes[i].nodeType != 1) continue; + originals[nodes[i].baseName || nodes[i][apf.TAGNAME]] = nodes[i]; + } + } + + /*for (var item in originals) { + pNodes[item] = originals[item]; + }*/ + + return originals; + }, + + getCssString : function(skinName){ + return apf.queryValue($xmlns(this.skins[skinName.split(":")[0]].xml, + "style", apf.ns.aml)[0], "text()"); + }, + + + changeSkinset : function(value){ + var node = apf.document.documentElement; + while (node) { + if (node && node.nodeFunc == apf.NODE_VISIBLE + && node.hasFeature(apf.__PRESENTATION__) && !node.skinset) { + node.$propHandlers["skinset"].call(node, value);//$forceSkinChange + node.skinset = null; + } + + //Walk tree + if (node.firstChild || node.nextSibling) { + node = node.firstChild || node.nextSibling; + } + else { + do { + node = node.parentNode; + } while (node && !node.nextSibling) + + if (node) + node = node.nextSibling; + } + } + }, + + + queue : {}, + waitForSkin : function(skinset, id, callback){ + if (this.skins[skinset]) + return; + + (this.queue[skinset] || (this.queue[skinset] = {}))[id] = callback; + return true; + }, + + + iconMaps : {}, + addIconMap : function(options){ + this.iconMaps[options.name] = options; + if (options.size) + options.width = options.height = options.size; + else { + if (!options.width) + options.width = 1; + if (!options.height) + options.height = 1; + } + }, + + + setIcon : function(oHtml, strQuery, iconPath){ + if (!strQuery) { + oHtml.style.backgroundImage = ""; + return; + } + + if (oHtml.tagName.toLowerCase() == "img") { + oHtml.setAttribute("src", strQuery + ? (iconPath || "") + strQuery + : ""); + return; + } + + + var parts = strQuery.split(":"); //@todo apf3.x optimize this + var map = this.iconMaps[parts[0]]; + if (map) { + var left, top, coords = parts[1].split(","); + left = (coords[(map.type == "vertical") ? 1 : 0] || 0) * map.width; + top = (coords[(map.type == "vertical") ? 0 : 1] || 0) * map.height; + + oHtml.style.backgroundImage = "url(" + (iconPath || "") + + map.src + ")"; + oHtml.style.backgroundPosition = ((-1 * left) - map.offset[0]) + + "px " + ((-1 * top) - map.offset[1]) + "px"; + } + else + + + //Assuming image url + { + + //@todo check here if it is really a url + + + oHtml.style.backgroundImage = "url(" + (iconPath || "") + + strQuery + ")"; + } + } +}; + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/core/lib/sort.js)SIZE(8413)TIME(Mon, 10 Jan 2011 10:53:11 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + + +/** + * Object handling sorting in a similar way as xslt. + * + * @constructor + * @todo use a struct instead of lots of local variables, and stop using eval + * + * @author Ruben Daniels (ruben AT ajax DOT org) + * @version %I%, %G% + * @since 0.8 + * + * @private + */ +apf.Sort = function(xmlNode){ + var settings = {}; + //order, xpath, type, method, getNodes, dateFormat, dateReplace, sort_dateFmtStr, getValue; + + //use this function to parse the each node + this.parseXml = function(xmlNode, clear){ + if (clear) settings = {}; + + settings.order = xmlNode.order; + settings.getValue = xmlNode.csort || xmlNode.$compile("sort"); + settings.getNodes = self[xmlNode["nodes-method"]]; + + settings.ascending = (settings.order || "").indexOf("desc") == -1; + settings.order = null; + + if (xmlNode["data-type"]) + settings.method = sort_methods[xmlNode["data-type"]]; + else if (xmlNode["sort-method"]) { + settings.method = self[xmlNode["sort-method"]]; + + + if (!settings.method) { + throw new Error(apf.formatErrorString(0, null, + "Sorting nodes", + "Invalid or missing sort function name provided '" + + xmlNode["sort-method"] + "'", xmlNode)); + } + + } + else + settings.method = sort_methods["alpha"]; + + var str = xmlNode["date-format"]; + if (str) { + settings.sort_dateFmtStr = str; + settings.method = sort_methods["date"]; + var result = str.match(/(D+|Y+|M+|h+|m+|s+)/g); + if (result) { + for (var pos = {}, i = 0; i < result.length; i++) + pos[result[i].substr(0, 1)] = i + 1; + settings.dateFormat = new RegExp(str.replace(/([^\sDYMhms])/g, '\\$1') + .replace(/YYYY/, "(\\d\\d\\d\\d)") + .replace(/(DD|YY|MM|hh|mm|ss)/g, "(\\d\\d)")); + settings.dateReplace = "$" + pos["M"] + "/$" + pos["D"] + "/$" + pos["Y"]; + if (pos["h"]) + settings.dateReplace += " $" + pos["h"] + ":$" + pos["m"] + ":$" + pos["s"]; + } + } + }; + + this.set = function(struct, clear){ + if (clear) settings = {}; + + apf.extend(settings, struct); + + if (settings.ascending == undefined) + settings.ascending = struct.order + ? struct.order.indexOf("desc") == -1 + : true; + + settings.order = null; + + if (struct["type"]) + settings.method = sort_methods[struct["type"]]; + else if (struct["method"]) + settings.method = self[struct["method"]]; + else if (!settings.method) + settings.method = sort_methods["alpha"]; + + if (struct.format) { + settings.sort_dateFmtStr = struct.format; + //settings.method = sort_methods["date"]; + var result = str.match(/(D+|Y+|M+|h+|m+|s+)/g); + if (result) { + for (var pos = {}, i = 0; i < result.length; i++) + pos[result[i].substr(0, 1)] = i + 1; + settings.dateFormat = new RegExp(str.replace(/([^\sDYMhms])/g, '\\$1') + .replace(/YYYY/, "(\\d\\d\\d\\d)") + .replace(/(DD|YY|MM|hh|mm|ss)/g, "(\\d\\d)")); + settings.dateReplace = "$" + pos["M"] + "/$" + pos["D"] + "/$" + pos["Y"]; + if (pos["h"]) + settings.dateReplace += " $" + pos["h"] + ":$" + pos["m"] + ":$" + pos["s"]; + } + } + + if (!settings.getValue) { + settings.getValue = function(item){ + return apf.queryValue(item, settings.xpath); + } + } + }; + + this.get = function(){ + return apf.extend({}, settings); + }; + + //use this function in __xmlUpdate [this function isnt done yet] + this.findSortSibling = function(pNode, xmlNode){ + var nodes = getNodes ? getNodes(pNode, xmlNode) : this.getTraverseNodes(pNode); + + for (var i = 0; i < nodes.length; i++) + if (!compare(xmlNode, nodes[i], true, sortSettings)) + return nodes[i]; + + return null; + }; + + // Sorting methods for sort() + var sort_intmask = ["", "0", "00", "000", "0000", "00000", "000000", + "0000000", "00000000", "000000000", "0000000000", "00000000000", + "000000000000", "0000000000000", "00000000000000"]; + var sort_methods = { + "alpha" : function (n){ + return n.toString().toLowerCase() + }, + + "number" : function (t){ + return (t.length < sort_intmask.length + ? sort_intmask[sort_intmask.length - t.length] + : "") + t; + }, + + "date" : function (t, args){ + var sort_dateFormat = settings.dateFormat; + var sort_dateReplace = settings.dateReplace; + var sort_dateFmtStr = settings.sort_dateFmtStr; + + var d;//|| (args && sort_dateFmtStr != args[0]) + if (!sort_dateFormat) { + d = new Date(t); + } + else if (sort_dateFmtStr == '*') + d = apf.date.getDateTime(t); + else + d = (new Date(t.replace(sort_dateFormat, sort_dateReplace))).getTime(); + t = "" + d.getTime();//parseInt(d); + if (t == "NaN") + t = "0"; + return (t.length < sort_intmask.length ? sort_intmask[sort_intmask.length + - t.length] : "") + t; + } + }; + + /* + sort(xpath, sort_xpath, sort_alpha, boolDesc, from, len) + jsort(n,f,p,ps,sm,desc,sp,ep) + */ + //var order, xpath, type, method, getNodes, dateFormat, dateReplace, sort_dateFmtStr, getValue; + this.apply = function(n, args, func, start, len){ + var sa = [], i = n.length; + + // build string-sortable list with sort method + while (i--) { + var v = settings.getValue(n[i]); + if (n) + sa[sa.length] = { + toString: function(){ + return this.v; + }, + xmlNode : n[i], + v : (settings.method || sort_methods.alpha)(v || "", args, n[i]) + }; + } + + // sort it + sa.sort(); + + //iterate like foreach + var end = len ? Math.min(sa.length, start + len) : sa.length; + if (!start) + start = 0; + + if (func) { + if (settings.ascending) + for (i = start; i < end; i++) + f(i, end, sa[i].xmlNode, sa[i].v); + else + for (i = end - 1; i >= start; i--) + f(end - i - 1, end, sa[i].xmlNode, sa[i].v); + } + else { + //this could be optimized by reusing n... time it later + var res = []; + if (settings.ascending) + for (i = start; i < end; i++) + res[res.length] = sa[i].xmlNode; + else + for (i = end - 1; i >= start; i--) + res[res.length] = sa[i].xmlNode; + return res; + } + }; + + if (xmlNode) + this.parseXml(xmlNode); +}; + + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/core/lib/storage.js)SIZE(9036)TIME(Mon, 10 Jan 2011 10:53:11 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + +/** + * Stores javascript structures based on a name and a namespace. This object + * is used by {@link element.offline apf offline support} as well as the + * {@link core.registry registry} and the {@link teleport.http http object} for + * caching. All but the memory storage provider, provide persistent storage. + * This means the data is kept between browser sessions. This allows apf to + * have inter-session communication. For instance offline support uses it to + * store data that could not be send to the server. When the application does + * go online (and this could be several sessions later), the data is send to the + * server. + * + * Remarks: + * The HTML5 specification advices an interface for local persistent storage. + * Not all browsers have implemented this yet. There are several plugins and/or + * browser containers that provide solutions for this. Among them are google + * gears and adobe's flash. Storage providers for these and others are available. + * + * @default_private + */ +apf.storage = { + modules : {}, + + /** + * Initializes the main storage engine based on the specified provider. + * @param {String} name the name of the provider that will provider storage + * Possible values: + * memory data is stored in memory and is lost when the application exits. + * air data is stored in the air name/value storage. + * air.file data is stored in the air file based storage. + * air.sql data is stored in the air sqlite storage. + * flash data is stored in a small flash container. + * gears data is stored using the sqlite interface of gears. + * html5 data is stored in a local storage object specified by the WHATWG html5 standard. + */ + init : function(name){ + if (!name || name == "autodetect") name = this.autodetect(); + var provider = this.getProvider(name); + + //Install the provider + apf.storage = apf.extend(this, provider); + apf.storage.init = null; + + + apf.console.info("Installed storage provider '" + name + "'"); + + + return provider; + }, + + /** + * Retrieves a storage provider without installing it as the central storage provider. + * @param {String} name the name of the storage provider. + * Possible values: + * memory data is stored in memory and is lost when the application exits. + * air data is stored in the air name/value storage. + * air.file data is stored in the air file based storage. + * air.sql data is stored in the air sqlite storage. + * flash data is stored in a small flash container. + * gears data is stored using the sqlite interface of gears. + * gears data is stored using the sqlite interface of gears. + * html5 data is stored in a local storage object specified by the WHATWG html5 standard. + */ + getProvider : function(name){ + var provider = apf.storage.modules[name]; + + if(!provider || typeof provider != "object") { + + apf.console.warn("Could not find storage provider '" + name + "'"); + + + return false; + } + + if (!provider.isAvailable()) { + + apf.console.warn( + "Storage provider '" + name + "' is not available"); + + + return false; + } + + if(!provider.initialized + && (!provider.init || provider.init() === false)) { + + apf.console.warn( + "Could not install storage provider '" + name + ""); + + + return false; + } + + provider.name = name; + apf.extend(provider, this.base); + + return provider; + }, + + /** + * Checks if a provider is available. + */ + autodetect : function(){ + for (var name in this.modules) { + if ("memory|cookie".indexOf(name) > -1) + continue; + + if (this.modules[name].isAvailable()) { + return name; + } + } + + return !location.host && this.modules.cookie + ? "cookie" + : (this.modules.memory + ? "memory" + : null); + }, + + /** + * @private + */ + base : { + namespace : "default", + + isValidKeyArray : function(keys) { + return (!keys || !keys.join) + ? false + : /^[0-9A-Za-z_\.\-]*$/.test(keys.join("")); + }, + + isValidKey : function(keyName){ + return (keyName === null || keyName === undefined) + ? false + : /^[0-9A-Za-z_\.\-]*$/.test(keyName); + }, + + //Optimization for slow API's + getAllPairs : function(namespace, store){ + var keys = this.getKeys(namespace); + + if (!keys || !keys.length) + return; + + var values = this.getMultiple(keys, namespace); + for (var i = 0; i < keys.length && values; i++) { + if (values[i]) + store[keys[i]] = values[i]; + } + + return keys.length; + } + } + + +}; + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/core/lib/tween.js)SIZE(35419)TIME(Mon, 10 Jan 2011 10:53:11 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + + +/** + * The animation library that is used for the animations inside elements + * @default_private + */ +apf.tween = (function(apf) { + +var modules = { + //Animation Modules + left: function(oHtml, value){ + oHtml.style.left = value + PX; + }, + right: function(oHtml, value){ + oHtml.style.left = ""; + oHtml.style.right = value + PX; + }, + top: function(oHtml, value){ + oHtml.style.top = value + PX; + }, + bottom: function(oHtml, value){ + oHtml.style.top = ""; + oHtml.style.bottom = value + PX; + }, + width: function(oHtml, value, center){ + oHtml.style.width = value + PX; + }, + height: function(oHtml, value, center){ + oHtml.style.height = value + PX; + }, + scrollTop: function(oHtml, value, center){ + oHtml.scrollTop = value; + }, + scrollLeft: function(oHtml, value, center){ + oHtml.scrollLeft = value; + }, + "height-rsz": function(oHtml, value, center){ + oHtml.style.height = value + PX; + if (apf.hasSingleResizeEvent) + window.onresize(); + }, + mwidth: function(oHtml, value, info) { + var diff = apf.getDiff(oHtml); + oHtml.style.width = value + PX; + oHtml.style.marginLeft = -1 * (value / 2 + (parseInt(apf.getStyle(oHtml, + "borderLeftWidth")) || diff[0]/2) + (info.margin || 0)) + PX; + }, + mheight: function(oHtml, value, info) { + var diff = apf.getDiff(oHtml); + oHtml.style.height = value + PX; + oHtml.style.marginTop = (-1 * value / 2 - (parseInt(apf.getStyle(oHtml, + "borderTopWidth")) || diff[1]/2) + (info.margin || 0)) + PX; + }, + scrollwidth: function(oHtml, value){ + oHtml.style.width = value + PX; + oHtml.scrollLeft = oHtml.scrollWidth; + }, + scrollheight_old: function(oHtml, value){ + try { + oHtml.style.height = value + PX; + oHtml.scrollTop = oHtml.scrollHeight; + } + catch (e) { + alert(value) + } + }, + scrollheight: function(oHtml, value, info){ + var diff = apf.getHeightDiff(oHtml), + oInt = info.$int || oHtml; + + oHtml.style.height = Math.max((value + (info.diff || 0)), 0) + PX; + oInt.scrollTop = oInt.scrollHeight - oInt.offsetHeight - diff + + (info.diff || 0) - (apf.isGecko ? 16 : 0); //@todo where does this 16 come from?? + }, + scrolltop: function(oHtml, value){ + oHtml.style.height = value + PX; + oHtml.style.top = (-1 * value - 2) + PX; + oHtml.scrollTop = 0;//oHtml.scrollHeight - oHtml.offsetHeight; + }, + clipright: function(oHtml, value, center){ + oHtml.style.clip = "rect(auto, auto, auto, " + value + "px)"; + oHtml.style.marginLeft = (-1 * value) + PX; + }, + fade: function(oHtml, value){ + if (!apf.supportOpacity && apf.hasStyleFilters) + oHtml.style.filter = value == 1 ? "" : "alpha(opacity=" + parseInt(value * 100) + ")"; + else + oHtml.style.opacity = value; + }, + bgcolor: function(oHtml, value){ + oHtml.style.backgroundColor = value; + }, + textcolor: function(oHtml, value){ + oHtml.style.color = value; + }, + htmlcss : function(oHtml, value, obj){ + if (apf.hasStyleFilters && obj.type == "filter") + oHtml.style.filter = value == 1 ? "" : "progid:DXImageTransform.Microsoft.Alpha(opacity=" + value + ")"; + else + oHtml.style[obj.type] = value + (obj.needsPx ? PX : ""); + }, + transformscale: function(oHtml, value, obj) { + oHtml.style[obj.type] = SCALEA + parseFloat(value) + SCALEB; + }, + transformrotate: function(oHtml, value, obj) { + oHtml.style[obj.type] = ROTATEA + parseFloat(value) + ROTATEB; + }, + transformvalscale: function(value) { + return SCALEA + parseFloat(value) + SCALEB; + }, + transformvalrotate: function(value) { + return ROTATEA + parseFloat(value) + ROTATEB; + } +}; + +var ID = "id", + PX = "px", + NUM = "number", + TRANSVAL = "transformval", + TRANSFORM = "transform", + SCALE = "scale", + SCALEA = "scale(", + ROTATEA = "rotate(", + SCALEB = ")", + ROTATEB = "deg)", + CSSTIMING = ["linear", "ease-in", "ease-out", "ease", "ease-in-out", "cubic-bezier"], + CSSPROPS = { + "left" : "left", + "right" : "right", + "top" : "top", + "bottom" : "bottom", + "width" : "width", + "height" : "height", + "scrollTop" : false, + "scrollLeft" : false, + "mwidth" : false, + "mheight" : false, + "scrollwidth" : false, + "scrollheight": false, + "fade" : "opacity", + "opacity" : "opacity", + "bgcolor" : "background-color", + "textcolor" : "color", + "transform" : "transform" + }, + __pow = Math.pow, + __round = Math.round, + + queue = {}, + + current= null, + + setQueue = function(oHtml, stepFunction){ + var id = oHtml.getAttribute(ID); + if (!id) { + apf.setUniqueHtmlId(oHtml); + id = oHtml.getAttribute(ID); + } + + if (!queue[id]) + queue[id] = []; + + queue[id].push(stepFunction); + if (queue[id].length == 1) + stepFunction(0); + }, + + nextQueue = function(oHtml){ + var q = queue[oHtml.getAttribute(ID)]; + if (!q) return; + + q.shift(); //Remove current step function + + if (q.length) + q[0](0); + }, + + clearQueue = function(oHtml, bStop){ + var q = queue[oHtml.getAttribute(ID)]; + if (!q) return; + + if (bStop && current && current.control) + current.control.stop = true; + q.length = 0; + }, + + purgeQueue = function(oHtml) { + var id = oHtml.getAttribute(ID); + if (!id) { + apf.setUniqueHtmlId(oHtml); + id = oHtml.getAttribute(ID); + } + + for (var i in queue) { + if (i == id) + queue[i] = []; + } + }, + + /** + * Calculates all the steps of an animation between a + * begin and end value based on 3 tween strategies + */ + calcSteps = function(func, fromValue, toValue, nrOfSteps){ + var i = 0, + l = nrOfSteps - 1, + steps = [fromValue]; + + // backward compatibility... + if (typeof func == NUM) { + if (!func) + func = apf.tween.linear; + else if (func == 1) + func = apf.tween.easeInCubic; + else if (func == 2) + func = apf.tween.easeOutCubic; + } + + /* + func should have the following signature: + func(t, x_min, dx) + where 0 <= t <= 1, dx = x_max - x_min + + easeInCubic: function(t, x_min, dx) { + return dx * pow(t, 3) + x_min; + } + */ + for (i = 0; i < l; ++i) + steps.push(func(i / nrOfSteps, fromValue, toValue - fromValue)); + steps.push(toValue); + + return steps; + }, + + /** + * Calculates all the steps of an animation between a + * begin and end value for colors + */ + calcColorSteps = function(animtype, fromValue, toValue, nrOfSteps){ + var d2, d1, + c = apf.color.colorshex, + a = parseInt((c[fromValue] || fromValue).slice(1), 16), + b = parseInt((c[toValue] || toValue).slice(1), 16), + i = 0, + out = []; + + for (; i < nrOfSteps; i++){ + d1 = i / (nrOfSteps - 1), d2 = 1 - d1; + out[out.length] = "#" + ("000000" + + ((__round((a & 0xff) * d2 + (b & 0xff) * d1) & 0xff) | + (__round((a & 0xff00) * d2 + (b & 0xff00) * d1) & 0xff00) | + (__round((a & 0xff0000) * d2 + (b & 0xff0000) * d1) & 0xff0000)).toString(16)).slice(-6); + } + + return out; + }, + + /** + * Tweens a single property of a single element or html element from a + * start to an end value. + * Example: + * + * apf.tween.single(myDiv, { + * type : "left", + * from : 10, + * to : 100, + * anim : apf.tween.EASEIN + * }); + * + * Example: + * Multiple animations can be run after eachother + * by calling this function multiple times. + * + * apf.tween.single(myDiv, options).single(myDiv2, options2); + * + * @param {Element} oHtml the object to animate. + * @param {Object} info the animation settings. + * Properties: + * {String} type the property to be animated. These are predefined + * property handlers and can be added by adding a + * method to apf.tween with the name of the property + * modifier. Default there are several handlers available. + * Possible values: + * left Sets the left position + * right Sets the right position + * top Sets the top position + * bottom Sets the bottom position + * width Sets the horizontal size + * height Sets the vertical size + * scrollTop Sets the scoll position + * mwidth Sets the width and the margin-left to width/2 + * mheight Sets the height ant the margin-top to height/2 + * scrollwidth Sets the width an sets the scroll to the maximum size + * scrollheight Sets the height an sets the scroll to the maximum size + * scrolltop Sets the height and the top as the negative height value + * fade Sets the opacity property + * bgcolor Sets the background color + * textcolor Sets the text color + * {Number, String} from the start value of the animation + * {Number, String} to the end value of the animation + * {Number} [steps] the number of steps to divide the tween in + * {Number} [interval] the time between each step + * {Number} [anim] the distribution of change between the step over the entire animation + * {Boolean} [color] whether the specified values are colors + * {Mixed} [userdata] any data you would like to have available in your callback methods + * {Function} [onfinish] a function that is called at the end of the animation + * {Function} [oneach] a function that is called at each step of the animation + * {Object} [control] an object that can stop the animation at any point + * Methods: + * stop set on the object passed . + */ + single = function(oHtml, info){ + info = apf.extend({steps: 10, interval: 5, anim: apf.tween.linear, control: {}}, info); + info.steps = Math.ceil(info.steps * apf.animSteps); + info.interval = Math.ceil(info.interval * apf.animInterval); + + if (oHtml.nodeFunc > 100) { + info.$int = oHtml.$int; + oHtml = oHtml.$ext; + } + try { //@TODO hack where currentStyle is still undefined + if ("fixed|absolute|relative".indexOf(apf.getStyle(oHtml, "position")) == -1) + oHtml.style.position = "relative"; + } catch(e){} + + var useCSSAnim = (apf.supportCSSAnim && apf.supportCSSTransition && CSSPROPS[info.type]), + isTransform = (info.type == TRANSFORM); + + info.method = useCSSAnim ? info.type : isTransform + ? modules[TRANSFORM + (info.subType || SCALE)] + : modules[info.type] + ? modules[info.type] + : (info.needsPx = needsPix[info.type] || false) + ? modules.htmlcss + : modules.htmlcss; + + + if (!info.method) + throw new Error(apf.formatErrorString(0, this, + "Single Value Tween", + "Could not find method for tweening operation '" + + info.type + "'")); + + + if (useCSSAnim) { + var type = CSSPROPS[info.type]; + if (type === false) + return apf.tween; + info.type = type || info.type; + if (isTransform) { + if (!info.subType) + info.subType = SCALE; + info.type = apf.supportCSSAnim; + } + + var transform = (isTransform) + ? modules[TRANSVAL + (info.subType || SCALE)] + : null; + + oHtml.style[info.type] = isTransform + ? transform(info.from) + : info.from + (needsPix[info.type] ? PX : ""); + $setTimeout(function() { + oHtml.style[info.type] = isTransform + ? transform(info.to) + : info.to + (needsPix[info.type] ? PX : ""); + oHtml.offsetTop; //force style recalc + oHtml.style[apf.cssPrefix + "Transition"] = info.type + " " + ((info.steps + * info.interval) / 1000) + "s " + + CSSTIMING[info.anim || 0]; + var f = function() { + if (info.onfinish) + info.onfinish(oHtml, info.userdata); + oHtml.style[apf.cssPrefix + "Transition"] = ""; + oHtml.removeEventListener(apf.cssAnimEvent, f); + }; + oHtml.addEventListener(apf.cssAnimEvent, f); + }); + return apf.tween; + } + + if (info.control) { + info.control.state = apf.tween.RUNNING; + info.control.stop = function(){ + info.control.state = apf.tween.STOPPING; + clearQueue(oHtml); + if (info.onstop) + info.onstop(oHtml, info.userdata); + } + } + + var steps = info.color + ? calcColorSteps(info.anim, info.from, info.to, info.steps) + : calcSteps(info.anim, parseFloat(info.from), parseFloat(info.to), info.steps), + stepFunction = function(step){ + if (info.control && info.control.state) { + info.control.state = apf.tween.STOPPED; + return; + } + + current = info; + + if (info.onbeforeeach + && info.onbeforeeach(oHtml, info.userdata) === false) + return; + + try { + info.method(oHtml, steps[step], info); + } + catch (e) {} + + if (info.oneach) + info.oneach(oHtml, info.userdata); + + if (step < info.steps) + return $setTimeout(function(){stepFunction(step + 1)}, info.interval); + + current = null; + if (info.control) + info.control.state = apf.tween.STOPPED; + if (info.onfinish) + info.onfinish(oHtml, info.userdata); + + nextQueue(oHtml); + }; + + if (info.type.indexOf("scroll") > -1) + purgeQueue(oHtml); + setQueue(oHtml, stepFunction); + + return apf.tween; + }, + + /** + * Tweens multiple properties of a single element or html element from a + * start to an end value. + * Example: + * Animating both the left and width at the same time. + * + * apf.tween.multi(myDiv, { + * anim : apf.tween.EASEIN + * tweens : [{ + * type : "left", + * from : 10, + * to : 100, + * }, + * { + * type : "width", + * from : 100, + * to : 400, + * }] + * }); + * + * Example: + * Multiple animations can be run after eachother + * by calling this function multiple times. + * + * apf.tween.multi(myDiv, options).multi(myDiv2, options2); + * + * @param {Element} oHtml the object to animate. + * @param {Object} info the settings of the animation. + * Properties: + * {Number} [steps] the number of steps to divide the tween in + * {Number} [interval] the time between each step + * {Number} [anim] the distribution of change between the step over + * the entire animation + * {Function} [onfinish] a function that is called at the end of the animation + * {Function} [oneach] a function that is called at each step of the animation + * {HTMLElement} [oHtml] another html element to animate. + * {Object} [control] an object that can stop the animation at any point + * Properties: + * {Boolean} stop whether the animation should stop. + * {Array} [tweens] a collection of simple objects specifying the single + * value animations that are to be executed simultaneously. + * (for the properties of these single tweens see the + * single tween method). + */ + multi = function(oHtml, info){ + info = apf.extend({steps: 10, interval: 5, anim: apf.tween.linear, control: {}}, info); + info.steps = Math.ceil(info.steps * apf.animSteps); + info.interval = Math.ceil(info.interval * apf.animInterval); + + if (oHtml.nodeFunc > 100) { + info.$int = oHtml.$int; + oHtml = oHtml.$ext; + } + + var animCSS, isTransform, + useCSSAnim = apf.supportCSSAnim && apf.supportCSSTransition, + hasCSSAnims = false, + cssDuration = ((info.steps * info.interval) / 1000), + cssAnim = CSSTIMING[info.anim || 0], + steps = [], + stepsTo = [], + i = 0, + l = info.tweens.length; + + for (; i < l; i++) { + var data = info.tweens[i]; + + if (data.oHtml && data.oHtml.nodeFunc > 100) { + data.$int = data.oHtml.$int; + data.oHtml = data.oHtml.$ext; + } + + animCSS = (useCSSAnim && CSSPROPS[data.type]); + isTransform = (data.type == TRANSFORM); + if (isTransform) { + if (!data.subType) + data.subType = SCALE; + data.type = apf.supportCSSAnim; + } + + data.method = animCSS + ? data.type + : isTransform + ? modules[TRANSFORM + (data.subType)] + : modules[data.type] + ? modules[data.type] + : (data.needsPx = needsPix[data.type] || false) + ? modules.htmlcss + : modules.htmlcss; + + + + if (!data.method) + throw new Error(apf.formatErrorString(0, this, + "Multi Value Tween", + "Could not find method for tweening operation '" + + data.type + "'")); + + + if (animCSS) { + var type = isTransform ? data.type : CSSPROPS[data.type]; + data.type = type || data.type; + var transform = modules[TRANSVAL + (data.subType)] + + oHtml.style[data.type] = isTransform + ? transform(data.from) + : data.from + (needsPix[data.type] ? PX : ""); + stepsTo.push([data.type, isTransform + ? transform(data.to) + : data.to + (needsPix[data.type] ? PX : "")]); + steps.push(data.type + " " + cssDuration + "s " + cssAnim + " 0"); + + hasCSSAnims = true; + } + else { + steps.push(data.color + ? calcColorSteps(info.anim, data.from, data.to, info.steps) + : calcSteps(info.anim, parseFloat(data.from), parseFloat(data.to), info.steps)); + } + } + + if (hasCSSAnims) { + oHtml.style[apf.cssPrefix + "Transition"] = steps.join(","); + oHtml.offsetTop; //force style recalc + var count = 0, + func = function() { + count++; + if (count == stepsTo.length) { + if (info.onfinish) + info.onfinish(oHtml, info.userdata); + oHtml.style[apf.cssPrefix + "Transition"] = ""; + oHtml.removeEventListener(apf.cssAnimEvent, func); + } + }; + oHtml.addEventListener(apf.cssAnimEvent, func, false); + for (var k = 0, j = stepsTo.length; k < j; k++) + oHtml.style[stepsTo[k][0]] = stepsTo[k][1]; + return apf.tween; + } + + if (info.control) { + info.control.state = apf.tween.RUNNING; + info.control.stop = function(){ + info.control.state = apf.tween.STOPPING; + clearQueue(oHtml); + if (info.onstop) + info.onstop(oHtml, info.userdata); + } + } + + var tweens = info.tweens, + stepFunction = function(step){ + if (info.control && info.control.state) { + info.control.state = apf.tween.STOPPED; + return; + } + + current = info; + + try { + for (var i = 0; i < steps.length; i++) { + tweens[i].method(tweens[i].oHtml || oHtml, + steps[i][step], tweens[i]); + } + } catch (e) {} + + if (info.oneach) + info.oneach(oHtml, info.userdata); + + if (step < info.steps) + return $setTimeout(function(){stepFunction(step + 1)}, info.interval); + + current = null; + if (info.control) + info.control.state = apf.tween.STOPPED; + if (info.onfinish) + info.onfinish(oHtml, info.userdata); + + nextQueue(oHtml); + }; + + setQueue(oHtml, stepFunction); + + return apf.tween; + }, + + /** + * Tweens an element or html element from it's current state to a css class. + * Example: + * Multiple animations can be run after eachother by calling this function + * multiple times. + * + * apf.tween.css(myDiv, 'class1').multi(myDiv2, 'class2'); + * + * @param {Element} oHtml the object to animate. + * @param {String} className the classname that defines the css properties to be set or removed. + * @param {Object} info the settings of the animation. + * Properties: + * {Number} [steps] the number of steps to divide the tween in + * {Number} [interval] the time between each step + * {Number} [anim] the distribution of change between the step over the entire animation + * {Function} [onfinish] a function that is called at the end of the animation + * {Function} [oneach] a function that is called at each step of the animation + * {Object} [control] an object that can stop the animation at any point + * Properties: + * {Boolean} stop whether the animation should stop. + * @param {Boolean} remove whether the class is set or removed from the element or html element + */ + css = function(oHtml, className, info, remove){ + (info = info || {}).tweens = []; + + if (oHtml.nodeFunc > 100) + oHtml = oHtml.$ext; + + if (remove) + apf.setStyleClass(oHtml, "", [className]); + + var resetAnim = function(remove, callback){ + if (remove) + apf.setStyleClass(oHtml, "", [className]); + else + apf.setStyleClass(oHtml, className); + + //Reset CSS values + for (var i = 0; i < info.tweens.length; i++){ + if (info.tweens[i].type == "filter") + continue; + + oHtml.style[info.tweens[i].type] = ""; + } + + if (callback) + callback.apply(this, arguments); + } + + var onfinish = info.onfinish, + onstop = info.onstop; + info.onfinish = function(){resetAnim(remove, onfinish);} + info.onstop = function(){resetAnim(!remove, onstop);} + + var result, newvalue, curvalue, j, isColor, style, rules, i, + tweens = {}; + for (i = 0; i < document.styleSheets.length; i++) { + rules = document.styleSheets[i][apf.styleSheetRules]; + for (j = rules.length - 1; j >= 0; j--) { + var rule = rules[j]; + + if (!rule.style || !rule.selectorText.match("\." + className + "$")) + continue; + + for (style in rule.style) { + if (!rule.style[style] || cssProps.indexOf("|" + style + "|") == -1) + continue; + + if (style == "filter") { + if (!rule.style[style].match(/opacity\=([\d\.]+)/)) + continue; + newvalue = RegExp.$1; + + result = (apf.getStyleRecur(oHtml, style) || "") + .match(/opacity\=([\d\.]+)/); + curvalue = result ? RegExp.$1 : 100; + isColor = false; + + if (newvalue == curvalue) { + if (remove) curvalue = 100; + else newvalue = 100; + } + } + else { + newvalue = remove && oHtml.style[style] || rule.style[style]; + if (remove) oHtml.style[style] = ""; + curvalue = apf.getStyleRecur(oHtml, style); + isColor = style.match(/color/i) ? true : false; + } + + tweens[style] = { + type : style, + from : (isColor ? String : parseFloat)(remove + ? newvalue + : curvalue), + to : (isColor ? String : parseFloat)(remove + ? curvalue + : newvalue), + color : isColor, + needsPx : needsPix[style.toLowerCase()] || false + }; + } + } + } + + for (var prop in tweens) + info.tweens.push(tweens[prop]); + + if (remove) + apf.setStyleClass(oHtml, className); + + return multi(oHtml, info); + }, + + cssRemove = function(oHtml, className, info){ + css(oHtml, className, info, true); + }, + + needsPix = { + "left" : true, + "top" : true, + "bottom" : true, + "right" : true, + "width" : true, + "height" : true, + "fontSize" : true, + "lineHeight" : true, + "textIndent" : true, + "marginLeft" : true, + "marginTop" : true, + "marginRight" : true, + "marginBottom": true + }, + + cssProps = "|backgroundColor|backgroundPosition|color|width|filter" + + "|height|left|top|bottom|right|fontSize" + + "|letterSpacing|lineHeight|textIndent|opacity" + + "|paddingLeft|paddingTop|paddingRight|paddingBottom" + + "|borderLeftWidth|borderTopWidth|borderRightWidth|borderBottomWidth" + + "|borderLeftColor|borderTopColor|borderRightColor|borderBottomColor" + + "|marginLeft|marginTop|marginRight|marginBottom" + + "|transform|", // transforms are special and get special treatment + cssTransforms = "|scale|rotate|"; + +return { + single: single, + multi: multi, + css: css, + cssRemove: cssRemove, + clearQueue: clearQueue, + addModule: function(name, func, force) { + if (typeof name != "string" || typeof func != "function" || (modules[name] && !force)) + return this; + modules[name] = func; + return this; + }, + /** Linear tweening method */ + NORMAL: 0, + /** Ease-in tweening method */ + EASEIN: 1, + /** Ease-out tweening method */ + EASEOUT: 2, + + RUNNING: 0, + STOPPING: 1, + STOPPED: 2, + + calcColorSteps: calcColorSteps, + + linear: function(t, x_min, dx) { + return dx * t + x_min; + }, + easeInQuad: function(t, x_min, dx) { + return dx * __pow(t, 2) + x_min; + }, + easeOutQuad: function(t, x_min, dx) { + return -dx * t * (t - 2) + x_min; + }, + easeInOutQuad: function(t, x_min, dx) { + if ((t /= .5) < 1) + return dx / 2 * t * t + x_min; + return -dx / 2 * ((--t) * (t - 2) - 1) + x_min; + }, + easeInCubic: function(t, x_min, dx) { + return dx * __pow(t, 3) + x_min; + }, + easeOutCubic: function(t, x_min, dx) { + return dx * (__pow(t - 1, 3) + 1) + x_min; + }, + easeInOutCubic: function(t, x_min, dx) { + if ((t /= .5) < 1) + return dx / 2 * __pow(t, 3) + x_min; + return dx / 2 * (__pow(t - 2, 3) + 2) + x_min; + }, + easeInQuart: function(t, x_min, dx) { + return dx * __pow(t, 4) + x_min; + }, + easeOutQuart: function(t, x_min, dx) { + return -dx * (__pow(t - 1, 4) - 1) + x_min; + }, + easeInOutQuart: function(t, x_min, dx) { + if ((t /= .5) < 1) + return dx / 2 * __pow(t, 4) + x_min; + return -dx / 2 * (__pow(t - 2, 4) - 2) + x_min; + }, + easeInQuint: function(t, x_min, dx) { + return dx * __pow(t, 5) + x_min; + }, + easeOutQuint: function(t, x_min, dx) { + return dx * (__pow(t - 1, 5) + 1) + x_min; + }, + easeInOutQuint: function(t, x_min, dx) { + if ((t /= .5) < 1) + return dx / 2 * __pow(t, 5) + x_min; + return dx / 2 * (__pow(t - 2, 5) + 2) + x_min; + }, + easeInSine: function(t, x_min, dx) { + return -dx * Math.cos(t * (Math.PI / 2)) + dx + x_min; + }, + easeOutSine: function(t, x_min, dx) { + return dx * Math.sin(t * (Math.PI / 2)) + x_min; + }, + easeInOutSine: function(t, x_min, dx) { + return -dx / 2 * (Math.cos(Math.PI * t) - 1) + x_min; + }, + easeInExpo: function(t, x_min, dx) { + return (t == 0) ? x_min : dx * __pow(2, 10 * (t - 1)) + x_min; + }, + easeOutExpo: function(t, x_min, dx) { + return (t == 1) ? x_min + dx : dx * (-__pow(2, -10 * t) + 1) + x_min; + }, + easeInOutExpo: function(t, x_min, dx) { + if (t == 0) + return x_min; + if (t == 1) + return x_min + dx; + if ((t /= .5) < 1) + return dx / 2 * __pow(2, 10 * (t - 1)) + x_min; + return dx / 2 * (-__pow(2, -10 * --t) + 2) + x_min; + }, + easeInCirc: function(t, x_min, dx) { + return -dx * (Math.sqrt(1 - t * t) - 1) + x_min; + }, + easeOutCirc: function(t, x_min, dx) { + return dx * Math.sqrt(1 - (t -= 1) * t) + x_min; + }, + easeInOutCirc: function(t, x_min, dx) { + if ((t /= .5) < 1) + return -dx / 2 * (Math.sqrt(1 - t * t) - 1) + x_min; + return dx / 2 * (Math.sqrt(1 - (t -= 2) * t) + 1) + x_min; + }, + easeInElastic: function(t, x_min, dx) { + var s = 1.70158, + p = .3, + a = dx; + if (t == 0) + return x_min; + if (t == 1) + return x_min + dx; + if (!a || a < Math.abs(dx)) { + a = dx; + s = p / 4; + } + else + s = p / (2 * Math.PI) * Math.asin (dx / a); + return -(a * __pow(2, 10 * (t -= 1)) * Math.sin((t * 1 - s) * (2 * Math.PI) / p)) + x_min; + }, + easeOutElastic: function(t, x_min, dx) { + var s = 1.70158, + p = .3, + a = dx; + if (t == 0) + return x_min; + if (t == 1) + return x_min + dx; + if (a < Math.abs(dx)) { + a = dx; + s = p / 4; + } + else { + s = p / (2 * Math.PI) * Math.asin(dx / a); + } + return a * __pow(2, -10 * t) * Math.sin((t - s) * (2 * Math.PI) / p) + dx + x_min; + }, + easeInOutElastic: function(t, x_min, dx) { + var s = 1.70158, + p = 0, + a = dx; + if (t == 0) + return x_min; + if ((t / 2) == 2) + return x_min + dx; + if (!p) + p = .3 * 1.5; + if (a < Math.abs(dx)) { + a = dx; + s = p / 4; + } + else { + s = p / (2 * Math.PI) * Math.asin(dx / a); + } + if (t < 1) + return -.5 * (a * __pow(2, 10 * (t -= 1)) * Math.sin((t - s) * (2 * Math.PI) / p)) + x_min; + return a * __pow(2, -10 * (t -= 1)) * Math.sin((t - s) * (2 * Math.PI) / p) * .5 + dx + x_min; + }, + easeInBack: function(t, x_min, dx) { + var s = 1.70158; + return dx * __pow(t, 2) * ((s + 1) * t - s) + x_min; + }, + easeOutBack: function(t, x_min, dx) { + var s = 1.70158; + return dx * ((t -= 1) * t * ((s + 1) * t + s) + 1) + x_min; + }, + easeInOutBack: function(t, x_min, dx) { + var s = 1.70158; + if ((t / 2) < 1) + return dx / 2 * (t * t * (((s *= (1.525)) + 1) * t - s)) + x_min; + return dx / 2 * ((t -= 2) * t * (((s *= (1.525)) + 1) * t + s) + 2) + x_min; + }, + easeInBounce: function(t, x_min, dx) { + return dx - apf.tween.easeOutBounce(1 - t, 0, dx) + x_min; + }, + easeOutBounce: function(t, x_min, dx) { + if (t < (1 / 2.75)) + return dx * (7.5625 * t * t) + x_min; + else if (t < (2 / 2.75)) + return dx * (7.5625 * (t -= (1.5 / 2.75)) * t + .75) + x_min; + else if (t < (2.5 / 2.75)) + return dx * (7.5625 * (t -= (2.25 / 2.75)) * t + .9375) + x_min; + else + return dx * (7.5625 * (t -= (2.625 / 2.75)) * t + .984375) + x_min; + }, + easeInOutBounce: function(t, x_min, dx) { + if (t < 1 / 2) + return apf.tween.easeInBounce(t * 2, 0, dx) * .5 + x_min; + return apf.tween.easeOutBounce(t * 2 - 1, 0, dx) * .5 + dx * .5 + x_min; + } +}; + +})(apf); + + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/core/lib/uirecorder.js)SIZE(86788)TIME(Mon, 10 Jan 2011 10:53:11 GMT)*/ + + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/core/lib/vector.js)SIZE(46289)TIME(Mon, 10 Jan 2011 10:53:11 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/core/lib/xmldb.js)SIZE(40521)TIME(Mon, 10 Jan 2011 10:53:11 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + + +/** + * The xml database object provides local storage for xml data. This object + * routes all changes to the xml data to the data bound objects. It further + * provides utility functions for xml handling. + * + * @constructor + * @apfclass + * + * @author Ruben Daniels (ruben AT ajax DOT org) + * @version %I%, %G% + * @since 0.8 + * + * @default_private + */ +apf.xmldb = new (function(){ + this.xmlDocTag = "a_doc"; + this.xmlIdTag = "a_id"; + this.xmlListenTag = "a_listen"; + this.htmlIdTag = "id"; + this.disableRDB = false; + + this.$xmlDocLut = []; + this.$nodeCount = {}; + + var cleanRE = /(?:a_doc|a_id|a_listen|a_loaded)=(?:"|')[^'"]+(?:"|')/g, + whiteRE = />[\s\n\r\t]+ -1) + this.notifyQueued(); //empty queue + + var listen, uId, uIds, i, j, hash, info, amlNode, runTimer, found, done = {}; + while (loopNode && loopNode.nodeType == 1) { + //Get List of Node this.$listeners ID's + listen = loopNode.getAttribute(this.xmlListenTag); + + if (listen) { + uIds = listen.split(";"); + + for (i = 0; i < uIds.length; i++) { + uId = uIds[i]; + if (!uId || done[uId]) continue; + done[uId] = true; + + //Property support + /*if (uId.charAt(0) == "p") { + uId = uId.split("|"); + + //@todo apf3.0 should this be exactly like in class.js? + //@todo optimize this to check the async flag: parsed[3] & 4 + + amlNode = apf.all[uId[1]]; //It's possible the aml node dissapeared in this loop. + if (amlNode) { + var model = apf.all[uId[3]]; + var xpath = model.$propBinds[uId[1]][uId[2]].root; + + amlNode.$execProperty(uId[2], xpath + ? model.data.selectSingleNode(xpath) + : model.data); + } + continue; + }*/ + + hash = notifyQueue[uId]; + if (!hash) + notifyQueue[uId] = hash = []; + + // Filtering + if (!apf.isO3 && "|update|attribute|text|".indexOf("|" + action + "|") > -1) { + found = false; + for (j = 0; j < hash.length; j++) { + if (hash[j] && xmlNode == hash[j][1] + && "|update|attribute|text|" + .indexOf("|" + hash[j][0] + "|") > -1) { + hash[j] = null; + found = true; + continue; + } + } + + hash.push([action, xmlNode, loopNode, undoObj, oParent]); + runTimer = true; + continue; + } + + //!this.delayUpdate && <- that doesnt work because of information that is destroyed + if (apf.isO3 || "|remove|move-away|move|add|".indexOf("|" + action + "|") > -1) { + if (this.$listeners[uId]) { + this.$listeners[uId]([action, xmlNode, + loopNode, undoObj, oParent]); + } + /*amlNode = apf.all[uId]; + if (amlNode) + amlNode.$xmlUpdate(action, xmlNode, + loopNode, undoObj, oParent);*/ + } + else { + hash.push([action, xmlNode, loopNode, undoObj, oParent]); + runTimer = true; + } + } + } + + //Go one level up + loopNode = loopNode.parentNode || nextloop; + if (loopNode == nextloop) + nextloop = null; + } + + if (undoObj && !this.delayUpdate) { + //Ok this was an action let's not delay execution + apf.xmldb.notifyQueued(); + } + else if (runTimer) { + clearTimeout(notifyTimer); + //@todo find a better solution for this (at the end of a event stack unroll) + this.$hasQueue = true; + notifyTimer = apf.setZeroTimeout(function(){ + //this.$hasQueue = true; + apf.xmldb.notifyQueued(); + }); + } + }; + + /** + * @todo in actiontracker - add stack auto purging + * - when undo item is purged which was a removed, remove cache item + * @todo shouldn't the removeNode method remove all this.$listeners? + * @todo rename to processQueue + * @private + */ + this.notifyQueued = function(){ + this.$hasQueue = false; + + var myQueue = notifyQueue; + notifyQueue = {}; + + clearTimeout(notifyTimer); + for (var uId in myQueue) { + if (!uId) continue; + + var q = myQueue[uId]; + var func = this.$listeners[uId]; + //!amlNode || + if (!q || !func) + continue; + + //Run queue items + for (var i = 0; i < q.length; i++) { + if (!q[i]) + continue; + + //Update xml data + //amlNode.$xmlUpdate.apply(amlNode, q[i]); + func(q[i]); + } + } + + + } + + /** + * @private + */ + this.notifyListeners = function(xmlNode){ + //This should be done recursive + var listen = xmlNode.getAttribute(apf.xmldb.xmlListenTag); + if (listen) { + listen = listen.split(";"); + for (var j = 0; j < listen.length; j++) { + apf.all[listen[j]].$xmlUpdate("synchronize", xmlNode, xmlNode); + //load(xmlNode); + } + } + }; + + + /** + * Sends Message through transport to tell remote databound this.$listeners + * that data has been changed + * @private + */ + this.applyRDB = function(args, undoObj){ + if (apf.xmldb.disableRDB) + return; + + var xmlNode = undoObj.localName || !undoObj.xmlNode + ? args[1] && args[1].length && args[1][0] || args[1] + : undoObj.xmlNode; + + if (xmlNode.nodeType == 2) + xmlNode = xmlNode.ownerElement || xmlNode.selectSingleNode(".."); + + var mdlId = apf.xmldb.getXmlDocId(xmlNode), + model = apf.nameserver.get("model", mdlId); + if (!model && apf.isO3) + model = self[mdlId]; + if (!model) { + if (!apf.nameserver.getAll("remote").length) + return; + + apf.console.log("Could not find model '" + mdlId + "' for Remote DataBinding connection, not sending change"); + + return; + } + + if (!model.rdb) return; + var rdb = model.rdb; + + // Add the messages to the undo object + if (undoObj.action) + rdb.$queueMessage(args, model, undoObj); + // Or send message now + else { + clearTimeout(rdb.queueTimer); + + rdb.$queueMessage(args, model, rdb); + // use a timeout to batch consecutive calls into one RDB call + rdb.queueTimer = $setTimeout(function() { + rdb.$processQueue(rdb); + }); + } + + }; + + + /** + * @private + */ + this.copyConnections = function(fromNode, toNode){ + //This should copy recursive + try { + toNode.setAttribute(this.xmlListenTag, fromNode.getAttribute(this.xmlListenTag)); + } + catch (e) {} + try { + toNode.setAttribute(this.xmlIdTag, fromNode.getAttribute(this.xmlIdTag)); + } + catch (e) {} + }; + + /** + * @private + */ + this.cleanXml = function(xml) { + if (typeof xml != "string") + return xml; + return xml.replace(cleanRE, "").replace(whiteRE, "><"); + }; + + /** + * @private + */ + this.cleanNode = function(xmlNode){ + try { + var i, nodes = xmlNode.selectNodes("descendant-or-self::node()[@" + this.xmlListenTag + "]"); + for (i = nodes.length - 1; i >= 0; i--) + nodes[i].removeAttribute(this.xmlListenTag); + nodes = xmlNode.selectNodes("descendant-or-self::node()[@" + this.xmlIdTag + "]"); + for (i = nodes.length - 1; i >= 0; i--) + nodes[i].removeAttribute(this.xmlIdTag); + nodes = xmlNode.selectNodes("descendant-or-self::node()[@" + this.xmlDocTag + "]"); + for (i = nodes.length - 1; i >= 0; i--) + nodes[i].removeAttribute(this.xmlDocTag); + nodes = xmlNode.selectNodes("descendant-or-self::node()[@a_loaded]"); + for (i = nodes.length - 1; i >= 0; i--) + nodes[i].removeAttribute("a_loaded"); + } + catch (e) {} + + return xmlNode; + }; + + /** + * Returns a copy of the passed {@link term.datanode data node}. Bound + * data nodes contain special attributes to track them. These attributes + * are removed from the copied node when using this method. + * + * @param {XMLElement} xmlNode the {@link term.datanode data node} to copy. + * @return {XMLElement} the copy of the {@link term.datanode data node}. + */ + this.copy = + this.getCleanCopy = + apf.getCleanCopy = function(xmlNode){ + return apf.xmldb.cleanNode(xmlNode.cloneNode(true)); + }; + + /** + * Unbind all APF Elements from a certain Form + * @private + */ + this.unbind = function(frm){ + //Loop through objects of all apf + for (var lookup = {}, i = 0; i < frm.apf.all.length; i++) + if (frm.apf.all[i] && frm.apf.all[i].unloadBindings) + lookup[frm.apf.all[i].unloadBindings()] = true; + + //Remove Listen Nodes + for (var k = 0; k < this.$xmlDocLut.length; k++) { + + if (!this.$xmlDocLut[k]) continue; + + + var Nodes = this.$xmlDocLut[k].selectNodes("//self::node()[@" + + this.xmlListenTag + "]"); + if (!Nodes) continue; + + //Loop through Nodes and rebuild listen array + for (var i = 0; i < Nodes.length; i++) { + var listen = Nodes[i].getAttribute(this.xmlListenTag).split(";"); + for (var nListen = [], j = 0; j < listen.length; j++) + if (!lookup[listen[j]]) + nListen.push(listen[j]); + + //Optimization?? + if (nListen.length != listen.length) + Nodes[i].setAttribute(this.xmlListenTag, nListen.join(";")); + } + } + }; + + /** + * @private + * @todo xml doc leakage + */ + this.getXmlDocId = function(xmlNode, model){ + var docEl = xmlNode.ownerDocument.documentElement; + if (!apf.isChildOf(docEl, xmlNode)) + docEl = xmlNode; + + var docId = (docEl || xmlNode).getAttribute(this.xmlDocTag) + || this.$xmlDocLut.indexOf(docEl || xmlNode.ownerDocument || xmlNode); + + if (model && apf.nameserver.get("model", docId) != model) { + docId = null; + docEl = xmlNode; + } + + if (!docId || docId == -1) { + docId = this.$xmlDocLut.push(docEl || xmlNode.ownerDocument || xmlNode) - 1; + if (docEl) + docEl.setAttribute(this.xmlDocTag, String(docId)); + } + + if (model) + apf.nameserver.register("model", docId, model); + + + return docId; + }; +}); + + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/core/lib/draw/canvas.js)SIZE(21818)TIME(Mon, 10 Jan 2011 10:53:11 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + +apf.draw.canvas = { + + //---------------------------------------------------------------------- + + // initialization + + //---------------------------------------------------------------------- + + initRoot : function(r){ + + var canvas = document.createElement("canvas"); + canvas.setAttribute("width",r.canvaswidth = r.width); + canvas.setAttribute("height",r.canvasheight = r.height); + canvas.className = "canvas"; + r.$int.appendChild(canvas); + r.canvaselem = canvas; + r.canvas = canvas.getContext('2d'); + r.canvas.translate(0.5,0.5); + r.imgcache = {}; + return this; + }, + + resizeRoot : function(r){ + r.canvaselem.setAttribute("width",r.width); + r.canvaselem.setAttribute("height",r.height); + for(var n = r.canvaselem.nextSibling;n != null;n = n.nextSibling){ + n.style.width = r.width+'px', n.style.height = r.height+'px'; + } + }, + + initLayer : function(l, r){ + l.imgcache = r.imgcache; + l.canvas = r.canvas; + l.textroot = r.$int; + l.dx = l.left; + l.dy = l.top; + l.dw = l.width; + l.dh = l.height; + l.ds = 1; + + l._styles = []; + l._htmljoin = []; + return this; + }, + + resizeLayer : function(l, r){ + // update layer position, and perhaps z-order or all items in a _vmlgroup. + l.dx = l.left; + l.dy = l.top; + l.dw = l.width; + l.dh = l.height; + }, + + destroyLayer : function(l){ + // lets clear our shite up + }, + + beginLayer : function(l){ + this.l = l,this.mx="",this.my="",this.last=null; this.tiletrans = 0; + this.dodraw = 0; + // check if we have styles.. ifso clean that shit up + if(l._styles.length){ + for(var j = l._styles.length,i=0;i>16)&0xff),", + "',',((_q>>8)&0xff),',',(_q&0xff),',',", + "(",i/(len-1),"*_o+",1-(i/(len-1)),"*_r)", + ",')'].join(''))",")_t[",i,"]=_l,_m=1;"); + }else{ + var t = parseInt((this.colors[fill[len-i-1].toLowerCase()] || + fill[len-i-1]).slice(1),16); + s.push("if(_t[",i,"]!=(_l=", + "['rgba(",(t>>16)&0xff, + ",",(t>>8)&0xff,",",t&0xff,",',","(",i/(len-1),"*_o+", + 1-(i/(len-1)),"*_r),')'].join(''))", + ")_t[",i,"]=_l,_m=1;"); + } + } + s.push("if(_s._angle!=(_u=(",style.angle,")*2*p) || _m){", + "_s._grad=_q=_c.createLinearGradient(", + "dtx+(__sin(_s._angle=_u)*0.5+0.5)*dw,", + "dty+(__cos(_u)*0.5+0.5)*dh,", + "dtx+(__sin(p+_u)*0.5+0.5)*dw,", + "dty+(__cos(p+_u)*0.5+0.5)*dh);"); + for(i=0;i>16)&0xff)+ + ','+((a>>8)&0xff)+','+((a)&0xff)+','+(u*o+(1-u)*r)+')'); + } + style._gradient = g; + s.push("_c.fillStyle=_styles[",style._id,"]._gradient;"); + } + } else { + if(this.isDynamic(fill) || pstyle.fill != fill) + s.push("_c.fillStyle=",this.getColor(fill),";"); + } + } + if(style.stroke!== undefined){ + fillmode |= 2; + if(this.isDynamic(style.stroke) || pstyle.stroke != style.stroke) + s.push("_c.strokeStyle=",this.getColor(style.stroke),";"); + + if(this.isDynamic(style.weight) || pstyle.weight != style.weight) + s.push("_c.lineWidth=",style.weight,";"); + } + this.fillopacity = ""; + this.strokeopacity = ""; + this.fillmode = fillmode; + switch(fillmode){ + case 3:// check if our fillopacity != stroke opacity, ifso we create switches between filling and stroking + if(style.fillopacity != style.strokeopacity ){ + this.fillopacity ="_c.globalAlpha="+style.fillopacity+";"; + this.strokeopacity ="_c.globalAlpha="+style.strokeopacity+";"; + }else{ + if(this.isDynamic(style.fillopacity) || style.fillopacity != pstyle.fillopacity) + s.push("_c.globalAlpha=",style.fillopacity,";"); + } + break; + case 2: + if(this.isDynamic(style.strokeopacity) || style.strokeopacity != pstyle.strokeopacity) + s.push("_c.globalAlpha=",style.strokeopacity,";"); + break; + case 1: + if(this.isDynamic(style.fillopacity) || style.fillopacity != pstyle.fillopacity) + s.push("_c.globalAlpha=",style.fillopacity,";"); + break; + } + return s.join(''); + }, + + + + moveTo : function(x,y){ + // check our mode. if its 3 we need to cache it + return "_c.moveTo("+x+this.mx+","+y+this.my+");\n"; + }, + lineTo : function(x, y){ + this.dodraw= 1; + return "_c.lineTo("+x+this.mx+","+y+this.my+");\n"; + }, + lineH : function(x,y,w){ + this.dodraw = 1; + return ["_c.moveTo(",x,this.mx,",",y,this.my,");", + "_c.lineTo(",x,this.mx,"+",w,",",y,this.my,");\n"].join(''); + }, + lineV : function(x,y,h){ + this.dodraw = 1; + return ["_c.moveTo(",x,this.mx,",",y,this.my,");", + "_c.lineTo(",x,this.mx,",",y,this.my,"+",h,");\n"].join(''); + }, + dot : function(x,y){ + this.dodraw = 1; + return ["_c.moveTo(",x,this.mx,",",y,this.my,");", + "_c.lineTo(",x,this.mx,",",y,this.my,");\n"].join(''); + }, + circle : function( x,y,r,s,e,c ){ + this.dodraw = 1; + if(!s)s='0'; if(!e)e='p';c=c?1:0; + return["_c.arc(",x,",",y,",",r,",",s,",",e,",",c,");"].join(''); + }, + ellipse : function(x,y,w,h,s,e,c){ + this.dodraw = 1; + if(!s) s = '0'; if(!e) e = 'p2';c=c?1:0; + return["if((_x2=(",w,"))!=0 && (_y2=-(",h,"))!=0){_c.translate(_x1=(",x,"),_y1=(",y,"));_c.scale(_x2,_y2);", + "_c.arc(0,0,1,(",s,")-1.5707965,(",e,")-1.5707965,",!c,");_c.scale(1/_x2,1/_y2);_c.translate(-_x1,-_y1);}"].join(''); + }, + rect : function( x,y,w,h){ + /* + if(this.style.outx){ + x=(parseFloat(x)==x)?(parseFloat(x)-this.ox):"("+x+"-"+this.ox+")"; + w=(parseFloat(w)==w)?(parseFloat(w)+2*this.ox):"("+w+"+"+2*this.ox+")"; + } + if(this.style.outy){ + y=(parseFloat(y)==y)?(parseFloat(y)-this.oy):"("+y+"-"+this.oy+")"; + h=(parseFloat(h)==h)?(parseFloat(h)+2*this.oy):"("+h+"+"+2*this.oy+")"; + } + */ + switch(this.fillmode){ + case 3: return this.fillopacity+ + "_c.fillRect(_x1="+x+this.mx+",_y1="+y+this.my+ + ",_x2="+w+",_y2="+h+");"+ + this.strokeopacity+ + "_c.strokeRect(_x1,_y1,_x2,_y2);"; + case 2: return "_c.strokeRect("+x+this.mx+","+y+this.my+","+w+","+h+");\n"; + case 1: return "_c.fillRect("+x+this.mx+","+y+this.my+","+w+","+h+");\n"; + } + }, + close : function (){ + return ["_c.closePath();",this.$dodraw()].join(''); + }, + $dodraw : function (){ + this.dodraw = 0; + switch(this.fillmode){ + case 3: return this.fillopacity+"_c.fill();"+ + this.strokeopacity+"_c.stroke();_c.beginPath();\n"; + case 2: return "_c.stroke();_c.beginPath();\n"; + case 1: return "_c.fill();_c.beginPath();\n"; + } + }, + + $endShape : function(){ + var s = this.dodraw?[this.$dodraw()]:[]; + + this.mx="",this.my=""; + this.last = this.style._id; + this.style = 0; + + if(this.tiletrans)s.push("_c.restore();"); + this.tiletrans=0; + if(this._clip)s.push("_c.restore();"); + this._clip = 0; + return s.join(''); + }, + + $finalizeShape : function(){ + return ''; + } + +}; + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/core/lib/draw/chartdraw.js)SIZE(47182)TIME(Mon, 10 Jan 2011 10:53:11 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + + +apf.chart_draw = { + + _axis2D: { + margin : { + left : 30, + top : 30, + right : 30, + bottom :30, + $:1}, + layout : { + pow : 10, + step : 4, + onsidex : 0, + onaxisx : 0, + onsidey : 1, + onaxisy : 0, + $:1}, + plane :{ + inherit : 'shape', + stroke : '#cfcfcf', + fill : '#e6f1f8', + $:1}, + plane2 :{ + inherit : 'shape', + $:1}, + label : { + inherit : 'font', + join : 'label', + left : 0, + top : 0, + format : "fixed(v,1)", + $:0}, + labelx : { + inherit : 'label', + width: 40, + top : 5, + left: -19, + side: 0, + axis: 0, + edgeclip : 2, + align:'center', + $:1}, + labely : { + inherit : 'label', + left : -110, + top : -6, + width: 100, + side:1, + edgeclip : 0, + align:1?'right':'left', + $:1}, + grid : { + inherit : 'shape', + join : 'grid', + extend : 0, + /*stroke : '#cfcfcf', + weight : 1, + opacity: 0.3, + extend : 0,*/ + $:0}, + hgrid : {inherit : 'grid',$:1}, + vgrid : {inherit : 'grid',$:1}, + tiles : { + inherit : 'shape', + join : 'tiles', + fill : '#dfe7f5', + $:1}, + bar : { + inherit : 'shape', + join : 'bar', + $:0}, + hbar : { + inherit : 'bar', + $:1}, + vbar : { + inherit : 'bar', + //stroke : '#cfcfcf', + //fill : 'green', + $:1}, + axis :{ + inherit : 'shape', + join : 'grid', + stroke : 'black', + weight: 1, + extend: 2, + $:0}, + axisx :{inherit : 'axis',$:1}, + axisy :{inherit : 'axis',$:1}, + tick : { + inherit : 'shape', + join : 'grid', + steps : 5, + left: 0, + top : 0, + size : 4, + stroke : '#000000', + $:0}, + tickx : {inherit : 'tick',$:1}, + ticky : {inherit : 'tick',$:1}, + tickgx : {inherit : 'tick',weight:2,size:6,$:1}, + tickgy : {inherit : 'tick',weight:2,size:6,$:1} + }, + axis2D : function(l,s){ + var e = apf.draw; + if(!s.margin || !s.layout) return new Function(''); + var ml = s.margin.left*l.ds, mt = s.margin.top*l.ds, + mr = s.margin.right*l.ds, mb = s.margin.bottom*l.ds; + var c = e.optimize([ + e.beginLayer(l), + e.vars(ml,mt,mr,mb), + e.clear(), + "var v,d,u,h,", + "vcx = 0.5*__pow(",s.layout.pow,", __round(__log(__abs(vw)/",s.layout.pow, + ")/__log(",s.layout.pow,")))*",s.layout.step,",", + "vcy = 0.5*__pow(",s.layout.pow,", __round(__log(__abs(vh)/",s.layout.pow, + ")/__log(",s.layout.pow,")))*",s.layout.step,",", + "vbx = __ceil(vx1/vcx) * vcx,", + "vby = __ceil(vy1/vcy) * vcy,", + "vex = __floor(vx2/vcx) * vcx,", + "vey = __floor(vy2/vcy) * vcy,", + "dcx = vcx*tw, dcy = vcy*th,", + "dbx = vbx*tw+tx, dby = vby*th+ty,", + "dex = vex*tw+tx, dey = vey*th+ty,", + "dcx2 = dcx*2, dcy2 = dcy*2,", + "dbx2 = __ceil(vx1/(2*vcx))*2*vcx*tw+tx,", + "dex2 = __floor(vx2/(2*vcx))*2*vcx*tw+tx,", + "dby2 = __floor(vy1/(2*vcy))*2*vcy*th+ty,", + "dey2 = __floor(vy2/(2*vcy))*2*vcy*th+ty;", + "var xmaxstep = __ceil( (dex-dbx)/dcx )+4,", + "ymaxstep = __ceil( (dey-dby)/dcy )+4;", + + s.plane?[ e.beginShape(s.plane), + e.rect(ml,mt,"dw","dh") + ].join(''):"", + s.tiles?[ + e.rectInv?[ + e.beginShape(s.tiles), + "if((u=dbx2-dcx-",ml,")>0){", + e.rectInv(ml,mt,"u","dh"), + "}", + "for( v = dbx2, u = dex-dcx; v < u; v += dcx2){", + e.rectInv("v",mt,"dcx","dh"), + "};", + "if((u=dr-v)>0){", + e.rectInv("v",mt,"__min(dcx,u)","dh"), + "}", + "if((u=dey2+dcy-",mt,")>0){", + e.rectInv(ml,mt,"dw","u"), + "}", + "for( v = dey2,u = dby+dcy; v < u; v -= dcy2){", + e.rectInv(ml,"v","dw","-dcy"), + "};", + "if((u=db-v)>0){", + e.rectInv(ml,"v","dw","__min(-dcy,u)"), + "}" + ].join(''):[ + e.beginShape(s.tiles,ml,mt,mr,mb), + "for( u = dey2+dcy2, t = dby2-dcy2; u <= t; u -= dcy2){", + "for( v = dbx2-dcx2, d = dex2+dcx2; v <= d; v += dcx2){", + e.rect("v+dcx","u","dcx","-dcy"), + e.rect("v","u-dcy","dcx","-dcy"), + "};", + "}"].join('') + ].join(''):"", + s.vbar?[ e.beginShape(s.vbar), + "if((u=dbx2-dcx-",ml,")>0){", + e.rect(ml,mt,"u","dh"), + "}", + "for( v = dbx2, u = dex-dcx; v < u; v += dcx2){", + e.rect("v",mt,"dcx","dh"), + "};", + "if((u=dr-v)>0){", + e.rect("v",mt,"__min(dcx,u)","dh"), + "}" + ].join(''):"", + s.hbar?[ e.beginShape(s.hbar), + "if((u=dey2+dcy-",mt,")>0){", + e.rect(ml,mt,"dw","u"), + "}", + "for( v = dey2, u = dby+dcy; v < u; v -= dcy2){", + e.rect(ml,"v","dw","-dcy"), + "};", + "if((u=db-v)>0){", + e.rect(ml,"v","dw","__min(-dcy,u)"), + "}" + ].join(''):"", + s.tickx?[ e.beginShape(s.tickx), + "u = ",s.layout.onaxisx?("ty+"+(s.tickx.top*l.ds)): + (s.layout.onsidex?s.tickx.size*-l.ds+ml:("db")),";", + "t = dcx/",s.tickx.steps,";", + "h = ",s.tickx.size*l.ds,";", + s.layout.onaxisx?[ + "if(u+h>",mt," && udb)h=db-u;"].join(''):"", + "x = dbx-dcx;while(xdx && udr)h=dr-u;"].join(''):"", + "y = dey+dcy;while(y= dy; y += dcy){", // Y INVERTED + e.lineH("u","y","t"), + "};" + ].join(''):"", + s.vgrid?[ e.beginShape(s.vgrid), + "t=dh+",s.vgrid.extend*l.ds,";", + "u=",(s.vgrid.extend*l.ds*-s.layout.onsidex)+mt,";", + "for(x = dbx; x <= dr; x += dcx){", + e.lineV("x","u","t"), + "};" + ].join(''):"", + s.tickgx?[ e.beginShape(s.tickgx), + "u = ",s.layout.onaxisx?("ty+"+s.tickgx.top*l.ds): + (s.layout.onsidex?s.tickgx.size*-l.ds+ml:("db")),";", + "h = ",s.tickgx.size*l.ds,";", + s.layout.onaxisx?[ + "if(u+h>dy && udb)h=db-u;"].join(''):"", + "for(v=dbx; v <= dr; v += dcx){", + e.lineV("v","u","h"), + "};", + s.layout.onaxisx?"}":"", + ].join(''):"", + s.tickgy?[ e.beginShape(s.tickgy), + "u = ",s.layout.onaxisy?("tx+"+s.tickgy.left*l.ds): + (s.layout.onsidey?s.tickgy.size*-l.ds+mt:"dr"),";", + "h = ",s.tickgy.size*l.ds,";", + s.layout.onaxisy?[ + "if(u+h>dx && udr)h=dr-u;"].join(''):"", + "for(v=dby; v >= dy; v += dcy){", // Y INVERTED + e.lineH("u","v","h"), + "};", + s.layout.onaxisy?"}":"", + ].join(''):"", + s.axisx?[ e.beginShape(s.axisx), + "if(ty>=dy && ty<=dy+dh){", + "t=dw+",s.axisx.extend*l.ds,";", + "u=dx+",(s.axisx.extend*l.ds*-s.layout.onsidey),";", + e.lineH("u","ty","t"), + "}" + ].join(''):"", + s.axisy?[ e.beginShape(s.axisy), + "if(tx>=dx && tx<=dx+dw){", + "t=dh+",s.axisy.extend*l.ds,";", + "u=dy+",(s.axisy.extend*l.ds*-s.layout.onsidex),";", + e.lineV("tx","u","t"), + "}" + ].join(''):"", + s.plane2?[ e.beginShape(s.plane2), + e.rect(ml,mt,"dw","dh") + ].join(''):"", + s.labelx?[ + s.layout.onaxisx? + e.beginFont(s.labelx, "xmaxstep", ml,mt,mr,mb): + e.beginFont(s.labelx, "xmaxstep", ml-s.labelx.edgeclip*l.ds,0, + mr-s.labelx.edgeclip*l.ds,0), + "for( v = vbx, u = vex,d = dbx; v <= u; v+= vcx, d+= dcx ){", + e.text("d",s.layout.onaxisx?"ty":(s.layout.onsidex?"dy":"db"), + s.labelx.format), + "}" + ].join(''):"", + s.labely?[ + s.layout.onaxisy? + e.beginFont(s.labely, "ymaxstep", ml,mt,mr,mb): + e.beginFont(s.labely, "ymaxstep",0,mt-s.labely.edgeclip*l.ds, + 0,mb-s.labely.edgeclip*l.ds), + "for( v = vby, u = vey,d = dby;v<= u; v+= vcy, d+= dcy ){;", + e.text(s.layout.onaxisy?"tx":(s.layout.onsidey?"dx":"dr"),"d",s.labelx.format), + "}" + ].join(''):"", + e.endLayer() + ]); + try{ + //logw(apf.highlightCode2(apf.formatJS(c))); + return new Function('l','v','m',c); + }catch(x){ + //return new Function('l','v','m',c); + //c = apf.formatJS(c); + //window.open().document.write(""); + alert("Failed to compile:\n"+x.message+'\n'+c);return 0; + } + }, + + + + _axis3D: { + layout :{ + pow : 10, + step : 4, + $:1}, + plane :{ + inherit : 'shape', + side: 1, + oneside : 0, + fill : '#e6f1f8', + $:0}, + plane2 :{ + inherit : 'shape', + $:1}, + planexy :{ + inherit: 'plane', + $:1}, + planexz :{ + inherit: 'plane', + $:1}, + planeyz :{ + inherit: 'plane', + //fill: '#e6f1f8', + $:1}, + + /* + grid : { + inherit : 'shape', + join : 'grid', + stroke : '#cfcfcf', + weight : 1, + opacity: 0.3, + extend : 0, + $:0}, + xgrid : {inherit : 'grid'}, + ygrid : {inherit : 'grid'},*/ + + bar : { + inherit : 'shape', + join : 'bar', + oneside : 0, + $:0}, + hbar : { + side: 1, + inherit : 'bar', + stroke : '#cfcfcf', + //opacity: 1, + $:0}, + vbar : { + side: 1, + inherit : 'bar', + stroke : '#cfcfcf', + fill: 'blue', + opacity: 0.25, + //stroke : '#cfcfcf', + $:0}, + hbarxy :{ + inherit: 'hbar', + $:1}, + vbarxy :{ + inherit: 'vbar', + $:1}, + hbarxz :{ + inherit: 'hbar', + stroke: 'black', + opacity:0.25, + $:1}, + vbarxz :{ + inherit: 'vbar', + fill: '#e6f1f8', + stroke: 'black', + // opacity:0.5, + $:1}, + hbaryz :{ + inherit: 'hbar', + fill: '#blue', + opacity:0.25, + // fill: null, + // opacity:0.5, + $:1}, + vbaryz :{ + inherit: 'vbar', + stroke: 'black', + opacity:0.25, + fill: null, + $:1}, + + axis :{ + inherit : 'shape', + join : 'grid', + stroke : 'black', + opacity: 0.5, + weight: 3, + $:0}, + axisx :{inherit : 'axis',$:1}, + axisy :{inherit : 'axis',$:1}, + axisz :{inherit : 'axis',$:1}, + + tick : { + inherit : 'shape', + join : 'grid', + steps : 5, + size : 4, + scale : 0.1, + angle : 'ang(180)', + stroke : '#000000', + $:0}, + tickx : {inherit : 'tick',angle:'ang(90+f1*90)',$:1}, + ticky : {inherit : 'tick',angle:'ang(90+f2*90)',$:1}, + tickz : {inherit : 'tick',$:1}, + + label : { + inherit : 'font', + join : 'label', + width: 40, + height: 40, + left: -20, + top: "fontz(-5,200)", + size: "fontz(10,200)", + scale: 0.2, + stroke: null, + angle : 'ang(180)', + format : "fixed(v,1)", + $:0}, + labelx : { + inherit : 'label', + angle:'ang(90+f1*90)', + align:'center', + $:1}, + labely : { + inherit : 'label', + angle:'ang(90+f2*90)', + align:'center', + $:1}, + labelz : { + inherit : 'label', + align:'center', + $:1}, + + /* + tickxg : {inherit : 'tick',weight:2,size:6}, + tickyg : {inherit : 'tick',weight:2,size:6}*/ + $:0}, + + chartView3D : function(l,e){ + return [ + "var s3x=v.x3d/vw,s3xi=1/s3x,s3y=v.y3d/vh,s3yi=1/s3y,s3z=v.z3d/vd,s3zi=1/s3z;", + e.sincos3('_m','v.rx','v.ry','v.rz'), + e.setMatrix3D( + e.matrixMul( + e.matrix4T('(-0.5*vx1-0.5*vx2)','(-0.5*vy1-0.5*vy2)','(-0.5*vz1-0.5*vz2)'), + e.matrix4S('s3x','s3y','s3z'), + e.matrix4RP('_m'), + e.matrix4T('v.tx','v.ty','v.tz')) + )].join(''); + }, + + axis3D : function(l,s){ + if(!s.layout) return new Function(''); + var e = apf.draw; + var dt = (new Date()).getTime(); + var zclip = -0.01; + function drawPlane(pr,fl,z1,z2,side, + vx1,vx2,vbx,vex,vbx2,vex2,vcx,vcx2, + vy1,vy2,vby,vey,vby2,vey2,vcy,vcy2 ){ + function plane(z){ + return e.poly3DClip([0,1,2,3],[[vx1,vy1,z],[vx1,vy2,z],[vx2,vy2,z],[vx2,vy1,z]],fl,zclip); + } + function hbar(z){ + return [ + "if((u=(",vbx2,"-",vcx,")-",vx1,")<",vcx," && u>0){", + e.poly3DClip([0,1,2,3],[[vx1,vy1,z],["("+vx1+"+u)",vy1,z],["("+vx1+"+u)",vy2,z],[vx1,vy2,z]],fl,zclip), + "}", + "for( v = ",vbx2,", u = ",vex2,"-0.000001; v < u; v += ",vcx2,"){", + e.poly3DClip([0,1,2,3],[["v",vy1,z],["(v+"+vcx+")",vy1,z],["(v+"+vcx+")",vy2,z],["v",vy2,z]],fl,zclip), + "};", + "if( v<",vx2," ){", + e.poly3DClip([0,1,2,3],[["v",vy1,z],["(u="+e.min("v+"+vcx,vx2)+")",vy1,z],["u",vy2,z],["v",vy2,z]],fl,zclip), + "}"].join(''); + } + function vbar(z){ + return [ + "if((u=(",vby2,"-",vcy,")-",vy1,")<",vcy," && u>0){", + e.poly3DClip([0,1,2,3],[[vx1,vy1,z],[vx1,"("+vy1+"+u)",z],[vx2,"("+vy1+"+u)",z],[vx2,vy1,z]],fl,zclip), + "}", + "for( v = ",vby2,", u = ",vey2,"-0.000001; v < u; v += ",vcy2,"){", + e.poly3DClip([0,1,2,3],[[vx1,"v",z],[vx1,"(v+"+vcy+")",z],[vx2,"(v+"+vcy+")",z],[vx2,"v",z]],fl,zclip), + "};", + "if( v<",vy2," ){", + e.poly3DClip([0,1,2,3],[[vx1,"v",z],[vx1,"(u="+e.min("v+"+vcy,vy2)+")",z],[vx2,"u",z],[vx2,"v",z]],fl,zclip), + "}"].join(''); + } + return [ + "s1=-(",e.backface3D([[vx1,vy1,z1],[vx1,vy2,z1],[vx2,vy1,z1]],fl),");", + "s2=",e.backface3D([[vx1,vy1,z2],[vx1,vy2,z2],[vx2,vy1,z2]],fl),";", + "s",pr,"=s1*",side,">=s2*",side,";", + // if we have to pick a side, (instead of both) we should pick the side which has the biggest visible angle + s['plane'+pr]?[ e.beginShape(s['plane'+pr]), + "sa = s1*",e.style.side*side,";","sb = s2*",e.style.side*side,";",e.style.oneside?"if(sa>=sb)sb = -1;if(sb>=sa)sa = -1;":"", + "for(i=1;i>=0;i--){if((i?sa:sb)>=0){z=i?",z1,":",z2,";", plane("z"),"}};", + ].join(''):"", + s['hbar'+pr]?[ e.beginShape(s['hbar'+pr]), + "sa = s1*",e.style.side*side,";","sb = s2*",e.style.side*side,";",e.style.oneside?"if(sa>=sb)sb = -1;if(sb>=sa)sa = -1;":"", + "for(i=1;i>=0;i--){if((i?sa:sb)>=0){z=i?",z1,":",z2,";",hbar("z"),"}};" + ].join(''):"", + s['vbar'+pr]?[ e.beginShape(s['vbar'+pr]), + "sa = s1*",e.style.side*side,";","sb = s2*",e.style.side*side,";",e.style.oneside?"if(sa>=sb)sb = -1;if(sb>=sa)sa = -1;":"", + "for(i=1;i>=0;i--){if((i?sa:sb)>=0){z=i?",z1,":",z2,";",vbar("z"),"}};" + ].join(''):"", + s['plane2'+pr]?[ e.beginShape(s['plane2'+pr]), + e.poly3DClip([0,1,2,3],[[vx1,vy1,z1],[vx2,vy1,z1],[vx2,vy2,z1],[vx1,vy2,z1]],fl,zclip), + ].join(''):"" + ].join(''); + } + + function drawAxis(pr,fl,z1,z2,side1,side2,us,ws,wind, + vx1,vx2,vbx,vex,vbx2,vex2,vcx,vcx2, + vy1,vy2,vby,vey,vby2,vey2,vcy,vcy2 ){ + // pick a side, then draw a poly3DClip from vx1,vy1 to vx2, vy1 + var zclip = -1, sider, sd; + if(s['axis'+pr]){ + if((sd = s['axis'+pr].side1) !== undefined )side1 = sd; + if((sd = s['axis'+pr].side2) !== undefined )side2 = sd; + } + var sideloop = [ + "for(i=0;i<4;i++)if((i&2?(",side2,"?((f1=0,c=1,y=",vy1,"),1):0):(",side2,"?0:((f1=1,c=-1,y=",vy2,"),1)))&&", + "(i&1?(",side1,"?((f2=0,d=1,z=",z1,"),1):0):(",side1,"?0:((f2=1,d=-1,z=",z2,"),1))))" + ].join(''); + return [ + s['axis'+pr]?[e.beginShape(s['axis'+pr]), + // "if(s//figure out side" + sideloop,"{", + e.poly3DClip([0,1],[[vx1,"y","z"],[vx2,"y","z"]],fl,zclip,true), + "};"].join(''):"", + // lets draw the ticks + s['tick'+pr]?[e.beginShape(s['tick'+pr]), + sideloop,"{", + ";u=y+",us,"*__sin(f=(",e.style.angle,")*",wind,")*(",e.style.scale,")*c;w=z+",ws,"*__cos(f)*(",e.style.scale,")*d;", + "for(v=",vbx,";v<=",vex,";v+=",vcx,"){", + e.poly3DClip([0,1],[["v","y","z"],["v","u","w"]],fl,zclip,true), + "};", + "};"].join(''):"",/* + ";u=",vy1,"+",us,"*__sin(n);w=",z1,"+",ws,"*__cos(n);", + "for(v=",vbx,";v<=",vex,";v+=",vcx,"/10){", + e.poly3DClip([0,1],[["v",vy1,z1],["v","u","w"]],fl,zclip,true), + "};" + */ + // lets draw some textitems + s['label'+pr]?[e.beginFont(s['label'+pr], ["__ceil(((",vex,")-(",vbx,"))/(",vcx,"))*2"].join('')), + sideloop,"{", + ";u=y+",us,"*__sin(f=(",e.style.angle,")*",wind,")*(",e.style.scale,")*c;w=z+",ws,"*__cos(f)*(",e.style.scale,")*d;", + "for(v=",vbx,";v<=",vex,";v+=",vcx,"){", + e.text3D(["v","u","w"],fl,zclip,e.style.format), + "}", + "}"].join(''):"", + + ].join('') + } + var c = e.optimize([ + e.beginLayer(l), + e.vars(), + this.chartView3D(l,e), + e.defCamVec(), + e.clear(), + "var v,d,c,u,h,w,f1,f2,", + "vcx = 0.5*__pow(",s.layout.pow,", __round(__log(__abs(vw)/",s.layout.pow, + ")/__log(",s.layout.pow,")))*",s.layout.step,",", + "vcy = 0.5*__pow(",s.layout.pow,", __round(__log(__abs(vh)/",s.layout.pow, + ")/__log(",s.layout.pow,")))*",s.layout.step,",", + "vcz = 0.5*__pow(",s.layout.pow,", __round(__log(__abs(vd)/",s.layout.pow, + ")/__log(",s.layout.pow,")))*",s.layout.step,",", + "vbx = __ceil(vx1/vcx) * vcx,", + "vby = __ceil(vy1/vcy) * vcy,", + "vbz = __ceil(vz1/vcz) * vcz,", + "vex = __floor(vx2/vcx) * vcx,", + "vey = __floor(vy2/vcy) * vcy,", + "vez = __floor(vz2/vcz) * vcz,", + "vcx2 = vcx*2, vcy2 = vcy*2, vcz2 = vcz*2,", + "vbx2 = __ceil(vx1/vcx2)*vcx2,", + "vex2 = __floor(vx2/vcx2)*vcx2,", + "vby2 = __ceil(vy1/vcy2)*vcy2,", + "vey2 = __floor(vy2/vcy2)*vcy2,", + "vbz2 = __ceil(vz1/vcz2)*vcz2,", + "vez2 = __floor(vz2/vcz2)*vcz2,", + "s1, s2, sa, sb, sxy, sxz, syz;", + "var xmaxstep = __ceil( (vex-vbx)/vcx )+4,", + "ymaxstep = __ceil( (vey-vby)/vcy )+4,", + "zmaxstep = __ceil( (vez-vbz)/vcz )+4;", + //e.beginShape(s.test), + //e.poly3DClip([0,1,2,3],[["-1","-1","1"],["1","-1","0"],["1","1","0"],["-1","1","1"]],null,-4), + + drawPlane('xy',[0,1,2],"vz1","vz2",1, + "vx1","vx2","vbx","vex","vbx2","vex2","vcx","vcx2", + "vy1","vy2","vby","vey","vby2","vey2","vcy","vcy2"), + drawPlane('xz',[0,2,1],"vy1","vy2",-1, + "vx1","vx2","vbx","vex","vbx2","vex2","vcx","vcx2", + "vz1","vz2","vbz","vez","vbz2","vez2","vcz","vcz2"), + drawPlane('yz',[2,0,1],"vx1","vx2",1, + "vy1","vy2","vby","vey","vby2","vey2","vcy","vcy2", + "vz1","vz2","vbz","vez","vbz2","vez2","vcz","vcz2"), + // this axis can be drawn on 4 sides. which side it draws is styled + dynamic + drawAxis('x',[0,1,2],"vz1","vz2","sxy","sxz","s3yi","s3zi",1, + "vx1","vx2","vbx","vex","vbx2","vex2","vcx","vcx2", + "vy1","vy2","vby","vey","vby2","vey2","vcy","vcy2"), + drawAxis('y',[1,0,2],"vz1","vz2","sxy","syz","s3xi","s3zi",-1, + "vy1","vy2","vby","vey","vby2","vey2","vcy","vcy2", + "vx1","vx2","vbx","vex","vbx2","vex2","vcx","vcx2"), + drawAxis('z',[2,1,0],"vx1","vx2","syz","sxz","s3yi","s3xi",1, + "vz1","vz2","vbz","vez","vbz2","vez2","vcz","vcz2", + "vy1","vy2","vby","vey","vby2","vey2","vcy","vcy2"), + e.endLayer() + ]); + try{ + //c = apf.formatJS(c); + //logw(apf.highlightCode2(c)); + var f = new Function('l','v','m',c); + //document.title = (new Date()).getTime() - dt; + return f; + }catch(x){ + //window.open().document.write(""); + alert("Failed to compile:\n"+x.message+'\n'+c);return 0; + } + }, + + + _line2D: { + graph : { + inherit : 'shape', + stroke : '#000000', + weight: 1, + $:1} + }, + line2D : function( l, d, s ){ + if(!s.graph) return new Function(''); + var wrap = s.graph.weight*8, e = apf.draw; + var clipy = s.graph.fillout?"db":"ty"; + var c = e.optimize([ + e.vars(), + "if(m){", + "return -1;", + "}", + e.beginLayer(l), + d.vars,d.stats, + e.beginShape(s.graph), + "var x1=",d.x1,",x2=",d.x2,",xs=",d.xs, + ",x = x1,xw=x2-x1,idx=xw/xs;",d.begin, + "var xfirst = x,dx=-vx1*tw,dy=-vy1*th;", + d.seek, + //"logw(",d.y,");", + d.ifdraw,"{", + s.graph.fill?[ + e.moveTo(d.x+"*tw+tx",clipy), + e.lineTo(d.x+"*tw+tx",d.y+"*th+ty"), + ].join(''):e.moveTo(d.x+"*tw+tx",d.y+"*th+ty"), + + "for(x+=idx;xfunction(){" + apf.formatJS(c) + "}"); + alert("Failed to compile:\n"+c);return 0; + } + }, + + + + _bar3D: { + graph : { + inherit : 'shape', + stroke: '#000000', + weight : 1, + fill : 'red', + $:1} + }, + + bar3D : function(l,d,s){ + if(!s.graph) return new Function(''); + var e = apf.draw, g = apf.visualize; + + var c = e.optimize([ + "if(m){return -1;}", + e.beginLayer(l), + e.vars(), + this.chartView3D(l,e), + e.defCamVec(), + d.vars, + "var x1=",d.x1,",x2=",d.x2,",xs=",d.xs,",x,xw=x2-x1,idx=xw/xs,", + "wx = idx*tw,wy,cx1,cx2,cy1,cy2;", + d.seek, + e.beginState(s.graph, e, e.shapedRect, 4), + d.ifdraw,"{", + "for(x=x1;x0)?"":[ + "for(x=x1;x" + c + ""); + alert("Failed to compile:\n"+c);return 0; + } + }, + + + /* bar3D : function(l,e){ + + e.allocShape(l, l.style.bar ); + e.allocDone(l); + var vz = l.style.bar.zpos; + var func = this.mathParse(l.formula); + var c = [ + this.head, + this.head3D, + e.beginLayer(l), + this.poly3DHead(8), + e.beginShape(0), + "var lx = vw/",l.style.bar.stepx,",xw, w = lx*",l.style.bar.sizex, + ",d=lx*",l.style.bar.sizey,"+",vz,";", + // we need the viewing angle, and create a switch with the 8 angles + "for(x = vx1; x<=vx2; x+=lx){", + "xw = x+w, z = ",func,";", + this.poly3DIndex(e,[ 0,1,5,6,7,3,-1,3,2,6,7], + [["x",vz,0],["xw",vz,0],["xw",vz,"z"],["x",vz,"z"], + ["x","d",0],["xw","d",0],["xw","d","z"],["x","d","z"]]), + "}", + e.endShape(), + e.endLayer()].join(''); + try{ + return new Function('l',c); + }catch(x){ + alert("Failed to compile:\n"+c);return 0; + } + },*/ + + + + /*bar3DXY : function(l,e){ + // we should allocate as many shapes as we have datasets, + // with different colors + e.allocShape(l, l.style.bar ); + e.allocDone(l); + var vz = l.style.bar.zpos; + var func = this.mathParse(l.formula); + var c = [ + this.head, + this.head3D, + e.beginLayer(l), + this.poly3DHead(8), + e.beginShape(0), + "var tx,ty,xw,yw,", + "lx = vw/",l.style.bar.stepx,",hxwv = 0.5*lx*",l.style.bar.sizex,",", + "ly = vh/",l.style.bar.stepy,",hywv = 0.5*ly*",l.style.bar.sizey,";", + // we need the viewing angle, and create a switch with the 8 angles + "for(y = vy1; y<=vy2; y+=ly){", + "for(x = vx1; x<=vx2; x+=lx){", + "tx = x-hxwv, ty = y-hywv, xw = x+hxwv, yw = y+hywv, z = ",func,";", + this.poly3DIndex(e,[ 0,1,5,6,7,3,-1,3,2,6,7], + [["tx","ty",0],["xw","ty",0],["xw","ty","z"],["tx","ty","z"], + ["tx","yw",0],["xw","yw",0],["xw","yw","z"],["tx","yw","z"]]), + "}", + "}", + e.endShape(), + e.endLayer()].join(''); + try{ + return new Function('l',c); + }catch(x){ + alert("Failed to compile:\n"+c);return 0; + } + },*/ + + + + _bar2D: { + graph : { + inherit : 'shape', + shape: 'rect', + stroke: '#000000', + weight : 1, + fill : 'red', + $:1} + }, + bar2D : function(l,d,s){ + var e = apf.draw, g = apf.visualize; + if(!s.graph) return new Function(''); + + var c = e.optimize([ + e.vars(), + d.vars, + "var x1=",d.x1,",x2=",d.x2,",xs=",d.xs,",x,xw=x2-x1,idx=xw/xs,", + "wx = idx*tw,wy;", + d.seek, + "\n\n/*------ bar2D Mousecode ------ */\n", + "if(m){", + e.beginMouseState(s.graph,e,e.shapedRect,4), + "for(x=x1;x" +apf.formatJS(c)+ ""); + alert(x.message+"\nFailed to compile:\n"+c);return 0; + } + }, + + + + _shape2D: { + graph: { + inherit : 'shape', + stroke: '#000000', + weight : 1, + left: -1.5, + top : -1.5, + height: 3, + width: 4, + fill : 'red', + $:1} + }, + shape2D : function(l,d,s){ + if(!s.graph) return new Function(''); + var e = apf.draw, g = apf.visualize; + + var c = e.optimize([ + "if(m){return -1;}", + e.beginLayer(l), + e.vars(), + d.vars, + "var x1=",d.x1,",x2=",d.x2,",xs=",d.xs,",x,xw=x2-x1,idx=xw/xs,", + "wx = idx*tw,wy;", + "tx += ",s.graph.left*l.ds,";", + "ty += ",s.graph.top*l.ds,";", + d.seek, + e.beginState(s.graph, e, e.draw2D, 4), + d.ifdraw,"{", + "for(x=x1;xl.mipthres && _mt1?__pow(_ms,_mt-2):0;"+ + "var _yv = l.v_yvalmip[_mt-1],_i;", + + vx1 : 0, vx2 : "_yv.length", vy1 : -1, vy2 : 1, vz1 : 0, vz2 : 1, + state : "0", + time : "0", + + sum : "l.v_sum", + min : "l.v_min", + max : "l.v_max", + avg : "l.v_avg", + + seek : "",stats:"", + ifdraw : "if((!l.mipmin || _mt>l.mipmin) && (!l.mipmax || _mt=0;--i)j+=((m=_yv[i])<_mi?_mi=m:m)>_ma?_ma=m:m;l.v_avg=(l.v_sum=j)/_yv.length;l.v_min=_mi;l.v_max=_ma;}", + x1 : "__max(__floor(vx1),0)", + x2 : "__min(__ceil(vx2)+1,_yv.length)", + x1c : "__max(__floor(vx1),0)", + x2c : "__min(__ceil(vx2)+1,_yv.length)", + xs : "x2-x1", + x : "x", + y : "_yv[x]" + }; + }, + dt_series2X : function(l) { + var len = l.yvalue.length; + return { + type : 'series2X', + vx1 : 0, vx2 : len, vy1 : -1, vy2 : 1, vz1 : 0, vz2 : 1, + vars : "var _vx = l.xvalue, _vy = l.yvalue, _len = _vy.length;", + x1 : 0, + x2 : "_len", + xs : "_len", + x : "_vx[x]", + y : "_vy[x]" + }; + }, +$:0}; +apf.chart_draw.height2D = apf.chart_draw.line2D; +apf.chart_draw._height2D = apf.chart_draw._line2D; + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/core/lib/draw/vml.js)SIZE(20284)TIME(Mon, 10 Jan 2011 10:53:11 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + +apf.draw.vml = { + //---------------------------------------------------------------------- + + // initialization + + //---------------------------------------------------------------------- + + initRoot : function(r){ + // Note to microsoft: !@#$&(@#*& you destroyed VML performance on purpose didnt you. Get people to go silverlight. + if(!apf.isIE8){ + apf.importCssString("v\\:fill {behavior: url(#default#VML);display:inline-block} v\\:stroke {behavior: url(#default#VML);} v\\:shape {behavior: url(#default#VML);} v\\:path {behavior: url(#default#VML);}"); + r.$ext.innerHTML = "\ +
\ +
\ +
\ +
"; + r.vmlroot = r.$ext.lastChild; + } else { + r.$ext.innerHTML = "\ +
\ +
\ + "; + r.vmliframe = r.$ext.lastChild; + r.vmliframe.allowTransparency=true; + var doc = r.vmliframe.contentWindow.document; + doc.open(); + doc.writeln("\ + \ + \ +
"); + doc.close(); + r.vmlroot = doc.body.firstChild; + } + // var div = r.vmlroot.document.createElement("div"); + // div.innerHTML = "EHLLO"; + // r.vmlroot.document.body.appendChild(div); +// var doc = r.vmlroot.contentWindow.document.innerHTML="WHEEE"; +// alert(); + return this; + }, + resizeRoot : function(r){ + var t = r.vmliframe || r.vmlroot; + t.style.width = r.width; + t.style.height = r.height; + t = t.previousSibling; + t.style.width = r.width, + t.style.height = r.height; + if(r.vmliframe){ + t = r.vmlroot; + t.style.width = r.width; + t.style.height = r.height; + } + }, + initLayer : function(l , r){ + // var vmlroot = r.vmlroot; + l.ds = 1; + l.dx = 0,l.dy = 0; + l.dw = parseFloat(l.width)*l.ds; + l.dh = parseFloat(l.height)*l.ds; + + l._styles = []; + l._htmljoin = []; + l._vmlroot = r.vmlroot; +// l._vmlgroup = vmlgroup; + }, + + resizeLayer : function(l, r){ + // update layer position, and perhaps z-order or all items in a _vmlgroup. + var lx,ly,lw,lh,t; + l.dx = 0, l.dy = 0; + l.dw = parseFloat(lw=l.width)*l.ds; + l.dh = parseFloat(lh=l.height)*l.ds; + var coord = (l.dw+1)+","+(l.dh+1); + if(l._vmlgroup){ + (t=l._vmlgroup).style.left=lx=l.left,t.style.top=ly=l.top,t.style.width=lw,t.style.height=lh; + for(var n = l._vmlgroup.childNodes, l = n.length, i = 0;i\",", + "\"",l._htmljoin.join(''),"\",", + "\"\"].join(''));", + "l._vmlgroup = l._vmlroot.lastChild;", + "var _n, _styles = l._styles = [];", + p.join(''), + "};" + ].join('')); + l._styles = null; + this.l = null; + return s.join(''); +// alert(l._htmljoin.join('')); + }, + + //---------------------------------------------------------------------- + + // Shape rendering + + //---------------------------------------------------------------------- + + beginShape : function(style) { + if(!style)return "document.title='beginShape Failed';"; + var l=this.l, html = l._htmljoin, i, t, + shape=[], path=[], child=[], opacity="", s=[this.$endDraw()]; + style._path = []; + if(style._id === undefined){ + style._id = l._styles.push(style)-1; + } + this.style = style; + + // find a suitable same-styled other shape so we minimize the VML nodes + for(i = l._styles.length-2;i>=0;i--){ + if(!l._styles[i]._prev && + this.$canJoin( t=l._styles[i], style )){ + style._prev = (t._prev !== undefined)?t._prev:i; + break; + } + } + + if(style._prev === undefined) { + s.push("_p=(_s=_styles[",style._id,"])._path=[];"); + // lets check the style object. what different values do we have? + if(typeof style.tile != 'undefined'){ + var fillopacity = style.fillopacity; + if( this.isDynamic(fillopacity) ){ + fillopacity = '1'; + s.push("_s._vmlfill.opacity=",style.fillopacity,";"); + }; + if(this.isDynamic(style.tile)){ + s.push("if(_s._vmlimg!=(_t=",style.tile,"))_s._vmlfill.src=_t;"); + child.push(""); + }else{ + child.push(""); + if(style.tilex || style.tiley){ + style._img = new Image(); style._img.src = style.tile; + if(style.tilex) + s.push("_s._vmlfill.origin.x=((_t=((", + style.tilex,")/(_s._img.width))%1)<0?1+_t:_t);"); + if(style.tiley) + s.push("_s._vmlfill.origin.y=((_t=((", + style.tiley,")/_s._img.height)%1)<0?1+_t:_t);"); + } + } + s.push("_p.push('m',_dx=-_s._img.width*100,' ',_dy=-_s._img.height*100,", + "',l',_dx,' ',_dy);"); + }else + if(style.fill !== undefined){ + // check if our fill is dynamic. + var fill = style.fill, fillopacity = style.fillopacity, + angle = style.angle, gradopacity = style.gradopacity; + if(!fill.sort)fill=[fill]; + var len = fill.length; + var color='black', colors, color2, getColorors; + // precalc the colors value, we might need it later + if(len>2){ + for(i=1;i1?'+",':'"',Math.round((i/(len-1))*100),'% "+', + this.getColor(fill[i])); + colors = t.join(''); + getColorors = 1; + }else{ + for(t=[],i=1;i1?',':'',Math.round((i/(len-1))*100),'% ',fill[i]); + colors = t.join(''); + } + } + if(len>1){ + // we have a gradient + if( this.isDynamic(gradopacity) || this.isDynamic(fillopacity)){ + // hack to allow animated opacitys for gradients. There is no o:opacity2 property unfortunately + if(gradopacity == fillopacity)fillopacity='_t='+fillopacity,gradopacity='_t'; + if(len>2)t=gradopacity,gradopacity=fillopacity,fillopacity=t; + s.push( + "if(_s._vmldata!=(_t=", + "[\"\"].join(''))){", + "_s._domnode.removeChild(_s._vmlfill);", + "_s._domnode.insertAdjacentHTML( 'beforeend',_s._vmldata=_t);", + "_s._vmlfill = _s._domnode.lastChild;};"); + child.push(""); + }else{ + if(len>2)t=gradopacity,gradopacity=fillopacity,fillopacity=t; + if( this.isDynamic(fill[0]) ) + s.push("_s._vmlfill.color=",this.getColor(fill[0]),";"); + else color = fill[0]; + + if(this.isDynamic(fill[len-1])) + s.push("_s._vmlfill.color2=", + this.getColor(fill[len-1]),";"); + else color2 = fill[len-1]; + + if(getColorors){ + s.push("_s._vmlfill.colors.value=",colors,";"); + } + if( this.isDynamic(angle) ){ + angle = '0'; + s.push("_s._vmlfill.angle=(((",style.angle,")+180)*360)%360;"); + }; + if( this.isDynamic(fillopacity) ){ + fillopacity = '1'; + s.push("_s._vmlfill.opacity=",style.fillopacity,";"); + }; + child.push(""); + } + }else{ + if( this.isDynamic(fillopacity) ){ + fillopacity = '1'; + s.push("_s._vmlfill.opacity=",style.fillopacity,";"); + }; + if( this.isDynamic(fill[0]) ){ + s.push("_s._vmlfill.color=",this.getColor(fill[0]),";"); + }else color = fill[0]; + + child.push(""); + } + shape.push("fill='t'"),path.push("fillok='t'"); + } else { + shape.push("fill='f'"),path.push("fillok='f'"); + } + if(style.stroke !== undefined){ + var weight = style.weight, + opacity = style.strokeopacity, + stroke = style.stroke; + if( this.isDynamic(opacity) ){ + opacity = '1'; + s.push("_s._vmlstroke.opacity=",style.opacity,";"); + } + if( this.isDynamic(weight) ){ + weight = '1'; + s.push("_t=",style.weight, + ";_s._vmlstroke.weight=_t;if(_t<",opacity, + ")_s._vmlstroke.opacity=_t;"); + } + if( this.isDynamic(stroke) ){ + stroke = 'black'; + s.push("_s._vmlstroke.color=",this.getColor(style.stroke),";"); + } + + //@todo @rik please check this I changed getColor(stroke) to getColor(stroke.dataType == apf.ARRAY ? stroke[0] : stroke) + child.push(""); + } else { + shape.push("stroke='f'"), path.push("strokeok='f'"); + } + + html.push(["",child.join(' '),""].join('')); + }else{ + if(style._prev !== undefined){ + if(this.last !== style._prev) + s.push("_p=(_s=_styles[",style._prev,"])._path;"); + } + } + //alert(html.join('')); + return s.join(''); + }, + + // drawing command + moveTo : function(x, y){ + return ["_p.push('m',__round(",x,")", + ",' ',__round(",y+"),'l');\n"].join(''); + }, + + lineTo : function(x, y){ + return ["_p.push(__round(",x,")", + ",' ',__round("+y+"));\n"].join(''); + }, + + lineH : function(x,y,w){ + return ["_p.push('m',__round(",x,")", + ",' ',__round(",y,")", + ",'r',__round(",w,"),' 0');"].join(''); + }, + + lineV : function(x,y,h){ + return ["_p.push('m',__round(",x,")", + ",' ',__round(",y,")", + ",'r0 ',__round(",h,"));"].join(''); + }, + + dot : function(x,y){ + return ["_p.push('m',__round(",x,")", + ",' ',__round(",y,")", + ",'r0 0');"].join(''); + }, + + rect : function( x,y,w,h,inv ){ + return ["_u=",x,";if((_t=__round(",w,"))>0)_p.push('m',__round(_u),' ',__round(",y,")", + ",'r',_t,' 0r0 ',__round(",h, + inv?"),'r-'+_t,' 0x');":"),'r-'+_t,' 0xe');"].join(''); + }, + + ellipse : function( x,y,w,h,s,e,c){ + if(!s){ + return ["_p.push('at ',(_x1=__round(",x,"))-(_x2=__round(",w, + ")),' ',(_y1=__round(",y,"))-(_y2=__round(",h,")),' ',", + "_x1+_x2,' ',_y1+_y2,' 0 0 0 0');"].join(''); + }else{ // generate heaps of crap + return ["if( (_t=",s,")+0.000001<(_u=",e,")){", + "_p.push('",c?"wa":"at"," ',(_x1=__round(",x, + "))-(_x2=__round(",w,")),' ',(_y1=__round(",y, + "))-(_y2=__round(",h,")),' ',_x1+_x2,' ',_y1+_y2,' ',", + "__round(_x1+__sin(_t)*_x2*4000),' ',", + "__round(_y1+__cos(_t)*_y2*4000),' ',", + "__round(_x1+__sin(_u)*_x2*4000),' ',", + "__round(_y1+__cos(_u)*_y2*4000),'x');}else{", + "_p.push('l',__round((",x,")+__sin(_t)*(",w, + ")),' ',__round((",y,")+__cos(_t)*(",h,")),'x');", + "}", + ].join(''); + } + /* + + return ["_p.push('al ',_x1=__round(",x,"),' ',_y1=__round(",y,"),' ',", + "_x1+__round(",w,"),' ',_y1+__round(",h,"),' 90 1024');"].join('');*/ + }, + + + rectInv : function( x,y,w,h ){ + return this.rect(x,y,w,h,1); + }, + + close : function (){ + return "_p.push('xe');"; + }, + + $endShape : function(){ + this.mx="",this.my=""; + this.last = this.style._id; + this.style = 0; + return ''; + }, + + $finalizeShape : function(style){ + return ["if((_s=_styles[",style._id,"])._pathstr!=(_t=", + "(_p=_s._path).length?_p.join(' '):'m'))_s._domnode.path=_t;\n"].join(''); + } + +}; + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/core/lib/storage/air.file.js)SIZE(10053)TIME(Mon, 10 Jan 2011 10:53:11 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + + +// summary: +// Storage provider that uses features in the Adobe AIR runtime to achieve +// permanent storage + +apf.storage.modules['air.file'] = { + initialized: false, + + init: function(){ + this.File = window.runtime.flash.filesystem.File; + this.FileStream = window.runtime.flash.filesystem.FileStream; + this.FileMode = window.runtime.flash.filesystem.FileMode; + + this.storagePath = "__APF_" + (apf.config.name + ? apf.config.name.toUpperCase() + : "STORAGE") + "/"; + + // need to initialize our storage directory + try { + var dir = this.File.applicationStorageDirectory.resolvePath(this.storagePath); + if (!dir.exists) + dir.createDirectory(); + this.initialized = true; + } + catch(e) { + apf.console.warn(e.message); + return false; + } + }, + + isAvailable: function(){ + return apf.isAIR; + }, + + put: function(key, value, namespace){ + + if (this.isValidKey(key) == false) + throw new Error(apf.formatErrorString(0, null, + "Setting name/value pair", + "Invalid key given: " + key)); + + + if (!namespace) + namespace = this.namespace; + + + if (this.isValidKey(namespace) == false) + throw new Error(apf.formatErrorString(0, null, + "Setting name/value pair", + "Invalid namespace given: " + namespace)); + + + // try to store the value + try { + this.remove(key, namespace); + + var dir = this.File.applicationStorageDirectory.resolvePath(this.storagePath + namespace); + if (!dir.exists) + dir.createDirectory(); + + var file = dir.resolvePath(key); + var stream = new this.FileStream(); + stream.open(file, this.FileMode.WRITE); + stream.writeObject(value); + stream.close(); + } + catch(e) { + + throw new Error(apf.formatErrorString(0, null, + "Setting name/value pair", + "Error writing file: " + e.message)); + + + return false; + } + + return true; + }, + + get: function(key, namespace){ + + if (this.isValidKey(key) == false) + throw new Error(apf.formatErrorString(0, null, + "Getting name/value pair", + "Invalid key given: " + key)); + + + if (!namespace) + namespace = this.namespace; + + + if (this.isValidKey(namespace) == false) + throw new Error(apf.formatErrorString(0, null, + "Getting name/value pair", + "Invalid namespace given: " + namespace)); + + + var file = this.File.applicationStorageDirectory.resolvePath(this.storagePath + namespace + '/' + key); + if (!file.exists || file.isDirectory) + return false; + + var stream = new this.FileStream(); + stream.open(file, this.FileMode.READ); + var results = stream.readObject(); + stream.close(); + + return results; + }, + + getNamespaces: function(){ + var results = [ this.namespace ]; + var dir = this.File.applicationStorageDirectory.resolvePath(this.storagePath); + var files = dir.getDirectoryListing(); + for (var i = 0; i < files.length; i++) { + if (files[i].isDirectory && files[i].name != this.namespace) + results.push(files[i].name); + } + + return results; + }, + + getKeys: function(namespace){ + if (!namespace) + namespace = this.namespace; + + + if (this.isValidKey(namespace) == false) + throw new Error(apf.formatErrorString(0, null, + "Clearing storage", + "Invalid namespace given: " + namespace)); + + + var results = []; + var dir = this.File.applicationStorageDirectory.resolvePath(this.storagePath + namespace); + if (dir.exists && dir.isDirectory){ + var files = dir.getDirectoryListing(); + for (var i = 0; i < files.length; i++) + results.push(files[i].name); + } + return results; + }, + + clear: function(namespace){ + if (!namespace) + namespace = this.namespace; + + + if (this.isValidKey(namespace) == false) + throw new Error(apf.formatErrorString(0, null, + "Clearing storage", + "Invalid namespace given: " + namespace)); + + + var dir = this.File.applicationStorageDirectory.resolvePath(this.storagePath + namespace); + if (dir.exists && dir.isDirectory) + dir.deleteDirectory(true); + }, + + remove: function(key, namespace){ + if (!namespace) + namespace = this.namespace; + + + if (this.isValidKey(namespace) == false) + throw new Error(apf.formatErrorString(0, null, + "Removing key", + "Invalid namespace given: " + namespace)); + + + var file = this.File.applicationStorageDirectory.resolvePath(this.storagePath + namespace + '/' + key); + if (file.exists && !file.isDirectory) + file.deleteFile(); + }, + + putMultiple: function(keys, values, namespace) { + + if (this.isValidKeyArray(keys) === false + || ! values instanceof Array + || keys.length != values.length){ + throw new Error(apf.formatErrorString(0, null, + "Setting multiple name/value pairs", + "Invalid arguments: keys = [" + keys + "], values = [" + values + "]")); + } + + + if (!namespace) + namespace = this.namespace; + + + if (this.isValidKey(namespace) == false) + throw new Error(apf.formatErrorString(0, null, + "Setting multiple name/value pairs", + "Invalid namespace given: " + namespace)); + + + // try to store the value + try { + for (var i = 0; i < keys.length; i++) + this.put(keys[i], values[i], null, namespace); + } + catch(e) { + + throw new Error(apf.formatErrorString(0, null, + "Writing multiple name/value pair", + "Error writing file: " + e.message)); + + return false; + } + + return true; + }, + + getMultiple: function(keys, namespace){ + + if (this.isValidKeyArray(keys) === false) + throw new Error(apf.formatErrorString(0, null, + "Getting name/value pair", + "Invalid key array given: " + keys)); + + + if (!namespace) + namespace = this.namespace; + + + if (this.isValidKey(namespace) == false) + throw new Error(apf.formatErrorString(0, null, + "Getting multiple name/value pairs", + "Invalid namespace given: " + namespace)); + + + var results = []; + for (var i = 0; i < keys.length; i++) + results[i] = this.get(keys[i], namespace); + + return results; + }, + + removeMultiple: function(keys, namespace){ + + if (this.isValidKeyArray(keys) === false) + throw new Error(apf.formatErrorString(0, null, + "Removing name/value pair", + "Invalid key array given: " + keys)); + + + if (!namespace) + namespace = this.namespace; + + + if (this.isValidKey(namespace) == false) + throw new Error(apf.formatErrorString(0, null, + "Removing multiple name/value pairs", + "Invalid namespace given: " + namespace)); + + + for (var i = 0; i < keys.length; i++) + this.remove(keys[i], namespace); + }, + + isPermanent: function(){ + return true; + }, + + getMaximumSize: function(){ + return this.SIZE_NO_LIMIT; + }, + + hasSettingsUI: function(){ + return false; + }, + + showSettingsUI: function(){ + throw new Error(this.declaredClass + + " does not support a storage settings user-interface"); + }, + + hideSettingsUI: function(){ + throw new Error(this.declaredClass + + " does not support a storage settings user-interface"); + } +}; + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/core/lib/storage/air.js)SIZE(9669)TIME(Mon, 10 Jan 2011 10:53:11 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + + +// summary: +// Storage provider that uses features in the Adobe AIR runtime to achieve +// permanent storage + +apf.storage.modules.air = { + init: function(){ + this.ByteArray = window.runtime.flash.utils.ByteArray; + this.EncryptedLocalStore = window.runtime.flash.data.EncryptedLocalStore; + }, + + isAvailable: function(){ + return apf.isAIR; + }, + + _getItem: function(key){ + var storedValue = this.EncryptedLocalStore.getItem("__apf_" + key); + return storedValue ? storedValue.readUTFBytes(storedValue.length) : ""; + }, + + _setItem: function(key, value){ + var bytes = new this.ByteArray(); + bytes.writeUTFBytes(value); + this.EncryptedLocalStore.setItem("__apf_" + key, bytes); + }, + + _removeItem: function(key){ + this.EncryptedLocalStore.removeItem("__apf_" + key); + }, + + put: function(key, value, namespace){ + + if (this.isValidKey(key) == false) + throw new Error(apf.formatErrorString(0, null, + "Setting name/value pair", "Invalid key given: " + key)); + + + if(!namespace) + namespace = this.namespace; + + + if (this.isValidKey(namespace) == false) + throw new Error(apf.formatErrorString(0, null, + "Setting name/value pair", "Invalid namespace given: " + namespace)); + + + // try to store the value + try { + var namespaces = this._getItem("namespaces") || ''; + if(namespaces.indexOf('|' + namespace + '|') == -1) + this._setItem("namespaces", namespaces + namespace + '|'); + + var keys = this._getItem(namespace + "_keys") || ''; + if(keys.indexOf('|' + key + '|') == -1) + this._setItem(namespace + "_keys", keys + key + '|'); + + this._setItem('_' + namespace + '_' + key, value); + } + catch(e) { + + throw new Error(apf.formatErrorString(0, null, + "Setting name/value pair", "Error writing: " + e.message)); + + + return false; + } + + return true; + }, + + get: function(key, namespace){ + + if (this.isValidKey(key) == false) + throw new Error(apf.formatErrorString(0, null, "Getting name/value pair", "Invalid key given: " + key)); + + + if (!namespace) + namespace = this.namespace; + + + if (this.isValidKey(namespace) == false) + throw new Error(apf.formatErrorString(0, null, + "Getting name/value pair", "Invalid namespace given: " + namespace)); + + + return this._getItem('_' + namespace + '_' + key); + }, + + getNamespaces: function(){ + var results = [ this.namespace ]; + var namespaces = (this._getItem("namespaces") || '').split('|'); + for (var i=0;i 0 : Wait until 'newDelay' ms have passed without any "put" request to flush + // -1 : Do not automatically flush + setFlushDelay: function(newDelay){ + if (newDelay === null || typeof newDelay === "undefined" || isNaN(newDelay)) + throw new Error("Invalid argunment: " + newDelay); + + this.callMethod('setFlushDelay', String(newDelay)); + }, + + getFlushDelay: function(){ + return Number(this.callMethod('getFlushDelay')); + }, + + flush: function(namespace){ + //@fixme: is this test necessary? Just use !namespace + if (namespace == null || typeof namespace == "undefined") { + namespace = apf.storage.namespace; + } + this.callMethod('flush', namespace); + }, + + /** + * @todo replace this with mikes flash detection code + */ + isAvailable: function(){ + return location.protocol != "file:" && apf.flash.isEightAvailable(); + }, + + put: function(key, value, namespace){ + + if (this.isValidKey(key) == false) + throw new Error(apf.formatErrorString(0, null, + "Setting name/value pair", "Invalid key given: " + key)); + + + if (!namespace) + namespace = this.namespace; + + + if (this.isValidKey(namespace) == false) + throw new Error(apf.formatErrorString(0, null, + "Setting name/value pair", "Invalid namespace given: " + namespace)); + + + this.callMethod('put', key, apf.serialize(value), namespace); + }, + + putMultiple: function(keys, values, namespace){ + + if (this.isValidKeyArray(keys) === false + || ! values instanceof Array + || keys.length != values.length){ + throw new Error(apf.formatErrorString(0, null, + "Setting multiple name/value pairs", "Invalid arguments: keys = [" + + keys + "], values = [" + values + "]")); + } + + + if (!namespace) + namespace = this.namespace; + + + if (this.isValidKey(namespace) == false) + throw new Error(apf.formatErrorString(0, null, "Setting multiple name/value pairs", "Invalid namespace given: " + namespace)); + + + // Convert the arguments on strings we can pass along to Flash + var metaKey = keys.join(","); + var lengths = []; + for (var i = 0; i < values.length; i++) { + values[i] = apf.unserialize(values[i]); + lengths[i] = values[i].length; + } + var metaValue = values.join(""); + var metaLengths = lengths.join(","); + this.callMethod('putMultiple', metaKey, metaValue, metaLengths, namespace); + }, + + get: function(key, namespace){ + + if (this.isValidKey(key) == false) + throw new Error(apf.formatErrorString(0, null, "Getting name/value pair", "Invalid key given: " + key)); + + + if (!namespace) + namespace = this.namespace; + + + if (this.isValidKey(namespace) == false) + throw new Error(apf.formatErrorString(0, null, + "Getting name/value pair", "Invalid namespace given: " + namespace)); + + + var results = this.callMethod('get', key, namespace); + if (results == "") + return null; + + return apf.unserialize(apf.flash.decode(results)); + }, + + getMultiple: function(keys, namespace){ + + if (this.isValidKeyArray(keys) === false) + throw new Error(apf.formatErrorString(0, null, + "Getting name/value pair", "Invalid key array given: " + keys)); + + + if (!namespace) + namespace = this.namespace; + + + if (this.isValidKey(namespace) == false) + throw new Error(apf.formatErrorString(0, null, + "Getting multiple name/value pairs", "Invalid namespace given: " + + namespace)); + + + var metaKey = keys.join(","); + var metaResults = this.callMethod('getMultiple', metaKey, namespace); + if (!metaResults) + return null; + var results = eval("(" + metaResults.replace(/""([^",\]]+)/g, '"\\"$1') + .replace(/([^",]+)""/g, '$1\\""') + ")"); + + // destringify each entry back into a real JS object + for (var i = 0; i < results.length; i++) + results[i] = (results[i] == "") ? null : apf.unserialize(apf.flash.decode(results[i])); + + return results; + }, + + _destringify: function(results){ + // destringify the content back into a + // real JavaScript object; + // handle strings differently so they have better performance + if (typeof results == "string" && (/^string:/.test(results))) + results = results.substring("string:".length); + else + results = eval(results); + + return results; + }, + + getKeys: function(namespace){ + if (!namespace) + namespace = this.namespace; + + + if (this.isValidKey(namespace) == false) + throw new Error(apf.formatErrorString(0, null, "Clearing storage", + "Invalid namespace given: " + namespace)); + + + var results = this.callMethod('getKeys', namespace); + + // Flash incorrectly returns an empty string as "null" + if (results == this || results == null || results == "null") + results = ""; + + results = results.split(","); + results.sort(); + + return results; + }, + + getNamespaces: function(){ + var results = this.callMethod('getNamespaces'); + + // Flash incorrectly returns an empty string as "null" + if (results == this || results == null || results == "null") + results = apf.storage.namespace || "default"; + + results = results.split(","); + results.sort(); + + return results; + }, + + clear: function(namespace){ + if (!namespace) + namespace = this.namespace; + + + if (this.isValidKey(namespace) == false) + throw new Error(apf.formatErrorString(0, null, "Clearing storage", + "Invalid namespace given: " + namespace)); + + + this.callMethod('clear', namespace); + }, + + remove: function(key, namespace){ + if (!namespace) + namespace = this.namespace; + + + if (this.isValidKey(namespace) == false) + throw new Error(apf.formatErrorString(0, null, "Removing key", + "Invalid namespace given: " + namespace)); + + + this.callMethod('remove', key, namespace); + }, + + removeMultiple: function(/*array*/ keys, /*string?*/ namespace){ /*Object*/ + + if (this.isValidKeyArray(keys) === false) + throw new Error(apf.formatErrorString(0, null, + "Getting name/value pair", "Invalid key array given: " + keys)); + + + if (!namespace) + namespace = this.namespace; + + + if (this.isValidKey(namespace) == false) + throw new Error(apf.formatErrorString(0, null, + "Getting multiple name/value pairs", "Invalid namespace given: " + + namespace)); + + + var metaKey = keys.join(","); + this.callMethod('removeMultiple', metaKey, namespace); + }, + + isPermanent: function(){ + return true; + }, + + getMaximumSize: function(){ + return this.SIZE_NO_LIMIT; + }, + + hasSettingsUI: function(){ + return false; + }, + + showSettingsUI: function(){ + throw new Error(apf.formatErrorString(0, null, this.declaredClass + + " does not support a storage settings user-interface")); + }, + + hideSettingsUI: function(){ + throw new Error(apf.formatErrorString(0, null, this.declaredClass + + " does not support a storage settings user-interface")); + }, + + getResourceList: function(){ /* Array[] */ + // @todo: implement offline support icw Flash storage + return []; + } +}; + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/core/lib/storage/gears.js)SIZE(12312)TIME(Mon, 10 Jan 2011 10:53:11 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + + +/** + * Storage provider that uses Google Gears to store data. + */ +apf.storage.modules.gears = +apf.storage.modules["gears.sql"] = { + + // instance methods and properties + table_name : "STORAGE", + initialized : false, + + $available : null, + $db : null, + + init: function(){ + this.factory = apf.nameserver.get("google", "gears"); + this.database_name = apf.config.name.substr(0, 46) + ".apf.offline.gears"; + + this.$db = this.factory.create('beta.database', '1.0'); + this.$db.open(this.database_name); + + // create the table that holds our data + try { + this.$sql("CREATE TABLE IF NOT EXISTS " + this.table_name + "( " + + " namespace TEXT, " + + " key TEXT, " + + " value TEXT " + + ")" + ); + this.$sql("CREATE UNIQUE INDEX IF NOT EXISTS namespace_key_index" + + " ON " + this.table_name + + " (namespace, key)"); + + this.initialized = true; + } + catch(e) { + apf.console.warn(e.message); + return false; + } + }, + + $sql: function(query, params){ + var rs = this.$db.execute(query, params); + + return this.$normalizeResults(rs); //can I do this after I close db? + }, + + destroy : function(){ + //if (!apf.isIE) + this.$db.close(); + }, + + $normalizeResults: function(rs){ + var results = []; + if (!rs) return []; + + while (rs.isValidRow()) { + var row = {}; + + for (var i = 0; i < rs.fieldCount(); i++) { + var fieldName = rs.fieldName(i); + var fieldValue = rs.field(i); + row[fieldName] = fieldValue; + } + + results.push(row); + + rs.next(); + } + + rs.close(); + + return results; + }, + + isAvailable: function(){ + // is Google Gears available and defined? + return apf.isGears; + }, + + put: function(key, value, namespace){ + + if (this.isValidKey(key) == false) + throw new Error(apf.formatErrorString(0, null, + "Setting name/value pair", + "Invalid key given: " + key)); + + + if (!namespace) + namespace = this.namespace; + + + if (this.isValidKey(namespace) == false) + throw new Error(apf.formatErrorString(0, null, + "Setting name/value pair", + "Invalid namespace given: " + namespace)); + + + value = apf.serialize(value); + + // try to store the value + try { + this.$sql("DELETE FROM " + this.table_name + + " WHERE namespace = ? AND key = ?", + [namespace, key]); + this.$sql("INSERT INTO " + this.table_name + + " VALUES (?, ?, ?)", + [namespace, key, value]); + } + catch(e) { + + throw new Error(apf.formatErrorString(0, null, + "Setting name/value pair", + "Error setting name/value pair: " + e.message)); + + return false; + } + + return true; + }, + + get: function(key, namespace){ + + if (this.isValidKey(key) == false) + throw new Error(apf.formatErrorString(0, null, + "Getting name/value pair", + "Invalid key given: " + key)); + + + if (!namespace) + namespace = this.namespace; + + + if (this.isValidKey(namespace) == false) + throw new Error(apf.formatErrorString(0, null, + "Getting name/value pair", + "Invalid namespace given: " + namespace)); + + + // try to find this key in the database + var results = this.$sql("SELECT * FROM " + this.table_name + + " WHERE namespace = ? AND " + + " key = ?", + [namespace, key]); + + if (!results.length) + return null; + + return apf.unserialize(results[0].value); + }, + + getNamespaces: function(){ + var results = [ this.namespace ]; + + var rs = this.$sql("SELECT namespace FROM " + this.table_name + + " DESC GROUP BY namespace"); + for (var i = 0; i < rs.length; i++) { + if (rs[i].namespace != this.namespace) + results.push(rs[i].namespace); + } + + return results; + }, + + getKeys: function(namespace){ + if (!namespace) + namespace = this.namespace; + + + if (this.isValidKey(namespace) == false) + throw new Error(apf.formatErrorString(0, null, + "Retrieving keys", + "Invalid namespace given: " + namespace)); + + + var rs = this.$sql("SELECT key FROM " + this.table_name + + " WHERE namespace = ?", + [namespace]); + + var results = []; + for (var i = 0; i < rs.length; i++) + results.push(rs[i].key); + + return results; + }, + + clear: function(namespace){ + if (!namespace) + namespace = this.namespace; + + + if (this.isValidKey(namespace) == false) + throw new Error(apf.formatErrorString(0, null, + "Clearing storage", + "Invalid namespace given: " + namespace)); + + + this.$sql("DELETE FROM " + this.table_name + + " WHERE namespace = ?", + [namespace]); + }, + + remove: function(key, namespace){ + if (!namespace) + namespace = this.namespace; + + + if (this.isValidKey(namespace) == false) + throw new Error(apf.formatErrorString(0, null, + "Removing key", + "Invalid namespace given: " + namespace)); + + + this.$sql("DELETE FROM " + this.table_name + + " WHERE namespace = ? AND" + + " key = ?", + [namespace, key]); + }, + + putMultiple: function(keys, values, namespace) { + + if(this.isValidKeyArray(keys) === false + || ! values instanceof Array + || keys.length != values.length){ + throw new Error(apf.formatErrorString(0, null, + "Setting multiple name/value pairs", + "Invalid arguments: keys = [" + keys + "], \ + values = [" + values + "]")); + } + + + if (!namespace) + namespace = this.namespace; + + + if (this.isValidKey(namespace) == false) + throw new Error(apf.formatErrorString(0, null, + "Setting multiple name/value pairs", + "Invalid namespace given: " + namespace)); + + + // try to store the value + try { + this.$sql.open(); + this.$sql.db.execute("BEGIN TRANSACTION"); + var stmt = "REPLACE INTO " + this.table_name + " VALUES (?, ?, ?)"; + for(var i=0;i= 0; i--) { + if (namespaceTester.test(myStorage.key(i)) == true) + myStorage.removeItem(myStorage.key(i)); + } + }, + + remove: function(key, namespace){ + // get our full key name, which is namespace + key + key = this.getFullKey(key, namespace); + + var myStorage = this.storage[this.domain]; + myStorage.removeItem(key); + }, + + isPermanent: function(){ + return true; + }, + + getMaximumSize: function(){ + return 0; + }, + + hasSettingsUI: function(){ + return false; + }, + + showSettingsUI: function(){ + throw new Error(apf.formatErrorString(0, null, this.declaredClass + + " does not support a storage settings user-interface")); + }, + + hideSettingsUI: function(){ + throw new Error(apf.formatErrorString(0, null, this.declaredClass + + " does not support a storage settings user-interface")); + }, + + getFullKey: function(key, namespace){ + if (!namespace) + namespace = this.namespace; + + + if (this.isValidKey(namespace) == false) + throw new Error(apf.formatErrorString(0, null, "Clearing storage", + "Invalid namespace given: " + namespace)); + + + // don't append a namespace string for the default namespace, + // for compatibility with older versions of dojox.storage + return namespace == this.namespace + ? key + : "__" + namespace + "_" + key; + } +}; + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/core/lib/storage/memory.js)SIZE(10208)TIME(Mon, 10 Jan 2011 10:53:11 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + + +/** + * Storage module using javascript objects to store the data. When the + * application restarts or closes this data is be purged. This module is used + * when no other storage mechanism is available to still allow for some of the + * features that depend on a storage mechanism to be available. + * @default_private + */ +apf.storage.modules.memory = { + initialized: true, + store : {}, + + isAvailable: function(){ + return true; + }, + + /** + * Stores a key value pair in a namespace. + * @param {String} key the identifier of the information. + * @param {mixed} value the information to store. + * @param {String} namespace the named context into which to store the key value pair. + */ + put: function(key, value, namespace){ + + if (this.isValidKey(key) == false) + throw new Error(apf.formatErrorString(0, null, + "Setting name/value pair", + "Invalid key given: " + key)); + + + if (!namespace) + namespace = this.namespace; + + + if (this.isValidKey(namespace) == false) + throw new Error(apf.formatErrorString(0, null, + "Setting name/value pair", + "Invalid namespace given: " + namespace)); + + + // serialize the value; + value = apf.serialize(value); + + // store the value + if (!this.store[namespace]) + this.store[namespace] = {}; + + this.store[namespace][key] = value; + }, + + /** + * Retrieves a keys in a namespace. + * @param {String} key the identifier of the information. + * @param {String} namespace the named context of the keys to retrieve. + * @return {mixed} the value that correspond to the key in the namespace. + */ + get: function(key, namespace){ + + if (this.isValidKey(key) == false) + throw new Error(apf.formatErrorString(0, null, + "Getting name/value pair", + "Invalid key given: " + key)); + + + if (!namespace) + namespace = this.namespace; + + + if (this.isValidKey(namespace) == false) + throw new Error(apf.formatErrorString(0, null, + "Getting name/value pair", + "Invalid namespace given: " + namespace)); + + + if (!this.store[namespace] || !this.store[namespace][key]) + return null; + + return apf.unserialize(this.store[namespace][key]); + }, + + /** + * Retrieves all the namespaces in use. + * @return {Array} list of namespaces. + */ + getNamespaces: function(){ + var results = [ this.namespace ]; + + for (var ns in this.store) + results.push(ns); + + return results; + }, + + /** + * Retrieves all the keys of a namespace. + * @param {String} namespace the named context of the keys to retrieve. + * @return {Array} the list of keys in the namespace. + */ + getKeys: function(namespace){ + if (!namespace) + namespace = this.namespace; + + + if (this.isValidKey(namespace) == false) + throw new Error(apf.formatErrorString(0, null, + "Clearing storage", + "Invalid namespace given: " + namespace)); + + + var results = []; + for (var prop in this.store[namespace]) + results.push(prop); + + return results; + }, + + /** + * Removes all keys from a namespace + */ + clear: function(namespace){ + if (!namespace) + namespace = this.namespace; + + + if (this.isValidKey(namespace) == false) + throw new Error(apf.formatErrorString(0, null, + "Clearing storage", + "Invalid namespace given: " + namespace)); + + + delete this.store[namespace] + }, + + /** + * Removes a key in a namespace. + * @param {String} key the identifier of the information. + * @param {String} namespace the named context of the keys to remove. + */ + remove: function(key, namespace){ + if (!namespace) + namespace = this.namespace; + + + if (this.isValidKey(namespace) == false) + throw new Error(apf.formatErrorString(0, null, + "Removing key", + "Invalid namespace given: " + namespace)); + + + if (this.store[namespace]) + delete this.store[namespace][key]; + }, + + /** + * Stores a key value pair in a namespace. + * @param {Array} keys a list of keys that identify the information stored. + * @param {Array} values a list of values to store. + * @param {String} namespace the named context into which to store the key value pair. + */ + putMultiple: function(keys, values, namespace) { + + if (this.isValidKeyArray(keys) === false + || ! values instanceof Array + || keys.length != values.length) { + throw new Error(apf.formatErrorString(0, null, + "Setting multiple name/value pairs", + "Invalid arguments: keys = [" + keys + "], \ + values = [" + values + "]")); + } + + + if (!namespace) + namespace = this.namespace; + + + if (this.isValidKey(namespace) == false) + throw new Error(apf.formatErrorString(0, null, + "Setting multiple name/value pairs", + "Invalid namespace given: " + namespace)); + + + // store the value + if (!this.store[namespace]) + this.store[namespace] = {}; + + // try to store the value + for (var i = 0; i < keys.length; i++) { + this.store[namespace][keys[i]] = apf.serialize(values[i]); + } + + return true; + }, + + /** + * Retrieves all the values of several keys in a namespace. + * @param {Array} keys a list of keys that identify the information retrieved. + * @param {String} namespace the named context into which to store the key value pair. + * @returns {Array} list of values that have been retrieved. + */ + getMultiple: function(keys, namespace){ + + if (this.isValidKeyArray(keys) === false) + throw new Error(apf.formatErrorString(0, null, + "Getting name/value pair", + "Invalid key array given: " + keys)); + + + if (!namespace) + namespace = this.namespace; + + + if (this.isValidKey(namespace) == false) + throw new Error(apf.formatErrorString(0, null, + "Getting multiple name/value pairs", + "Invalid namespace given: " + namespace)); + + + if (!this.store[namespace]) + return []; + + var results = []; + for (var i = 0; i < keys.length; i++){ + if (this.store[namespace][keys[i]]) + results.push(apf.unserialize(this.store[namespace][keys[i]])); + } + + return results; + }, + + /** + * Removes a key in a namespace. + * @param {Array} keys a list of keys that identify the information to be removed. + * @param {String} namespace the named context of the keys to remove. + */ + removeMultiple: function(keys, namespace){ + + if (this.isValidKeyArray(keys) === false) + throw new Error(apf.formatErrorString(0, null, + "Removing name/value pair", + "Invalid key array given: " + keys)); + + + if (!namespace) + namespace = this.namespace; + + + if (this.isValidKey(namespace) == false) + throw new Error(apf.formatErrorString(0, null, + "Removing multiple name/value pairs", + "Invalid namespace given: " + namespace)); + + + if (!this.store[namespace]) + return; + + for (var i = 0; i < keys.length; i++) + delete this.store[namespace][keys[i]]; + }, + + isPermanent: function(){ + return false; + }, + + getMaximumSize: function(){ + return this.SIZE_NO_LIMIT; + }, + + hasSettingsUI: function(){ + return false; + }, + + showSettingsUI: function(){ + throw new Error(this.declaredClass + + " does not support a storage settings user-interface"); + }, + + hideSettingsUI: function(){ + throw new Error(this.declaredClass + + " does not support a storage settings user-interface"); + } +}; + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/core/lib/teleport/http.js)SIZE(35422)TIME(Mon, 10 Jan 2011 10:53:11 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + + +/** + * This object does what is commonly known as Ajax, it Asynchronously + * communicates using Javascript And in most + * cases it sends or receives Xml. It allows for easy http + * communication from within the browser. This object provides + * {@link teleport.http.method.savecache caching} on top of + * the browser's cache. This enables you to optimize your application, because + * this can be set on a per call basis. + * Example: + * Retrieving content over http synchronously: + * + * var http = new apf.http(); + * var data = http.get("http://www.example.com/mydata.jsp", {async: false}); + * alert(data); + * + * Example: + * Retrieving content over http asynchronously: + * + * var http = new apf.http(); + * http.get("http://www.example.com/mydata.jsp", { + * callback: function(data, state, extra){ + * if (state != apf.SUCCESS) + * return alert('an error has occurred'); + * + * alert(data); + * } + * }); + * + * Example: + * Async http request with retry. + * + * var http = new apf.http(); + * http.get("http://www.example.com/mydata.jsp", { + * callback: function(data, state, extra){ + * if (state != apf.SUCCESS) { + * var oError = new Error(apf.formatErrorString(0, null, + * "While loading data", "Could not load data\n" + extra.message)); + * + * if (extra.tpModule.retryTimeout(extra, state, null, oError) === true) + * return true; + * + * throw oError; + * } + * + * alert(data); + * } + * }); + * + * + * @event error Fires when a communication error occurs. + * bubbles: yes + * cancelable: Prevents a communication error to be thrown. + * object: + * {Error} error the error object that is thrown when the event + * callback doesn't return false. + * {Number} state the state of the call + * Possible values: + * apf.SUCCESS the request was successfull + * apf.TIMEOUT the request has timed out. + * apf.ERROR an error has occurred while making the request. + * apf.OFFLINE the request was made while the application was offline. + * {mixed} userdata data that the caller wanted to be available in + * the callback of the http request. + * {XMLHttpRequest} http the object that executed the actual http request. + * {String} url the url that was requested. + * {Http} tpModule the teleport module that is making the request. + * {Number} id the id of the request. + * {String} message the error message. + * + * @constructor + * @define http + * @addnode teleport + * @default_private + * + * @author Ruben Daniels (ruben AT ajax DOT org) + * @version %I%, %G% + * @since 0.4 + */ +apf.http = function(){ + this.queue = [null]; + this.callbacks = {}; + this.cache = {}; + + /** + * Sets the timeout of http requests in milliseconds. Default is 10000ms (10s). + */ + this.timeout = this.timeout || 10000; //default 10 seconds + + /** + * Sets whether this element routes traffic through a server proxy. + * Remarks: + * This can also be set on a per call basis. See {@link teleport.http.method.get}. + */ + this.autoroute = this.autoroute || false; + + /** + * String specifying the url to the route script. + * Remarks: + * The route script will receive the route information in 3 extra headers: + * X-Route-Request - Containing the destination url.
+ * X-Proxy-Request - Containing the destination url.
+ * X-Compress-Response - Set to 'gzip'.
+ */ + this["route-server"] = this["route-server"] || null; + + if (!this.$uniqueId) + this.$uniqueId = apf.all.push(this) - 1; + + this.toString = this.toString || function(){ + return "[Ajax.org Teleport Component : (HTTP)]"; + }; + + + var namespace = apf.config.name + ".apf.http"; + + /** + * Saves the apf http cache to the available {@link core.storage storage engine}. + */ + this.saveCache = function(){ + + if (!apf.serialize) + throw new Error(apf.formatErrorString(1079, this, + "HTTP save cache", + "Could not find JSON library.")); + + + + apf.console.info("[HTTP] Loading HTTP Cache", "teleport"); + + + var strResult = apf.serialize(comm.cache); + apf.storage.put("cache_" + this.name, strResult, + apf.config.name + ".apf.http"); + }; + + /** + * Loads the apf http cache from the available {@link core.storage storage engine}. + */ + this.loadCache = function(){ + var strResult = apf.storage.get("cache_" + this.name, + apf.config.name + ".apf.http"); + + + apf.console.info("[HTTP] Loading HTTP Cache", "steleport"); + + + if (!strResult) + return false; + + this.cache = apf.unserialize(strResult); + + return true; + }; + + /** + * Removes the stored http cache from the available {@link core.storage storage engine}. + */ + this.clearCache = function(){ + apf.storage.remove("cache_" + this.name, + apf.config.name + ".apf.http"); + }; + + + /** + * Makes an http request that receives xml + * @param {String} url the url that is accessed. + * @param {Object} options the options for the http request + * Properties: + * {Boolean} async whether the request is sent asynchronously. Defaults to true. + * {mixed} userdata custom data that is available to the callback function. + * {String} method the request method (POST|GET|PUT|DELETE). Defaults to GET. + * {Boolean} nocache whether browser caching is prevented. + * {String} data the data sent in the body of the message. + * {Boolean} autoroute whether the request can fallback to a server proxy. + * {Boolean} caching whether the request should use internal caching. + * {Boolean} ignoreOffline whether to ignore offline catching. + * {Function} callback the handler that gets called whenever the + * request completes succesfully or with an error, + * or when the request times out. + */ + this.getXml = function(url, callback, options){ + if (!options) options = {}; + options.useXML = true; + options.callback = callback; + return this.get(url, options); + }; + + this.getJSON = function(url, callback, options){ + if (!options) options = {}; + options.callback = callback; + options.useJSON = true; + return this.get(url, options); + }; + + /** + * Makes an http request. + * @param {String} url the url that is accessed. + * @param {Object} options the options for the http request + * Properties: + * {Boolean} async whether the request is sent asynchronously. Defaults to true. + * {mixed} userdata custom data that is available to the callback function. + * {String} method the request method (POST|GET|PUT|DELETE). Defaults to GET. + * {Boolean} nocache whether browser caching is prevented. + * {String} data the data sent in the body of the message. + * {Boolean} useXML whether the result should be interpreted as xml. + * {Boolean} autoroute whether the request can fallback to a server proxy. + * {Boolean} caching whether the request should use internal caching. + * {Boolean} ignoreOffline whether to ignore offline catching. + * {String} contentType the mime type of the message + * {Function} callback the handler that gets called whenever the + * request completes succesfully or with an error, + * or when the request times out. + */ + this.get = this.$get = function(url, options){ + if (!options) + options = {}; + + var _self = this; + var id = options.id; + + var bHasOffline = (typeof apf.offline != "undefined"); + if (bHasOffline && !apf.offline.onLine && options.notWhenOffline) + return false; + + if (bHasOffline && !apf.offline.onLine && !options.ignoreOffline) { + if (apf.offline.queue.enabled) { + //Let's record all the necesary information for future use (during sync) + var info = apf.extend({ + url : url, + callback : options.callback, + retry : function(){ + _self.get(this.url, this.options); + }, + $object : [this.name, "apf.oHttp", "new apf.http()"], + $retry : "this.object.get(this.url, this.options)" + }, options); + + apf.offline.queue.add(info); + + return; + } + + /* + Apparently we're doing an HTTP call even though we're offline + I'm allowing it, because the developer seems to know more + about it than I right now + */ + + + apf.console.warn("Executing HTTP request even though application is offline"); + + } + + + var async = options.async = (options.async + || typeof options.async == "undefined" || apf.isOpera || false); + + + if (apf.isWebkit) + url = apf.html_entity_decode(url); + + + var data = options.data || ""; + + if (apf.isNot(id)) { + + if (this.cache[url] && this.cache[url][data]) { + var http = { + responseText : this.cache[url][data], + responseXML : {}, + status : 200, + isCaching : true + } + } + else + + var http = apf.getHttpReq(); + + id = this.queue.push({ + http : http, + url : url, + callback : options.callback, + retries : 0, + options : options + }) - 1; + + + if (http.isCaching) { + if (async) + return $setTimeout("apf.lookup(" + this.$uniqueId + + ").receive(" + id + ");", 50); + else + return this.receive(id); + } + + } + else { + var http = this.queue[id].http; + + + if (http.isCaching) + http = apf.getHttpReq(); + else + + http.abort(); + } + + if (async) { + + if (apf.hasReadyStateBug) { + this.queue[id].starttime = new Date().getTime(); + this.queue[id].timer = setInterval(function(){ + var diff = new Date().getTime() - _self.queue[id].starttime; + if (diff > _self.timeout) { + _self.$timeout(id); + return + }; + + if (_self.queue[id].http.readyState == 4) { + clearInterval(_self.queue[id].timer); + _self.receive(id); + } + }, 20); + } + else + + { + http.onreadystatechange = function(){ + if (!_self.queue[id] || http.readyState != 4) + return; + if (async && arguments.callee.caller) + setTimeout(function(){_self.receive(id)}); + else + _self.receive(id); + } + } + } + + var autoroute = this.autoroute && apf.isOpera + ? true //Bug in opera + : (options.autoroute || this.shouldAutoroute), + httpUrl = autoroute ? this["route-server"] : url; + + + if (!options.hideLogMessage) { + apf.console.teleport(this.queue[id].log = new apf.teleportLog({ + id : id, + tp : this, + type : options.type, + method : this.method || options.method || "GET", + url : url, + route : autoroute ? httpUrl : "", + data : new String(data && data.xml ? data.xml : data), + start : new Date() + })); + } + + var headers = []; + + function setRequestHeader(name, value){ + + headers.push(name + ": " + value); + + http.setRequestHeader(name, value); + } + + var errorFound = false; + try { + if (options.nocache) + httpUrl = apf.getNoCacheUrl(httpUrl); + + + if (apf.config.queryAppend) { + httpUrl += (httpUrl.indexOf("?") == -1 ? "?" : "&") + + apf.config.queryAppend; + } + + + // experimental for Firefox Cross Domain problem + // http://ubiquity-xforms.googlecode.com/svn/wiki/CrossDomainSubmissionDeployment.wiki + + + try { + if (apf.isGecko) + netscape.security.PrivilegeManager.enablePrivilege("UniversalBrowserRead"); + } + catch (e) { + + + //Currently we don't support html5 cross domain access + if (apf.hasHtml5XDomain + && httpUrl.match(/^http:\/\//) + && !new apf.url(httpUrl).isSameLocation()) { + throw new Error(apf.formatErrorString(0, + this, "Communication error", "Url: " + httpUrl + + "\nReason: Same origin policy in effect")); + } + + + } + + + //end experimental + + http.open(this.method || options.method || "GET", httpUrl, async); + + if (options.username) { + setRequestHeader("Authorization", "Basic " + + apf.crypto.Base64.encode(options.username + ":" + options.password)) + } + + //@todo OPERA ERROR's here... on retry [is this still applicable?] + setRequestHeader("X-Requested-With", "XMLHttpRequest"); + if (!options.headers || !options.headers["Content-type"]) + setRequestHeader("Content-type", options.contentType || this.contentType + || (this.useXML || options.useXML ? "text/xml" : "text/plain")); + + if (autoroute) { + setRequestHeader("X-Route-Request", url); + setRequestHeader("X-Proxy-Request", url); + setRequestHeader("X-Compress-Response", "gzip"); + } + } + catch (e) { + errorFound = e.message; + } + + if (errorFound) { + var useOtherXH = false; + + + if (self.XMLHttpRequestUnSafe) { + try { + http = new XMLHttpRequestUnSafe(); + http.onreadystatechange = function(){ + if (!_self.queue[id] || http.readyState != 4) + return; + + _self.receive(id); + } + http.open(this.method || options.method || "GET", (options.nocache + ? apf.getNoCacheUrl(httpUrl) + : httpUrl), async); + + setRequestHeader("X-Requested-With", "XMLHttpRequest"); + if (!options.headers || !options.headers["Content-type"]) + setRequestHeader("Content-type", this.contentType + || (this.useXML || options.useXML ? "text/xml" : "text/plain")); + + this.queue[id].http = http; + options.async = true; //force async + useOtherXH = true; + } + catch (e) {} + } + + + // Retry request by routing it + if (!useOtherXH && this.autoroute && !autoroute) { + + if (!apf.isNot(id)) + clearInterval(this.queue[id].timer); + + + this.shouldAutoroute = true; + + options.autoroute = true; + return this.get(url, options); + } + + if (!useOtherXH) { + //Routing didn't work either... Throwing error + var noClear = options.callback ? options.callback(null, apf.ERROR, { + userdata: options.userdata, + http : http, + url : url, + tpModule: this, + id : id, + message : "Permission denied accessing remote resource: " + + url + "\nMessage: " + errorFound + }) : false; + if (!noClear) + this.clearQueueItem(id); + + return; + } + } + + if (this.$headerHook) + this.$headerHook(http); + + //Set request headers + if (options.headers) { + for (var name in options.headers) + setRequestHeader(name, options.headers[name]); + } + + + if (!options.hideLogMessage) + this.queue[id].log.request(headers); + + + function handleError(){ + var msg = self.navigator && self.navigator.onLine + ? "File or Resource not available " + url + : "Browser is currently working offline"; + + + apf.console.warn(msg, "teleport"); + if (!options.hideLogMessage) + _self.queue[id].log.response({ + + end : new Date(), + + message : msg, + http : http + }); + + + var state = self.navigator && navigator.onLine + ? apf.ERROR + : apf.TIMEOUT; + + // File not found + var noClear = options.callback ? options.callback(null, state, { + userdata : options.userdata, + http : http, + url : url, + tpModule : _self, + id : id, + message : msg + }) : false; + if(!noClear) _self.clearQueueItem(id); + } + + function send(isLocal){ + var hasError; + + if (apf.isIE && isLocal) { //When local IE calls onreadystate immediately + var oldWinOnerror = window.onerror; + window.onerror = function(){ + if (arguments.caller.callee == send) { + window.onerror = oldWinOnerror; + //_self.receive(id); + //setTimeout(function(){handleError();}); + return true; + } + else { + window.onerror = oldWinOnerror; + + if (oldWinOnerror) + return oldWinOnerror.apply(window, arguments); + } + } + http.send(data); + window.onerror = oldWinOnerror; + } + else { + try{ + http.send(data); + } + catch(e){ + hasError = true; + } + } + + if (hasError) { + handleError(); + return; + } + } + + if (!async) { + send.call(this); + return this.receive(id); + } + else { + if (apf.loadsLocalFilesSync && location.protocol == "file:" + && url.indexOf("http://") == -1) { + $setTimeout(function(){ + send.call(_self, true); + }); + } + else + send.call(_self); + + return id; + } + }; + + + /** + * Method that all async objects should implement + * @private + */ + if (!this.exec) { + this.exec = function(method, args, callback, options){ + if (!options) + options = {}; + + var url = args[0], query = ""; + if (!options.method) + options.method = method.toUpperCase(); + if (!options.callback) + options.callback = callback; + + this.contentType = "application/x-www-form-urlencoded"; + this.$get( + apf.getAbsolutePath(apf.config.baseurl, url), + options.method == "GET" + ? options + : apf.extend({data : query}, options) + ); + } + } + + + /** + * @private + */ + this.receive = function(id){ + if (!this.queue[id]) + return false; + + var qItem = this.queue[id], + http = qItem.http, + callback = qItem.callback; + //if (apf.isGecko) + // var apf = self.apf || apf; // needed here to fix a rare ReferenceError in FF + + + clearInterval(qItem.timer); + + + if (self.navigator && navigator.onLine === false + && (location.protocol != "file:" + || qItem.url.indexOf("http://") > -1)) + return false; + + // Test if HTTP object is ready + if (qItem.async) { + try { + if (http.status) {} + } + catch (e) { + var _self = this; + return $setTimeout(function(){ + _self.receive(id) + }, 10); + } + } + + + if (!qItem.options.hideLogMessage) { + apf.console.info("[HTTP] Receiving [" + id + "]" + + (http.isCaching + ? "[cached]" + : "") + + " from " + qItem.url, + "teleport", + http.responseText); + } + + + //Gonna check for validity of the http response + var errorMessage = [], + extra = { + + end : new Date(), + + tpModule : this, + http : http, + status : http.status, + url : qItem.url, + callback : callback, + id : id, + retries : qItem.retries || 0, + userdata : qItem.options.userdata + }; + + // Check HTTP Status + // The message didn't receive the server. We consider this a timeout (i.e. 12027) + if (http.status > 600) + return this.$timeout(id); + + extra.data = qItem.options.useJSON + ? eval("(" + http.responseText + ")") + : http.responseText; //Can this error? + + if (http.status >= 400 && http.status < 600 || http.status < 10 + && (http.status != 0 || !apf.isIE && !http.responseText)) { //qItem.url.substr(0, 6) == "file:/" + + //@todo This should probably have an RPC specific handler + if (http.status == 401) { + var auth = apf.document.getElementsByTagNameNS(apf.ns.apf, "auth")[0]; + if (auth) { + var wasDelayed = qItem.isAuthDelayed; + qItem.isAuthDelayed = true; + if (auth.authRequired(extra, wasDelayed) === true) + return; + } + } + + + errorMessage.push("HTTP error [" + id + "]:" + http.status + "\n" + + http.responseText); + } + + // Check for XML Errors + if (qItem.options.useXML || this.useXML) { + /* Note (Mike, Oct 14th 2008): for WebDAV, I had to copy the lines below, + it required custom responseXML handling/ + parsing. + If you alter this code, please correct + webdav.js appropriately. + */ + if ((http.responseText || "").replace(/^[\s\n\r]+|[\s\n\r]+$/g, "") == "") + errorMessage.push("Received an empty XML document (0 bytes)"); + else { + try { + var xmlDoc = (http.responseXML && http.responseXML.documentElement) + ? apf.xmlParseError(http.responseXML) + : apf.getXmlDom(http.responseText); + + if (!apf.supportNamespaces) + xmlDoc.setProperty("SelectionLanguage", "XPath"); + + extra.data = xmlDoc.documentElement; + } + catch(e){ + errorMessage.push("Received invalid XML\n\n" + e.message); + } + } + } + + //Process errors if there are any + if (errorMessage.length) { + extra.message = errorMessage.join("\n"); + + + if (qItem.log) + qItem.log.response(extra); + + + // Send callback error state + if (!callback || !callback(extra.data, apf.ERROR, extra)) + this.clearQueueItem(id); + + return; + } + + + if (qItem.options.caching) { + if (!this.cache[qItem.url]) + this.cache[qItem.url] = {}; + + this.cache[qItem.url][qItem.options.data] = http.responseText; + } + + + + if (qItem.log) + qItem.log.response(extra); + + + //Http call was successfull Success + if (!callback || !callback(extra.data, apf.SUCCESS, extra)) + this.clearQueueItem(id); + + return extra.data; + }; + + this.$timeout = function(id){ + if (!this.queue[id]) + return false; + + var qItem = this.queue[id], + http = qItem.http; + + + clearInterval(qItem.timer); + + + // Test if HTTP object is ready + try { + if (http.status) {} + } + catch (e) { + var _self = this; + return $setTimeout(function(){ + _self.$timeout(id) + }, 10); + } + + var callback = qItem.callback; + + http.abort(); + + + apf.console.info("HTTP Timeout [" + id + "]", "teleport"); + + + var extra; + var noClear = callback ? callback(null, apf.TIMEOUT, extra = { + + end : new Date(), + + userdata: qItem.options.userdata, + http : http, + url : qItem.url, + tpModule: this, + id : id, + message : "HTTP Call timed out", + retries : qItem.retries || 0 + }) : false; + + + if (qItem.log) + qItem.log.response(extra); + + + if (!noClear) + this.clearQueueItem(id); + }; + + /** + * Checks if the request has times out. If so it's retried + * three times before an exception is thrown. Request retrying is a very + * good way to create robust Ajax applications. In many cases, even with + * good connections requests time out. + * @param {Object} extra the information object given as a third + * argument of the http request callback. + * @param {Number} state the return code of the http request. + * Possible values: + * apf.SUCCESS the request was successfull + * apf.TIMEOUT the request has timed out. + * apf.ERROR an error has occurred while making the request. + * apf.OFFLINE the request was made while the application was offline. + * @param {AmlNode} [amlNode] the element receiving the error event. + * @param {Error} [oError] the error to be thrown when the request is + * not retried. + * @param {Number} [maxRetries] the number of retries that are done before + * the request times out. Default is 3. + */ + this.retryTimeout = function(extra, state, amlNode, oError, maxRetries){ + if (state == apf.TIMEOUT + && extra.retries < (maxRetries || apf.maxHttpRetries)) + return extra.tpModule.retry(extra.id); + + oError = oError || new Error(apf.formatErrorString(0, + this, "Communication " + (state == apf.TIMEOUT + ? "timeout" + : "error"), "Url: " + extra.url + "\nInfo: " + extra.message)); + + if ((amlNode || apf).dispatchEvent("error", apf.extend({ + error : oError, + state : state, + extra : extra, + bubbles : true + }, extra)) === false) + return 2; + }; + + /** + * Removes the item from the queue. This is usually done automatically. + * However when the callback returns true the queue isn't cleared, for instance + * when a request is retried. The id of the call + * is found on the 'extra' object. The third argument of the callback. + * Example: + * + * http.clearQueueItem(extra.id); + * + * @param {Number} id the id of the call that should be removed from the queue. + */ + this.clearQueueItem = function(id){ + if (!this.queue[id]) + return false; + + + clearInterval(this.queue[id].timer); + + + if (apf.releaseHTTP && !apf.isGecko) + apf.releaseHTTP(this.queue[id].http); + + this.queue[id] = null; + delete this.queue[id]; + + return true; + }; + + /** + * Retries a call based on it's id. The id of the call is found on the + * 'extra' object. The third argument of the callback. + * Example: + * + * function callback(data, state, extra){ + * if (state == apf.TIMEOUT && extra.retries < apf.maxHttpRetries) + * return extra.tpModule.retry(extra.id); + * + * //Do stuff here + * } + * + * @param {Number} id the id of the call that should be retried. + */ + this.retry = function(id){ + if (!this.queue[id]) + return false; + + var qItem = this.queue[id]; + + + clearInterval(qItem.timer); + + + + apf.console.info("[HTTP] Retrying request [" + id + "]", "teleport"); + + + qItem.retries++; + qItem.options.id = id; + this.get(qItem.url, qItem.options); + + return true; + }; + + /** + * see {@link teleport.http.method.clearqueueitem} + */ + this.cancel = function(id){ + if (id === null) + id = this.queue.length - 1; + + if (!this.queue[id]) + return false; + + if (apf.isGecko) + this.queue[id].http.abort(); + + this.clearQueueItem(id); + }; + + if (!this.$loadAml && !this.instantiate && !this.call) { + /** + * @private + */ + this.$loadAml = function(x){ + var receive = this["receive"]; + + for (var i = 0, l = this.childNodes.length; i < l; i++) { + if (this.childNodes[i].nodeType != 1) + continue; + + var url = this.childNodes[i].getAttribute("url"), + callback = self[this.childNodes[i].getAttribute("receive") || receive], + options = { + useXML : this.childNodes[i].getAttribute("type") == "XML", + async : !apf.isFalse(this.childNodes[i].getAttribute("async")) + }; + + this[this.childNodes[i].getAttribute("name")] = function(data, userdata){ + options.userdata = userdata; + options.data = data; + return this.get(url, options); + } + } + }; + + /** + * @private + */ + this.instantiate = function(x){ + var url = x.getAttribute("src"), + options = { + async : x.getAttribute("async") != "false", + nocache : true + }; + + this.getURL = function(data, userdata){ + options.data = data; + options.userdata = userdata; + options.callback = this.callbacks.getURL; + return this.get(url, options); + } + + var name = "http" + Math.round(Math.random() * 100000); + apf.setReference(name, this); + + return name + ".getURL()"; + }; + + /** + * @private + */ + this.call = function(method, args){ + this[method].call(this, args); + }; + } +}; + + + +apf.Init.run("http"); + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/core/lib/teleport/iframe.js)SIZE(5720)TIME(Mon, 10 Jan 2011 10:53:11 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + + +/* + * This object create a compatibility layer for environments where the + * native XMLHttpRequest object isn't available. It fall backs on an iframe for + * it's communication + */ +apf.USEIFRAME = false; +/** + * @private + */ +function runTpIframe(){ + function XMLHttpRequest(){ + this.$uniqueId = apf.all.push(this); + var _self = this; + + if (apf.USEIFRAME) { + this.iframe = document.body.appendChild(document.createElement("iframe")); + //this.iframe.style.display = "none"; + this.iframe.style.width = "400px"; + this.iframe.style.height = "300px"; + this.iframe.style.position = "absolute"; + this.iframe.style.left = "0"; + this.iframe.style.top = "0"; + this.iframe.style.zIndex = 1000000000; + document.body.style.display = "block"; + } + else { + var xml = document.body.appendChild(document.createElement("xml")); + this.xmlDocument = xml.XMLDocument; + this.xmlDocument.setProperty("SelectionLanguage", "XPath"); + document.body.removeChild(xml); + } + + this.readyState = 0; + this.status = null; + this.responseText = null; + this.responseXML = null; + + this.setRequestHeader = function(){ + //Not supported + } + + this.send = function(){ + this.done = false; + if (apf.USEIFRAME) + this.iframe.src = this.url; + else + this.xmlDocument.load(this.url); + } + + this.open = function(protocol, url, async){ + this.method = protocol; + this.url = url; + this.async = async; + + if (apf.USEIFRAME) { + if (!async) + throw new Error(apf.formatErrorString(1081, null, "Iframe callback", "Compatibility mode prevents possibility of non-async calls")); + this.iframe.onload = stateChangeHandlerIframe; + //onreadystatechange + //onerror + } + else { + this.xmlDocument.async = async; + this.xmlDocument.onreadystatechange = stateChangeHandler; + } + } + + this.abort = function(){ + if (apf.USEIFRAME) { + this.iframe.onreadystatechange = null; + this.iframe.src = "about:blank"; + _self.done = false; + _self.readyState = ""; + } + else + this.xmlDocument.abort(); + } + + var me = this; + function stateChangeHandler(){ + me.receive(); + } + + this.receive = function(){ + this.readyState = this.xmlDocument.readyState; + + if (this.readyState == 4) { + this.status = 200; + this.responseText = this.xmlDocument.responseText || this.xmlDocument.xml; + this.responseXML = this.xmlDocument; + } + + if (this.onreadystatechange) + this.onreadystatechange(); + } + + function stateChangeHandlerIframe(){ + if (_self.done) // || _self.readyState != "complete") + return; + _self.done = true; + _self.receiveIframe(); + } + + this.receiveIframe = function(){ + var strXml = this.iframe.contentWindow.document.body.innerText; + strXml = strXml.replace(/^- /gm, ""); + + try { + this.responseXML = apf.getXmlDom(strXml); + this.responseXML.setProperty("SelectionLanguage", "XPath"); + this.responseXML.loadXML(strXml); + } + catch (e) { + this.responseXML = null; + } + this.responseText = this.responseXML ? this.responseXML.xml : strXml; + this.status = 200; + this.readyState = 4; + + if (this.onreadystatechange) + this.onreadystatechange(); + } + } + + function getDOMParser(message, no_error){ + var xml = document.body.appendChild(document.createElement("xml")); + var xmlParser = xml.XMLDocument; + document.body.removeChild(xml); + + xmlParser.setProperty("SelectionLanguage", "XPath"); + if (message) + xmlParser.loadXML(message); + if (!no_error) + apf.xmlParseError(xmlParser); + + return xmlParser; + } +} + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/core/lib/teleport/socket.js)SIZE(19222)TIME(Mon, 10 Jan 2011 10:53:11 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: socket://www.fsf.org. + * + */ + + + +/** + * This object does what is commonly known as Ajax, it Asynchronously + * communicates using Javascript And in most + * cases it sends or receives Xml. It allows for easy socket + * communication from within the browser. This object provides + * {@link teleport.socket.method.savecache caching} on top of + * the browser's cache. This enables you to optimize your application, because + * this can be set on a per call basis. + * Example: + * Retrieving content over socket synchronously: + * + * var socket = new apf.socket(); + * var data = socket.get("http://www.example.com/mydata.jsp", {async: false}); + * alert(data); + * + * Example: + * Retrieving content over socket asynchronously: + * + * var socket = new apf.socket(); + * socket.get("socket://www.example.com/mydata.jsp", { + * callback: function(data, state, extra){ + * if (state != apf.SUCCESS) + * return alert('an error has occurred'); + * + * alert(data); + * } + * }); + * + * Example: + * Async socket request with retry. + * + * var socket = new apf.socket(); + * socket.get("http://www.example.com/mydata.jsp", { + * callback: function(data, state, extra){ + * if (state != apf.SUCCESS) { + * var oError = new Error(apf.formatErrorString(0, null, + * "While loading data", "Could not load data\n" + extra.message)); + * + * if (extra.tpModule.retryTimeout(extra, state, null, oError) === true) + * return true; + * + * throw oError; + * } + * + * alert(data); + * } + * }); + * + * + * @event error Fires when a communication error occurs. + * bubbles: yes + * cancelable: Prevents a communication error to be thrown. + * object: + * {Error} error the error object that is thrown when the event + * callback doesn't return false. + * {Number} state the state of the call + * Possible values: + * apf.SUCCESS the request was successfull + * apf.TIMEOUT the request has timed out. + * apf.ERROR an error has occurred while making the request. + * apf.OFFLINE the request was made while the application was offline. + * {mixed} userdata data that the caller wanted to be available in + * the callback of the socket request. + * {XMLHttpRequest} socket the object that executed the actual socket request. + * {String} url the url that was requested. + * {Http} tpModule the teleport module that is making the request. + * {Number} id the id of the request. + * {String} message the error message. + * + * @constructor + * @define socket + * @addnode teleport + * @default_private + * + * @author Mike de Boer (mike AT ajax DOT org) + * @version %I%, %G% + * @since 3.0 + */ +apf.socket = function(){ + this.pool = {}; + this.callbacks = {}; + this.cache = {}; + + /** + * Sets the timeout of a socket requests in milliseconds. Default is 10000ms (10s). + */ + this.timeout = this.timeout || 10000; //default 10 seconds + + if (!this.$uniqueId) + this.$uniqueId = apf.all.push(this) - 1; + + this.toString = this.toString || function(){ + return "[Ajax.org Teleport Component : (Socket)]"; + }; + + + + this.saveCache = apf.K; + + this.loadCache = function(){ return true; }; + + this.clearCache = apf.K; + + + /** + * Makes an socket request that receives xml + * @param {String} url the url that is accessed. + * @param {Object} options the options for the socket request + * Properties: + * {Boolean} async whether the request is sent asynchronously. Defaults to true. + * {mixed} userdata custom data that is available to the callback function. + * {String} method the request method (POST|GET|PUT|DELETE). Defaults to GET. + * {Boolean} nocache whether browser caching is prevented. + * {String} data the data sent in the body of the message. + * {Boolean} autoroute whether the request can fallback to a server proxy. + * {Boolean} caching whether the request should use internal caching. + * {Boolean} ignoreOffline whether to ignore offline catching. + * {Function} callback the handler that gets called whenever the + * request completes succesfully or with an error, + * or when the request times out. + */ + this.getXml = function(url, callback, options){ + if (!options) options = {}; + options.useXML = true; + options.callback = callback; + return this.get(url, options); + }; + + /** + * Makes an socket request. + * @param {String} url the url that is accessed. + * @param {Object} options the options for the socket request + * Properties: + * {mixed} userdata custom data that is available to the callback function. + * {String} data the data sent in the body of the message. + * {Boolean} useXML whether the result should be interpreted as xml. + * {Function} callback the handler that gets called whenever the + * request completes succesfully or with an error, + * or when the request times out. + */ + this.get = this.$get = function(url, options){ + if (!options) + options = {}; + + var socket, + _self = this, + id = options.id || url, + data = options.data || ""; + + function handleError(){ + var msg = "File or Resource not available " + url; + + + apf.console.warn(msg, "teleport"); + + + var state = apf.ERROR; + + // File not found + if (options.callback) { + options.callback(null, state, { + userdata : options.userdata, + socket : socket, + url : url, + tpModule : _self, + id : id, + message : msg + }); + } + } + + function send(isLocal){ + var hasError; + + try{ + socket.write(data); + } + catch(e){ + hasError = true; + } + + if (hasError) { + handleError(); + return; + } + } + + if (!(socket = this.pool[id] ? this.pool[id].socket : null)) { + //socket = apf.getSocket(); + var oUrl = new apf.url(url); + if (!oUrl.host || !oUrl.port) + throw new Error("no valid connection string for a socket connection: " + url); + + var net = require("net"); + socket = net.createConnection(oUrl.port, oUrl.host); + socket.setEncoding("utf8"); + + socket.addListener("connect", function() { + send.call(_self); + }); + + socket.addListener("data", function(data) { + if (arguments.callee.caller) + setTimeout(function(){_self.receive(id, data)}); + else + _self.receive(id, data); + }); + + + socket.addListener("close", function() { + apf.console.log("[socket] connection closed"); + }); + + socket.addListener("error", function(e) { + apf.console.log("[socket] connection error: " + e); + }); + + + socket.addListener("end", function() { + + apf.console.log("[socket] connection ended"); + + _self.pool[id] = null; + delete _self.pool[id]; + }); + + this.pool[id] = { + socket : socket, + url : url, + callbacks : [options.callback], + retries : 0, + options : options, + received : "" + }; + } + else { + this.pool[id].callbacks.push(options.callback); + send.call(this); + } + + return id; + }; + + + /** + * Method that all async objects should implement + * @private + */ + if (!this.exec) { + this.exec = function(method, args, callback, options){ + if (!options) + options = {}; + + var url = args[0], query = ""; + if (!options.method) + options.method = method.toUpperCase(); + if (!options.callback) + options.callback = callback; + + this.$get(apf.getAbsolutePath(apf.config.baseurl, url), options); + } + } + + + /** + * @private + */ + this.receive = function(id, data){ + if (!this.pool[id]) + return false; + + var qItem = this.pool[id], + socket = qItem.socket, + callback = qItem.callbacks.shift(); + + //Gonna check for validity of the socket response + var errorMessage = [], + extra = { + + end : new Date(), + + tpModule : this, + socket : socket, + url : qItem.url, + callback : callback, + id : id, + retries : qItem.retries || 0, + userdata : qItem.options.userdata + }; + + // Check HTTP Status + // The message didn't receive the server. We consider this a timeout (i.e. 12027) + //if (socket.status > 600) + // return this.$timeout(id); + + extra.data = data; //Can this error? + + qItem.received += data; + var t = qItem.received.split(/(<\/(?:message|stream:features|challenge)>)/); + + if(t.length<=1){ + return; + } + data = t[0]+t[1]; + qItem.received = t[2]; + + // Check for XML Errors + if (qItem.options.useXML || this.useXML) { + /* Note (Mike, Oct 14th 2008): for WebDAV, I had to copy the lines below, + it required custom responseXML handling/ + parsing. + If you alter this code, please correct + webdav.js appropriately. + */ + if ((data || "").replace(/^[\s\n\r]+|[\s\n\r]+$/g, "") == "") + errorMessage.push("Received an empty XML document (0 bytes)"); + else { + try { + var xmlDoc = apf.getXmlDom(data); + + if (!apf.supportNamespaces) + xmlDoc.setProperty("SelectionLanguage", "XPath"); + + extra.data = xmlDoc.documentElement; + } + catch(e){ + errorMessage.push("Received invalid XML\n\n" + e.message); + } + } + } + + //Process errors if there are any + if (errorMessage.length) { + extra.message = errorMessage.join("\n"); + + // Send callback error state + if (callback) + callback(extra.data, apf.ERROR, extra); + + return; + } + + //Http call was successfull Success + if (callback) + callback(extra.data, apf.SUCCESS, extra); + + return extra.data; + }; + + this.$timeout = function(id){ + if (!this.pool[id]) + return false; + + var qItem = this.pool[id], + socket = qItem.socket; + + // Test if HTTP object is ready + try { + if (socket.status) {} + } + catch (e) { + var _self = this; + return $setTimeout(function(){ + _self.$timeout(id) + }, 10); + } + + var callback = qItem.callback; + + socket.abort(); + + + apf.console.info("Socket Timeout [" + id + "]", "teleport"); + + + var extra; + var noClear = callback ? callback(null, apf.TIMEOUT, extra = { + + end : new Date(), + + userdata: qItem.options.userdata, + socket : socket, + url : qItem.url, + tpModule: this, + id : id, + message : "HTTP Call timed out", + retries : qItem.retries || 0 + }) : false; + }; + + /** + * Checks if the request has times out. If so it's retried + * three times before an exception is thrown. Request retrying is a very + * good way to create robust Ajax applications. In many cases, even with + * good connections requests time out. + * @param {Object} extra the information object given as a third + * argument of the socket request callback. + * @param {Number} state the return code of the socket request. + * Possible values: + * apf.SUCCESS the request was successfull + * apf.TIMEOUT the request has timed out. + * apf.ERROR an error has occurred while making the request. + * apf.OFFLINE the request was made while the application was offline. + * @param {AmlNode} [amlNode] the element receiving the error event. + * @param {Error} [oError] the error to be thrown when the request is + * not retried. + * @param {Number} [maxRetries] the number of retries that are done before + * the request times out. Default is 3. + */ + this.retryTimeout = function(extra, state, amlNode, oError, maxRetries){ + if (state == apf.TIMEOUT + && extra.retries < (maxRetries || apf.maxHttpRetries)) + return extra.tpModule.retry(extra.id); + + oError = oError || new Error(apf.formatErrorString(0, + this, "Communication " + (state == apf.TIMEOUT + ? "timeout" + : "error"), "Url: " + extra.url + "\nInfo: " + extra.message)); + + if ((amlNode || apf).dispatchEvent("error", apf.extend({ + error : oError, + state : state, + bubbles : true + }, extra)) === false) + return true; + }; + + /** + * Removes the item from the queue. This is usually done automatically. + * For the socket component, this Function is just a stub, to be compatible with + * the HTTP interface + * @param {Number} id the id of the call that should be removed from the queue. + */ + this.clearQueueItem = function(id){ + return; + }; + + /** + * Retries a call based on it's id. The id of the call is found on the + * 'extra' object. The third argument of the callback. + * Example: + * + * function callback(data, state, extra){ + * if (state == apf.TIMEOUT && extra.retries < apf.maxHttpRetries) + * return extra.tpModule.retry(extra.id); + * + * //Do stuff here + * } + * + * @param {Number} id the id of the call that should be retried. + */ + this.retry = function(id){ + if (!this.pool[id]) + return false; + + var qItem = this.pool[id]; + + + apf.console.info("[Socket] Retrying request [" + id + "]", "teleport"); + + + qItem.retries++; + qItem.options.id = id; + this.get(qItem.url, qItem.options); + + return true; + }; + + /** + * see {@link teleport.socket.method.clearqueueitem} + */ + this.cancel = function(id){ + if (id === null) + id = this.pool.length - 1; + + if (!this.pool[id]) + return false; + + //this.pool[id][0].abort(); + }; + + if (!this.$loadAml && !this.instantiate && !this.call) { + /** + * @private + */ + this.$loadAml = function(x){ + var receive = this["receive"]; + + for (var i = 0, l = this.childNodes.length; i < l; i++) { + if (this.childNodes[i].nodeType != 1) + continue; + + var url = this.childNodes[i].getAttribute("url"), + callback = self[this.childNodes[i].getAttribute("receive") || receive], + options = { + useXML : this.childNodes[i].getAttribute("type") == "XML" + }; + + this[this.childNodes[i].getAttribute("name")] = function(data, userdata){ + options.userdata = userdata; + options.data = data; + return this.get(url, options); + } + } + }; + + /** + * @private + */ + this.instantiate = function(x){ + var url = x.getAttribute("src"), + options = { + async : x.getAttribute("async") != "false", + nocache : true + }; + + this.getURL = function(data, userdata){ + options.data = data; + options.userdata = userdata; + options.callback = this.callbacks.getURL; + return this.get(url, options); + } + + var name = "socket" + Math.round(Math.random() * 100000); + apf.setReference(name, this); + + return name + ".getURL()"; + }; + + /** + * @private + */ + this.call = function(method, args){ + this[method].call(this, args); + }; + } +}; + + +apf.teleportLog = function(extra){ + // @todo when WebSockets are implemented + this.setXml = function(pNode){ return; }; + this.request = function(headers){ return; }; + this.response = function(extra){ return; }; +} + + + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/core/markup/domparser.js)SIZE(16761)TIME(Mon, 10 Jan 2011 10:53:11 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + +/** + * The parser of the Ajax.org Markup Language. Besides aml this parser takes care + * of distributing parsing tasks to other parsers like the native html parser and + * the xsd parser. + * @parser + * @private + * + * @define include element that loads another aml files. + * Example: + * + * + * + * @attribute {String} src the location of the aml file to include in this application. + * @addnode global, anyaml + */ +apf.DOMParser = function(){}; + +apf.DOMParser.prototype = new (function(){ + this.caseInsensitive = true; + this.preserveWhiteSpace = false; //@todo apf3.0 whitespace issue + + /* + @todo domParser needs to get a queue based on the parentNode that is + waiting to be parsed. This will prevent collisions when multiple + parts of the document are altered at the same time. + */ + this.$shouldWait = 0; + + // privates + var RE = [ + /\<\!(DOCTYPE|doctype)[^>]*>/, + / /g, + /<\s*\/?\s*(?:\w+:\s*)[\w-]*[\s>\/]/g + ], + XPATH = "//@*[not(contains(local-name(), '.')) and not(translate(local-name(), 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz') = local-name())]"; + + this.parseFromString = function(xmlStr, mimeType, options){ + var xmlNode; + if (this.caseInsensitive) { + //replace(/&\w+;/, ""). replace this by something else + //.replace(RE[1], " ") + var str = xmlStr.replace(RE[0], "") + .replace(RE[2], //.replace(/^[\r\n\s]*/, "") + function(m){ return m.toLowerCase(); }); + + /* @todo apf3.0 integrate this + x.ownerDocument.setProperty("SelectionNamespaces", + "xmlns:a='" + apf.ns.aml + "'"); + */ + + if (!this.supportNamespaces) + str = str.replace(/xmlns\=\"[^"]*\"/g, ""); + + + xmlNode = apf.getXmlDom(str, null, this.preserveWhiteSpace || apf.debug).documentElement; + var i, l, + nodes = xmlNode.selectNodes(XPATH); + // Case insensitive support + for (i = 0, l = nodes.length; i < l; i++) { + (nodes[i].ownerElement || nodes[i].selectSingleNode("..")) + .setAttribute(nodes[i].nodeName.toLowerCase(), nodes[i].nodeValue); + } + + } + else { + xmlNode = apf.getXmlDom(xmlStr, null, this.preserveWhiteSpace || apf.debug).documentElement; + } + + return this.parseFromXml(xmlNode, options); + }; + + //@todo prevent leakage by not recording .$aml + this.parseFromXml = function(xmlNode, options){ + var doc, docFrag, amlNode, beforeNode; + if (!options) + options = {}; + + if (!options.delayedRender && !options.include) { + //Create a new document + if (options.doc) { + doc = options.doc; + docFrag = options.docFrag || doc.createDocumentFragment(); + } + else { + doc = new apf.AmlDocument(); + doc.$aml = xmlNode; + doc.$domParser = this; + } + if (options.host) + doc.$parentNode = options.host; //This is for sub docs that need to access the outside tree + + + //Check for children in Aml node + /*if (!xmlNode.childNodes.length) { + apf.console.warn("DOMParser got markup without any children"); + return (docFrag || doc); + }*/ + + + //Let's start building our tree + amlNode = this.$createNode(doc, xmlNode.nodeType, xmlNode); //Root node + (docFrag || doc).appendChild(amlNode); + if (options.htmlNode) + amlNode.$int = options.htmlNode; + } + else { + amlNode = options.amlNode; + doc = options.doc; + + if (options.include) { + var n = amlNode.childNodes; + var p = n.indexOf(options.beforeNode); + var rest = p ? n.splice(p, n.length - p) : []; + } + } + + //Set parse context + this.$parseContext = [amlNode, options]; + + //First pass - Node creation + var nodes, nodelist = {}, prios = [], _self = this; + (function recur(amlNode, nodes){ + var cL, newNode, node, nNodes, + cNodes = amlNode.childNodes, + i = 0, + l = nodes.length; + for (; i < l; i++) { + //Create child + newNode = _self.$createNode(doc, (node = nodes[i]).nodeType, node); + if (!newNode) continue; //for preserveWhiteSpace support + + cNodes[cL = cNodes.length] = newNode; //Add to children + + //Set tree refs + newNode.parentNode = amlNode; + if (cL > 0) + (newNode.previousSibling = cNodes[cL - 1]).nextSibling = newNode; + + //Create children + if (!newNode.render && newNode.canHaveChildren && (nNodes = node.childNodes).length) + recur(newNode, nNodes); + + //newNode.$aml = node; //@todo should be deprecated... + + //Store high prio nodes for prio insertion + if (newNode.$parsePrio) { + if (newNode.$parsePrio == "001") { + newNode.dispatchEvent("DOMNodeInsertedIntoDocument"); //{relatedParent : nodes[j].parentNode} + continue; + } + + (nodelist[newNode.$parsePrio] || (prios.push(newNode.$parsePrio) + && (nodelist[newNode.$parsePrio] = []))).push(newNode); //for second pass + } + } + + amlNode.firstChild = cNodes[0]; + amlNode.lastChild = cNodes[cL]; + })(amlNode, xmlNode.childNodes); + + if (options.include && rest.length) { + var index = n.length - 1; + n.push.apply(n, rest); + var last = n[index]; + var next = n[index + 1]; + (next.previousSibling = last).nextSibling = next; + amlNode.lastChild = n[n.length - 1]; + } + + if (options.delay) { + amlNode.$parseOptions = { + prios: prios, + nodelist: nodelist + }; + return (docFrag || doc); + } + + //Second pass - Document Insert signalling + prios.sort(); + var i, j, l, l2; + for (i = 0, l = prios.length; i < l; i++) { + nodes = nodelist[prios[i]]; + for (j = 0, l2 = nodes.length; j < l2; j++) { + nodes[j].dispatchEvent("DOMNodeInsertedIntoDocument"); //{relatedParent : nodes[j].parentNode} + } + } + + if (this.$shouldWait) + return (docFrag || doc); + + if (options.timeout) { + $setTimeout(function(){ + _self.$continueParsing(amlNode, options); + }); + } + else { + this.$continueParsing(amlNode, options); + } + + return (docFrag || doc); + }; + + this.$callCount = 0; + this.$continueParsing = function(amlNode, options){ + if (this.$shouldWait && --this.$shouldWait != 0) + return false; + + if (!options) + options = {}; + + this.$callCount++; + + if (amlNode.$parseOptions) { + var prios = amlNode.$parseOptions.prios, + nodelist = amlNode.$parseOptions.nodelist, + i, j, l, l2, node; + delete amlNode.$parseOptions; + + //Second pass - Document Insert signalling + prios.sort(); + for (i = 0, l = prios.length; i < l; i++) { + nodes = nodelist[prios[i]]; + for (j = 0, l2 = nodes.length; j < l2; j++) { + if (!(node = nodes[j]).parentNode || node.$amlLoaded) //@todo generalize this using compareDocumentPosition + continue; + nodes[j].dispatchEvent("DOMNodeInsertedIntoDocument"); //{relatedParent : nodes[j].parentNode} + } + } + } + + //instead of $amlLoaded use something more generic see compareDocumentPosition + if (!options.ignoreSelf && !amlNode.$amlLoaded) + amlNode.dispatchEvent("DOMNodeInsertedIntoDocument"); //{relatedParent : nodes[j].parentNode} + + //Recursively signal non prio nodes + (function _recur(nodes){ + var node, nNodes; + for (var i = 0, l = nodes.length; i < l; i++) { + if (!(node = nodes[i]).$parsePrio && !node.$amlLoaded) { + node.dispatchEvent("DOMNodeInsertedIntoDocument"); //{relatedParent : nodes[j].parentNode} + } + + //Create children + if (!node.render && (nNodes = node.childNodes).length) + _recur(nNodes); + } + })(amlNode.childNodes); + + if (!--this.$callCount && !options.delay) + apf.queue.empty(); + + if (options.callback) + options.callback.call(amlNode.ownerDocument); + + delete this.$parseContext; + }; + + this.$createNode = function(doc, nodeType, xmlNode, namespaceURI, nodeName, nodeValue){ + var o; + + switch (nodeType) { + case 1: + var id, prefix; + if (xmlNode) { + if ((namespaceURI = xmlNode.namespaceURI || apf.ns.xhtml) + && !(prefix = doc.$prefixes[namespaceURI])) { + doc.$prefixes[prefix = xmlNode.prefix || xmlNode.scopeName || ""] = namespaceURI; + doc.$namespaceURIs[namespaceURI] = prefix; + + if (!doc.namespaceURI && !prefix) { + doc.namespaceURI = namespaceURI; + doc.prefix = prefix; + } + } + nodeName = xmlNode.baseName || xmlNode.localName || xmlNode.tagName.split(":").pop(); + } + else { + prefix = doc.$prefixes[namespaceURI] || ""; + } + + + if (!namespaceURI) { + throw new Error("Missing namespace definition."); //@todo apf3.0 make proper error + } + if (!apf.namespaces[namespaceURI]) { + if (this.allowAnyElement) + namespaceURI = apf.ns.xhtml; + else + throw new Error("Missing namespace handler for '" + namespaceURI + "'"); //@todo apf3.0 make proper error + } + + + var els = apf.namespaces[namespaceURI].elements; + + + if (!(els[nodeName] || els["@default"])) { + throw new Error("Missing element constructor: " + nodeName); //@todo apf3.0 make proper error + } + + + o = new (els[nodeName] || els["@default"])(null, nodeName); + + o.prefix = prefix || ""; + o.namespaceURI = namespaceURI; + o.tagName = prefix ? prefix + ":" + nodeName : nodeName; + + if (xmlNode) { + if (id = xmlNode.getAttribute("id")) + o.$propHandlers["id"].call(o, o.id = id); + + //attributes + var attr = xmlNode.attributes, n; + for (var a, i = 0, l = attr.length; i < l; i++) { + o.attributes.push(new apf.AmlAttr(o, + (n = (a = attr[i]).nodeName), a.nodeValue)); + + if (n == "render") + o.render = true; + + } + } + + break; + case 2: + o = new apf.AmlAttr(); + o.name = o.nodeName = nodeName; + if (nodeValue || (nodeValue = xmlNode && xmlNode.nodeValue)) + o.value = o.nodeValue = nodeValue; + + if (xmlNode) { + if (xmlNode.namespaceURI && !(o.prefix = doc.$namespaceURIs[o.namespaceURI = xmlNode.namespaceURI])) + doc.$prefixes[o.prefix = xmlNode.prefix || xmlNode.scopeName] = o.namespaceURI; + } + else { + o.prefix = doc.$prefixes[namespaceURI]; + } + + break; + case 3: + if (xmlNode) + nodeValue = xmlNode && xmlNode.nodeValue; + if (!this.preserveWhiteSpace && !(nodeValue || "").trim()) + return; + + o = new apf.AmlText(); + o.nodeValue = nodeValue || xmlNode && xmlNode.nodeValue; + break; + case 7: + var target = nodeName || xmlNode && xmlNode.nodeName; + + if(!apf.aml.processingInstructions[target]) + throw new Error(apf.formatErrorString(0, null, + "The processing instruction does not exist", "Could not find the processing instruction with target: " + target)); + + o = new apf.aml.processingInstructions[target](); + + o.target = o.nodeName = target; + o.data = o.nodeValue = nodeValue || xmlNode && xmlNode.nodeValue; + break; + case 4: + o = new apf.AmlCDATASection(); + o.nodeValue = nodeValue || xmlNode && xmlNode.nodeValue; + break; + case 5: //unsupported + o = new apf.AmlNode(); + o.nodeType = nodeType; + break; + case 6: //unsupported + o = new apf.AmlNode(); + o.nodeType = nodeType; + break; + case 8: + o = new apf.AmlComment(); + o.nodeValue = nodeValue || xmlNode && xmlNode.nodeValue; + break; + case 9: + o = new apf.AmlDocument(); + o.$domParser = this; + break; + case 10: //unsupported + o = new apf.AmlNode(); + o.nodeType = nodeType; + break; + case 11: + o = new apf.AmlDocumentFragment(); + break; + } + + o.ownerDocument = doc; + o.$aml = xmlNode; + + return o; + }; +})(); + +/** + * + * @author Ruben Daniels (ruben AT ajax DOT org) + * @version %I%, %G% + * @since 0.8 + */ +apf.AmlNamespace = function(){ + this.elements = {}; + this.processingInstructions = {}; +}; + +apf.AmlNamespace.prototype = { + setElement : function(tagName, fConstr){ + return this.elements[tagName] = fConstr; + }, + + setProcessingInstruction : function(target, fConstr){ + this.processingInstructions[target] = fConstr; + } +}; + + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/core/markup/aml.js)SIZE(1478)TIME(Mon, 10 Jan 2011 10:53:11 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + +/** + * The parser of the Ajax.org Markup Language. Besides aml this parser takes care + * of distributing parsing tasks to other parsers like the native html parser and + * the xsd parser. + * @parser + * @private + * + * @define include element that loads another aml files. + * Example: + * + * + * + * @attribute {String} src the location of the aml file to include in this application. + * @addnode global, anyaml + */ +apf.aml = new apf.AmlNamespace(); +apf.setNamespace("http://ajax.org/2005/aml", apf.aml); + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/core/markup/aml/node.js)SIZE(22071)TIME(Mon, 10 Jan 2011 10:53:11 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + +apf.__AMLNODE__ = 1 << 14; + + + +/** + * All elements inheriting from this {@link term.baseclass baseclass} have Document Object Model (DOM) support. The DOM + * is the primary method for accessing and manipulating an xml document. This + * includes html documents and aml documents. Every element in the ajax.org + * markup language can be manipulated using the W3C DOM. This means + * that every element and attribute you can set in the xml format, can be + * changed, set, removed reparented, etc runtime. This offers a great deal of + * flexibility. Well known methods + * from this specification are .appendChild .removeChild .setAttribute and + * insertBefore to name a few. Ajax.org Platform aims to implement DOM1 + * completely and parts of DOM2. Which should be extended in the future to fully + * implement DOM Level 2. For more information see {@link http://www.w3.org/DOM/} + * or {@link http://www.w3schools.com/dom/default.asp}. + * Example: + * Ajax.org Markup Language + * + * + * + * + * + * Document Object Model in javascript + * + * //The following line is only there for completeness sake. In fact apf + * //automatically adds a reference in javascript called winExample based + * //on the id it has. + * var winExample = apf.document.getElementById("winExample"); + * winExample.setAttribute("title", "Example"); + * winExample.setAttribute("icon", "icoFolder.gif"); + * winExample.setAttribute("left", "100"); + * + * var lblNew = apf.document.createElement("label"); + * winExample.appendChild(lblNew); + * lblNew.setAttribute("caption", "Example"); + * + * tstButton.setAttribute("caption", "Click me"); + * + * That would be the same as having the following aml: + * + * + * + * + * + * + * Remarks: + * Because the W3C DOM is native to all modern browsers the internet is full + * of tutorials and documentation for this API. If you need more information + * it's a good idea to search for tutorials online. + * + * @event DOMNodeInserted + * @event DOMNodeInsertedIntoDocument + * @event DOMNodeRemoved + * @event DOMNodeRemovedFromDocument + * + * @constructor + * @baseclass + * + * @author Ruben Daniels (ruben AT ajax DOT org) + * @version %I%, %G% + * @since 0.5 + */ +apf.AmlNode = function(){ + this.$init(function(){ + /** + * Nodelist containing all the child nodes of this element. + */ + this.childNodes = []; //@todo AmlNodeList + }); +}; + +(function() { + + + /** + * Returns a string representation of this object. + */ + this.toString = function(){ + if (this.nodeName) + return "[" + this.nodeName.uCaseFirst() + " Node]"; + + return "[" + this.localName.uCaseFirst() + " Element Node, <" + + (this.prefix ? this.prefix + ":" : "") + this.localName + " " + + this.attributes.join(" ") + + " /> : " + (this.name || this.$uniqueId || "") + "]"; + }; + + + /** + * Number specifying the type of node within the document. + */ + this.$regbase = this.$regbase | apf.__AMLNODE__; + + /** + * Constant for a dom element node. + * @type {Number} + */ + this.NODE_ELEMENT = 1; + /** + * Constant for a dom attribute node. + * @type {Number} + */ + this.NODE_ATTRIBUTE = 2; + /** + * Constant for a dom text node. + * @type {Number} + */ + this.NODE_TEXT = 3; + /** + * Constant for a dom cdata section node. + * @type {Number} + */ + this.NODE_CDATA_SECTION = 4; + /** + * Constant for a dom entity reference node. + * @type {Number} + */ + this.NODE_ENTITY_REFERENCE = 5; + /** + * Constant for a dom entity node. + * @type {Number} + */ + this.NODE_ENTITY = 6; + /** + * Constant for a dom processing instruction node. + * @type {Number} + */ + this.NODE_PROCESSING_INSTRUCTION = 7; + /** + * Constant for a dom comment node. + * @type {Number} + */ + this.NODE_COMMENT = 8; + /** + * Constant for a dom document node. + * @type {Number} + */ + this.NODE_DOCUMENT = 9; + /** + * Constant for a dom document type node. + * @type {Number} + */ + this.NODE_DOCUMENT_TYPE = 10; + /** + * Constant for a dom document fragment node. + * @type {Number} + */ + this.NODE_DOCUMENT_FRAGMENT = 11; + /** + * Constant for a dom notation node. + * @type {Number} + */ + this.NODE_NOTATION = 12; + + + + /** + * The document node of this application + */ + this.ownerDocument = null; + + /** + * Returns the value of the current node. + */ + this.nodeValue = ""; + + /** + * The namespace URI of the node, or null if it is unspecified (read-only). + * When the node is a document, it returns the XML namespace for the current + * document. + */ + this.namespaceURI = ""; + + /** + * @todo + */ + //this.baseURI = alsdjlasdj + + /** + * @todo + */ + //this.prefix = asdkljahqsdkh + + /** + * Appends an element to the end of the list of children of this element. + * If the element was already a child of another element it is removed from + * that parent before adding it this element. + * + * @param {AmlNode} amlNode the element to insert as child of this element. + * @return {AmlNode} the appended node + * @method + */ + this.appendChild = + + /** + * Inserts an element before another element in the list of children of this + * element. * If the element was already a child of another element it is + * removed from that parent before adding it this element. + * + * @param {AmlNode} amlNode the element to insert as child of this element. + * @param {AmlNode} beforeNode the element which determines the insertion position of the element. + * @return {AmlNode} the inserted node + */ + this.insertBefore = function(amlNode, beforeNode, noHtmlDomEdit){ + + if (!amlNode || !amlNode.hasFeature || !amlNode.hasFeature(apf.__AMLNODE__)){ + throw new Error(apf.formatErrorString(1072, this, + "Insertbefore DOM operation", + "Invalid argument passed. Expecting an AmlElement.")); + } + + + if (this.nodeType == this.NODE_DOCUMENT) { + if (this.childNodes.length) { + throw new Error(apf.formatErrorString(0, this, + "Insertbefore DOM operation", + "Only one top level element is allowed in an AML document.")); + } + else this.documentElement = amlNode; //@todo apf3.0 removal + } + + if (this == amlNode) { + throw new Error(apf.formatErrorString(0, this, + "Insertbefore DOM operation", + "Cannot append node as a child of itself.")); + } + + if (amlNode.nodeType == this.NODE_DOCUMENT_FRAGMENT) { + var nodes = amlNode.childNodes.slice(0); + for (var i = 0, l = nodes.length; i < l; i++) { + this.insertBefore(nodes[i], beforeNode); + } + return amlNode; + } + + var isMoveWithinParent = amlNode.parentNode == this, + oldParentHtmlNode = amlNode.$pHtmlNode, + oldParent = amlNode.parentNode, + index = -1, + _self = this; + + if (beforeNode) { + index = this.childNodes.indexOf(beforeNode); + if (index < 0) { + + if (beforeNode == this) + throw new Error(apf.formatErrorString(1072, this, + "Insertbefore DOM operation", + "Before node is the same node as inserted node")); + else + throw new Error(apf.formatErrorString(1072, this, + "Insertbefore DOM operation", + "Before node is not a child of the parent node specified")); + + + return false; + } + } + + if (!amlNode.ownerDocument) + amlNode.ownerDocument = this.ownerDocument || apf.ownerDocument; + + if (amlNode.parentNode) + amlNode.removeNode(isMoveWithinParent, noHtmlDomEdit); + amlNode.parentNode = this; + + if (beforeNode) + index = this.childNodes.indexOf(beforeNode); + + if (beforeNode) { + amlNode.nextSibling = beforeNode; + amlNode.previousSibling = beforeNode.previousSibling; + beforeNode.previousSibling = amlNode; + if (amlNode.previousSibling) + amlNode.previousSibling.nextSibling = amlNode; + } + + if (index >= 0) { + this.childNodes = this.childNodes.slice(0, index).concat(amlNode, + this.childNodes.slice(index)); + } + else { + index = this.childNodes.push(amlNode) - 1; + + amlNode.nextSibling = null; + if (index > 0) { + amlNode.previousSibling = this.childNodes[index - 1]; + amlNode.previousSibling.nextSibling = amlNode; + } + else { + amlNode.previousSibling = null; + } + } + + this.firstChild = this.childNodes[0]; + this.lastChild = this.childNodes[this.childNodes.length - 1]; + + //@todo fix event struture, fix tree events + var initialAppend = !amlNode.$amlLoaded; + function triggerUpdate(){ + amlNode.$pHtmlNode = _self.canHaveChildren ? _self.$int : document.body; + + //@todo this is a hack, a good solution should be found + /*var iframelist; + var containsIframe = (amlNode.$ext && amlNode.$ext.nodeType == 1 + && (iframelist = amlNode.$ext.getElementsByTagName("iframe")).length > 0 + && apf.findHost(iframelist[0].parentNode) == amlNode);*/ + + var nextNode = beforeNode; + if (!initialAppend && !noHtmlDomEdit && amlNode.$ext && !amlNode.$coreHtml) { + nextNode = beforeNode; + while (nextNode && !(nextNode.$altExt || nextNode.$ext)) { + nextNode = nextNode.nextSibling; + } + + amlNode.$pHtmlNode.insertBefore(amlNode.$altExt || amlNode.$ext, + nextNode && (nextNode.$altExt || nextNode.$ext) || null); + } + + //Signal node and all it's ancestors + amlNode.dispatchEvent("DOMNodeInserted", { + $beforeNode : beforeNode, + relatedNode : _self, + $isMoveWithinParent : isMoveWithinParent, + $oldParentHtmlNode : oldParentHtmlNode, + $oldParent : oldParent, + bubbles : true + }); + + if (initialAppend && !noHtmlDomEdit && beforeNode && amlNode.$ext && !amlNode.$coreHtml) { + nextNode = beforeNode; + while (nextNode && !(nextNode.$altExt || nextNode.$ext)) { + nextNode = nextNode.nextSibling; + } + + amlNode.$pHtmlNode.insertBefore(amlNode.$altExt || amlNode.$ext, + nextNode && (nextNode.$altExt || nextNode.$ext) || null); + } + } + + var doc = this.nodeType == this.NODE_DOCUMENT ? this : this.ownerDocument; + if (!doc || doc.$domParser.$shouldWait) + return amlNode; + + if (this.nodeType == this.NODE_DOCUMENT_FRAGMENT) + return; //We don't update the tree if this is a doc fragment + + //@todo review this... + if (initialAppend) { + (this.ownerDocument || this).$domParser.$continueParsing(amlNode, {delay: true}); + } + + triggerUpdate(); + return amlNode; + }; + + /** + * Removes this element from the document hierarchy. Call-chaining is + * supported. + * + */ + this.removeNode = function(doOnlyAdmin, noHtmlDomEdit){ + + if (doOnlyAdmin && typeof doOnlyAdmin != "boolean") { + throw new Error(apf.formatErrorString(0, this, + "Removing node from parent", + "Invalid DOM Call. removeNode() does not take any arguments.")); + } + + + if (!this.parentNode || !this.parentNode.childNodes) + return this; + + + if (!this.parentNode.childNodes.contains(this)) { + /*throw new Error(apf.formatErrorString(0, this, + "Removing node from parent", + "Passed node is not a child of this node.", this.$aml));*/ + return false; + } + + + this.parentNode.childNodes.remove(this); + + //If we're not loaded yet, just remove us from the aml to be parsed + if (this.$amlLoaded && !apf.isDestroying) { + //this.parentNode.$aml.removeChild(this.$aml); + + this.dispatchEvent("DOMNodeRemoved", { + relatedNode : this.parentNode, + bubbles : true, + $doOnlyAdmin : doOnlyAdmin + }); + + if (!noHtmlDomEdit && !doOnlyAdmin && this.$ext && this.$ext.parentNode) { + this.$ext.parentNode.removeChild(this.$ext); + //delete this.$ext; //WTF??? + } + } + + if (this.parentNode.firstChild == this) + this.parentNode.firstChild = this.nextSibling; + if (this.parentNode.lastChild == this) + this.parentNode.lastChild = this.previousSibling; + + if (this.nextSibling) + this.nextSibling.previousSibling = this.previousSibling; + if (this.previousSibling) + this.previousSibling.nextSibling = this.nextSibling; + + this.$pHtmlNode = + this.parentNode = + this.previousSibling = + this.nextSibling = null; + + return this; + }; + + /** + * Removes a child from the node list of this element. Call-chaining is + * supported. + */ + this.removeChild = function(childNode) { + + if (!childNode || !childNode.hasFeature || !childNode.hasFeature(apf.__AMLNODE__)) { + throw new Error(apf.formatErrorString(0, this, + "Removing a child node", + "Invalid Argument. removeChild() requires one argument of type AMLElement.")); + } + + + childNode.removeNode(); + return this; + }; + + //@todo + this.replaceChild = function(){}; + + /** + * Clones this element, creating an exact copy of it but does not insert + * it in the document hierarchy. + * @param {Boolean} deep whether the element's are cloned recursively. + * @return {AmlNode} the cloned element. + */ + this.cloneNode = function(deep){ + if (deep && this.nodeType == 1) { + return this.ownerDocument.$domParser.parseFromXml(this, { + doc : this.ownerDocument, + delay : true + }).childNodes[0]; + } + else { + return this.ownerDocument.$domParser.$createNode( + this.ownerDocument, this.nodeType, this); + } + }; + + //@todo + this.canDispatch = function(namespaceURI, type){}; + + //@todo + this.compareDocumentPosition = function(otherNode){ + /* + DOCUMENT_POSITION_DISCONNECTED = 0x01; + DOCUMENT_POSITION_PRECEDING = 0x02; + DOCUMENT_POSITION_FOLLOWING = 0x04; + DOCUMENT_POSITION_CONTAINS = 0x08; + DOCUMENT_POSITION_CONTAINED_BY = 0x10; + */ + }; + + this.hasAttributes = function(){ + return this.attributes && this.attributes.length; + }; + + this.hasChildNodes = function(){ + return this.childNodes && this.childNodes.length; + }; + + this.isDefaultNamespace = function(namespaceURI){ + if (node.nodeType == 1) { + if (!this.prefix) + return this.namespaceURI == namespaceURI; + + //@todo Loop through attributes here + } + + var node = this.parentNode || this.ownerElement; + return node && node.isDefaultNamespace(namespaceURI); + }; + + this.lookupNamespaceURI = function(prefix){ + if (node.nodeType == 1) { + if (this.namespaceURI && prefix == this.prefix) + return this.namespaceURI ; + + //@todo Loop through attributes here + } + + var node = this.parentNode || this.ownerElement; + return node && node.lookupNamespaceURI(prefix); + }; + + this.lookupPrefix = function(namespaceURI){ + if (this.nodeType == 1) { + if (namespaceURI == this.namespaceURI && this.prefix) + return this.prefix; + + //@todo Loop through attributes here + } + + var node = this.parentNode || this.ownerElement; + return node && node.lookupPrefix(namespaceURI); + }; + + this.normalize = function(){}; + + /**** Xpath support ****/ + + /** + * Queries the aml dom using the W3C xPath query language and returns a node + * list. This is not an official API call but can be useful in certain cases. + * see {@link core.documentimplementation.method.evaluate evaluate on the apf.document} + * @param {String} sExpr the xpath expression to query the aml DOM tree with. + * @param {AmlNode} [contextNode] the element that serves as the starting point of the search. Defaults to this element. + * @returns {NodeList} list of found nodes. + */ + this.selectNodes = function(sExpr, contextNode){ + if (!apf) return; + + if (!apf.XPath) + apf.runXpath(); + return apf.XPath.selectNodes(sExpr, + contextNode || (this.nodeType == 9 ? this.documentElement : this)); + }; + + /** + * Queries the aml dom using the W3C xPath query language and returns a single + * node. This is not an official API call but can be useful in certain cases. + * see {@link core.documentimplementation.method.evaluate evaluate on the apf.document} + * @param {String} sExpr the xpath expression to query the aml DOM tree with. + * @param {AmlNode} [contextNode] the element that serves as the starting point of the search. Defaults to this element. + * @returns {AmlNode} the first node that matches the query. + */ + this.selectSingleNode = function(sExpr, contextNode){ + if (!apf) return; + + if (!apf.XPath) + apf.runXpath(); + return apf.XPath.selectNodes(sExpr, + contextNode || (this.nodeType == 9 ? this.documentElement : this))[0]; + }; + + /*this.addEventListener("DOMNodeInsertedIntoDocument", function(e){ + + }, true);*/ +}).call(apf.AmlNode.prototype = new apf.Class()); + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/core/markup/aml/element.js)SIZE(21829)TIME(Mon, 10 Jan 2011 10:53:11 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + +apf.AmlElement = function(struct, tagName){ + var $init = this.$init; + this.$init = function(tagName, nodeFunc, struct){ + this.$supportedProperties = this.$supportedProperties.slice(); + + var prop, p, q; + p = this.$propHandlers; + q = this.$propHandlers = {}; + for (prop in p) + q[prop] = p[prop]; + + p = this.$booleanProperties; + q = this.$booleanProperties = {}; + for (prop in p) + q[prop] = p[prop]; + + $init.call(this, tagName, nodeFunc, struct); + }; + + this.$init(function(tagName, nodeFunc, struct){ + this.$events = {}; + this.$inheritProperties = {}; + + /** + * Nodelist containing all attributes. This is implemented according to the + * W3C specification. + * Example: + * + * for (var i = 0; i < obj.attributes.length; i++) { + * alert(obj.attributes.item(i)); + * } + * + * @see baseclass.amldom.method.getAttribute + * @see baseclass.amldom.method.setAttribute + */ + this.attributes = new apf.AmlNamedNodeMap(this); //@todo apf3.0 move to init? + + /** + * The purpose of this element + * Possible values: + * apf.NODE_VISIBLE this element has a gui representation + * apf.NODE_HIDDEN this element does not display a gui + */ + this.nodeFunc = nodeFunc; + + /** + * The local name of this element + */ + this.localName = tagName; //@todo + + //Parse struct to create attributes and child nodes + if (struct) { + var nodes, prop, i, l; + if (struct.childNodes) { + nodes = struct.childNodes; + delete struct.childNodes; //why delete? + } + + //Attributes + for (prop in struct){ + if (prop == "htmlNode") continue; + + this.attributes.push(new apf.AmlAttr(this, prop, struct[prop])); + } + + if (!this.ownerDocument) { + this.ownerDocument = apf.document; + this.prefix = "a"; + this.namespaceURI = apf.ns.aml; + this.tagName = tagName; + } + + if (nodes) { + this.childNodes = nodes; + + for (i = 0, l = nodes.length; i < l; i++) { + nodes[i].nextSibling = nodes[i + 1] || null; + nodes[i].previousSibling = nodes[i - 1] || null; + nodes[i].parentNode = this; + } + this.firstChild = nodes[0] || null; + this.lastChild = nodes[nodes.length - 1] || null; + } + + //Temp hack + this.$aml = apf.$emptyNode || (apf.$emptyNode = apf.getXml("")); + } + }); + + if (tagName) //of typeof is not function and not true + $init.call(this, tagName, apf.NODE_HIDDEN, struct); +}; + +(function(){ + /** + * Number specifying the type of node within the document. + */ + this.nodeType = this.NODE_ELEMENT; + this.canHaveChildren = true; + + this.$propHandlers = { + /** + * @attribute {String} id the identifier of this element. When set this + * identifier is the name of the variable in javascript to access this + * element directly. This identifier is also the way to get a reference to + * this element using apf.document.getElementById. + * Example: + * + * + * + * alert(barExample); + * + * + */ + "id": function(value){ + + if (value == "apf") { + throw new Error(apf.formatErrorString(0, this, + "Setting Name of Element", + "Cannot set name of element to 'apf'")); + } + + + if (this.name == value) + return; + + if (self[this.name] == this) + self[this.name] = null + + if (!self[value] || !self[value].hasFeature) { + try { + self[value] = this; + } + catch(ex) { + + var error = true; + + } + } + + if (error && value in self) { + apf.console.warn("trying to set a value in the global scope with " + + "a reserved name '" + value + "'.\nNothing wrong " + + "with that, except that you will not be able to " + + "reference\nthe object from the global scope in JS.") + } + + + //@todo dispatch event for new name creation. + //@todo old name disposal + + apf.nameserver.register(this.localName, value, this) + + + this.name = value; + } + }; + + this.$booleanProperties = {}; + this.$inheritProperties = {}; + this.$supportedProperties = []; + + /** + * Returns a list of elements with the given tag name. + * The subtree below the specified element is searched, excluding the + * element itself. + * + * @method + * @param {String} tagName the tag name to look for. The special string "*" represents any tag name. + * @return {NodeList} containing any node matching the search string + */ + this.getElementsByTagName = function(tagName, norecur){ + tagName = tagName.toLowerCase(); + var node, i, l, + nodes = this.childNodes, + result = []; + for (i = 0, l = nodes.length; i < l; i++) { + if ((node = nodes[i]).nodeType != 1) + continue; + + if (node.tagName == tagName || tagName == "*") + result.push(node); + + if (!norecur && node.nodeType == 1) + result = result.concat(node.getElementsByTagName(tagName)); + } + + return result; + }; + + this.getElementsByTagNameNS = function(namespaceURI, localName, norecur){ + localName = localName.toLowerCase(); + var node, i, l, + nodes = this.childNodes, + result = []; + for (i = 0, l = nodes.length; i < l; i++) { + if ((node = nodes[i]).nodeType != 1) + continue; + + if (node.namespaceURI == namespaceURI && (node.localName == localName || localName == "*")) + result.push(node); + + if (!norecur && node.nodeType == 1) + result = result.concat(node.getElementsByTagNameNS(namespaceURI, localName)); + } + + return result; + }; + + /** + * Sets an attribute on this element. Call-chaining is supported. + * @param {String} name the name of the attribute to which the value is set + * @param {String} value the new value of the attribute. + */ + this.setAttribute = function(name, value, noTrigger) { + name = name.toLowerCase(); + + var a = this.attributes.getNamedItem(name); + if (!a) { + this.attributes.push(a = new apf.AmlAttr(this, name, value)); + + if (!this.$amlLoaded) + return; + + if (noTrigger) + a.$setValue(value); + else { + //@todo apf3.0 domattr + a.dispatchEvent("DOMNodeInsertedIntoDocument", { + relatedNode : this + }); + + //@todo apf3.0 domattr + a.dispatchEvent("DOMNodeInserted", { + relatedNode : this, + bubbles : true + }); + } + + return; + } + + var oldValue = a.nodeValue; + a.$setValue(value); + + if (noTrigger || !this.$amlLoaded) + return; + + //@todo apf3.0 domattr + a.$triggerUpdate(null, oldValue); + }; + + //@todo apf3.0 domattr + this.setAttributeNode = function(attrNode){ + this.attributes.setNamedItem(attrNode); + }; + + this.setAttributeNS = function(namespaceURI, name, value){ + return this.setAttribute(name, value); + }; + + //@todo apf3.0 domattr + this.hasAttribute = function(name){ + return this.getAttributeNode(name) ? true : false; + }; + + //@todo + this.hasAttributeNS = function(namespaceURI, name){ + return this.hasAttribute(name); + }; + + /** + * Removes an attribute from this element. Call-chaining is supported. + * @param {String} name the name of the attribute to remove. + */ + //@todo apf3.0 domattr + this.removeAttribute = function(name){ + this.attributes.removeNamedItem(name); + return this; + }; + + //@todo apf3.0 domattr + this.removeAttributeNS = function(namespaceURI, name){ + return this.removeAttribute(name); + }; + + //@todo apf3.0 domattr + this.removeAttributeNode = function(attrNode){ + this.attributes.removeNamedItem(attrNode.name); //@todo this should probably be slightly different. + }; + + /** + * Retrieves the value of an attribute of this element + * @param {String} name the name of the attribute for which to return the value. + * @param {Boolean} [inherited] + * @return {String} the value of the attribute or null if none was found with the name specified. + * @method + */ + this.getAttribute = function(name, inherited){ + var item = this.attributes.getNamedItem(name); + return item ? (inherited + ? item.inheritedValue || item.nodeValue + : item.nodeValue) : null; + }; + + /** + * Retrieves the attribute node for a given name + * @param {String} name the name of the attribute to find. + * @return {AmlNode} the attribute node or null if none was found with the name specified. + */ + this.getAttributeNode = function(name){ + return this.attributes.getNamedItem(name); + }; + + this.getBoundingClientRect = function(){ + return new apf.AmlTextRectangle(this); + }; + + //@todo + this.querySelector = function(){ + // here we should use: http://code.google.com/p/css2xpath/source/browse/trunk/src/css2xpath.js + }; + + //@todo + this.querySelectorAll = function(){ + // here we should use: http://code.google.com/p/css2xpath/source/browse/trunk/src/css2xpath.js + }; + + //@todo + this.scrollIntoView = function(){ + + }; + + /** + * Replaces the child aml elements with new aml. + * @param {mixed} amlDefNode the aml to be loaded. This can be a string or a parsed piece of xml. + * @param {HTMLElement} oInt the html parent of the created aml elements. + */ + this.replaceMarkup = function(amlDefNode, options) { + + apf.console.info("Remove all children from element"); + + + if (!options) + options = {}; + + if (!options.$intAML) + options.$intAML = this.$aml; + if (!options.$int) + options.$int = this.$int; + options.clear = true; + + //Remove All the childNodes + for (var i = this.childNodes.length - 1; i >= 0; i--) { + var oItem = this.childNodes[i]; + /*var nodes = oItem.childNodes; + for (var k = 0; k < nodes.length; k++) + if (nodes[k].destroy) + nodes[k].destroy(true); + + if (oItem.$aml && oItem.$aml.parentNode) + oItem.$aml.parentNode.removeChild(oItem.$aml);*/ + + if (oItem.destroy) + oItem.destroy(true); + + if (oItem.$ext != this.$int) + apf.destroyHtmlNode(oItem.$ext); + } + + this.childNodes.length = 0; + + this.$int.innerHTML = "
loading...
"; + + //Do an insertMarkup + this.insertMarkup(amlDefNode, options); + }; + + /** + * Inserts new aml into this element. + * @param {mixed} amlDefNode the aml to be loaded. This can be a string or a parsed piece of xml. + * @param {Object} options + * Properties: + * callback + * clear + */ + this.insertMarkup = function(amlDefNode, options){ + + apf.console.info("Loading sub markup from external source"); + + + + if (typeof apf.offline != "undefined" && !apf.offline.onLine) + return false; //it's the responsibility of the dev to check this + + + var _self = this; + var include = new apf.XiInclude(); + + if (amlDefNode.trim().charAt(0) == "<") + amlDefNode = apf.getXml(amlDefNode); + + include.setAttribute("href", amlDefNode); + if (options && options.clear) + include.setAttribute("clear", true); + include.options = options; + include.callback = options && options.callback || function(){ + _self.dispatchEvent("afteramlinserted", {src: amlDefNode}); + }; + this.appendChild(include); + }; + + //@todo prefix only needs on top element + this.serialize = function(shallow){ + if (shallow || !this.firstChild) { + return "<" + + (this.prefix + ? this.prefix + ":" + this.localName + " xmlns:" + + this.prefix + "=\"" + this.namespaceURI + "\"" + : this.localName) + (this.attributes && this.attributes.length ? " " : "") + + (this.attributes && this.attributes.join(" ") || "") + + "/>"; + } + else { + var str = ["<" + + (this.prefix + ? this.prefix + ":" + this.localName + " xmlns:" + + this.prefix + "=\"" + this.namespaceURI + "\"" + : this.localName) + (this.attributes && this.attributes.length ? " " : "") + + (this.attributes && this.attributes.join(" ") || "") + + ">"]; + + for (var i = this.firstChild; i; i = i.nextSibling) + str.push(i.serialize()); + + return str.join("") + ""; + } + }; + + this.$setInheritedAttribute = function(prop){ + var value, node = this, isInherit = false; + + value = node.getAttribute(prop); + if (!value) { + node = node.parentNode; + + //Second argument fetches special inheritance value, if any + while (node && node.nodeType == 1 && !(value = node.getAttribute(prop, true))) { + node = node.parentNode; + } + + isInherit = true; + } + + if (!value && apf.config && prop) + value = apf.config[prop]; + + if (value) { + + //Remove any bounds if relevant + this.$clearDynamicProperty(prop); + + if (isInherit) + this.$inheritProperties[prop] = 2; + + if (typeof value == "string" + && (value.indexOf("{") > -1 || value.indexOf("[") > -1)) { + this.$setDynamicProperty(prop, value); + } + else + + this.setProperty(prop, value, false, false, 2); + } + + return value; + }; + + //@todo in proper W3C implementation this needs to change + //@todo this won't work with a combo of remove/append + this.addEventListener("DOMNodeInserted", function(e){ + if (e.currentTarget != this || e.$isMoveWithinParent || !e.$oldParent) + return; + + //Check inherited attributes for reparenting + /* + States: + -1 Set + undefined Pass through + 2 Inherited + 3 Semi-inherited + 10 Dynamic property + */ + var vOld, vNew; + var aci = apf.config.$inheritProperties; + for (var prop in aci) { + vOld = apf.getInheritedAttribute(e.$oldParent, prop); + vNew = apf.getInheritedAttribute(this.parentNode, prop); + + //Property has changed, lets recursively set it on inherited nodes + if (vOld != vNew) { + //@todo code duplication from class.js + (function recur(nodes) { + var i, l, node, n; + for (i = 0, l = nodes.length; i < l; i++) { + node = nodes[i]; + if (node.nodeType != 1 && node.nodeType != 7) + continue; + + //Pass through + n = node.$inheritProperties[prop]; + if (aci[prop] == 1 && !n) + recur(node.childNodes); + + //Set inherited property + //@todo why are dynamic properties overwritten?? + else if(!(n < 0)) {//Will also pass through undefined - but why??? @todo seems inefficient + if (n == 3) { + var sameValue = node[prop]; + node[prop] = null; + } + node.setProperty(prop, n != 3 + ? vNew + : sameValue, false, false, n); //This is recursive already + } + } + })([this]); + } + } + }); + + this.$handlePropSet = function(prop, value, force){ + if (value && this.$booleanProperties[prop]) + value = apf.isTrue(value); + + + if (typeof this[prop] == "function") { + throw new Error("Could not set property/attribute '" + prop + + "' which has the same name as a method on this object: '" + + this.toString() + "'"); + } + + + this[prop] = value; + + var handler; + return (handler = this.$propHandlers && this.$propHandlers[prop] + || this.nodeFunc == apf.NODE_VISIBLE && apf.GuiElement && apf.GuiElement.propHandlers[prop] || null) + && handler.call(this, value, prop, force); + }; + + //var aci = apf.config.$inheritProperties; << UNUSED + this.addEventListener("DOMNodeInsertedIntoDocument", function(e){ + var a, i, l, attr = this.attributes; + + + if (typeof apf.offline != "undefined" && apf.offline.state.enabled) { + var offlineLookup = apf.offline.state.getAll(this); + for (i in offlineLookup) { + a = attr.getNamedItem(i); + if (a) + a.$setValue(offlineLookup[i]); + else { + this.attributes.push( + new apf.AmlAttr(this, i, offlineLookup[i])) + } + } + } + + + + + //Get defaults from the defaults element if it exists + var defs = apf.nameserver.getAll("defaults_" + this.localName); + if (defs.length) { + for (var j = 0, jl = defs.length; j < jl; j++) { + var d = defs[j].attributes, di; + for (i = 0, l = d.length; i < l; i++) { + a = attr.getNamedItem((di = d[i]).nodeName); + if (a) { + if (a.value)//specified + continue; + + a.$setValue(di.nodeValue); + this.$inheritProperties[di.nodeName] = 2; + } + else { + this.attributes.push( + new apf.AmlAttr(this, di.nodeName, di.nodeValue)); + this.$inheritProperties[di.nodeName] = 2; + } + } + } + } + + + + //Set all attributes + for (i = 0, l = attr.length; i < l; i++) { + attr[i].dispatchEvent("DOMNodeInsertedIntoDocument"); + } + + this.$amlLoaded = true; + }, true); +}).call(apf.AmlElement.prototype = new apf.AmlNode()); + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/core/markup/aml/characterdata.js)SIZE(2018)TIME(Mon, 10 Jan 2011 10:53:11 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + +//@todo apf3.0 The functions seem to not set nodeValue... +apf.AmlCharacterData = function(){ + this.data = ""; + this.length = 0; + + this.$init(true); + + this.appendData = function(sValue){ + this.dispatchEvent("DOMCharacterDataModified", { + value : sValue + }); + }; + + this.deleteData = function(nOffset, nCount){ + this.dispatchEvent("DOMCharacterDataModified", { + offset: nOffset, + count : nCount + }); + }; + + this.insertData = function(nOffset, nCount){ + this.dispatchEvent("DOMCharacterDataModified", { + offset: nOffset, + count : nCount + }); + }; + + this.replaceData = function(nOffset, nCount, sValue){ + this.dispatchEvent("DOMCharacterDataModified", { + offset: nOffset, + count : nCount, + value : sValue + }); + }; + + this.substringData = function(nOffset, nCount){}; +} +apf.AmlCharacterData.prototype = new apf.AmlNode(); + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/core/markup/aml/text.js)SIZE(4136)TIME(Mon, 10 Jan 2011 10:53:11 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + +apf.AmlText = function(isPrototype){ + this.$init(isPrototype); +}; + +(function(){ + this.nodeType = this.NODE_TEXT; + this.nodeName = "#text"; + + this.serialize = function(){ + return apf.xmlentities(this.nodeValue).replace(//g, ">"); + }; + + + + //@todo think about using this.replaceData(); + this.$setValue = function(value){ + //if (!this.$amlLoaded) + //return; + + this.dispatchEvent("DOMCharacterDataModified", { + bubbles : true, + prevValue : this.nodeValue, + newValue : this.nodeValue = value + }); + + if (this.$amlLoaded && this.$ext) + this.$ext.nodeValue = value; + } + + this.addEventListener("DOMNodeInsertedIntoDocument", function(e){ + var pHtmlNode; + if (!(pHtmlNode = this.parentNode.$int) || this.parentNode.hasFeature(apf.__CHILDVALUE__)) + return; + + this.$amlLoaded = true; + + var nodeValue = this.nodeValue; + + //@todo optimize for inside elements? + if (apf.config.liveText && !this.parentNode.hasFeature(apf.__CHILDVALUE__) + && (nodeValue.indexOf("{") > -1 || nodeValue.indexOf("[") > -1)) { + + //Convert to live markup pi + this.$supportedProperties = []; + this.$propHandlers = {}; + this.$booleanProperties = {}; + this.$inheritProperties = {}; + + this.$propHandlers["calcdata"] = apf.LiveMarkupPi.prototype.$propHandlers["calcdata"]; + + this.$setInheritedAttribute = apf.AmlElement.prototype.$setInheritedAttribute; + + this.implement(apf.StandardBinding); + + + pHtmlNode.appendChild(this.$ext = document.createElement("span")); + this.$setDynamicProperty("calcdata", this.nodeValue); + + return; + } + + if (apf.hasTextNodeWhiteSpaceBug) { + var nodeValue = nodeValue.replace(/[\t\n\r ]+/g, " "); + + if (nodeValue && nodeValue != " ") + this.$ext = pHtmlNode.appendChild( + pHtmlNode.ownerDocument.createTextNode(nodeValue)); + } + else + this.$ext = pHtmlNode.appendChild( + pHtmlNode.ownerDocument.createTextNode(nodeValue)); + }, true); +}).call(apf.AmlText.prototype = new apf.AmlCharacterData()); + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/core/markup/aml/attr.js)SIZE(4716)TIME(Mon, 10 Jan 2011 10:53:11 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + +apf.AmlAttr = function(ownerElement, name, value){ + this.$init(); + + if (ownerElement) { + this.ownerElement = ownerElement; + this.ownerDocument = ownerElement.ownerDocument; + } + + this.nodeName = this.name = name; + this.nodeValue = this.value = value; +}; + +(function(){ + this.nodeType = this.NODE_ATTRIBUTE; + + this.MODIFICATION = 1; + this.ADDITION = 2; + this.REMOVAL = 3; + + this.serialize = + this.toString = function(){ + return this.name + "=\"" + apf.xmlentities(String(this.value)) + .replace(//g, ">") + "\""; + }; + + + + this.$setValue = function(value){ + this.nodeValue = this.value = value; + this.specified = true; + + //@todo apf3.0 domattr + this.ownerElement.dispatchEvent("DOMAttrModified", { + relatedNode : this, + attrChange : this.MODIFICATION, + attrName : this.name, + newValue : value, + prevValue : this.$lastValue || "", + bubbles : true + }); + + this.$lastValue = value; + }; + + this.$triggerUpdate = function(e, oldValue){ + var name = this.name, + value = this.value || this.nodeValue, + host = this.ownerElement; + + if (name == "id" && !this.specified && host.id) { + this.specified = true; + return; + } + + if (name.substr(0, 2) == "on") { + if (host.$events[name]) + host.removeEventListener(name.replace(/^on/, ""), host.$events[name]); + if (value) + host.addEventListener(name, (host.$events[name] = + (typeof value == "string" + ? + apf.lm.compile(value, {event: true, parsecode: true}) + + : value))); + return; + } + + if (this.specified) + host.$clearDynamicProperty(name); + + if (typeof value == "string" && (host.$attrExcludePropBind[name] || + (value.indexOf("{") > -1 || value.indexOf("[") > -1))) + host.$setDynamicProperty(name, value); + else + + { + host.setProperty(name, value); //@todo apf3.0 is this a lot slower? + } + //host.$handlePropSet(name, value); + + if (this.specified) { + //@todo apf3.0 domattr - slow? + host.dispatchEvent("DOMAttrModified", { //@todo this is not good, node might not be specified at init + relatedNode : this, + attrChange : this.MODIFICATION, + attrName : name, + newValue : value, + prevValue : this.$lastValue || "", + bubbles : true + }); + } + else this.specified = true; + + this.$lastValue = value; + }; + + //@todo apf3.0 domattr + this.addEventListener("DOMNodeInsertedIntoDocument", this.$triggerUpdate); +}).call(apf.AmlAttr.prototype = new apf.AmlNode()); + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/core/markup/aml/cdatasection.js)SIZE(1300)TIME(Mon, 10 Jan 2011 10:53:11 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + +apf.AmlCDATASection = function(isPrototype){ + this.nodeType = this.NODE_CDATA_SECTION; + this.nodeName = "#cdata-section"; + + this.$init(isPrototype); +}; + +apf.AmlCDATASection.prototype = new apf.AmlText(true); +apf.AmlCDATASection.prototype.serialize = function(){ + return ""; +}; + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/core/markup/aml/comment.js)SIZE(1509)TIME(Mon, 10 Jan 2011 10:53:11 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + +apf.AmlComment = function(isPrototype){ + this.nodeType = this.NODE_COMMENT; + this.nodeName = "#comment"; + + this.$init(isPrototype); +}; + +(function(){ + this.serialize = function(){ + return ""; + }; + + this.$setValue = function(value){ + this.dispatchEvent("DOMCharacterDataModified", { + bubbles : true, + newValue : value, + prevValue : this.nodeValue + }); + } +}).call(apf.AmlComment.prototype = new apf.AmlCharacterData()); + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/core/markup/aml/configuration.js)SIZE(1384)TIME(Mon, 10 Jan 2011 10:53:11 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + +apf.AmlConfiguration = function(isPrototype){ + this.parameterNames = []; + + this.$init(isPrototype); +}; + +(function(){ + this.setParameter = this.setProperty; + + this.getParameter = this.getProperty; + + this.canSetParameter = function(name, value){ //@todo for value + return this.parameterNames.indexOf(name) > -1; + }; +}).call(apf.AmlConfiguration.prototype = new apf.Class()); + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/core/markup/aml/document.js)SIZE(9454)TIME(Mon, 10 Jan 2011 10:53:11 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + + +/** + * The aml document, this is the root of the DOM Tree and has a nodeType with + * value 9 (apf.NODE_DOCUMENT). + * + * @constructor + * @inherits apf.AmlNode + * @inherits apf.Class + * @default_private + * @see baseclass.amldom + * + * @author Ruben Daniels (ruben AT ajax DOT org) + * @version %I%, %G% + * @since 0.8 + */ +apf.AmlDocument = function(){ + this.$prefixes = {}; + this.$namespaceURIs = {}; + this.domConfig = new apf.AmlConfiguration(); + + this.$init(); +}; + +(function() { + /** + * The type of node within the document. + * Possible values: + */ + this.nodeType = this.NODE_DOCUMENT; + this.nodeFunc = apf.NODE_HIDDEN; + this.nodeName = "#document"; + + this.$amlLoaded = true; + + this.activeElement = null; //@todo alias of window.foccussed; + this.doctype = null; + this.domConfig = null; + this.implementation = null; + this.characterSet = apf.characterSet; + + /** + * The root element node of the aml application. This is an element with + * the tagName 'application'. This is similar to the 'html' element + */ + this.documentElement = null; + + /** + * Gets a aml element based on it's id. + * @param {String} id the id of the aml element to return. + * @return {AMLElement} the aml element with the id specified. + */ + this.getElementById = function(id){ + return self[id]; + }; + + this.getElementsByTagName = function(tagName){ + var docEl, res = (docEl = this.documentElement) + .getElementsByTagName(tagName); + + if (tagName == "*" || docEl.tagName == tagName) + res.unshift(docEl); + return res; + }; + + this.getElementsByTagNameNS = function(nameSpaceURI, tagName){ + var docEl, + res = (docEl = this.documentElement) + .getElementsByTagNameNS(nameSpaceURI, tagName); + + if (tagName == "*" || docEl.tagName == tagName && docEl.namespaceURI == nameSpaceURI) + res.unshift(docEl); + return res; + }; + + /** + * Creates a new aml element. + * @param {mixed} tagName information about the new node to create. + * Possible values: + * {String} the tagName of the new element to create + * {String} the aml definition for a single or multiple elements. + * {XMLElement} the aml definition for a single or multiple elements. + * @return {AMLElement} the created aml element. + */ + this.createElement = function(qualifiedName){ + return this.$domParser.$createNode(this, this.NODE_ELEMENT, null, + this.namespaceURI, qualifiedName); + }; + + this.createElementNS = function(namespaceURI, qualifiedName){ + return this.$domParser.$createNode(this, this.NODE_ELEMENT, null, + namespaceURI, qualifiedName); + }; + + this.importNode = function(node, deep){ + if (deep && node.nodeType == 1) { + return this.$domParser.parseFromXml(node, { + doc : this, + delay : true + }).childNodes[0]; + } + else { + return this.$domParser.$createNode(this, node.nodeType, node); + } + }; + + //@todo + this.createAttribute = function(nodeName){ + return this.$domParser.$createNode(this, this.NODE_ATTRIBUTE, null, + this.nameSpaceURI, nodeName); + }; + + //@todo + this.createAttributeNS = function(nameSpaceURI, nodeName){ + return this.$domParser.$createNode(this, this.NODE_ATTRIBUTE, null, + nameSpaceURI, nodeName); + }; + + this.createEvent = function(){ + return new apf.AmlEvent(); + }; + + this.createComment = function(nodeValue){ + return this.$domParser.$createNode(this, this.NODE_COMMENT, null, null, + null, nodeValue); + }; + + this.createProcessingInstruction = function(target, data){ + return this.$domParser.$createNode(this, this.NODE_PROCESSING_INSTRUCTION, + null, null, target, data); + }; + + this.createCDATASection = function(nodeValue){ + return this.$domParser.$createNode(this, this.NODE_CDATA_SECTION, null, + null, null, nodeValue); + }; + + this.createTextNode = function(nodeValue){ + return this.$domParser.$createNode(this, this.NODE_TEXT, null, null, + null, nodeValue); + }; + + this.createDocumentFragment = function(){ + return this.$domParser.$createNode(this, this.NODE_DOCUMENT_FRAGMENT); + }; + + this.querySelector = function(){}; + + this.querySelectorAll = function(){}; + + + /** + * See W3C evaluate + */ + this.evaluate = function(sExpr, contextNode, nsResolver, type, x){ + var result = apf.XPath.selectNodes(sExpr, + contextNode || this.documentElement); + + /** + * @private + */ + return { + snapshotLength : result.length, + snapshotItem : function(i){ + return result[i]; + } + } + }; + + /** + * See W3C createNSResolver + */ + this.createNSResolver = function(contextNode){ + return {}; + }; + + + this.hasFocus = function(){ + + } + + + //designMode property + + var selection; + this.getSelection = function(){ + if (!selection) + selection = new apf.AmlSelection(this); + return selection; + } + + var selectrect; + this.$getSelectRect = function(){ + if (!selectrect) + selectrect = new apf.selectrect(); + return selectrect; + } + + var visualselect; + this.$getVisualSelect = function(){ + if (!visualselect) + visualselect = new apf.visualSelect(this.getSelection()); + return visualselect; + } + + var visualconnect; + this.$getVisualConnect = function(){ + if (!visualconnect) + visualconnect = new apf.visualConnect(this.getSelection()); + return visualconnect; + } + + this.createRange = function(){ + return new apf.AmlRange(this); + } + + this.queryCommandState = function(commandId){ + return (this.$commands[commandId.toLowerCase()] || apf.K) + .call(this, null, null, null, 1) || false; + }; + + this.queryCommandValue = function(commandId){ + return (this.$commands[commandId.toLowerCase()] || apf.K) + .call(this, null, null, null, 2) || false; + }; + + this.queryCommandEnabled = function(commandId){ + return (this.$commands[commandId.toLowerCase()] || apf.K) + .call(this, this.getSelection().$getNodeList(), false, arguments[2], 3) || false; + }; + + this.queryCommandIndeterm = function(commandId){ + return (this.$commands[commandId.toLowerCase()] || apf.K) + .call(this, null, null, null, 4) || false; + }; + + this.queryCommandSupported = function(commandId){ + return this.$commands[commandId.toLowerCase()] ? true : false; + }; + + var special = {"commit":1,"rollback":1,"begin":1,"undo":1,"redo":1,"contextmenu":2,"mode":2,"pause":1}; + this.execCommand = function(commandId, showUI, value, query){ + var f; + + //if command is not enabled, do nothing + if (!(f = this.$commands[commandId.toLowerCase()])) + return false; + + if (special[commandId] == 1) + return f.call(this, null, null, value); + + //Get Selection + //var nodes = this.getSelection().$getNodeList(); + var nodes = this.$getVisualSelect().getLastSelection() + || this.getSelection().$getNodeList(); + + //Execute Action + if (special[commandId] == 2) + f.call(this, nodes, showUI, value, query); + else { + this.$commands.begin.call(this); + if (f.call(this, nodes, showUI, value, query) === false) + this.$commands.rollback.call(this); + else + this.$commands.commit.call(this); //Will only record if there are any changes + } + }; + + this.$commands = {}; + +}).call(apf.AmlDocument.prototype = new apf.AmlNode()); + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/core/markup/aml/documentfragment.js)SIZE(1286)TIME(Mon, 10 Jan 2011 10:53:11 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + +apf.AmlDocumentFragment = function(isPrototype){ + this.$init(isPrototype); +}; + +apf.AmlDocumentFragment.prototype = new apf.AmlNode(); +apf.AmlDocumentFragment.prototype.nodeName = "#document-fragment"; +apf.AmlDocumentFragment.prototype.nodeType = + apf.AmlDocumentFragment.prototype.NODE_DOCUMENT_FRAGMENT; + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/core/markup/aml/event.js)SIZE(2086)TIME(Mon, 10 Jan 2011 10:53:11 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + +/** + * Implementation of W3C event object. An instance of this class is passed as + * the first argument of any event handler. Per event it will contain different + * properties giving context based information about the event. + * @constructor + * @default_private + */ +apf.AmlEvent = function(name, data){ + this.name = name; + + var prop; + for (prop in data) + this[prop] = data[prop]; +}; + +apf.AmlEvent.prototype = { + + bubbles : false, + cancelBubble : false, + + + /** + * Cancels the event if it is cancelable, without stopping further + * propagation of the event. + */ + preventDefault : function(){ + this.returnValue = false; + }, + + + /** + * Prevents further propagation of the current event. + */ + stopPropagation : function(){ + this.cancelBubble = true; + }, + + + stop : function() { + this.returnValue = false; + + this.cancelBubble = true; + + } +}; + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/core/markup/aml/namednodemap.js)SIZE(3407)TIME(Mon, 10 Jan 2011 10:53:11 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + +//@todo apf3.0 +apf.AmlNamedNodeMap = function(host){ + this.$host = host; +}; + +(function(){ + this.getNamedItem = function(name){ + for (var i = 0; i < this.length; i++) { + if (this[i].name == name) + return this[i]; + } + return false; + }; + + this.setNamedItem = function(node){ + var name = node.name; + for (var item, i = this.length - 1; i >= 0; i--) { + if (this[i].name == name) { + this[i].ownerElement = null; + this.splice(i, 1); + break; + } + } + + this.push(node); + + node.ownerElement = this.$host; + node.ownerDocument = this.$host.ownerDocument; + node.$triggerUpdate(); + }; + + //@todo apf3.0 domattr + this.removeNamedItem = function(name){ + //Should deconstruct dynamic properties + + for (var item, i = this.length - 1; i >= 0; i--) { + if (this[i].name == name) { + item = this[i]; + this.splice(i, 1); + break; + } + } + if (!item) return false; + + //@todo hack! + //this should be done properly + var oldValue = item.nodeValue; + item.nodeValue = item.value = ""; + item.$triggerUpdate(null, oldValue); + item.ownerElement = null; + item.nodeValue = item.value = oldValue; + + return item; + }; + + this.item = function(i){ + return this[i]; + }; + + //if (apf.isIE < 8) { //Only supported by IE8 and above + this.length = 0; + + this.splice = function(pos, length){ + for (var i = pos, l = this.length - length; i < l; i++) { + this[i] = this[i + 1]; + } + delete this[i]; + this.length -= length; + } + + this.push = function(o) { + this[this.length++] = o; + return this.length; + } + //} + + this.join = function(glue){ + var x = []; + for (var e, a, i = 0, l = this.length; i < l; i++) { + if ((e = (a = this[i]).ownerElement) && e.$inheritProperties[a.nodeName] != 2) + x.push(this[i]); + } + return x.join(glue); + } +}).call(apf.AmlNamedNodeMap.prototype = {}); //apf.isIE < 8 ? {} : [] + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/core/markup/aml/processinginstruction.js)SIZE(4311)TIME(Mon, 10 Jan 2011 10:53:11 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + +apf.AmlProcessingInstruction = function(isPrototype){ + this.$init(isPrototype); +}; + +(function(){ + this.nodeType = this.NODE_PROCESSING_INSTRUCTION; + + /** + * @todo docs + */ + this.data = null; + + /** + * @todo docs + */ + this.target = null; + + this.serialize = function(){ + return ""; + }; + + this.reload = function(){ + this.$handlePropSet("data", this.data); + }; + + //1 = force no bind rule, 2 = force bind rule + this.$attrExcludePropBind = apf.extend({ + calcdata : 0 //Start in code mode + }, this.$attrExcludePropBind); + + this.getAttribute = function(){}; + this.$setInheritedAttribute = apf.AmlElement.prototype.$setInheritedAttribute; + this.$supportedProperties = []; + this.$propHandlers = {}; + this.$booleanProperties = {}; + this.$inheritProperties = {}; + + + apf.LiveEdit && this.implement(apf.LiveEdit); + + + this.$setValue = function(value){ + this.setProperty("data", value); + }; + + this.$handlePropSet = function(prop, value, force){ + this[prop] = value; + if (prop == "data") { + this.$clearDynamicProperty("calcdata"); + this.$setDynamicProperty("calcdata", value); + } + + else if (prop == "liveedit") { + this.$propHandlers["liveedit"].call(this, value); + + this.$clearDynamicProperty("calcdata"); + this.$setDynamicProperty("calcdata", this.data); + } + + else if (prop == "target") { + //not implemented + } + else if (this.$propHandlers[prop]) { + this.$propHandlers[prop].call(this, value, prop); + } + }; + + this.addEventListener("DOMNodeInsertedIntoDocument", function(e){ + var pHtmlNode = e.pHtmlNode; + if (!pHtmlNode && (this.parentNode.$bindingRule + || !(pHtmlNode = this.parentNode.$int))) + return; + + pHtmlNode.appendChild(this.$ext = document.createElement("span")); + this.$ext.host = this; + + + if (!this.hasFeature(apf.__LIVEEDIT__)) { + this.implement(apf.LiveEdit); + } + + this.liveedit = apf.isTrue(apf.getInheritedAttribute(this, "liveedit")); + if (this.liveedit) { + //this.implement(apf.LiveEdit); + this.$inheritProperties["liveedit"] = 2; + this.$propHandlers["liveedit"].call(this, this.liveedit); + } + + + this.$setDynamicProperty("calcdata", this.data); + + + if (this.target.match(/\-debug$/)) { + apf.console.info(this.$lastFParsed.toString()); + } + + }, true); + + /*this.addEventListener("DOMNodeRemovedFromDocument", function(e){ + this.$clearDynamicProperty("calcdata"); + });*/ + + this.$destroy = function(){ + this.$clearDynamicProperty("calcdata"); + this.$propHandlers["calcdata"].call(this, ""); + }; +}).call(apf.AmlProcessingInstruction.prototype = new apf.AmlNode()); + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/core/markup/aml/range.js)SIZE(15809)TIME(Mon, 10 Jan 2011 10:53:11 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + +/** + * HTML5 Range object + * @todo copy docs from http://www.w3.org/TR/DOM-Level-2-Traversal-Range/ranges.html + */ +apf.AmlRange = function(doc){ + this.$init(); + + this.$ownerDocument = + this.startContainer = + this.endContainer = doc || apf.document; + this.startOffset = + this.endOffset = 0; + + var _self = this; + this.$domCharModS = function (e){ + + } + this.$domCharModE = function (e){ + + } + + this.$domNodeInsS = function(e){ + if (e.relatedNode == this && e.currentTarget.nodeType != 2) { + var nr = apf.getChildNumber(e.currentTarget); + if (nr < _self.startOffset) + _self.startOffset++; + } + } + this.$domNodeInsE = function(e){ + if (e.relatedNode == this && e.currentTarget.nodeType != 2) { + var nr = apf.getChildNumber(e.currentTarget); + if (nr < _self.endOffset) + _self.endOffset++; + } + } + + this.$domNodeRemS = function(e){ + if (e.relatedNode == this && e.currentTarget.nodeType != 2) { + var nr = apf.getChildNumber(e.currentTarget); + if (nr < _self.startOffset) + _self.startOffset--; + } + } + this.$domNodeRemE = function(e){ + if (e.relatedNode == this && e.currentTarget.nodeType != 2) { + var nr = apf.getChildNumber(e.currentTarget); + if (nr < _self.endOffset) + _self.endOffset--; + } + } + + //@todo this should be rewritten when fromdoc events are fixed + this.$domNodeRemDoc = function(e){ + if (apf.isChildOf(e.currentTarget, _self.startContainer, true)) { + _self.setStart(e.currentTarget.parentNode, apf.getChildNumber(e.currentTarget)); + } + if (apf.isChildOf(e.currentTarget, _self.endContainer, true)) { + _self.setEnd(e.currentTarget.parentNode, apf.getChildNumber(e.currentTarget)); + } + } + + this.$ownerDocument.addEventListener("DOMNodeRemoved", this.$domNodeRemDoc); +}; +(function() { + this.START_TO_START = 0; + this.START_TO_END = 1; + this.END_TO_END = 2; + this.END_TO_START = 3; + + this.collapsed = true; + this.commonAncestorContainer = null; //@todo + + this.$detached = false; + + this.toString = function(){ + return "[apf.AmlRange]"; + /*var n = this.$contents(true); + if (n.serialize) + return n.serialize().replace(/<[^>]*>/g, ""); + else { + var str = []; + for (var i = 0, l = n.childNodes.length; i < l; i++) { + str.push(n.childNodes[0].serialize()); + } + return str.join("\n"); + }*/ + } + + var charNode = {2:1,3:1,4:1,7:1} + + this.setStart = function(refNode, offset, noEvent){ + if (!refNode) return; + + + if (offset < 0 || offset > (charNode[refNode.nodeType] + ? refNode.nodeValue.length + : refNode.childNodes.length)) { + + throw new Error("INDEX_SIZE_ERR: Raised if offset is negative or \ + greater than the number of child units in refNode."); //@todo turn into decent apf error + + return; + } + + + if (charNode[this.startContainer.nodeType]) + this.startContainer.removeEventListener("DOMCharacterDataModified", this.$domCharModS); + //@todo + else { + this.startContainer.removeEventListener("DOMNodeInserted", this.$domNodeInsS); + this.startContainer.removeEventListener("DOMNodeRemoved", this.$domNodeRemS); + } + + this.startContainer = refNode; + this.startOffset = offset; + + this.collapsed = this.startContainer == this.endContainer + && this.startOffset == this.endOffset; + + //@todo If start > end -> start = end; + + if (charNode[refNode.nodeType]) + refNode.addEventListener("DOMCharacterDataModified", this.$domCharModS); + //@todo + else { + refNode.addEventListener("DOMNodeInserted", this.$domNodeInsS); + refNode.addEventListener("DOMNodeRemoved", this.$domNodeRemS); + } + + if (!noEvent) + this.dispatchEvent("update"); + } + + this.setStartBefore = function(refNode){ + this.setStart(this.endContainer.parentNode, apf.getChildNumber(refNode)); + } + + this.setStartAfter = function(refNode){ + this.setStart(this.endContainer.parentNode, apf.getChildNumber(refNode) + 1); + } + + /** + * Sets the attributes describing the end of a Range. + *DOMException + INDEX_SIZE_ERR: Raised if offset is negative or greater than the number of child units in refNode. Child units are 16-bit units if refNode is a type of CharacterData node (e.g., a Text or Comment node) or a ProcessingInstruction node. Child units are Nodes in all other cases. + + INVALID_STATE_ERR: Raised if detach() has already been invoked on this object. + */ + this.setEnd = function(refNode, offset, noEvent){ + if (!refNode) return; + + + if (offset < 0 || offset > (charNode[refNode.nodeType] + ? refNode.nodeValue.length + : refNode.childNodes.length)) { + + throw new Error("INDEX_SIZE_ERR: Raised if offset is negative or \ + greater than the number of child units in refNode."); //@todo turn into decent apf error + + return; + } + + + if (charNode[this.endContainer.nodeType]) + this.endContainer.removeEventListener("DOMCharacterDataModified", this.$domCharModE); + //@todo + else { + this.endContainer.removeEventListener("DOMNodeInserted", this.$domNodeInsE); + this.endContainer.removeEventListener("DOMNodeRemoved", this.$domNodeRemE); + } + + this.endContainer = refNode; + this.endOffset = offset; + + this.collapsed = this.startContainer == this.endContainer + && this.startOffset == this.endOffset; + + //@todo If start > end -> start = end; + if (charNode[refNode.nodeType]) + refNode.addEventListener("DOMCharacterDataModified", this.$domCharModE); + //@todo + else { + refNode.addEventListener("DOMNodeInserted", this.$domNodeInsE); + refNode.addEventListener("DOMNodeRemoved", this.$domNodeRemE); + } + + if (!noEvent) + this.dispatchEvent("update"); + } + + /** + * Sets the end position to be before a node. + */ + this.setEndBefore = function(refNode){ + this.setEnd(this.endContainer.parentNode, apf.getChildNumber(refNode)); + } + + /** + * Sets the end of a Range to be after a node + */ + this.setEndAfter = function(refNode){ + this.setEnd(this.endContainer.parentNode, apf.getChildNumber(refNode) + 1); + } + + this.collapse = function(toStart){ + if (toStart) + this.setEnd(this.startContainer, this.startOffset, true); + else + this.setStart(this.endContainer, this.endOffset, true); + + this.dispatchEvent("update"); + } + + this.selectNode = function(refNode){ + this.setStart(refNode.parentNode, apf.getChildNumber(refNode), true); + this.setEnd(refNode.parentNode, this.startOffset + 1); + } + + this.selectNodeContents = function(refNode){ + this.setStart(refNode, 0, true); + this.setEnd(refNode, refNode.childNodes.length); + } + + //@todo quite possibly the assumption of end > start is wrong.. fix that... + this.$contents = function(clone){ + var s = this.startContainer, e = this.endContainer, cA = e.childNodes[this.endOffset], + last, simple = s == e; + + while (cA && !apf.isChildOf(cA, s, true)) + cA = (last = cA).parentNode; + + + if (!cA) { + + throw new Error("Range start/end does not have a common ancestor"); //@todo turn into decent apf error + + return; + } + + + //last = this.endContainer.childNodes[this.endOffset - 1]; + var collection = []; + var node = s.childNodes[this.startOffset]; + var doc = this.startContainer.ownerDocument; + var frag = doc.createDocumentFragment(); + + //Walk up to the common ancestor + //@todo s could be a textnode... + if (s != e && s != cA) { + var pNode = s.cloneNode(false); + while (node.parentNode != cA) { + pNode.appendChild(clone ? node.cloneNode(true) : node); + if (!node.nextSibling) { + do { + node = node.parentNode; + pNode = node.cloneNode(false).appendChild(pNode).parentNode; + } while(!node.nextSibling); + if (node.parentNode == cA) + break; + } + node = node.nextSibling; + } + frag.appendChild(pNode); + } + + //Walk to the peer + while (node != last) { + node = node.nextSibling + frag.appendChild(clone ? node.cloneNode(true) : node); + } + + //Walk up from the end container + //@todo e could be a textnode... + if (s != e && e != cA) { + var pNode = e.cloneNode(false); + var node = last.previousSibling || last.parentNode;//this.endOffset > 0 ? e.childNodes[this.endOffset - 1] : e; + while (node.parentNode != cA) { + pNode.appendChild(clone ? node.cloneNode(true) : node); + if (!node.previousSibling) { + do { + node = node.parentNode; + pNode = node.cloneNode(false).appendChild(pNode).parentNode; + } while(!node.previousSibling); + if (node.parentNode == cA) + break; + } + node = node.previousSibling; + } + frag.appendChild(pNode); + } + + return frag; + } + + /** + * Compare the boundary-points of two Ranges in a document. + * @return -1, 0 or 1 depending on whether the corresponding boundary-point + * of the Range is respectively before, equal to, or after the + * corresponding boundary-point of sourceRange. + */ + this.compareBoundaryPoints = function(how, sourceRange){ + var d; + switch(how) { + case this.START_TO_START: + d = this.startOffset - sourceRange.startOffset; + break; + case this.START_TO_END: + d = this.startOffset - sourceRange.endOffset; + break; + case this.END_TO_START: + d = this.endOffset - sourceRange.startOffset; + break; + case this.END_TO_END: + d = this.endOffset - sourceRange.endOffset; + break; + } + + return d < 0 ? -1 : (d > 0 ? 1 : 0); + } + + this.deleteContents = function(){ + this.$contents().destroy(); + this.collapse(); + } + + this.extractContents = function(){ + var frag = this.$contents(); + this.collapse(); + return frag; + } + + this.cloneContents = function(){ + return this.$contents(true); + } + + /** + * Inserts a node into the Document or DocumentFragment at the start of the + * Range. If the container is a Text node, this will be split at the start + * of the Range (as if the Text node's splitText method was performed at the + * insertion point) and the insertion will occur between the two resulting + * Text nodes. Adjacent Text nodes will not be automatically merged. If the + * node to be inserted is a DocumentFragment node, the children will be + * inserted rather than the DocumentFragment node itself. + */ + this.insertNode = function(newNode){ + //@todo + this.collapsed = false; + } + + this.surroundContents = function(newParent){ + this.setStart(newParent, newParent.childNodes.length, true); + newParent.appendChild(this.$contents()); + this.setEnd(newParent, newParent.childNodes.length); + } + + this.cloneRange = function(){ + var range = new apf.AmlRange(); + range.setStart(this.startContainer, this.startOffset, true); + range.setEnd(this.endContainer, this.endOffset); + return range; + } + + this.detach = function(){ + this.startContainer = this.endContainer = null; + + function detachError(){ + + throw new Error("INVALID_STATE_ERR: Raised if detach() \ + has already been invoked on this object."); //@todo turn into decent apf error + + } + + for (var prop in this) { + if (typeof this[prop] == "function" && this[prop] != apf.Class.prototype[prop]) + this[prop] = detachError; + } + + this.$ownerDocument.removeEventListener("DOMNodeRemoved", this.$domNodeRemDoc); + + if (this.startContainer) { + if (charNode[this.startContainer.nodeType]) + this.startContainer.removeEventListener("DOMCharacterDataModified", this.$domCharModS); + //@todo + else { + this.startContainer.removeEventListener("DOMNodeInserted", this.$domNodeInsS); + this.startContainer.removeEventListener("DOMNodeRemoved", this.$domNodeRemS); + } + + if (charNode[this.endContainer.nodeType]) + this.endContainer.removeEventListener("DOMCharacterDataModified", this.$domCharModE); + //@todo + else { + this.endContainer.removeEventListener("DOMNodeInserted", this.$domNodeInsE); + this.endContainer.removeEventListener("DOMNodeRemoved", this.$domNodeRemE); + } + } + } +}).call(apf.AmlRange.prototype = new apf.Class()); + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/core/markup/aml/selection.js)SIZE(8861)TIME(Mon, 10 Jan 2011 10:53:11 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + +apf.AmlSelection = function(doc){ + this.$init(); + + this.$ownerDocument = doc; + + var _self = this; + var monitor = false; + this.$monitorRange = function(){ + apf.queue.add("selrange" + this.$uniqueId, function(){ + var range = _self.$ranges[0]; + _self.anchorNode = range.startContainer; + _self.anchorOffset = range.startOffset; + + range = _self.$ranges[_self.rangeCount - 1]; + _self.focusNode = range.endContainer; + _self.focusOffset = range.endOffset; + monitor = false; + }); + monitor = true; + } + + var update = false; + this.$update = function(){ + if (!update) { + apf.queue.add("selupdate" + this.$uniqueId, function(){ + _self.dispatchEvent("update"); + update = false; + }); + update = true; + } + } +}; +(function() { + /** + * Returns the element that contains the start of the selection. + * Returns null if there's no selection. + */ + this.anchorNode = null; + + /** + * Returns the offset of the start of the selection relative to the element that contains the start of the selection. + * Returns 0 if there's no selection. + */ + this.anchorOffset = 0; + + /** + * Returns the element that contains the end of the selection. + * Returns null if there's no selection. + */ + this.focusNode = null; + + /** + * Returns the offset of the end of the selection relative to the element that contains the end of the selection. + *Returns 0 if there's no selection. + */ + this.focusOffset = null; + + /** + * Returns the number of ranges in the selection. + */ + this.rangeCount = 0; + this.$ranges = []; + + this.toString = function(){ + return "[apf.AmlSelection]";// this.$ranges.join(""); + } + + /** + * Returns true if there's no selection or if the selection is empty. Otherwise, returns false. + */ + this.isCollapsed = function(){ + return !this.rangeCount || this.rangeCount == 1 && this.$ranges[0].collapsed; + } + + /** + * Replaces the selection with an empty one at the given position. + */ + this.collapse = function(parentNode, offset, noEvent){ + for (var i = 0, l = this.$ranges.length; i < l; i++) { + (range = this.$ranges[i]).removeEventListener("update", this.$monitorRange); + range.detach(); + } + + var range; + this.$ranges = [range = new apf.AmlRange(this.ownerDocument)]; + range.addEventListener("update", this.$monitorRange); + range.setStart(parentNode, offset); + range.setEnd(parentNode, offset); + this.rangeCount = 1; + + this.focusNode = + this.anchorNode = parentNode; + this.anchorOffset = + this.focusOffset = offset; + + if (!noEvent) + this.dispatchEvent("update"); + } + + /** + * Replaces the selection with an empty one at the position of the start of the current selection. + */ + this.collapseToStart = function(){ + + if (!this.rangeCount) { + + throw new Error("The selection has no ranges"); + + return; + } + + + var range = this.$ranges[0]; + this.collapse(range.startContainer, range.startOffset); + } + + /** + * Replaces the selection with an empty one at the position of the end of the current selection. + */ + this.collapseToEnd = function(){ + + if (!this.rangeCount) { + + throw new Error("The selection has no ranges"); + + return; + } + + + var range = this.$ranges[this.rangeCount - 1]; + this.collapse(range.endContainer, range.endOffset); + } + + /** + * Replaces the selection with one that contains all the contents of the given element. + */ + this.selectAllChildren = function(parentNode){ + this.collapse(parentNode, 0, true); + var range = this.$ranges[0]; + range.selectNodeContents(parseNode); + + this.focusNode = + this.anchorNode = parentNode; + this.anchorOffset = 0; + this.focusOffset = range.endOffset; + + this.$update(); + } + + /** + * Deletes the selection. + */ + this.deleteFromDocument = function(){ + for (var i = 0, l = this.$ranges.length; i < l; i++) { + this.$ranges[i].deleteContents(); + } + + var range = this.$ranges[0]; + this.anchorNode = range.startContainer; + this.anchorOffset = range.startOffset; + + range = this.$ranges[this.rangeCount - 1]; + this.focusNode = range.endContainer; + this.focusOffset = range.endOffset; + + this.$update(); + } + + /** + * Returns the given range. + */ + this.getRangeAt = function(index){ + + if (index < 0 || index > this.rangeCount - 1) + throw new Error("INDEX_SIZE_ERR"); + + + return this.$ranges[index]; + } + + /** + * Adds the given range to the selection. + */ + this.addRange = function(range){ + this.rangeCount = this.$ranges.push(range); + + this.focusNode = range.endContainer; + this.focusOffset = range.endOffset; + range.addEventListener("update", this.$monitorRange); + + this.$update(); + + return range; + } + + /** + * Removes the given range from the selection, if the range was one of the ones in the selection. + */ + this.removeRange = function(range){ + this.$ranges.remove(range); + this.rangeCount = this.$ranges.length; + range.removeEventListener("update", this.$monitorRange); + + var range = this.$ranges[0]; + this.anchorNode = range.startContainer; + this.anchorOffset = range.startOffset; + + range = this.$ranges[this.rangeCount - 1]; + this.focusNode = range.endContainer; + this.focusOffset = range.endOffset; + + this.$update(); + } + + /** + * Removes all the ranges in the selection. + */ + this.removeAllRanges = function(){ + for (var range, i = 0, l = this.$ranges.length; i < l; i++) { + (range = this.$ranges[i]).removeEventListener("update", this.$monitorRange); + range.detach(); + } + + this.$ranges = []; + this.rangeCount = 0; + + this.anchorNode = null; + this.anchorOffset = 0; + this.focusNode = null; + this.focusOffset = 0; + + this.$update(); + } + + //currently only ranges with single element selected is supported + this.$getNodeList = function(){ + var nodes = []; + for (var r, i = 0; i < this.rangeCount; i++) { + nodes.push((r = this.$ranges[i]).startContainer.childNodes[r.startOffset]); + } + return nodes; + } + + this.$selectList = function(list){ + this.removeAllRanges(); + for (var i = 0; i < list.length; i++) { + this.addRange(new apf.AmlRange(this)).selectNode(list[i]); + } + } + + this.$getFirstNode = function(){ + var r; + return (r = this.$ranges[0]).startContainer.childNodes[r.startOffset]; + } +}).call(apf.AmlSelection.prototype = new apf.Class()); + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/core/markup/aml/textrectangle.js)SIZE(1662)TIME(Mon, 10 Jan 2011 10:53:11 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + +apf.AmlTextRectangle = function(host){ + var _self = this; + function handler(){ + var pos = _self.getAbsolutePosition(_self.$ext); + _self.setProperty("left", pos[0]); + _self.setProperty("top", pos[1]); + _self.setProperty("right", document.documentElement.offsetWidth - pos[0]); + _self.setProperty("bottom", document.documentElement.offsetWidth - pos[1]); + } + + host.addEventListener("prop.width", handler); + host.addEventListener("prop.height", handler); + host.addEventListener("prop.left", handler); + host.addEventListener("prop.top", handler); + + handler.call(host); +}; +apf.AmlTextRectangle.prototype = new apf.Class(); + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/core/markup/xhtml.js)SIZE(1530)TIME(Mon, 10 Jan 2011 10:53:11 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + + +/** + * Object creating the XHTML namespace for the aml parser. + * + * @constructor + * @parser + * + * @author Ruben Daniels (ruben AT ajax DOT org) + * @version %I%, %G% + * @since 0.8 + */ +apf.xhtml = new apf.AmlNamespace(); +apf.setNamespace("http://www.w3.org/1999/xhtml", apf.xhtml); + + +/* +if (apf.getTextNode(x)) { + var data = { + amlNode : x, + htmlNode : o + } + + + apf.language.addElement(apf.getTextNode(x) + .nodeValue.replace(/^\$(.*)\$$/, "$1"), data); + +} + +*/ + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/core/markup/xhtml/element.js)SIZE(5022)TIME(Mon, 10 Jan 2011 10:53:11 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + + +apf.XhtmlElement = function(struct, tagName){ + this.$init(tagName || true, apf.NODE_VISIBLE, struct); + + this.$xoe = this.addEventListener; + this.addEventListener = this.$xae; + this.removeEventListener = this.$xre; + + var _self = this; + this.$de = function(e){ + _self.dispatchEvent(e.type, null, e); + } +}; + +(function(){ + var excludedEvents = { + "contextmenu": 1, + "keydown": 1, + "keypress": 1, + "keyup": 1, + "DOMNodeInserted": 2, + "DOMNodeInsertedIntoDocument": 2, + "DOMNodeRemoved": 2, + "DOMNodeRemovedFromDocument": 2 + }; + + this.$xae = function(type, fn){ + this.$xoe.apply(this, arguments); + + if (excludedEvents[type] > (this.editable ? 0 : 1) + || type.substr(0, 5) == "prop.") + return; + + if (this.$ext) { + if (type.substr(0,2) == "on") + type = type.substr(2); + apf.addListener(this.$ext, type, this.$de); + } + }; + + this.$xre = function(type, fn) { + apf.AmlElement.prototype.removeEventListener.apply(this, arguments); + + + if (this.editable && "contextmenu|keydown|keypress|keyup".indexOf(type) > -1) + return; + + + if (this.$ext) + apf.removeListener(this.$ext, type, this.$de); + } + + this.$handlePropSet = function(name, value, force, inherit){ + if (this.$booleanProperties[name]) + value = apf.isTrue(value); + + this[name] = value; + var handler = this.$propHandlers && this.$propHandlers[name] + || apf.GuiElement.propHandlers[name]; + + if (handler) + handler.call(this, value, null, name); + else if (this.$int && (force || this.$amlLoaded)) { + this.$int.setAttribute(apf.isIE && apf.isIE < 8 && name == "class" + ? "className" : name, value); + } + }; + + this.addEventListener("DOMNodeInsertedIntoDocument", function(e){ + var pHtmlNode; + if (!(pHtmlNode = this.$pHtmlNode = this.parentNode.$int)) + return; + + var str, aml = this.$aml; + if (aml) { + if (aml.serialize) + str = aml.serialize(); + else { + aml = aml.cloneNode(false); + str = aml.xml || aml.nodeValue; + } + + str = str.replace(/ on\w+="[^"]*"| on\w+='[^']*'/g, ""); + + this.$ext = + this.$int = apf.insertHtmlNode(null, pHtmlNode, null, apf.html_entity_decode(str)); + } + else { + this.$ext = this.$int = + pHtmlNode.appendChild(document.createElement(this.localName)); + } + + if (this.localName != "a") + this.$ext.host = this; + + this.style = this.$ext.style; + }, true); + + + this.addEventListener("DOMNodeInsertedIntoDocument", function(e){ + this.$amlLoaded = true; + + if (this.$setLayout) + this.$setLayout(); + }); + +}).call(apf.XhtmlElement.prototype = new apf.AmlElement()); + +apf.Init.addConditional(function(){ + if (apf.isO3) return; + var prot = apf.XhtmlElement.prototype; + + //prot.implement(apf.Interactive); + prot.implement( + + apf.Anchoring + + ); + + + prot.$drawn = true; + prot.$setLayout = apf.GuiElement.prototype.$setLayout; + + prot.addEventListener("DOMNodeInserted", function(e){ + if (e.currentTarget == this + && "vbox|hbox|table".indexOf(this.parentNode.localName) == -1) { + this.$setLayout(); + } + }); + +}, null, ["interactive"]); + +apf.xhtml.setElement("@default", apf.XhtmlElement); + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/core/markup/xhtml/body.js)SIZE(1783)TIME(Mon, 10 Jan 2011 10:53:11 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + +apf.XhtmlBodyElement = function(struct, tagName){ + this.$init(tagName || "body", apf.NODE_VISIBLE, struct); +}; + +(function(){ + + this.$coreHtml = true; + + + this.addEventListener("DOMNodeInsertedIntoDocument", function(e){ + if (!this.ownerDocument.body) + this.ownerDocument.body = this; + + this.$ext = + this.$int = document.body; + }, true); +}).call(apf.XhtmlBodyElement.prototype = new apf.AmlElement()); + +apf.Init.addConditional(function(){ + if (apf.isO3) return; + var prot = apf.XhtmlBodyElement.prototype; + + + prot.implement( + apf.ContentEditable + ); + +}, null, ["interactive"]); + +apf.xhtml.setElement("body", apf.XhtmlBodyElement); + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/core/markup/xhtml/html.js)SIZE(2693)TIME(Mon, 10 Jan 2011 10:53:11 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + + +/** + * @todo description + * + * @author Ruben Daniels (ruben AT ajax DOT org) + * @version %I%, %G% + * @since 0.4 + */ +apf.XhtmlHtmlElement = function(struct, tagName){ + this.$init(tagName || "html", apf.NODE_VISIBLE, struct); + + + this.$coreHtml = true; + + + this.$ext = document.documentElement; + this.$ext.host = this; + + this.$int = document.body; + this.$tabList = []; //Prevents documentElement from being focussed + this.$focussable = apf.KEYBOARD; + this.focussable = true; + this.visible = true; + this.$isWindowContainer = true; + //this.focus = function(){ this.dispatchEvent("focus"); }; + //this.blur = function(){ this.dispatchEvent("blur"); }; + + this.implement(apf.Focussable); + + + apf.window.$addFocus(this); + + + this.addEventListener("DOMNodeInsertedIntoDocument", function(e){ + var i, l, n, a, c, + attr = this.attributes, doc = this.ownerDocument; + for (i = 0, l = attr.length; i < l; i++) { + n = (a = attr[i]).nodeName.split(":"); + if (n[0] == "xmlns") { + if (c = n[1]) { + doc.$prefixes[c] = a.nodeValue; + doc.$namespaceURIs[a.nodeValue] = c; + } + else { + doc.namespaceURI = a.nodeValue; + } + } + } + + if (!doc.namespaceURI) + doc.namespaceURI = apf.ns.xhtml; + }); +}; +apf.XhtmlHtmlElement.prototype = new apf.XhtmlElement(); + +apf.xhtml.setElement("html", apf.XhtmlHtmlElement); + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/core/markup/xhtml/ignore.js)SIZE(1360)TIME(Mon, 10 Jan 2011 10:53:11 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + +apf.XhtmlIgnoreElement = function(struct, tagName){ + this.$init(tagName, apf.NODE_VISIBLE, struct); +}; + +apf.XhtmlIgnoreElement.prototype = new apf.AmlElement(); + +apf.xhtml.setElement("script", apf.XhtmlIgnoreElement); +apf.xhtml.setElement("noscript", apf.XhtmlIgnoreElement); +apf.xhtml.setElement("head", apf.XhtmlIgnoreElement); +apf.xhtml.setElement("meta", apf.XhtmlIgnoreElement); + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/core/markup/xhtml/input.js)SIZE(2187)TIME(Mon, 10 Jan 2011 10:53:11 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + +apf.XhtmlInputElement = function(struct, tagName){ + this.$init(tagName || "input", apf.NODE_VISIBLE, struct); +}; + +(function(){ + this.$xae = apf.XhtmlElement.prototype.$xae; + this.$xre = apf.XhtmlElement.prototype.$xre; + this.$handlePropSet = function(name, value, force){ + if (name == "type") + return; + + return apf.XhtmlElement.prototype.$handlePropSet.call(this, name, value, force); + }; + + this.addEventListener("DOMNodeInsertedIntoDocument", function(e){ + var pHtmlNode; + if (!(pHtmlNode = this.parentNode.$int)) + return; + + if (this.$aml) { + this.$ext = + this.$int = apf.insertHtmlNode(this.$aml.serialize + ? this.$aml + : this.$aml.cloneNode(false), pHtmlNode); + } + else { + this.$ext = this.$int = document.createElement(this.localName); + if (this.getAttribute("type")) + this.$int.setAttribute("type", this.getAttribute("type")); + pHtmlNode.appendChild(this.$int); + } + }, true); +}).call(apf.XhtmlInputElement.prototype = new apf.AmlElement()); + +apf.xhtml.setElement("input", apf.XhtmlInputElement); + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/core/markup/xhtml/option.js)SIZE(1537)TIME(Mon, 10 Jan 2011 10:53:11 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + + +apf.XhtmlOptionElement = function(struct, tagName){ + this.$init(tagName || "option", apf.NODE_VISIBLE, struct); +}; + +(function(){ + this.addEventListener("DOMNodeInsertedIntoDocument", function(e){ + this.$ext = + this.$int = this.parentNode.$int.appendChild( + this.parentNode.$int.ownerDocument.createElement("option")); + + if (this.value) + this.$int.setAttribute("value", this.value); + }, true); +}).call(apf.XhtmlOptionElement.prototype = new apf.AmlElement()); + +apf.xhtml.setElement("option", apf.XhtmlOptionElement); + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/core/markup/xhtml/skipchildren.js)SIZE(2342)TIME(Mon, 10 Jan 2011 10:53:11 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + +apf.XhtmlSkipChildrenElement = function(struct, tagName){ + this.$init(tagName, apf.NODE_VISIBLE, struct); +}; + +(function(){ + this.canHaveChildren = false; + + this.$redraw = function(){ + var _self = this; + apf.queue.add("redraw" + this.$uniqueId, function(){ + var pHtmlNode = _self.$ext.parentNode; + var beforeNode = _self.$ext.nextSibling; + pHtmlNode.removeChild(_self.$ext); + + _self.$ext = apf.insertHtmlNode(null, pHtmlNode, beforeNode, _self.$aml + ? (_self.$aml.serialize ? _self.$aml.serialize() : _self.$aml.xml) + : _self.serialize()); + }); + } + + this.addEventListener("DOMNodeInsertedIntoDocument", function(e){ + var pHtmlNode; + if (!(pHtmlNode = this.parentNode.$int)) + return; + + this.$ext = apf.insertHtmlNode(null, pHtmlNode, null, this.$aml + ? (this.$aml.serialize ? this.$aml.serialize() : this.$aml.xml) + : this.serialize()); + }, true); +}).call(apf.XhtmlSkipChildrenElement.prototype = new apf.AmlElement()); + +apf.xhtml.setElement("object", apf.XhtmlSkipChildrenElement); +apf.xhtml.setElement("embed", apf.XhtmlSkipChildrenElement); +apf.xhtml.setElement("table", apf.XhtmlSkipChildrenElement); + +apf.xhtml.setElement("pre", apf.XhtmlSkipChildrenElement); + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/core/markup/xsd.js)SIZE(12998)TIME(Mon, 10 Jan 2011 10:53:11 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + + +/** + * Object creating the XML Schema namespace for the aml parser. + * + * @constructor + * @parser + * + * @allownode simpleType, complexType + * + * @author Ruben Daniels (ruben AT ajax DOT org) + * @version %I%, %G% + * @since 0.8 + */ +apf.xsd = new apf.AmlNamespace(); +apf.setNamespace("http://www.w3.org/2001/XMLSchema", apf.xsd); + +apf.xsd.typeHandlers = { + "http://www.w3.org/2001/XMLSchema" : { + //XSD datetypes [L10n potential] + "dateTime": function(value){ + value = value.replace(/-/g, "/"); + + value.match(/^(\d{2})\/(\d{2})\/(\d{4}) (\d{2}):(\d{2}):(\d{2})$/); + if (!RegExp.$3 || RegExp.$3.length < 4) + return false; + + var dt = new Date(value); + if (dt.getFullYear() != parseFloat(RegExp.$3)) + return false; + if (dt.getMonth() != parseFloat(RegExp.$2) - 1) + return false; + if (dt.getDate() != parseFloat(RegExp.$1)) + return false; + if (dt.getHours() != parseFloat(RegExp.$4)) + return false; + if (dt.getMinutes() != parseFloat(RegExp.$5)) + return false; + if (dt.getSeconds() != parseFloat(RegExp.$5)) + return false; + + return true; + }, + "time": function(value){ + value.match(/^(\d{2}):(\d{2}):(\d{2})$/); + + var dt = new Date("21/06/1980 " + value); + if (dt.getHours() != parseFloat(RegExp.$1)) + return false; + if (dt.getMinutes() != parseFloat(RegExp.$2)) + return false; + if (dt.getSeconds() != parseFloat(RegExp.$3)) + return false; + + return true; + }, + "date": function(value){ + value = value.replace(/-/g, "/"); + value.match(/^(\d{2})\/(\d{2})\/(\d{4})$/); + if (!RegExp.$3 || RegExp.$3.length < 4) + return false; + + //@todo this is a dutch date, localization... + var dt = new Date(RegExp.$2 + "/" + RegExp.$1 + "/" + RegExp.$3); + if (dt.getFullYear() != parseFloat(RegExp.$3)) + return false; + if (dt.getMonth() != parseFloat(RegExp.$2) - 1) + return false; + if (dt.getDate() != parseFloat(RegExp.$1)) + return false; + + return true; + }, + "gYearMonth": function(value){ + value = value.replace(/-/g, "/"); + value.match(/^\/?(\d{4})(?:\d\d)?\/(\d{2})(?:\w|[\+\-]\d{2}:\d{2})?$/); + if (!RegExp.$1 || RegExp.$1.length < 4) + return false; + + var dt = new Date(value); + if (dt.getFullYear() != parseFloat(RegExp.$)) + return false; + if (dt.getMonth() != parseFloat(RegExp.$2) - 1) + return false; + + return true; + }, + "gYear": function(value){ + value.match(/^\/?(\d{4})(?:\d\d)?(?:\w|[\+\-]\d{2}:\d{2})?$/); + if (!RegExp.$1 || RegExp.$1.length < 4) + return false; + + var dt = new Date(value); + if (dt.getFullYear() != parseFloat(RegExp.$1)) + return false; + + return true; + }, + "gMonthDay": function(value){ + value = value.replace(/-/g, "/"); + value.match(/^\/\/(\d{2})\/(\d{2})(?:\w|[\+\-]\d{2}:\d{2})?$/); + + var dt = new Date(value); + if (dt.getMonth() != parseFloat(RegExp.$1) - 1) + return false; + if (dt.getDate() != parseFloat(RegExp.$2)) + return false; + + return true; + }, + "gDay": function(value){ + value = value.replace(/-/g, "/"); + value.match(/^\/{3}(\d{2})(?:\w|[\+\-]\d{2}:\d{2})?$/); + + var dt = new Date(value); + if (dt.getDate() != parseFloat(RegExp.$1)) + return false; + + return true; + }, + "gMonth": function(value){ + value = value.replace(/-/g, "/"); + value.match(/^\/{2}(\d{2})(?:\w|[\+\-]\d{2}:\d{2})?$/); + + var dt = new Date(value); + if (dt.getMonth() != parseFloat(RegExp.$1) - 1) + return false; + + return true; + }, + + //XSD datetypes + "string": function(value){ + return typeof value == "string"; + }, + "boolean": function(value){ + return /^(true|false)$/i.test(value); + }, + "base64Binary": function(value){ + return true; + }, + "hexBinary": function(value){ + return /^(?:0x|x|#)?[A-F0-9]{0,8}$/i.test(value); + }, + "float": function(value){ + return parseFloat(value) == value; + }, + "decimal": function(value){ + return /^[0-9\.\-,]+$/.test(value); + }, + "double": function(value){ + return parseFloat(value) == value; + }, + "anyURI": function(value){ + return /^(?:\w+:\/\/)?(?:(?:[\w\-]+\.)+(?:[a-z]+)|(?:(?:1?\d?\d?|2[0-4]9|25[0-5])\.){3}(?:1?\d\d|2[0-4]9|25[0-5]))(?:\:\d+)?(?:\/([^\s\\\%]+|%[\da-f]{2})*)?$/i + .test(value); + }, + "QName": function(value){ + return true; + }, + "normalizedString": function(value){ + return true; + }, + "token": function(value){ + return true; + }, + "language": function(value){ + return true; + }, + "Name": function(value){ + return true; + }, + "NCName": function(value){ + return true; + }, + "ID": function(value){ + return true; + }, + "IDREF": function(value){ + return true; + }, + "IDREFS": function(value){ + return true; + }, + "NMTOKEN": function(value){ + return true; + }, + "NMTOKENS": function(value){ + return true; + }, + "integer": function(value){ + return parseInt(value) == value; + }, + "nonPositiveInteger": function(value){ + return parseInt(value) == value && value <= 0; + }, + "negativeInteger": function(value){ + return parseInt(value) == value && value < 0; + }, + "long": function(value){ + return parseInt(value) == value && value >= -2147483648 + && value <= 2147483647; + }, + "int": function(value){ + return parseInt(value) == value; + }, + "short": function(value){ + return parseInt(value) == value && value >= -32768 && value <= 32767; + }, + "byte": function(value){ + return parseInt(value) == value && value >= -128 && value <= 127; + }, + "nonNegativeInteger": function(value){ + return parseInt(value) == value && value >= 0; + }, + "unsignedLong": function(value){ + return parseInt(value) == value && value >= 0 && value <= 4294967295; + }, + "unsignedInt": function(value){ + return parseInt(value) == value && value >= 0; + }, + "unsignedShort": function(value){ + return parseInt(value) == value && value >= 0 && value <= 65535; + }, + "unsignedByte": function(value){ + return parseInt(value) == value && value >= 0 && value <= 255; + }, + "positiveInteger": function(value){ + return parseInt(value) == value && value > 0; + } + }, + + + + "http://ajax.org/2005/aml" : { + //Ajax.org Platform datatypes + "url": function(value){ + //@todo please make this better + return /\b(https?|ftp):\/\/([\-A-Z0-9.]+)(\/[\-A-Z0-9+&@#\/%=~_|!:,.;]*)?(\?[\-A-Z0-9+&@#\/%=~_|!:,.;]*)?/i.test(value.trim()); + }, + "website": function(value){ + //@todo please make this better + return /^(?:http:\/\/)?([\w-]+\.)+\w{2,4}$/.test(value.trim()); + }, + "email": function(value){ + // @see http://fightingforalostcause.net/misc/2006/compare-email-regex.php + return /^([\w\!\#$\%\&\'\*\+\-\/\=\?\^\`{\|\}\~]+\.)*[\w\!\#$\%\&\'\*\+\-\/\=\?\^\`{\|\}\~]+@((((([a-z0-9]{1}[a-z0-9\-]{0,62}[a-z0-9]{1})|[a-z])\.)+[a-z]{2,6})|(\d{1,3}\.){3}\d{1,3}(\:\d{1,5})?)$/i + .test(value.trim()); + }, + "creditcard": function(value){ + value = value.replace(/ /g, ""); + value = value.pad(21, "0", apf.PAD_LEFT); + for (var total = 0, r, i = value.length; i >= 0; i--) { + r = value.substr(i, 1) * (i % 2 + 1); + total += r > 9 ? r - 9 : r; + } + return total % 10 === 0; + }, + "expdate": function(value){ + value = value.replace(/-/g, "/"); + value = value.split("/");//.match(/(\d{2})\/(\d{2})/); + var dt = new Date(value[0] + "/01/" + value[1]); + //if(fulldate && dt.getFullYear() != parseFloat(value[1])) return false; + if (dt.getYear() != parseFloat(value[1])) + return false;//!fulldate && + if (dt.getMonth() != parseFloat(value[0]) - 1) + return false; + + return true; + }, + "wechars": function(value){ + return /^[0-9A-Za-z\xC0-\xCF\xD1-\xD6\xD8-\xDD\xDF-\xF6\xF8-\xFF -\.',]+$/ + .test(value) + }, + "phonenumber": function(value){ + return /^[\d\+\- \(\)]+$/.test(value) + }, + "faxnumber": function(value){ + return /^[\d\+\- \(\)]+$/.test(value) + }, + "mobile": function(value){ + return /^[\d\+\- \(\)]+$/.test(value) + } + } +}; + +apf.xsd.custumTypeHandlers = {}; + +apf.xsd.matchType = function(value, type){ + var split = type.split(":"), + prefix = split[0], + doc = apf.document, + ns = doc.$prefixes[prefix]; + type = split[1]; + if (prefix == "xsd") + ns = "http://www.w3.org/2001/XMLSchema"; + if (!ns) + ns = doc.namespaceURI || apf.ns.xhtml; + + var c = this.typeHandlers[ns]; + + //check if type is type + if (c && c[type]) + return c[type](value); + + throw new Error(apf.formatErrorString(0, null, + "Validating XSD Type", "Could not find type: " + type)); + + return true; +}; + +apf.xsd.checkType = function(type, xmlNode){ + var value = typeof xmlNode == "object" + ? apf.queryValue(xmlNode) + : xmlNode; + + if (type.indexOf(":") > -1) { + var split = type.split(":"), + prefix = split[0], + name = split[1], + doc = apf.document, + ns = doc.$prefixes[prefix]; + if (prefix == "xsd") + ns = "http://www.w3.org/2001/XMLSchema"; + if (!ns) + ns = doc.namespaceURI || apf.ns.xhtml; + + var c = this.typeHandlers[ns]; + if (c && c[name]) + return c[name](value); + } + + if (this.custumTypeHandlers[type]) { + return this.custumTypeHandlers[type](value); + } + else { + //@todo MIKE: to be implemented? + } +}; + + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/core/markup/xsd/element.js)SIZE(1869)TIME(Mon, 10 Jan 2011 10:53:11 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + +apf.XsdElement = function(struct, tagName){ + this.$init(true); + + this.addEventListener("DOMNodeInserted", function(){ + if (!this.parentNode.$compile) + this.$compile(); + }); + + this.addEventListener("DOMNodeRemoved", function(){ + if (!this.parentNode.$compile) + this.$compile(); + }); + + this.addEventListener("DOMNodeInsertedIntoDocument", function(e){ + if (this.parentNode.$compile) + return; + + var _self = this; + apf.queue.add("compile" + this.$uniqueId, function(){ + _self.$compile(); + }); + }); +} +apf.XsdElement.prototype = new apf.AmlElement(); +apf.XsdElement.prototype.$recompile = function(stack){ + if (!this.$amlLoaded) + return; + + if (this.parentNode.$recompile) + this.parentNode.$recompile(); + else + this.$compile(); +}; + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/core/markup/xsd/enumeration.js)SIZE(1844)TIME(Mon, 10 Jan 2011 10:53:11 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + +/** + * Defines a list of acceptable values + */ +apf.XsdEnumeration = function(struct, tagName){ + this.$init(tagName || "enumeration", apf.NODE_HIDDEN, struct); +}; + +(function(){ + this.$propHandlers["value"] = function(){ + this.parentNode.$recompile(); + }; + + this.$compile = function(stack){ + if (stack.enumDone) + return; + + stack.enumDone = true; + + var k, l, + re = [], + nodes = this.parentNode.getElementsByTagNameNS(this.namespaceURI, + "enumeration"); + for (k = 0, l = nodes.length; k < l; k++) + re.push(nodes[k].value); + + stack.push("if (!/^(?:" + re.join("|") + ")$/.test(value)) return false;"); + }; +}).call(apf.XsdEnumeration.prototype = new apf.XsdElement()); + +apf.xsd.setElement("enumeration", apf.XsdEnumeration); + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/core/markup/xsd/fractiondigits.js)SIZE(1620)TIME(Mon, 10 Jan 2011 10:53:11 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + +/** + * Specifies the maximum number of decimal places allowed. Must be equal to or greater than zero + */ +apf.XsdFractionDigits = function(struct, tagName){ + this.$init(tagName || "maxinclusive", apf.NODE_HIDDEN, struct); +}; + +(function(){ + this.$propHandlers["value"] = function(){ + this.parentNode.$recompile(); + }; + + this.$compile = function(stack){ + stack.push("if (parseFloat(value) == value && value.split('.')[1].length != " + + this.value + ") return false;"); + }; +}).call(apf.XsdFractionDigits.prototype = new apf.XsdElement()); + +apf.xsd.setElement("maxinclusive", apf.XsdFractionDigits); + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/core/markup/xsd/length.js)SIZE(1527)TIME(Mon, 10 Jan 2011 10:53:11 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + +/** + * Specifies the exact number of characters or list items allowed. Must be equal to or greater than zero + */ +apf.XsdLength = function(struct, tagName){ + this.$init(tagName || "length", apf.NODE_HIDDEN, struct); +}; + +(function(){ + this.$propHandlers["value"] = function(){ + this.parentNode.$recompile(); + }; + + this.$compile = function(stack){ + stack.push("if (value.length != " + this.value + ") return false;"); + }; +}).call(apf.XsdLength.prototype = new apf.XsdElement()); + +apf.xsd.setElement("length", apf.XsdLength); + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/core/markup/xsd/list.js)SIZE(1215)TIME(Mon, 10 Jan 2011 10:53:11 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + +apf.XsdList = function(struct, tagName){ + this.$init(tagName || "list", apf.NODE_HIDDEN, struct); +}; + +(function(){ + this.$compile = function(stack){}; +}).call(apf.XsdList.prototype = new apf.XsdElement()); + +apf.xsd.setElement("list", apf.XsdList); + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/core/markup/xsd/maxexclusive.js)SIZE(1553)TIME(Mon, 10 Jan 2011 10:53:11 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + +/** + * Specifies the upper bounds for numeric values (the value must be less than this value) + */ +apf.XsdMaxExclusive = function(struct, tagName){ + this.$init(tagName || "maxexclusive", apf.NODE_HIDDEN, struct); +}; + +(function(){ + this.$propHandlers["value"] = function(){ + this.parentNode.$recompile(); + }; + + this.$compile = function(stack){ + stack.push("if (parseFloat(value) =< " + this.value + ") return false;"); + }; +}).call(apf.XsdMaxExclusive.prototype = new apf.XsdElement()); + +apf.xsd.setElement("maxexclusive", apf.XsdMaxExclusive); + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/core/markup/xsd/maxinclusive.js)SIZE(1568)TIME(Mon, 10 Jan 2011 10:53:11 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + + +/** + * Specifies the upper bounds for numeric values (the value must be less than or equal to this value) + */ +apf.XsdMaxInclusive = function(struct, tagName){ + this.$init(tagName || "maxinclusive", apf.NODE_HIDDEN, struct); +}; + +(function(){ + this.$propHandlers["value"] = function(){ + this.parentNode.$recompile(); + }; + + this.$compile = function(stack){ + stack.push("if (parseFloat(value) > " + this.value + ") return false;"); + }; +}).call(apf.XsdMaxInclusive.prototype = new apf.XsdElement()); + +apf.xsd.setElement("maxinclusive", apf.XsdMaxInclusive); + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/core/markup/xsd/maxlength.js)SIZE(1597)TIME(Mon, 10 Jan 2011 10:53:11 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + +/** + * Specifies the maximum number of characters or list items allowed. Must be equal to or greater than zero + */ +apf.XsdMaxLength = function(struct, tagName){ + this.$init(tagName || "maxlength", apf.NODE_HIDDEN, struct); +}; + +(function(){ + this.$propHandlers["value"] = function(){ + this.parentNode.$recompile(); + }; + + //@todo This should also check for list items + this.$compile = function(stack){ + stack.push("if (value.length > " + this.value + ") return false;"); + }; +}).call(apf.XsdMaxLength.prototype = new apf.XsdElement()); + +apf.xsd.setElement("maxlength", apf.XsdMaxLength); + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/core/markup/xsd/maxscale.js)SIZE(1436)TIME(Mon, 10 Jan 2011 10:53:11 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + +apf.XsdMaxScale = function(struct, tagName){ + this.$init(tagName || "maxscale", apf.NODE_HIDDEN, struct); +}; + +(function(){ + this.$propHandlers["value"] = function(){ + this.parentNode.$recompile(); + }; + + //@todo http://www.w3.org/TR/2006/WD-xmlschema11-2-20060217/datatypes.html#element-maxScale + this.$compile = function(stack){}; +}).call(apf.XsdMaxScale.prototype = new apf.XsdElement()); + +apf.xsd.setElement("maxscale", apf.XsdMaxScale); + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/core/markup/xsd/minexclusive.js)SIZE(1556)TIME(Mon, 10 Jan 2011 10:53:11 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + +/** + * Specifies the lower bounds for numeric values (the value must be greater than this value) + */ +apf.XsdMinExclusive = function(struct, tagName){ + this.$init(tagName || "minexclusive", apf.NODE_HIDDEN, struct); +}; + +(function(){ + this.$propHandlers["value"] = function(){ + this.parentNode.$recompile(); + }; + + this.$compile = function(stack){ + stack.push("if (parseFloat(value) => " + this.value + ") return false;"); + }; +}).call(apf.XsdMinExclusive.prototype = new apf.XsdElement()); + +apf.xsd.setElement("minexclusive", apf.XsdMinExclusive); + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/core/markup/xsd/mininclusive.js)SIZE(1567)TIME(Mon, 10 Jan 2011 10:53:11 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + +/** + * Specifies the lower bounds for numeric values (the value must be greater than or equal to this value) + */ +apf.XsdMinInclusive = function(struct, tagName){ + this.$init(tagName || "mininclusive", apf.NODE_HIDDEN, struct); +}; + +(function(){ + this.$propHandlers["value"] = function(){ + this.parentNode.$recompile(); + }; + + this.$compile = function(stack){ + stack.push("if (parseFloat(value) < " + this.value + ") return false;"); + }; +}).call(apf.XsdMinInclusive.prototype = new apf.XsdElement()); + +apf.xsd.setElement("mininclusive", apf.XsdMinInclusive); + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/core/markup/xsd/minlength.js)SIZE(1610)TIME(Mon, 10 Jan 2011 10:53:11 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + +/** + * Specifies the minimum number of characters or list items allowed. Must be equal to or greater than zero + */ +apf.XsdMinLength = function(struct, tagName){ + this.$init(tagName || "minlength", apf.NODE_HIDDEN, struct); +}; + +(function(){ + this.$propHandlers["value"] = function(){ + this.parentNode.$recompile(); + }; + + //@todo This should also check for list items + this.$compile = function(stack){ + stack.push("if (value.length < " + this.value + + ") return false;"); + }; +}).call(apf.XsdMinLength.prototype = new apf.XsdElement()); + +apf.xsd.setElement("minlength", apf.XsdMinLength); + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/core/markup/xsd/minscale.js)SIZE(1436)TIME(Mon, 10 Jan 2011 10:53:11 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + +apf.XsdMinScale = function(struct, tagName){ + this.$init(tagName || "minscale", apf.NODE_HIDDEN, struct); +}; + +(function(){ + this.$propHandlers["value"] = function(){ + this.parentNode.$recompile(); + }; + + //@todo http://www.w3.org/TR/2006/WD-xmlschema11-2-20060217/datatypes.html#element-minScale + this.$compile = function(stack){}; +}).call(apf.XsdMinScale.prototype = new apf.XsdElement()); + +apf.xsd.setElement("minscale", apf.XsdMinScale); + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/core/markup/xsd/pattern.js)SIZE(1537)TIME(Mon, 10 Jan 2011 10:53:11 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + +/** + * Defines the exact sequence of characters that are acceptable + */ +apf.XsdPattern = function(struct, tagName){ + this.$init(tagName || "pattern", apf.NODE_HIDDEN, struct); +}; + +(function(){ + this.$propHandlers["value"] = function(){ + this.parentNode.$recompile(); + }; + + this.$compile = function(stack){ + stack.push("if (!/^" + this.value + .replace(/(\/|\^|\$)/g, "\\$1") + "$/.test(value)) return false;") + }; +}).call(apf.XsdPattern.prototype = new apf.XsdElement()); + +apf.xsd.setElement("pattern", apf.XsdPattern); + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/core/markup/xsd/restriction.js)SIZE(1644)TIME(Mon, 10 Jan 2011 10:53:11 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + +apf.XsdRestriction = function(struct, tagName){ + this.$init(tagName || "restriction", apf.NODE_HIDDEN, struct); +}; + +(function(){ + this.$propHandlers["base"] = function(){ + this.parentNode.$recompile(); + } + + this.$compile = function(stack){ + stack.push("if (!apf.xsd.matchType(value, '" + + this.base + "')) return false;"); + + var i, l, node, + nodes = this.childNodes; + for (i = 0, l = nodes.length; i < l; i++) + (node = nodes[i]).$compile && node.$compile(stack); + } +}).call(apf.XsdRestriction.prototype = new apf.XsdElement()); + +apf.xsd.setElement("restriction", apf.XsdRestriction); + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/core/markup/xsd/schema.js)SIZE(1124)TIME(Mon, 10 Jan 2011 10:53:11 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + +apf.XsdSchema = function(struct, tagName){ + this.$init(true); +}; + +apf.XsdSchema.prototype = new apf.XsdElement(); + +apf.xsd.setElement("schema", apf.XsdSchema); + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/core/markup/xsd/simpletype.js)SIZE(2201)TIME(Mon, 10 Jan 2011 10:53:11 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + +apf.XsdSimpleType = function(struct, tagName){ + this.$init(tagName || "simpletype", apf.NODE_HIDDEN, struct); + + var lastName; + this.$propHandlers["name"] = function(value){ + if (lastName) { + apf.xsd.custumTypeHandlers[value] = + apf.xsd.custumTypeHandlers[lastName] + apf.xsd.custumTypeHandlers[lastName] = null; + } + + lastName = value; + }; +}; + +(function(){ + this.$compile = function(stack){ + var i, l, nodes, node; + if (!this.parentNode.$compile) { + stack = []; + + nodes = this.childNodes; + for (i = 0, l = nodes.length; i < l; i++) + (node = nodes[i]).$compile && node.$compile(stack); + + stack.push("return true;"); + apf.xsd.custumTypeHandlers[this.name] = + new Function('value', stack.join("\n")); + } + else { + nodes = this.childNodes; + for (i = 0, l = nodes.length; i < l; i++) + (node = nodes[i]).$compile && node.$compile(stack); + } + }; +}).call(apf.XsdSimpleType.prototype = new apf.XsdElement()); + +apf.xsd.setElement("simpletype", apf.XsdSimpleType); + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/core/markup/xsd/totaldigits.js)SIZE(1564)TIME(Mon, 10 Jan 2011 10:53:11 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + +/** + * Specifies the exact number of digits allowed. Must be greater than zero + */ +apf.XsdTotalDigits = function(struct, tagName){ + this.$init(tagName || "totaldigits", apf.NODE_HIDDEN, struct); +}; + +(function(){ + this.$propHandlers["value"] = function(){ + this.parentNode.$recompile(); + }; + + this.$compile = function(stack){ + stack.push("if (new String(parseFloat(value)).length == " + + this.value + ") return false;"); + }; +}).call(apf.XsdTotalDigits.prototype = new apf.XsdElement()); + +apf.xsd.setElement("totaldigits", apf.XsdTotalDigits); + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/core/markup/xsd/union.js)SIZE(2331)TIME(Mon, 10 Jan 2011 10:53:11 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + +/* + + + + + + + + + + + + + + + + + + +*/ +apf.XsdUnion = function(struct, tagName){ + this.$init(tagName || "union", apf.NODE_HIDDEN, struct); +}; + +(function(){ + this.$propHandlers["memberTypes"] = function(value){ + this.$memberTypes = value.splitSafe(" "); + this.parentNode.$recompile(); + }; + + this.$compile = function(stack){ + var i, l, node, + nodes = this.childNodes; + for (i = 0, l = this.$memberTypes.length; i < l; i++) { + stack.push("if (apf.xsd.checkType('" + + this.$memberTypes[i] + "', value)) return true;"); + } + + for (i = 0, l = nodes.length; i < l; i++) + (node = nodes[i]).$compile && node.$compile(stack); + } +}).call(apf.XsdUnion.prototype = new apf.XsdElement()); + +apf.xsd.setElement("union", apf.XsdUnion); + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/core/markup/html5.js)SIZE(3232)TIME(Mon, 10 Jan 2011 10:53:11 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + + +/** + * Object creating the HTML5 namespace for the aml parser. + * + * @constructor + * @parser + * + * @author Ruben Daniels (ruben AT ajax DOT org) + * @version %I%, %G% + * @since 0.8 + */ +apf.html5 = new apf.AmlNamespace(); +apf.setNamespace("", apf.html5); + + + +/** + * @define input + * Remarks: + * Ajax.org Platform supports the input types specified by the WHATWG html5 spec. + * @attribute {String} type the type of input element. + * Possible values: + * email provides a way to enter an email address. + * url provides a way to enter a url. + * password provides a way to enter a password. + * datetime provides a way to pick a date and time. + * date provides a way to pick a date. + * month provides a way to pick a month. + * week provides a way to pick a week. + * time provides a way to pick a time. + * number provides a way to pick a number. + * range provides a way to select a point in a range. + * checkbox provides a way to set a boolean value. + * radio used in a set, it provides a way to select a single value from multiple options. + * file provides a way to upload a file. + * submit provides a way to submit data. + * image provides a way to submit data displaying an image instead of a button. + * reset provides a way to reset entered data. + * @addnode elements + */ +/** + * @private + */ +apf.HTML5INPUT = { + "email" : "textbox", + "url" : "textbox", + "password" : "textbox", + "datetime" : "spinner", //@todo + "date" : "calendar", + "month" : "spinner", //@todo + "week" : "spinner", //@todo + "time" : "spinner", //@todo + "number" : "spinner", + "range" : "slider", + "checkbox" : "checkbox", + "radio" : "radiobutton", + "file" : "upload", + "submit" : "submit", + "image" : "submit", + "reset" : "button" +}; + +/* way to implement this: +var o = (new function(){ +}) +o.constructor.prototype = new apf.list(); + +*/ + + + +/* #-ifdef __WITH_HTML5 +if (tagName == "input") { + objName = apf.HTML5INPUT[objName = x.getAttribute("type")] + || objName || "textbox"; +} +//#-endif*/ + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/core/markup/xforms.js)SIZE(4191)TIME(Mon, 10 Jan 2011 10:53:11 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + + + + +//XForms + + + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/core/markup/xinclude.js)SIZE(1325)TIME(Mon, 10 Jan 2011 10:53:11 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + + +/** + * Object creating the XML Include namespace for the aml parser. + * + * @constructor + * @parser + * + * @allownode simpleType, complexType + * + * @author Ruben Daniels (ruben AT ajax DOT org) + * @version %I%, %G% + * @since 0.8 + */ +apf.xinclude = new apf.AmlNamespace(); +apf.setNamespace("http://www.w3.org/2001/XInclude", apf.xinclude); + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/core/markup/xinclude/fallback.js)SIZE(1322)TIME(Mon, 10 Jan 2011 10:53:11 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + +/** + * Defines a list of acceptable values + */ +apf.XiFallback = function(struct, tagName){ + this.$init(tagName || "fallback", apf.NODE_HIDDEN, struct); +}; + +apf.XiFallback.prototype = new apf.AmlElement(); +apf.XiFallback.prototype.$parsePrio = "002"; + +apf.xinclude.setElement("fallback", apf.XiFallback); +apf.aml.setElement("fallback", apf.XiFallback); + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/core/markup/xinclude/include.js)SIZE(7025)TIME(Mon, 10 Jan 2011 10:53:11 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + +/** + * Defines a list of acceptable values + */ +apf.XiInclude = function(struct, tagName){ + this.$init(tagName || "include", apf.NODE_HIDDEN, struct); +}; + +apf.xinclude.setElement("include", apf.XiInclude); +apf.aml.setElement("include", apf.XiInclude); + +//@todo test defer="true" situation +(function(){ + this.$parsePrio = "002"; + + //1 = force no bind rule, 2 = force bind rule + /*this.$attrExcludePropBind = apf.extend({ + href : 1, + src : 1 + }, this.$attrExcludePropBind);*/ + + this.$propHandlers["href"] = + this.$propHandlers["src"] = function(value){ + if (typeof value != "string") + return finish.call(this, value); + + if (value.trim().charAt(0) == "<") { + loadIncludeFile.call(this, value.trim()); + return; + } + + this.$path = value.charAt(0) == "{" //@todo this shouldn't happen anymore + ? value + : apf.getAbsolutePath(apf.hostPath, value); + + var domParser = this.ownerDocument.$domParser; + if (!this.defer) { + domParser.$shouldWait++; + this.$parseContext = domParser.$parseContext || [this.parentNode]; + } + + //var basePath = apf.hostPath;//only for recursion: apf.getDirname(xmlNode.getAttribute("filename")) || + loadIncludeFile.call(this, this.$path); + }; + + function done(xmlNode) { + if (this.callback) { + this.callback({ + xmlNode : xmlNode, + amlNode : this.parentNode, + addedNode: this.previousSibling || this.nextSibling + }) + } + + //@todo hack!! this should never happen. Find out why it happens + if (this.parentNode) + this.parentNode.removeChild(this); + } + + function finish(xmlNode){ + var domParser = this.ownerDocument.$domParser; + + if (this.clear) + this.parentNode.$int.innerHTML = ""; + + if (xmlNode) { + domParser.parseFromXml(xmlNode, { + doc : this.ownerDocument, + amlNode : this.parentNode, + beforeNode : this, + include : true + }); + + if (!this.defer && this.$parseContext) { + var o = (this.$parseContext[1] || (this.$parseContext[1] = {})), + cb = o.callback, + _self = this; + + o.callback = function(){ + done.call(_self, xmlNode); + if (cb) + cb.call(_self.ownerDocument); + }; + + //@todo this is wrong... probably based on load order of last include element. Please rearchitect parse continuation. + if (domParser.$continueParsing.apply(domParser, this.$parseContext) === false) { + var o2 = (domParser.$parseContext[1] || (domParser.$parseContext[1] = {})), + cb2 = o.callback; + o2.callback = function(){ + if (cb) + cb.call(_self.ownerDocument); + domParser.$continueParsing.apply(domParser, _self.$parseContext); + }; + } + } + else + done.call(this, xmlNode); + } + else { + if (!this.defer) + domParser.$continueParsing.apply(domParser, this.$parseContext); + + done.call(this, xmlNode); + } + } + + function loadIncludeFile(path){ + + apf.console.info("Loading include file: " + path); + + + var _self = this; + apf.getData(path, apf.extend(this.options || {}, { + + type : "markup", + + callback : function(xmlString, state, extra){ + if (state != apf.SUCCESS) { + var oError = new Error(apf.formatErrorString(1007, + _self, "Loading Includes", "Could not load Include file '" + + (path || _self.src) + + "'\nReason: " + extra.message)); + + if (extra.tpModule.retryTimeout(extra, state, null, oError) === true) + return true; + + apf.console.error(oError.message); + + finish.call(_self, null); + + //throw oError; + return; + } + + //@todo apf3.0 please make one way of doing this + xmlString = xmlString.replace(/\<\!DOCTYPE[^>]*>/, "") + .replace(/^[\r\n\s]*/, ""); //.replace(/ /g, " ") + if (!apf.supportNamespaces) + xmlString = xmlString.replace(/xmlns\=\"[^"]*\"/g, ""); + + if (xmlString.indexOf("" + + xmlString + ""; + + var xmlNode = apf.getXml(xmlString, null, true);//apf.getAmlDocFromString(xmlString); + + if (!xmlNode) { + throw new Error(apf.formatErrorString(0, null, + "Loading include", + "Could not parse include file. Maybe the file does not exist?", xmlNode)); + } + xmlNode.setAttribute("filename", extra.url); + + + apf.console.info("Loading of " + xmlNode[apf.TAGNAME].toLowerCase() + " include done from file: " + extra.url); + + + finish.call(_self, xmlNode); //@todo add recursive includes support here + }, + async : true, + ignoreOffline : true + })); + } +}).call(apf.XiInclude.prototype = new apf.AmlElement()); + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/core/markup/xslt/xslt.js)SIZE(13722)TIME(Mon, 10 Jan 2011 10:53:11 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + +/** + * @private + */ +apf.runXslt = function(){ + /** + * @constructor + * @parser + */ + apf.XSLTProcessor = function(){ + this.templates = {}; + this.p = { + "value-of": function(context, xslNode, childStack, result){ + var value, + xmlNode = apf.XPath.selectNodes(xslNode.getAttribute("select"), + context)[0];// + "[0]" + if (!xmlNode) { + value = ""; + } + else { + if (xmlNode.nodeType == 1) + value = xmlNode.firstChild ? xmlNode.firstChild.nodeValue : ""; + else + value = typeof xmlNode == "object" ? xmlNode.nodeValue : xmlNode; + } + + result.appendChild(this.xmlDoc.createTextNode(value)); + }, + + "copy-of": function(context, xslNode, childStack, result){ + var xmlNode = apf.XPath.selectNodes(xslNode.getAttribute("select"), + context)[0];// + "[0]" + if (xmlNode) + result.appendChild(apf.canImportNode + ? result.ownerDocument.importNode(xmlNode, true) + : xmlNode.cloneNode(true)); + }, + + "if": function(context, xslNode, childStack, result){ + if (apf.XPath.selectNodes(xslNode.getAttribute("test"), context)[0])// + "[0]" + this.parseChildren(context, xslNode, childStack, result); + }, + + "for-each": function(context, xslNode, childStack, result){ + var i, l, + nodes = apf.XPath.selectNodes(xslNode.getAttribute("select"), context); + for (i = 0, l = nodes.length; i < l; i++) + this.parseChildren(nodes[i], xslNode, childStack, result); + }, + + "choose": function(context, xslNode, childStack, result){ + var i, l, + nodes = xslNode.childNodes; + for (i = 0, l = nodes.length; i < l; i++) { + if (!nodes[i].tagName) + continue; + + if (nodes[i][apf.TAGNAME] == "otherwise" + || nodes[i][apf.TAGNAME] == "when" + && apf.XPath.selectNodes(nodes[i].getAttribute("test"), context)[0]) + return this.parseChildren(context, nodes[i], childStack[i][2], result); + } + }, + + "output": function(context, xslNode, childStack, result){}, + + "param": function(context, xslNode, childStack, result){}, + + "attribute": function(context, xslNode, childStack, result){ + var nres = this.xmlDoc.createDocumentFragment(); + this.parseChildren(context, xslNode, childStack, nres); + + result.setAttribute(xslNode.getAttribute("name"), nres.xml); + }, + + "apply-templates": function(context, xslNode, childStack, result){ + var t, i, l, nodes; + if (!xslNode) { + t = this.templates["/"] || this.templates[context.tagName]; + if (t) { + this.parseChildren(t == this.templates["/"] + ? context.ownerDocument : context, t[0], t[1], result); + } + } + else { + if (xslNode.getAttribute("select")) { + t = this.templates[xslNode.getAttribute("select")]; + if (t) { + if (xslNode.getAttribute("select") == "/") + return alert("Something went wrong. The / template was executed as a normal template"); + + nodes = context.selectNodes(xslNode.getAttribute("select")); + for (i = 0, l = nodes.length; i < l; i++) + this.parseChildren(nodes[i], t[0], t[1], result); + } + } + //Named templates should be in a different hash + else { + if (xslNode.getAttribute("name")) { + t = this.templates[xslNode.getAttribute("name")]; + if (t) + this.parseChildren(context, t[0], t[1], result); + } + else { + //Copy context + var ncontext = context.cloneNode(true), //importnode here?? + nres = this.xmlDoc.createDocumentFragment(), + tName, p, k; + + nodes = ncontext.childNodes; + for (i = nodes.length - 1; i >= 0; i--) { + if (nodes[i].nodeType == 3 || nodes[i].nodeType == 4) { + //result.appendChild(this.xmlDoc.createTextNode(nodes[i].nodeValue)); + continue; + } + if (!nodes[i].nodeType == 1) + continue; + var n = nodes[i]; + + //Loop through all templates + for (tName in this.templates) { + if (tName == "/") + continue; + t = this.templates[tName]; + + var snodes = n.selectNodes("self::" + tName); + for (var j = snodes.length - 1; j >= 0; j--) { + var s = snodes[j]; + p = s.parentNode; + this.parseChildren(s, t[0], t[1], nres); + if (nres.childNodes) { + for (k = nres.childNodes.length - 1; k >= 0; k--) + p.insertBefore(nres.childNodes[k], s); + } + p.removeChild(s); + } + } + + if (n.parentNode) { + p = n.parentNode; + this.p["apply-templates"].call(this, n, xslNode, childStack, nres); + if (nres.childNodes) { + for (k = nres.childNodes.length - 1; k >= 0; k--) + p.insertBefore(nres.childNodes[k], n); + } + p.removeChild(n); + } + } + + for (i = ncontext.childNodes.length - 1; i >= 0; i--) + result.insertBefore(ncontext.childNodes[i], result.firstChild); + } + } + } + }, + + cache : {}, + "import": function(context, xslNode, childStack, result){ + var file = xslNode.getAttribute("href"); + if (!this.cache[file]) { + var data = apf.oHttp.get(file); + this.cache[file] = data; + } + //compile + //parseChildren + }, + + "include" : function(context, xslNode, childStack, result){}, + + "when" : function(){}, + "otherwise": function(){}, + + "copy-clone": function(context, xslNode, childStack, result){ + result = result.appendChild(apf.canImportNode + ? result.ownerDocument.importNode(xslNode, false) + : xslNode.cloneNode(false)); + if (result.nodeType == 1) { + for (var value, i = 0, l = result.attributes.length; i < l; i++) { + var blah = result.attributes[i].nodeValue; //stupid Safari shit + if (!apf.isSafariOld && result.attributes[i].nodeName.match(/^xmlns/)) + continue; + result.attributes[i].nodeValue = result.attributes[i].nodeValue + .replace(/\{([^\}]+)\}/g, function(m, xpath){ + var xmlNode = apf.XPath.selectNodes(xpath, context)[0]; + + if (!xmlNode) { + value = ""; + } + else { + if (xmlNode.nodeType == 1) { + value = xmlNode.firstChild + ? xmlNode.firstChild.nodeValue + : ""; + } + else { + value = typeof xmlNode == "object" + ? xmlNode.nodeValue + : xmlNode; + } + } + + return value; + }); + + result.attributes[i].nodeValue; //stupid Safari shit + } + } + + this.parseChildren(context, xslNode, childStack, result); + } + } + + this.parseChildren = function(context, xslNode, childStack, result){ + if (!childStack) return; + for (var i = 0, l = childStack.length; i < l; i++) { + childStack[i][0].call(this, context, childStack[i][1], + childStack[i][2], result); + } + }; + + this.compile = function(xslNode){ + var nodes = xslNode.childNodes; + for (var stack = [], i = 0, l = nodes.length; i < l; i++) { + if (nodes[i].nodeType != 1 && nodes[i].nodeType != 3 && nodes[i].nodeType != 4) + continue; + + if (nodes[i][apf.TAGNAME] == "template") { + this.templates[nodes[i].getAttribute("match") + || nodes[i].getAttribute("name")] = [nodes[i], this.compile(nodes[i])]; + } + else { + if (nodes[i][apf.TAGNAME] == "stylesheet") { + this.compile(nodes[i]) + } + else { + if (nodes[i].prefix == "xsl") { + var func = this.p[nodes[i][apf.TAGNAME]]; + if (!func) + alert("xsl:" + nodes[i][apf.TAGNAME] + " is not supported at this time on this platform"); + else + stack.push([func, nodes[i], this.compile(nodes[i])]); + } + else { + stack.push([this.p["copy-clone"], nodes[i], this.compile(nodes[i])]); + } + } + } + } + return stack; + }; + + this.importStylesheet = function(xslDoc){ + this.xslDoc = xslDoc.nodeType == 9 ? xslDoc.documentElement : xslDoc; + var xslStack = this.compile(xslDoc); + + //var t = this.templates["/"] ? "/" : false; + //if(!t) for(t in this.templates) if(typeof this.templates[t] == "array") break; + this.xslStack = [[this.p["apply-templates"], null]];//{getAttribute : function(n){if(n=="name") return t} + }; + + //return nodes + this.transformToFragment = function(doc, newDoc){ + //new DOMParser().parseFromString("", "text/xml");// + this.xmlDoc = newDoc.nodeType != 9 ? newDoc.ownerDocument : newDoc; + var docfrag = this.xmlDoc.createDocumentFragment(); + + if (!apf.isSafariOld && doc.nodeType == 9) + doc = doc.documentElement; + var result = this.parseChildren(doc, this.xslDoc, this.xslStack, docfrag); + return docfrag; + }; + }; + + self.XSLTProcessor = apf.XSLTProcessor; +}; + + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/core/baseclasses/liveedit.js)SIZE(34022)TIME(Mon, 10 Jan 2011 10:53:11 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ +apf.__LIVEEDIT__ = 1 << 23; + + + +/** + * Baseclass of an element whose content is editable. This is usually an + * {@link element.editor}. + * + * @constructor + * @baseclass + * + * @inherits apf.Presentation + * @inherits apf.StandardBinding + * @inherits apf.DataAction + * + * @author Mike de Boer (mike AT javeline DOT com) + * @version %I%, %G% + * @since 3.0 + */ +apf.LiveEdit = function() { + this.$regbase = this.$regbase | apf.__LIVEEDIT__; + + + this.implement(apf.DataAction || apf.K); + + + this.implement(apf.Validation || apf.K); + + + this.$activeDocument = document; + this.$selection = null; + this.$activeNode = null; + this.$lastActiveNode = null; + this.$activeIndex = 0; + + this.$supportedProperties.push("liveedit", "realtime"); + this.$booleanProperties["liveedit"] = true; + this.$booleanProperties["realtime"] = true; + + this.$propHandlers["liveedit"] = function(value){ + if (this.childNodes.length && !this.hasFeature(apf.__CHILDVALUE__)) //@todo rearch this + return; + + if (this.realtime == undefined) + this.$setInheritedAttribute("realtime"); + + if (value) { + if (!this.$focussable || !this.focussable) { + this.$focussable = true; + this.$unFocussable = [this.$focussable, + typeof this.focussable == "undefined" ? true : this.focussable]; + apf.GuiElement.propHandlers.focussable.call(this, true); + } + + if (!this.$leMouseOver) + this.$setMouseEvents(); + + apf.addListener(this.$ext, "mouseup", this.$leMouseUp); + apf.addListener(this.$ext, "mousedown", this.$leMouseDown); + apf.addListener(this.$ext, "mouseout", this.$leMouseOut); + apf.addListener(this.$ext, "mouseover", this.$leMouseOver); + + this.addEventListener("keydown", keydownHandler, true); + this.addEventListener("blur", blurHandler); + this.addEventListener("focus", focusHandler); + this.addEventListener("load", loadHandler); + + if (this.nodeType == 7) + this.addEventListener("prop.calcdata", xmlupdateHandler); + else if (this.hasFeature(apf.__CHILDVALUE__)) + this.addEventListener("prop." + this.$childProperty, xmlupdateHandler); + else + this.addEventListener("xmlupdate", xmlupdateHandler); + } + else if (this.$leMouseOver) { + if (this.$unFocussable) { + this.$focussable = this.$unFocussable[0]; + apf.GuiElement.propHandlers.focussable.call(this, this.$unFocussable[1]); + delete this.$unFocussable; + } + + apf.removeListener(this.$ext, "mouseover", this.$leMouseOver); + apf.removeListener(this.$ext, "mouseout", this.$leMouseOut); + apf.removeListener(this.$ext, "mousedown", this.$leMouseDown); + apf.removeListener(this.$ext, "mouseup", this.$leMouseUp); + + this.$activeNode = null; + this.$lastActiveNode = null; + + this.removeEventListener("keydown", keydownHandler, true); + this.removeEventListener("blur", blurHandler); + this.removeEventListener("focus", focusHandler); + this.removeEventListener("load", loadHandler); + + if (this.nodeType == 7) + this.removeEventListener("prop.calcdata", xmlupdateHandler); + else if (this.hasFeature(apf.__CHILDVALUE__)) + this.removeEventListener("prop." + this.$childProperty, xmlupdateHandler); + else + this.removeEventListener("xmlupdate", xmlupdateHandler); + } + + this.$tabStack = null; // redraw of editable region, invalidate cache + }; + + this.$setMouseEvents = function(){ + var _self = this, moEditor; + + function focusHandler(){ + moEditor.removeEventListener("focus", focusHandler); + apf.removeListener(moEditor.$ext, "mouseout", extOutHandler); + moEditor = null; + } + + function removeHandler(){ + apf.removeListener(moEditor.$ext, "mouseout", extOutHandler); + if (_self.$lastEditor && _self.$lastEditor[0] == moEditor) + removeEditor.call(_self, _self.$activeNode, true); + moEditor.removeEventListener("focus", focusHandler); + moEditor = null; + } + + function extOutHandler(){ + if (!_self.$lastEditor) { + apf.removeListener(moEditor.$ext, "mouseout", extOutHandler); + return; + } + + if (moEditor && !moEditor.hasFocus()) + removeHandler.call(moEditor); + } + + this.$leMouseOver = function(e) { + var el = e.srcElement || e.target; + if (!el) return; + while (el && (!el.className || el.className.indexOf("liveEdit") == -1) && el != _self.$ext) + el = el.parentNode; + if (!el || el == _self.$ext || el == _self.$activeNode) + return; + + if (moEditor) + removeHandler.call(moEditor); + + var editor = el.getAttribute("editor"); + if (editor && "richtext|default".indexOf(editor) == -1 && !_self.$activeNode) { + createEditor.call(_self, el); + moEditor = _self.$lastEditor[0]; + moEditor.addEventListener("focus", focusHandler); + apf.addListener(moEditor.$ext, "mouseout", extOutHandler); + } + else + apf.setStyleClass(el, "liveEditOver"); + }; + this.$leMouseOut = function(e) { + var el = e.srcElement || e.target; + if (!el) return; + while (el && (!el.className || el.className.indexOf("liveEdit") == -1) && el != _self.$ext) + el = el.parentNode; + if (!el || el == _self.$ext || el == _self.$activeNode) + return; + + apf.setStyleClass(el, null, ["liveEditOver"]); + }; + this.$leMouseDown = function(e, preventPropagation) { + if (!preventPropagation) + apf.cancelBubble(e); + + var el = e.srcElement || e.target; + if (!el) return; + if (_self.$activeNode && _self.$selection && apf.isChildOf(_self.$activeNode, el, true)) + $setTimeout(function(){_self.$selection.cache();}); + + while (el && (!el.className || el.className.indexOf("liveEdit") == -1) && el != _self.$ext) + el = el.parentNode; + + if (!el || el == _self.$ext) { + if (_self.$activeNode) + removeEditor.call(_self, _self.$activeNode, true); + return; + } + + createEditor.call(_self, el); + if (!_self.$lastEditor && !preventPropagation) { + e.cancelBubble = true; + apf.window.$mousedown({srcElement: _self.$activeNode}); + $setTimeout(function(){ + //_self.$selection.set(); + if (_self.$activeNode) + _self.$activeNode.focus(); + }, 10); + } + else { + /*$setTimeout(function(){ + _self.$lastEditor[0].focus(); + }, 100);*/ + //_self.$lastEditor.childNodes[0].slideDown(); + } + + return false; + }; + this.$leMouseUp = function(e) { + var el = e.srcElement || e.target; + if (_self.$activeNode && _self.$selection && apf.isChildOf(_self.$activeNode, el, true)) + _self.$selection.cache(); + }; + } + + function focusHandler(e){ + if (this.$skipFocusOnce && !(this.$skipFocusOnce = false)) { + this.$lastActiveNode = null; + return; + } + + this.setProperty("state", (this.$pluginsActive == "code") + ? apf.DISABLED + : apf.OFF); + + if (this.$lastActiveNode && this.$lastActiveNode.parentNode + || typeof e.shiftKey == "boolean") { + createEditor.call(this, this.$lastActiveNode || (this.$tabStack + || initTabStack.call(this))[e.shiftKey ? this.$tabStack.length - 1 : 0]); + + if (this.$lastActiveNode && !this.$lastEditor) + this.$lastActiveNode.focus(); + } + this.$lastActiveNode = null; + + if (this.$lastEditor) { + //this.$lastEditor[0].focus(); + } + else if (this.$activeNode) { + var _self = this, + node = this.$activeNode; + $setTimeout(function(){ + //this.$selection.selectNode(node); + _self.$selection.set(); + if (node.parentNode) //@todo why? + node.focus(); + }, 10); + } + } + + function blurHandler(e){ + this.$lastActiveNode = this.$activeNode || this.$lastActiveNode; + //@todo should be recursive in refactor + if (e.toElement && (e.toElement == this.$docklet + || e.toElement.parentNode == this.$lastEditor)) + return; + + var pParent = apf.popup.last && apf.lookup(apf.popup.last); + if (pParent && pParent.editor == this) + apf.popup.forceHide(); + + if (this.$selection) + this.$selection.cache(); + removeEditor.call(this, this.$activeNode, true); + + this.setProperty("state", apf.DISABLED); + } + + function loadHandler(){ + createEditor.call(this, initTabStack.call(this)[0]); + } + + function xmlupdateHandler(){ + if (!this.$skipChange) { + initTabStack.call(this); + //this.$activeNode = null; + } + } + //@todo skin change + + var commandKeys = { + 66 : 1, // B + 98 : 1, // b + 105 : 1, // i + 73 : 1, // I + 117 : 1, // u + 85 : 1 // U + }; + + /** + * Event handler; fired when the user pressed a key inside the editor IFRAME. + * For IE, we apply some necessary behavior correction and for other browsers, like + * Firefox and Safari, we enable some of the missing default keyboard shortcuts. + * + * @param {Event} e + * @type {Boolean} + * @private + */ + function keydownHandler(e) { + //if (!this.$bStandalone) + //return; + + var oNode, isDone, found, + code = e.which || e.keyCode; + + if (!this.$activeNode) { + //F2 starts editing + if (code == 113) { + if (this.$selected) { + var nodes = this.$selected.getElementsByTagName("*"); + for (var i = nodes.length - 1; i >= 0; i--) { + if ((nodes[i].className || "").indexOf("liveEdit") > -1) { + oNode = nodes[i]; + break; + } + } + } + + createEditor.call(this, oNode || (this.$tabStack || initTabStack.call(this))[0]); + if (this.$activeNode) { + this.$activeNode.focus(); + this.$selection.selectNode(this.$activeNode); + this.$selection.collapse(); + try { + this.$activeDocument.execCommand("SelectAll", false, true); + } + catch(e) {} + } + } + return; + } + + if (code == 13) { //Enter + isDone = e.ctrlKey || (apf.isMac && e.metaKey); + if (!isDone) + isDone = !apf.isTrue(this.$activeNode.getAttribute("multiline")); + e.returnValue = true; + } + + this.$visualFocus(); + + // Tab navigation handling + if (code == 9 || isDone) { + if (!this.$tabStack) + initTabStack.call(this); + var idx, + bShift = e.shiftKey; + oNode = this.$activeNode; + removeEditor.call(this, this.$activeNode, true) || initTabStack.call(this)[this.$activeIndex]; + oNode = this.$tabStack[this.$activeIndex + (bShift ? -1 : 1)]; + + if (oNode) { + createEditor.call(this, oNode); + if (this.$lastEditor) { + this.$lastEditor[0].focus(); + } + else if (oNode.parentNode) { //this.$lastEditor + oNode.focus(); + try { + //this.$selection.selectNode(oNode.firstChild); + this.$activeDocument.execCommand("SelectAll", false, true); + } + catch(e) {} + } + found = true; + } + } + // Esc key handling + else if (code == 27) { + removeEditor.call(this); + found = true; + } + else if (apf.isIE && e.ctrlKey && commandKeys[code]) { + found = true; + } + + if (!e.ctrlKey && !e.altKey && !e.metaKey && (code < 112 || code > 122) + && (code < 33 && code > 31 || code > 42 || code == 8 || code == 13)) { + if (this.realtime && this.$changeTimer === null && this.$bStandalone) { //@todo realtime not supported for liveedit inline + var _self = this; + this.$changeTimer = $setTimeout(function() { + clearTimeout(_self.$changeTimer); + + + _self.change(_self.getValue()); + + + _self.$changeTimer = null; + }, 200); + } + + // remove the content of a selection manually when it's ranging + // multiple DOM nodes + + + if (apf.w3cRange && !this.$selection.isCollapsed() && apf.isCharacter(code)) { + if (this.$activeNode.innerHTML.trim() != '') + this.$selection.remove(); + //else + //debugger; + } + } + + if (found) + return apf.stopEvent(e); + else if (this.$activeNode) + e.returnValue = -1; //@todo what is this for? + } + + function createEditor(oHtml) { + if (!oHtml || oHtml.nodeType != 1 || this.$activeNode == oHtml || !this.xmlRoot) + return; + + var _self = this; + var hasRule = getOptions(oHtml); + var rule = hasRule && apf.unserialize(hasRule.replace(/"/g, '"').unescapeHTML()) || {}; + oHtml.setAttribute("multiline", rule.multiline || "false"); //For lookup at keydown 13 + + //Initialize selection + if (!this.$selection) + this.$selection = new apf.selection(); + + if (this.$activeNode) { + var lastPos = initTabStack.call(this).indexOf(oHtml);//tabStack can be old... + removeEditor.call(this, this.$activeNode, true); + oHtml = initTabStack.call(this)[lastPos]; + $setTimeout(function(){oHtml.focus();}, 10); + } + + //Check Validation of previous element + /*if (this.validityState && !this.validityState.valid) { + oHtml = initTabStack.call(this)[this.validityState.$lastPos]; + $setTimeout(function(){ + oHtml.focus(); + _self.$selection.selectNode(oHtml); + // @todo need to select all contents here? + _self.$selection.collapse(); + try { + _self.$activeDocument.execCommand("SelectAll", false, true); + } + catch(e) {} + _self.getModel(true).validate(xmlNode, false, _self.validityState, _self); + }, 10); + }*/ + + //@todo this fucks undo state - elements created here are not undone + //var xmlNode = apf.createNodeFromXpath(this.xmlRoot.ownerDocument, + // oHtml.getAttribute("xpath")); + + //@todo dirty hack, how to solve this properly + if (this.hasFocus && !this.hasFocus()) + this.$skipFocusOnce = true; + + this.$activeNode = oHtml; + this.$activeIndex = (this.$tabStack || initTabStack.call(this)).indexOf(oHtml); + apf.setStyleClass(oHtml, "liveEditActive", ["liveEditOver", "liveEditInitial"]); + + if (oHtml.innerHTML == rule.initial) + oHtml.innerHTML = ""; + + this.$lastValue = oHtml.innerHTML; + + var handler = this.$editors[rule.editor || "default"] || this.$editors.custom; + handler.create.call(this, oHtml, rule, getXpath(oHtml)); + + + if (apf.hasFocusBug) { + //@todo this leaks like a -> use apf.addListener + apf.sanitizeTextbox(oHtml); + } + + + this.dispatchEvent("$createEditor", rule); + } + + function getXpath(node){ + return node.getAttribute("xpath").replace(/\\([\[\{\]\}])/g, "$1"); + } + + function getOptions(node){ + return (node.getAttribute("options") || "").replace(/\\([\[\{\]\}])/g, "$1"); + } + + function removeEditor(oHtml, bProcess, callback) { + if (!oHtml) oHtml = this.$activeNode; + if (!oHtml || oHtml.nodeType != 1) return false; + + var xpath = getXpath(oHtml), + xmlNode = this.xmlRoot.ownerDocument.selectSingleNode(xpath), + hasRule = getOptions(oHtml), + rule = hasRule && apf.unserialize(hasRule.replace(/"/g, '"').unescapeHTML()) || {}; + + this.$activeNode = null; + apf.setStyleClass(oHtml, null, ["liveEditOver", "liveEditActive"]); + this.$selection.collapse(true); + + var handler = this.$editors[rule.editor || "default"] || this.$editors.custom; + var execChange = handler.remove.call(this, oHtml, rule, xpath); + + if (execChange !== false) { + if (!bProcess || this.$lastValue && oHtml.innerHTML.toLowerCase().replace(/[\r\n]/g, "") + == (this.$lastValue.dataType == apf.ARRAY + ? this.$lastValue[0] : this.$lastValue).toLowerCase().replace(/[\r\n]/g, "")) { + oHtml.innerHTML = this.$lastValue.dataType == apf.ARRAY + ? this.$lastValue[1] + : this.$lastValue; + } + else { + // do additional handling, first we check for a change in the data... + // @todo this will not always work in IE + // @todo this is bullshit, because it tests on unparsed data --> incorrect + if (apf.queryValue(this.xmlRoot.ownerDocument, xpath) != oHtml.innerHTML) { + rule.htmlNode = oHtml; + var res = this.getValue(oHtml); + + var valid; + + if (this.validityState) + this.validityState.$reset(); + + if (hasRule) { + apf.validator.compile(rule).call(this, + res, false, this.validityState || + (this.validityState = new apf.validator.validityState())); + + this.invalidmsg = valid ? "" : rule.invalidmsg || ""; + + valid = this.validityState.valid; + if (!valid) { + this.validityState.$errorHtml = oHtml; + this.dispatchEvent("invalid", this.validityState); + this.setError(); + } + else + this.clearError(); + } + + + if (valid !== false) { + this.$executeAction("setValueByXpath", + [this.xmlRoot, res, xpath], "setValueByXpath", xmlNode); + } + } + } + + if (!oHtml.innerHTML && rule.initial) { + oHtml.innerHTML = rule.initial; + apf.setStyleClass(oHtml, "liveEditInitial"); + } + } + + if (callback) + $setTimeout(callback); + + this.dispatchEvent("$removeEditor", rule); + } + + this.$editors = { + "default" : { + create : function(oHtml, rule){ + this.getValue = function(){ + var html = oHtml.innerHTML.replace(//g, "\n"); + if (apf.isIE) { + html = html.replace(/]*>/gi, "") + .replace(/<\/P>/g, "") + .replace(/<\/p>/g, "\n"); + } + + return apf.html_entity_decode(html); + }; + + if (apf.hasContentEditable) + oHtml.contentEditable = true; + else { + document.body.setAttribute("spellcheck", "false"); + document.designMode = "on"; + } + + if (apf.isGecko) { + try { + // On each return, insert a BR element + document.execCommand("insertBrOnReturn", false, true); + } + catch(e){} + } + }, + remove : function(oHtml, rule){ + oHtml.blur(); + if (apf.hasContentEditable) + oHtml.contentEditable = false; + else + document.designMode = "off"; + } + }, + + /** + * @todo for template + * - Focus handling enters at the first and leaves at the last both entry/leave + * are from/to parent element + * - Parent focus/blur events aren't called ($focus / $blur should use event system) + * - Keyboard events for leave (both ends) are forwarded to parent + * - Attach manages height of container + * - Attach code / Detach code + */ + "custom" : { + create : function(oHtml, rule, xpath){ + //Set window container to notify that this element will contain sub aml element. + if (this.$isWindowContainer != -1) { + this.$isWindowContainer = -1; + apf.window.$removeFocus(this); + apf.window.$addFocus(this, this.tabindex); + } + + delete this.getValue; + + //@todo cache it + oHtml.innerHTML = ""; + + var editParent = oHtml; + var oEditor, editor = rule.editor; + if (editor.charAt(0) == "<") { + //template from aml + } + //Lookup + else if (self[editor]) { + oEditor = self[editor]; + oHtml.appendChild(oEditor.$ext); + } + //Reference to class + else if (apf.namespaces[apf.ns.aml].elements[editor]) { + var cacheId = apf.serialize(rule); //Using full element definition as cache id. @todo when should I clear this cache? - at least at destroy - same for propedit/datagrid + if (!this.$editorCache) + this.$editorCache = {}; + + if (!this.$editorCache[cacheId]) { + if (!this.id) + apf.setReference(this.id = (this.localName || "liveedit") + String(this.$uniqueId), this); + + var _self = this; + var constr = apf.namespaces[apf.ns.aml].elements[editor]; + var isTextbox = "textarea|textbox|secret".indexOf(editor) > -1; + var info = apf.extend({ + htmlNode : editParent, + //width : "100%+2", + //height : 19, + //display:inline-block; + style : "position:relative;", //z-index:10000 + value : "[{" + this.id + ".root}::" + xpath + "]", + focussable : false, + realtime : "{" + this.id + ".realtime}", + onbeforeselect : function(){ + _self.$skipChange = true; + }, + onafterselect : function(){ + delete _self.$skipChange; + }, + onkeydown : function(e){ + keydownHandler.call(_self, e); + } + }, rule); + if (info.initial) { + info["initial-message"] = info.initial; + delete info.initial; + } + + if (isTextbox) { + info.focusselect = true; + info.onkeydown = function(e){ + if (e.keyCode == 13) + this.change(this.getValue()); + } + } + else if (editor == "checkbox" && !info.values) + info.values = "true|false"; + + //@todo copy all non-known properties of the prop element + + /*if (constr.prototype.hasFeature(apf.__MULTISELECT__)) { + info.caption = "[text()]"; + info.eachvalue = "[@value]"; + info.each = "item"; + info.model = "{apf.xmldb.getElementById('" + + prop.getAttribute(apf.xmldb.xmlIdTag) + "')}"; + }*/ + + oEditor = this.$editorCache[cacheId] = new constr(info); + + /*var box = apf.getBox(apf.getStyle(oEditor.$ext, "margin")); + if (box[1] || box[3]) { + oEditor.setAttribute("width", "100%+2-" + (box[1] + box[3])); + } + else if (!box[3]) + oEditor.$ext.style.marginLeft = "-1px";*/ + + //oEditor.$focussable = false; + /* + oEditor.addEventListener("focus", function(){ + _self.focus(); + this.$focus(); + });*/ + /*oEditor.addEventListener("blur", function(){ + hideEditor.call(_self); + });*/ + oEditor.parentNode = this; + oEditor.$focusParent = this; + oEditor.setAttribute("focussable", "true"); + //delete oEditor.parentNode; + + //@todo set actiontracker + oEditor.$parentId = editParent.getAttribute("id"); + oEditor.$parentRsz = editParent.onresize; + + //Patch oEditor to forward change + oEditor.$executeAction = function(atAction, args, action, xmlNode, noevent, contextNode, multiple){ + if (atAction == "setAttribute" && !args[2]) + atAction = "removeAttribute"; + + this.parentNode.$executeAction.call(this.parentNode, + atAction, args, action, xmlNode, noevent, contextNode, multiple); + } + } + else { + oEditor = this.$editorCache[cacheId]; + + /*if (oEditor.hasFeature(apf.__MULTISELECT__)) { + oEditor.setAttribute("model", "{apf.xmldb.getElementById('" + + prop.getAttribute(apf.xmldb.xmlIdTag) + "')}"); + }*/ + + oEditor.setAttribute("value", + "[{" + this.id + ".root}::" + xpath + "]"); + + oEditor.setProperty("visible", true); + if (oEditor.$ext.parentNode + && oEditor.$ext.parentNode.nodeType == 1 + && !apf.hasSingleResizeEvent) { + if (!oEditor.$parentRsz) + oEditor.$parentRsz = oEditor.$ext.parentNode.onresize; + oEditor.$ext.parentNode.removeAttribute("id"); + delete oEditor.$ext.parentNode.onresize; + } + + editParent.appendChild(oEditor.$ext); + if (editParent.$parentId) + editParent.setAttribute("id", editParent.$parentId); + if (oEditor.$parentRsz && !apf.hasSingleResizeEvent) { + editParent.onresize = oEditor.$parentRsz; + editParent.onresize(); + } + } + + /*setTimeout(function(){ + oEditor.focus(); + });*/ + } + else { + throw new Error("Unkown editor: " + editor); + } + + this.$lastEditor = [oEditor] + + /*onkeydown = function(e){ + if (e.keyCode == 9) { + e.currentTarget = null; + _self.dispatchEvent("keydown", e, true); + return false; + } + };*/ + }, + remove : function(oHtml, rule, xpath){ + var el = this.$lastEditor[0].$ext; + if (el.parentNode) + el.parentNode.removeChild(el); + + delete this.$lastEditor; + oHtml.innerHTML = apf.queryValue(this.xmlRoot.ownerDocument, xpath);//this.$lastValue; + + this.focus(); + + return false; + } + } + }; + + this.isValid = function(checkRequired){ + //@todo only place for checkRequired + return true; + }; + + function initTabStack() { + this.$tabStack = []; + var aNodes = this.$ext.getElementsByTagName("*"); + for (var i = 0, l = aNodes.length; i < l && aNodes[i].nodeType == 1; i++) { + if (aNodes[i].className + && aNodes[i].className.indexOf("liveEdit") != -1) { + this.$tabStack.push(aNodes[i]); + } + } + return this.$tabStack; + } + + /** + * Give or return the focus to the editable area, hence 'visual' focus. + * + * @param {Boolean} bNotify Flag set to TRUE if plugins should be notified of this event + * @type {void} + */ + this.$visualFocus = function(bNotify) { + // setting focus to the iframe content, upsets the 'code' plugin + var bCode = (this.$pluginsActive == "code"); + if (this.$bStandalone && apf.document.activeElement == this && !bCode) { + try { + this.$oWin.focus(); + } + catch(e) {}; + } + else if (!this.$bStandalone && this.$activeNode) { + this.$activeNode.focus(); + } + + if (bCode) { + this.$notifyAllButtons(apf.DISABLED); + this.$notifyButton("code", apf.SELECTED); + } + else if (bNotify) + this.$notifyAllButtons(); + }; + + + apf.LiveEdit.richtext.call(this); + +}; + +apf.config.$inheritProperties["liveedit"] = 2; + +apf.LiveEdit.mousedown = function(oHtml, event){ + if (oHtml.$ext) { + var amlNode = apf.findHost(oHtml.$ext); + var lm = amlNode.ownerDocument.$parentNode; + if (!lm.hasFocus()) + lm.focus(); + lm.$leMouseDown(event, true); + apf.stopPropagation(event); + } +} + + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/core/baseclasses/liveedit/richtext.js)SIZE(53610)TIME(Mon, 10 Jan 2011 10:53:11 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + + +apf.LiveEdit.richtext = function(){ + this.$state = apf.ON; + this.$buttons = {}; + this.$classToolbar = "editor_Toolbar"; + this.$plugins = {}; + this.$pluginsHooks = {}; + this.$pluginsTypes = {}; + this.$pluginsKeys = []; + this.$pluginsActive = null; + this.$pluginsOn = ["pasteword", "tablewizard"];//, "spell"]; + this.$nativeCommands = ["bold", "italic", "underline", "strikethrough", + "justifyleft", "justifycenter", "justifyright", + "justifyfull", "removeformat", "cut", "copy", + "paste", "outdent", "indent", "undo", "redo"]; + this.$changeTimer = null; + this.$toolbar = null; + + // List of punctuation characters from + // http://www.adobe.com/livedocs/coldfusion/5.0/Using_ColdFusion_Studio/language5.htm + // ! ' # S % & ' ( ) * + , - . / : ; < = > ? @ [ / ] ^ _ { | } ~ + var rePunctuation = /[\n\t!`#%&'"\(\)\*\.+-\.\/\:;<=>\?@\[\\\]\^_\{\|\}\~]+/g; + + this.$supportedProperties.push("state", "plugins", "language"); + + this.$propHandlers["state"] = function(value){ + this.state = parseInt(value); // make sure it's an int + // the state has changed, update the button look/ feel + var _self = this; + $setTimeout(function() { + _self.$notifyAllButtons(value); + if (_self.$pluginsActive == "code") + _self.$notifyButton("code", apf.SELECTED); + }); + }; + + this.$propHandlers["plugins"] = function(value){ + this.$pluginsOn = value && value.splitSafe(",") || null; + if (this.$pluginsOn && this.$pluginsOn.length) { + for (var i = 0, l = this.$pluginsOn.length; i < l; i++) + this.$addPlugin(this.$pluginsOn[i]); + } + }; + + this.$propHandlers["language"] = function(value){ + // @todo implement realtime language switching + }; + + this.addEventListener("prop.liveedit", function(e){ + if (e.value) { + if (this.plugins == undefined && this.$propHandlers["plugins"]) + this.$propHandlers["plugins"].call(this, this.$pluginsOn.join(",")); + } + else { + if (this.$docklet) + this.$docklet.hide(); + } + }); + + /** + * Event handler; fired when the user releases a key inside the editable area + * + * @see object.abstractevent + * @param {Event} e + * @type {void} + * @private + */ + function keyupHandler(e) { + var _self = this; + //if (!this.$bStandalone) + // return; + if (this.$selection) + this.$selection.cache(); + if (this.$keyupTimer) + return; + + function keyupHandler() { + clearTimeout(_self.$keyupTimer); + _self.$keyupTimer = null; + if (_self.state == apf.DISABLED) return; + _self.$notifyAllButtons(); + _self.dispatchEvent("typing", {editor: _self, event: e}); + _self.$notifyAllPlugins("typing", e.keyCode); + } + + this.$keyupTimer = window.setTimeout(keyupHandler, 100); + } + + function keydownHandler(e) { + //if (!this.$bStandalone) + //return; + + //@todo check the current field to see if it supports richtext + var found, + code = e.which || e.keyCode; + + if (apf.isIE) { + switch (code) { + case 66: // B + case 98: // b + case 105: // i + case 73: // I + case 117: // u + case 85: // U + //case 86: // V |_ See this.$paste() + //case 118: // v | event handler... + if ((e.ctrlKey || (apf.isMac && e.metaKey)) && !e.shiftKey + && !e.altKey && this.realtime) + + this.change(this.getValue()); + + break; + case 8: // backspace + found = false; + if (this.$selection.getType() == "Control") { + this.$selection.remove(); + found = true; + } + listBehavior.call(this, e, true); //correct lists, if any + if (found) + return false; + break; + case 46: + listBehavior.call(this, e, true); //correct lists, if any + break; + case 9: // tab + if (listBehavior.call(this, e)) + return false; + break; + } + } + else { + this.$visualFocus(); + if ((e.ctrlKey || (apf.isMac && e.metaKey)) && !e.shiftKey && !e.altKey) { + found = false; + switch (code) { + case 66: // B + case 98: // b + this.$execCommand("Bold"); + found = true; + break; + case 105: // i + case 73: // I + this.$execCommand("Italic"); + found = true; + break; + case 117: // u + case 85: // U + this.$execCommand("Underline"); + found = true; + break; + case 86: // V + case 118: // v + if (!apf.isGecko) + this.$paste(); + //found = true; + break; + } + if (found) { + apf.stopEvent(e.htmlEvent || e); + if (this.realtime && this.$bStandalone) + + this.change(this.getValue()); + + } + } + var sel, r, r2, m; + // gecko cannot collapse the selection with the arrow keys. Please, don't ask me why... + if (apf.isGecko && (code == 37 || code == 39) && !e.shiftKey && !e.metaKey && !e.ctrlKey && !this.$selection.isCollapsed()) { + // get current range + sel = this.$selection.get() + r = this.$selection.getRange(); + r2 = r.cloneRange(); + var _self = this; + // we need to use a timeout here because a direct collapse() leaves + // rendering artifacts. + setTimeout(function() { + sel.removeAllRanges(); + sel.addRange(r2); + _self.$selection.collapse((code == 37)); + }, 20); + } + // in webkit, first word cannot be reached by ctrl+left + else if (apf.isWebkit && code == 37 && (e.ctrlKey || (apf.isMac && e.altKey)) && !e.shiftKey) { + // get current range + sel = this.$selection.get() + r = this.$selection.getRange(); + // get first word from node: + r2 = r.cloneRange(); + r2.setStart(r2.commonAncestorContainer, 0); + m = r2.toString().match(/[\w]*(:?\W)?/g); + if (m && m.length == 2 && !m[1]) { //only one word left + r2.setEnd(r2.commonAncestorContainer, 0); + sel.removeAllRanges(); + sel.addRange(r2); + } + } + // in gecko, the last word cannot be reached by ctrl+right (strangely + // enough this does not occur on mac - prolly 'cause it uses the alt-key) + else if (apf.isGecko && code == 39 && !apf.isMac && e.ctrlKey && !e.altKey) { + // get current range + sel = this.$selection.get() + r = this.$selection.getRange(); + // get first word from node: + r2 = r.cloneRange(); + if (r2.commonAncestorContainer.nodeType == 3) { + // set the end of the range to match the end of the string of the + // active textNode inside the active node. + var s = r2.commonAncestorContainer.textContent.replace(rePunctuation, ""), + l = s.length; + r2.setEnd(r2.commonAncestorContainer, l); + m = r2.toString().match(/[\w]*(:?\W)?/g); + if (m && m.length == 2 && !m[1]) { //only one word left + r2.setEnd(r2.commonAncestorContainer, l); + r2.setStart(r2.commonAncestorContainer, l); + sel.removeAllRanges(); + sel.addRange(r2); + } + } + } + } + + this.$visualFocus(); + if (e.metaKey || e.ctrlKey || e.altKey || e.shiftKey) { + found = this.$notifyKeyBindings({ + code : code, + control: e.ctrlKey, + alt : e.altKey, + shift : e.shiftKey, + meta : e.metaKey + }); + } + + // Tab navigation handling + if (code == 9) { + if (listBehavior.call(this, e.htmlEvent || e)) + return apf.stopEvent(e.htmlEvent || e); + } + else if (code == 8 || code == 46) { //backspace or del + listBehavior.call(this, e.htmlEvent || e, true); //correct lists, if any + } + + if (found) + return apf.stopEvent(e); + }; + + /** + * Turns an object of mapped keystrokes to a numeric representation. + * Since ASCII numbers of character codes don't go above 255, we start + * with 256 for modifier keys and shift bits around to the left until we + * get a unique hash for each key combination. + * + * @param {Object} keyMap + * @type {Number} + * @private + */ + function createKeyHash(keyMap) { + return (keyMap.meta ? 2048 : 0) | (keyMap.control ? 1024 : 0) + | (keyMap.alt ? 512 : 0) | (keyMap.shift ? 256 : 0) + | (keyMap.key || "").charCodeAt(0); + } + + /** + * Transform the state of a button node to "enabled" + * + * @type {void} + * @private + */ + function buttonEnable() { + apf.setStyleClass(this, "editor_enabled", + ["editor_selected", "editor_disabled"]); + this.disabled = false; + } + + /** + * Transform the state of a button node to "disabled" + * + * @type {void} + * @private + */ + function buttonDisable() { + apf.setStyleClass(this, "editor_disabled", + ["editor_selected", "editor_enabled"]); + this.disabled = true; + } + + /** + * Retrieve the state of a command and if the command is a plugin, retrieve + * the state of the plugin + * + * @param {String} id + * @param {Boolean} isPlugin + * @return The command state as an integer that maps to one of the editor state constants + * @type {Number} + * @private + */ + function getState(id, isPlugin) { + if (isPlugin) { + var plugin = this.$plugins[id]; + if (this.state == apf.DISABLED && !plugin.noDisable) + return apf.DISABLED; + return plugin.queryState + ? plugin.queryState(this) + : this.state; + } + + if (this.state == apf.DISABLED) + return apf.DISABLED; + + return this.$queryCommandState(id); + } + + /** + * Corrects the default/ standard behavior of list elements (<ul> and + * <ol> HTML nodes) to match the general user experience match with + * M$ Office Word. + * + * @param {Event} e + * @param {Boolean} bFix Flag set to TRUE if you want to correct list indentation + * @type Boolean + * @private + */ + function listBehavior(e, bFix) { + if (!this.$plugins["bullist"] || !this.$plugins["numlist"]) + return false; + if (typeof e.shift != "undefined") + e.shiftKey = e.shift; + var pList = this.$plugins["bullist"].queryState(this) == apf.ON + ? this.$plugins["bullist"] + : this.$plugins["numlist"].queryState(this) == apf.ON + ? this.$plugins["numlist"] + : null; + if (!pList) return false; + if (bFix === true) + pList.correctLists(this); + else + pList.correctIndentation(this, e.shiftKey ? "outdent" : "indent"); + + return true; + } + + /** + * Get the state of a command (on, off or disabled) + * + * @param {String} name + * @type Number + */ + this.$queryCommandState = function(name) { + if (apf.isGecko && (name == "paste" || name == "copy" || name == "cut")) + return apf.DISABLED; + try { + if (!this.$activeDocument.queryCommandEnabled(name)) + return apf.DISABLED; + else + return this.$activeDocument.queryCommandState(name) + ? apf.ON + : apf.OFF; + } + catch (e) { + return apf.OFF; + } + }; + + /** + * Get the value of a command + * + * @param {String} name + * @type Number + */ + this.$queryCommandValue = function(name) { + var val; + if (typeof this.$activeDocument.queryCommand != "function") + return null; + try { + val = this.$activeDocument.queryCommand(name); + } + catch (e) {} + + return val || null; + }; + + /** + * Issue a command to the editable area. + * + * @param {String} name + * @param {mixed} param + * @type {void} + */ + this.$execCommand = function(name, param) { + if (this.$plugins[name] || this.state == apf.DISABLED) + return; + + this.$selection.set(); + + this.$visualFocus(); + + if (name.toLowerCase() == "removeformat") { + var c = this.$selection.getContent(), + disallowed = {FONT: 1, SPAN: 1, H1: 1, H2: 1, H3: 1, H4: 1, + H5: 1, H6: 1, PRE: 1, ADDRESS: 1, BLOCKQUOTE: 1, STRONG: 1, + B: 1, U: 1, I: 1, EM: 1, LI: 1, OL: 1, UL: 1, DD: 1, DL: 1, + DT: 1}; + c = c.replace(/<\/?(\w+)(?:\s.*?|)>/g, function(m, tag) { + return !disallowed[tag] ? m : ""; + }); + if (apf.isIE) { + var htmlNode = this.$selection.setContent("
" + c + + "
"); + this.$selection.selectNode(htmlNode); + htmlNode.removeNode(false); + return; + } + else { + this.$selection.setContent(c); + } + } + + this.$activeDocument.execCommand(name, false, param); + + // make sure that the command didn't leave any

tags behind (cleanup) + name = name.toLowerCase(); + var bNoSel = (name == "selectall"); + if (apf.isIE) { + if ((name == "insertunorderedlist" || name == "insertorderedlist") + && this.$queryCommandState(name) == apf.OFF) { + bNoSel = true; + } + else if (name == "outdent") { + bNoSel = true; + if (this.$plugins["bullist"] && this.$plugins["numlist"]) { + if (this.$plugins["bullist"].queryState(this) != apf.OFF + && this.$plugins["numlist"].queryState(this) != apf.OFF) + bNoSel = false; + } + var oNode = this.$selection.getSelectedNode(); + if (bNoSel && oNode && oNode.tagName == "BLOCKQUOTE") + bNoSel = false; + } + + if (bNoSel) { + var el = this.$bStandalone + ? this.$activeDocument.body + : this.$activeNode; + + el.innerHTML = apf.htmlCleaner.prepare(el.innerHTML); + + this.$controlAgentBehavior(el); + } + var r = this.$selection.getRange(); + if (r) + r.scrollIntoView(); + } + + this.$notifyAllButtons(); + + if (this.$bStandalone) { + + this.change(this.getValue()); + + } + + var _self = this; + $setTimeout(function() { + //_self.$notifyAllButtons(); // @todo This causes pain, find out why + if (!bNoSel) + _self.$selection.set(); + if (apf.isIE) { + _self.focus(); + $setTimeout(function() {_self.$visualFocus();}); + } + else + _self.$visualFocus(); + + _self.dispatchEvent("execcommand", {name: name, param: param}); + }); + }; + + /** + * Add a plugin to the collection IF an implementation actually exists. + * + * @param {String} sPlugin The plugin identifier/ name + * @type {apf.LiveEdit.plugin} + */ + this.$addPlugin = function(sPlugin) { + if (this.$plugins[sPlugin]) + return this.$plugins[sPlugin]; + if (!apf.LiveEdit.plugin[sPlugin]) return null; + // yay, plugin does exist, so we can instantiate it for the editor + var plugin = new apf.LiveEdit.plugin[sPlugin](sPlugin); + // add it to main plugin collection + this.$plugins[plugin.name] = plugin; + + if (plugin.type) { + // a type prop is set, push it up the type-collection + if (!this.$pluginsTypes[plugin.type]) + this.$pluginsTypes[plugin.type] = []; + this.$pluginsTypes[plugin.type].push(plugin); + } + if (plugin.subType) { + // a subType prop is set, push it up the type-collection + if (!this.$pluginsTypes[plugin.subType]) + this.$pluginsTypes[plugin.subType] = []; + this.$pluginsTypes[plugin.subType].push(plugin); + } + if (plugin.hook) { + // a hook prop is set, push it up the event hooks-collection + plugin.hook = plugin.hook.toLowerCase(); + if (!this.$pluginsHooks[plugin.hook]) + this.$pluginsHooks[plugin.hook] = []; + this.$pluginsHooks[plugin.hook].push(plugin); + } + + if (typeof plugin.keyBinding == "string") { + // a keyBinding prop has been set, parse it and push it up the + // keys-collection + plugin.keyBinding = { + meta : (plugin.keyBinding.indexOf("meta") > -1), + control: (plugin.keyBinding.indexOf("ctrl") > -1), + alt : (plugin.keyBinding.indexOf("alt") > -1), + shift : (plugin.keyBinding.indexOf("shift") > -1), + key : plugin.keyBinding.charAt(plugin.keyBinding.length - 1).toLowerCase() + }; + plugin.keyHash = createKeyHash(plugin.keyBinding); + if (!this.$pluginsKeys[plugin.keyHash]) + this.$pluginsKeys[plugin.keyHash] = []; + this.$pluginsKeys[plugin.keyHash].push(plugin); + } + return plugin; + }; + + /** + * Notify a plugin of any occuring Event, if it has subscribed to it + * + * @param {String} name + * @param {String} hook + * @type {mixed} + */ + this.$notifyPlugin = function(name, hook) { + var item = this.$plugins[name]; + if (item && item.hook == hook && !item.busy) + return item.execute(this, arguments); + return null; + }; + + /** + * Notify all plugins of an occuring Event + * + * @param {String} hook + * @param {Event} e + * @type {Array} + */ + this.$notifyAllPlugins = function(hook, e) { + var res = []; + if (!this.$pluginsHooks) + return res; + + var coll = this.$pluginsHooks[hook]; + for (var i in coll) { + if (!coll[i].busy && coll[i].execute) + res.push(coll[i].execute(this, e)); + } + //this.active = res.length ? res : res[0]; + return res; + }; + + /** + * Notify all plugins of an occuring keyboard Event with a certain key combo + * + * @param {Object} keyMap + * @type {Array} + */ + this.$notifyKeyBindings = function(keyMap) { + var hash = createKeyHash(keyMap); + if (!this.$pluginsKeys[hash] || !this.$pluginsKeys[hash].length) + return false; + + var coll = this.$pluginsKeys[hash]; + for (var i = 0, j = coll.length; i < j; i++) + coll[i].execute(this, arguments); + //this.active = coll.length ? coll : coll[0]; + return true; + }; + + /** + * Notify a specific button item on state changes (on, off, disabled, visible or hidden) + * + * @param {String} item + * @param {Number} state Optional. + * @type {void} + */ + this.$notifyButton = function(item, state) { + if (!this.$plugins) //We're in the process of being destroyed + return; + + var oButton = this.$buttons[item]; + if (!oButton) + return; + + var oPlugin = this.$plugins[item]; + if (typeof state == "undefined" || state === null) { + if (oPlugin && oPlugin.queryState) + state = oPlugin.queryState(this); + else + state = this.$queryCommandState(item); + } + + if (oButton.state === state) + return; + + oButton.state = state; + + if (state == apf.DISABLED) + buttonDisable.call(oButton); + else if (state == apf.HIDDEN) + oButton.style.display = "none"; + else if (state == apf.VISIBLE) + oButton.style.display = ""; + else { + if (oButton.style.display == "none") + oButton.style.display = ""; + + if (oButton.disabled) + buttonEnable.call(oButton); + + var btnState = (oButton.selected) + ? apf.ON + : apf.OFF; + + if (state != btnState) { + this.$buttonClick({ + state : state, + isPlugin: oPlugin ? true : false, + _bogus : true + }, oButton); + } + } + }; + + /** + * Notify all button items on state changes (on, off or disabled) + * + * @param {Number} state Optional. + * @type {void} + */ + this.$notifyAllButtons = function(state) { + var item, o = this.$buttons; + for (item in o) + this.$notifyButton(item, state); + }; + + this.$restoreFocus = function(bWithSel) { + // for now, only IE needs this workaround to return the focus to the + // liveEdit area - whilst preserving the correct state. + if (apf.isIE) { + var _self = this; + $setTimeout(function() { + if (bWithSel) + _self.$selection.set(); + _self.focus(); + $setTimeout(function() {_self.$visualFocus();}); + }); + } + }; + + /** + * Handler function; invoked when a toolbar button node was clicked + * + * @see object.abstractevent + * @param {Event} e + * @param {DOMElement} oButton + * @type {void} + */ + this.$buttonClick = function(e, oButton, userAction) { + if (userAction && this.disabled) + return; + + if (this.$selection) + this.$selection.cache(); + + apf.setStyleClass(oButton, "active"); + var item = oButton.getAttribute("type"); + + //context 'this' is the buttons' DIV domNode reference + if (!e._bogus) { + e.isPlugin = this.$plugins[item] ? true : false; + e.state = getState.call(this, item, e.isPlugin); + } + + if (e.state == apf.DISABLED) { + buttonDisable.call(oButton); + } + else { + if (this.disabled) + buttonEnable.call(oButton); + + if (e.state == apf.ON) { + apf.setStyleClass(oButton, "editor_selected"); + oButton.selected = true; + } + else { + apf.setStyleClass(oButton, "", ["editor_selected"]); + oButton.selected = false; + } + + if (!e._bogus) { + if (e.isPlugin) + this.$plugins[(this.$pluginsActive = item)].execute(this); + else + this.$execCommand(item); + e.state = getState.call(this, item, e.isPlugin); + } + } + apf.setStyleClass(oButton, "", ["active"]); + }; + + /** + * Draw all HTML elements for the editor toolbar + * + * @param {HTMLElement} oParent DOM element which the toolbars should be inserted into + * @param {String} [sSkinTag] Tagname of a toolbar node inside the editor skin definition + * @param {String} [sBtnClick] JS that will be executed when a button node is clicked + * @type {void} + */ + this.$drawToolbars = function(oParent, sSkinTag, sBtnClick, bAfterRender) { + var tb, l, k, i, j, z, x, node, buttons, bIsPlugin, item, bNode, + plugin, oButton, + sButton = this.$bStandalone ? "button" : "toolbarbutton", + oNode = this.$docklet.$getOption("toolbars"), + plugins = this.$plugins; + + if (!sSkinTag) + sSkinTag = "toolbar"; + + for (i = 0, l = oNode.childNodes.length; i < l; i++) { + node = oNode.childNodes[i]; + if (node.nodeType != 1 || node[apf.TAGNAME] != sSkinTag) + continue; + + + /*if (node[apf.TAGNAME] != "toolbar") { + throw new Error(apf.formatErrorString(0, this, + "Creating toolbars", + "Invalid element found in toolbars definition", + node)); + }*/ + + + for (j = 0, k = node.childNodes.length; j < k; j++) { + bNode = node.childNodes[j]; + + + if (bNode.nodeType != 3 && bNode.nodeType != 4) { + throw new Error(apf.formatErrorString(0, this, + "Creating toolbars", + "Invalid element found in toolbar definition", + bNode)); + } + + + buttons = bNode.nodeValue.splitSafe(",", -1, true); + } + + if (!buttons || !buttons.length) + continue; + + this.$docklet.$getNewContext("toolbar"); + tb = bAfterRender + ? apf.insertHtmlNode(this.$docklet.$getLayoutNode("toolbar"), oParent, oParent.firstChild) + : oParent.insertBefore(this.$docklet.$getLayoutNode("toolbar"), oParent.firstChild);//, oParent.lastChild + + for (z = 0, x = buttons.length; z < x; z++) { + item = buttons[z]; + + if (item == "|") { //seperator! + this.$docklet.$getNewContext("divider"); + if (bAfterRender) + apf.insertHtmlNode(this.$docklet.$getLayoutNode("divider"), tb); + else + tb.appendChild(this.$docklet.$getLayoutNode("divider")); + } + else { + this.$docklet.$getNewContext(sButton); + oButton = bAfterRender + ? oButton = apf.insertHtmlNode(this.$docklet.$getLayoutNode(sButton), tb) + : oButton = tb.appendChild(this.$docklet.$getLayoutNode(sButton)); + + bIsPlugin = false; + // Plugin toolbarbuttons may only be placed inside the main toolbar + if (sSkinTag == "toolbar" && !this.$nativeCommands.contains(item)) { + plugin = this.$addPlugin(item); + + if (!plugin) + apf.console.error("Plugin '" + item + "' can not " + + "be found and/ or instantiated.", + "editor"); + + bIsPlugin = true; + } + + if (bIsPlugin) { + plugin = plugin || plugins[item]; + if (!plugin) + continue; + if (!(plugin.type & apf.TOOLBARITEM)) + continue; + + this.$docklet.$getLayoutNode(sButton, "label", oButton) + .setAttribute("class", "editor_icon editor_" + plugin.icon); + + oButton.setAttribute("title", this.$translate(plugin.name)); + } + else { + this.$docklet.$getLayoutNode(sButton, "label", oButton) + .setAttribute("class", "editor_icon editor_" + item); + + oButton.setAttribute("title", this.$translate(item)); + } + + oButton.setAttribute("onmousedown", sBtnClick || "apf.all[" + + this.$uniqueId + "].$buttonClick(event, this, true);"); + oButton.setAttribute("onmouseover", + "apf.setStyleClass(this, 'hover', null, true);"); + oButton.setAttribute("onmouseout", + "apf.setStyleClass(this, '', ['hover'], true);"); + + oButton.setAttribute("type", item); + } + } + + buttons = null; + } + + if (apf.isIE) { + var nodes = oParent.getElementsByTagName("*"); + for (i = nodes.length - 1; i >= 0; i--) + nodes[i].setAttribute("unselectable", "On"); + } + }; + + var wasVisible; + this.$editors["richtext"] = { + create : function(oHtml, rule){ + this.getValue = function(){ + return apf.htmlCleaner.parse(oHtml.innerHTML); + }; + oHtml.setAttribute("richtext", "true"); + + if (apf.isGecko) { + try { + // On each return, insert a BR element + document.execCommand("insertBrOnReturn", false, true); + // Tell Gecko (Firefox 1.5+) to enable or not live resizing of objects + document.execCommand("enableObjectResizing", false, false); + // Disable the standard table editing features of Firefox. + document.execCommand("enableInlineTableEditing", false, false); + } + catch(ex){} + } + + if (!this.$docklet) + this.$editable(); + if (this.$docklet) { + this.$docklet.show(); + + //Docklet animation + var pos = apf.getAbsolutePosition(oHtml); + var w = this.$docklet.$ext.offsetWidth; + var h = this.$docklet.$ext.offsetHeight; + if (pos[0] + oHtml.offsetWidth + w + 5 > apf.getWindowWidth()) + pos[0] = apf.getWindowWidth() - w - 5; + else + pos[0] += oHtml.offsetWidth + 5; + + if (pos[1] - h - 5 < 0) { + if (pos[1] + oHtml.offsetHeight + 5 + h < apf.getWindowHeight()) + pos[1] = pos[1] + oHtml.offsetHeight + 5; + else + pos[1] = 0; + } + else + pos[1] -= h + 5; + + var dPos = apf.getAbsolutePosition(this.$docklet.$ext); + if (this.$control) + this.$control.stop(); + this.$control = {}; + + var tweens = [{ + from : dPos[0], + to : pos[0], + type : "left" + },{ + from : dPos[1], + to : pos[1], + type : "top" + }]; + if (!wasVisible) + tweens.push({ + from : 0, //apf.getOpacity(this.$docklet.$ext), + to : 1, + type : "fade" + }); + else + apf.setOpacity(this.$docklet.$ext, 1); + + apf.tween.multi(this.$docklet, { + steps : 30, + interval: 10, + control : this.$control, + anim : apf.tween.easeInOutCubic, + tweens : tweens + }); + + setTimeout(function(){wasVisible = true;}); + } + + this.setProperty("state", apf.OFF); + + if (apf.hasContentEditable) + oHtml.contentEditable = true; + else { + document.body.setAttribute("spellcheck", "false"); + document.designMode = "on"; + } + + + this.$lastValue = []; + this.$lastValue[0] = apf.htmlCleaner.prepare( + (this.$lastValue[1] = oHtml.innerHTML) + .replace(/]*>/gi, "").replace(/<\/P>/g, "").replace(/<\/p>/g, + "

")); + + if (this.$lastValue[1] != this.$lastValue[0]) { + //Set bookmark for cursor position + var obm = this.$selection.getBookmark(); + oHtml.innerHTML = this.$lastValue[0]; + this.$controlAgentBehavior(oHtml); + // restore selection to bookmark + this.$selection.moveToBookmark(obm); + } + + + this.addEventListener("keydown", keydownHandler, true); + this.addEventListener("keyup", keyupHandler, true); + }, + remove : function(oHtml, rule){ + oHtml.blur(); + if (apf.hasContentEditable) + oHtml.contentEditable = false; + else + document.designMode = "off"; + oHtml.setAttribute("richtext", "false"); + + this.removeEventListener("keydown", keydownHandler, true); + this.removeEventListener("keyup", keyupHandler, true); + } + }; + + this.addEventListener("$createEditor", function(e) { + if (e.editor != "richtext" && this.$docklet) { + setTimeout(function(){wasVisible = false;}); + this.$docklet.hide(); + } + }); + + this.addEventListener("$removeEditor", function(e) { + if (e.editor == "richtext" && this.$docklet) { + setTimeout(function(){wasVisible = false;}); + this.$docklet.hide(); + } + }); + + this.$editable = function(callback) { + if (this.getAttribute("plugins")) { + this.$propHandlers["plugins"] + .call(this, this.getAttribute("plugins")); + } + if (this.getAttribute("language")) { + this.$propHandlers["language"] + .call(this, this.getAttribute("language")); + } + + // no External representation yet, which means that we're dealing with + // a full-mode editor. + if (!this.$ext) { + this.$docklet = this; + this.$bStandalone = true; + this.$ext = this.$getExternal("main", null, function(oExt){ + var o = this.$getLayoutNode("main", "toolbar"); + this.$drawToolbars(o); + }); + this.$toolbar = this.$getLayoutNode("main", "toolbar", this.$ext); + + this.addEventListener("keydown", keydownHandler, true); + this.addEventListener("keyup", keyupHandler, true); + } + //@todo apf3.0 get this from portal.js + else if (!this.$docklet && !(apf.LiveEdit.toolwin = this.$docklet)) { + var _self = this; + this.$docklet = apf.LiveEdit.toolwin = + new apf.modalwindow({ + htmlNode : document.body, + skinset : apf.getInheritedAttribute(this.parentNode, "skinset"), + buttons : "", + title : "Format", + icon : "application.png", + resizable : false, //"horizontal", + draggable : true, + focussable : true, + modal : false, + left : 500, + top : 100, + width : 275, + zindex : 100000, + //resizeoutline : true, + onfocus : function(){ + _self.focus(); + } + }, "toolwindow"); + + var aNodes = this.$docklet.$ext.getElementsByTagName("div"); + for (var j = 0, l = aNodes.length; j < l; j++) { + if (aNodes[j].className.indexOf("content") != -1) { + this.$toolbar = aNodes[j]; + break; + } + } + this.$drawToolbars(this.$toolbar, "toolbar", null, true); + + // @todo make this hack disappear... + //this.$toolbar.innerHTML = this.$toolbar.innerHTML; //Why is this here? + } + + if (callback) + callback.call(this); + + if (this.$toolbar) { + // fetch the DOM references of all toolbar buttons and let the + // respective plugins finish initialization + var item, plugin, + btns = this.$toolbar.getElementsByTagName("div"), + i = btns.length - 1; + for (; i >= 0; i--) { + item = btns[i].getAttribute("type"); + if (!item) continue; + + this.$buttons[item] = btns[i]; + plugin = this.$plugins[item]; + if (!plugin) continue; + + plugin.buttonNode = btns[i]; + + if (plugin.init) + plugin.init(this); + } + } + }; + + this.$getPluginOption = function(node) { + return this.$docklet.$getOption(node); + }; + + /** + * Make an instance of apf.popup (identified with a pointer to the cached + * DOM node - sCacheId) visible to the user. + * + * @param {apf.LiveEdit.plugin} oPlugin The plugin instance + * @param {String} sCacheId Pointer to the cached DOM node + * @param {DOMElement} oRef Button node to show popup below to + * @param {Number} iWidth New width of the popup + * @param {Number} iHeight New height of the popup + * @type {void} + */ + this.$showPopup = function(oPlugin, sCacheId, oRef, iWidth, iHeight) { + if (apf.popup.last && apf.popup.last != sCacheId) { + var o = apf.lookup(apf.popup.last); + if (o) { + o.state = apf.OFF; + this.$notifyPlugin(o.name, o.state); + } + } + + //this.$selection.cache(); + this.$selection.set(); + if (this.$visualFocus) + this.$visualFocus(); + + oPlugin.state = apf.ON; + this.$notifyPlugin(oPlugin.name, apf.ON); + + if (apf.popup.isShowing(sCacheId)) + return; + + // using $setTimeout here, because I want the popup to be shown AFTER the + // event bubbling is complete. Another click handler further up the DOM + // tree may call a apf.popup.forceHide(); + $setTimeout(function() { + apf.popup.show(sCacheId, { + x : 0, + y : 22, + animate : false, + ref : oRef, + width : iWidth, + height : iHeight, + callback : function(oPopup) { + if (oPopup.onkeydown) return; + oPopup.onkeydown = function(e) { + e = e || window.event; + var key = e.which || e.keyCode; + if (key == 13 && typeof oPlugin["submit"] == "function") //Enter + return oPlugin.submit(new apf.AbstractEvent(e)); + } + } + }); + }); + }; + + /** + * Returns the translated key from a locale pack/ collection + * + * @param {String} key + * @param {Boolean} bIsPlugin + * @type {String} + * @private + */ + this.$translate = function(key, bIsPlugin) { + + if ((!bIsPlugin && !apf.LiveEdit.i18n[this.language][key]) + || (bIsPlugin && !apf.LiveEdit.i18n[this.language]["plugins"][key])) + apf.console.error("Translation does not exist" + + (bIsPlugin ? " for plugin" : "") + ": " + key); + + + return bIsPlugin + ? apf.LiveEdit.i18n[this.language]["plugins"][key] + : apf.LiveEdit.i18n[this.language][key]; + }; + + /** + * Inserts any given text (or HTML) at cursor position into the Editor + * + * @param {String} html + * @param {Boolean} [bNoParse] Prevents parsing the HTML, which might alter the string + * @param {Boolean} [bNoFocus] Prevents setting the focus back to the editor area + * @type {void} + */ + this.$insertHtml = function(html, bNoParse, bNoFocus) { + //removed check: if (inited && complete) + if (!bNoFocus) + this.$selection.set(); + this.$visualFocus(true); + + html = bNoParse ? html : apf.htmlCleaner.prepare(html); + + this.$selection.setContent(html, true); + if (this.$bStandalone) { + // notify SmartBindings that we changed stuff... + + this.change(this.getValue()); + + } + + if (!bNoFocus) + this.$restoreFocus(); + }; + + /** + * Paste (clipboard) data into the Editor + * + * @see element.editor.method.inserthtml + * @param {Event} e + * @type {void} + */ + this.$paste = function (e) { + var _self = this; + $setTimeout(function() { + var s = _self.$activeDocument.body.innerHTML; + if (s.match(/mso[a-zA-Z]+/i)) { //check for Paste from Word + var o = _self.$plugins["pasteword"]; + if (o) + _self.$propHandlers["value"].call(_self, o.parse(s)); + } + if (_self.realtime) + _self.change(_self.getValue()); + }); + } + + /** + * Corrects the default/ standard behavior of user agents that do not match + * our intentions or those of the user. + * + * @param {DOMElement} oParent LiveEdit element + * @type void + * @private + */ + this.$controlAgentBehavior = function(oParent) { + if (apf.isGecko) { + //var oParent = this.$activeDocument.body; + var oNode; + while (oParent.childNodes.length) { + oNode = oParent.firstChild; + if (oNode.nodeType == 1) { + if (oNode.nodeName == "BR" + && oNode.getAttribute("_moz_editor_bogus_node") == "TRUE") { + this.$selection.selectNode(oNode); + this.$selection.remove(); + this.$selection.collapse(false); + break; + } + } + oParent = oNode; + } + } + else if (apf.isWebkit) { + this.$activeDocument.designMode = "on"; + } + else if (apf.isIE) { + // yes, we fix hyperlinks...%&$#*@*! + var s, aLinks = oParent.getElementsByTagName("a"); + for (var i = 0, j = aLinks.length; i < j; i++) { + s = aLinks[i].getAttribute("_apf_href"); + if (s) //prefix 'http://' if it's not there yet... + aLinks[i].href = (!s.match(/^[a-zA-Z]+\:/) ? "http://" : "") + s; + } + } + }; + + this.language = "en_GB";//"nl_NL"; + this.state = apf.OFF; +}; + +apf.ON = 1; +apf.OFF = 0; +apf.DISABLED = -1; +apf.VISIBLE = 2; +apf.HIDDEN = 3; +apf.SELECTED = 4; + +apf.LiveEdit.i18n = { + "en_GB": { + "cancel": "Cancel", + "insert": "Insert", + "bold": "Bold", + "italic": "Italic", + "underline": "Underline", + "strikethrough": "Strikethrough", + "justifyleft": "Align text left", + "justifycenter": "Center", + "justifyright": "Align text right", + "justifyfull": "Justify", + "removeformat": "Clear formatting", + "cut": "Cut", + "copy": "Copy", + "paste": "Paste", + "outdent": "Decrease indent", + "indent": "Increase indent", + "undo": "Undo", + "redo": "Redo", + // plugin keys: + "anchor": "Insert anchor", + "blockquote": "Blockquote", + "charmap": "Character map", + "code": "HTML source view", + "listitem": "List item", + "nbsp": "Non-breaking space", + "break": "Linebreak", + "paragraph": "Paragraph", + "forecolor": "Font color", + "backcolor": "Highlight color", + "insertdate": "Insert current date", + "inserttime": "Insert current time", + "rtl": "Change text direction to right-to-left", + "ltr": "Change text direction to left-to-right", + "emotions": "Insert emotion", + "fonts": "Font", + "fontsize": "Font size", + "fontstyle": "Font style", + "blockformat": "Paragraph style", + "help": "Help", + "hr": "Insert horizontal rule", + "image": "Insert image", + "imagespecial": "Choose an image to insert", + "link": "Insert hyperlink", + "unlink": "Remove hyperlink", + "bullist": "Bullets", + "numlist": "Numbering", + "media": "Insert medium", + "pasteworddialog": "Paste from Word", + "pastetext": "Paste plaintext", + "paste_keyboardmsg": "Use %s on your keyboard to paste the text into the window.", + "print": "Print document", + "preview": "Preview document", + "spell": "Turn spellcheck on/ off", + "search": "Search", + "replace": "Search and Replace", + "findnext": "Find next", + "doreplace": "Replace", + "replaceall": "Replace all", + "sub": "Subscript", + "sup": "Superscript", + "table": "Insert table", + "table_noun": "Table", + "visualaid": "Toggle visual aid on/ off" + }, + "nl_NL": { + "cancel": "Annuleren", + "insert": "Invoegen", + "bold": "Vet", + "italic": "Schuingedrukt", + "underline": "Onderstreept", + "strikethrough": "Doorgestreept", + "justifyleft": "Recht uitlijnen", + "justifycenter": "Centreren", + "justifyright": "Rechts uitlijnen", + "justifyfull": "Justify", + "removeformat": "Stijlen verwijderen", + "cut": "Knippen", + "copy": "Kopieren", + "paste": "Plakken", + "outdent": "Inspringen verkleinen", + "indent": "Inspringen vergroten", + "undo": "Ongedaan maken", + "redo": "Opnieuw", + // plugin keys: + "anchor": "Anchor invoegen", + "blockquote": "Blockquote", + "charmap": "Speciale tekens", + "code": "HTML broncode", + "listitem": "Lijst item", + "nbsp": "Niet-brekende spatie", + "break": "Regelafbreuk", + "paragraph": "Paragraaf", + "forecolor": "Tekstkleur", + "backcolor": "Markeerkleur", + "insertdate": "Huidige datum invoegen", + "inserttime": "Huidige tijd invoegen", + "rtl": "Verander tekstrichting naar rechts-naar-links", + "ltr": "Verander tekstrichting naar links-naar-rechts", + "emotions": "Emoticon invoegen", + "fonts": "Lettertype", + "fontsize": "Letter grootte", + "fontstyle": "Tekststijl", + "blockformat": "Paragraafstijl", + "help": "Hulp", + "hr": "Horizontale lijn invoegen", + "image": "Afbeelding invoegen", + "imagespecial": "Afbeelding kiezen", + "link": "Link invoegen", + "unlink": "Link verwijderen", + "bullist": "Ongenummerd", + "numlist": "Genummerd", + "media": "Medium invoegen", + "pasteworddialog": "Word Tekst Plakken", + "pastetext": "Tekst Plakken", + "paste_keyboardmsg": "Gebruik %s op uw toetsenbord om tekst in dit scherm te plakken.", + "print": "Printen", + "preview": "Voorbeeldvertoning", + "spell": "Spellingscontrole aan/ uit", + "search": "Zoeken", + "replace": "Zoeken en vervangen", + "findnext": "Volgende", + "doreplace": "Vervangen", + "replaceall": "Vervang alle", + "sub": "Subscript", + "sup": "Superscript", + "table": "Tabel invoegen", + "table_noun": "Tabel", + "visualaid": "Visuele hulp aan/ uit" + } +}; + +apf.TOOLBARITEM = 0x0001;//"toolbaritem"; +apf.TOOLBARBUTTON = 0x0002;//"toolbarbutton"; +apf.TOOLBARPANEL = 0x0004;//"toolbarpanel"; +apf.TEXTMACRO = 0x0008;//"textmacro"; +apf.CMDMACRO = 0x0010;//"commandmacro"; + +/** + * @class plugin + * @constructor + * @extends LiveEdit + * @namespace apf + * @author Mike de Boer (mike AT javeline DOT com) + * + * Example plugin: + * + * apf.LiveEdit.plugin("sample", function() { + * this.name = "SamplePluginName"; + * this.type = "PluginType"; + * this.subType = "PluginSubType"; + * this.hook = "EventHook"; + * this.params = null; + * + * this.execute = function(editor) { + * // code to be executed when the event hook is fired + * } + * // That's it! you can add more code below to your liking... + * }); + * + */ +apf.LiveEdit.plugin = function(sName, fExec) { + apf.LiveEdit.plugin[sName] = function() { + this.$uniqueId = apf.all.push(this) - 1; + + /** + * Appends a new AML element - in its string representation - to an + * existing AML node. A new AML node will be created as specified by the + * contents of sNode and appended to oParent. + * + * @param {String} sNode + * @param {AmlNode} oParent + * @type {AmlNode} + */ + this.appendAmlNode = function(sNode, oParent) { + if (!sNode) return null; + var domParser = new apf.DOMParser(), + oFrag = this.editor.ownerDocument.createDocumentFragment(); + oFrag.$int = oParent; + var oNode = domParser.parseFromString(sNode, "text/xml", { + doc : this.editor.ownerDocument, + docFrag : oFrag + }); + return oNode.firstChild; + }; + + this.dispatchEvent = function() { + var _self = this; + window.setTimeout(function() { + if (_self.type == apf.editor.CONTEXTPANEL + && _self.queryState(_self.editor) == apf.editor.ON) + return; + _self.state = apf.editor.OFF; + if (_self.editor) + _self.editor.$notifyButton(_self.name, _self.state); + //@todo: add animation? + apf.popup.hide(); + apf.popup.last = null; + }); + + return false; + }; + + this.$destroy = function() { + // @todo should we keep this, or does apf.Popup destroy itself? what + // if we removeNode() the editor? + apf.popup.forceHide(); + this.buttonNode = this.editor = null; + delete this.buttonNode; + delete this.editor; + if (this.destroy) + this.destroy(); + } + + fExec.apply(this, arguments); + }; +}; + + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/core/baseclasses/liveedit/anchor.js)SIZE(4565)TIME(Mon, 10 Jan 2011 10:53:11 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + + +apf.LiveEdit.plugin("anchor", function() { + this.name = "anchor"; + this.icon = "anchor"; + this.type = apf.TOOLBARITEM; + this.subType = apf.TOOLBARPANEL; + this.hook = "ontoolbar"; + this.keyBinding = "ctrl+shift+a"; + this.state = apf.OFF; + + this.editor = null; + + var panelBody; + + this.init = function(editor, btn) { + this.editor = editor; + this.buttonNode.className = this.buttonNode.className + " dropdown_small"; + var oArrow = this.buttonNode.insertBefore(document.createElement("span"), + this.buttonNode.getElementsByTagName("div")[0]); + oArrow.className = "selectarrow"; + }; + + this.execute = function(editor) { + if (!panelBody) { + this.editor = editor; + apf.popup.setContent(this.$uniqueId, this.createPanelBody()); + } + + this.editor.dispatchEvent("pluginexecute", {name: this.name, plugin: this}); + + this.editor.$showPopup(this, this.$uniqueId, this.buttonNode, 218, 47); + if (panelBody.style.visibility == "hidden") + panelBody.style.visibility = "visible"; + var _self = this; + $setTimeout(function() { + _self.oName.focus(); + }); + //return button id, icon and action: + return { + id: this.name, + action: null + }; + }; + + this.queryState = function() { + // @todo: for webkit compat, we need to insert images instead of inline an elements + var oNode = this.editor.$selection.getSelectedNode(); + if (oNode.tagName == "A" && oNode.getAttribute("name")) + return apf.ON; + + return this.state; + }; + + this.submit = function(e) { + apf.popup.forceHide(); + + if (!this.oName.value) return; + + //this.storeSelection(); + this.editor.$insertHtml(''); + //this.restoreSelection(); + this.editor.$selection.collapse(false); + }; + + this.createPanelBody = function() { + panelBody = document.body.appendChild(document.createElement("div")); + panelBody.className = "editor_popup"; + panelBody.style.display = "none"; + var idName = "editor_" + this.$uniqueId + "_anchor_url", + idButton = "editor_" + this.$uniqueId + "_anchor_button"; + panelBody.innerHTML = + '

\ +
'; + + new apf.toolbar({ + htmlNode: document.getElementById(idButton), + skinset: apf.getInheritedAttribute(this.editor.parentNode, "skinset"), + childNodes: [ + new apf.bar({ + childNodes: [new apf.button({ + caption: this.editor.$translate("insert"), + onclick: "apf.lookup(" + this.$uniqueId + ").submit(event)" + })] + }) + ] + }); + + this.oName = document.getElementById(idName); + + apf.sanitizeTextbox(this.oName); + + return panelBody; + }; + + this.destroy = function() { + panelBody = this.editor = this.oName = null; + delete panelBody; + delete this.editor; + delete this.oName; + }; +}); + + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/core/baseclasses/liveedit/blockquote.js)SIZE(1594)TIME(Mon, 10 Jan 2011 10:53:11 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + + +apf.LiveEdit.plugin("blockquote", function(){ + this.name = "blockquote"; + this.icon = "blockquote"; + this.type = apf.TOOLBARITEM; + this.subType = apf.TOOLBARBUTTON; + this.hook = "ontoolbar"; + this.keyBinding = "ctrl+shift+b"; + this.buttonBuilt = false; + this.state = apf.OFF; + + this.execute = function(editor) { + editor.$execCommand("FormatBlock", "BLOCKQUOTE"); + + editor.dispatchEvent("pluginexecute", {name: this.name, plugin: this}); + }; + + this.queryState = function(editor) { + return editor.$queryCommandState("FormatBlock"); + }; +}); + + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/core/baseclasses/liveedit/charmap.js)SIZE(6951)TIME(Mon, 10 Jan 2011 10:53:11 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + + +apf.LiveEdit.plugin("charmap", function() { + this.name = "charmap"; + this.icon = "charmap"; + this.type = apf.TOOLBARITEM; + this.subType = apf.TOOLBARPANEL; + this.hook = "ontoolbar"; + this.buttonNode = null; + this.state = apf.OFF; + this.colspan = 20; + + var panelBody; + + this.init = function(editor, btn) { + this.buttonNode.className = this.buttonNode.className + " dropdown_small"; + var oArrow = this.buttonNode.insertBefore(document.createElement("span"), + this.buttonNode.getElementsByTagName("div")[0]); + oArrow.className = "selectarrow"; + }; + + this.execute = function(editor) { + if (!panelBody) { + this.editor = editor; + apf.popup.setContent(this.$uniqueId, this.createPanelBody()); + } + + editor.dispatchEvent("pluginexecute", {name: this.name, plugin: this}); + + this.editor.$showPopup(this, this.$uniqueId, this.buttonNode, apf.isIE6 ? 469 : 466, 318); + //return button id, icon and action: + return { + id: this.name, + action: null + }; + }; + + this.queryState = function() { + return this.state; + }; + + var chars = ["!",""","#","$","%","&","\\'","(",")","*","+","-",".", + "/","0","1","2","3","4","5","6","7","8","9",":",";","<","=",">", + "?","@","A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P", + "Q","R","S","T","U","V","W","X","Y","Z","[","]","^","_","`","a","b","c", + "d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t","u", + "v","w","x","y","z","{","|","}","~","¡","¢","£","¤", + "¥","¦","§","¨","©","ª","«","¬", + "®","¯","°","±","²","³","´","µ", + "¶","·","¸","¹","º","»","¼","½", + "¾","¿","À","Á","Â","Ã","Ä","Å", + "Æ","Ç","È","É","Ê","Ë","Ì","Í", + "Î","Ï","Ð","Ñ","Ò","Ó","Ô","Õ", + "Ö","×","Ø","Ù","Ú","Û","Ü","Ý", + "Þ","ß","à","á","â","ã","ä","å", + "æ","ç","è","é","ê","ë","ì","í", + "î","ï","ñ","ò","ó","ô","õ","ö", + "÷","ø","ù","ú","û","ü","ý","þ", + "ÿ","Œ","œ","Š","š","Ÿ","ƒ","ˆ", + "˜","Α","Β","Γ","Δ","Ε","Ζ","Η", + "Θ","Ι","Κ","Λ","Μ","Ν","Ξ","Ο", + "Π","Ρ","Σ","Τ","Υ","Φ","Χ","Ψ", + "Ω","α","β","γ","δ","ε","ζ","η", + "θ","ι","κ","λ","μ","ν","ξ","ο", + "π","ρ","ς","σ","τ","υ","φ","χ", + "ψ","ω","ϑ","ϒ","ϖ"," ","–","—", + "‘","’","‚","“","”","„","†", + "‡","•","…","‰","′","″","‹", + "›","‾","⁄","€","ℑ","℘","ℜ", + "™","ℵ","←","↑","→","↓","↔", + "↵","⇐","⇑","⇒","⇓","⇔","∀", + "∂","∃","∅","∇","∈","∉","∋", + "∏","∑","−","∗","√","∝","∞", + "∠","∧","∨","∩","∪","∫","∴", + "∼","≅","≈","≠","≡","≤","≥", + "⊂","⊃","⊄","⊆","⊇","⊕","⊗", + "⊥","⋅","⌈","⌉","⌊","⌋","〈", + "〉","◊","♠","♣","♥","♦"]; + + this.submit = function(e) { + var el = e.target || e.srcElement; + while (el.tagName.toLowerCase() != "a" && el.className != "editor_popup") + el = el.parentNode; + var sCode = el.getAttribute("rel"); + if (sCode) { + apf.popup.forceHide(); + //this.storeSelection(); + this.editor.$insertHtml(sCode, true); + var _self = this; + $setTimeout(function() { //make sure the 'change' is notified to the smartbindings + + _self.editor.change(_self.editor.getValue()); + + }); + //this.restoreSelection(); + } + }; + + this.createPanelBody = function() { + panelBody = document.body.appendChild(document.createElement("div")); + panelBody.className = "editor_popup"; + panelBody.style.display = "none"; + var aHtml = []; + var rowLen = this.colspan - 1; + for (var i = 0; i < chars.length; i++) { + if (i % this.colspan == 0) + aHtml.push('
'); + aHtml.push('\ + ', chars[i],"\ + "); + if (i % this.colspan == rowLen) + aHtml.push("
"); + } + panelBody.innerHTML = aHtml.join(""); + return panelBody; + }; + + this.destroy = function() { + panelBody = null; + delete panelBody; + }; +}); + + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/core/baseclasses/liveedit/clipboard.js)SIZE(13429)TIME(Mon, 10 Jan 2011 10:53:11 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + + +apf.LiveEdit.pasteDialog = function(sName) { + this.name = sName; + this.icon = sName == "pasteworddialog" ? "pasteword" : sName; + this.type = apf.TOOLBARITEM; + this.subType = apf.TOOLBARPANEL; + this.hook = "ontoolbar"; + this.keyBinding = sName == "pastetext" ? "ctrl+shift+v" : "ctrl+shift+w"; + this.state = apf.OFF; + + var panelBody; + + this.init = function() { + this.buttonNode.className = this.buttonNode.className + " dropdown_small"; + this.buttonNode.insertBefore(document.createElement("span"), + this.buttonNode.getElementsByTagName("div")[0]) + .className = "selectarrow"; + }; + + this.execute = function(editor) { + if (!panelBody) { + this.editor = editor; + apf.popup.setContent(this.$uniqueId, this.createPanelBody()); + } + + editor.dispatchEvent("pluginexecute", {name: this.name, plugin: this}); + + this.editor.$showPopup(this, this.$uniqueId, this.buttonNode, 300, 270); + if (panelBody.style.visibility == "hidden") + panelBody.style.visibility = "visible"; + var _self = this; + $setTimeout(function() { + try { + _self.oArea.focus(); + } + catch (ex) {} + }, 100); // 100ms, because of the $focusfix code... + //return button id, icon and action: + return { + id: this.name, + action: null + }; + }; + + this.queryState = function(editor) { + return this.state; + }; + + this.submit = function(e) { + apf.popup.forceHide(); + + var sContent = this.oArea.value; + if (!sContent || sContent.length == 0) return; + + if (this.name == "pastetext") { + var rl = ["\u2122", "TM", "\u2026", "...", "\u201c|\u201d", + "\"", "\u2019,'", "\u2013|\u2014|\u2015|\u2212", "-"]; + sContent = sContent.replace(/\u2122/gi, "TM") + .replace(/\u2026/gi, "...") + .replace(/\u201c|\u201d/gi, "\"") + .replace(/\u2019,'/gi, "'") + .replace(/\u2013|\u2014|\u2015|\u2212/gi, "-") + .replace(/\r\n/g, "
") + .replace(/\r/g, "
") + .replace(/\n/g, "
"); + } + else { + sContent = this.editor.$plugins["pasteword"].parse(sContent); + } + this.editor.$insertHtml(sContent); + + apf.stopEvent(e); + return false; + }; + + this.createPanelBody = function() { + panelBody = document.body.appendChild(document.createElement("div")); + panelBody.className = "editor_popup"; + panelBody.style.display = "none"; + var idArea = "editor_" + this.$uniqueId + "_input", + idBtns = "editor_" + this.$uniqueId + "_btns"; + panelBody.innerHTML = + '\ + \ +
'; + + this.oArea = document.getElementById(idArea); + + + apf.sanitizeTextbox(this.oArea); + + + if (apf.isIE) { + this.oArea.onselectstart = this.oArea.onpaste = function(e) { + e = e || window.event; + e.cancelBubble = true; + }; + } + + new apf.toolbar({ + htmlNode: document.getElementById(idBtns), + skinset: apf.getInheritedAttribute(this.editor.parentNode, "skinset"), + childNodes: [ + new apf.bar({ + childNodes: [new apf.button({ + caption: this.editor.$translate("insert"), + onclick: "apf.lookup(" + this.$uniqueId + ").submit(event)" + })] + }) + ] + }); + + return panelBody; + }; + + this.destroy = function() { + panelBody = this.oArea = null; + delete panelBody; + delete this.oArea; + }; +}; + +apf.LiveEdit.plugin("pasteworddialog", apf.LiveEdit.pasteDialog); +apf.LiveEdit.plugin("pastetext", apf.LiveEdit.pasteDialog); + +apf.LiveEdit.plugin("pasteword", function() { + this.name = "pasteword"; + this.icon = "pasteword"; + this.type = apf.CMDMACRO; + this.hook = "onpaste"; + this.keyBinding = "ctrl+shift+v"; + this.state = apf.OFF; + + this.parse = function(sContent) { + var bull = String.fromCharCode(8226), + middot = String.fromCharCode(183); + + // Remove comments [SF BUG-1481861]. + sContent = sContent.replace(new RegExp("

(.*?)<\/p>", "gi"), "

$1

") + .replace(new RegExp("tab-stops: list [0-9]+.0pt\">", "gi"), "\">--list--") + .replace(new RegExp(bull + "(.*?)
", "gi"), "

" + middot + "$1

") + // Covert to bull list + .replace(new RegExp('', "gi"), "" + bull) + // Replace pagebreaks.replace(/<\!--[\s\S]*?-->/g, "") + .replace(new RegExp('
\s*<\/o:p>/g, "") + .replace(/[\s\S]*?<\/o:p>/g, " ") + // Remove mso-xxx styles. + .replace(/\s*mso-[^:]+:[^;"]+;?/gi, "") + // Remove margin styles. + .replace(/\s*MARGIN: 0(?:cm|in) 0(?:cm|in) 0pt\s*;/gi, "") + .replace(/\s*MARGIN: 0(?:cm|in) 0(?:cm|in) 0pt\s*"/gi, "\"") + .replace(/\s*TEXT-INDENT: 0cm\s*;/gi, "") + .replace(/\s*TEXT-INDENT: 0cm\s*"/gi, "\"") + .replace(/\s*TEXT-ALIGN: [^\s;]+;?"/gi, "\"") + .replace(/\s*PAGE-BREAK-BEFORE: [^\s;]+;?"/gi, "\"") + .replace(/\s*FONT-VARIANT: [^\s;]+;?"/gi, "\"") + .replace(/\s*tab-stops:[^;"]*;?/gi, "") + .replace(/\s*tab-stops:[^"]*/gi, "") + // Remove Class attributes + .replace(/<(\w[^>]*) class=([^ |>]*)([^>]*)/gi, "<$1$3") + // Remove pagebreaks + .replace(/-- page break --\s*

 <\/p>/gi, "") + // Remove pagebreaks + .replace(/-- page break --/gi, "") + //convert

newlines to
ones + .replace(/<\/p>/gi, "

") + .replace(/]*cellPadding=[^>]*>/gi, '') //correct tables + .replace(/]*vAlign=[^>]*>/gi, ""); + for (k = 0, l = oSize[1]; k < l; k++) + aOut.push(""); + aOut.push("") + } + aOut.push("
");; + + // Remove FONT face attributes. + // @todo make this a checkbox in the UI + if (true) {//ignoreFont) { + sContent = sContent.replace(/\s*face="[^"]*"/gi, "") + .replace(/\s*face=[^ >]*/gi, "") + .replace(/\s*FONT-FAMILY:[^;"]*;?/gi, ""); + } + + // Remove styles. + // @todo make this a checkbox in the UI + if (true)//removeStyles) + sContent = sContent.replace(/<(\w[^>]*) style="([^\"]*)"([^>]*)/gi, "<$1$3" ); + + // Remove style, meta and link tags + sContent = sContent.replace(/]*>[\s\S]*?<\/STYLE[^>]*>/gi, "") + .replace(/<(?:META|LINK)[^>]*>\s*/gi, "") + // Remove empty styles. + .replace(/\s*style="\s*"/gi, "") + .replace(/]*>\s* \s*<\/SPAN>/gi, " ") + .replace(/]*><\/SPAN>/gi, "") + // Remove Lang attributes + .replace(/<(\w[^>]*) lang=([^ |>]*)([^>]*)/gi, "<$1$3") + .replace(/([\s\S]*?)<\/SPAN>/gi, "$1") + .replace(/([\s\S]*?)<\/FONT>/gi, "$1") + // Remove XML elements and declarations + .replace(/<\\?\?xml[^>]*>/gi, "") + // Remove w: tags with contents. + .replace(/]*>[\s\S]*?<\/w:[^>]*>/gi, "") + // Remove Tags with XML namespace declarations: <\/o:p> + .replace(/<\/?\w+:[^>]*>/gi, "") + .replace(/<(U|I|STRIKE)> <\/\1>/g, " ") + .replace(/\s*<\/H\d>/gi, "") + // Remove "display:none" tags. + .replace(/<(\w+)[^>]*\sstyle="[^"]*DISPLAY\s?:\s?none[\s\S]*?<\/\1>/ig, "") + // Remove language tags + .replace(/<(\w[^>]*) language=([^ |>]*)([^>]*)/gi, "<$1$3") + // Remove onmouseover and onmouseout events (from MS Word comments effect) + .replace(/<(\w[^>]*) onmouseover="([^\"]*)"([^>]*)/gi, "<$1$3") + .replace(/<(\w[^>]*) onmouseout="([^\"]*)"([^>]*)/gi, "<$1$3") + // The original tag send from Word is something like this: + .replace(/]*)>/gi, "") + // Word likes to insert extra tags, when using MSIE. (Wierd). + .replace(/<(H\d)>]*>([\s\S]*?)<\/FONT><\/\1>/gi, "<$1>$2<\/$1>") + .replace(/<(H\d)>([\s\S]*?)<\/EM><\/\1>/gi, "<$1>$2<\/$1>"); + + // Convert all middlot lists to UL lists + var div = document.createElement("div"); + div.innerHTML = sContent; + // Convert all middot paragraphs to li elements + while (this._convertMiddots(div, "--list--")); // bull + while (this._convertMiddots(div, middot, "unIndentedList")); // Middot + while (this._convertMiddots(div, bull)); // bull + sContent = div.innerHTML; + + return sContent.replace(/--list--/gi, ""); // Remove temporary --list-- + }; + + this._convertMiddots = function(div, search, class_name) { + var mdot = String.fromCharCode(183), + bull = String.fromCharCode(8226), + nodes, prevul, i, p, ul, li, np, cp; + + nodes = div.getElementsByTagName("p"); + for (i = 0; i < nodes.length; i++) { + p = nodes[i]; + + // Is middot + if (p.innerHTML.indexOf(search) != 0) continue; + + ul = document.createElement("ul"); + if (class_name) + ul.className = class_name; + + // Add the first one + li = document.createElement("li"); + li.innerHTML = p.innerHTML.replace(new RegExp("" + mdot + "|" + bull + "|--list--| ", "gi"), ""); + ul.appendChild(li); + + // Add the rest + np = p.nextSibling; + while (np) { + // If the node is whitespace, then + // ignore it and continue on. + if (np.nodeType == 3 && new RegExp("^\\s$", "m").test(np.nodeValue)) { + np = np.nextSibling; + continue; + } + + if (search == mdot) { + if (np.nodeType == 1 && new RegExp("^o(\\s+| )").test(np.innerHTML)) { + // Second level of nesting + if (!prevul) { + prevul = ul; + ul = document.createElement("ul"); + prevul.appendChild(ul); + } + np.innerHTML = np.innerHTML.replace(/^o/, ""); + } + else { + // Pop the stack if we're going back up to the first level + if (prevul) { + ul = prevul; + prevul = null; + } + // Not element or middot paragraph + if (np.nodeType != 1 || np.innerHTML.indexOf(search) != 0) + break; + } + } + else { + // Not element or middot paragraph + if (np.nodeType != 1 || np.innerHTML.indexOf(search) != 0) + break; + } + + cp = np.nextSibling; + li = document.createElement("li"); + li.innerHTML = np.innerHTML.replace(new RegExp("" + mdot + "|" + bull + "|--list--| ", "gi"), ""); + np.parentNode.removeChild(np); + ul.appendChild(li); + np = cp; + } + p.parentNode.replaceChild(ul, p); + return true; + } + return false; + }; +}); + + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/core/baseclasses/liveedit/code.js)SIZE(11899)TIME(Mon, 10 Jan 2011 10:53:11 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + + +apf.LiveEdit.plugin("code", function() { + this.name = "code"; + this.icon = "code"; + this.type = apf.TOOLBARITEM; + this.subType = apf.TOOLBARBUTTON; + this.hook = "ontoolbar"; + this.keyBinding = "ctrl+shift+h"; + this.state = apf.OFF; + this.noDisable = true; + this.regex = null; + + var oCont, oToolbar, oButtons = {}, oPreview, protectedData, lastLoaded, + _self = this; + + this.execute = function(editor) { + //this.buttonNode.onclick(editor.mimicEvent()); + if (!oPreview) + this.drawPreview(editor); + + if (oCont.style.display == "none") { + // remember the selection for IE + editor.$selection.cache(); + + this.update(editor); + + editor.$pluginsActive = this.name; + // disable the editor... + editor.setProperty("state", apf.DISABLED); + + // show the textarea and position it correctly... + this.setSize(editor); + oCont.style.display = ""; + + oPreview.focus(); + } + else { + editor.$pluginsActive = null; + + oCont.style.display = "none"; + editor.setProperty("state", apf.OFF); + + propagateChange(); + + $setTimeout(function() { + editor.$selection.set(); + editor.$visualFocus(); + }); + } + editor.$notifyButton("code", this.queryState(editor)); + + editor.dispatchEvent("pluginexecute", {name: this.name, plugin: this}); + }; + + this.update = function(editor, sHtml) { + if (changeTimer) { + lastLoaded = sHtml; + return; + } + // update the contents of the (hidden) textarea + oPreview.value = format.call(this, sHtml + ? apf.htmlCleaner.parse(sHtml) + : (lastLoaded = editor.getValue())); + }; + + this.getValue = function() { + return oPreview.value; + }; + + function propagateChange() { + //if (lastLoaded == oPreview.value) return false; + var html = apf.htmlCleaner.parse(oPreview.value + .replace(/<\/p>/gi, "

") + .replace(/\n/g, "")); + + try{ + apf.getXml("" + html.replace(/&.{3,5};/g, "") + ""); + } + catch(e){ + if (confirm("Er zit een fout in de html. Klik op OK om deze \ + te corrigeren, of op Cancel om door te gaan")){ + //@todo mike: finish this + return false; + } + } + + if (lastLoaded == oPreview.value + || _self.editor.$value.replace(/[\r\n]/g, "") == html.replace(/[\r\n]/g, "")) { + _self.editor.$value = ""; + _self.editor.$propHandlers["value"].call(_self.editor, html); + } + else + + _self.editor.change(html); + + + return true; + } + + var changeTimer = null; + + function resumeChangeTimer() { + if (!_self.editor.realtime || changeTimer !== null) return; + changeTimer = $setTimeout(function() { + clearTimeout(changeTimer); + _self.editor.change(oPreview.value); + changeTimer = null; + }, 200); + } + + function onKeydown(e) { + e = e || window.event; + var code = e.which || e.keyCode; + if (!e.ctrlKey && !e.altKey && (code < 112 || code > 122) + && (code == 32 || code > 42 || code == 8 || code == 13)) { + resumeChangeTimer(); + } + } + + this.drawPreview = function(editor) { + this.editor = editor; + + //this.editor.$getNewContext("code"); + oCont = editor.$getExternal("code", editor.$ext); + + oToolbar = oCont.getElementsByTagName("div")[0]; + //oToolbar.className = ""; + this.editor.$drawToolbars(oToolbar, "codetoolbar", + "apf.all[" + this.$uniqueId + "].$buttonClick(event, this);", true); + // @todo make this hack disappear... + oToolbar.innerHTML = oToolbar.innerHTML; + var btns = oToolbar.getElementsByTagName("div"); + for (var item, i = btns.length - 1; i >= 0; i--) { + item = btns[i].getAttribute("type"); + if (!item) continue; + + oButtons[item] = btns[i]; + apf.setStyleClass(btns[i], "editor_enabled", + ["editor_selected", "editor_disabled"]); + btns[i].disabled = false; + } + + oPreview = oCont.getElementsByTagName("textarea")[0];//oCont.appendChild(document.createElement("textarea")); + // make selections in IE possible. + if (apf.isIE) + oPreview.onselectstart = function(e) { + e = e || window.event; + e.cancelBubble = true; + }; + oPreview.onkeydown = onKeydown; + + this.setSize(editor); + oCont.style.display = "none"; + + + apf.sanitizeTextbox(oPreview); + + } + + this.setSize = function(editor) { + if (!oPreview || !editor) return; + + var w = editor.$ext.offsetWidth - 2; + var h = editor.$ext.offsetHeight - editor.$toolbar.offsetHeight - 4; + oCont.style.top = editor.$toolbar.offsetHeight + "px"; + oCont.style.width = + oToolbar.style.width = w + "px"; + oPreview.style.width = w - (apf.isIE ? 2 : 0) + "px"; + oCont.style.height = h + (apf.isIE ? 2 : 3) + "px"; + oPreview.style.height = h - (apf.isIE ? 26 : 24) + "px"; + }; + + var elements = { + "bullist" : ["
    ", "
"], + "numlist" : ["
    ", "
"], + "listitem" : ["
  • ", "
  • "], + "nbsp" : [" ", null], + "break" : ["
    ", null], + "paragraph" : ["

    ", "

    "] + }; + + this.$buttonClick = function(e, oButton) { + apf.setStyleClass(oButton, "active"); + var item = oButton.getAttribute("type"); + if (elements[item]) + insertElement.apply(this, elements[item]); + + this.editor.$visualFocus(); + oPreview.focus(); + + apf.setStyleClass(oButton, "", ["active"]); + } + + function insertElement(sStart, sEnd) { + if (!sStart) return; + var range, val, end; + if (!sEnd) { + // no end tag provided, so insert sStart at the current caret position + if (apf.hasMsRangeObject) { + range = document.selection.createRange(); + range.collapse(); + range.text = sStart; + range.moveEnd("character", sStart.length); + range.collapse(); + if (apf.document.activeElement == this.editor) + range.select(); + } + else { + val = oPreview.value; + end = oPreview.selectionEnd; + oPreview.selectionStart = end; + oPreview.value = val.substr(0, end) + sStart + + val.substr(end); + oPreview.selectionStart = oPreview.selectionEnd = end + + sStart.length; + } + } + else { + // end tag provided, so we need to encapsulate the selection with + // sStart and sEnd + if (apf.hasMsRangeObject) { + range = document.selection.createRange(); + val = range.text; + range.text = sStart + val + sEnd; + range.moveStart("character", -(val.length + sEnd.length)); + range.moveEnd("character", -sEnd.length); + if (apf.document.activeElement == this.editor) + range.select(); + } + else { + var start = oPreview.selectionStart; + val = oPreview.value; + end = oPreview.selectionEnd; + oPreview.value = val.substr(0, start) + sStart + + val.substr(start, end - start) + sEnd + val.substr(end); + oPreview.selectionStart = start + sStart.length; + oPreview.selectionEnd = end + sEnd.length - 1; + } + } + } + + function protect(outer, opener, data, closer) { + return opener + "apf.___APFpd___" + protectedData.push(data) + closer; + } + + function format(sHtml) { + if (!this.regex) + setupRegex.call(this); + protectedData = []; + + var sFmt = sHtml.replace(this.regex.protectedTags, protect); + // Line breaks. + sFmt = sFmt.replace(this.regex.blocksOpener, "\n$&") + .replace(this.regex.blocksCloser, "$&\n") + .replace(this.regex.newLineTags, "$&\n") + .replace(this.regex.mainTags, "\n$&\n"); + + // Indentation. + var i, j, + sIdt = "", + asLines = sFmt.split(this.regex.lineSplitter); + sFmt = ""; + for (i = 0, j = asLines.length; i < j; i++) { + var sLn = asLines[i]; + if (sLn.length == 0) + continue ; + if (this.regex.decreaseIndent.test(sLn)) + sIdt = sIdt.replace(this.regex.formatIndentatorRemove, ""); + sFmt += sIdt + sLn + "\n"; + if (this.regex.increaseIndent.test(sLn)) + sIdt += " "; + } + + // Now we put back the protected data. + for (i = 0, j = protectedData.length; i < j; i++) { + var oRegex = new RegExp("apf.___JPFpd___" + i); + sFmt = sFmt.replace(oRegex, protectedData[i].replace(/\$/g, "$$$$")); + } + + return sFmt.trim(); + } + + function setupRegex() { + // Regex for line breaks. + this.regex = { + blocksOpener : /\<(P|DIV|H1|H2|H3|H4|H5|H6|ADDRESS|PRE|OL|UL|LI|TITLE|META|LINK|BASE|SCRIPT|LINK|TD|TH|AREA|OPTION)[^\>]*\>/gi, + blocksCloser : /\<\/(P|DIV|H1|H2|H3|H4|H5|H6|ADDRESS|PRE|OL|UL|LI|TITLE|META|LINK|BASE|SCRIPT|LINK|TD|TH|AREA|OPTION)[^\>]*\>/gi, + newLineTags : /\<(BR|HR)[^\>]*\>/gi, + mainTags : /\<\/?(HTML|HEAD|BODY|FORM|TABLE|TBODY|THEAD|TR)[^\>]*\>/gi, + lineSplitter : /\s*\n+\s*/g, + // Regex for indentation. + increaseIndent: /^\<(HTML|HEAD|BODY|FORM|TABLE|TBODY|THEAD|TR|UL|OL)[ \/\>]/i, + decreaseIndent: /^\<\/(HTML|HEAD|BODY|FORM|TABLE|TBODY|THEAD|TR|UL|OL)[ \>]/i, + protectedTags : /(]*>)([\s\S]*?)(<\/PRE>)/gi, + formatIndentatorRemove: /^ / + }; + } + + this.queryState = function(editor) { + if (editor.$pluginsActive == this.name) + return apf.SELECTED; + return apf.OFF; + }; + + this.destroy = function() { + oPreview = this.regex = null; + delete oPreview; + delete this.regex; + }; +}); + + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/core/baseclasses/liveedit/color.js)SIZE(7167)TIME(Mon, 10 Jan 2011 10:53:11 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + +apf.LiveEdit.colorPlugin = function(sName) { + this.name = sName; + this.icon = sName; + this.type = apf.TOOLBARITEM; + this.subType = apf.TOOLBARPANEL; + this.hook = "ontoolbar"; + this.buttonNode = null; + this.state = apf.OFF; + this.colspan = 18; + + var panelBody; + + var colorAtoms = ["00", "33", "66", "99", "CC", "FF"]; + function generatePalette() { + apf.LiveEdit.colorPlugin.palette = []; + var r, g, b, iCol; + for (r = 0; r < colorAtoms.length; r++) { + for (g = 0; g < colorAtoms.length; g++) { + iCol = (r % 3) * 6 + g; + for (b = 0; b < colorAtoms.length; b++) { + if (!apf.LiveEdit.colorPlugin.palette[iCol]) + apf.LiveEdit.colorPlugin.palette[iCol] = []; + apf.LiveEdit.colorPlugin.palette[iCol][(r < 3 ? 0 : 6) + b] = { + red : colorAtoms[r], + green: colorAtoms[g], + blue : colorAtoms[b] + }; + } + } + } + } + + /** + * Color code from MS sometimes differ from RGB; it's BGR. This method + * converts both ways + * + * @param {color} c code - RGB-->BGR or BGR-->RGB + * @type String + * @return RGB<-->BGR + */ + function RGBToBGRToRGB(c) { + if (typeof c == "string" && c.length > 0) { + //c = c.parseColor(); + var tmp = []; + var ch1 = c.charAt(0); + var ch2 = c.charAt(4); + tmp[0] = ch2; + tmp[4] = ch1; + ch1 = c.charAt(1); + ch2 = c.charAt(5); + tmp[1] = ch2; + tmp[5] = ch1; + return tmp[0] + tmp[1] + c.charAt(2) + c.charAt(3) + tmp[4] + tmp[5]; + } + return c; + } + + function int2Color(intVal) { + var colorVal = (intVal & 0xFFFFFF).toString(16); + return ("000000").substring(0, 6 - colorVal.length) + colorVal; + } + + this.init = function(editor, btn) { + this.buttonNode.className = this.buttonNode.className + " dropdown_small"; + var oArrow = this.buttonNode.getElementsByTagName("div")[0]; + this.colorPreview = this.buttonNode.insertBefore(document.createElement("div"), + oArrow); + this.colorPreview.className = "colorpreview"; + var colorArrow = this.buttonNode.insertBefore(document.createElement("span"), + oArrow); + colorArrow.className = "selectarrow"; + }; + + this.execute = function(editor) { + if (!panelBody) { + this.editor = editor; + apf.popup.setContent(this.$uniqueId, this.createPanelBody()); + } + + editor.dispatchEvent("pluginexecute", {name: this.name, plugin: this}); + + this.editor.$showPopup(this, this.$uniqueId, this.buttonNode, apf.isIE6 ? 296 : 292, 167); + //return button id, icon and action: + return { + id: this.name, + action: null + }; + }; + + this.setStyleMethod = function(useSpan) { + if (typeof useSpan == "undefined") + useSpan = true; + // Tell Gecko to use or not the tag for the bold, italic and underline. + try { + this.editor.$activeDocument.execCommand("styleWithCSS", false, useSpan); + } + catch (ex) { + this.editor.$activeDocument.execCommand("useCSS", false, !useSpan); + } + }; + + this.queryState = function(editor) { + var cmdName = this.name == "forecolor" + ? "ForeColor" + : apf.isIE ? "BackColor" : "HiliteColor"; + this.state = editor.$queryCommandState(cmdName); + var currValue = ""; + try { + currValue = editor.$queryCommandValue(cmdName); + } + catch (ex) {} + if (apf.isIE) + currValue = "#" + RGBToBGRToRGB(int2Color(currValue)); + if (currValue != this.colorPreview.style.backgroundColor) + this.colorPreview.style.backgroundColor = currValue; + }; + + this.submit = function(e) { + var el = e.target || e.srcElement; + while (el.tagName.toLowerCase() != "a" && el.className != "editor_popup") + el = el.parentNode; + var sColor = el.getAttribute("rel"); + if (sColor) { + apf.popup.forceHide(); +// if (this.name == "backcolor" && apf.isGecko) +// this.setStyleMethod(true); + this.editor.$execCommand(this.name == "forecolor" + ? "ForeColor" + : apf.isIE ? "BackColor" : "HiliteColor", + "#" + sColor); +// if (this.name == "backcolor" && apf.isGecko) +// this.setStyleMethod(false); + } + }; + + this.createPanelBody = function() { + if (!apf.LiveEdit.colorPlugin.palette) + generatePalette(); + + panelBody = document.body.appendChild(document.createElement("div")); + panelBody.className = "editor_popup"; + panelBody.style.display = "none"; + var aHtml = []; + + var row, col, colorCode, palette = apf.LiveEdit.colorPlugin.palette; + for (row = 0; row < palette[0].length; row++) { + aHtml.push('
    '); + for (col= 0; col < palette.length; col++) { + colorCode = palette[col][row].red + + palette[col][row].green + + palette[col][row].blue; + aHtml.push('\ +  '); + } + aHtml.push("
    "); + } + panelBody.innerHTML = aHtml.join(""); + + return panelBody; + } + + this.destroy = function() { + panelBody = this.colorPreview = null; + delete panelBody; + delete this.colorPreview; + }; +}; +apf.LiveEdit.colorPlugin.palette = null; + +apf.LiveEdit.plugin("forecolor", apf.LiveEdit.colorPlugin); +apf.LiveEdit.plugin("backcolor", apf.LiveEdit.colorPlugin); + + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/core/baseclasses/liveedit/datetime.js)SIZE(3585)TIME(Mon, 10 Jan 2011 10:53:11 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + + +apf.LiveEdit.dateTimePlugin = function(sName) { + this.name = sName; + this.icon = sName; + this.type = apf.TOOLBARITEM; + this.subType = apf.TOOLBARBUTTON; + this.hook = "ontoolbar"; + this.keyBinding = sName == "insertdate" ? "ctrl+shift+d" : "ctrl+shift+t"; + this.state = apf.OFF; + this.i18n = { //default English (en_GB) + date_format :"%Y-%m-%d", + time_format :"%H:%M:%S", + months_long :"January,February,March,April,May,June,July,August,September,October,November,December", + months_short :"Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec", + days_long :"Sunday,Monday,Tuesday,Wednesday,Thursday,Friday,Saturday,Sunday", + days_short :"Sun,Mon,Tue,Wed,Thu,Fri,Sat,Sun" + }; + + this.execute = function(editor) { + if (typeof this.i18n.months_long == "string") { + this.i18n.months_long = this.i18n.months_long.split(","); + this.i18n.months_short = this.i18n.months_short.split(","); + this.i18n.days_long = this.i18n.days_long.split(","); + this.i18n.days_short = this.i18n.days_short.split(","); + } + var d = new Date(); + var fmt = (this.name == "insertdate") ? this.i18n.date_format : this.i18n.time_format; + fmt = fmt.replace("%D", "%m/%d/%y") + .replace("%r", "%I:%M:%S %p") + .replace("%Y", "" + d.getFullYear()) + .replace("%y", "" + d.getYear()) + .replace("%m", ("" + d.getMonth() + 1).pad(2, "0")) + .replace("%d", ("" + d.getDate()).pad(2, "0")) + .replace("%H", ("" + d.getHours()).pad(2, "0")) + .replace("%M", ("" + d.getMinutes()).pad(2, "0")) + .replace("%S", ("" + d.getSeconds()).pad(2, "0")) + .replace("%I", "" + (d.getHours() + 11) % 12 + 1) + .replace("%p", "" + (d.getHours() < 12 ? "AM" : "PM")) + .replace("%B", "" + this.i18n.months_long[d.getMonth()]) + .replace("%b", "" + this.i18n.months_short[d.getMonth()]) + .replace("%A", "" + this.i18n.days_long[d.getDay()]) + .replace("%a", "" + this.i18n.days_short[d.getDay()]) + .replace("%%", "%"); + + editor.$insertHtml(fmt, true); + + editor.dispatchEvent("pluginexecute", {name: this.name, plugin: this}); + }; + + this.queryState = function() { + return this.state; + }; +}; + +apf.LiveEdit.plugin("insertdate", apf.LiveEdit.dateTimePlugin); +apf.LiveEdit.plugin("inserttime", apf.LiveEdit.dateTimePlugin); + + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/core/baseclasses/liveedit/directions.js)SIZE(1579)TIME(Mon, 10 Jan 2011 10:53:11 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + + +apf.LiveEdit.directionPlugin = function(sName) { + this.name = sName; + this.icon = sName; + this.type = apf.TOOLBARITEM; + this.subType = apf.TOOLBARBUTTON; + this.hook = "ontoolbar"; + this.state = apf.OFF; + + this.execute = function(editor) { + // @todo: implement this baby + editor.dispatchEvent("pluginexecute", {name: this.name, plugin: this}); + }; + + this.queryState = function(editor) { + return this.state; + }; +}; + +apf.LiveEdit.plugin("ltr", apf.LiveEdit.directionPlugin); +apf.LiveEdit.plugin("rtl", apf.LiveEdit.directionPlugin); + + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/core/baseclasses/liveedit/emotions.js)SIZE(4322)TIME(Mon, 10 Jan 2011 10:53:11 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + + +apf.LiveEdit.plugin("emotions", function() { + this.name = "emotions"; + this.icon = "emotions"; + this.type = apf.TOOLBARITEM; + this.subType = apf.TOOLBARPANEL; + this.hook = "ontoolbar"; + this.buttonNode = null; + this.state = apf.OFF; + this.colspan = 4; + this.emotions = []; + + var panelBody; + + this.init = function(editor, btn) { + this.buttonNode.className = this.buttonNode.className + " dropdown_small"; + var oArrow = this.buttonNode.insertBefore(document.createElement("span"), + this.buttonNode.getElementsByTagName("div")[0]); + oArrow.className = "selectarrow"; + }; + + this.execute = function(editor) { + if (!panelBody) { + this.editor = editor; + + // parse smiley images, or 'emotions' + var node, + oNode = editor.$getPluginOption("emotions"), + i = 0, + l = oNode.childNodes.length; + for (; i < l; i++) { + node = oNode.childNodes[i]; + if (node.nodeType == 3 || node.nodeType == 4) + this.emotions = node.nodeValue.splitSafe(","); + } + this.emotionsPath = oNode.getAttribute("path") || ""; + + apf.popup.setContent(this.$uniqueId, this.createPanelBody()); + } + + editor.dispatchEvent("pluginexecute", {name: this.name, plugin: this}); + + this.editor.$showPopup(this, this.$uniqueId, this.buttonNode, 123, 110); + //return button id, icon and action: + return { + id: this.name, + action: null + }; + }; + + this.queryState = function() { + return this.state; + }; + + this.submit = function(e) { + var el = e.target || e.srcElement; + this.editor.$visualFocus(); + var icon = el.getAttribute("rel"); + // @todo still iffy... + if (!icon || icon == null) + icon = el.parentNode.getAttribute("rel"); + if (!icon) return; + apf.popup.forceHide(); + this.editor.$insertHtml('', true); + //this.restoreSelection(); + }; + + this.createPanelBody = function() { + panelBody = document.body.appendChild(document.createElement("div")); + panelBody.className = "editor_popup"; + panelBody.style.display = "none"; + var aHtml = [], + emotions = this.emotions, + path = this.emotionsPath, + rowLen = this.colspan - 1, + i = 0, + l = emotions.length; + for (; i < l; i++) { + if (i % this.colspan == 0) + aHtml.push('
    '); + aHtml.push(''); + if (i % this.colspan == rowLen) + aHtml.push("
    "); + } + panelBody.innerHTML = aHtml.join(""); + + return panelBody; + }; + + this.destroy = function() { + panelBody = null; + delete panelBody; + }; +}); + + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/core/baseclasses/liveedit/fontbase.js)SIZE(8575)TIME(Mon, 10 Jan 2011 10:53:11 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + + +apf.LiveEdit.plugin("fonts", function() { + this.name = "fonts"; + this.icon = "fonts"; + this.type = apf.TOOLBARITEM; + this.subType = apf.TOOLBARPANEL; + this.hook = "ontoolbar"; + this.buttonNode = null; + this.state = apf.OFF; + this.colspan = 1; + this.fontNames = {}; + + var panelBody; + + this.init = function(editor) { + this.buttonNode.className = this.buttonNode.className + " fontpicker"; + this.fontPreview = this.buttonNode.getElementsByTagName("span")[0]; + this.fontPreview.className += " fontpreview"; + var fontArrow = this.buttonNode.insertBefore(document.createElement("span"), + this.buttonNode.getElementsByTagName("div")[0]); + fontArrow.className = "selectarrow"; + + this.editor = editor; + + // parse fonts + var l, j, font, fonts, node; + var oNode = editor.$getPluginOption("fonts").childNodes[0]; + while(oNode) { + fonts = oNode.nodeValue.splitSafe("(?:;|=)"); + if (fonts[0]) { + for (j = 0, l = fonts.length; j < l; j++) + this.fontNames[fonts[j]] = fonts[++j]; + break; + } + oNode = oNode.nextSibling + } + + this.queryState(editor); + }; + + this.execute = function() { + if (!panelBody) + apf.popup.setContent(this.$uniqueId, this.createPanelBody()); + + this.editor.dispatchEvent("pluginexecute", {name: this.name, plugin: this}); + + this.editor.$showPopup(this, this.$uniqueId, this.buttonNode, 120); + //return button id, icon and action: + + return { + id: this.name, + action: null + }; + }; + + this.queryState = function(editor) { + this.state = editor.$queryCommandState("FontName"); + + var currValue = editor.$queryCommandValue("FontName"); + if (!currValue || (this.fontNames[currValue] && this.fontPreview.innerHTML != currValue)) + this.fontPreview.innerHTML = currValue ? currValue : "Font"; + }; + + this.submit = function(e) { + var el = e.target || e.srcElement; + while (el.tagName.toLowerCase() != "a" && el.className != "editor_popup") + el = el.parentNode; + var sFont = el.getAttribute("rel"); + if (sFont) { + apf.popup.forceHide(); + if (apf.isIE) { + this.editor.$selection.set(); + if (this.editor.$selection.isCollapsed()) { + this.editor.$visualFocus(); + var r = this.editor.$selection.getRange(); + r.moveStart("character", -1); + r.select(); + } + } + this.editor.$execCommand("FontName", sFont); + if (apf.isIE) + this.editor.$selection.collapse(false); + } + }; + + this.createPanelBody = function() { + panelBody = document.body.appendChild(document.createElement("div")); + panelBody.className = "editor_popup"; + panelBody.style.display = "none"; + var aHtml = []; + + for (var i in this.fontNames) { + aHtml.push('', i, ""); + } + panelBody.innerHTML = aHtml.join(""); + + return panelBody; + }; + + this.destroy = function() { + panelBody = this.fontPreview = null; + delete panelBody; + delete this.fontPreview; + }; +}); + +apf.LiveEdit.plugin("fontsize", function() { + this.name = "fontsize"; + this.icon = "fontsize"; + this.type = apf.TOOLBARITEM; + this.subType = apf.TOOLBARPANEL; + this.hook = "ontoolbar"; + this.buttonNode = null; + this.state = apf.OFF; + + var panelBody; + + // this hashmap maps font size number to it's equivalent in points (pt) + var sizeMap = { + "1" : "8", + "2" : "10", + "3" : "12", + "4" : "14", + "5" : "18", + "6" : "24", + "7" : "36" + }; + + this.init = function(editor) { + this.buttonNode.className = this.buttonNode.className + " fontsizepicker"; + this.sizePreview = this.buttonNode.getElementsByTagName("span")[0]; + this.sizePreview.className += " fontsizepreview"; + var sizeArrow = this.buttonNode.insertBefore(document.createElement("span"), + this.buttonNode.getElementsByTagName("div")[0]); + sizeArrow.className = "selectarrow"; + + this.queryState(editor); + }; + + this.execute = function(editor) { + if (!panelBody) { + this.editor = editor; + + // parse font sizes + var i, node, oNode = editor.$getPluginOption("fontsizes"); + for (i = 0; i < oNode.childNodes.length; i++) { + node = oNode.childNodes[i]; + if (node.nodeType == 3 || node.nodeType == 4) + this.fontSizes = node.nodeValue.splitSafe(","); + } + + apf.popup.setContent(this.$uniqueId, this.createPanelBody()); + } + this.editor.$showPopup(this, this.$uniqueId, this.buttonNode, 203); + //return button id, icon and action: + return { + id: this.name, + action: null + }; + }; + + this.queryState = function(editor) { + this.state = editor.$queryCommandState("FontSize"); + + var currValue = editor.$queryCommandValue("FontSize") + if (!currValue || this.sizePreview.innerHTML != currValue) + this.sizePreview.innerHTML = currValue ? currValue : "Size"; + }; + + this.submit = function(e) { + var el = e.target || e.srcElement; + while (el.tagName.toLowerCase() != "a" && el.className != "editor_popup") + el = el.parentNode; + var sSize = el.getAttribute("rel"); + if (sSize) { + apf.popup.forceHide(); + if (apf.isIE) { + this.editor.$selection.set(); + if (this.editor.$selection.isCollapsed()) { + this.editor.$visualFocus(); + var r = this.editor.$selection.getRange(); + r.moveStart("character", -1); + r.select(); + } + } + this.editor.$execCommand("FontSize", sSize); + if (apf.isIE) + this.editor.$selection.collapse(false); + } + apf.stopEvent(e); + return false; + }; + + this.createPanelBody = function() { + panelBody = document.body.appendChild(document.createElement("div")); + panelBody.className = "editor_popup"; + panelBody.style.display = "none"; + var aHtml = [], + aSizes = this.fontSizes, + i = 0, + l = aSizes.length; + for (; i < l; i++) { + aHtml.push('', aSizes[i], " (", sizeMap[aSizes[i]], "pt)"); + } + panelBody.innerHTML = aHtml.join(""); + + return panelBody; + }; + + this.destroy = function() { + panelBody = this.sizePreview = null; + delete panelBody; + delete this.sizePreview; + }; +}); + + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/core/baseclasses/liveedit/fontstyle.js)SIZE(25741)TIME(Mon, 10 Jan 2011 10:53:11 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + + +apf.LiveEdit.plugin("fontstyle", function() { + this.name = "fontstyle"; + this.icon = "fontstyle"; + this.type = apf.TOOLBARITEM; + this.subType = apf.TOOLBARPANEL; + this.hook = "ontoolbar"; + this.buttonNode = null; + this.state = apf.OFF; + + var panelBody, oStyles = null, oEditor = null; + + function getStyles(editor) { + if (!oStyles) { + // parse font styles from skin definition + var node, aCss, bCss, oNode = editor.$getPluginOption("fontstyles"); + + if (!oNode || !oNode.childNodes) + throw new Error(apf.formatErrorString(0, editor, + "Initializing plugin: fontstyle", + "No fontstyle block found in skin definition")); + + for (var i = 0, j = oNode.childNodes.length; i < j && !oStyles; i++) { + node = oNode.childNodes[i]; + if (node.nodeType == 3 || node.nodeType == 4) { + oStyles = {}; + aCss = []; + bCss = []; + + node.nodeValue.replace(/([\w ]+)\s*=\s*(([^\{]+?)\s*\{[\s\S]*?\})\s*/g, + function (m, caption, css, className) { + + if (!css || css.charAt(css.length - 1) != "}") + throw new Error(apf.formatErrorString(0, editor, + "Initializing plugin: fontstyle", + "Invalid fontstyle block, please check if formatting rules have been applied")); + + if (css.charAt(0) != ".") + css = "." + css; + css = css.trim().replace(/[\s]+/g, ""); + className = className.trim().replace(/\./, ""); + oStyles[className] = { + caption: caption.trim(), + cname : className, + css : css, + node : null + }; + aCss.push(css); + bCss.push(".editor_fontstyle " + css); + } + ); + } + } + + if (aCss.length) { + // insert resulting CSS into container document AND inside the + // document of the editor's iframe + apf.importCssString(bCss.join("")); + apf.importCssString(aCss.join(""), editor.$activeDocument); + if (apf.isIE) { + // removing text nodes from the HEAD section, which are added + // by IE in some cases. + var nodes = editor.$activeDocument.getElementsByTagName("head")[0].childNodes, + cnt = nodes.length - 1; + while (cnt) { + if (nodes[cnt].nodeType == 3) //text + nodes[cnt].parentNode.removeChild(nodes[cnt]); + cnt--; + } + } + } + } + return oStyles; + } + + this.init = function(editor) { + oEditor = editor; + + this.buttonNode.className = this.buttonNode.className + " fontstylepicker"; + this.stylePreview = this.buttonNode.getElementsByTagName("span")[0]; + this.stylePreview.className += " fontstylepreview"; + var styleArrow = this.buttonNode.appendChild(document.createElement("span")); + styleArrow.className = "selectarrow"; + + this.queryState(editor); + }; + + this.execute = function(editor) { + if (!panelBody) { + oEditor = editor; + + apf.popup.setContent(this.$uniqueId, this.createPanelBody(editor)); + } + + editor.dispatchEvent("pluginexecute", {name: this.name, plugin: this}); + + oEditor.$showPopup(this, this.$uniqueId, this.buttonNode, 203); + //return button id, icon and action: + return { + id: this.name, + action: null + }; + }; + + function getCurrentStyle() { + getStyles(oEditor); + + var oNode = oEditor.$selection.getSelectedNode(); + while (oNode && oNode.nodeType != 1) // we need a block element + oNode = oNode.parentNode; + + var oCurrent; + while (!oCurrent && oNode && oNode.tagName != "BODY") { + var cs = oNode.className; + for (var i in oStyles) { + if (cs.indexOf(i) > -1) { + oCurrent = oStyles[i]; + oCurrent.node = oNode; + } + } + oNode = oNode.parentNode; + } + + return oCurrent; + } + + this.submit = function(e, sStyle) { + if (!sStyle) { + el = e.target || e.srcElement; + while (el.tagName.toLowerCase() != "a" && el.className != "editor_popup") + el = el.parentNode; + sStyle = el.getAttribute("rel"); + } + + if (sStyle) { + apf.popup.forceHide(); + var sel = oEditor.$selection; + + sel.set(); + oEditor.$visualFocus(); + + var o = getCurrentStyle(oEditor); + + if (o && sStyle == "normal") { + var n = o.node.childNodes, p = o.node.parentNode; + while (n.length) { + p.insertBefore(n[0], o.node); + } + p.removeChild(o.node); + + this.queryState(oEditor); + } + else if (o && (sel.isCollapsed() + || sel.getContent("text") == o.node.innerHTML) + && apf.isChildOf(o.node, sel.getSelectedNode(), true)) { + if (o.cname == sStyle) return; + apf.setStyleClass(o.node, sStyle, [o.cname]); + } + else { + if (sel.isCollapsed()) { + if (apf.isIE) { + var oNode = sel.getRange().parentElement(); + var p = oEditor.$activeDocument.createElement("span"); + p.className = sStyle; + p.innerHTML = oNode.innerHTML; + if (oNode.tagName == "BODY") { + oNode.innerHTML = ""; + oNode.appendChild(p); + } + else { + oNode.parentNode.insertBefore(p, oNode); + oNode.parentNode.removeChild(oNode); + } + sel.selectNode(p); + } + else { + var range = sel.getRange(); + var oCaret = range.commonAncestorContainer; + range.setStartBefore(oCaret); + range.setEndAfter(oCaret); + sel.setRange(range); + var htmlNode = sel.setContent('' + + sel.getContent() + ""); + sel.selectNode(htmlNode); + } + } + else { + //s.match(/^([\s\S]*?)(<(?:normal|pre|p|address|h1|h2|h3|h4|h5|h6)[\s\S]*?<\/(?:normal|pre|p|address|h1|h2|h3|h4|h5|h6)>)([\s\S]*?)$/gi) + var s = sel.getContent().trim(); + var shouldPrefixSpan = s.substr(0,5) == "|<\/SPAN>/gi, ""); + if (s.charAt(0) == "<") { + s = s + .replace(/<(normal|pre|p|address|h1|h2|h3|h4|h5|h6)(?:\s.*?|)>(.+?)<\/(normal|pre|p|address|h1|h2|h3|h4|h5|h6)>/gi, + '<$1>$2') + .replace(/^([\s\S]*?)(<(?:normal|pre|p|address|h1|h2|h3|h4|h5|h6)[\s\S]*<\/(?:normal|pre|p|address|h1|h2|h3|h4|h5|h6)>)([\s\S]*?)$/gi, + function(m, m1, m2, m3){ + return (m1 ? '' + m1 + "" : "") + m2 + (m3 ? '' + m3 + "" : ""); + }) + .replace(/^\s*<(?:normal|pre|p|address|h1|h2|h3|h4|h5|h6)(?:\s.*?|)>|<\/(?:normal|pre|p|address|h1|h2|h3|h4|h5|h6)>\s*$/gi, ""); + if (apf.isIE) + s = s.replace(/<\/P>/, ""); + } + else { + s = '' + s + ""; + } + + if (shouldPrefixSpan) + s = "
    " + s.replace(/<\/SPAN>$/i, ""); + + var htmlNode = sel.setContent(s, true); + sel.selectNode(htmlNode); + } + } + + oEditor.$restoreFocus(); + // Notify the SmartBindings we've changed... + + oEditor.change(oEditor.getValue()); + + } + }; + + this.queryState = function() { + var o = getCurrentStyle(); + if (o) { + if (this.stylePreview.innerHTML != o.caption) + this.stylePreview.innerHTML = o.caption; + this.state = apf.ON; + } + else { + this.stylePreview.innerHTML = "Style"; + this.state = apf.OFF; + } + + return this.state; + }; + + this.createPanelBody = function(editor) { + panelBody = document.body.appendChild(document.createElement("div")); + panelBody.className = "editor_popup"; + panelBody.style.display = "none"; + + getStyles(editor); + var aHtml = ['Normal']; + for (var i in oStyles) { + aHtml.push('', + oStyles[i].caption, "") + } + panelBody.innerHTML = aHtml.join(""); + + return panelBody; + }; + + this.destroy = function() { + panelBody = this.stylePreview = oEditor = null; + delete panelBody; + delete this.stylePreview; + delete oEditor; + }; +}); + +//############################################################################## + +apf.LiveEdit.plugin("blockformat", function() { + this.name = "blockformat"; + this.icon = "blockformat"; + this.type = apf.TOOLBARITEM; + this.subType = apf.TOOLBARPANEL; + this.hook = "ontoolbar"; + this.buttonNode = null; + this.state = apf.OFF; + this.node = null; + + var panelBody, oEditor, + + // this hashmap maps font size number to it's equivalent in points (pt) + blocksMap = { + "normal" : "Normal", + "p" : "Paragraph", + "pre" : "Preformatted", + "address" : "Address", + "h1" : "Header 1", + "h2" : "Header 2", + "h3" : "Header 3", + "h4" : "Header 4", + "h5" : "Header 5", + "h6" : "Header 6" + }, + blocksRE, blocksRE2, blocksRE3, blocksRE4, blockFormats; + + function getFormats(editor) { + if (!blockFormats) { + // parse font styles from skin definition + var i, j, node, oNode = editor.$getPluginOption("blockformats"); + + if (!oNode || !oNode.childNodes) + throw new Error(apf.formatErrorString(0, editor, + "Initializing plugin: Blockformat", + "No block formats found in skin definition")); + + for (i = 0, j = oNode.childNodes.length; i < j; i++) { + node = oNode.childNodes[i]; + if (node.nodeType == 3 || node.nodeType == 4) + blockFormats = node.nodeValue.splitSafe(","); + } + + var sJoin = "(" + blockFormats.join("|") + ")"; + blocksRE = new RegExp("^" + sJoin + "$", "gi"); + blocksRE2 = new RegExp("<\\/?" + sJoin + ">", "gi"); + blocksRE3 = new RegExp("<\\/?(" + blockFormats.join("|") + "|p)>", "gi"); + blocksRE4 = new RegExp("^(" + blockFormats.join("|") + "|p)$", "gi"); + } + return blockFormats; + } + + this.init = function(editor) { + oEditor = editor; + this.buttonNode.className = this.buttonNode.className + " blockformatpicker"; + this.blockPreview = this.buttonNode.getElementsByTagName("span")[0]; + this.blockPreview.className += " blockformatpreview"; + var blockArrow = this.buttonNode.appendChild(document.createElement("span")); + blockArrow.className = "selectarrow"; + + this.queryState(editor); + }; + + this.execute = function(editor) { + if (!panelBody) { + oEditor = editor; + + apf.popup.setContent(this.$uniqueId, this.createPanelBody(editor)); + } + oEditor.$showPopup(this, this.$uniqueId, this.buttonNode, 203); + //return button id, icon and action: + return { + id: this.name, + action: null + }; + }; + + this.queryState = function() { + var oNode = oEditor.$selection.getSelectedNode(), + aFormats = getFormats(oEditor), + /*bCurrent = (oNode && oNode.nodeType == 1 + && aFormats.contains(oNode.tagName.toLowerCase())), + bParent = (oNode && oNode.parentNode && oNode.parentNode.nodeType == 1 + && aFormats.contains(oNode.parentNode.tagName.toLowerCase())),*/ + tagName = oNode.nodeType == 1 ? oNode.tagName.toLowerCase() : ""; + + while (tagName && !tagName.match(blocksRE) && tagName != "body") { + oNode = oNode.parentNode; + tagName = (oNode.tagName || "").toLowerCase(); + } + if (tagName.match(blocksRE)) {//bCurrent || bParent) { + var sBlock = blocksMap[tagName]; + if (this.blockPreview.innerHTML != sBlock) + this.blockPreview.innerHTML = sBlock; + this.state = apf.ON; + this.node = oNode; + } + else { + this.blockPreview.innerHTML = "Normal"; + this.state = apf.OFF; + this.node = null; + } + return this.state; + }; + + this.submit = function(e, sBlock) { + if (!sBlock) { + var el = e.target || e.srcElement; + while (el.tagName.toLowerCase() != "a" && el.className != "editor_popup") + el = el.parentNode; + sBlock = el.getAttribute("rel"); + } + + if (sBlock) { + apf.popup.forceHide(); + var oNode, sel = oEditor.$selection; + + sel.set(); + oEditor.$visualFocus(); + var s = sel.getContent(); + if (sBlock == "normal" && this.queryState(oEditor) == apf.ON) { + // revert style to NORMAL, i.e. no style at all. + /*sel.selectNode(this.node); + sel.setContent(this.node.innerHTML);*/ + + var n = this.node.childNodes, p = this.node.parentNode; + + if (apf.isIE) { + //var textlength = sel.getContent("text").length; + var l = p.insertBefore(p.ownerDocument.createElement("p"), this.node); + + while (n.length) { + l.insertBefore(n[0], l.firstChild); + } + + p.removeChild(this.node); + sel.selectNode(l); + if (l.previousSibling && l.previousSibling.tagName == "P") { + if (l.previousSibling.innerHTML == "") { + l.parentNode.removeChild(l.previousSibling); + } + } + } + else { + while (n.length) { + p.insertBefore(n[0], this.node); + } + + p.removeChild(this.node); + } + + this.state = apf.OFF; + this.node = null; + this.blockPreview.innerHTML = "Normal"; + } + else if (sel.isCollapsed() || s.trim() == "") { + if (apf.isIE) { + var startNode, oNode; + oNode = startNode = sel.getRange().parentElement(); + while(!oNode.tagName.match(blocksRE4) && oNode.tagName != "BODY") { + oNode = oNode.parentNode; + } + + if (oNode && oNode.tagName == "BODY") { + if (startNode != oNode) + oNode = startNode; + else { + //r = sel.getRange();r.moveEnd("character", 500); r.htmlText + } + } + + var p = oEditor.$activeDocument.createElement(sBlock); + p.innerHTML = oNode.innerHTML; + if (oNode.tagName == "BODY") { + oNode.innerHTML = ""; + oNode.appendChild(p); + } + else { + oNode.parentNode.insertBefore(p, oNode); + oNode.parentNode.removeChild(oNode); + } + sel.selectNode(p); + } + else { + oEditor.$execCommand("FormatBlock", sBlock); + } + + this.blockPreview.innerHTML = blocksMap[sBlock]; + } + else { + oNode = sel.getSelectedNode(); + while (oNode.nodeType != 1) + oNode = oNode.parentNode; + + // @todo FF is DEFINITELY b0rking when we try to nest HTML 4.01 block elements... + // REALLY not like Word does it... + if (oNode.tagName.match(blocksRE4) && s.length == oNode[apf.hasInnerText ? "innerText" : "textContent"].length) { + var p = oEditor.$activeDocument.createElement(sBlock); + p.innerHTML = oNode.innerHTML; + oNode.parentNode.insertBefore(p, oNode); + oNode.parentNode.removeChild(oNode); + sel.selectNode(p); + } + else { + while(!oNode.tagName.match(blocksRE4) && oNode.tagName != "BODY") { + oNode = oNode.parentNode; + } + if (oNode && oNode.tagName != "BODY") { + var s2; + if (oNode.tagName == "P" && apf.isIE) { + s2 = "<" + sBlock + ">" + s.trim().replace(blocksRE3, "") + ""; + addedNode = sel.setContent(s2); + } + else { + s2 = '

    ' + s + "

    "; + sel.setContent(s2); + + var sBlock2 = oNode.tagName; + var html = [], first, last; + var strHtml = oNode.innerHTML.replace(s2, function(m, pos){ + return (pos != 0 + ? (first = true) && "" + : "") + + "<" + sBlock + ' __apf_placeholder="true">' + s.replace(blocksRE3, "") + + "" + + (pos < oNode.innerHTML.length - s.length + ? (last = true) && "<" + sBlock2 + ">" + : ""); + }); + if (first) + html.push("<" + sBlock2 + ">"); + html.push(strHtml); + if (last) + html.push(""); + + oNode.innerHTML = html.join(""); + var addedNode, n = oNode.getElementsByTagName(sBlock); + for (var i = 0; i < n.length; i++) { + if (n[i].getAttribute("__apf_placeholder")) { + n[i].removeAttribute("__apf_placeholder"); + addedNode = n[i]; + break; + } + } + + n = oNode.childNodes, p = oNode.parentNode; + while (n.length) + p.insertBefore(n[0], oNode); + p.removeChild(oNode); + } + + if (addedNode) { + if (apf.isIE) { + var prev = addedNode.previousSibling + if (prev && prev.tagName == "P" && prev.innerHTML == " ") + prev.parentNode.removeChild(prev); + + //@todo make this a setting? + /*addedNode.parentNode.insertBefore( + addedNode.ownerDocument.createElement("P"), + addedNode);*/ + } + sel.selectNode(addedNode); + } + } + else { + var addedNode = sel.setContent("<" + sBlock + ">" + + s.replace(/

    (.*?)<\/p>(.)/gi, "$1
    $2") + .replace(blocksRE3, "") + ""); + + if (apf.isIE) { + var prev = addedNode.previousSibling + if (prev && prev.tagName == "P" && prev.innerHTML == " ") + prev.parentNode.removeChild(prev); + + //@todo make this a setting? + /*addedNode.parentNode.insertBefore( + addedNode.ownerDocument.createElement("P"), + addedNode);*/ + } + + sel.selectNode(addedNode); + } + } + + this.blockPreview.innerHTML = blocksMap[sBlock]; + } + + oEditor.$restoreFocus(); + // Notify the SmartBindings we've changed... + + oEditor.change(oEditor.getValue()); + + } + }; + + this.createPanelBody = function(editor) { + panelBody = document.body.appendChild(document.createElement("div")); + panelBody.className = "editor_popup"; + panelBody.style.display = "none"; + + var aHtml = [], + aFormats = getFormats(editor); + for (var i = 0, j = aFormats.length; i < j; i++) { + aHtml.push('<', aFormats[i], ">", + blocksMap[aFormats[i]], ""); + } + panelBody.innerHTML = aHtml.join(""); + + return panelBody; + }; + + this.destroy = function() { + panelBody = oEditor = this.blockPreview = this.node = null; + delete panelBody; + delete oEditor; + delete this.blockPreview; + delete this.node; + }; +}); + + + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/core/baseclasses/liveedit/help.js)SIZE(1485)TIME(Mon, 10 Jan 2011 10:53:11 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + + +apf.LiveEdit.plugin("help", function(){ + this.name = "help"; + this.icon = "help"; + this.type = apf.TOOLBARITEM; + this.subType = apf.TOOLBARBUTTON; + this.hook = "ontoolbar"; + this.keyBinding = "ctrl+h"; + this.state = apf.OFF; + + this.execute = function(editor) { + // @todo: implement this plugin + editor.dispatchEvent("pluginexecute", {name: this.name, plugin: this}); + }; + + this.queryState = function(editor) { + return this.state; + }; +}); + + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/core/baseclasses/liveedit/hr.js)SIZE(1593)TIME(Mon, 10 Jan 2011 10:53:11 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + + +apf.LiveEdit.plugin("hr", function(){ + this.name = "hr"; + this.icon = "hr"; + this.type = apf.TOOLBARITEM; + this.subType = apf.TOOLBARBUTTON; + this.hook = "ontoolbar"; + this.keyBinding = "ctrl+h"; + this.state = apf.OFF; + + this.execute = function(editor) { + if (apf.isGecko || apf.isIE) + editor.$insertHtml("


    ", true); + else + editor.$execCommand("InsertHorizontalRule"); + + editor.dispatchEvent("pluginexecute", {name: this.name, plugin: this}); + }; + + this.queryState = function(editor) { + return this.state; + }; +}); + + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/core/baseclasses/liveedit/image.js)SIZE(5033)TIME(Mon, 10 Jan 2011 10:53:11 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + + +apf.LiveEdit.plugin("image", function(){ + this.name = "image"; + this.icon = "image"; + this.type = apf.TOOLBARITEM; + this.subType = apf.TOOLBARPANEL; + this.hook = "ontoolbar"; + this.keyBinding = "ctrl+alt+i"; + this.state = apf.OFF; + + var panelBody; + + this.init = function(editor) { + this.buttonNode.className = this.buttonNode.className + " dropdown_small"; + var oArrow = this.buttonNode.insertBefore(document.createElement("span"), + this.buttonNode.getElementsByTagName("div")[0]); + oArrow.className = "selectarrow"; + }; + + this.execute = function(editor) { + if (!panelBody) { + this.editor = editor; + apf.popup.setContent(this.$uniqueId, this.createPanelBody()); + } + + editor.dispatchEvent("pluginexecute", {name: this.name, plugin: this}); + + // @todo: auto-fill input with currently selected image url + this.editor.$showPopup(this, this.$uniqueId, this.buttonNode, 218, 47); + var _self = this; + $setTimeout(function() { + _self.oUrl.focus(); + }); + //return button id, icon and action: + return { + id: this.name, + action: null + }; + }; + + this.queryState = function(editor) { + return this.state; + }; + + this.submit = function(e) { + var sUrl = this.oUrl.value; + if (sUrl) { + apf.popup.forceHide(); + var oUrl = new apf.url(sUrl); + if (!oUrl.protocol || !oUrl.host || !oUrl.file) + alert("Please enter a valid URL"); + else + this.editor.$insertHtml('', true); + } + }; + + this.createPanelBody = function() { + panelBody = document.body.appendChild(document.createElement("div")); + panelBody.className = "editor_popup"; + panelBody.style.display = "none"; + var idUrl = "editor_" + this.$uniqueId + "_input"; + var idBtns = "editor_" + this.$uniqueId + "_btns"; + panelBody.innerHTML = + '
    \ + \ + \ +
    \ +
    '; + this.oUrl = document.getElementById(idUrl); + this.appendAmlNode( + '\ + \ + ', + document.getElementById(idBtns)); + + + if (apf.hasFocusBug) { + apf.sanitizeTextbox(this.oUrl); + this.oUrl.onselectstart = function(e) { + e = e || window.event; + e.cancelBubble = true; + }; + } + + + return panelBody; + }; + + this.destroy = function() { + panelBody = this.oUrl = null; + delete panelBody; + delete this.oUrl; + }; +}); + +apf.LiveEdit.plugin("imagespecial", function() { + this.name = "imagespecial"; + this.icon = "image"; + this.type = apf.TOOLBARITEM; + this.subType = apf.TOOLBARBUTTON; + this.hook = "ontoolbar"; + this.keyBinding = "ctrl+alt+j"; + this.state = apf.OFF; + + var winHandle; + + this.execute = function(editor) { + if (!winHandle) { + // get window handle from editor AML attribute + var s = (editor.getAttribute("imagewindow") || "").trim(); + if (s) + winHandle = self[s]; + } + + if (winHandle && winHandle.show) + winHandle.show(); + + editor.dispatchEvent("pluginexecute", {name: this.name, plugin: this}); + }; + + this.queryState = function(editor) { + return this.state; + }; +}); + + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/core/baseclasses/liveedit/links.js)SIZE(7721)TIME(Mon, 10 Jan 2011 10:53:11 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + + +apf.LiveEdit.plugin("link", function(){ + this.name = "link"; + this.icon = "link"; + this.type = apf.TOOLBARITEM; + this.subType = apf.TOOLBARPANEL; + this.hook = "ontoolbar"; + this.keyBinding = "ctrl+shift+l"; + this.state = apf.OFF; + + var panelBody; + + this.init = function(editor, btn) { + this.buttonNode.className = this.buttonNode.className + " dropdown_small"; + var oArrow = this.buttonNode.insertBefore(document.createElement("span"), + this.buttonNode.getElementsByTagName("div")[0]); + oArrow.className = "selectarrow"; + }; + + this.execute = function(editor) { + if (!panelBody) { + this.editor = editor; + apf.popup.setContent(this.$uniqueId, this.createPanelBody()); + } + + editor.dispatchEvent("pluginexecute", {name: this.name, plugin: this}); + + this.oUrl.value = "http://"; + this.editor.$showPopup(this, this.$uniqueId, this.buttonNode, 218, 95); + if (panelBody.style.visibility == "hidden") + panelBody.style.visibility = "visible"; + var _self = this; + $setTimeout(function() { + _self.oUrl.focus(); + }); + //return button id, icon and action: + return { + id: this.name, + action: null + }; + }; + + this.queryState = function(editor) { + if (editor.$selection.isCollapsed() || editor.$selection.getSelectedNode().tagName == "A") + return apf.DISABLED; + return this.state; + }; + + this.submit = function(e) { + apf.popup.forceHide(); + + if (!this.oUrl.value.replace("http://", "")) return; + + this.editor.$execCommand("CreateLink", "javascript:apftmp(0);"); + var oLink, + oEditor = this.editor, + aLinks = oEditor.$activeDocument.getElementsByTagName("a"), + i = 0, + l = aLinks.length; + for (; i < l && !oLink; i++) + if (aLinks[i].href == "javascript:apftmp(0);") + oLink = aLinks[i]; + if (oLink) { + var val = this.oUrl.value; + oLink.href = (!val.match(/^[a-zA-Z]+\:/) ? "http://" : "") + val; + oLink.target = this.oTarget.value; + oLink.title = this.oTitle.value; + } + oEditor.$selection.collapse(false); + + oEditor.$restoreFocus(); + + // propagate the change AFTER changing back the link to its proper format + oEditor.change(oEditor.getValue()); + + apf.stopEvent(e); + return false; + }; + + this.createPanelBody = function() { + panelBody = document.body.appendChild(document.createElement("div")); + panelBody.className = "editor_popup"; + panelBody.style.display = "none"; + var idUrl = "editor_" + this.$uniqueId + "_link_url"; + var idTarget = "editor_" + this.$uniqueId + "_link_target"; + var idTitle = "editor_" + this.$uniqueId + "_link_title"; + var idBtns = "editor_" + this.$uniqueId + "_link_btns"; + panelBody.innerHTML = + '
    \ + \ + \ +
    \ +
    \ + \ + \ +
    \ +
    \ + \ + \ +
    \ +
    '; + + this.oUrl = document.getElementById(idUrl); + this.oTarget = document.getElementById(idTarget); + this.oTitle = document.getElementById(idTitle); + + + if (apf.hasFocusBug) { + apf.sanitizeTextbox(this.oUrl); + apf.sanitizeTextbox(this.oTarget); + apf.sanitizeTextbox(this.oTitle); + this.oUrl.onselectstart = this.oTarget.onselectstart = + this.oTitle.onselectstart = function(e) { + e = e || window.event; + e.cancelBubble = true; + }; + } + + + new apf.toolbar({ + htmlNode: document.getElementById(idBtns), + skinset: apf.getInheritedAttribute(this.editor.parentNode, "skinset"), + childNodes: [ + new apf.bar({ + childNodes: [new apf.button({ + caption: this.editor.$translate("insert"), + onclick: "apf.lookup(" + this.$uniqueId + ").submit(event)" + })] + }) + ] + }); + + return panelBody; + }; + + this.destroy = function() { + panelBody = this.oUrl = this.oTarget = this.oTitle = null; + delete panelBody; + delete this.oUrl; + delete this.oTarget; + delete this.oTitle; + }; +}); + +apf.LiveEdit.plugin("unlink", function(){ + this.name = "unlink"; + this.icon = "unlink"; + this.type = apf.TOOLBARITEM; + this.subType = apf.TOOLBARBUTTON; + this.hook = "ontoolbar"; + this.keyBinding = "ctrl+shift+l"; + this.state = apf.OFF; + + this.execute = function(editor) { + if (this.queryState(editor) == apf.DISABLED) + return; + + if (apf.isIE) { + editor.$execCommand("Unlink"); + } + else { + var sel = editor.$selection; + sel.set(); + var oNode = sel.getSelectedNode(); + if (oNode.tagName == "A") { + var txt = oNode.innerHTML; + sel.selectNode(oNode); + sel.remove(); + sel.collapse(); + editor.$insertHtml(txt); + } + } + }; + + this.queryState = function(editor) { + //if (!editor.$selection) + // console.dir(editor); + if (editor.$selection.getSelectedNode().tagName == "A") + return apf.OFF; + + return apf.DISABLED; + }; +}); + + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/core/baseclasses/liveedit/list.js)SIZE(4641)TIME(Mon, 10 Jan 2011 10:53:11 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + + +apf.LiveEdit.listPlugin = function(sName) { + this.name = sName; + this.icon = sName; + this.type = apf.TOOLBARITEM; + this.subType = apf.TOOLBARBUTTON; + this.hook = "ontoolbar"; + this.keyBinding = sName == "bullist" ? "ctrl+shift+u" : "ctrl+shift+o"; + this.state = apf.OFF; + + var emptyRegex = apf.isIE + ? /^( )?]*_apf_placeholder(="1"> )?<\/DIV>$/gi + : /^( )?$/gi; + + this.execute = function(editor) { + editor.$execCommand(this.name == "bullist" + ? "InsertUnorderedList" + : "InsertOrderedList"); + + this.correctLists(editor); + editor.$visualFocus(); + + editor.dispatchEvent("pluginexecute", {name: this.name, plugin: this}); + }; + + function moveListItems(from, to) { + var i, oNode, oLastNode, + listNode = (this.name == "bullist") ? "OL" : "UL"; + for (i = from.childNodes.length; i >= 0; i--) { + oNode = from.childNodes[i]; + if (!oNode) continue; + if (oNode.tagName == listNode) break; + if (!oLastNode) + to.appendChild(oNode); + else + to.insertBefore(oNode, oLastNode); + oLastNode = oNode; + } + from.parentNode.removeChild(from); + } + + function getEmptyLi(oParent) { + if (!oParent || oParent.nodeType != 1) return; + var sHtml, aNodes = oParent.getElementsByTagName("li"); + for (var i = 0, j = aNodes.length; i < j; i++) { + sHtml = aNodes[i].innerHTML.trim(); + if (sHtml == "" || sHtml == " " || sHtml.match(emptyRegex)) + return aNodes[i]; + } + return null; + } + + this.correctLists = function(editor) { + editor.$selection.set(); + + var oNode = editor.$selection.getSelectedNode(); + //window.console.log("correcting lists0: ", oNode); + //window.console.dir(editor.$selection.getRange()); + if (oNode.tagName != "LI") { + oNode = getEmptyLi(oNode); + if (!oNode || oNode.tagName != "LI") + return false; + } + var oParent = oNode.parentNode, + oSiblingP = oNode.parentNode.previousSibling, + oHasBr = null; + if (!oSiblingP) return false + if (oSiblingP && oSiblingP.tagName == "BR") { + oHasBr = oSiblingP; + oSiblingP = oSiblingP.previousSibling; + } + var oSibling = (oSiblingP && oSiblingP.tagName == oParent.tagName) + ? oSiblingP + : null; + if (!oSibling) return; + if (oHasBr) + oParent.removeChild(oHasBr); + + moveListItems(oParent, oSibling); + + //while (oSibling.nextSibling && oSibling.tagName == oSibling.nextSibling.tagName) + // moveListItems(oSibling.nextSibling, oSibling); + + editor.$selection.selectNode(oNode); + if (!apf.isIE) + editor.$selection.getRange().setStart(oNode, 0); + editor.$selection.collapse(!apf.isIE); + editor.$visualFocus(); + return true; + }; + + this.correctIndentation = function(editor, dir) { + //this.correctLists(editor); + editor.$execCommand(dir); + this.correctLists(editor); + }; + + this.queryState = function(editor) { + var state = editor.$queryCommandState(this.name == "bullist" + ? "InsertUnorderedList" + : "InsertOrderedList"); + if (state == apf.DISABLED) + return apf.OFF; + return state; + }; +}; + +apf.LiveEdit.plugin("bullist", apf.LiveEdit.listPlugin); +apf.LiveEdit.plugin("numlist", apf.LiveEdit.listPlugin); + + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/core/baseclasses/liveedit/media.js)SIZE(1489)TIME(Mon, 10 Jan 2011 10:53:11 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + + +apf.LiveEdit.plugin("media", function(){ + this.name = "media"; + this.icon = "media"; + this.type = apf.TOOLBARITEM; + this.subType = apf.TOOLBARBUTTON; + this.hook = "ontoolbar"; + this.keyBinding = "ctrl+m"; + this.state = apf.OFF; + + this.execute = function(editor) { + // @todo: implement this plugin + editor.dispatchEvent("pluginexecute", {name: this.name, plugin: this}); + }; + + this.queryState = function(editor) { + return this.state; + }; +}); + + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/core/baseclasses/liveedit/printing.js)SIZE(2098)TIME(Mon, 10 Jan 2011 10:53:11 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + + +apf.LiveEdit.plugin("print", function(){ + this.name = "print"; + this.icon = "print"; + this.type = apf.TOOLBARITEM; + this.subType = apf.TOOLBARBUTTON; + this.hook = "ontoolbar"; + this.keyBinding = "ctrl+p"; + this.state = apf.OFF; + + this.execute = function(editor) { + if (apf.print) + apf.print(editor.getValue()); + + editor.dispatchEvent("pluginexecute", {name: this.name, plugin: this}); + }; + + this.queryState = function() { + return this.state; + }; +}); + +apf.LiveEdit.plugin("preview", function(){ + this.name = "preview"; + this.icon = "preview"; + this.type = apf.TOOLBARITEM; + this.subType = apf.TOOLBARBUTTON; + this.hook = "ontoolbar"; + this.keyBinding = "ctrl+shift+p"; + this.state = apf.OFF; + + this.execute = function(editor) { + if (apf.printer) + apf.printer.preview(editor.getValue()).show(); + + editor.dispatchEvent("pluginexecute", {name: this.name, plugin: this}); + }; + + this.queryState = function() { + return this.state; + }; +}); + + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/core/baseclasses/liveedit/search.js)SIZE(10436)TIME(Mon, 10 Jan 2011 10:53:11 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + + +apf.LiveEdit.searchPlugin = function(sName) { + this.name = sName; + this.icon = sName; + this.type = apf.TOOLBARITEM; + this.subType = apf.TOOLBARPANEL; + this.hook = "ontoolbar"; + this.keyBinding = this.name == "search" ? "ctrl+f" : "ctrl+shift+f"; + this.state = apf.OFF; + + var panelBody; + + this.init = function(editor, btn) { + this.buttonNode.className = this.buttonNode.className + " dropdown_small"; + var oArrow = this.buttonNode.insertBefore(document.createElement("span"), + this.buttonNode.getElementsByTagName("div")[0]); + oArrow.className = "selectarrow"; + }; + + this.execute = function(editor) { + if (!panelBody) { + this.editor = editor; + apf.popup.setContent(this.$uniqueId, this.createPanelBody()); + } + + editor.dispatchEvent("pluginexecute", {name: this.name, plugin: this}); + + this.editor.$showPopup(this, this.$uniqueId, this.buttonNode, 218, + this.name == "search" ? 71 : 95); + // prefill search box with selected text + this.oSearch.value = this.editor.$selection.getContent(); + var _self = this; + $setTimeout(function() { + _self.oSearch.focus(); + }); + //return button id, icon and action: + + return { + id: this.name, + action: null + }; + }; + + this.queryState = function(editor) { + return this.state; + }; + + this.submit = function(e) { + var val = this.oSearch.value, bMatchCase = this.oCase.checked, flag = 0; + if (!val) + return; + + if (apf.isIE) + this.editor.$selection.set(); + //this.editor.$execCommand("SelectAll"); + this.editor.$selection.collapse(false); + this.editor.$visualFocus(); + + if (bMatchCase) //IE specific flagging + flag = flag | 4; + + var found = false; + + if (apf.isIE) { + var sel = this.editor.$selection; + var range = sel.getRange(); + if (!(found = range.findText(val, 1, flag))) { + // simulate 'wrapAround' search... + this.editor.$activeDocument.execCommand("SelectAll"); + sel.collapse(true); + range = sel.getRange(); + //no chaining of calls here, seems to b0rk selection in IE + found = range.findText(val, 1, flag); + } + if (found) { + range.scrollIntoView(); + range.select(); + } + //this.storeSelection(); + sel.cache(); + } + else { + if (this.editor.oWin.find(val, bMatchCase, false, true, false, false, false)) + found = true; + } + if (this.oReplBtn) + this.oReplBtn[!found ? "disable" : "enable"](); + + if (!found) { + if (this.oReplBtn) + this.oReplBtn.disable(); + alert("No occurences found for '" + val + "'"); + } + else if (this.oReplBtn) + this.oReplBtn.enable(); + + if (e.stop) + e.stop(); + else + e.cancelBubble = true; + + if (!apf.isIE) { + // IE cannot show the selection anywhere else then where the cursor + // is, so no show for them users... + var _self = this; + $setTimeout(function() { + _self.oSearch.focus(); + }); + } + + return false; + }; + + this.onDoReplClick = function(e) { + this.replace(); + }; + + this.onReplAllClick = function(e) { + var val = this.oSearch.value, bMatchCase = this.oCase.checked, flag = 0, + ed = this.editor; + if (!val) + return; + + // Move caret to beginning of text + this.editor.$execCommand("SelectAll"); + this.editor.$selection.collapse(true); + this.editor.$visualFocus(); + + var range = this.editor.$selection.getRange(), found = 0; + + if (bMatchCase) //IE specific flagging + flag = flag | 4; + + if (apf.isIE) { + while (range.findText(val, 1, flag)) { + range.scrollIntoView(); + range.select(); + this.replace(); + found++; + } + this.editor.$selection.cache(); + //this.storeSelection(); + } + else { + while (this.editor.oWin.find(val, bMatchCase, false, false, false, false, false)) { + this.replace(); + found++; + } + } + + if (found > 0) + alert(found + " occurences found and replaced with '" + this.oReplace.value + "'"); + else + alert("No occurences found for '" + val + "'"); + }; + + this.replace = function() { + var sRepl = this.oReplace.value; + // Needs to be duplicated due to selection bug in IE + if (apf.isIE) { + //this.editor.$selection.set(); //Change by RLD + this.editor.$selection.getRange().duplicate().pasteHTML(sRepl); + } + else + this.editor.$activeDocument.execCommand("InsertHTML", false, sRepl); + }; + + this.createPanelBody = function() { + panelBody = document.body.appendChild(document.createElement("div")); + panelBody.className = "editor_popup"; + panelBody.style.display = "none"; + var idSearch = "editor_" + this.$uniqueId + "_input", + idReplace = "editor_" + this.$uniqueId + "_replace", + idCase = "editor_" + this.$uniqueId + "_case", + idBtns = "editor_" + this.$uniqueId + "_btns"; + panelBody.innerHTML = + '
    \ + \ + \ +
    ' + + (this.name == "replace" ? + '
    \ + \ + \ +
    ' : '') + + '
    \ + \ + \ +
    \ +
    '; + this.oSearch = document.getElementById(idSearch); + this.oCase = document.getElementById(idCase); + + new apf.toolbar({ + htmlNode: document.getElementById(idBtns), + skinset: apf.getInheritedAttribute(this.editor.parentNode, "skinset"), + childNodes: [ + new apf.bar({ + childNodes: this.name == "search" + ? [new apf.button({ + caption: this.editor.$translate("findnext"), + onclick: "apf.lookup(" + this.$uniqueId + ").submit(event)" + })] + : [ + new apf.button({ + caption: this.editor.$translate("findnext"), + onclick: "apf.lookup(" + this.$uniqueId + ").submit(event)" + }), + (this.oReplBtn = new apf.button({ + caption: this.editor.$translate("doreplace"), + onclick: "apf.lookup(" + this.$uniqueId + ").onDoReplClick(event)" + })), + (this.oReplAllBtn = new apf.button({ + caption: this.editor.$translate("replaceall"), + onclick: "apf.lookup(" + this.$uniqueId + ").onReplAllClick(event)" + })) + ] + }) + ] + }); + + if (this.name == "replace") { + this.oReplace = document.getElementById(idReplace); + this.oReplBtn.disable(); + } + + + if (apf.hasFocusBug) { + var fSel = function(e) { + e = e || window.event; + e.cancelBubble = true; + }; + apf.sanitizeTextbox(this.oSearch); + this.oSearch.onselectstart = fSel; + if (this.oReplace) { + apf.sanitizeTextbox(this.oReplace); + this.oReplace.onselectstart = fSel; + } + // checkboxes also need the focus fix: + apf.sanitizeTextbox(this.oCase); + } + + + return panelBody; + }; + + this.destroy = function() { + panelBody = this.oSearch = this.oCase = null; + delete panelBody; + delete this.oSearch; + delete this.oCase; + if (this.oReplace) { + this.oReplace = this.oReplBtn = this.oReplAllBtn = null; + delete this.oReplace; + delete this.oReplBtn; + delete this.oReplAllBtn; + } + }; +}; + +apf.LiveEdit.plugin("search", apf.LiveEdit.searchPlugin); +apf.LiveEdit.plugin("replace", apf.LiveEdit.searchPlugin); + + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/core/baseclasses/liveedit/spell.js)SIZE(11849)TIME(Mon, 10 Jan 2011 10:53:11 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + + +apf.LiveEdit.plugin("spell", function(){ + this.name = "spell"; + this.icon = "spell"; + this.type = apf.TEXTMACRO; + this.hook = "typing"; + this.state = apf.OFF; + + var engine, lastVal, + crtLang = null, + basepath = null, + canCheck = false; + + this.execute = function(editor) { + apf.console.log("spell active..." + typeof editor); + var _self = this; + + if (basepath === null) { + + basepath = (apf.config.resourcePath || apf.basePath) + "resources/"; + + + } + if (crtLang === null || editor.language != crtLang) { + crtLang = editor.language; + canCheck = false; + engine = BJSpell(basepath + crtLang + ".js", function(){ + canCheck = true; + }); + } + // start checking the spelling: + if (canCheck) { + lastVal = editor.getValue().replace(/]+underline[^>]+>(.*)<\/span>/gi, "$1"); + var html = engine.replace(lastVal, + function(word) { + return "" + word + ""; + }); + if (lastVal.replace(/[\r\n]/g, "") != html.replace(/[\r\n]/g, "")) { + editor.$value = ""; + var oBm = editor.$selection.getBookmark(); + editor.$propHandlers["value"].call(editor, html); + setTimeout(function() { + editor.$selection.moveToBookmark(oBm); + }); + } + } + //div.scrollTop = div.scrollHeight; + editor.dispatchEvent("pluginexecute", {name: this.name, plugin: this}); + }; + + this.suggest = function() { + // @todo + apf.console.log('suggestions to be delivered here...'); + }; + + this.queryState = function(editor) { + return this.state; + }; + + /*! + * BJSpell JavaScript Library v0.0.1 + * http://code.google.com/p/bjspell/ + * + * Copyright (c) 2009 Andrea Giammarchi + * Licensed under the Lesser GPL licenses. + * + * Date: 2009-02-05 23:50:01 +0000 (Wed, 05 Feb 2009) + * Revision: 1 + * ToDo: better suggestion (at least one that make more sense) + */ + BJSpell = apf.LiveEdit.spell = function(){ + /** + * Every BJSpell call is a dictionary based singleton. + * new BJSpell("en_US") === BJSpell("en_US") + * var en = BJSpell("en_US", function(){ + * this === new BJSpell("en_US") === en + * }); + * It is possible to create many languages instances without problems + * but every instance will be cached in the browser after its download. + * Every BJSpell instance uses a big amount of RAM due to the + * JavaScript "pre-compiled" dictionary and caching optimizations. + * @param String a dictionary file to load. It has to contain the + * standard name of the language (e.g. en_US, en_EN, it_IT ...) + * @param Function a callback to execute asyncronously on dictionary ready + * @return BJSpell even invoked as regular callback returns the singleton + * instance for specified dictionary + * @constructor + */ + function BJSpell(dic, callback){ + if(this instanceof BJSpell){ + var lang = /^.*?([a-zA-Z]{2}_[a-zA-Z]{2}).*$/.exec(dic)[1], + self = this; + if(!callback) + callback = empty; + if(BJSpell[lang]){ + if(BJSpell[lang] instanceof BJSpell) + self = BJSpell[lang] + else + BJSpell[lang] = sync(self, BJSpell[lang]); + self.checked = {}; + var keys = self.keys = [], + words = self.words, + i = 0, + key; + for(key in words) + keys[i++] = key; + keys.indexOf = indexOf; + setTimeout(function(){callback.call(self)}, 1); + } else { + apf.console.log("loading dic script: " + dic); + var script = document.createElement("script"); + BJSpell[lang] = self; + script.src = dic; + script.type = "text/javascript"; + script.onload = function(){ + apf.console.log("incoming script..."); + script.onload = script.onreadystatechange = empty; + script.parentNode.removeChild(script); + BJSpell.call(self, dic, callback); + }; + script.onreadystatechange = function(){ + apf.console.log("script readyState change..."); + if(/loaded|completed/i.test(script.readyState)) + script.onload(); + }; + (document.getElementsByTagName("head")[0] || document.documentElement).appendChild(script); + } + return self; + } else + return new BJSpell(dic, callback); + }; + + /** + * check a word, case insensitive + * + * @param String a word to check if it is correct or not + * @return Boolean false if the word does not exist + */ + BJSpell.prototype.check = function(word){ + var checked = this.checked[word = word.toLowerCase()]; + return typeof checked === "boolean" ? checked : this.parse(word); + }; + + /** + * check a "lowercased" word in the dictionary + * + * @param String a lowercase word to search in the dictionary + * @return Boolean false if the word does not exist + */ + BJSpell.prototype.parse = function(word){ + if(/^[0-9]+$/.test(word)) + return this.checked[word] = true; + var result = !!this.words[word], + parsed, rules, len, length, i, rule, str, seek, re, add; + if(!result){ + for(parsed = word, + rules = this.rules.PFX, + length = rules.length, + i = 0; + i < length; + i++ + ){ + rule = rules[i]; add = rule[0]; seek = rule[1]; re = rule[2]; + str = word.substr(0, seek.length); + if(str === seek){ + parsed = word.substring(str.length); + if(add !== "0") + parsed = add + parsed; + result = !!this.words[parsed]; + break; + } + } + if(!result && parsed.length){ + for( + rules = this.rules.SFX, + len = parsed.length, + length = rules.length, + i = 0; + i < length; + i++ + ){ + rule = rules[i]; add = rule[0]; seek = rule[1]; re = rule[2]; + str = parsed.substring(len - seek.length); + if(str === seek){ + seek = parsed.substring(0, len - str.length); + if(add !== "0") + seek += add; + if((re === "." || new RegExp(re + "$").test(seek)) && this.words[seek]){ + result = true; + break; + } + } + } + } + } + return this.checked[word] = result; + }; + + /** + * invoke a specific callback for each word that is not valid + * + * @param String a string with zero or more words to check + * @param Function a callback to use as replace. Only wrong words will + * be passed to the callback. + * @return Boolean false if the word does not exist + */ + BJSpell.prototype.replace = function(String, callback){ + var self = this; + return String.replace(self.alphabet, function(word){ + return self.check(word) ? word : callback(word); + }); + }; + + /** + * basic/silly implementation of a suggestion - I will write something + * more interesting one day + * + * @param String a word, generally bad, to look for a suggestion + * @param Number an optional unsigned integer to retrieve N suggestions. + * @return Array a list of possibilities/suggestions + */ + BJSpell.prototype.suggest = function(word, many){ + + if(typeof this.words[word] === "string"){ + // not implemented yet, requires word classes parser + var words = [word]; + } else { + var keys = this.keys, + length = keys.length, + i = Math.abs(many) || 1, + ceil, indexOf, words; + word = word.toLowerCase(); + if(-1 === keys.indexOf(word)) + keys[length] = word; + keys.sort(); + indexOf = keys.indexOf(word); + many = indexOf - ((i / 2) >> 0); + ceil = indexOf + 1 + Math.ceil(i / 2); + words = keys.slice(many, indexOf).concat(keys.slice(indexOf + 1, ceil)); + while(words.length < i && ++ceil < keys.length) + words[words.length] = keys[ceil]; + if(length !== keys.length) + keys.splice(indexOf, 1); + } + return words; + }; + + /* + * private scope functions + * empty, as empty callback + * indexOf, as Array.prototype.indexOf, normalized for IE or other browsers + * sync, to copy over two objects keys/values + */ + var empty = function(){}, + indexOf = Array.prototype.indexOf || function(word){ + for(var i=0,length=this.length;i']; + for (; i < j; i++) { + aOut.push("
    ", (apf.isIE ? "" : ' 
    '),"
    ") + + oEditor.$insertHtml(aOut.join(""), true); + oEditor.$selection.collapse(false); + }; + + var bMorphing = false, oMorphCurrent, iMorphXCount, iMorphYCount; + function mouseDown(e) { + bMorphing = true; + oMorphCurrent = e.client; + iMorphXCount = iMorphYCount = 0; + //apf.plane.show(panelBody, true); + document.onmousemove = function(e) { + if (!bMorphing) return; + e = new apf.AbstractEvent(e || window.event); + // only morph the table when the mouse reaches beyond the table + if (e.client.x > oTablePos[0] + oTable.offsetWidth + || e.client.y > oTablePos[1] + oTable.offsetHeight) + morphTable(e); + e.stop(); + return false; + } + document.onmouseup = function(e) { + e = new apf.AbstractEvent(e || window.event); + mouseUp.call(_self, e); + e.stop(); + return false; + } + //e.stop(); + return false; + } + + function mouseUp(e) { + if (bMorphing) { + bMorphing = false, + oMorphCurrent = document.onmousemove = document.onmouseup = null, + iMorphXCount = iMorphYCount = 0; + //apf.plane.hide(); + } + mouseOver.call(this, e); + if (iCurrentX > 0 && iCurrentY > 0) + this.submit([iCurrentY, iCurrentX]); + e.stop(); + return false; + } + + function morphTable(e) { + oMorphCurrent = e.client; + iMorphXCount = (Math.floor((oMorphCurrent.x - oTablePos[0]) / CELL_SIZE) * CELL_SIZE) + CELL_SIZE; + if (iMorphXCount > oTable.offsetWidth) { + panelBody.style.width = (iMorphXCount + (BUTTON_SIZE / 2)) + "px", + oTableCont.style.width = (iMorphXCount + GUTTER_SIZE) + "px", + oTable.style.width = (iMorphXCount) + "px", + oTableSel.style.width = (iMorphXCount) + "px"; + } + iMorphYCount = (Math.floor((oMorphCurrent.y - oTablePos[1]) / CELL_SIZE) * CELL_SIZE) + CELL_SIZE; + if (iMorphYCount > oTable.offsetHeight) { + panelBody.style.height = (iMorphYCount + BUTTON_SIZE) + "px", + oTableCont.style.height = (iMorphYCount + GUTTER_SIZE) + "px", + oTable.style.height = (iMorphYCount) + "px", + oTableSel.style.height = (iMorphYCount) + "px"; + } + } + + function resetTableMorph() { + oTableCont.style.width = oTableCont.style.height = TABLE_SIZE + "px", + oTableSel.style.width = oTableSel.style.height = "0px", + oTable.style.width = oTable.style.height = TABLE_SIZE - GUTTER_SIZE + "px"; + } + + var sCurrentCaption = ""; + + function mouseOver(e) { + if (typeof oTablePos == "undefined") return; + iCurrentX = Math.ceil((e.page.x - oTablePos[0]) / CELL_SIZE); + iCurrentY = Math.ceil((e.page.y - oTablePos[1]) / CELL_SIZE); + if (iCurrentX > 0 && iCurrentY > 0) { + oTableSel.style.width = Math.min((iCurrentX * CELL_SIZE), oTable.offsetWidth) + "px"; + oTableSel.style.height = Math.min((iCurrentY * CELL_SIZE), oTable.offsetHeight) + "px"; + var sCaption = iCurrentY + " x " + iCurrentX + " " + + _self.editor.$translate("table_noun"); + if (sCurrentCaption != sCaption) + oStatus.innerHTML = sCurrentCaption = sCaption; + } + else + iCurrentX = iCurrentY = 0; + } + + function mouseOut(e) { + if (bMorphing) return; + oTableSel.style.width = oTableSel.style.height = "0px"; + iCurrentX = iCurrentY = 0; + oStatus.innerHTML = sCurrentCaption = _self.editor.$translate("cancel"); + } + + function statusClick(e) { + mouseOut.call(this, e); + apf.popup.forceHide(); + } + + this.createPanelBody = function() { + panelBody = document.body.appendChild(document.createElement("div")); + panelBody.className = "editor_popup editor_tablepopup"; + panelBody.style.display = "none"; + + var idTableCont = "editor_" + this.$uniqueId + "_tablecont", + idTableSel = "editor_" + this.$uniqueId + "_tablesel", + idTable = "editor_" + this.$uniqueId + "_table", + idStatus = "editor_" + this.$uniqueId + "_table_status"; + panelBody.innerHTML = + '

    \ +
    \ +
    \ +
    \ +
    ' + + this.editor.$translate("cancel") + '
    '; + + oTableCont = document.getElementById(idTableCont), + oTableSel = document.getElementById(idTableSel), + oTable = document.getElementById(idTable), + oTable.onmousedown = mouseDown.bindWithEvent(this, true), + oTable.onmouseup = mouseUp.bindWithEvent(this, true), + oTable.onmousemove = mouseOver.bindWithEvent(this, true), + oTable.onmouseout = mouseOut.bindWithEvent(this, true), + oStatus = document.getElementById(idStatus), + oStatus.onmousedown = statusClick.bindWithEvent(this, true), + panelBody.onselectstart = function() { return false; }, + resetTableMorph(); + + return panelBody; + }; + + this.destroy = function() { + //oTableCont, oTableSel, oTable, oStatus, oTablePos, oDoc + panelBody = oTableCont = oTableSel = oTable = oStatus = oTablePos + = oDoc = null; + delete panelBody, + delete oTableCont, + delete oTableSel, + delete oTable, + delete oStatus, + delete oTablePos, + delete oDoc; + }; +}); + +apf.LiveEdit.plugin("tablewizard", function() { + this.name = "tablewizard"; + this.icon = "tablewizard"; + this.type = apf.CONTEXTPANEL; + this.hook = "context"; + this.state = apf.OFF; + this.oTable = null; + this.oRow = null; + this.oCell = null; + + var activeNode, oDoc, _self = this; + + this.execute = function(editor, e) { + if (this.queryState(editor) != apf.ON) + return; + // get the active table, row and cell nodes: + this.oTable = this.oRow = this.oCell = null; + while (activeNode.tagName != "TABLE") { + if (activeNode.tagName == "TD") + this.oCell = activeNode; + else if (activeNode.tagName == "TR") + this.oRow = activeNode; + activeNode = activeNode.parentNode; + } + this.oTable = activeNode; + + if (!this.editor) + this.editor = editor; + if (!apf.editor.oMenu) + this.createContextMenu(); + if (!oDoc) + oDoc = editor.useIframe ? document : editor.$activeDocument; + apf.editor.oMenu.tablePlugin = this; + + var pos = apf.getAbsolutePosition(editor.iframe); + if (!e.client) + e = new apf.AbstractEvent(e); + apf.editor.oMenu.display(e.client.x + pos[0], e.client.y + pos[1], true); + + e.stop(); + + editor.dispatchEvent("pluginexecute", {name: this.name, plugin: this}); + + return false; + }; + + this.queryState = function(editor) { + var oNode = editor.$selection.getSelectedNode(); + while (oNode.nodeType != 1 || oNode.tagName != "BODY") { + if (oNode.tagName == "TABLE" || oNode.tagName == "TBODY" + || oNode.tagName == "TR" || oNode.tagName == "TD") { + activeNode = oNode; + return apf.ON; + } + oNode = oNode.parentNode; + } + + return apf.OFF; + }; + + function addRows(td_elm, tr_elm, rowspan) { + // Add rows + td_elm.rowSpan = 1; + var trNext = nextElm(tr_elm, ["TR"]); + for (var i = 1; i < rowspan && trNext; i++) { + var newTD = oDoc.createElement("td"); + if (!apf.isIE) + newTD.innerHTML = '
    '; + if (apf.isIE) + trNext.insertBefore(newTD, trNext.cells(td_elm.cellIndex)); + else + trNext.insertBefore(newTD, trNext.cells[td_elm.cellIndex]); + trNext = nextElm(trNext, ["TR"]); + } + } + + function getColRowSpan(td) { + var colspan = td.getAttribute("colspan") || ""; + var rowspan = td.getAttribute("rowspan") || ""; + + return { + colspan : colspan == "" ? 1 : parseInt(colspan), + rowspan : rowspan == "" ? 1 : parseInt(rowspan) + }; + } + + function getTableGrid(table) { + var grid = [], rows = table.rows, x, y, td, sd, xstart, x2, y2; + + for (y = 0; y < rows.length; y++) { + for (x = 0; x < rows[y].cells.length; x++) { + td = rows[y].cells[x]; + sd = getColRowSpan(td); + + // All ready filled + for (xstart = x; grid[y] && grid[y][xstart]; xstart++){} + + // Fill box + for (y2 = y; y2 < y + sd.rowspan; y2++) { + if (!grid[y2]) + grid[y2] = []; + + for (x2 = xstart; x2 < xstart + sd.colspan; x2++) + grid[y2][x2] = td; + } + } + } + + return grid; + } + + function getCellPos(grid, td) { + for (var i = 0; i < grid.length; i++) { + for (var j = 0; j < grid[i].length; j++) { + if (grid[i][j] == td) + return {cellindex : j, rowindex : i}; + } + } + + return null; + } + + function getCell(grid, row, col) { + if (grid[row] && grid[row][col]) + return grid[row][col]; + return null; + } + + function nextElm(node, names) { + while ((node = node.nextSibling) != null) { + for (var i = 0; i < names.length; i++) { + if (node.nodeName.toLowerCase() == names[i].toLowerCase()) + return node; + } + } + return null; + } + + this.createContextMenu = function(){ + var idMenu = "editor_" + this.$uniqueId + "_menu"; + this.appendAmlNode('\ + \ + Insert row before\ + Insert row after\ + Delete row\ + \ + Insert column before\ + Insert column after\ + Delete column\ + \ + Split merged table cells\ + Merge table cells\ + ', document.body); + //nodes disabled: + // \ + // Table row properties\ + // Table column properties\ + var oMenu = apf.editor.oMenu = self[idMenu]; + oMenu.addEventListener("onitemclick", function(e){ + if (this.tablePlugin != _self) + return; + + var oRow, i, j, + idx = 0, + oEditor = _self.editor; + + if (_self.oCell) { + for (i = 0, j = _self.oRow.cells.length; i < j; i++) + if (_self.oRow.cells[i] == _self.oCell) + idx = i; + } + + oEditor.$selection.set(); + + switch (e.value) { + case "rowbefore": + oRow = oDoc.createElement("tr"); + _self.oRow.parentNode.insertBefore(oRow, _self.oRow); + for (i = 0, j = _self.oRow.cells.length; i < j; i++) + oRow.insertCell(0); + break; + case "rowafter": + oRow = oDoc.createElement("tr"); + _self.oRow.parentNode.insertBefore(oRow, _self.oRow.nextSibling); + for (i = 0, j = _self.oRow.cells.length; i < j; i++) + oRow.insertCell(0); + break; + case "deleterow": + if (!_self.oRow || !_self.oRow.parentNode) return; + _self.oRow.parentNode.removeChild(_self.oRow); + break; + case "colbefore": + if (!_self.oCell) return; + for (i = 0, j = _self.oTable.rows.length; i < j; i++) + _self.oTable.rows[i].insertCell(idx); + break; + case "colafter": + if (!_self.oCell) return; + idx += 1; + for (i = 0, j = _self.oTable.rows.length; i < j; i++) + _self.oTable.rows[i].insertCell(idx); + break; + case "deletecol": + if (!_self.oCell || _self.oTable.rows[0].cells.length == 1) + return; + //@todo: fix this to understand cell spanning + for (i = 0, j = _self.oTable.rows.length; i < j; i++) { + if (_self.oTable.rows[i].cells[idx]) + _self.oTable.rows[i].deleteCell(idx); + } + break; + case "splitcells": + if (!_self.oRow || !_self.oCell) + return true; + + var spandata = getColRowSpan(_self.oCell); + + var colspan = spandata["colspan"]; + var rowspan = spandata["rowspan"]; + + // Needs splitting + if (colspan > 1 || rowspan > 1) { + // Generate cols + _self.oCol.colSpan = 1; + for (i = 1; i < colspan; i++) { + var newTD = oDoc.createElement("td"); + if (!apf.isIE) + newTD.innerHTML = '
    '; + + _self.oRow.insertBefore(newTD, nextElm(_self.oCell, ["TD","TH"])); + + if (rowspan > 1) + addRows(newTD, _self.oRow, rowspan); + } + + addRows(_self.oCell, _self.oRow, rowspan); + } + break; + case "mergecells": + var rows = [], cells = [], + oSel = oEditor.$selection.get(), + grid = getTableGrid(_self.oTable), + oCellPos, aRows, aRowCells, aBrs, oTd, k; + + if (apf.isIE || oSel.rangeCount == 1) { + var numRows = 1; + var numCols = 1; + oCellPos = getCellPos(grid, _self.oCell); + + // Get rows and cells + aRows = _self.oTable.rows; + for (i = oCellPos.rowindex; i < grid.length; i++) { + aRowCells = []; + + for (j = oCellPos.cellindex; j < grid[i].length; j++) { + oTd = getCell(grid, i, j); + + if (oTd && !rows.contains(oTd) && !aRowCells.contains(oTd)) { + var cp = getCellPos(grid, oTd); + // Within range + if (cp.cellindex < oCellPos.cellindex + numCols + && cp.rowindex < oCellPos.rowindex + numRows) + aRowCells[aRowCells.length] = oTd; + } + } + if (aRowCells.length > 0) + rows[rows.length] = aRowCells; + + oTd = getCell(grid, oCellPos.rowindex, oCellPos.cellindex); + aBrs = oTd.getElementsByTagName("br"); + if (aBrs.length > 1) { + for (j = aBrs.length; j >= 1; j--) { + if (aBrs[j].getAttribute("_apf_placeholder")) + aBrs[j].parentNode.removeChild(aBrs[j]); + } + } + } + } + else { + var x1 = -1, y1 = -1, x2, y2; + + // Only one cell selected, whats the point? + if (oSel.rangeCount < 2) + return true; + + // Get all selected cells + for (i = 0; i < oSel.rangeCount; i++) { + var rng = oSel.getRangeAt(i); + _self.oCell = rng.startContainer.childNodes[rng.startOffset]; + if (!_self.oCell) + break; + if (_self.oCell.nodeName == "TD" || _self.oCell.nodeName == "TH") + cells.push(_self.oCell); + } + + // Get rows and cells + aRows = _self.oTable.rows; + for (i = 0; i < aRows.length; i++) { + aRowCells = []; + for (j = 0; j < aRows[i].cells.length; j++) { + oTd = aRows[i].cells[j]; + for (k = 0; k < cells.length; k++) { + if (oTd != cells[k]) continue; + aRowCells.push(oTd); + } + } + if (aRowCells.length > 0) + rows.push(aRowCells); + } + + // Find selected cells in grid and box + for (i = 0; i < grid.length; i++) { + for (j = 0; j < grid[i].length; j++) { + grid[i][j]._selected = false; + for (k = 0; k < cells.length; k++) { + if (grid[i][j] != cells[k]) continue; + // Get start pos + if (x1 == -1) { + x1 = j; + y1 = i; + } + // Get end pos + x2 = j; + y2 = i; + grid[i][j]._selected = true; + } + } + } + + // Is there gaps, if so deny + for (i = y1; i <= y2; i++) { + for (j = x1; j <= x2; j++) { + if (!grid[i][j]._selected) { + alert("Invalid selection for merge."); + return true; + } + } + } + } + + // Validate selection and get total rowspan and colspan + var rowSpan = 1, colSpan = 1; + + // Validate horizontal and get total colspan + var sd, lastRowSpan = -1; + for (i = 0; i < rows.length; i++) { + var rowColSpan = 0; + for (j = 0, k = rows[i].length; j < k; j++) { + sd = getColRowSpan(rows[i][j]); + rowColSpan += sd.colspan; + if (lastRowSpan != -1 && sd.rowspan != lastRowSpan) { + alert("Invalid selection for merge."); + return true; + } + lastRowSpan = sd.rowspan; + } + if (rowColSpan > colSpan) + colSpan = rowColSpan; + lastRowSpan = -1; + } + + // Validate vertical and get total rowspan + var lastColSpan = -1; + for (j = 0; j < rows[0].length; j++) { + var colRowSpan = 0; + for (i = 0; i < rows.length; i++) { + sd = getColRowSpan(rows[i][j]); + colRowSpan += sd.rowspan; + if (lastColSpan != -1 && sd.colspan != lastColSpan) { + alert("Invalid selection for merge."); + return true; + } + lastColSpan = sd.colspan; + } + if (colRowSpan > rowSpan) + rowSpan = colRowSpan; + lastColSpan = -1; + } + + // Setup td + _self.oCell = rows[0][0]; + _self.oCell.rowSpan = rowSpan; + _self.oCell.colSpan = colSpan; + + // Merge cells + for (i = 0; i < rows.length; i++) { + for (j = 0; j < rows[i].length; j++) { + var html = rows[i][j].innerHTML; + var chk = html.replace(/[ \t\r\n]/g, ""); + if (chk != "
    " && chk != '
    ' + && (j + i > 0)) + _self.oCell.innerHTML += html; + + // Not current cell + if (rows[i][j] == _self.oCell && rows[i][j]._deleted) + continue; + oCellPos = getCellPos(grid, rows[i][j]); + var tr = rows[i][j].parentNode; + + tr.removeChild(rows[i][j]); + rows[i][j]._deleted = true; + + if (tr.hasChildNodes()) continue; + // Empty TR, remove it + tr.parentNode.removeChild(tr); + + var cellElm, lastCell = null; + for (k = 0; cellElm = getCell(grid, oCellPos.rowindex, k); k++) { + if (cellElm != lastCell && cellElm.rowSpan > 1) + cellElm.rowSpan--; + lastCell = cellElm; + } + if (_self.oCell.rowSpan > 1) + _self.oCell.rowSpan--; + } + } + + // Remove all but one bogus br + aBrs = _self.oCell.getElementsByTagName("br"); + if (aBrs.length > 1) { + for (i = aBrs.length; i >= 1; i--) { + if (aBrs[i] && aBrs[i].getAttribute("_apf_placeholder")) + aBrs[i].parentNode.removeChild(aBrs[i]); + } + } + break; + } + + oEditor.$restoreFocus(); + + oEditor.change(_self.editor.getValue()); + + }); + }; +}); + + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/core/baseclasses/liveedit/visualaid.js)SIZE(1736)TIME(Mon, 10 Jan 2011 10:53:11 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + + +apf.LiveEdit.plugin("visualaid", function(){ + this.name = "visualaid"; + this.icon = "visualaid"; + this.type = apf.TOOLBARITEM; + this.subType = apf.TOOLBARBUTTON; + this.hook = "ontoolbar"; + this.keyBinding = "ctrl+shift+v"; + this.state = apf.OFF; + + this.execute = function(editor) { + var state = this.queryState(editor); + editor.$activeDocument.body.className = (state == apf.ON) ? "" : "visualAid"; + editor.$notifyButton(this.name); + + apf.dispatchEvent("pluginexecute", {name: this.name, plugin: this}); + }; + + this.queryState = function(editor) { + this.state = apf[editor.$activeDocument.body.className == "visualAid" ? "ON" : "OFF"]; + return this.state; + }; +}); + + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/core/baseclasses/anchoring.js)SIZE(19356)TIME(Mon, 10 Jan 2011 10:53:11 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + +apf.__ANCHORING__ = 1 << 13; + + + +/** + * All elements inheriting from this {@link term.baseclass baseclass} have anchoring features. Each side of the + * element can be attached at a certain distance to it's parent's rectangle. + * When the parent is resized the anchored side of the element stays + * at the specified distance at all times. If both sides are anchored the + * element size is changed to make sure the specified distance is maintained. + * Example: + * This example shows a bar that has 10% as a margin around it and contains a + * frame that is displayed using different calculations and settings. + * + * + * + * + * + * Remarks: + * This is one of three positioning methods. + * See {@link baseclass.alignment} + * See {@link element.grid} + * + * @constructor + * @baseclass + * @author Ruben Daniels (ruben AT ajax DOT org) + * @version %I%, %G% + * @since 0.3 + */ +apf.Anchoring = function(){ + this.$regbase = this.$regbase | apf.__ANCHORING__; + this.$anchors = []; + + var VERTICAL = 1; + var HORIZONTAL = 2; + + this.$updateQueue = 0; + this.$inited = + this.$parsed = + this.$anchoringEnabled = false; + this.$hordiff = + this.$verdiff = 0; + this.$rule_v = + this.$rule_h = + this.$rule_header = ""; + + var l = apf.layout; + + this.$supportedProperties.push("anchors"); + + var propHandlers = { + "right" : function(value, prop){ + if (!this.$anchoringEnabled && !this.$setLayout("anchoring")) + return; + + if (!value && value !== 0) + this.$ext.style[prop] = ""; + + //@note Removed apf.isParsing here to activate general queuing + if (!this.$updateQueue) + l.queue(this.$pHtmlNode, this); + this.$updateQueue = this.$updateQueue | HORIZONTAL; + }, + + "bottom" : function(value, prop){ + if (!this.$anchoringEnabled && !this.$setLayout("anchoring")) + return; + + if (!value && value !== 0) + this.$ext.style[prop] = ""; + + //@note Removed apf.isParsing here to activate general queuing + if (!this.$updateQueue) + l.queue(this.$pHtmlNode, this); + this.$updateQueue = this.$updateQueue | VERTICAL; + } + }; + propHandlers.left = propHandlers.width = propHandlers.right; + propHandlers.top = propHandlers.height = propHandlers.bottom; + + this.$propHandlers["anchors"] = function(value){ + this.$anchors = value ? value.splitSafe("(?:, *| )") : []; + + if (!this.$anchoringEnabled && !this.$setLayout("anchoring")) + return; + + if (!this.$updateQueue && apf.loaded) + l.queue(this.$pHtmlNode, this); + this.$updateQueue = this.$updateQueue | HORIZONTAL | VERTICAL; + }; + + /** + * Turns anchoring off. + * + */ + this.$disableAnchoring = function(activate){ + //!this.$parsed || + if (!this.$inited || !this.$anchoringEnabled || !this.$pHtmlNode) + return; + + l.removeRule(this.$pHtmlNode, this.$uniqueId + "_anchors"); + if (l.queue) + l.queue(this.$pHtmlNode); + + for (var prop in propHandlers) { + delete this.$propHandlers[prop]; + } + + this.removeEventListener("DOMNodeRemoved", remove); + this.removeEventListener("DOMNodeInserted", reparent); + + if (this.$ext) { + this.$ext.style.left = + this.$ext.style.right = + this.$ext.style.top = + this.$ext.style.bottom = + this.$ext.style.width = + this.$ext.style.height = + this.$ext.style.position = ""; + } + + /*if (this.right) + this.$ext.style.left = apf.getHtmlLeft(this.$ext) + "px"; + + if (this.bottom) + this.$ext.style.top = apf.getHtmlTop(this.$ext) + "px";*/ + + this.removeEventListener("prop.visible", visibleHandler); + + this.$inited = false; + this.$anchoringEnabled = false; //isn't this redundant? + }; + + /** + * Enables anchoring based on attributes set in the AML of this element + */ + /* + * @attribute {Number, String} [left] a way to determine the amount of pixels from the left border of this element to the left edge of it's parent's border. This attribute can also contain percentages, arithmetic and even full expressions. + * Example: + * + * @attribute {Number, String} [right] a way to determine the amount of pixels from the right border of this element to the right edge of it's parent's border.This attribute can also contain percentages, arithmetic and even full expressions. + * Example: + * + * @attribute {Number, String} [width] a way to determine the amount of pixels from the left border to the right border of this element.This attribute can also contain percentages, arithmetic and even full expressions. + * Example: + * + * @attribute {Number, String} [top] a way to determine the amount of pixels from the top border of this element to the top edge of it's parent's border.This attribute can also contain percentages, arithmetic and even full expressions. + * Example: + * + * @attribute {Number, String} [bottom] a way to determine the amount of pixels from the bottom border of this element to the bottom edge of it's parent's border.This attribute can also contain percentages, arithmetic and even full expressions. + * Example: + * + * @attribute {Number, String} [height] a way to determine the amount of pixels from the top border to the bottom border of this element.This attribute can also contain percentages, arithmetic and even full expressions. + * Example: + * + */ + this.$enableAnchoring = function(){ + if (this.$inited) //@todo add code to reenable anchoring rules (when showing) + return; + + /**** Properties and Attributes ****/ + apf.extend(this.$propHandlers, propHandlers); + + /**** Event handlers ****/ + this.addEventListener("DOMNodeRemoved", remove); + this.addEventListener("DOMNodeInserted", reparent); + this.addEventListener("prop.visible", visibleHandler); + + this.$updateQueue = 0 + | ((this.left || this.width || this.right || this.anchors) && HORIZONTAL) + | ((this.top || this.height || this.bottom || this.anchors) && VERTICAL) ; + + if (this.$updateQueue) + l.queue(this.$pHtmlNode, this); + + this.$inited = true; + this.$anchoringEnabled = true; + }; + + function visibleHandler(e){ + if (!(this.$rule_header || this.$rule_v || this.$rule_h)) + return; + + if (e.value) { + if (this.$rule_v || this.$rule_h) { + var rules = this.$rule_header + "\n" + this.$rule_v + "\n" + this.$rule_h; + l.setRules(this.$pHtmlNode, this.$uniqueId + "_anchors", rules); + //this.$ext.style.display = "none"; + l.queue(this.$pHtmlNode, this); + } + l.processQueue(); + } + else { + l.removeRule(this.$pHtmlNode, this.$uniqueId + "_anchors"); + l.queue(this.$pHtmlNode) + } + } + + function remove(e){ + if (e && (e.$doOnlyAdmin || e.currentTarget != this)) + return; + + if (l.queue && this.$pHtmlNode) { + l.removeRule(this.$pHtmlNode, this.$uniqueId + "_anchors"); + l.queue(this.$pHtmlNode) + } + } + + function reparent(e){ + if (!this.$amlLoaded || e.currentTarget != this) + return; + + if (!e.$isMoveWithinParent && this.$parsed) //@todo hmm weird state check + this.$moveAnchoringRules(e.$oldParentHtmlNode); + //else if (e.relatedNode == this) //@todo test this + //e.currentTarget.$setLayout("anchoring"); + } + + this.$moveAnchoringRules = function(oldParent, updateNow){ + var rules = oldParent && l.removeRule(oldParent, this.$uniqueId + "_anchors"); + if (rules) + l.queue(oldParent); + + if (!this.$rule_v && !this.$rule_h && !this.$rule_header) + return; + + this.$rule_header = getRuleHeader.call(this); + rules = this.$rule_header + "\n" + this.$rule_v + "\n" + this.$rule_h; + + //@todo sometimes the content is not displayed anymore (when reparenting by xinclude) + //this.$ext.style.display = "none"; + + l.setRules(this.$pHtmlNode, this.$uniqueId + "_anchors", rules); + l.queue(this.$pHtmlNode, this); + }; + + this.$hasAnchorRules = function(){ + return this.$rule_v || this.$rule_h ? true : false; + }; + + function getRuleHeader(){ + if (!this.$pHtmlDoc) return ""; + return "try{\ + var oHtml = " + (apf.hasHtmlIdsInJs + ? this.$ext.getAttribute("id") + : "document.getElementById('" + + this.$ext.getAttribute("id") + "')") + ";\ + \ + var pWidth = " + (this.$pHtmlNode == this.$pHtmlDoc.body + ? "apf.getWindowWidth()" //@todo only needed for debug? + : "apf.getHtmlInnerWidth(oHtml.parentNode)") + ";\ + \ + var pHeight = " + (this.$pHtmlNode == this.$pHtmlDoc.body + ? "apf.getWindowHeight()" //@todo only needed for debug? + : "apf.getHtmlInnerHeight(oHtml.parentNode)") + ";\ + }catch(e){\ + }"; + } + + /** + * @macro + */ + function setPercentage(expr, value){ + return String(expr).replace(apf.percentageMatch, "((" + value + " * $1)/100)"); + } + + + this.$recalcAnchoring = function(queueDelay){ + this.$updateQueue = this.$updateQueue | HORIZONTAL | VERTICAL; + this.$updateLayout(); + l.queue(this.$pHtmlNode, this); + + if (!queueDelay) + l.processQueue(); + }; + + + function visCheck(){ + if (this.$updateQueue) { + this.$updateLayout(); + apf.layout.activateRules(this.$ext.parentNode); + } + } + + this.$updateLayout = function(){ + if (!this.$anchoringEnabled) + return; + + if (!apf.window.vManager.check(this, "anchoring", visCheck)) + return; + + if (!this.$parsed) { + if (!this.$ext.getAttribute("id")) + apf.setUniqueHtmlId(this.$ext); + + this.$rule_header = getRuleHeader.call(this); + this.$parsed = true; + } + + if (!this.$updateQueue) { + if (this.visible && this.$ext.style.display == "none") + this.$ext.style.display = ""; + return; + } + + if (this.draggable == "relative") { + if ("absolute|fixed|relative".indexOf(apf.getStyle(this.$ext, "position")) == -1) //@todo apf3.1 the IDE doesn't like this + this.$ext.style.position = "absolute"; + } + else if (this.left || this.left === 0 || this.top || this.top === 0 + || this.right || this.right === 0 || this.bottom || this.bottom === 0 + || this.$anchors.length) { + if ("absolute|fixed".indexOf(apf.getStyle(this.$ext, "position")) == -1) + this.$ext.style.position = "absolute"; + } + else if (!this.center) { + if ("absolute|fixed|relative".indexOf(apf.getStyle(this.$ext, "position")) == -1) + this.$ext.style.position = "relative"; + if (!this.width) + this.$ext.style.width = ""; + if (!this.height) + this.$ext.style.height = ""; + } + + var rules; + if (this.$updateQueue & HORIZONTAL) { + rules = []; + + this.$hordiff = apf.getWidthDiff(this.$ext); + + var left = this.$anchors[3] || this.left, + right = this.$anchors[1] || this.right, + width = this.width, hasLeft = left || left === 0, + hasRight = right || right === 0, + hasWidth = width || width === 0; + + if (right && typeof right == "string") + right = setPercentage(right, "pWidth"); + + if (hasLeft) { + if (parseInt(left) != left) { + left = setPercentage(left, "pWidth"); + rules.push("oHtml.style.left = (" + left + ") + 'px'"); + } + else + this.$ext.style.left = left + "px"; + } + if ((apf.hasStyleAnchors || !hasLeft) && hasRight) { + if (parseInt(right) != right) { + right = setPercentage(right, "pWidth"); + rules.push("oHtml.style.right = (" + right + ") + 'px'"); + } + else + this.$ext.style.right = right + "px"; + } + + if (hasLeft && hasRight) { //right != null && left != null) { + if (!apf.hasStyleAnchors) + rules.push("oHtml.style.width = (pWidth - (" + right + + ") - (" + left + ") - " + this.$hordiff + ") + 'px'"); + else + this.$ext.style.width = ""; + } + else if (hasWidth && typeof this.maxwidth == "number" && typeof this.minwidth == "number") { + if (parseInt(width) != width) { + width = setPercentage(width, "pWidth"); + rules.push("oHtml.style.width = Math.max(" + + (this.minwidth - this.$hordiff) + + ", Math.min(" + (this.maxwidth - this.$hordiff) + ", " + + width + " - " + this.$hordiff + ")) + 'px'"); + } + else { + this.$ext.style.width = ((width > this.minwidth + ? (width < this.maxwidth + ? width + : this.maxwidth) + : this.minwidth) - this.$hordiff) + "px"; + } + } + + this.$rule_h = (rules.length + ? "try{" + rules.join(";}catch(e){};try{") + ";}catch(e){};" + : ""); + } + + if (this.$updateQueue & VERTICAL) { + rules = []; + + this.$verdiff = apf.getHeightDiff(this.$ext); + + var top = this.$anchors[0] || this.top, + bottom = this.$anchors[2] || this.bottom, + height = this.height, hasTop = top || top === 0, + hasBottom = bottom || bottom === 0, + hasHeight = height || height === 0; + + if (bottom && typeof bottom == "string") + bottom = setPercentage(bottom, "pHeight"); + + if (hasTop) { + if (parseInt(top) != top) { + top = setPercentage(top, "pHeight"); + rules.push("oHtml.style.top = (" + top + ") + 'px'"); + } + else + this.$ext.style.top = top + "px"; + } + if ((apf.hasStyleAnchors || !hasTop) && hasBottom) { + if (parseInt(bottom) != bottom) { + rules.push("oHtml.style.bottom = (" + bottom + ") + 'px'"); + } + else + this.$ext.style.bottom = bottom + "px"; + } + if (hasTop && hasBottom) { //bottom != null && top != null) { + if (!apf.hasStyleAnchors) + rules.push("oHtml.style.height = (pHeight - (" + bottom + + ") - (" + top + ") - " + this.$verdiff + ") + 'px'"); + else + this.$ext.style.height = ""; + } + else if (hasHeight && typeof this.minheight == "number") { + if (parseInt(height) != height) { + height = setPercentage(height, "pHeight"); + rules.push("oHtml.style.height = Math.max(" + + (this.minheight - this.$verdiff) + + ", Math.min(" + (this.maxheight - this.$verdiff) + ", " + + height + " - " + this.$verdiff + ")) + 'px'"); + } + else { + this.$ext.style.height = Math.max(0, (height > this.minheight + ? (height < this.maxheight + ? height + : this.maxheight) + : this.minheight) - this.$verdiff) + "px"; + } + } + + this.$rule_v = (rules.length + ? "try{" + rules.join(";}catch(e){};try{") + ";}catch(e){};" + : ""); + } + + if (this.$rule_v || this.$rule_h) { + l.setRules(this.$pHtmlNode, this.$uniqueId + "_anchors", + this.$rule_header + "\n" + this.$rule_v + "\n" + this.$rule_h, true); + } + else { + l.removeRule(this.$pHtmlNode, this.$uniqueId + "_anchors"); + } + + this.$updateQueue = 0; + + if (this.$box && !apf.hasFlexibleBox) //temporary fix + apf.layout.forceResize(this.$ext); + }; + + this.addEventListener("DOMNodeInsertedIntoDocument", function(e){ + //if (this.$updateQueue) + //this.$updateLayout(); + }); + + this.addEventListener("DOMNodeRemovedFromDocument", function(e){ + this.$disableAnchoring(); + }); +}; + + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/core/baseclasses/contenteditable.js)SIZE(20162)TIME(Mon, 10 Jan 2011 10:53:11 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ +apf.__CONTENTEDITABLE__ = 1 << 24; + + +apf.addEventListener("load", function(){ + var lastShift; + + apf.window.undoManager.addEventListener("afterchange", function(){ + apf.layout.processQueue(); + apf.document.$getVisualSelect().updateGeo(); //Possibly not best place for this + }); + + var sel = apf.document.getSelection(); + sel.addEventListener("update", function(){ + + }); + + //Init visual selection + //apf.document.$getVisualSelect(); + + //@todo only for editable elements + apf.addEventListener("keydown", function(e){ + var key = e.keyCode; + + if (!apf.document.activeElement + || !apf.document.activeElement.editable) + return; + + if (apf.document.queryCommandState("rename")) { + if (apf.hasSingleResizeEvent) + $setTimeout(function(){ + apf.document.$getVisualSelect().updateGeo(); + }); + + if (key == 27 || key == 13) { + apf.document.execCommand("rename", false, key == 13); + + window.focus(); //@todo don't know why this is needed... + apf.stopPropagation(e); + return false; + } + else if (apf.hasContentEditableContainerBug && key == 8 + && document.getElementById("txt_rename").innerHTML == "
    ") { + e.preventDefault(); + } + + return; + } + + //F2 + switch(key) { + case 27: //ESC + if (apf.dragMode) { + var sel = apf.document.$getVisualSelect().getLastSelection(); + (apf.document.activeElement || sel[0]).$cancelInteractive(); + } + break; + case 113: //F2 + apf.document.execCommand("rename", true); + return false; + case 16: + if (!this.dragMode && (apf.document.documentElement.editable + || self.app && self.app.editable)) { //@hack! + if (e.ctrlKey) + apf.document.execCommand("mode", null, "select"); + else { + apf.document.execCommand("mode", null, { + mode : "connect", + timeout : 1000, + event : e.htmlEvent + }); + } + } + break; + case 17: + if (!lastShift || new Date() - lastShift > 500 || e.htmlEvent.repeat) + lastShift = new Date().getTime(); + else { + var isShowingConnections = (apf.document.queryCommandState("mode") || "").substr(0,8) == "connect-"; + apf.document.execCommand("mode", null, { + mode : isShowingConnections ? "arrow" : "connect-element", + timeout : 1000, + event : e.htmlEvent + }); + lastShift = null; + } + break; + /*case 36: //HOME + return false; + case 35: //END + return false; + case 107: //+ + case 187: //+ + case 109: + case 189: //- + break; + case 38: //UP + return false; + case 40: //DOWN + return false; + case 39: //RIGHT + return false; + case 37: //LEFT + return false; + case 33: //PGUP + case 34: //PGDN + break;*/ + } + }); + + apf.addEventListener("keyup", function(e){ + if (e.keyCode == 16 + && "select|connect".indexOf(apf.document.queryCommandState("mode")) > -1) + apf.document.execCommand("mode", null, "arrow"); + }); + + var recursion, $focus, lastFocussed; + apf.document.addEventListener("focus", $focus = function(e){ + if (recursion) + return; + + recursion = true; + var node = e.currentTarget, isSelected; + if (node.editable) { + if (sel.rangeCount) { + isSelected = sel.$getNodeList().indexOf(node) > -1; //@todo use visualSelect cache here? + + if (!e.ctrlKey) { + if (isSelected) { + recursion = false; + return; + } + + sel.removeAllRanges(); + } + else { + //Only allow selection with nodes of the same parent + if (sel.getRangeAt(0).startContainer != node.parentNode) { + delete e.currentTarget; + e.fromElement && e.fromElement.focus(null, e); //@todo we should look into sel + recursion = false; + return; + } + + if (isSelected) { + recursion = false; + return; + } + } + } + + //Add element to the selection + sel.addRange(this.createRange()).selectNode(node); + lastFocussed = node; + } + recursion = false; + }); + if (apf.document.activeElement && apf.document.activeElement.editable) + sel.addRange(apf.document.createRange()).selectNode(apf.document.activeElement); + + var lastPos = [-1000, -1000]; + apf.addEventListener("mousedown", function(e){ + lastPos = [e.htmlEvent.clientX, e.htmlEvent.clientY]; + }); + + //Focus isn't set when the node already has the focus + apf.addEventListener("mouseup", function(e){ + if ((apf.document.queryCommandState("mode") || "").indexOf("connect-") > -1) + apf.document.execCommand("mode", null, "arrow"); + + if (Math.abs(lastPos[0] - e.htmlEvent.clientX) > 2 + || Math.abs(lastPos[1] - e.htmlEvent.clientY) > 2) + return; + + var o = apf.document.$getVisualSelect().$getOutline(); + if (!o) return; + + //apf.plane.hide(); + var lastTop = o.style.top; + o.style.top = "-10000px" + var hOutline = document.getElementById("apf_outline"); + if (hOutline) + hOutline.style.top = "-10000px"; + + var node = apf.findHost( + document.elementFromPoint(e.htmlEvent.clientX, e.htmlEvent.clientY)); + + o.style.top = lastTop; + + if (lastFocussed == node) { + lastFocussed = null; + return; + } + + if (!node.editable) return; + + //apf.activeElement == node && + if (sel.rangeCount > 1) { + //Deselect a node when its already selected + var idx, list = sel.$getNodeList(); //@todo use visualSelect cache here? + if (e.htmlEvent.ctrlKey && (idx = list.indexOf(node)) > -1) { + sel.removeRange(sel.getRangeAt(idx)); + delete e.currentTarget; + var r = sel.getRangeAt(sel.rangeCount - 1); + recursion = true; + r.startContainer.childNodes[r.startOffset].focus(); + recursion = false; + return; + } + else { //@todo this could be optimized by checking whether the object is already the only selected + sel.removeAllRanges(); + sel.addRange(apf.document.createRange()).selectNode(node); + } + } + }); + + /*apf.addEventListener("contextmenu", function(e){ + if (e.currentTarget.namespaceURI == apf.ns.xhtml) { + e.currentTarget.ownerDocument.execCommand("contextmenu", true, { + amlNode : e.currentTarget, + htmlEvent : e + }); + + return false; + } + });*/ +}); + +apf.ContentEditable = function() { + this.$regbase = this.$regbase | apf.__CONTENTEDITABLE__; + + this.editable = false; + this.$canEdit = true; + /*this.$init(function(tagName, nodeFunc, struct){ + //this.$inheritProperties["editable"] = 2; + });*/ + + this.$booleanProperties["editable"] = true; + this.$propHandlers["editable"] = function(value, prop){ + if (this.nomk) { //A way to have UI elements excluded from editing + this.editable = false; + return false; + } + + if (value) { + if (this.$canEdit && this.$ext && !this.$coreHtml) { + this.dragOutline = true; //@todo via config setting?? + + //Make this element draggable + (this.$propHandlers["draggable"] + || apf.GuiElement.propHandlers["draggable"]).call(this, true); + + //Make this element resizable + (this.$propHandlers["resizable"] + || apf.GuiElement.propHandlers["resizable"]).call(this, true); + + //Make this element focussable + this.$lastFocussable = [this.$focussable, this.focussable]; + if (!this.$focussable || !this.focussable) + apf.GuiElement.propHandlers.focussable.call(this, true); + this.$focussable = + this.focussable = true; + if (this.$blur) + this.$blur(); + + //Handle invisible elements + if (this.$propHandlers.visible) + this.$propHandlers.visible_original = this.$propHandlers.visible; + this.$propHandlers.visible = function(value){ + apf.setOpacity(this.$ext, value ? 1 : 0.5); + this.$ext.onmouseover = value ? null : function(){ + apf.setOpacity(this, 1); + } + this.$ext.onmouseout = value ? null : function(e){ + if (!e) e = event; + if (!e.toElement || e.toElement.host !== false) + apf.setOpacity(this, 0.5); + } + } + if (this.visible === false) { + if (this.$propHandlers.visible_original) + this.$propHandlers.visible_original.call(this, true); + this.$propHandlers.visible.call(this, false); + } + + //Disable + this.$lastDisabled = this.disabled; + this.disabled = -1; + + //If an element is editable and its parent element is not, or if an element is editable and it has no parent element, then the element is an editing host + //if this is the editing host, this value should be set to refrain it from being entered by tabbing + if (this.$isWindowContainer) + this.$isWindowContainer = -2; //@todo bug in window.js causes child to be selected at window focus change + + //If this element supports rename, enable it via dblclick + var rInfo + if (rInfo = this.ownerDocument.queryCommandEnabled("rename", false, this)) { + var htmlNode = !rInfo[0] + ? this.$ext + : (rInfo[0].nodeType == 1 ? rInfo[0] : rInfo[0].parentNode); + if (apf.isIE) + htmlNode.ondblclick = apf.ContentEditable.$renameStart; + else { + apf.addListener(htmlNode, "mousedown", + apf.ContentEditable.$renameStart); + } + + this.addEventListener("$skinchange", + apf.ContentEditable.$renameSkinChange); + } + + //Contextmenu + this.addEventListener("contextmenu", + apf.ContentEditable.$contextMenu); + + //Drag & Resize + apf.ContentEditable.addInteraction(this); + } + this.isContentEditable = true; + + //@todo apf3.0 this needs optimization + if (!this.parentNode.editable) { + var curfoc, vsel, lsel = + (vsel = this.ownerDocument.$getVisualSelect()).getLastSelection(); + if (!(curfoc = apf.window.getLastActiveElement()) && lsel.length + || curfoc && lsel.indexOf(curfoc) > -1) { + vsel.show(); + } + else if (curfoc && curfoc.editable) { + //@todo check for editable + this.ownerDocument.getSelection().$selectList([curfoc]); + } + } + + apf.setStyleClass(this.$ext, "editable"); + } + else { + if (this.$canEdit && this.$ext && !this.$coreHtml) { + var n; + + //Unset draggable + if (n = this.getAttributeNode("draggable")) { + apf.removeListener(this.$ext, "mousedown", this.$dragStart); + n.$triggerUpdate(); + } + else { + apf.removeListener(this.$ext, "mousedown", this.$dragStart); + (this.$propHandlers["draggable"] + || apf.GuiElement.propHandlers["draggable"]).call(this, this.localName == "window" || false); //@todo hack! + } + + delete this.dragOutline; //@todo hack! + delete this.$showDrag; + delete this.$showResize; + delete this.realtime; //@todo this should be renamed to something else + + //Unset resizable + if (n = this.getAttributeNode("resizable")) + n.$triggerUpdate(); + else + (this.$propHandlers["resizable"] + || apf.GuiElement.propHandlers["resizable"]).call(this, false); + + //Unset focussable + if (this.$lastFocussable) { + this.$focussable = this.$lastFocussable[0]; + this.focussable = this.$lastFocussable[0] && this.$lastFocussable[1]; + if (!this.focussable) + apf.GuiElement.propHandlers.focussable.call(this, this.focussable); + delete this.$lastFocussable; + } + if (this.hasFocus && this.hasFocus()) + this.$focus(); + + //Enable + this.disabled = this.$lastDisabled; + delete this.$lastDisabled; + + //Hide invisible elements + if (this.visible === false) { + if (this.$propHandlers.visible_original) + this.$propHandlers.visible_original.call(this, false); + this.$propHandlers.visible.call(this, true); + } + if (this.$propHandlers.visible_original) { + this.$propHandlers.visible = this.$propHandlers.visible_original; + delete this.$propHandlers.visible_original; + } + + delete this.$isWindowContainer; //Should fall back to value from prototype + + var rInfo; + if (rInfo = this.ownerDocument.queryCommandEnabled("rename", false, this)) { + var htmlNode = !rInfo[0] + ? this.$ext + : (rInfo[0].nodeType == 1 ? rInfo[0] : rInfo[0].parentNode); + if (apf.isIE) + htmlNode.ondblclick = null; + else { + apf.removeListener(htmlNode, "mousedown", + apf.ContentEditable.$renameStart); + } + + this.removeEventListener("$skinchange", + apf.ContentEditable.$renameSkinChange); + } + + //Contextmenu + this.removeEventListener("contextmenu", + apf.ContentEditable.$contextMenu); + + apf.ContentEditable.removeInteraction(this); + + var sel = this.ownerDocument.$getVisualSelect().getLastSelection();//this.ownerDocument.getSelection().$getNodeList(); + if (sel.indexOf(this) > -1) + this.ownerDocument.$getVisualSelect().hide() + } + this.isContentEditable = false; + + //@todo hack! + //apf.ContentEditable.resize.hide(); + + apf.setStyleClass(this.$ext, "", ["editable"]); + } + } + + this.addEventListener("DOMNodeInsertedIntoDocument", function(e){ + //@todo shouldn't this only be set when editable is enabled + /*try{ + if (this.$ext && !this.$ext.host) + this.$ext.host = this; + }catch(e){}*/ + + if (!this.editable) { + this.editable = apf.isTrue(apf.getInheritedAttribute(this, "editable")); + if (this.editable) { + this.$propHandlers["editable"].call(this, true); + this.dispatchEvent("prop.editable", {value: true}); + this.$inheritProperties["editable"] = 2; + } + } + }); + + this.addEventListener("DOMNodeRemovedFromDocument", function(e){ + + }); +}; +apf.ContentEditable.$contextMenu = function(e){ + this.ownerDocument.execCommand("contextmenu", true, { + amlNode: this, + htmlEvent: e + }); + + apf.stopEvent(e) + return false; +}; + +(function(){ + var time; + apf.ContentEditable.$renameStart = apf.isIE + ? function(){ + e = event; + if (e.srcElement != this) + return; + + apf.findHost(e.srcElement).ownerDocument.execCommand("rename", true); + apf.stopPropagation(e); + } + : function(e){ + if (e.target != this) + return; + + if (!time || new Date() - time[0] > 500 || time[1] != e.target) + time = [new Date().getTime(), e.target]; + else if (time) { + apf.findHost(e.target).ownerDocument.execCommand("rename", true); + apf.stopPropagation(e); + time = null; + } + } +})(); + +apf.ContentEditable.$renameSkinChange = function(e){ + var rInfo = this.ownerDocument.queryCommandEnabled("rename", false, this); + var htmlNode = !rInfo[0] + ? this.$ext + : (rInfo[0].nodeType == 1 ? rInfo[0] : rInfo[0].parentNode); + if (apf.isIE) { + htmlNode.ondblclick = apf.ContentEditable.$renameStart; + //@todo apf3.0 memory leak - fix this + //e.ext ... .ondblclick = null; + } + else { + apf.addListener(htmlNode, "mousedown", + apf.ContentEditable.$renameStart); + } +} + + +apf.XhtmlElement.prototype.implement(apf.ContentEditable); + +apf.config.$inheritProperties["editable"] = 2; + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/core/baseclasses/guielement.js)SIZE(33078)TIME(Mon, 10 Jan 2011 10:53:11 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + +apf.__GUIELEMENT__ = 1 << 15; +apf.__VALIDATION__ = 1 << 6; + + + +/** + * All elements inheriting from this {@link term.baseclass baseclass} are a aml component. + * + * @constructor + * @baseclass + * @author Ruben Daniels (ruben AT ajax DOT org) + * @version %I%, %G% + * @since 0.4 + * + * @inherits apf.Anchoring + * @inherits apf.DelayedRender + * @inherits apf.DragDrop + * @inherits apf.Focussable + * @inherits apf.Interactive + * @inherits apf.Transaction + * @inherits apf.Validation + * + * @attribute {String} span the number of columns this element spans. Only used inside a table element. + * @attribute {String} margin + * @todo attribute align + * + * @attribute {mixed} left the left position of this element. Depending + * on the choosen layout method the unit can be pixels, a percentage or an + * expression. + * + * @attribute {mixed} top the top position of this element. Depending + * on the choosen layout method the unit can be pixels, a percentage or an + * expression. + * + * @attribute {mixed} right the right position of this element. Depending + * on the choosen layout method the unit can be pixels, a percentage or an + * expression. + * + * @attribute {mixed} bottom the bottom position of this element. Depending + * on the choosen layout method the unit can be pixels, a percentage or an + * expression. + * + * @attribute {mixed} width the different between the left edge and the + * right edge of this element. Depending on the choosen layout method the + * unit can be pixels, a percentage or an expression. + * Remarks: + * When used as a child of a grid element the width can also be set as '*'. + * This will fill the rest space. + * + * @attribute {mixed} height the different between the top edge and the + * bottom edge of this element. Depending on the choosen layout method the + * unit can be pixels, a percentage or an expression. + * Remarks: + * When used as a child of a grid element the height can also be set as '*'. + * This will fill the rest space. + * + * @event resize Fires when the element changes width or height. + * + * @event contextmenu Fires when the user requests a context menu. Either + * using the keyboard or mouse. + * bubbles: yes + * cancelable: Prevents the default contextmenu from appearing. + * object: + * {Number} x the x coordinate where the contextmenu is requested on. + * {Number} y the y coordinate where the contextmenu is requested on. + * {Event} htmlEvent the html event object that triggered this event from being called. + * @event focus Fires when this element receives focus. + * @event blur Fires when this element loses focus. + * @event keydown Fires when this element has focus and the user presses a key on the keyboard. + * cancelable: Prevents the default key action. + * bubbles: + * object: + * {Boolean} ctrlKey whether the ctrl key was pressed. + * {Boolean} shiftKey whether the shift key was pressed. + * {Boolean} altKey whether the alt key was pressed. + * {Number} keyCode which key was pressed. This is an ascii number. + * {Event} htmlEvent the html event object that triggered this event from being called. + */ +apf.GuiElement = function(){ + this.$init(true); +}; + +(function(){ + this.$regbase = this.$regbase | apf.__GUIELEMENT__; + + this.$focussable = apf.KEYBOARD_MOUSE; // Each GUINODE can get the focus by default + this.visible = 2; //default value; + + /*this.minwidth = 5; + this.minheight = 5; + this.maxwidth = 10000; + this.maxheight = 10000;*/ + + + this.$booleanProperties["disable-keyboard"] = true; + + this.$booleanProperties["visible"] = true; + this.$booleanProperties["focussable"] = true; + + + this.$supportedProperties.push("draggable", "resizable"); + + this.$supportedProperties.push( + "focussable", "zindex", "disabled", "tabindex", + "disable-keyboard", "contextmenu", "visible", "autosize", + "loadaml", "actiontracker", "alias", + "width", "left", "top", "height", "tooltip" + ); + + this.$setLayout = function(type, insert){ + if (!this.$drawn || !this.$pHtmlNode) + return false; + + if (this.parentNode) { + + if (this.parentNode.localName == "table") { + if (this.$disableCurrentLayout) + this.$disableCurrentLayout(); + this.parentNode.register(this, insert); + this.$disableCurrentLayout = null; + return type == "table"; + }else + + + + if ("vbox|hbox".indexOf(this.parentNode.localName) > -1) { + if (this.$disableCurrentLayout) + this.$disableCurrentLayout(); + this.parentNode.register(this, insert); + this.$disableCurrentLayout = null; + return type == this.parentNode.localName; + } //else + + } + + + if (!this.$anchoringEnabled) { + if (this.$disableCurrentLayout) + this.$disableCurrentLayout(); + this.$enableAnchoring(); + this.$disableCurrentLayout = this.$disableAnchoring; + } + return type == "anchoring"; + + } + + this.addEventListener("DOMNodeInserted", function(e){ + if (e.currentTarget == this + && "vbox|hbox|table".indexOf(this.parentNode.localName) == -1) { + this.$setLayout(); + } + }); + + this.implement( + + apf.Anchoring + + + ,apf.ContentEditable + + + ,apf.LiveEdit + + ); + + /**** Convenience functions for gui nodes ****/ + + + + /**** Geometry ****/ + + /** + * Sets the different between the left edge and the right edge of this + * element. Depending on the choosen layout method the unit can be + * pixels, a percentage or an expression. + * Call-chaining is supported. + * @param {Number} value the new width of this element. + */ + this.setWidth = function(value){ + this.setProperty("width", value, false, true); + return this; + }; + + /** + * Sets the different between the top edge and the bottom edge of this + * element. Depending on the choosen layout method the unit can be + * pixels, a percentage or an expression. + * Call-chaining is supported. + * @param {Number} value the new height of this element. + */ + this.setHeight = function(value){ + this.setProperty("height", value, false, true); + return this; + }; + + /** + * Sets the left position of this element. Depending on the choosen + * layout method the unit can be pixels, a percentage or an expression. + * Call-chaining is supported. + * @param {Number} value the new left position of this element. + */ + this.setLeft = function(value){ + this.setProperty("left", value, false, true); + return this; + }; + + /** + * Sets the top position of this element. Depending on the choosen + * layout method the unit can be pixels, a percentage or an expression. + * Call-chaining is supported. + * @param {Number} value the new top position of this element. + */ + this.setTop = function(value){ + this.setProperty("top", value, false, true); + return this; + }; + + if (!this.show) { + /** + * Makes the elements visible. Call-chaining is supported. + */ + this.show = function(){ + this.setProperty("visible", true, false, true); + return this; + }; + } + + if (!this.hide) { + /** + * Makes the elements invisible. Call-chaining is supported. + */ + this.hide = function(){ + this.setProperty("visible", false, false, true); + return this; + }; + } + + /** + * Retrieves the calculated width in pixels for this element + */ + this.getWidth = function(){ + return (this.$ext || {}).offsetWidth; + }; + + /** + * Retrieves the calculated height in pixels for this element + */ + this.getHeight = function(){ + return (this.$ext || {}).offsetHeight; + }; + + /** + * Retrieves the calculated left position in pixels for this element + * relative to the offsetParent. + */ + this.getLeft = function(){ + return (this.$ext || {}).offsetLeft; + }; + + /** + * Retrieves the calculated top position in pixels for this element + * relative to the offsetParent. + */ + this.getTop = function(){ + return (this.$ext || {}).offsetTop; + }; + + /**** Disabling ****/ + + /** + * Activates the functions of this element. Call-chaining is supported. + */ + this.enable = function(){ + this.setProperty("disabled", false, false, true); + return this; + }; + + /** + * Deactivates the functions of this element. + * Call-chaining is supported. + */ + this.disable = function(){ + this.setProperty("disabled", true, false, true); + return this; + }; + + /**** z-Index ****/ + + /** + * Moves this element to the lowest z ordered level. + * Call-chaining is supported. + */ + this.sendToBack = function(){ + this.setProperty("zindex", 0, false, true); + return this; + }; + + /** + * Moves this element to the highest z ordered level. + * Call-chaining is supported. + */ + this.bringToFront = function(){ + this.setProperty("zindex", apf.all.length + 1, false, true); + return this; + }; + + /** + * Moves this element one z order level deeper. + * Call-chaining is supported. + */ + this.sendBackwards = function(){ + this.setProperty("zindex", this.zindex - 1, false, true); + return this; + }; + + /** + * Moves this element one z order level higher. + * Call-chaining is supported. + */ + this.bringForward = function(){ + this.setProperty("zindex", this.zindex + 1, false, true); + return this; + }; + + + + this.hasFocus = function(){} + + this.addEventListener("DOMNodeInsertedIntoDocument", function(e){ + var x = this.$aml; + + // will $pHtmlNode be deprecated soon? + // check used to be: + //if (!this.$pHtmlNode && this.parentNode) + if (this.parentNode) { + if (this.localName == "item" + && this.parentNode.hasFeature(apf.__MULTISELECT__)) //special case for item nodes, using multiselect rendering + this.$pHtmlNode = this.parentNode.$container; + else + this.$pHtmlNode = this.parentNode.$int; //@todo apf3.0 change this in the mutation events + } + + if (!this.$pHtmlNode) //@todo apf3.0 retry on DOMNodeInserted + return; + + this.$pHtmlDoc = this.$pHtmlNode.ownerDocument || document; + + if (this.$initSkin) + this.$initSkin(x); + + if (this.$draw) + this.$draw(); + + if (e.id) + this.$ext.setAttribute("id", e.id); + + if (typeof this.visible == "undefined") + this.visible = true; + + + if (apf.debug && this.$ext && this.$ext.nodeType) + this.$ext.setAttribute("uniqueId", this.$uniqueId); + + + + if (this.$focussable && typeof this.focussable == "undefined") + apf.GuiElement.propHandlers.focussable.call(this); + + + this.$drawn = true; + }, true); + + var f = function(e){ + if (!this.$pHtmlNode) //@todo apf3.0 retry on DOMInsert or whatever its called + return; + + this.$setLayout(); //@todo apf3.0 moving an element minwidth/height should be recalced + + //@todo apf3.0 set this also for skin change + if (this.$ext) { + var hasPres = (this.hasFeature(apf.__PRESENTATION__)) || false; + var type = this.$isLeechingSkin ? this.localName : "main"; + if (this.minwidth == undefined) + this.minwidth = apf.getCoord(hasPres && parseInt(this.$getOption(type, "minwidth")), 0); + if (this.minheight == undefined) + this.minheight = apf.getCoord(hasPres && parseInt(this.$getOption(type, "minheight")), 0); + if (this.maxwidth == undefined) + this.maxwidth = apf.getCoord(hasPres && parseInt(this.$getOption(type, "maxwidth")), 10000); + if (this.maxheight == undefined) + this.maxheight = apf.getCoord(hasPres && parseInt(this.$getOption(type, "maxheight")), 10000); + + + //@todo slow?? + var diff = apf.getDiff(this.$ext); + this.$ext.style.minWidth = Math.max(0, this.minwidth - diff[0]) + "px"; + this.$ext.style.minHeight = Math.max(0, this.minheight - diff[1]) + "px"; + this.$ext.style.maxWidth = Math.max(0, this.maxwidth - diff[0]) + "px"; + this.$ext.style.maxHeight = Math.max(0, this.maxheight - diff[1]) + "px"; + + if (this.$altExt && apf.isGecko) { + this.$altExt.style.minHeight = this.$ext.style.minHeight; + this.$altExt.style.maxHeight = this.$ext.style.maxHeight; + this.$altExt.style.minWidth = this.$ext.style.minWidth; + this.$altExt.style.maxWidth = this.$ext.style.maxWidth; + } + + } + + if (this.$loadAml) + this.$loadAml(this.$aml); //@todo replace by event + }; + + this.addEventListener("DOMNodeInsertedIntoDocument", f); + this.addEventListener("$skinchange", f); + + + var f; + this.addEventListener("$event.resize", f = function(c){ + apf.layout.setRules(this.$ext, "resize", "var o = apf.all[" + this.$uniqueId + "];\ + if (o) o.dispatchEvent('resize');", true); + + apf.layout.queue(this.$ext); + //apf.layout.activateRules(this.$ext); + this.removeEventListener("$event.resize", f); + }); + + + + this.addEventListener("contextmenu", function(e){ + + if (this.editable) { //@todo when the event system is done proper this should be handled in ce2.js + e.returnValue = false; + e.cancelBubble = true; + return false; + } + + + if (!this.contextmenus) return; + + if (this.hasFeature(apf.__DATABINDING__)) { + var contextmenu; + var xmlNode = this.hasFeature(apf.__MULTISELECT__) + ? this.selected + : this.xmlRoot; + + var i, l, m, isRef, sel, menuId, cm, result; + for (i = 0, l = this.contextmenus.length; i < l; i++) { + isRef = (typeof (cm = this.contextmenus[i]) == "string"); + result = null; + if (!isRef && cm.match && xmlNode) {//@todo apf3.0 cache this statement + result = (cm.cmatch || (cm.cmatch = apf.lm.compile(cm.match, { + xpathmode : 3, + injectself : true + })))(xmlNode) + } + + if (isRef || xmlNode && result || !cm.match) { //!xmlNode && + menuId = isRef + ? cm + : cm.menu + + if (!self[menuId]) { + + throw new Error(apf.formatErrorString(0, this, + "Showing contextmenu", + "Could not find contextmenu by name: '" + menuId + "'"), + this.$aml); + + + return; + } + + self[menuId].display(e.x, e.y, null, this, xmlNode); + + e.returnValue = false;//htmlEvent. + e.cancelBubble = true; + break; + } + } + + //IE6 compatiblity + /* + @todo please test that disabling this is OK + if (!apf.config.disableRightClick) { + document.oncontextmenu = function(){ + document.oncontextmenu = null; + e.cancelBubble = true; + return false; + } + }*/ + } + else { + menuId = typeof this.contextmenus[0] == "string" + ? this.contextmenus[0] + : this.contextmenus[0].getAttribute("menu") + + if (!self[menuId]) { + + throw new Error(apf.formatErrorString(0, this, + "Showing contextmenu", + "Could not find contextmenu by name: '" + menuId + "'", + this.$aml)); + + + return; + } + + self[menuId].display(e.x, e.y, null, this); + + e.returnValue = false;//htmlEvent. + e.cancelBubble = true; + } + }); + +}).call(apf.GuiElement.prototype = new apf.AmlElement()); + +/** + * @for apf.amlNode + * @private + */ +apf.GuiElement.propHandlers = { + + /** + * @attribute {Boolean} focussable whether this element can receive the focus. + * The focussed element receives keyboard event.s + */ + "focussable": function(value){ + this.focussable = typeof value == "undefined" || value; + + if (!this.hasFeature(apf.__FOCUSSABLE__)) //@todo should this be on the prototype + this.implement(apf.Focussable); + + if (this.focussable) { + apf.window.$addFocus(this, this.tabindex); + } + else { + apf.window.$removeFocus(this); + } + }, + + + /** + * @attribute {Number} zindex the z ordered layer in which this element is + * drawn. + */ + "zindex": function(value){ + this.$ext.style.zIndex = value; + }, + + /** + * @attribute {Boolean} visible whether this element is shown. + */ + "visible": function(value){ + if (apf.isFalse(value) || typeof value == "undefined") { + if (this.$ext) + this.$ext.style.display = "none"; + + if (apf.document.activeElement == this || this.canHaveChildren + && apf.isChildOf(this, apf.document.activeElement, false)) { + if (apf.config.allowBlur && this.hasFeature(apf.__FOCUSSABLE__)) + this.blur(); + else + apf.window.moveNext(); + } + + this.visible = false; + } + else { //if (apf.isTrue(value)) default + if (this.$ext) { + this.$ext.style.display = ""; //Some form of inheritance detection + if (!this.$ext.offsetHeight) + this.$ext.style.display = this.$display || "block"; + } + + + if (apf.layout && this.$int) //apf.hasSingleRszEvent) + apf.layout.forceResize(this.$int);//this.$int + + + this.visible = true; + } + }, + + /** + * @attribute {Boolean} disabled whether this element's functions are active. + * For elements that can contain other apf.NODE_VISIBLE elements this + * attribute applies to all it's children. + */ + "disabled": function(value){ + if (!this.$drawn) { + var _self = this; + //this.disabled = false; + + apf.queue.add("disable" + this.$uniqueId, function(e){ + _self.disabled = value; + apf.GuiElement.propHandlers.disabled.call(_self, value); + }); + return; + } + else + apf.queue.remove("disable" + this.$uniqueId); + + //For child containers we only disable its children + if (this.canHaveChildren) { + //@todo Fix focus here first.. else it will jump whilst looping + if (value != -1) + value = this.disabled = apf.isTrue(value); + + var nodes = this.childNodes; + for (var node, i = 0, l = nodes.length; i < l; i++) { + node = nodes[i]; + if (node.nodeFunc == apf.NODE_VISIBLE) { + if (value && node.disabled != -1) + node.$disabled = node.disabled || false; + node.setProperty("disabled", value ? -1 : false); + } + } + + //this.disabled = undefined; + if (this.$isWindowContainer) + return; + } + + if (value == -1 || value == false) { + //value = true; + } + else if (typeof this.$disabled == "boolean") { + if (value === null) { + value = this.$disabled; + this.$disabled = null; + } + else { + this.$disabled = value || false; + return; + } + } + + if (apf.isTrue(value) || value == -1) { + this.disabled = false; + if (apf.document.activeElement == this) { + apf.window.moveNext(true); //@todo should not include window + if (apf.document.activeElement == this) + this.$blur(); + } + + if (this.hasFeature(apf.__PRESENTATION__)) + this.$setStyleClass(this.$ext, this.$baseCSSname + "Disabled"); + + if (this.$disable) + this.$disable(); + + + + this.disabled = value; + } + else { + if (this.hasFeature(apf.__DATABINDING__) && apf.config.autoDisable + && !(!this.$bindings || this.xmlRoot)) + return false; + + this.disabled = false; + + if (apf.document.activeElement == this) + this.$focus(); + + if (this.hasFeature(apf.__PRESENTATION__)) + this.$setStyleClass(this.$ext, null, [this.$baseCSSname + "Disabled"]); + + if (this.$enable) + this.$enable(); + + + } + }, + + /** + * @attribute {Boolean} enables whether this element's functions are active. + * For elements that can contain other apf.NODE_VISIBLE elements this + * attribute applies to all it's children. + */ + "enabled" : function(value){ + this.setProperty("disabled", !value); + }, + + /** + * @attribute {Boolean} disable-keyboard whether this element receives + * keyboard input. This allows you to disable keyboard independently from + * focus handling. + */ + "disable-keyboard": function(value){ + this.disableKeyboard = apf.isTrue(value); + }, + + /** + * @attribute {String} tooltip the text displayed when a user hovers with + * the mouse over the element. + */ + "tooltip" : function(value){ + this.$ext.setAttribute("title", value + (this.hotkey ? " (" + this.hotkey + ")" : "")); + }, + + + /** + * @attribute {String} contextmenu the name of the menu element that will + * be shown when the user right clicks or uses the context menu keyboard + * shortcut. + * Example: + * + * + * test + * test2 + * + * + * + * + * + */ + "contextmenu": function(value){ + this.contextmenus = [value]; + }, + + + + /** + * @attribute {String} actiontracker the name of the actiontracker that + * is used for this element and it's children. If the actiontracker doesn't + * exist yet it is created. + * Example: + * In this example the list uses a different actiontracker than the two + * textboxes which determine their actiontracker based on the one that + * is defined on the bar. + * + * + * + * + * + * + * + * + */ + "actiontracker": function(value){ + if (!value) { + this.$at = null; + } + else if (value.localName == "actiontracker") { + this.$at = value; + } + else { + + this.$at = typeof value == "string" && self[value] + ? apf.nameserver.get("actiontracker", value) || self[value].getActionTracker() + : apf.setReference(value, + apf.nameserver.register("actiontracker", + value, new apf.actiontracker())); + + if (!this.$at.name) + this.$at.name = value; + + } + }, + + + //Load subAML + /** + * @attribute {String} aml the {@link term.datainstruction data instruction} + * that loads new aml as children of this element. + */ + "aml": function(value){ + this.replaceMarkup(value); + } + + /** + * @attribute {String} sets this aml element to be editable + * that loads new aml as children of this element. + */ + + /*"editable": function(value){ + this.implement(apf.ContentEditable); + this.$propHandlers["editable"].apply(this, arguments); + },*/ + + + +}; + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/core/baseclasses/presentation.js)SIZE(20706)TIME(Mon, 10 Jan 2011 10:53:11 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + +apf.__PRESENTATION__ = 1 << 9; + + +/** + * All elements inheriting from this {@link term.baseclass baseclass} have skinning features. A skin is a description + * of how the element is rendered. In the web browser this is done using html + * elements and css. + * Remarks: + * The skin is set using the skin attribute. The skin of each element can be + * changed at run time. Other than just changing the look of an element, a skin + * change can help the user to perceive information in a different way. For + * example a list element has a default skin, but can also use the thumbnail + * skin to display thumbnails of the {@link term.datanode data nodes}. + * + * A skin for an element is always build up out of a standard set of parts. + * + * + * + * ... + * + * + * + * + * + * ... + * + * ... + * + * + * + * The alias contains a name that contains alternative names for the skin. The + * style tags contain the css. The main tag contains the html elements that are + * created when the component is created. Any other skin items are used to render + * other elements of the widget. In this reference guide you will find these + * skin items described on the pages of each widget. + * + * @constructor + * @baseclass + * @author Ruben Daniels (ruben AT ajax DOT org) + * @version %I%, %G% + * @since 0.5 + */ +apf.Presentation = function(){ + this.$init(true); +}; + +(function(){ + this.$regbase = this.$regbase | apf.__PRESENTATION__; + + /**** Properties and Attributes ****/ + + this.$supportedProperties.push("skin"); + + /** + * @attribute {string} skinset the skinset for + * this element. If none is specified the skinset attribute + * of the appsettings is used. When not defined the default skinset + * is used. + * Example: + * + * + * + */ + this.$propHandlers["skinset"] = + + /** + * @attribute {string} skin the name of the skin in the skinset that defines + * how this element is rendered. When a skin is changed the full state of the + * element is kept including it's selection, all the + * aml attributes, loaded data, focus and disabled state. + * Example: + * + * + * + * Example: + * + * lstExample.setAttribute("skin", "list"); + * + */ + + this.$propHandlers["skin"] = function(value){ + if (!this.$amlLoaded) //If we didn't load a skin yet, this will be done when we attach to a parent + return; + + if (!this.$skinTimer) { + var _self = this; + clearTimeout(this.$skinTimer); + this.$skinTimer = $setTimeout(function(){ + changeSkin.call(_self, _self.skin); + delete _self.$skinTimer; + }); + } + } + + + /** + * @attribute {String} style the css style applied to the this element. This can be a string containing one or more css rules. + */ + this.$propHandlers["style"] = function(value){ + if (!this.styleAttrIsObj && this.$amlLoaded) + this.$ext.setAttribute("style", value); + } + + /** + * @attribute {String} border turns borders on and off. Set sizes in the seq top, right, bottom, left. + */ + this.$propHandlers["border"] = function(value){ + if (!value) + this.$ext.style.borderWidth = ""; + else + this.$ext.style.borderWidth = apf.getBox(value).join("px ") + "px"; + } + + /** + * @attribute {String} margin turns margins on and off. Set sizes in the seq top, right, bottom, left. + */ + this.$propHandlers["margin"] = function(value){ + if (!value) + this.$ext.style.margin = ""; + else + this.$ext.style.margin = apf.getBox(value).join("px ") + "px"; + } + + /** + * @attribute {String} class the name of the css style class applied to the this element. + */ + this.$propHandlers["class"] = function(value){ + this.$setStyleClass(this.$ext, value, this.$lastClassValue ? [this.$lastClassValue] : null); + this.$lastClassValue = value; + } + + + this.$forceSkinChange = function(skin, skinset){ + changeSkin.call(this, skin, skinset); + } + + //@todo objects don't always have an $int anymore.. test this + function changeSkin(skin, skinset){ + clearTimeout(this.$skinTimer); + + //var skinName = (skinset || this.skinset || apf.config.skinset) + // + ":" + (skin || this.skin || this.localName); + + + //Store selection + if (this.selectable) + var valueList = this.getSelection();//valueList; + + + //Store needed state information + var oExt = this.$ext, + oInt = this.$int, + pNode = this.$ext.parentNode, + beforeNode = oExt.nextSibling, + idExt = this.$ext.getAttribute("id"), + idInt = this.$int && this.$int.getAttribute("id"), + oldBase = this.$baseCSSname; + + if (oExt.parentNode) + oExt.parentNode.removeChild(oExt); + + //@todo changing skin will leak A LOT, should call $destroy here, with some extra magic + if (this.$destroy) + this.$destroy(true); + + //Load the new skin + this.skin = skin; + this.$loadSkin(skinset ? skinset + ":" + skin : null); + + //Draw + if (this.$draw) + this.$draw(true); + + if (idExt) + this.$ext.setAttribute("id", idExt); + + if (beforeNode || pNode != this.$ext.parentNode) + pNode.insertBefore(this.$ext, beforeNode); + + //Style + + //Border + + //Margin + + //Classes + var i, l, newclasses = [], + classes = (oExt.className || "").splitSafe("\\s+"); + for (i = 0; i < classes.length; i++) { + if (classes[i] && classes[i].indexOf(oldBase) != 0) + newclasses.push(classes[i].replace(oldBase, this.$baseCSSname)); + } + apf.setStyleClass(this.$ext, newclasses.join(" ")); + + //Copy events + var en, ev = apf.skins.events; + for (i = 0, l = ev.length; i < l; i++) { + en = ev[i]; + if (typeof oExt[en] == "function" && !this.$ext[en]) + this.$ext[en] = oExt[en]; + } + + //Copy css state (dunno if this is best) + this.$ext.style.left = oExt.style.left; + this.$ext.style.top = oExt.style.top; + this.$ext.style.width = oExt.style.width; + this.$ext.style.height = oExt.style.height; + this.$ext.style.right = oExt.style.right; + this.$ext.style.bottom = oExt.style.bottom; + this.$ext.style.zIndex = oExt.style.zIndex; + this.$ext.style.position = oExt.style.position; + this.$ext.style.display = oExt.style.display; + + //Widget specific + //if (this.$loadAml) + //this.$loadAml(this.$aml); + + if (idInt) + this.$int.setAttribute("id", idInt); + + if (this.$int && this.$int != oInt) { + var node, newNode = this.$int, nodes = oInt.childNodes; + for (var i = nodes.length - 1; i >= 0; i--) { + if ((node = nodes[i]).host) { + node.host.$pHtmlNode = newNode; + if (node.host.$isLeechingSkin) + setLeechedSkin.call(node.host); + } + newNode.insertBefore(node, newNode.firstChild); + } + //this.$int.onresize = oInt.onresize; + } + + + //DragDrop + if (this.hasFeature(apf.__DRAGDROP__)) { + if (document.elementFromPointAdd) { + document.elementFromPointRemove(oExt); + document.elementFromPointAdd(this.$ext); + } + } + + + //Check disabled state + if (this.disabled) + this.$disable(); //@todo apf3.0 test + + //Check focussed state + if (this.$focussable && apf.document.activeElement == this) + this.$focus(); //@todo apf3.0 test + + //Dispatch event + this.dispatchEvent("$skinchange", { + ext : oExt, + "int": oInt + }); + + + //Reload data + if (this.hasFeature(apf.__DATABINDING__) && this.xmlRoot) + this.reload(); + else + + if (this.value) + this.$propHandlers["value"].call(this, this.value); + + + //Set Selection + if (this.hasFeature(apf.__MULTISELECT__)) { + if (this.selectable) + this.selectList(valueList, true); + } + + + //Move layout rules + if (!apf.hasSingleRszEvent) { + apf.layout.activateRules(this.$ext); + if (this.$int) + apf.layout.activateRules(this.$int); + } + +/* + if (this.hasFeature(apf.__ANCHORING__)) + this.$recalcAnchoring(); + + + + if (this.hasFeature(apf.__ALIGNMENT__)) { + if (this.aData) + this.aData.oHtml = this.$ext; + + if (this.pData) { + this.pData.oHtml = this.$ext; + this.pData.pHtml = this.$int; + + var nodes = this.pData.childNodes; + for (i = 0; i < nodes.length; i++) + nodes[i].pHtml = this.$int; //Should this be recursive?? + } + } + +*/ + + if (this.draggable && this.$propHandlers["draggable"]) //@todo move these to the event below apf3.0) + this.$propHandlers["draggable"].call(this, this.draggable); + if (this.resizable && this.$propHandlers["resizable"]) + this.$propHandlers["resizable"].call(this, this.resizable); + + + + apf.layout.forceResize(this.$ext); + + }; + + + /**** Private methods ****/ + + this.$setStyleClass = apf.setStyleClass; + + function setLeechedSkin(e){ + if (!this.$amlLoaded || e && (e.$isMoveWithinParent + || e.currentTarget != this || !e.$oldParent)) + return; + + this.$setInheritedAttribute(this, "skinset"); + + if (this.attributes.getNamedItem("skin")) + return; + + //e.relatedNode + var skinName, pNode = this.parentNode, skinNode; + if ((skinName = this.$canLeechSkin.dataType + == apf.STRING ? this.$canLeechSkin : this.localName) + && pNode.$originalNodes + && (skinNode = pNode.$originalNodes[skinName]) + && skinNode.getAttribute("inherit")) { + var link = skinNode.getAttribute("link"); + this.$isLeechingSkin = true; + if (link) { + this.$forceSkinChange(link); + } + else { + var skin = pNode.skinName.split(":"); + this.$forceSkinChange(skin[1], skin[0]); + } + } + else if (this.$isLeechingSkin) { + delete this.kin; + this.$forceSkinChange(); + } + } + + //Skin Inheritance + //@todo Probably requires some cleanup + this.$initSkin = function(x){ + if (this.$canLeechSkin) { + this.addEventListener("DOMNodeInserted", setLeechedSkin); + } + + if (!this.skin) + this.skin = this.getAttribute("skin"); + + var skinName, pNode = this.parentNode, skinNode; + if (this.$canLeechSkin && !this.skin + && (skinName = this.$canLeechSkin.dataType == apf.STRING + ? this.$canLeechSkin + : this.localName) + && pNode.$originalNodes + && (skinNode = pNode.$originalNodes[skinName]) + && skinNode.getAttribute("inherit")) { + var link = skinNode.getAttribute("link"); + this.$isLeechingSkin = true; + if (link) { + this.skin = link; + this.$loadSkin(); + } + else { + this.$loadSkin(pNode.skinName); + } + } + else { + if (!this.skinset) + this.skinset = this.getAttribute("skinset"); + + this.$loadSkin(null, this.$canLeechSkin); + } + } + + /** + * Initializes the skin for this element when none has been set up. + * + * @param {String} skinName required Identifier for the new skin (for example: 'default:List' or 'win'). + * @param {Boolean} [noError] + */ + this.$loadSkin = function(skinName, noError){ + //this.skinName || //where should this go and why? + this.baseSkin = skinName || (this.skinset + || this.$setInheritedAttribute("skinset")) + + ":" + (this.skin || this.localName); + + clearTimeout(this.$skinTimer); + + if (this.skinName) { + this.$blur(); + this.$baseCSSname = null; + } + + this.skinName = this.baseSkin; //Why?? + //this.skinset = this.skinName.split(":")[0]; + + this.$pNodes = {}; //reset the this.$pNodes collection + this.$originalNodes = apf.skins.getTemplate(this.skinName, true); + + if (!this.$originalNodes) { + var skin = this.skin; + if (skin) { + var skinset = this.skinName.split(":")[0]; + this.baseName = this.skinName = "default:" + skin; + this.$originalNodes = apf.skins.getTemplate(this.skinName); + + if (!this.$originalNodes && skinset != "default") { + this.baseName = this.skinName = skinset + ":" + this.localName; + this.$originalNodes = apf.skins.getTemplate(this.skinName, true); + } + } + + if (!this.$originalNodes) { + this.baseName = this.skinName = "default:" + this.localName; + this.$originalNodes = apf.skins.getTemplate(this.skinName); + } + + if (!this.$originalNodes) { + if (noError) { + return (this.baseName = this.skinName = + this.originalNode = null); + } + + throw new Error(apf.formatErrorString(1077, this, + "Presentation", + "Could not load skin: " + this.baseSkin)); + } + + //this.skinset = this.skinName.split(":")[0]; + } + + if (this.$originalNodes) + apf.skins.setSkinPaths(this.skinName, this); + }; + + this.$getNewContext = function(type, amlNode){ + + if (type != type.toLowerCase()) { + throw new Error("Invalid layout node name ('" + type + "'). lowercase required"); + } + + if (!this.$originalNodes[type]) { + throw new Error(apf.formatErrorString(0, this, + "Getting new skin item", + "Missing node in skin description '" + type + "'")); + } + + + this.$pNodes[type] = this.$originalNodes[type].cloneNode(true); + }; + + this.$hasLayoutNode = function(type){ + + if (type != type.toLowerCase()) { + throw new Error("Invalid layout node name ('" + type + "'). lowercase required"); + } + + + return this.$originalNodes[type] ? true : false; + }; + + this.$getLayoutNode = function(type, section, htmlNode){ + + if (type != type.toLowerCase()) { + throw new Error("Invalid layout node name ('" + type + "'). lowercase required"); + } + if (!this.$pNodes) { + throw new Error("Skin not loaded for :" + this.serialize(true)); + } + + + var node = this.$pNodes[type] || this.$originalNodes[type]; + if (!node) { + + if (!this.$dcache) + this.$dcache = {} + + if (!this.$dcache[type + "." + this.skinName]) { + this.$dcache[type + "." + this.skinName] = true; + apf.console.info("Could not find node '" + type + + "' in '" + this.skinName + "'", "skin"); + } + + return false; + } + + if (!section) + return htmlNode || apf.getFirstElement(node); + + var textNode = node.getAttribute(section); + if (!textNode) + return null; + + return (htmlNode + ? apf.queryNode(htmlNode, textNode) + : apf.getFirstElement(node).selectSingleNode(textNode)); + }; + + this.$getOption = function(type, section){ + type = type.toLowerCase(); //HACK: lowercasing should be solved in the comps. + + //var node = this.$pNodes[type]; + var node = this.$pNodes[type] || this.$originalNodes[type]; + if (!section || !node) + return node;//apf.getFirstElement(node); + var option = node.selectSingleNode("@" + section); + + return option ? option.nodeValue : ""; + }; + + this.$getExternal = function(tag, pNode, func, aml){ + if (!pNode) + pNode = this.$pHtmlNode; + if (!tag) + tag = "main"; + //if (!aml) + //aml = this.$aml; + + tag = tag.toLowerCase(); //HACK: make components case-insensitive + + this.$getNewContext(tag); + var oExt = this.$getLayoutNode(tag); + + var node; + if (node = (aml || this).getAttributeNode("style")) + oExt.setAttribute("style", node.nodeValue); + + //if (node = (aml || this).getAttributeNode("class")) + //this.$setStyleClass(oExt, (oldClass = node.nodeValue)); + + if (func) + func.call(this, oExt); + + oExt = apf.insertHtmlNode(oExt, pNode); + oExt.host = this; + if (node = (aml || this).getAttributeNode("bgimage")) + oExt.style.backgroundImage = "url(" + apf.getAbsolutePath( + this.mediaPath, node.nodeValue) + ")"; + + if (!this.$baseCSSname) + this.$baseCSSname = oExt.className.trim().split(" ")[0]; + + return oExt; + }; + + /**** Focus ****/ + this.$focus = function(){ + if (!this.$ext) + return; + + this.$setStyleClass(this.oFocus || this.$ext, this.$baseCSSname + "Focus"); + }; + + this.$blur = function(){ + + if (this.renaming) + this.stopRename(null, true); + + + if (!this.$ext) + return; + + this.$setStyleClass(this.oFocus || this.$ext, "", [this.$baseCSSname + "Focus"]); + }; + + this.$fixScrollBug = function(){ + if (this.$int != this.$ext) + this.oFocus = this.$int; + else { + this.oFocus = + this.$int = + this.$ext.appendChild(document.createElement("div")); + + this.$int.style.height = "100%"; + this.$int.className = "focusbug" + } + }; + + /**** Caching ****/ + /* + this.$setClearMessage = function(msg){}; + this.$updateClearMessage = function(){} + this.$removeClearMessage = function(){};*/ +}).call(apf.Presentation.prototype = new apf.GuiElement()); + +apf.config.$inheritProperties["skinset"] = 1; + + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/core/baseclasses/validation.js)SIZE(27646)TIME(Wed, 26 Jan 2011 15:01:32 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + +apf.__VALIDATION__ = 1 << 6; + + + +//if checkequal then notnull = true +apf.validator = { + macro : { + + "datatype" : "apf.xsd.matchType(value, '", + "datatype_" : "')", + + + //var temp + "pattern" : "value.match(", + "pattern_" : ")", + "custom" : "(", + "custom_" : ")", + "min" : "parseInt(value) >= ", + "max" : "parseInt(value) <= ", + "maxlength" : "value.toString().length <= ", + "minlength" : "value.toString().length >= ", + "notnull" : "value.toString().length > 0", + "checkequal" : "!(temp = ", + "checkequal_" : ").isValid() || temp.getValue() == value" + }, + + compile : function(options){ + var m = this.macro, s = ["var temp, valid = true; \ + if (!validityState) \ + validityState = new apf.validator.validityState(); "]; + + if (options.required) { + s.push("if (checkRequired && (!value || value.toString().trim().length == 0)) {\ + validityState.$reset();\ + validityState.valueMissing = true;\ + valid = false;\ + }") + } + + s.push("validityState.$reset();\ + if (value) {"); + + for (prop in options) { + if (!m[prop]) continue; + s.push("if (!(", m[prop], options[prop], m[prop + "_"] || "", ")){\ + validityState.$set('", prop, "');\ + valid = false;\ + }"); + } + + s.push("};validityState.valid = valid; return validityState;"); + return new Function('value', 'checkRequired', 'validityState', s.join("")); + } +} + +/** + * Object containing information about the validation state. It contains + * properties that specify whether a certain validation was passed. + * Remarks: + * This is part of {@link http://www.whatwg.org/specs/web-apps/current-work/multipage/forms.html#validitystatethe HTML 5 specification}. + */ +apf.validator.validityState = function(){ + this.valueMissing = false, + this.typeMismatch = false, + this.patternMismatch = false, + this.tooLong = false, + this.rangeUnderflow = false, + this.rangeOverflow = false, + this.stepMismatch = false, + this.customError = false, + this.valid = true, + + this.$reset = function(){ + for (var prop in this) { + if (prop.substr(0,1) == "$") + continue; + this[prop] = false; + } + this.valid = true; + }, + + this.$set = function(type) { + switch (type) { + case "min" : this.rangeUnderflow = true; break; + case "max" : this.rangeOverflow = true; break; + case "minlength" : this.tooShort = true; break; + case "maxlength" : this.tooLong = true; break; + case "pattern" : this.patternMismatch = true; break; + case "datatype" : this.typeMismatch = true; break; + case "notnull" : this.typeMismatch = true; break; + case "checkequal" : this.typeMismatch = true; break; + } + } +}; + +/** + * All elements inheriting from this {@link term.baseclass baseclass} have validation support. + * Example: + * + * + * Number + * + * Name + * + * Message + * + * + * + * Validate + * + * + * + * + * @event invalid Fires when this component goes into an invalid state. + * + * @constructor + * @baseclass + * @author Ruben Daniels (ruben AT ajax DOT org) + * @version %I%, %G% + * @since 0.5 + */ +apf.Validation = function(){ + this.$regbase = this.$regbase | apf.__VALIDATION__; + + /** + * Checks if this element's value is valid. + * + * @param {Boolean} [checkRequired] whether this check also adheres to the 'required' ruled. + * @return {Boolean} specifying whether the value is valid + * @see baseclass.validationgroup + * @see element.submitform + */ + this.isValid = function(checkRequired){ + if (!this.$vOptions) + return true; + + (this.$vOptions.isValid || (this.$vOptions.isValid + = apf.validator.compile(this.$vOptions))).call(this, + typeof this.getValue == "function" ? this.getValue(null, true) : null, + checkRequired, this.validityState || + (this.validityState = new apf.validator.validityState())); + + var valid = this.validityState.valid; + + + + if (!valid) + this.dispatchEvent("invalid", this.validityState); + + return valid; + }; + + /** + * @private + */ + this.setCustomValidity = function(message){ + //do stuff + } + + /** + * @private + * @todo This method should also scroll the element into view + */ + this.showMe = function(){ + var p = this.parentNode; + while (p) { + if (p.show) + p.show(); + p = p.parentNode; + } + }; + + /** + * Puts this element in the error state, optionally showing the + * error box if this element's is invalid. + * + * @param {Boolean} [ignoreReq] whether this element required check is turned on. + * @param {Boolean} [nosetError] whether the error box is displayed if this component does not validate. + * @param {Boolean} [force] whether this element in the error state and don't check if the element's value is invalid. + * @return {Boolean} boolean specifying whether the value is valid + * @see object.validationgroup + * @see element.submitform + * @method + */ + + this.checkValidity = + + + /** + * Puts this element in the error state, optionally showing the + * error box if this element is invalid. + * + * @param {Boolean} [ignoreReq] whether this element required check is turned on. + * @param {Boolean} [nosetError] whether the error box is displayed if this component does not validate. + * @param {Boolean} [force] whether this element in the error state and don't check if the element's value is invalid. + * @return {Boolean} boolean specifying whether the value is valid + * @see object.validationgroup + * @see element.submitform + * @method + */ + this.validate = function(ignoreReq, nosetError, force){ + //if (!this.$validgroup) return this.isValid(); + + if (force || !this.isValid(!ignoreReq) && !nosetError) { + this.setError(); + return false; + } + else { + this.clearError(); + return true; + } + }; + + /** + * @private + */ + this.setError = function(value){ + if (!this.$validgroup) + this.$propHandlers["validgroup"].call(this, "vg" + this.parentNode.$uniqueId); + + var errBox = this.$validgroup.getErrorBox(this); + + if (!this.$validgroup.allowMultipleErrors) + this.$validgroup.hideAllErrors(); + + errBox.setMessage(this.invalidmsg || value); + + apf.setStyleClass(this.$ext, this.$baseCSSname + "Error"); + this.showMe(); //@todo scroll refHtml into view + + errBox.display(this); + + + if (this.hasFeature(apf.__MULTISELECT__) && this.validityState.$errorXml) + this.select(this.validityState.$errorXml); + + + if (apf.document.activeElement && apf.document.activeElement != this) + this.focus(null, {mouse:true}); //arguable... + }; + + /** + * @private + */ + this.clearError = function(value){ + if (this.$setStyleClass) + this.$setStyleClass(this.$ext, "", [this.$baseCSSname + "Error"]); + + if (this.$validgroup) { + var errBox = this.$validgroup.getErrorBox(null, true); + if (!errBox || errBox.host != this) + return; + + errBox.hide(); + } + }; + + this.addEventListener("DOMNodeRemovedFromDocument", function(e){ + if (this.$validgroup) + this.$validgroup.unregister(this); + }); + + /** + * + * @attribute {Boolean} required whether a valid value for this element is required. + * @attribute {RegExp} pattern the pattern tested against the value of this element to determine it's validity. + * @attribute {String} datatype the datatype that the value of this element should adhere to. This can be any + * of a set of predefined types, or a simple type created by an XML Schema definition. + * Possible values: + * {String} xsd:dateTime + * {String} xsd:time + * {String} xsd:date + * {String} xsd:gYearMonth + * {String} xsd:gYear + * {String} xsd:gMonthDay + * {String} xsd:gDay + * {String} xsd:gMonth + * {String} xsd:string + * {String} xsd:boolean + * {String} xsd:base64Binary + * {String} xsd:hexBinary + * {String} xsd:float + * {String} xsd:decimal + * {String} xsd:double + * {String} xsd:anyURI + * {String} xsd:integer + * {String} xsd:nonPositiveInteger + * {String} xsd:negativeInteger + * {String} xsd:long + * {String} xsd:int + * {String} xsd:short + * {String} xsd:byte + * {String} xsd:nonNegativeInteger + * {String} xsd:unsignedLong + * {String} xsd:unsignedInt + * {String} xsd:unsignedShort + * {String} xsd:unsignedByte + * {String} xsd:positiveInteger + * {String} apf:url + * {String} apf:website + * {String} apf:email + * {String} apf:creditcard + * {String} apf:expdate + * {String} apf:wechars + * {String} apf:phonenumber + * {String} apf:faxnumber + * {String} apf:mobile + * @attribute {Integer} min the minimal value for which the value of this element is valid. + * @attribute {Integer} max the maximum value for which the value of this element is valid. + * @attribute {Integer} minlength the minimal length allowed for the value of this element. + * @attribute {Integer} maxlength the maximum length allowed for the value of this element. + * @attribute {Boolean} notnull whether the value is filled. Same as {@link baseclass.validation.attribute.required} but this rule is checked realtime when the element looses the focus, instead of at specific request (for instance when leaving a form page). + * @attribute {String} checkequal the id of the element to check if it has the same value as this element. + * @attribute {String} invalidmsg the message displayed when this element has an invalid value. Use a ; character to seperate the title from the message. + * @attribute {String} validgroup the identifier for a group of items to be validated at the same time. This identifier can be new. It is inherited from a AML node upwards. + * @attribute {String} validtest the instruction on how to test for success. This attribute is generally used to check the value on the server. + * Example: + * This example shows how to check the username on the server. In this case + * comm.loginCheck is an async rpc function that checks the availability of the + * username. If it exists it will return 0, otherwise 1. The value variable + * contains the current value of the element (in this case the textbox). It + * can be used as a convenience variable. + *
    +     *  Username
    +     *  
    +     * 
    + */ + this.addEventListener("DOMNodeInsertedIntoDocument", function(e){ + //this.addEventListener(this.hasFeature(apf.__MULTISELECT__) ? "onafterselect" : "onafterchange", onafterchange); + /* Temp disabled, because I don't understand it (RLD) + this.addEventListener("beforechange", function(){ + if (this.xmlRoot && apf.getBoundValue(this) === this.getValue()) + return false; + });*/ + + // validgroup + if (!this.validgroup) + this.$setInheritedAttribute("validgroup"); + }); + + //1 = force no bind rule, 2 = force bind rule + this.$attrExcludePropBind = apf.extend({ + pattern : 1, + validtest : 3 + }, this.$attrExcludePropBind); + + this.$booleanProperties["required"] = true; + this.$supportedProperties.push("validgroup", "required", "datatype", + "pattern", "min", "max", "maxlength", "minlength", "validtest", + "notnull", "checkequal", "invalidmsg", "requiredmsg"); + + this.$fValidate = function(){ + if (this.liveedit) + return; + + if (!this.$validgroup) + this.validate(true); + else { + var errBox = this.$validgroup.getErrorBox(this); + if (!errBox.visible || errBox.host != this) + this.validate(true); + } + }; + this.addEventListener("blur", this.$fValidate); + + this.$propHandlers["validgroup"] = function(value){ + if (value) { + var vgroup; + if (typeof value != "string") { + this.$validgroup = value.name; + vgroup = value; + } + else { + + vgroup = apf.nameserver.get("validgroup", value); + + } + + this.$validgroup = vgroup || new apf.ValidationGroup(value); + this.$validgroup.register(this); + /* + @todo What about children, when created after start + See button login action + */ + } + else { + this.$validgroup.unregister(this); + this.$validgroup = null; + } + }; + + this.$propHandlers["pattern"] = function(value, prop){ + if (value.substr(0, 1) != "/") + value = "/" + value + "/"; + + (this.$vOptions || (this.$vOptions = {}))[prop] = value; + delete this.$vOptions.isValid; + }; + + + this.$propHandlers["datatype"] = + + this.$propHandlers["required"] = + this.$propHandlers["custom"] = + this.$propHandlers["min"] = + this.$propHandlers["max"] = + this.$propHandlers["maxlength"] = + this.$propHandlers["minlength"] = + this.$propHandlers["notnull"] = + this.$propHandlers["checkequal"] = function(value, prop){ + (this.$vOptions || (this.$vOptions = {}))[prop] = value; + delete this.$vOptions.isValid; + }; + + //@todo rewrite this for apf3.0 - it should just execute a live markup + this.$propHandlers["validtest"] = function(value){ + var _self = this, rvCache = {}; + /** + * Removes the validation cache created by the validtest rule. + */ + this.removeValidationCache = function(){ + rvCache = {}; + } + + this.$checkRemoteValidation = function(){ + var value = this.getValue(); + if(typeof rvCache[value] == "boolean") return rvCache[value]; + if(rvCache[value] == -1) return true; + rvCache[value] = -1; + + apf.getData(this.validtest.toString(), { + xmlNode : this.xmlRoot, + value : this.getValue(), + callback : function(data, state, extra){ + if (state != apf.SUCCESS) { + if (state == apf.TIMEOUT && extra.retries < apf.maxHttpRetries) + return extra.tpModule.retry(extra.id); + else { + var commError = new Error(apf.formatErrorString(0, _self, + "Validating entry at remote source", + "Communication error: \n\n" + extra.message)); + + if (_self.dispatchEvent("error", apf.extend({ + error : commError, + state : status + }, extra)) !== false) + throw commError; + return; + } + } + + rvCache[value] = apf.isTrue(data);//instr[1] ? data == instr[1] : apf.isTrue(data); + + if(!rvCache[value]){ + if (!_self.hasFocus()) + _self.setError(); + } + else _self.clearError(); + } + }); + + return true; + }; + + (this.$vOptions || (this.$vOptions = {})).custom = "apf.lookup(" + this.$uniqueId + ").$checkRemoteValidation()"; + delete this.$vOptions.isValid; + }; +}; + + +apf.GuiElement.propHandlers["datatype"] = + +apf.GuiElement.propHandlers["required"] = +apf.GuiElement.propHandlers["pattern"] = +apf.GuiElement.propHandlers["min"] = +apf.GuiElement.propHandlers["max"] = +apf.GuiElement.propHandlers["maxlength"] = +apf.GuiElement.propHandlers["minlength"] = +apf.GuiElement.propHandlers["notnull"] = +apf.GuiElement.propHandlers["checkequal"] = +apf.GuiElement.propHandlers["validtest"] = function(value, prop){ + this.implement(apf.Validation); + this.$propHandlers[prop].call(this, value, prop); +} + +/** + * Object allowing for a set of AML elements to be validated, an element that + * is not valid shows the errorbox. + * Example: + * + * + * Phone number + * + * + * Password + * + * + * + * + * To check if the element has valid information entered, leaving the textbox + * (focussing another element) will trigger a check. Programmatically a check + * can be done using the following code: + * + * txtPhone.validate(); + * + * //Or use the html5 syntax + * txtPhone.checkValidity(); + * + * + * To check for the entire group of elements use the validation group. For only + * the first non-valid element the errorbox is shown. That element also receives + * focus. + * + * vgForm.validate(); + * + * + * @event validation Fires when the validation group isn't validated. + * + * @inherits apf.Class + * @constructor + * @default_private + * + * @author Ruben Daniels (ruben AT ajax DOT org) + * @version %I%, %G% + * @since 0.9 + */ +apf.ValidationGroup = function(name){ + this.$init(); + + this.childNodes = []; + + if (name) + apf.setReference(name, this); + + this.name = name || "validgroup" + this.$uniqueId; + + apf.nameserver.register("validgroup", this.name, this); + +}; + +(function(){ + /** + * When set to true, only visible elements are validated. Default is false. + * @type Boolean + */ + this.validateVisibleOnly = false; + + /** + * When set to true, validation doesn't stop at the first invalid element. Default is false. + * @type Boolean + */ + this.allowMultipleErrors = false; + + /** + * Adds a aml element to this validation group. + */ + this.register = function(o){ + if (o.hasFeature(apf.__VALIDATION__)) + this.childNodes.push(o); + }; + + /** + * Removes a aml element from this validation group. + */ + this.unregister = function(o){ + this.childNodes.remove(o); + }; + + /** + * Returns a string representation of this object. + */ + this.toString = function(){ + return "[APF Validation Group]"; + }; + + //Shared among all validationgroups + var errbox; + /** + * Retrieves {@link element.errorbox} used for a specified element. + * + * @param {AmlNode} o required AmlNode specifying the element for which the Errorbox should be found. If none is found, an Errorbox is created. Use the {@link object.validationgroup.property.allowMultipleErrors} to influence when Errorboxes are created. + * @param {Boolean} no_create Boolean that specifies whether new Errorbox may be created when it doesn't exist already + * @return {Errorbox} the found or created Errorbox; + */ + this.getErrorBox = function(o, no_create){ + if (this.allowMultipleErrors || !errbox && !no_create) { + errbox = new apf.errorbox(); + errbox.$pHtmlNode = o.$ext.parentNode; + errbox.skinset = apf.getInheritedAttribute(o.parentNode, "skinset"); //@todo use skinset here. Has to be set in presentation + errbox.dispatchEvent("DOMNodeInsertedIntoDocument"); + } + return errbox; + }; + + /** + * Hide all Errorboxes for the elements using this element as their validation group. + * + */ + this.hideAllErrors = function(){ + if (errbox && errbox.host) + errbox.host.clearError(); + }; + + function checkValidChildren(oParent, ignoreReq, nosetError){ + var found; + //Per Element + for (var v, i = 0; i < oParent.childNodes.length; i++) { + var oEl = oParent.childNodes[i]; + + if (!oEl) + continue; + if (!oEl.disabled + && (!this.validateVisibleOnly && oEl.visible || !oEl.$ext || oEl.$ext.offsetHeight) + && (oEl.hasFeature(apf.__VALIDATION__) && oEl.isValid && !oEl.isValid(!ignoreReq))) { + //|| !ignoreReq && oEl.required && (!(v = oEl.getValue()) || new String(v).trim().length == 0) + + if (!nosetError) { + if (!found) { + oEl.validate(true, null, true); + found = true; + if (!this.allowMultipleErrors) + return true; //Added (again) + } + else if (oEl.errBox && oEl.errBox.host == oEl) + oEl.errBox.hide(); + } + else if (!this.allowMultipleErrors) + return true; + } + if (oEl.canHaveChildren && oEl.childNodes.length) { + found = checkValidChildren.call(this, oEl, ignoreReq, nosetError) || found; + if (found && !this.allowMultipleErrors) + return true; //Added (again) + } + } + return found; + } + + /** + * Checks if (part of) the set of element's registered to this element are + * valid. When an element is found with an invalid value the error state can + * be set for that element. + * + * @param {Boolean} [ignoreReq] whether to adhere to the 'required' check. + * @param {Boolean} [nosetError whether to not set the error state of the element with an invalid value + * @param {AMLElement} [page] the page for which the children will be checked. When not specified all elements of this validation group will be checked. + * @return {Boolean} specifying whether the checked elements are valid. + * @method isValid, validate, checkValidity + */ + + this.checkValidity = + + + /** + * Checks if (part of) the set of element's registered to this element are + * valid. When an element is found with an invalid value the error state can + * be set for that element. + * + * @param {Boolean} [ignoreReq] whether to adhere to the 'required' check. + * @param {Boolean} [nosetError whether to not set the error state of the element with an invalid value + * @param {AMLElement} [page] the page for which the children will be checked. When not specified all elements of this validation group will be checked. + * @return {Boolean} specifying whether the checked elements are valid. + * @method isValid, validate, checkValidity + */ + this.validate = + + /** + * Checks if (part of) the set of element's registered to this element are + * valid. When an element is found with an invalid value the error state can + * be set for that element. + * + * @param {Boolean} [ignoreReq] whether to adhere to the 'required' check. + * @param {Boolean} [nosetError whether to not set the error state of the element with an invalid value + * @param {AMLElement} [page] the page for which the children will be checked. When not specified all elements of this validation group will be checked. + * @return {Boolean} specifying whether the checked elements are valid. + * @method isValid, validate, checkValidity + */ + this.isValid = function(ignoreReq, nosetError, page){ + var found = checkValidChildren.call(this, page || this, ignoreReq, + nosetError); + + if (page) { + + try { + + if (page.validation && !eval(page.validation)) { + alert(page.invalidmsg); + found = true; + } + + } + catch(e) { + throw new Error(apf.formatErrorString(0, this, + "Validating Page", + "Error in javascript validation string of page: '" + + page.validation + "'", page.$aml)); + } + + } + + //Global Rules + // + //if (!found) + //found = this.dispatchEvent("validation"); + + return !found; + }; +}).call(apf.ValidationGroup.prototype = new apf.Class()); + +apf.config.$inheritProperties["validgroup"] = 1; + + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/core/baseclasses/databinding.js)SIZE(60133)TIME(Mon, 10 Jan 2011 10:53:11 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + +apf.__DATABINDING__ = 1 << 1; + + + +/** + * Baseclass adding data binding features to this element. Databinding takes + * care of automatically going from data to representation and establishing a + * permanent link between the two. In this way data that is changed will + * change the representation as well. Furthermore, actions that are executed on + * the representation will change the underlying data. + * Example: + * + * + * + * + * Item 1 + * Item 2 + * + * + * + * + * + * + * + * + * + * + * @event error Fires when a communication error has occured while + * making a request for this element. + * cancelable: Prevents the error from being thrown. + * bubbles: + * object: + * {Error} error the error object that is thrown when the event + * callback doesn't return false. + * {Number} state the state of the call + * cancellable: Prevents the error from being thrown. + * Possible values: + * apf.SUCCESS the request was successfull + * apf.TIMEOUT the request has timed out. + * apf.ERROR an error has occurred while making the request. + * apf.OFFLINE the request was made while the application was offline. + * {mixed} userdata data that the caller wanted to be available in + * the callback of the http request. + * {XMLHttpRequest} http the object that executed the actual http request. + * {String} url the url that was requested. + * {Http} tpModule the teleport module that is making the request. + * {Number} id the id of the request. + * {String} message the error message. + * @event beforeretrieve Fires before a request is made to retrieve data. + * cancelable: Prevents the data from being retrieved. + * @event afterretrieve Fires when the request to retrieve data returns both + * on success and failure. + * @event receive Fires when data is successfully retrieved + * object: + * {String} data the retrieved data + * + * @constructor + * @baseclass + * @author Ruben Daniels (ruben AT ajax DOT org) + * @version %I%, %G% + * @since 0.4 + * @default_private + */ +apf.DataBinding = function(){ + this.$init(true); + + this.$loadqueue = + this.$dbTimer = null; + this.$regbase = this.$regbase | apf.__DATABINDING__; + this.$mainBind = "value"; + + this.$bindings = + this.$cbindings = + this.$attrBindings = false; + + //1 = force no bind rule, 2 = force bind rule + this.$attrExcludePropBind = apf.extend({ + model : 1, + each : 1 + //eachvalue : 1 //disabled because of line 1743 valueRule = in multiselect.js + }, this.$attrExcludePropBind); + + /**** Public Methods ****/ + + /** + * Sets a value of an XMLNode based on an xpath statement executed on the data of this model. + * + * @param {String} xpath the xpath used to select a XMLNode. + * @param {String} value the value to set. + * @return {XMLNode} the changed XMLNode + */ + this.setQueryValue = function(xpath, value, type){ + var node = apf.createNodeFromXpath(this[type || 'xmlRoot'], xpath); + if (!node) + return null; + + apf.setNodeValue(node, value, true); + //apf.xmldb.setTextNode(node, value); + return node; + }; + + /** + * Queries the bound data for a string value + * + * @param {String} xpath the xpath statement which queries on the data this element is bound on. + * @param {String} type the node that is used as the context node for the query. + * Possible values: + * selected the selected data anode of this element. + * xmlRoot the root data node that this element is bound on. + * indicator the data node that is highlighted for keyboard navigation. + * @return {String} value of the selected XML Node + * @todo + * lstRev.query('revision/text()', 'selected'); + * lstRev.query('revision/text()', 'xmlRoot'); + * lstRev.query('revision/text()', 'indicator'); + */ + this.queryValue = function(xpath, type){ + return apf.queryValue(this[type || 'xmlRoot'], xpath ); + }; + /** + * Queries the bound data for an array of string values + * + * @param {String} xpath the xpath statement which queries on the data this element is bound on. + * @param {String} type the node that is used as the context node for the query. + * Possible values: + * selected the selected data anode of this element. + * xmlRoot the root data node that this element is bound on. + * indicator the data node that is highlighted for keyboard navigation. + * @return {String} value of the selected XML Node + */ + this.queryValues = function(xpath, type){ + return apf.queryValues(this[type || 'xmlRoot'], xpath ); + }; + + /** + * Executes an xpath statement on the data of this model + * + * @param {String} xpath the xpath used to select the XMLNode(s). + * @param {String} type the node that is used as the context node for the query. + * Possible values: + * selected the selected data anode of this element. + * xmlRoot the root data node that this element is bound on. + * indicator the data node that is highlighted for keyboard navigation. + * @return {variant} XMLNode or NodeList with the result of the selection + */ + this.queryNode = function(xpath, type){ + var n = this[type||'xmlRoot']; + return n ? n.selectSingleNode(xpath) : null; + }; + + /** + * Executes an xpath statement on the data of this model + * + * @param {String} xpath the xpath used to select the XMLNode(s). + * @param {String} type the node that is used as the context node for the query. + * Possible values: + * selected the selected data anode of this element. + * xmlRoot the root data node that this element is bound on. + * indicator the data node that is highlighted for keyboard navigation. + * @return {variant} XMLNode or NodeList with the result of the selection + */ + this.queryNodes = function(xpath, type){ + var n = this[type||'xmlRoot']; + return n ? n.selectNodes(xpath) : []; + }; + + this.$checkLoadQueue = function(){ + // Load from queued load request + if (this.$loadqueue) { + if (!this.caching) + this.xmlRoot = null; + var q = this.load(this.$loadqueue[0], {cacheId: this.$loadqueue[1]}); + if (!q || q.dataType != apf.ARRAY || q != this.$loadqueue) + this.$loadqueue = null; + } + else return false; + }; + + //setProp + this.$execProperty = function(prop, xmlNode, undoObj){ + var attr = this.$attrBindings[prop]; + + //@todo this is a hacky solution for replaceNode support - Have to rethink this. + if (this.nodeType == 7) { + if (xmlNode != this.xmlRoot) + this.xmlRoot = xmlNode; + } + + + if (!attr) { + apf.console.error("Could not find attribute handler for property '" + + prop + "' on " + this.localName + ":" + (this.id || "")); + return; + } + + + + apf.$lm_has_lang = false; + + + + if (attr.cvalue.asyncs) { //if async + var _self = this; + return attr.cvalue.call(this, xmlNode, function(value){ + _self.setProperty(prop, value, true); + + + //@todo apf3.0 + if (apf.$lm_has_lang) + apf.language.addProperty(this, prop, attr.cvalue); //@todo should auto remove + + + }); + } + else { + var value = attr.cvalue.call(this, xmlNode); + } + + + this.setProperty(prop, undoObj && undoObj.extra.range || value, true); //@todo apf3.0 range + + + //@todo apf3.0 + if (apf.$lm_has_lang) + apf.language.addProperty(this, prop, attr.cvalue); //@todo should auto remove + + }; + + //@todo apf3.0 contentEditable support + this.$applyBindRule = function(name, xmlNode, defaultValue, callback, oHtml){ + var handler = this.$attrBindings[name] + && this.$attrBindings[name].cvalue || this.$cbindings[name]; + + return handler ? handler.call(this, xmlNode, callback) : defaultValue || ""; + }; + + + this.addEventListener("$clear", function(){ + var queue; + if (!this.$amlBindQueue || !(queue = this.$amlBindQueue.caption)) + return; + + for (var id in queue) { + var lm = queue[id]; + if (lm.destroy) { + lm.parentNode = lm.$focusParent = lm.$model = lm.xmlRoot = null; + lm.destroy(); + } + delete queue[id]; + } + + //delete this.$amlBindQueue; + }); + + var afterloadUpdate; + this.addEventListener("afterload", afterloadUpdate = function(){ + var queue; + if (!this.$amlBindQueue + || !(queue = this.$amlBindQueue.caption || this.$amlBindQueue.column)) + return; + + var div, doc = this.ownerDocument; + for (var lm, i = 0, l = queue.length; i < l; i++) { + if (!queue[i]) continue; //@todo check out why this happens + + div = document.getElementById("placeholder_" + + this.$uniqueId + "_" + i); + + lm = doc.createProcessingInstruction("lm", this.$cbindings.caption + || this.$cbindings.column); + lm.$model = this.$model; + lm.xmlRoot = queue[i]; //xmlNode + lm.$noInitModel = true; + //lm.$useXmlDiff = true; + lm.$focusParent = this; + lm.parentNode = this; + lm.dispatchEvent("DOMNodeInsertedIntoDocument", { + pHtmlNode: div + }); + queue[lm.xmlRoot.getAttribute(apf.xmldb.xmlIdTag)] = lm; + delete queue[i]; + } + + queue.length = 0; + }); + + //Add and remove handler + this.addEventListener("xmlupdate", function(e){ + if (e.xmlNode != e.traverseNode) //@todo this should be more specific + return; + + if ("insert|add|synchronize|move".indexOf(e.action) > -1) + afterloadUpdate.call(this, e); + else if ("remove|move-away".indexOf(e.action) > -1) { + var queue; + if (!this.$amlBindQueue || !(queue = this.$amlBindQueue.caption)) + return; + + /*var htmlNode = apf.xmldb.findHtmlNode(e.xmlNode, this); + var captionNode = this.$getLayoutNode("caption", htmlNode); + var id = captionNode.getAttribute("id").split("_")[2]; + var lm = queue[id];*/ + var lm = queue[e.xmlNode.getAttribute(apf.xmldb.xmlIdTag)]; + lm.parentNode = lm.$focusParent = lm.$model = lm.xmlRoot = null; + lm.destroy(); + } + }); + + + this.$hasBindRule = function(name){ + return this.$attrBindings[name] || this.$bindings + && this.$bindings[name]; + }; + + this.$getBindRule = function(name, xmlNode){ + return this.$attrBindings[name] || this.$bindings + && this.$bindings.getRule(name, xmlNode); + }; + + var ruleIsMatch = {"drag":1,"drop":1,"dragcopy":1} + this.$getDataNode = function(name, xmlNode, createNode, ruleList, multiple){ + var node, rule = this.$attrBindings[name]; + if (rule) { //@todo apf3.0 find out why drag and drop rules are already compiled here + if (rule.cvalue.type != 3) //@todo warn here? + return false; + + var func = rule.cvalue2 || rule.compile("value", { + xpathmode : multiple ? 4 : 3, + parsecode : 1, + injectself : ruleIsMatch[name] + }); + if (func && (node = func(xmlNode, createNode))) { + if (ruleList) + ruleList.push(rule); + + return node; + } + + return false; + } + + return this.$bindings + && this.$bindings.getDataNode(name, xmlNode, createNode, ruleList, multiple); + }; + + /** + * Sets the model of the specified element + * + * @param {Model} The model this element is going to connect to. + * + */ + + this.setModel = function(model){ + this.setAttribute("model", model, false, true); + }; + + + /** + * Gets the model to which this element is connected. + * This is the model which acts as a datasource for this element. + * + * @param {Boolean} doRecur whether the model should be searched recursively up the data tree. + * @returns {Model} The model this element is connected to. + * @see element.smartbinding + */ + this.getModel = function(doRecur){ + if (doRecur && !this.$model) + return this.dataParent ? this.dataParent.parent.getModel(true) : null; + + return this.$model; + }; + + /** + * Reloads the data in this element. + * + */ + this.reload = this.reload || function(){ + this.load(this.xmlRoot, {cacheId: this.cacheId, force: true}); + }; + + /** + * Loads data in to this element using binding rules to transform the + * data in to a presentation. + * Example: + * + * + * + * + * + * + * + * + * + * + * + * + * @param {mixed} [xmlNode] + * Possible Values: + * {XMLElement} an xml element loaded in to this element. + * {String} an xml string which is loaded in this element. + * {String} an instruction to load data from a remote source. + * {Null} null clears this element from it's data {@link baseclass.cache.method.clear}. + * @param {Object} [options] + * Properties: + * {XMLElement} [xmlNode] the {@link term.datanode data node} that provides + * context to the data instruction. + * {Function} [callback] the code executed when the data request returns. + * {mixed} [] Custom properties available in the data instruction. + * {String} [cacheId] the xml element to which the binding rules are applied. + * {Boolean} [force] whether cache is checked before loading the data. + * {Boolean} [noClearMsg] wether a message is set when clear is called. + * + * @event beforeload Fires before loading data in this element. + * cancelable: Prevents the data from being loaded. + * object: + * {XMLElement} xmlNode the node that is loaded as the root {@link term.datanode data node}. + * @event afterload Fires after loading data in this element. + * object: + * {XMLElement} xmlNode the node that is loaded as the root {@link term.datanode data node}. + * @see element.smartbinding + * @see baseclass.cache.method.clear + */ + this.load = function(xmlNode, options){ + if (options) { + var cacheId = options.cacheId, + forceNoCache = options.force, + noClearMsg = options.noClearMsg; + } + + if (cacheId && cacheId == this.cacheId && !forceNoCache) + return; + + + if (apf.popup.isShowing(this.$uniqueId)) + apf.popup.forceHide(); //This should be put in a more general position + + + // Convert first argument to an xmlNode we can use; + if (xmlNode) { + if (typeof xmlNode == "string") { + if (xmlNode.charAt(0) == "<") + xmlNode = apf.getXmlDom(xmlNode).documentElement; + else { + return apf.model.prototype.$loadFrom.call(this, xmlNode, options); + } + } + else if (xmlNode.nodeType == 9) { + xmlNode = xmlNode.documentElement; + } + else if (xmlNode.nodeType == 3 || xmlNode.nodeType == 4) { + xmlNode = xmlNode.parentNode; + } + else if (xmlNode.nodeType == 2) { + xmlNode = xmlNode.ownerElement + || xmlNode.parentNode + || xmlNode.selectSingleNode(".."); + } + } + + // If control hasn't loaded databinding yet, queue the call + if (this.$preventDataLoad || !this.$canLoadData + && ((!this.$bindings && (!this.$canLoadDataAttr || !this.each)) || !this.$amlLoaded) + && (!this.hasFeature(apf.__MULTISELECT__) || !this.each) + || this.$canLoadData && !this.$canLoadData()) { + + if (!this.caching || !this.hasFeature(apf.__CACHE__)) { + + //@todo this is wrong. It is never updated when there are only + //Property binds and then it just leaks xml nodes + this.xmlRoot = xmlNode; + + + this.setProperty("root", this.xmlRoot); + + } + + + if (this.$amlLoaded && !this.$attrBindings) { + apf.console.warn("Could not load data yet in " + (this.localName + ? this.localName + "[" + (this.name || "") + "]" + : this.nodeName) + ". The loaded data is queued " + + "until smartbinding rules are loaded or set manually."); + } + + + return this.$loadqueue = [xmlNode, cacheId]; + } + + // If no xmlNode is given we clear the control, disable it and return + if (this.dataParent && this.dataParent.xpath) + this.dataParent.parent.signalXmlUpdate[this.$uniqueId] = !xmlNode; + + if (!xmlNode && (!cacheId || !this.$isCached || !this.$isCached(cacheId))) { + + apf.console.warn("No xml root node was given to load in " + + this.localName + "[" + (this.name || '') + "]. Clearing any " + + "loaded xml in this component"); + + + this.clear(noClearMsg); + + + if (apf.config.autoDisable && this.$createModel === false) + this.setProperty("disabled", true); + + //@todo apf3.0 remove , true in clear above + //this.setProperty("selected", null); + + return; + } + + // If reloading current document, and caching is disabled, exit + if (!this.caching && !forceNoCache && xmlNode + && !this.$loadqueue && xmlNode == this.xmlRoot) + return; + + var disabled = this.disabled; + this.disabled = false; + + //Run onload event + if (this.dispatchEvent("beforeload", {xmlNode : xmlNode}) === false) + return false; + + + apf.console.info("Loading XML data in " + + (this.localName + ? this.localName + "[" + (this.name || '') + "]" + : this.nodeName)); + + + this.clear(true, true); + + this.cacheId = cacheId; + + if (this.dispatchEvent("$load", { + forceNoCache : forceNoCache, + xmlNode : xmlNode + }) === false) { + //delete this.cacheId; + return; + } + + //Set usefull vars + this.documentId = apf.xmldb.getXmlDocId(xmlNode); + this.xmlRoot = xmlNode; + + + this.setProperty("root", this.xmlRoot); + + + + apf.$lm_has_lang = false; + + + // Draw Content + this.$load(xmlNode); + + + //@todo apf3.0 + if (apf.$lm_has_lang) + apf.language.addBinding(this); //@todo should auto remove + else + apf.language.removeBinding(this); + + + // Check if subtree should be loaded + this.$loadSubData(xmlNode); + + if (this.$createModel === false) { + this.disabled = true; + this.setProperty("disabled", false); + } + else + this.disabled = disabled; + + // Run onafteronload event + this.dispatchEvent('afterload', {xmlNode : xmlNode}); + }; + + /** + * @binding load Determines how new data is loaded data is loaded into this + * element. Usually this is only the root node containing no children. + * Example: + * This example shows a load rule in a text element. It gets its data from + * a list. When a selection is made on the list the data is loaded into the + * text element. + * + * + * + * + * + * + * + * + * + * message 1 + * message 2 + * + * + * + * + * + * + * + * + * + * + * + * @attribute {string} get the {@link term.datainstruction data instruction} + * that is used to load data into the xmlRoot of this component. + */ + this.$loadSubData = function(xmlRootNode){ + if (this.$hasLoadStatus(xmlRootNode) && !this.$hasLoadStatus(xmlRootNode, "potential")) + return; + + //var loadNode = this.$applyBindRule("load", xmlRootNode); + var rule = this.$getBindRule("load", xmlRootNode); + if (rule && (!rule[1] || rule[1](xmlRootNode))) { + + if (typeof apf.offline != "undefined" && !apf.offline.onLine) { + apf.offline.transactions.actionNotAllowed(); + this.$loadedWhenOffline = true; + + //this.hasFeature(apf.__MULTISELECT__) + if (this.$setClearMessage && !this.getTraverseNodes().length) + this.$setClearMessage(this["offline-message"], "offline"); + + return; + } + + + this.$setLoadStatus(xmlRootNode, "loading"); + + if (this.$setClearMessage) + this.$setClearMessage(this["loading-message"], "loading"); + + //||apf.xmldb.findModel(xmlRootNode) + var mdl = this.getModel(true); + + if (!mdl) + throw new Error("Could not find model"); + + + var amlNode = this; + if (mdl.$insertFrom(rule.getAttribute("get"), { + xmlNode : xmlRootNode, //@todo apf3.0 + insertPoint : xmlRootNode, //this.xmlRoot, + amlNode : this, + callback : function(){ + + amlNode.setProperty(amlNode.hasFeature(apf.__MULTISELECT__) + ? "selected" + : "root", xmlRootNode); + + } + }) === false + ) { + this.clear(true); + + if (apf.config.autoDisable) + this.setProperty("disabled", true); + + //amlNode.setProperty("selected", null); //@todo is this not already done in clear? + + } + } + }; + + /** + * Unloads data from this element and resets state displaying an empty message. + * Empty message is set on the {@link baseclass.guielement.property.msg}. + * + * @param {Boolean} [nomsg] whether to display the empty message. + * @param {Boolean} [doEvent] whether to sent select events. + * @see baseclass.databinding.method.load + * @private + */ + //@todo this function is called way too much for a single load of a tree + //@todo should clear listener + this.clear = function(nomsg, doEvent, fakeClear){ + if (!this.$container) + return;//@todo apf3.0 + + if (this.clearSelection) + this.clearSelection(true);//!doEvent);//@todo move this to the $clear event in multiselect.js + + var lastHeight = this.$container.offsetHeight; + + if (this.dispatchEvent("$clear") !== false) + this.$container.innerHTML = ""; //@todo apf3.0 + + if (typeof nomsg == "string") { + var msgType = nomsg; + nomsg = false; + + //@todo apf3.0 please use attr. inheritance + if (!this[msgType + "-message"]) { + this.$setInheritedAttribute(msgType + "-message"); + } + } + this.$lastClearType = msgType || null; + + if (!nomsg && this.$setClearMessage) { + this.$setClearMessage(msgType + ? this[msgType + "-message"] + : this["empty-message"], msgType || "empty", lastHeight); + + //this.setProperty("selected", null); //@todo apf3.0 get the children to show loading... as well (and for each selected, null + //c[i].o.clear(msgType, doEvent); + } + else if(this.$removeClearMessage) + this.$removeClearMessage(); + + if (!fakeClear) + this.documentId = this.xmlRoot = this.cacheId = null; + + + if (!nomsg) { + if (this.hasFeature(apf.__MULTISELECT__)) //@todo this is all wrong + this.setProperty("length", 0); + //else + //this.setProperty("value", ""); //@todo redo apf3.0 + } + + }; + + this.clearMessage = function(msg){ + this.customMsg = msg; + this.clear("custom"); + }; + + /** + * @private + */ + //@todo optimize this + this.$setLoadStatus = function(xmlNode, state, remove){ + var group = this.loadgroup || "default"; + var re = new RegExp("\\|(\\w+)\\:" + group + ":(\\d+)\\|"); + var loaded = xmlNode.getAttribute("a_loaded") || ""; + + var m; + if (!remove && (m = loaded.match(re)) && m[1] != "potential" && m[2] != this.$uniqueId) + return; + + //remove old status if any + var ostatus = loaded.replace(re, "") + if (!remove) + ostatus += "|" + state + ":" + group + ":" + this.$uniqueId + "|"; + + xmlNode.setAttribute("a_loaded", ostatus); + }; + + /** + * @private + */ + this.$removeLoadStatus = function(xmlNode){ + this.$setLoadStatus(xmlNode, null, true); + }; + + /** + * @private + */ + this.$hasLoadStatus = function(xmlNode, state, unique){ + var ostatus = xmlNode.getAttribute("a_loaded"); + if (!ostatus) return false; + + var group = this.loadgroup || "default"; + var re = new RegExp("\\|" + (state || "\\w+") + ":" + group + ":" + (unique ? this.$uniqueId : "\\d+") + "\\|"); + return ostatus.match(re) ? true : false; + }; + + /** + * @event beforeinsert Fires before data is inserted. + * cancelable: Prevents the data from being inserted. + * object: + * {XMLElement} xmlParentNode the parent in which the new data is inserted + * @event afterinsert Fires after data is inserted. + */ + + /** + * @private + */ + this.insert = function(xmlNode, options){ + if (typeof xmlNode == "string") { + if (xmlNode.charAt(0) == "<") { + xmlNode = apf.getXmlDom(xmlNode).documentElement; + } + else { + if (!options.insertPoint) + options.insertPoint = this.xmlRoot; + return apf.model.prototype.$insertFrom.call(this, xmlNode, options); + } + } + + var insertPoint = options.insertPoint || this.xmlRoot; + + if (this.dispatchEvent("beforeinsert", { + xmlParentNode : insertPoint + }) === false) + return false; + + //Integrate XMLTree with parentNode + if (typeof options.copyAttributes == "undefined") + options.copyAttributes = true; + + var newNode = apf.mergeXml(xmlNode, insertPoint, options); + + //Call __XMLUpdate on all listeners + apf.xmldb.applyChanges("insert", insertPoint); + + //Select or propagate new data + if (this.selectable && this.autoselect) { + if (this.xmlNode == newNode) + this.$selectDefault(this.xmlNode); + } + + else if (this.xmlNode == newNode) { + this.setProperty("root", this.xmlNode); + } + + + if (this.$hasLoadStatus(insertPoint, "loading")) + this.$setLoadStatus(insertPoint, "loaded"); + + this.dispatchEvent("afterinsert"); + + //Check Connections + //this one shouldn't be called because they are listeners anyway...(else they will load twice) + //if(this.selected) this.setConnections(this.selected, "select"); + }; + + /** + * @attribute {Boolean} render-root whether the xml element loaded into this + * element is rendered as well. Default is false. + * Example: + * This example shows a tree which also renders the root element. + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + */ + this.$booleanProperties["render-root"] = true; + this.$supportedProperties.push("empty-message", "loading-message", + "offline-message", "render-root", "smartbinding", + "bindings", "actions"); + + /** + * @attribute {Boolean} render-root wether the root node of the data loaded + * into this element is rendered as well. + * @see element.tree + */ + this.$propHandlers["render-root"] = function(value){ + this.renderRoot = value; + }; + + /** + * @attribute {String} empty-message the message displayed by this element + * when it contains no data. This property is inherited from parent nodes. + * When none is found it is looked for on the appsettings element. Otherwise + * it defaults to the string "No items". + */ + this.$propHandlers["empty-message"] = function(value){ + this["empty-message"] = value; + + if (this.$updateClearMessage) + this.$updateClearMessage(this["empty-message"], "empty"); + }; + + /** + * @attribute {String} loading-message the message displayed by this + * element when it's loading. This property is inherited from parent nodes. + * When none is found it is looked for on the appsettings element. Otherwise + * it defaults to the string "Loading...". + * Example: + * This example uses property binding to update the loading message. The + * position of the progressbar should be updated by the script taking care + * of loading the data. + * + * + * + * + * Remarks: + * Usually a static loading message is displayed for only 100 milliseconds + * or so, whilst loading the data from the server. This is done for instance + * when the load binding rule is used. In the code example below a list + * binds on the selection of a tree displaying folders. When the selection + * changes, the list loads new data by extending the model. During the load + * of this new data the loading message is displayed. + * + * + * + * ... + * + * + * + * + */ + this.$propHandlers["loading-message"] = function(value){ + this["loading-message"] = value; + + if (this.$updateClearMessage) + this.$updateClearMessage(this["loading-message"], "loading"); + }; + + /** + * @attribute {String} offline-message the message displayed by this + * element when it can't load data because the application is offline. + * This property is inherited from parent nodes. When none is found it is + * looked for on the appsettings element. Otherwise it defaults to the + * string "You are currently offline...". + */ + this.$propHandlers["offline-message"] = function(value){ + this["offline-message"] = value; + + if (this.$updateClearMessage) + this.$updateClearMessage(this["offline-message"], "offline"); + }; + + /** + * @attribute {String} smartbinding the name of the SmartBinding for this + * element. A smartbinding is a collection of rules which define how data + * is transformed into representation, how actions on the representation are + * propagated to the data and it's original source, how drag&drop actions + * change the data and where the data is loaded from. Each of these are + * optionally defined in the smartbinding set and can exist independently + * of the smartbinding object. + * Example: + * This example shows a fully specified smartbinding. Usually only parts + * are used. This example shows a tree with files and folders. + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * Remarks: + * The smartbinding parts can also be assigned to an element by adding them + * directly as a child in aml. + * + * + * + * ... + * + * + * + * + * + * See: + * There are several ways to be less verbose in assigning certain rules. + *
      + *
    • {@link baseclass.multiselectbinding.binding.each}
    • + *
    • {@link baseclass.dragdrop.attribute.drag}
    • + *
    • {@link element.bindings}
    • + *
    • {@link element.actions}
    • + *
    • {@link element.dragdrop}
    • + *
    + */ + this.$propHandlers["smartbinding"] = + + /** + * @attribute {String} actions the id of the actions element which + * provides the action rules for this element. Action rules are used to + * send changes on the bound data to a server. + * Example: + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * Undo + * + */ + this.$propHandlers["actions"] = + + /** + * @attribute {String} bindings the id of the bindings element which + * provides the binding rules for this element. + * Example: + * This example shows a set of binding rules that transform data into the + * representation of a list. In this case it displays the names of + * several email accounts, with after each account name the number of unread + * mails in that account. It uses JSLT to transform the caption. + * + * + * + * Account 1 + * + * + * + * + * Account 2 + * + * + * + * + * + * [text()] (#[mail[@read != 'true']]) + * + * + * + * + * Remarks: + * Bindings can also be assigned directly by putting the bindings tag as a + * child of this element. + * + * If the rule only contains a select attribute, it can be written in a + * short way by adding an attribute with the name of the rule to the + * element itself: + * + * + * + */ + this.$propHandlers["bindings"] = function(value, prop){ + var local = "$" + prop + "Element"; + if (this[local]) + this[local].unregister(this); + + if (!value) + return; + + + if (!apf.nameserver.get(prop, value)) + throw new Error(apf.formatErrorString(1064, this, + "Setting " + prop, + "Could not find " + prop + " by name '" + value + "'")); + + + apf.nameserver.get(prop, value).register(this); + + + if (prop != "actions" && + this.$checkLoadQueue() === false && this.$amlLoaded) + 1+1; //@todo add reload queue. + //this.reload(); + }; + + + var eachBinds = {"caption":1, "icon":1, "select":1, "css":1, "sort":1, + "drop":2, "drag":2, "dragcopy":2, "eachvalue":1}; //Similar to apf.Class + + this.$addAttrBind = function(prop, fParsed, expression) { + //Detect if it uses an external model + if (fParsed.models) { + + if (this.hasFeature(apf.__MULTISELECT__)) { + + if (eachBinds[prop]) { + //throw new Error("Cannot use external model inside " + prop + " rule"); //@todo apf3.0 convert to apf error + } + + } + + } + + //Set listener for all models + var i, xpath, modelId, model, + paths = fParsed.xpaths, + list = {}; + //@todo when there is no model in xpath modelId == null... + for (i = 0; i < paths.length; i+=2) { + if (!list[(modelId = paths[i])]) + list[modelId] = 1; + else list[modelId]++ + } + + if (!this.$propsUsingMainModel) + this.$propsUsingMainModel = {}; + + var rule = (this.$attrBindings || (this.$attrBindings = {}))[prop] = { + cvalue : fParsed, + value : expression, + compile : apf.BindingRule.prototype.$compile, + models : [] + }; + + delete this.$propsUsingMainModel[prop]; + for (xpath, i = 0; i < paths.length; i+=2) { + modelId = paths[i]; + if (list[modelId] == -1) + continue; + + xpath = paths[i + 1]; + + if (modelId == "#" || xpath == "#") { + var m = (rule.cvalue3 || (rule.cvalue3 = apf.lm.compile(rule.value, { + xpathmode: 5 + })))(this.xmlRoot); + + //@todo apf3 this needs to be fixed in live markup + if (typeof m != "string") { + model = m.model && m.model.$isModel && m.model; + if (model) + xpath = m.xpath; + else if (m.model) { + model = typeof m.model == "string" ? apf.xmldb.findModel(m.model) : m.model; + xpath = apf.xmlToXpath(m.model, model.data) + (m.xpath ? "/" + m.xpath : ""); //@todo make this better + } + else { + //wait until model becomes available + this.addEventListener("prop." + prop, function(e){ + var m = (rule.cvalue3 || (rule.cvalue3 = apf.lm.compile(rule.value, { + xpathmode: 5 + })))(this.xmlRoot); + + if (m.model) { + this.removeEventListener("prop." + prop, arguments.callee); + var _self = this; + setTimeout(function(){ + _self.$clearDynamicProperty(prop); + _self.$setDynamicProperty(prop, expression); + }, 10); + } + }); + continue; + } + } + else model = null; + } + else model = null; + + if (!model) { + if (modelId) { + + //@todo apf3.0 how is this cleaned up??? + //Add change listener to the data of the model + model = apf.nameserver.get("model", modelId) //is model creation useful here? + || apf.setReference(modelId, apf.nameserver.register("model", modelId, new apf.model())); + + } + else { + if (!this.$model && !this.$initingModel) + initModel.call(this); + + model = this.$model; + + if (!this.hasFeature(apf.__MULTISELECT__) + && eachBinds[prop] != 2 || !eachBinds[prop]) //@experimental - should not set this because model will load these attributes + this.$propsUsingMainModel[prop] = { + xpath : xpath, + optimize : list[modelId] == 1 + }; + } + } + + //@todo warn here if no model?? + if (model && (!this.hasFeature(apf.__MULTISELECT__) + && eachBinds[prop] != 2 || !eachBinds[prop])) { + //Create the attribute binding + //@todo: remove listenRoot = expression.indexOf("*[") > -1 -> because it doesnt make sense in certain context. recheck selection handling + model.$bindXmlProperty(this, prop, xpath, list[modelId] == 1); + rule.models.push(model); + } + + list[modelId] = -1; + } + + rule.xpath = xpath; + + this.$canLoadDataAttr = eachBinds[prop] == 1; //@todo apf3.0 remove + this.$checkLoadQueue(); + } + + this.$removeAttrBind = function(prop){ + //@todo apf3.0 + //$model.$unbindXmlProperty + var rule = this.$attrBindings[prop] + if (!rule) + return; + + delete this.$attrBindings[prop]; + delete this.$propsUsingMainModel[prop] + + var models = rule.models; + if (models.length) + for (var i = 0; i < models.length; i++) { + models[i].$unbindXmlProperty(this, prop); + } + else if (this.$model) + this.$model.$unbindXmlProperty(this, prop); + }; + + this.$initingModel; + function initModel(){ + this.$initingModel = true; + this.$setInheritedAttribute("model"); + } + + this.addEventListener("DOMNodeInsertedIntoDocument", function(e){ + //Set empty message if there is no data + if (!this.model && this.$setClearMessage && !this.value) + this.$setClearMessage(this["empty-message"], "empty"); + + this.$amlLoaded = true; //@todo this can probably be removed + this.$checkLoadQueue(); + }); + + + this.addEventListener("DOMNodeRemovedFromDocument", function(e){ + apf.language.removeBinding(this); + }); + + + /** + * @attribute {String} model the name of the model to load data from or a + * datainstruction to load data. + * Example: + * + * + * + * + * + * + * + * + * + * Example: + * + * + * + * + * + * + * + * + * Example: + * + * + * + * + * + * + * + * + * + * Remarks: + * This attribute is inherited from a parent when not set. You can use this + * to tell sets of elements to use the same model. + * + * + * Name + * + * + * Happiness + * + * + * + * + * + * + * + * When no model is specified the default model is chosen. The default + * model is the first model that is found without a name, or if all models + * have a name, the first model found. + * Example: + * This example shows a dropdown from which the user can select a country. + * The list of countries is loaded from a model. Usually this would be loaded + * from a separate url, but for clarity it's inlined. When the user selects + * a country in the dropdown the value of the item is stored in the second + * model (mdlForm) at the position specified by the ref attribute. In this + * case this is the country element. + * + * Name + * + * + * Country + * + * + * + * + * + * USA + * Great Britain + * The Netherlands + * + * + * + * + * + * + * + * + * + * + * @see baseclass.databinding.attribute.model + */ + this.$propHandlers["model"] = function(value){ + //Unset model + if (!value && !this.$modelParsed) { + if (this.$model) { + this.clear(); + this.$model.unregister(this); + this.$model = null; + this.lastModelId = ""; + } + else if (this.dataParent) + this.dataParent.parent = null; //Should be autodisconnected by property binding + + return; + } + this.$initingModel = true; + + var fParsed; + //Special case for property binding + if ((fParsed = this.$modelParsed) && fParsed.type != 2) { + var found, pb = fParsed.props; + + if (this.dataParent) + this.dataParent = null; //Should be autodisconnected by property binding + + //Try to figure out who is the dataParent + for (var prop in pb){ + + if (typeof pb[prop] == "function") + continue; + + + this.dataParent = { + parent : self[prop.split(".")[0]], + xpath : null, + model : this.$modelParsed.instruction + }; + + found = true; + break; // We currently only support one data parent + } + + if (found) { + //@todo this statement doesnt make sense + /*//Maybe a compound model is found + if (!this.dataParent && (pb = fParsed.xpaths && fParsed.xpaths[0])) { + this.dataParent = { + parent : self[pb.split(".")[0]], + xpath : fParsed.xpaths[1], + model : this.$modelParsed.instruction + }; + }*/ + + if (this.dataParent && !this.dataParent.signalXmlUpdate) + this.dataParent.signalXmlUpdate = {}; + } + + this.$modelParsed = null; + } + + //Analyze the data + var model; + if (typeof value == "object") { + if (value.dataType == apf.ARRAY) { //Optimization used for templating + + model = apf.nameserver.get("model", value[0]); + model.register(this, value[1]); + return; + + } + else if (value.$isModel) { // A model node is passed + //Convert model object to value; + model = value; + value = this.model = model.name; + if (!value) + model.setProperty("id", value = this.model = "model" + model.$uniqueId); + + //@todo why not set directly here? + } + else { //if (this.dataParent) { //Data came through data parent + if (this.dataParent) + this.model = this.dataParent.model; //reset this property + + model = apf.xmldb.findModel(value); + if (!model) //@todo very strange, this should never happen, but it does + return; + var xpath = apf.xmlToXpath(value, null, true) || "."; + + + if (model.queryNode(xpath) != value) { + throw new Error("xml data node is not attached to model (" + + xpath + ") : " + value + ":" + (value && value.xml)); + } + + + model.register(this, xpath); + return; + } + /*else { + //@todo Error ?? + }*/ + } + else if (value.indexOf("[::") > -1) { //@experimental + var model, pNode = this; + do { + pNode = pNode.parentNode + model = pNode.getAttribute("model"); + } + while (pNode.parentNode && pNode.parentNode.nodeType == 1 && (!model || model == value)); + + if (model && typeof model == "object") + model = model.id; + + this.$inheritProperties.model = 3; + if (model) { + value = value.replace(/\[\:\:/g, "[" + model + "::"); + } + else { + apf.console.warn("No found model on any of the parents for this element while trying to overload model: " + value); + return; + } + } + + //Optimize xmlroot position and set model async (unset the old one) + //@todo apf3.0 this could be optimized by using apf.queue and only when not all info is there... + clearTimeout(this.$dbTimer); + if (!this.$amlLoaded && this.nodeType == 1) { + var _self = this; + this.$dbTimer = $setTimeout(function(){ + if (!_self.$amlDestroyed) + apf.setModel(value, _self); + }); + } + else + apf.setModel(value, this); + }; + + + /** + * @attribute {String} viewport the way this element renders its data. + * Possible values: + * virtual this element only renders data that it needs to display. + * normal this element renders all data at startup. + * @experimental + */ + this.$propHandlers["viewport"] = function(value){ + if (value != "virtual") + return; + + this.implement(apf.VirtualViewport); + }; + +}; + + apf.DataBinding.prototype = new apf[apf.Presentation ? "Presentation" : "AmlElement"](); + + +apf.config.$inheritProperties["model"] = 1; +apf.config.$inheritProperties["empty-message"] = 1; +apf.config.$inheritProperties["loading-message"] = 1; +apf.config.$inheritProperties["offline-message"] = 1; +apf.config.$inheritProperties["noloading"] = 1; + +apf.Init.run("databinding"); + + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/core/baseclasses/databinding/multiselect.js)SIZE(45794)TIME(Mon, 10 Jan 2011 10:53:11 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + +/** + * All elements inheriting from this {@link term.baseclass baseclass} can bind to data + * which contains multiple nodes. + * + * @allowchild item, choices + * @define choices Container for item nodes which receive presentation. + * This element is part of the XForms specification. It is not necesary for + * the Ajax.org Markup Language. + * Example: + * + * + * + * red + * blue + * green + * + * + * + * @allowchild item + * + * @constructor + * @baseclass + * @default_private + */ +apf.MultiselectBinding = function(){ + if (!this.setQueryValue) + this.implement(apf.DataBinding); + + this.$regbase = this.$regbase|apf.__MULTISELECT__; //We're pretending to have multiselect even though we might not. + + this.$init(function(){ + this.$selectTimer = {}; + }); +}; + +(function(){ + this.length = 0; + + //1 = force no bind rule, 2 = force bind rule + this.$attrExcludePropBind = apf.extend({ + caption : 2, + icon : 2, + eachvalue : 2, + select : 2, + css : 2, + sort : 2, + drag : 2, + drop : 2, + dragcopy : 2, + selected : 3, + //caret : 2, + each : 1, + "selection" : 3, //only databound when has an xpath + "selection-unique" : 3, //only databound when has an xpath + "selection-constructor" : 3 //only databound when has an xpath + }, this.$attrExcludePropBind); + + + /** + * Change the sorting order of this element + * + * @param {Object} options the new sort options. These are applied incrementally. + * Any property not set is maintained unless the clear + * parameter is set to true. + * Properties: + * {String} order see {@link baseclass.multiselectbinding.binding.each.attribute.order} + * {String} [xpath] see {@link baseclass.multiselectbinding.binding.each.attribute.sort} + * {String} [type] see {@link baseclass.multiselectbinding.binding.each.attribute.data-type} + * {String} [method] see {@link baseclass.multiselectbinding.binding.each.attribute.sort-method} + * {Function} [getNodes] Function that retrieves a list of nodes. + * {String} [dateFormat] see {@link baseclass.multiselectbinding.binding.each.attribute.date-format} + * {Function} [getValue] Function that determines the string content based + * on an xml node as it's first argument. + * @param {Boolean} clear removes the current sort options. + * @param {Boolean} noReload whether to reload the data of this component. + * @see baseclass.multiselectbinding.binding.each + */ + this.resort = function(options, clear, noReload){ + if (!this.$sort) + this.$sort = new apf.Sort(); + + this.$sort.set(options, clear); + + if (this.clearAllCache) + this.clearAllCache(); + + if (noReload) + return; + + + /*if(this.hasFeature(apf.__VIRTUALVIEWPORT__)){ + this.$clearVirtualDataset(this.xmlRoot); + this.reload(); + + return; + }*/ + + + var _self = this; + (function sortNodes(xmlNode, htmlParent) { + if(!xmlNode) + return; + var sNodes = _self.$sort.apply( + apf.getArrayFromNodelist(xmlNode.selectNodes(_self.each))); + + for (var i = 0; i < sNodes.length; i++) { + if (_self.$isTreeArch || _self.$withContainer){ + var htmlNode = apf.xmldb.findHtmlNode(sNodes[i], _self); + + + if (!_self.$findContainer){ + throw new Error(apf.formatErrorString(_self, + "Sorting Nodes", + "This component does not \ + implement _self.$findContainer")); + } + + + var container = _self.$findContainer(htmlNode); + + htmlParent.appendChild(htmlNode); + if (!apf.isChildOf(htmlNode, container, true)) + htmlParent.appendChild(container); + + sortNodes(sNodes[i], container); + } + else + htmlParent.appendChild(apf.xmldb.findHtmlNode(sNodes[i], _self)); + } + })(this.xmlRoot, this.$container); + + return options; + }; + + /** + * Change sorting from ascending to descending and vice versa. + */ + this.toggleSortOrder = function(){ + return this.resort({"ascending" : !this.$sort.get().ascending}).ascending; + }; + + /** + * Retrieves the current sort options + * + * @returns {Object} the current sort options. + * Properties: + * {String} order see {@link baseclass.multiselectbinding.binding.each.attribute.order} + * {String} xpath see {@link baseclass.multiselectbinding.binding.each.attribute.sort} + * {String} type see {@link baseclass.multiselectbinding.binding.each.attribute.data-type} + * {String} method see {@link baseclass.multiselectbinding.binding.each.attribute.sort-method} + * {Function} getNodes Function that retrieves a list of nodes. + * {String} dateFormat see {@link baseclass.multiselectbinding.binding.each.attribute.date-format} + * {Function} getValue Function that determines the string content based on + * an xml node as it's first argument. + * @see baseclass.multiselectbinding.binding.each + */ + this.getSortSettings = function(){ + return this.$sort.get(); + }; + + + /** + * Retrieves a nodelist containing the {@link term.datanode data nodes} which + * are rendered by this element (see each nodes, see + * {@link baseclass.multiselectbinding.binding.each}). + * + * @param {XMLElement} [xmlNode] the parent element on which the each query is applied. + */ + this.getTraverseNodes = function(xmlNode){ + + if (!this.each) { + throw new Error("Could not render bound data. Missing 'each' rule for " + + this.localName + (this.id && "[" + this.id + "]" || "")); //@todo apf3.0 make into proper apf error + } + + + + if (this.$sort) { + var nodes = apf.getArrayFromNodelist((xmlNode || this.xmlRoot).selectNodes(this.each)); + return this.$sort.apply(nodes); + } + + + return (xmlNode || this.xmlRoot).selectNodes(this.each); + }; + + /** + * Retrieves the first {@link term.datanode data node} which gets representation + * in this element + * (see each nodes, see {@link baseclass.multiselectbinding.binding.each}). + * + * @param {XMLElement} [xmlNode] the parent element on which the each query is executed. + * @return {XMLElement} + */ + this.getFirstTraverseNode = function(xmlNode){ + + if (this.$sort) { + var nodes = (xmlNode || this.xmlRoot).selectNodes(this.each); + return this.$sort.apply(nodes)[0]; + } + + + return (xmlNode || this.xmlRoot).selectSingleNode(this.each); + }; + + /** + * Retrieves the last {@link term.datanode data node} which gets representation + * in this element + * (see each nodes, see {@link baseclass.multiselectbinding.binding.each}). + * + * @param {XMLElement} [xmlNode] the parent element on which the each query is executed. + * @return {XMLElement} the last {@link term.datanode data node} + * @see baseclass.multiselectbinding.binding.each + */ + this.getLastTraverseNode = function(xmlNode){ + var nodes = this.getTraverseNodes(xmlNode || this.xmlRoot); + return nodes[nodes.length-1]; + }; + + /** + * Determines whether an {@link term.datanode data node} is a each node (see + * {@link baseclass.multiselectbinding.binding.each}) + * + * @param {XMLElement} [xmlNode] the parent element on which the each query is executed. + * @return {Boolean} whether the xml element is a each node. + * @see baseclass.multiselectbinding.binding.each + */ + this.isTraverseNode = function(xmlNode){ + /* + Added optimization, only when an object has a tree architecture is it + important to go up to the each parent of the xmlNode, else the node + should always be based on the xmlroot of this component + */ + //this.$isTreeArch + var nodes = this.getTraverseNodes( + this.getTraverseParent(xmlNode) || this.xmlRoot); + for (var i = 0; i < nodes.length; i++) + if (nodes[i] == xmlNode) + return true; + return false; + }; + + /** + * Retrieves the next each node (see {@link baseclass.multiselectbinding.binding.each}) + * to be selected + * from a given each node. The method can do this in either direction and + * also return the Nth node for this algorithm. + * + * @param {XMLElement} xmlNode the starting point for determining the next selection. + * @param {Boolean} [up] the direction of the selection. Default is false. + * @param {Integer} [count] the distance in number of nodes. Default is 1. + * @return {XMLElement} the {@link term.datanode data node} to be selected next. + * @see baseclass.multiselectbinding.binding.each + */ + this.getNextTraverseSelected = function(xmlNode, up, count){ + if (!xmlNode) + xmlNode = this.selected; + if (!count) + count = 1; + + var i = 0; + var nodes = this.getTraverseNodes(this.getTraverseParent(xmlNode) || this.xmlRoot); + while (nodes[i] && nodes[i] != xmlNode) + i++; + + var node = (up == null) + ? nodes[i + count] || nodes[i - count] + : (up ? nodes[i + count] : nodes[i - count]); + + //arguments[2] + return node || count && (i < count || (i + 1) > Math.floor(nodes.length / count) * count) + ? node + : (up ? nodes[nodes.length-1] : nodes[0]); + }; + + /** + * Retrieves the next each node (see {@link baseclass.multiselectbinding.binding.each}). + * The method can do this in either direction and also return the Nth next node. + * + * @param {XMLElement} xmlNode the starting point for determining the next node. + * @param {Boolean} [up] the direction. Default is false. + * @param {Integer} [count] the distance in number of nodes. Default is 1. + * @return {XMLElement} the next each node + * @see baseclass.multiselectbinding.binding.each + */ + this.getNextTraverse = function(xmlNode, up, count){ + if (!count) + count = 1; + if (!xmlNode) + xmlNode = this.selected; + + var i = 0; + var nodes = this.getTraverseNodes(this.getTraverseParent(xmlNode) || this.xmlRoot); + while (nodes[i] && nodes[i] != xmlNode) + i++; + + return nodes[i + (up ? -1 * count : count)]; + }; + + /** + * Retrieves the parent each node (see {@link baseclass.multiselectbinding.binding.each}). + * In some cases the each rules has a complex form like 'children/item'. In + * those cases the generated tree has a different structure from that of the xml + * data. For these situations the xmlNode.parentNode property won't return + * the each parent, this method will give you the right parent. + * + * @param {XMLElement} xmlNode the node for which the parent element will be determined. + * @return {XMLElement} the parent node or null if none was found. + * @see baseclass.multiselectbinding.binding.each + */ + this.getTraverseParent = function(xmlNode){ + if (!xmlNode.parentNode || xmlNode == this.xmlRoot) + return false; + + //@todo this can be removed when we have a new xpath implementation + if (xmlNode.$regbase) + return xmlNode.parentNode; + + var x, id = xmlNode.getAttribute(apf.xmldb.xmlIdTag); + if (!id) { + //return false; + xmlNode.setAttribute(apf.xmldb.xmlIdTag, "temp"); + id = "temp"; + } + + /* + do { + xmlNode = xmlNode.parentNode; + if (xmlNode == this.xmlRoot) + return false; + if (this.isTraverseNode(xmlNode)) + return xmlNode; + } while (xmlNode.parentNode); + */ + + //This is not 100% correct, but good enough for now + + x = xmlNode.selectSingleNode("ancestor::node()[((" + + this.each + ")/@" + apf.xmldb.xmlIdTag + ")='" + + id + "']"); + + if (id == "temp") + xmlNode.removeAttribute(apf.xmldb.xmlIdTag); + return x; + }; + + /** + * Finds HTML presentation node in cache by ID + * + * @param {String} id the id of the HTMLElement which is looked up. + * @return {HTMLElement} the HTMLElement found. When no element is found, null is returned. + */ + if (!this.$findHtmlNode) { //overwritten by apf.Cache + this.$findHtmlNode = function(id){ + return this.$pHtmlDoc.getElementById(id); + }; + } + + this.$setClearMessage = function(msg, className, lastHeight){ + if (this.more && this.$addMoreItem) this.$addMoreItem(); + if (!this.$empty) { + if (!this.$hasLayoutNode("empty")) + return; + + this.$getNewContext("empty"); + + var xmlEmpty = this.$getLayoutNode("empty"); + if (!xmlEmpty) return; + + this.$empty = apf.insertHtmlNode(xmlEmpty, this.$container); + } + else { + this.$container.appendChild(this.$empty); + } + + var empty = this.$getLayoutNode("empty", "caption", this.$empty); + + if (empty) + apf.setNodeValue(empty, msg || ""); + + this.$empty.setAttribute("id", "empty" + this.$uniqueId); + apf.setStyleClass(this.$empty, className, ["loading", "empty", "offline"]); + + //@todo apf3.0 cleanup? + var extH = apf.getStyle(this.$ext, "height"); + this.$empty.style.height = (lastHeight && (!extH || extH == "auto") && className != "empty") + ? (Math.max(10, (lastHeight + - apf.getHeightDiff(this.$empty) + - apf.getHeightDiff(this.$ext))) + "px") + : ""; + }; + + this.$updateClearMessage = function(msg, className) { + if (!this.$empty || this.$empty.parentNode != this.$container + || this.$empty.className.indexOf(className) == -1) + return; + + var empty = this.$getLayoutNode("empty", "caption", this.$empty); + if (empty) + apf.setNodeValue(empty, msg || ""); + } + + this.$removeClearMessage = function(){ + if (!this.$empty) + this.$empty = document.getElementById("empty" + this.$uniqueId); + if (this.$empty && this.$empty.parentNode) + this.$empty.parentNode.removeChild(this.$empty); + }; + + /** + * Set listeners, calls HTML creation methods and + * initializes select and focus states of object. + */ + this.$load = function(XMLRoot){ + //Add listener to XMLRoot Node + apf.xmldb.addNodeListener(XMLRoot, this); + + var length = this.getTraverseNodes(XMLRoot).length; + if (!this.renderRoot && !length) + return this.clear(null, null, true); //@todo apf3.0 this should clear and set a listener + + //Traverse through XMLTree + var nodes = this.$addNodes(XMLRoot, null, null, this.renderRoot); + + //Build HTML + this.$fill(nodes); + + //Select First Child + if (this.selectable) { + + //@todo move this to multiselect event handler inside multiselect.js + var sel, bHasOffline = (typeof apf.offline != "undefined"); + if (!this.firstLoad && bHasOffline && apf.offline.state.enabled + && apf.offline.state.realtime) { + sel = apf.offline.state.get(this, "selection"); + this.firstLoad = true; + } + + if (sel) { + var selstate = apf.offline.state.get(this, "selstate"); + + if (sel.length == 0) { + this.clearSelection(); + } + else { + for (var i = 0; i < sel.length; i++) { + sel[i] = apf.xpathToXml(sel[i], + this.xmlRoot); + } + + if (selstate[1]) { + var selected = apf.remote + .xpathToXml(selstate[1], this.xmlRoot); + } + + this.selectList(sel, null, selected); + } + + if (selstate[0]) { + this.setCaret(apf.remote + .xpathToXml(selstate[0], this.xmlRoot)); + } + } + else + + //@todo apf3.0 optimize to not set selection when .selection or .selected is set on initial load + if (this.autoselect) { + if (!this.selected) { + if (this.renderRoot) + this.select(XMLRoot, null, null, null, true); + else if (nodes.length) + this.$selectDefault(XMLRoot); + //else @todo apf3.0 this one doesnt seem needed + //this.clearSelection(); + } + } + else { + this.clearSelection(true); + var xmlNode = this.renderRoot + ? this.xmlRoot + : this.getFirstTraverseNode(); //should this be moved to the clearSelection function? + if (xmlNode) + this.setCaret(xmlNode); + + if (this.selected) + this.setProperty("selected", null); + if (this.choosen) + this.setProperty("choosen", null); + + } + } + + if (this.focussable) + apf.document.activeElement == this ? this.$focus() : this.$blur(); + + + if (length != this.length) + this.setProperty("length", length); + + }; + + var actionFeature = { + "insert" : 127,//11111110 + "replacenode" : 127,//11110110 + "attribute" : 255,//11110111 + "add" : 251,//11110111 + "remove" : 46, //01011100 + "redo-remove" : 79, //10011110 + "synchronize" : 127,//11111110 + "move-away" : 233,//11010011 + "move" : 77 //10011011 + }; + + /** + * Loops through parents of changed node to find the first + * connected node. Based on the action it will change, remove + * or update the representation of the data. + * + * @event xmlupdate Fires when xml of this element is updated. + * object: + * {String} action the action that was executed on the xml. + * Possible values: + * text a text node is set. + * attribute an attribute is set. + * update an xml node is updated. + * insert xml nodes are inserted. + * add an xml node is added. + * remove an xml node is removed (parent still set). + * redo-remove an xml node is removed (parent not set). + * synchronize unknown update. + * move-away an xml node is moved (parent not set). + * move an xml node is moved (parent still set). + * {XMLElement} xmlNode the node that is subject to the update. + * {Mixed} result the result. + * {UndoObj} UndoObj the undo information. + */ + this.$xmlUpdate = function(action, xmlNode, listenNode, UndoObj, lastParent){ + if (!this.xmlRoot) + return; //@todo think about purging cache when xmlroot is removed + + var result, length, pNode, htmlNode, + startNode = xmlNode; + if (!listenNode) + listenNode = this.xmlRoot; + + if (action == "redo-remove") { + lastParent.appendChild(xmlNode); //ahum, i'm not proud of this one + var eachNode = this.isTraverseNode(xmlNode); + lastParent.removeChild(xmlNode); + + if (!eachNode) + xmlNode = lastParent; + } + + //Get First ParentNode connected + do { + if (action == "add" && this.isTraverseNode(xmlNode) + && startNode == xmlNode) + break; //@todo Might want to comment this out for adding nodes under a eachd node + + if (xmlNode.getAttribute(apf.xmldb.xmlIdTag)) { + htmlNode = this.$findHtmlNode( + xmlNode.getAttribute(apf.xmldb.xmlIdTag) + + "|" + this.$uniqueId); + + if (xmlNode == listenNode && !this.renderRoot) { + if (xmlNode == this.xmlRoot && action != "insert" && action != "replacenode") { + //@todo apf3.0 - fix this for binding on properties + this.dispatchEvent("xmlupdate", { + action : action, + xmlNode: xmlNode, + UndoObj: UndoObj + }); + return; + } + break; + } + + if (htmlNode && actionFeature[action] & 2 + && !this.isTraverseNode(xmlNode)) + action = "remove"; //@todo why not break here? + + else if (!htmlNode && actionFeature[action] & 4 + && this.isTraverseNode(xmlNode)){ + action = "add"; + break; + } + + else if (htmlNode + && (startNode != xmlNode || xmlNode == this.xmlRoot)) { + if (actionFeature[action] & 1) + action = "update"; + else if (action == "remove") + return; + } + + if (htmlNode || action == "move") + break; + } + else if (actionFeature[action] & 8 && this.isTraverseNode(xmlNode)){ + action = "add"; + break; + } + + if (xmlNode == listenNode) { + if (actionFeature[action] & 128) //The change is not for us. + return; + + break; + } + xmlNode = xmlNode.parentNode; + } + while (xmlNode && xmlNode.nodeType != 9); + + + apf.$lm_has_lang = false; + + + + /** + * @todo Think about not having this code here + */ + if (this.hasFeature(apf.__VIRTUALVIEWPORT__)) { + if(!this.$isInViewport(xmlNode)) //xmlNode is a eachd node + return; + } + + + //if(xmlNode == listenNode && !action.match(/add|synchronize|insert/)) + // return; //deleting nodes in parentData of object + + var foundNode = xmlNode; + if (xmlNode && xmlNode.nodeType == 9) + xmlNode = startNode; + + if (action == "replacenode") { + //var tmpNode; + //Case for replacing the xmlroot or its direct parent + if (UndoObj ? UndoObj.args[1] == this.xmlRoot : !this.xmlRoot.parentNode) + return this.load(UndoObj ? UndoObj.xmlNode : listenNode, {force: true}); + + //Case for replacing a node between the xmlroot and the traverse nodes + var nodes = this.getTraverseNodes(); + for (var i = 0, l = nodes.length; i < l; i++) { + if (apf.isChildOf(startNode, nodes[i])) + return this.load(this.xmlRoot, {force: true}); //This can be more optimized by using addNodes + } + //if ((tmpNode = this.getFirstTraverseNode()) && apf.isChildOf(startNode, tmpNode)) + } + + //Action Tracker Support - && xmlNode correct here??? - UndoObj.xmlNode works but fishy.... + if (UndoObj && xmlNode && !UndoObj.xmlNode) + UndoObj.xmlNode = xmlNode; + + //Check Move -- if value node isn't the node that was moved then only perform a normal update + if (action == "move" && foundNode == startNode) { + //if(!htmlNode) alert(xmlNode.getAttribute("id")+"|"+this.$uniqueId); + var isInThis = apf.isChildOf(this.xmlRoot, xmlNode.parentNode, true); //@todo this.getTraverseParent(xmlNode) + var wasInThis = apf.isChildOf(this.xmlRoot, UndoObj.extra.parent, true); + + //Move if both previous and current position is within this object + if (isInThis && wasInThis) + this.$moveNode(xmlNode, htmlNode, UndoObj.extra.oldParent); + else if (isInThis) //Add if only current position is within this object + action = "add"; + else if (wasInThis) //Remove if only previous position is within this object + action = "remove"; + } + else if (action == "move-away") { + var goesToThis = apf.isChildOf(this.xmlRoot, UndoObj.extra.parent, true); + if (!goesToThis) + action = "remove"; + } + + //Remove loading message + if (this.$removeClearMessage && this.$setClearMessage) { + if (this.getFirstTraverseNode()) + this.$removeClearMessage(); + else + this.$setClearMessage(this["empty-message"], "empty") + } + + //Check Insert + if (action == "insert" && (this.$isTreeArch || xmlNode == this.xmlRoot)) { + if (!xmlNode) + return; + + if (this.$hasLoadStatus(xmlNode) && this.$removeLoading) + this.$removeLoading(htmlNode); + + if (this.$container.firstChild && !apf.xmldb.getNode(this.$container.firstChild)) { + //Appearantly the content was cleared + this.$container.innerHTML = ""; + + if (!this.renderRoot) { + length = this.getTraverseNodes().length; + if (!length) + this.clear(); + } + } + + result = this.$addNodes(xmlNode, null, true, false, null, null, "insert");//this.$isTreeArch?? + + this.$fillParentHtml = (this.$getParentNode + ? this.$getParentNode(htmlNode) + : htmlNode); + this.$fillParent = xmlNode; + this.$fill(result); + + + if (this.selectable && !this.xmlRoot.selectSingleNode(this.each)) + apf.console.warn("No traversable nodes were found for " + + this.name + " [" + this.localName + "]\n\ + Traverse Rule : " + this.$getBindRule("each")[4].getAttribute("match")); + + + if (this.selectable && (length === 0 || !this.xmlRoot.selectSingleNode(this.each))) + return; + } + else if (action == "add") {// || !htmlNode (Check Add) + var parentHTMLNode; + pNode = this.getTraverseParent(xmlNode); + + if (pNode == this.xmlRoot) + parentHTMLNode = this.$container; + + if (!parentHTMLNode && this.$isTreeArch) { + parentHTMLNode = this.$findHtmlNode( + pNode.getAttribute(apf.xmldb.xmlIdTag) + "|" + this.$uniqueId); + } + + //This should be moved into a function (used in setCache as well) + + if (!parentHTMLNode && this.getCacheItem) + parentHTMLNode = this.getCacheItem(pNode.getAttribute(apf.xmldb.xmlIdTag) + || (pNode.getAttribute(apf.xmldb.xmlDocTag) + ? "doc" + pNode.getAttribute(apf.xmldb.xmlDocTag) + : false)); + + + //Only update if node is in current representation or in cache + if (parentHTMLNode || this.$isTreeArch + && pNode == this.xmlRoot) { //apf.isChildOf(this.xmlRoot, xmlNode) + parentHTMLNode = (this.$findContainer && parentHTMLNode + ? this.$findContainer(parentHTMLNode) + : parentHTMLNode) || this.$container; + + result = this.$addNodes(xmlNode, parentHTMLNode, true, true, + apf.xmldb.getHtmlNode(this.getNextTraverse(xmlNode), this)); + + if (parentHTMLNode) + this.$fill(result); + } + } + else if (action == "remove") { //Check Remove + //&& (!xmlNode || foundNode == xmlNode && xmlNode.parentNode + //if (!xmlNode || startNode != xmlNode) //@todo unsure if I can remove above commented out statement + //return; + //I've commented above code out, because it disabled removing a + //subnode of a node that through an each rule makes the traverse + //node no longer a traverse node. + + //Remove HTML Node + if (htmlNode) + this.$deInitNode(xmlNode, htmlNode); + else if (startNode == this.xmlRoot) { + return this.load(null, { + noClearMsg: !this.dataParent || !this.dataParent.autoselect + }); + } + } + else if (htmlNode) { + + if (this.$sort) + this.$moveNode(xmlNode, htmlNode); + + + this.$updateNode(xmlNode, htmlNode); + + //Transaction 'niceties' + if (action == "replacenode" && this.hasFeature(apf.__MULTISELECT__) + && this.selected && xmlNode.getAttribute(apf.xmldb.xmlIdTag) + == this.selected.getAttribute(apf.xmldb.xmlIdTag)) { + this.selected = xmlNode; + } + + //if(action == "synchronize" && this.autoselect) this.reselect(); + } + else if (action == "redo-remove") { //Check Remove of the data (some ancestor) that this component is bound on + var testNode = this.xmlRoot; + while (testNode && testNode.nodeType != 9) + testNode = testNode.parentNode; + + if (!testNode) { + //Set Component in listening state until data becomes available again. + var model = this.getModel(true); + + + if (!model) + throw new Error(apf.formatErrorString(0, this, + "Setting change notifier on component", + "Component without a model is listening for changes", + this.$aml)); + + + return model.$waitForXml(this); + } + } + + + //@todo apf3.0 + if (apf.$lm_has_lang) + apf.language.addBinding(this); //@todo should auto remove + + + //For tree based nodes, update all the nodes up + pNode = xmlNode ? xmlNode.parentNode : lastParent; + if (this.$isTreeArch && !this.$preventRecursiveUpdate + && pNode && pNode.nodeType == 1) { + do { + htmlNode = this.$findHtmlNode(pNode.getAttribute( + apf.xmldb.xmlIdTag) + "|" + this.$uniqueId); + + if (htmlNode) + this.$updateNode(pNode, htmlNode); + } + while ((pNode = this.getTraverseParent(pNode)) && pNode.nodeType == 1); + } + + //Make sure the selection doesn't become corrupted + if (actionFeature[action] & 32 && this.selectable + && startNode == xmlNode + && (action != "insert" || xmlNode == this.xmlRoot)) { + + clearTimeout(this.$selectTimer.timer); + // Determine next selection + if (action == "remove" && apf.isChildOf(xmlNode, this.selected, true) + || xmlNode == this.$selectTimer.nextNode) + this.$selectTimer.nextNode = this.getDefaultNext(xmlNode, this.$isTreeArch); + + //@todo Fix this by putting it after xmlUpdate when its using a timer + var _self = this; + this.$selectTimer.timer = $setTimeout(function(){ + _self.$checkSelection(_self.$selectTimer.nextNode); + _self.$selectTimer.nextNode = null; + }); + } + + + //Set dynamic properties that relate to the changed content + if (actionFeature[action] & 64) { + if (!length) + length = this.xmlRoot.selectNodes(this.each).length; + if (length != this.length) + this.setProperty("length", length); + } + + + //Let's signal components that are waiting for xml to appear (@todo what about clearing the signalXmlUpdate) + if (this.signalXmlUpdate && actionFeature[action] & 16) { + var uniqueId; + for (uniqueId in this.signalXmlUpdate) { + if (parseInt(uniqueId) != uniqueId) continue; //safari_old stuff + + var o = apf.lookup(uniqueId); + if (!this.selected) continue; + + xmlNode = this.selected.selectSingleNode(o.dataParent.xpath); + if (!xmlNode) continue; + + o.load(xmlNode); + } + } + + this.dispatchEvent("xmlupdate", { + action : action, + xmlNode: startNode, + traverseNode : xmlNode, + result : result, + UndoObj: UndoObj + }); + }; + + /** + * Loop through NodeList of selected Traverse Nodes + * and check if it has representation. If it doesn't + * representation is created via $add(). + */ + this.$addNodes = function(xmlNode, parent, checkChildren, isChild, insertBefore, depth, action){ + + if (!this.each) { + throw new Error(apf.formatErrorString(1060, this, + "adding Nodes for load", + "No each SmartBinding rule was specified. This rule is \ + required for a " + this.localName + " component.", this.$aml)); + } + + + var htmlNode, lastNode; + isChild = (isChild && (this.renderRoot && xmlNode == this.xmlRoot + || this.isTraverseNode(xmlNode))); + var nodes = isChild ? [xmlNode] : this.getTraverseNodes(xmlNode); + /*var loadChildren = nodes.length && this.$bindings["insert"] + ? this.$applyBindRule("insert", xmlNode) + : false; << UNUSED */ + + + var cId, cItem; + if (this.$isTreeArch && this.caching + && (cItem = this.cache[(cId = xmlNode.getAttribute(apf.xmldb.xmlIdTag))])) { + if (this.$subTreeCacheContext || this.$needsDepth) { + //@todo + //We destroy the current items, because currently we + //don't support multiple treecachecontexts + //and because datagrid needs to redraw depth + this.clearCacheItem(cId); + } + else { + this.$subTreeCacheContext = { + oHtml : cItem, + container : parent, + parentNode : null, + beforeNode : null + }; + + var htmlNode; + while (cItem.childNodes.length) + (parent || this.$container).appendChild(htmlNode = cItem.childNodes[0]); + + return nodes; + } + } + + + if (this.$isTreeArch && depth === null && action == "insert") { + depth = 0, loopNode = xmlNode; + while(loopNode && loopNode != this.xmlRoot) { + depth++; + loopNode = this.getTraverseParent(loopNode); + } + } + + for (var i = 0; i < nodes.length; i++) { + if (nodes[i].nodeType != 1) { + + if (this.$addNonElement) + this.$addNonElement(nodes[i], parent || this.$container, checkChildren, insertBefore, depth); + + continue; + } + + if (checkChildren) { + htmlNode = this.$findHtmlNode(nodes[i] + .getAttribute(apf.xmldb.xmlIdTag) + "|" + this.$uniqueId); + } + + if (!htmlNode) { + //Retrieve DataBind ID + var Lid = apf.xmldb.nodeConnect(this.documentId, nodes[i], null, this); + + //Add Children + var beforeNode = isChild + ? insertBefore + : (lastNode ? lastNode.nextSibling : null),//(parent || this.$container).firstChild); + parentNode = this.$add(nodes[i], Lid, isChild ? xmlNode.parentNode : xmlNode, + beforeNode ? parent || this.$container : parent, beforeNode, + (!beforeNode && i == nodes.length - 1), depth, nodes[i + 1], action);//Should use getTraverParent + + //Exit if component tells us its done with rendering + if (parentNode === false) { + //Tag all needed xmlNodes for future reference + // @todo apf3.0 code below looks harmful... hence commented out (Mike) + /*for (var j = i; j < nodes.length; j++) + apf.xmldb.nodeConnect(this.documentId, nodes[j], + null, this);*/ + break; + } + + //Parse Children Recursively -> optimize: don't check children that can't exist + //if(this.$isTreeArch) this.$addNodes(nodes[i], parentNode, checkChildren); + } + + if (checkChildren) + lastNode = htmlNode;// ? htmlNode.parentNode.parentNode : null; + } + + return nodes; + }; + + this.$handleBindingRule = function(value, prop){ + if (!value) + this[prop] = null; + + //@todo apf3.0 fix parsing + if (prop == "each") { + value = value.charAt(0) == "[" && value.charAt(value.length - 1) == "]" + ? value.replace(/^\[|\]$/g, "") + : value; + + if (value.indexOf("::") > -1) { + var model = value.split("::"); //@todo could be optimized + if (!apf.xPathAxis[model[0]]) { + this.setProperty("model", model[0]); + this.each = model[1]; + } + else + this.each = value; + } + else + this.each = value; + + if (this.each == this.$lastEach) + return; + + this.$lastEach = value; + + if (!this.$model && !this.$initingModel) { + this.$initingModel = true; + this.$setInheritedAttribute("model"); + + return; //@experimental + } + + if (this.$checkLoadQueue() !== false) //@experimental + return; + } + + //@todo apf3.0 find a better heuristic (portal demo) + if (this.xmlRoot && !this.$bindRuleTimer && this.$amlLoaded) { + var _self = this; + apf.queue.add("reload" + this.$uniqueId, function(){ + + apf.console.log("Reloading multiselect based on attribute '" + + prop + "' bind change to value '" + + value + "'\n\n" + _self.serialize(true)); + + _self.reload(); + }); + } + }; + + this.$select = function(o){ + + if (this.renaming) + this.stopRename(null, true); + + + if (!o || !o.style) + return; + return this.$setStyleClass(o, "selected"); + }; + + this.$deselect = function(o){ + + if (this.renaming) { + this.stopRename(null, true); + + if (this.ctrlselect) + return false; + } + + + if (!o) + return; + return this.$setStyleClass(o, "", ["selected", "indicate"]); + }; + + this.$indicate = function(o){ + + if (this.renaming) + this.stopRename(null, true); + + + if (!o) + return; + return this.$setStyleClass(o, "indicate"); + }; + + this.$deindicate = function(o){ + + if (this.renaming) + this.stopRename(null, true); + + + if (!o) + return; + return this.$setStyleClass(o, "", ["indicate"]); + }; + + + /** + * @attribute {String} each the xpath statement that determines which + * {@link term.datanode data nodes} are rendered by this element (also known + * as {@link term.eachnode each nodes}. See + * {@link baseclass.multiselectbinding.binding.each} for more information. + * Example: + * + * Country + * + * + * + * + * + * USA + * Great Brittain + * The Netherlands + * ... + * + * + * + * @see baseclass.multiselectbinding.binding.each + */ + this.$propHandlers["each"] = + + /** + * @attribute {String} caption the xpath statement that determines from + * which xml node the caption is retrieved. + * Example: + * + * + * + */ + this.$propHandlers["caption"] = + + /** + * @attribute {String} valuerule the xpath statement that determines from + * which xml node the value is retrieved. + * Example: + * + * + * + * @see baseclass.multiselect.binding.value + */ + this.$propHandlers["eachvalue"] = + + /** + * @attribute {String} icon the xpath statement that determines from + * which xml node the icon url is retrieved. + * Example: + * + * + * + */ + this.$propHandlers["icon"] = + + /** + * @attribute {String} tooltip the xpath statement that determines from + * which xml node the tooltip text is retrieved. + * Example: + * + * + * + */ + this.$propHandlers["tooltip"] = this.$handleBindingRule; + + + /** + * @attribute {String} sort the xpath statement that selects the sortable value. + * Example: + * + * + * + * @see element.each.attribute.sort + */ + this.$propHandlers["sort"] = function(value){ + if (value) { + this.$sort = new apf.Sort() + this.$sort.set({ + getValue : apf.lm.compile(value) + }); + } + else { + this.$sort = null; + } + } + + + /** + * @attribute {String} select the xpath statement that determines whether + * this node is selectable. + * Example: + * + * + * + * @see baseclass.multiselect.binding.select + */ + //this.$propHandlers["select"] = + +}).call(apf.MultiselectBinding.prototype = new apf.DataBinding()); + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/core/baseclasses/databinding/standard.js)SIZE(6499)TIME(Mon, 10 Jan 2011 10:53:11 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + + +/** + * @constructor + * @private + * @baseclass + */ +apf.StandardBinding = function(){ + this.$init(true); + + + if (apf.Validation) + this.implement(apf.Validation); + + + if (!this.setQueryValue) + this.implement(apf.DataBinding); + + if (!this.defaultValue) //@todo please use this in a sentence + this.defaultValue = ""; + + /** + * Load XML into this element + * @private + */ + this.$load = function(xmlNode){ + //Add listener to XMLRoot Node + apf.xmldb.addNodeListener(xmlNode, this); + //Set Properties + + + var b, lrule, rule, bRules, bRule, value; + if (b = this.$bindings) { + for (rule in b) { + lrule = rule.toLowerCase(); + if (this.$supportedProperties.indexOf(lrule) > -1) { + bRule = (bRules = b[lrule]).length == 1 + ? bRules[0] + : this.$getBindRule(lrule, xmlNode); + + value = bRule.value || bRule.match; + + + //Remove any bounds if relevant + this.$clearDynamicProperty(lrule); + + if (value.indexOf("{") > -1 || value.indexOf("[") > -1) + this.$setDynamicProperty(lrule, value); + else + + if (this.setProperty) + this.setProperty(lrule, value, true); + } + } + } + + + //Think should be set in the event by the Validation Class + if (this.errBox && this.isValid && this.isValid()) + this.clearError(); + }; + + /** + * Set xml based properties of this element + * @private + */ + this.$xmlUpdate = function(action, xmlNode, listenNode, UndoObj){ + //Clear this component if some ancestor has been detached + if (action == "redo-remove") { + var retreatToListenMode = false, model = this.getModel(true); + if (model) { + var xpath = model.getXpathByAmlNode(this); + if (xpath) { + xmlNode = model.data.selectSingleNode(xpath); + if (xmlNode != this.xmlRoot) + retreatToListenMode = true; + } + } + + if (retreatToListenMode || this.xmlRoot == xmlNode) { + + //RLD: Disabled because sometimes indeed components do not + //have a model when their xmlRoot is removed. + if (!model) { + throw new Error(apf.formatErrorString(0, this, + "Setting change notifier on component", + "Component without a model is listening for changes", + this.$aml)); + } + + + //Set Component in listening state untill data becomes available again. + return model.$waitForXml(this); + } + } + + //Action Tracker Support + if (UndoObj && !UndoObj.xmlNode) + UndoObj.xmlNode = this.xmlRoot; + + //Set Properties + + + var b, lrule, rule, bRules, bRule, value; + if (b = this.$bindings) { + for (rule in b) { + lrule = rule.toLowerCase(); + if (this.$supportedProperties.indexOf(lrule) > -1) { + bRule = (bRules = b[lrule]).length == 1 + ? bRules[0] + : this.$getBindRule(lrule, xmlNode); + + value = bRule.value || bRule.match; + + + //Remove any bounds if relevant + this.$clearDynamicProperty(lrule); + + if (value.indexOf("{") > -1 || value.indexOf("[") > -1) + this.$setDynamicProperty(lrule, value); + else + + if (this.setProperty) + this.setProperty(lrule, value); + } + } + } + + + //@todo Think should be set in the event by the Validation Class + if (this.errBox && this.isValid && this.isValid()) + this.clearError(); + + this.dispatchEvent("xmlupdate", { + action : action, + xmlNode: xmlNode, + UndoObj: UndoObj + }); + }; + + /** + * Clears the data loaded into this element resetting it's value. + */ + //@todo apf3.0 this is wrong + this.addEventListener("$clear", function(nomsg, do_event){ + if (this.$propHandlers && this.$propHandlers["value"]) { + this.value = -99999; //force resetting + this.$propHandlers["value"].call(this, ""); + } + }); +}; +apf.StandardBinding.prototype = new apf.DataBinding(); + +apf.Init.run("standardbinding"); + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/core/baseclasses/multiselect.js)SIZE(73638)TIME(Mon, 10 Jan 2011 10:53:11 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + +apf.__MULTISELECT__ = 1 << 8; + + + +/** + * @term eachnode A each node is a {@link term.datanode data node} that is in the set selected by the + * {@link baseclass.multiselectbinding.binding.each each binding rule}. + * These {@link term.datanode data nodes} get representation within the visual element. For instance + * each item in a list is connected to such a each node. A each node + * can be selected, removed, added, dragged, dropped and so on. + * Example: + * In this example the person nodes that have the show attribute set to 1 are the + * each nodes of the list. This list will display three items. + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * Remarks: + * A somewhat advanced topic is understanding how an element can use the + * each {@link term.binding binding rule}. For the tree this binding rules + * can be used to create a virtual tree mapping of the xml. + */ + +/** + * @term caret When selecting nodes in a list using the keyboard, the caret is + * the indication of the position within that list. The item that the caret is + * on might or might not be selected. This feature is especially useful when + * holding the control key or using the shift key to multi select items. + */ + +/** + * All elements inheriting from this {@link term.baseclass baseclass} have selection features. This includes handling + * for multiselect and several keyboard based selection interaction. It also + * takes care of {@link term.caret caret} handling when multiselect is enabled. Furthermore features + * for dealing with multinode component are included like adding and removing + * {@link term.datanode data nodes}. + * + * @constructor + * @baseclass + * @author Ruben Daniels (ruben AT ajax DOT org) + * @version %I%, %G% + * @since 0.5 + * + * @inherits apf.MultiselectBinding + * + * @binding select Determines whether the {@link term.eachnode each node} can be selected. + * Example: + * In this example the tree contains nodes that have a disabled flag set. + * These nodes cannot be selected + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * @binding value Determines the way the value for the element is retrieved + * from the selected node. The value property contains this value. + * Example: + * + * + * + * + * + * + * + * + * + * red + * green + * blue + * + * + * + * + */ +apf.MultiSelect = function(){ + this.$init(function(){ + this.$valueList = []; + this.$selectedList = []; + }); +}; + +//@todo investigate if selectedList can be deprecated +(function() { + this.$regbase = this.$regbase|apf.__MULTISELECT__; + + /**** Properties ****/ + + /** + * the last selected item of this element. + * @type {XMLElement} + */ + this.sellength = 0; + this.selected = null; + this.$selected = null; + + /** + * the xml element that has the {@link term.caret caret}. + * @type {XMLElement} + */ + this.caret = null; + this.$caret = null; + + /** + * whether to use a {@link term.caret caret} in the interaction of this element. + * @type {Boolean} + */ + this.useindicator = true; + + + + /** + * Removes an {@link term.datanode data node} from the data of this element. + * Example: + * A simple list showing products. This list is used in all following examples. + * + * + * + * + * + * [@type].png + * + * + * + * + * + * + * + * + * + * + * + * + * + * Example: + * This example selects a product by it's value and then removes the + * selection. + * + * + * + * Example: + * This example gets a product by it's value and then removes it. + * + * + * var xmlNode = myList.findXmlNodeByValue("product20"); + * myList.remove(xmlNode); + * + * + * Example: + * This example retrieves all nodes from a list. All items with a length + * greater than 10 are singled out and removed. + * + * 10) + * removeList.push(list[i]); + * } + * myList.remove(removeList); + * } + * ]]> + * + * Remarks: + * Another way to trigger this method is by using the action attribute on a + * button. + * + * Remove item + * + * Using the action methodology you can let the original data source + * (usually the server) know that the user removed an item. + * + * + * + * + * + * + * For undo this action should be extended and the server should maintain a + * copy of the deleted item. + * + * + * + * + * + * Remove item + * + * + * @action + * @param {mixed} [nodeList] the {@link term.datanode data node}(s) to be removed. If none are specified, the current selection is removed. + * Possible values: + * {NodeList} the {@link term.datanode data nodes} to be removed. + * {XMLElement} the {@link term.datanode data node} to be removed. + * @return {Boolean} specifies if the removal succeeded + */ + this.remove = function(nodeList){ + //Use the current selection if no xmlNode is defined + if (!nodeList) + nodeList = this.$valueList; + + //If we're an xml node let's convert + if (nodeList.nodeType) + nodeList = [nodeList]; + + //If there is no selection we'll exit, nothing to do + if (!nodeList || !nodeList.length) + return; + + + //We're not removing the XMLRoot, that would be suicide ;) + if (nodeList.contains(this.xmlRoot)) { + throw new Error(apf.formatErrorString(0, + "Removing nodes", + "You are trying to delete the xml root of this \ + element. This is not allowed.")); + } + + + var changes = []; + for (var i = 0; i < nodeList.length; i++) { + changes.push({ + action : "removeNode", + args : [nodeList[i]] + }); + } + + if (this.$actions["removegroup"]) + return this.$executeAction("multicall", changes, "removegroup", nodeList[0]); + else { + return this.$executeAction("multicall", changes, "remove", + nodeList[0], null, null, nodeList.length > 1 ? nodeList : null); + } + }; + + /** + * Adds an {@link term.datanode data node} to the data of this element. + * Example: + * A simple list showing products. This list is used in all following examples. + * + * + * + * + * + * [@type].png + * + * + * + * + * + * + * + * + * + * + * + * + * + * Example: + * This example adds a product to this element. + * selection. + * + * '); + * } + * ]]> + * + * Example: + * This example copy's the selected product, changes it's name and then + * adds it. After selecting the new node the user is offered a rename input + * box. + * + * + * + * Remarks: + * Another way to trigger this method is by using the action attribute on a + * button. + * + * + * + * + * + * + * + * + * + * + * Add new product + * + * Using the action methodology you can let the original data source + * (usually the server) know that the user added an item. + * + * + * + * For undo this action should be extended as follows. + * + * + * + * + * + * + * + * + * + * + * Add new product + * + * + * In some cases the server needs to create the new product before it's + * added. This is done as follows. + * + * + * + * Alternatively the template for the addition can be provided as a child of + * the action rule. + * + * + * + * + * + * @action + * @param {XMLElement} [xmlNode] the {@link term.datanode data node} which is added. If none is specified the action will use the action rule to try to retrieve a new node to add. + * @param {XMLElement} [pNode] the parent node of the added {@link term.datanode data node}. + * @param {XMLElement} [beforeNode] the position where the xml element should be inserted. + * @return {XMLElement} the added {@link term.datanode data node} or false on failure. + */ + this.add = function(xmlNode, pNode, beforeNode, userCallback){ + var rule; + + if (this.$actions) { + if (xmlNode && xmlNode.nodeType) + rule = this.$actions.getRule("add", xmlNode); + else if (typeof xmlNode == "string") { + if (xmlNode.trim().charAt(0) == "<") { + xmlNode = apf.getXml(xmlNode); + rule = this.$actions.getRule("add", xmlNode); + } + else { + var rules = this.$actions["add"]; + for (var i = 0, l = rules.length; i < l; i++) { + if (rules[i].getAttribute("type") == xmlNode) { + xmlNode = null; + rule = rules[i]; + break; + } + } + } + } + + if (!rule) + rule = (this.$actions["add"] || {})[0]; + } + else + rule = null; + + + var bHasOffline = (typeof apf.offline != "undefined"); + if (bHasOffline && !apf.offline.canTransact()) + return false; + + if (bHasOffline && !apf.offline.onLine && (!xmlNode || !rule.get)) + return false; + + + var refNode = this.$isTreeArch ? this.selected || this.xmlRoot : this.xmlRoot, + amlNode = this, + callback = function(addXmlNode, state, extra){ + if (state != apf.SUCCESS) { + var oError; + + oError = new Error(apf.formatErrorString(1032, amlNode, + "Loading xml data", + "Could not add data for control " + amlNode.name + + "[" + amlNode.tagName + "] \nUrl: " + extra.url + + "\nInfo: " + extra.message + "\n\n" + xmlNode)); + + if (extra.tpModule.retryTimeout(extra, state, amlNode, oError) === true) + return true; + + throw oError; + } + + /*if (apf.supportNamespaces && node.namespaceURI == apf.ns.xhtml) { + node = apf.getXml(node.xml.replace(/xmlns\=\"[^"]*\"/g, "")); + //@todo import here for webkit? + }*/ + + if (typeof addXmlNode != "object") + addXmlNode = apf.getXmlDom(addXmlNode).documentElement; + if (addXmlNode.getAttribute(apf.xmldb.xmlIdTag)) + addXmlNode.setAttribute(apf.xmldb.xmlIdTag, ""); + + var actionNode = amlNode.$actions && + amlNode.$actions.getRule("add", amlNode.$isTreeArch + ? amlNode.selected + : amlNode.xmlRoot); + if (!pNode) { + if (actionNode && actionNode.parent) { + pNode = (actionNode.cparent + || actionNode.compile("parent", { + xpathmode : 2, + injectself : true + }))(amlNode.$isTreeArch + ? amlNode.selected || amlNode.xmlRoot + : amlNode.xmlRoot); + } + else { + pNode = amlNode.$isTreeArch + ? amlNode.selected || amlNode.xmlRoot + : amlNode.xmlRoot + } + } + + if (!pNode) + pNode = amlNode.xmlRoot; + + //Safari issue not auto importing nodes: + if (apf.isWebkit && pNode.ownerDocument != addXmlNode.ownerDocument) + addXmlNode = pNode.ownerDocument.importNode(addXmlNode, true); + + + if (!pNode) { + throw new Error(apf.formatErrorString(0, amlNode, + "Executing add action", + "Missing parent node. You can only add nodes to a list if it\ + has data loaded. Unable to perform action.")); + } + + + if (amlNode.$executeAction("appendChild", + [pNode, addXmlNode, beforeNode], "add", addXmlNode) !== false + && amlNode.autoselect) + amlNode.select(addXmlNode); + + if (userCallback) + userCallback.call(amlNode, addXmlNode); + + return addXmlNode; + }; + + if (xmlNode) + return callback(xmlNode, apf.SUCCESS); + else { + if (rule.get) + return apf.getData(rule.get, {xmlNode: refNode, callback: callback}) + else { + + throw new Error(apf.formatErrorString(0, this, + "Executing add action", + "Missing add action defined in action rules. Unable to \ + perform action.")); + + } + } + + return addXmlNode; + }; + + if (!this.setValue) { + /** + * Sets the value of this element.The value + * corresponds to an item in the list of loaded {@link term.datanode data nodes}. This + * element will receive the selection. If no {@link term.datanode data node} is found, the + * selection is cleared. + * + * @param {String} value the new value for this element. + * @param {Boolean} disable_event + * @see baseclass.multiselect.method.getValue + */ + this.setValue = function(value, disable_event){ + // @todo apf3.0 what does noEvent do? in this scope it's useless and + // doesn't improve codeflow with a global lookup and assignment + noEvent = disable_event; + this.setProperty("value", value, false, true); + noEvent = false; + }; + } + + /** + * Retrieves an {@link term.datanode data node} that has a value that corresponds to the + * string that is searched on. + * @param {String} value the value to match. + */ + this.findXmlNodeByValue = function(value){ + var nodes = this.getTraverseNodes(), + bindSet = this.$attrBindings["eachvalue"] + && "eachvalue" || this.$bindings["value"] + && "value" || this.$hasBindRule("caption") && "caption"; + + if (!bindSet) + return false; + + for (var i = 0; i < nodes.length; i++) { + if (this.$applyBindRule(bindSet, nodes[i]) == value) + return nodes[i]; + } + }; + + if (!this.getValue) { + /** + * Retrieves the value of this element. This is the value of the + * first selected {@link term.datanode data node}. + * @see #setValue + */ + this.getValue = function(xmlNode, noError){ + return this.value; + /* + if (!this.bindingRules && !this.caption) + return false; + + + if (!this.caption && !this.bindingRules[this.$mainBind] && !this.bindingRules["caption"]) { + if (noError) + return false; + + throw new Error(apf.formatErrorString(1074, this, + "Retrieving the value of this component.", + "No value rule has been defined. There is no way \ + to determine the value of the selected item.")); + } + + + return this.$applyBindRule(this.$mainBind, xmlNode || this.selected, null, true) + || this.$applyBindRule("caption", xmlNode || this.selected, null, true); + */ + }; + } + + /** + * Select the current selection again. + * + * @todo Add support for multiselect + */ + this.reselect = function(){ + if (this.selected) this.select(this.selected, null, this.ctrlselect, + null, true);//no support for multiselect currently. + }; + + /** + * Selects a single, or set of {@link term.eachnode each nodes}. + * The selection can be visually represented in this element. + * + * @param {mixed} xmlNode the identifier to determine the selection. + * Possible values: + * {XMLElement} the {@link term.datanode data node} to be used in the selection as a start/end point or to toggle the selection on the node. + * {HTMLElement} the html element node used as visual representation of {@link term.datanode data node}. Used to determine the {@link term.datanode data node} for selection. + * {String} the value of the {@link term.datanode data node} to be select. + * @param {Boolean} [ctrlKey] whether the Ctrl key was pressed + * @param {Boolean} [shiftKey] whether the Shift key was pressed + * @param {Boolean} [fakeselect] whether only visually a selection is made + * @param {Boolean} [force] whether reselect is forced. + * @param {Boolean} [noEvent] whether to not call any events + * @return {Boolean} whether the selection could be made + * + * @event beforeselect Fires before a {@link baseclass.multiselect.method.select selection} is made + * object: + * {XMLElement} selected the {@link term.datanode data node} that will be selected. + * {Array} selection an array of {@link term.datanode data node} that will be selected. + * {HTMLElement} htmlNode the html element that visually represents the {@link term.datanode data node}. + * @event afterselect Fires after a {@link baseclass.multiselect.method.select selection} is made + * object: + * {XMLElement} selected the {@link term.datanode data node} that was selected. + * {Array} selection an array of {@link term.datanode data node} that are selected. + * {HTMLElement} htmlNode the html element that visually represents the {@link term.datanode data node}. + */ + this.select = function(xmlNode, ctrlKey, shiftKey, fakeselect, force, noEvent, userAction){ + if (!this.selectable || userAction && this.disabled) + return; + + if (parseInt(fakeselect) == fakeselect) { + //Don't select on context menu + if (fakeselect == 2) { + fakeselect = true; + userAction = true; + } + else { + fakeselect = false; + userAction = true; + } + } + + if (this.$skipSelect) { + this.$skipSelect = false; + return; + } + + if (this.ctrlselect && !shiftKey) + ctrlKey = true; + + if (!this.multiselect) + ctrlKey = shiftKey = false; + + // Selection buffering (for async compatibility) + if (!this.xmlRoot) { + if (!this.$buffered) { + var f; + this.addEventListener("afterload", f = function(){ + this.select.apply(this, this.$buffered); + this.removeEventListener("afterload", f); + delete this.$buffered; + }); + } + + this.$buffered = Array.prototype.slice.call(arguments); + return; + } + + var htmlNode; + + /**** Type Detection ****/ + if (!xmlNode) { + + throw new Error(apf.formatErrorString(1075, this, + "Making a selection", + "No selection was specified")) + + + return false; + } + + if (typeof xmlNode != "object") { + var str = xmlNode; xmlNode = null; + if (typeof xmlNode == "string") + xmlNode = apf.xmldb.getNodeById(str); + + //Select based on the value of the xml node + if (!xmlNode) { + xmlNode = this.findXmlNodeByValue(str); + if (!xmlNode) { + this.clearSelection(noEvent); + return; + } + } + } + + if (!(typeof (xmlNode.style || "") == "object")) { + htmlNode = this.$findHtmlNode(xmlNode.getAttribute( + apf.xmldb.xmlIdTag) + "|" + this.$uniqueId); + } + else { + var id = (htmlNode = xmlNode).getAttribute(apf.xmldb.htmlIdTag); + while (!id && htmlNode.parentNode) + id = (htmlNode = htmlNode.parentNode).getAttribute( + apf.xmldb.htmlIdTag); + + xmlNode = apf.xmldb.getNodeById(id);//, this.xmlRoot); + } + + if (!shiftKey && !ctrlKey && !force && !this.reselectable + && this.$valueList.length <= 1 && this.$valueList.indexOf(xmlNode) > -1) + return; + + if (this.dispatchEvent('beforeselect', { + selected : xmlNode, + htmlNode : htmlNode, + ctrlKey : ctrlKey, + shiftKey : shiftKey, + force : force, + captureOnly : noEvent + }) === false) + return false; + + /**** Selection ****/ + + var lastIndicator = this.caret; + this.caret = xmlNode; + + //Multiselect with SHIFT Key. + if (shiftKey) { + var range = this.$calcSelectRange( + this.$valueList[0] || lastIndicator, xmlNode); + + if (this.$caret) + this.$deindicate(this.$caret); + + this.selectList(range); + + this.$selected = + this.$caret = this.$indicate(htmlNode); + } + else if (ctrlKey) { //Multiselect with CTRL Key. + //Node will be unselected + if (this.$valueList.contains(xmlNode)) { + if (this.selected == xmlNode) { + this.$deselect(this.$findHtmlNode(this.selected.getAttribute( + apf.xmldb.xmlIdTag) + "|" + this.$uniqueId)); + + this.$deindicate(this.$caret); + + if (this.$valueList.length && !fakeselect) { + //this.$selected = this.$selectedList[0]; + this.selected = this.$valueList[0]; + } + } + else + this.$deselect(htmlNode, xmlNode); + + if (!fakeselect) { + this.$selectedList.remove(htmlNode); + this.$valueList.remove(xmlNode); + } + + if (htmlNode != this.$caret) + this.$deindicate(this.$caret); + + this.$selected = + this.$caret = this.$indicate(htmlNode); + } + // Node will be selected + else { + if (this.$caret) + this.$deindicate(this.$caret); + this.$caret = this.$indicate(htmlNode, xmlNode); + + this.$selected = this.$select(htmlNode, xmlNode); + this.selected = xmlNode; + + if (!fakeselect) { + this.$selectedList.push(htmlNode); + this.$valueList.push(xmlNode); + } + } + } + else if (fakeselect && htmlNode && this.$selectedList.contains(htmlNode)) {//Return if selected Node is htmlNode during a fake select + return; + } + else { //Normal Selection + //htmlNode && this.$selected == htmlNode && this.$valueList.length <= 1 && this.$selectedList.contains(htmlNode) + if (this.$selected) + this.$deselect(this.$selected); + if (this.$caret) + this.$deindicate(this.$caret); + if (this.selected) + this.clearSelection(true); + + this.$caret = this.$indicate(htmlNode, xmlNode); + this.$selected = this.$select(htmlNode, xmlNode); + this.selected = xmlNode; + + this.$selectedList.push(htmlNode); + this.$valueList.push(xmlNode); + } + + if (this.delayedselect && (typeof ctrlKey == "boolean")){ + var _self = this; + $setTimeout(function(){ + if (_self.selected == xmlNode) + _self.dispatchEvent("afterselect", { + selection : _self.$valueList, + selected : xmlNode, + caret : _self.caret, + captureOnly : noEvent + }); + }, 10); + } + else { + this.dispatchEvent("afterselect", { + selection : this.$valueList, + selected : xmlNode, + caret : this.caret, + captureOnly : noEvent + }); + } + + return true; + }; + + /** + * Choose a selected item. This is done by double clicking on the item or + * pressing the Enter key. + * + * @param {mixed} xmlNode the identifier to determine the selection. + * Possible values: + * {XMLElement} the {@link term.datanode data node} to be choosen. + * {HTMLElement} the html element node used as visual representation of {@link term.datanode data node}. Used to determine the {@link term.datanode data node}. + * {String} the value of the {@link term.datanode data node} to be choosen. + * @event beforechoose Fires before a choice is made. + * object: + * {XMLElement} xmlNode the {@link term.datanode data node} that was choosen. + * @event afterchoose Fires after a choice is made. + * object: + * {XMLElement} xmlNode the {@link term.datanode data node} that was choosen. + */ + this.choose = function(xmlNode, userAction){ + if (!this.selectable || userAction && this.disabled) return; + + if (this.dispatchEvent("beforechoose", {xmlNode : xmlNode}) === false) + return false; + + if (xmlNode && !(typeof (xmlNode.style || "") == "object")) + this.select(xmlNode); + + + if (this.hasFeature(apf.__DATABINDING__) + && this.dispatchEvent("afterchoose", {xmlNode : this.selected}) !== false) + this.setProperty("chosen", this.selected); + + }; + + /** + * Removes the selection of one or more selected nodes. + * + * @param {Boolean} [singleNode] whether to only deselect the indicated node + * @param {Boolean} [noEvent] whether to not call any events + */ + this.clearSelection = function(noEvent, userAction){ + if (!this.selectable || userAction && this.disabled || !this.$valueList.length) + return; + + if (!noEvent) { + if (this.dispatchEvent("beforeselect", { + selection : [], + selected : null, + caret : this.caret + }) === false) + return false; + } + + //Deselect html nodes + var htmlNode; + for (var i = this.$valueList.length - 1; i >= 0; i--) { + htmlNode = this.$findHtmlNode(this.$valueList[i].getAttribute( + apf.xmldb.xmlIdTag) + "|" + this.$uniqueId); + this.$deselect(htmlNode); + } + + //Reset internal variables + this.$selectedList.length = 0; + this.$valueList.length = 0; + this.$selected = + this.selected = null; + + //Redraw indicator + if (this.caret) { + htmlNode = this.$findHtmlNode(this.caret.getAttribute( + apf.xmldb.xmlIdTag) + "|" + this.$uniqueId); + + this.$caret = this.$indicate(htmlNode); + } + + if (!noEvent) { + this.dispatchEvent("afterselect", { + selection : this.$valueList, + selected : null, + caret : this.caret + }); + } + }; + + /** + * Selects a set of items + * + * @param {Array} xmlNodeList the {@link term.datanode data nodes} that will be selected. + */ + //@todo I think there are missing events here? + this.selectList = function(xmlNodeList, noEvent, selected, userAction){ + if (!this.selectable || userAction && this.disabled) return; + + if (this.dispatchEvent("beforeselect", { + selection : xmlNodeList, + selected : selected || xmlNodeList[0], + caret : this.caret, + captureOnly : noEvent + }) === false) + return false; + + this.clearSelection(true); + + for (var sel, i = 0; i < xmlNodeList.length; i++) { + //@todo fix select state in unserialize after removing + if (!xmlNodeList[i] || xmlNodeList[i].nodeType != 1) continue; + var htmlNode, + xmlNode = xmlNodeList[i]; + + //Type Detection + if (typeof xmlNode != "object") + xmlNode = apf.xmldb.getNodeById(xmlNode); + if (!(typeof (xmlNode.style || "") == "object")) + htmlNode = this.$pHtmlDoc.getElementById(xmlNode.getAttribute( + apf.xmldb.xmlIdTag) + "|" + this.$uniqueId); + else { + htmlNode = xmlNode; + xmlNode = apf.xmldb.getNodeById(htmlNode.getAttribute( + apf.xmldb.htmlIdTag)); + } + + if (!xmlNode) { + + apf.console.warn("Component : " + this.name + " [" + + this.localName + "]\nMessage : xmlNode whilst selecting a " + + "list of xmlNodes could not be found. Ignoring.") + + continue; + } + + //Select Node + if (htmlNode) { + if (!sel && selected == htmlNode) + sel = htmlNode; + + this.$select(htmlNode, xmlNode); + this.$selectedList.push(htmlNode); + } + this.$valueList.push(xmlNode); + } + + this.$selected = sel || this.$selectedList[0]; + this.selected = selected || this.$valueList[0]; + + this.dispatchEvent("afterselect", { + selection : this.$valueList, + selected : this.selected, + caret : this.caret, + captureOnly : noEvent + }); + }; + + /** + * Sets the {@link term.caret caret} on an item to indicate to the user that the keyboard + * actions are done relevant to that item. Using the keyboard + * a user can change the position of the indicator using the Ctrl and arrow + * keys while not making a selection. When making a selection with the mouse + * or keyboard the indicator is always set to the selected node. Unlike a + * selection there can be only one indicator item. + * + * @param {mixed} xmlNode the identifier to determine the indicator. + * Possible values: + * {XMLElement} the {@link term.datanode data node} to be set as indicator. + * {HTMLElement} the html element node used as visual representation of + * {@link term.datanode data node}. Used to determine the {@link term.datanode data node}. + * {String} the value of the {@link term.datanode data node} to be set as indicator. + * @event indicate Fires when an item becomes the indicator. + */ + this.setCaret = function(xmlNode){ + if (!xmlNode) { + if (this.$caret) + this.$deindicate(this.$caret); + this.caret = + this.$caret = null; + return; + } + + /**** Type Detection ****/ + var htmlNode; + if (typeof xmlNode != "object") + xmlNode = apf.xmldb.getNodeById(xmlNode); + if (!(typeof (xmlNode.style || "") == "object")) { + htmlNode = this.$findHtmlNode(xmlNode.getAttribute( + apf.xmldb.xmlIdTag) + "|" + this.$uniqueId); + } + else { + var id = (htmlNode = xmlNode).getAttribute(apf.xmldb.htmlIdTag); + while (!id && htmlNode.parentNode && htmlNode.parentNode.nodeType == 1) + id = (htmlNode = htmlNode.parentNode).getAttribute( + apf.xmldb.htmlIdTag); + if (!id) alert(this.$int.outerHTML); + + xmlNode = apf.xmldb.getNodeById(id); + } + + if (this.$caret) { + //this.$deindicate(this.$findHtmlNode(this.caret.getAttribute( + //apf.xmldb.xmlIdTag) + "|" + this.$uniqueId)); + this.$deindicate(this.$caret); + } + + this.$caret = this.$indicate(htmlNode); + this.setProperty("caret", this.caret = xmlNode); + }; + + /** + * @private + */ + this.$setTempSelected = function(xmlNode, ctrlKey, shiftKey, down){ + clearTimeout(this.timer); + + if (this.$bindings.selectable) { + while (xmlNode && !this.$getDataNode("selectable", xmlNode)) { + xmlNode = this.getNextTraverseSelected(xmlNode, !down); + } + if (!xmlNode) return; + } + + if (!this.multiselect) + ctrlKey = shiftKey = false; + + if (ctrlKey || this.ctrlselect) { + if (this.$tempsel) { + this.select(this.$tempsel); + this.$tempsel = null; + } + + this.setCaret(xmlNode); + } + else if (shiftKey){ + if (this.$tempsel) { + this.$selectTemp(); + this.$deselect(this.$tempsel); + this.$tempsel = null; + } + + this.select(xmlNode, null, shiftKey); + } + else if (!this.bufferselect || this.$valueList.length > 1) { + this.select(xmlNode); + } + else { + var id = apf.xmldb.getID(xmlNode, this); + + this.$deselect(this.$tempsel || this.$selected); + this.$deindicate(this.$tempsel || this.$caret); + this.$tempsel = this.$indicate(document.getElementById(id)); + this.$select(this.$tempsel); + + var _self = this; + this.timer = $setTimeout(function(){ + _self.$selectTemp(); + }, 400); + } + }; + + /** + * @private + */ + this.$selectTemp = function(){ + if (!this.$tempsel) + return; + + clearTimeout(this.timer); + this.select(this.$tempsel); + + this.$tempsel = null; + this.timer = null; + }; + + /** + * Selects all the {@link term.eachnode each nodes} of this element + * + */ + this.selectAll = function(userAction){ + if (!this.multiselect || !this.selectable + || userAction && this.disabled || !this.xmlRoot) + return; + + var nodes = this.$isTreeArch + ? this.xmlRoot.selectNodes(".//" + + this.each.split("|").join("|.//")) + : this.xmlRoot.selectNodes(this.each); + + this.selectList(nodes); + }; + + /** + * Retrieves an array or a document fragment containing all the selected + * {@link term.datanode data nodes} from this element. + * + * @param {Boolean} [xmldoc] whether the method should return a document fragment. + * @return {mixed} the selection of this element. + */ + this.getSelection = function(xmldoc){ + var i, r; + if (xmldoc) { + r = this.xmlRoot + ? this.xmlRoot.ownerDocument.createDocumentFragment() + : apf.getXmlDom().createDocumentFragment(); + for (i = 0; i < this.$valueList.length; i++) + apf.xmldb.cleanNode(r.appendChild( + this.$valueList[i].cloneNode(true))); + } + else { + for (r = [], i = 0; i < this.$valueList.length; i++) + r.push(this.$valueList[i]); + } + + return r; + }; + + this.$getSelection = function(htmlNodes){ + return htmlNodes ? this.$selectedList : this.$valueList; + }; + + /** + * Selects the next {@link term.datanode data node} to be selected. + * + * @param {XMLElement} xmlNode the context {@link term.datanode data node}. + * @param {Boolean} isTree + */ + this.defaultSelectNext = function(xmlNode, isTree){ + var next = this.getNextTraverseSelected(xmlNode); + //if(!next && xmlNode == this.xmlRoot) return; + + //Why not use this.$isTreeArch ?? + if (next || !isTree) + this.select(next ? next : this.getTraverseParent(xmlNode)); + else + this.clearSelection(true); + }; + + /** + * Selects the next {@link term.datanode data node} when available. + */ + this.selectNext = function(){ + var xmlNode = this.getNextTraverse(); + if (xmlNode) + this.select(xmlNode); + }; + + /** + * Selects the previous {@link term.datanode data node} when available. + */ + this.selectPrevious = function(){ + var xmlNode = this.getNextTraverse(null, -1); + if (xmlNode) + this.select(xmlNode); + }; + + /** + * @private + */ + this.getDefaultNext = function(xmlNode, isTree){ //@todo why is isTree an argument + var next = this.getNextTraverseSelected(xmlNode); + //if(!next && xmlNode == this.xmlRoot) return; + + return (next && next != xmlNode) + ? next + : (isTree + ? this.getTraverseParent(xmlNode) + : null); //this.getFirstTraverseNode() + }; + + /** + * Determines whether a node is selected. + * + * @param {XMLElement} xmlNode The {@link term.datanode data node} to be checked. + * @return {Boolean} whether the element is selected. + */ + this.isSelected = function(xmlNode){ + if (!xmlNode) return false; + + for (var i = 0; i < this.$valueList.length; i++) { + if (this.$valueList[i] == xmlNode) + return this.$valueList.length; + } + + return false; + }; + + /** + * This function checks whether the current selection is still correct. + * Selection can become invalid when updates to the underlying data + * happen. For instance when a selected node is removed. + */ + this.$checkSelection = function(nextNode){ + if (this.$valueList.length > 1) { + //Fix selection if needed + for (var lst = [], i = 0, l = this.$valueList.length; i < l; i++) { + if (apf.isChildOf(this.xmlRoot, this.$valueList[i])) + lst.push(this.$valueList[i]); + } + + if (lst.length > 1) { + this.selectList(lst); + if(this.caret + && !apf.isChildOf(this.xmlRoot, this.caret)) { + this.setCaret(nextNode || this.selected); + } + return; + } + else if (lst.length) { + //this.clearSelection(true); //@todo noEvents here?? + nextNode = lst[0]; + } + } + + if (!nextNode) { + if (this.selected + && !apf.isChildOf(this.xmlRoot, this.selected)) { + nextNode = this.getFirstTraverseNode(); + } + else if(this.selected && this.caret + && !apf.isChildOf(this.xmlRoot, this.caret)) { + this.setCaret(this.selected); + } + else if (!this.selected){ + nextNode = this.xmlRoot + ? this.getFirstTraverseNode() + : null; + } + else { + return; //Nothing to do + } + } + + if (nextNode) { + if (this.autoselect) { + this.select(nextNode); + } + else { + if (!this.multiselect) + this.clearSelection(); + this.clearSelection(); + this.setCaret(nextNode); + } + } + else + this.clearSelection(); + + //if(action == "synchronize" && this.autoselect) this.reselect(); + }; + + /** + * @attribute {Boolean} [multiselect] whether the user may select multiple items. Default is true, false for dropdown. + * @attribute {Boolean} [autoselect] whether a selection is made after data is loaded. Default is true, false for dropdown. When the string 'all' is set, all {@link term.datanode data nodes} are selected. + * @attribute {Boolean} [selectable] whether the {@link term.datanode data nodes} of this element can be selected. Default is true. + * @attribute {Boolean} [ctrlselect] whether when a selection is made as if the user is holding the Ctrl key. When set to true each mouse selection will add to the current selection. selecting an already selected element will deselect it. + * @attribute {Boolean} [allowdeselect] whether the user can remove the selection of this element. When set to true it is possible for this element to have no selected {@link term.datanode data node}. + * @attribute {Boolean} [reselectable] whether selected nodes can be selected again and the selection events are called again. Default is false. When set to false a selected {@link term.datanode data node} cannot be selected again. + * @attribute {String} [default] the value that this component has when no selection is made. + * @attribute {String} [eachvalue] the {@link term.expression expression} that determines the value for each {@link term.datanode data nodes} in the dataset of the element. + * Remarks: + * + * Example: + * + * @see baseclass.multiselect.attribute.eachvalue + */ + this.selectable = true; + if (typeof this.ctrlselect == "undefined") + this.ctrlselect = false; + if (typeof this.multiselect == "undefined") + this.multiselect = true; + if (typeof this.autoselect == "undefined") + this.autoselect = true; + if (typeof this.delayedselect == "undefined") + this.delayedselect = true; + if (typeof this.allowdeselect == "undefined") + this.allowdeselect = true; + this.reselectable = false; + + this.$booleanProperties["selectable"] = true; + //this.$booleanProperties["ctrlselect"] = true; + this.$booleanProperties["multiselect"] = true; + this.$booleanProperties["autoselect"] = true; + this.$booleanProperties["delayedselect"] = true; + this.$booleanProperties["allowdeselect"] = true; + this.$booleanProperties["reselectable"] = true; + + this.$supportedProperties.push("selectable", "ctrlselect", "multiselect", + "autoselect", "delayedselect", "allowdeselect", "reselectable", + "selection", "selected", "default", "value", "caret"); + + /** + * @attribute {String} [value] the value of the element that is selected. + * Remarks: + * + * Example: + * + * @see baseclass.multiselect.attribute.eachvalue + */ + //@todo add check here + this.$propHandlers["value"] = function(value){ + if (this.$lastValue == value) { + delete this.$lastValue; + return; + } + + if (!this.$attrBindings["eachvalue"] && !this.$amlLoaded + && this.getAttribute("eachvalue")) { + var _self = this; + return apf.queue.add("value" + this.$uniqueId, function(){ + _self.$propHandlers["value"].call(_self, value); + }); + } + + + var rule = this.$getBindRule("value", this.xmlRoot); + if (rule) { + /*var compiled = rule.cvalue || rule.cmatch; + if (compiled.type != 3) { + throw new Error(apf.formatErrorString(0, + "Setting value attribute", + "Value attribute does not have legal value.")); + }*/ + + if (rule.models[0] == this.$model && rule.cvalue.xpaths[0] != "#") { + throw new Error(apf.formatErrorString(0, this, + "Setting value attribute", + "Value should not point to the same model where the items\ + are loaded from. Please use value=\"[mdlName::xpath]\" to\ + specify the value. Use selected=\"[xpath]\" to just select\ + a node without making a databinding to it.")); + } + } + + + if (value || value === 0 || this["default"]) + this.select(String(value) || this["default"]); + else + this.clearSelection(); + } + + this.$propHandlers["default"] = function(value, prop){ + if (!this.value || !this.$amlLoaded && !(this.getAttribute("value") + || this.getAttribute("selected") || this.getAttribute("selection"))) { + this.$propHandlers["value"].call(this, ""); + } + } + + /** + * @attribute {String} [value] the value of the element that is selected. + * Remarks: + * + * Example: + * + * @see baseclass.multiselect.attribute.selected, baseclass.multiselect.attribute.selection + */ + //@todo fill this in + this.$propHandlers["caret"] = function(value, prop){ + if (value) + this.setCaret(value); + } + + + + //@todo optimize this thing. Also implement virtual dataset support. + /** + * @attribute {String} [selection] the {@link term.expression expression} that determines the selection for this element. A reference to an xml nodelist can be passed as well. + * Remarks: + * + * Example: + * + * @see baseclass.multiselect.attribute.selected, baseclass.multiselect.attribute.selection + */ + this.$propHandlers["selection"] = + + /** + * @attribute {String} [selected] the {@link term.expression expression} that determines the selected node for this element. A reference to an xml element can be passed as well. + * Remarks: + * + * Example: + * + * @see baseclass.multiselect.attribute.selected, baseclass.multiselect.attribute.selection + */ + this.$propHandlers["selected"] = function(value, prop) { + if (!value) value = this[prop] = null; + + if (prop == "selected" && typeof value != "string") { // && value == this.selected + if (value && value.nodeType != 1) + value = value.nodeValue; + else + //this.selected = null; //I don't remember why this is here. It removes the selected property without setting it again. (dropdown test) + return; + } + + + if (prop == "selection" && (this.getAttribute("selection") || "*").substr(0, 1) != "*"){ + apf.console.warn("Selection attribute (" + this.getAttributeNode("selection") + + ") should select multiple nodes. Please prefix xpath query with a * (ex.: *[item])."); + } + + + if (this.$isSelecting) { + this.selection = this.$valueList; + return false; + } + + var nodes, bindSet, getValue, i, j, c, d; + //Update the selection + if (prop == "selection") { + if (typeof value == "object" && value == this.$valueList) { + var pNode; + //We're using an external model. Need to update bound nodeset + if ((c = this.$attrBindings[prop]) && c.cvalue.models) { //added check, @todo whats up with above assumption? + this.$isSelecting = true; //Prevent reentrance (optimization) + + bindSet = this.$attrBindings["eachvalue"] + && "eachvalue" || this.$bindings["value"] + && "value" || this.$hasBindRule("caption") && "caption"; + + if (!bindSet) + throw new Error("Missing bind rule set: eachvalue, value or caption");//@todo apf3.0 make this into a proper error + + //@todo this may be optimized by keeping a copy of the selection + var selNodes = this.$getDataNode(prop, this.xmlRoot); + nodes = value; + getValue = (d = this.$attrBindings["selection-unique"]) && d.cvalue; + + if (selNodes.length) { + pNode = selNodes[0].parentNode; + } + else { + var model, path; + if (c.cvalue.xpaths[0] == "#" || c.cvalue.xpaths[1] == "#") { + var m = (c.cvalue3 || (c.cvalue3 = apf.lm.compile(c.value, { + xpathmode: 5 + })))(this.xmlRoot); + + model = m.model && m.model.$isModel && m.model; + if (model) + path = m.xpath; + else if (m.model) { + model = apf.xmldb.findModel(m.model); + path = apf.xmlToXpath(m.model, model.data) + (m.xpath ? "/" + m.xpath : ""); //@todo make this better + } + else { + //No selection - nothing to do + } + } + else { + + model = apf.nameserver.get("model", c.cvalue.xpaths[0]); + + path = c.cvalue.xpaths[1]; + } + + if (!model || !model.data) { + this.$isSelecting = false; + return false; + } + + pNode = model.queryNode(path.replace(/\/[^\/]+$|^[^\/]*$/, "") || "."); + + if (!pNode) + throw new Error("Missing parent node"); //@todo apf3.0 make this into a proper error + } + + //Nodes removed + remove_loop: + for (i = 0; i < selNodes.length; i++) { + //Value is either determined by special property or in the + //same way as the value for the bound node. + value = getValue + ? getValue(selNodes[i]) + : this.$applyBindRule(bindSet, selNodes[i]); + + //Compare the value with the traverse nodes + for (j = 0; j < nodes.length; j++) { + if (this.$applyBindRule(bindSet, nodes[j]) == value) //@todo this could be cached + continue remove_loop; + } + + //remove node + apf.xmldb.removeNode(selNodes[i]); + } + + //Nodes added + add_loop: + for (i = 0; i < nodes.length; i++) { + //Value is either determined by special property or in the + //same way as the value for the bound node. + value = this.$applyBindRule(bindSet, nodes[i]); + + //Compare the value with the traverse nodes + for (j = 0; j < selNodes.length; j++) { + if (getValue + ? getValue(selNodes[j]) + : this.$applyBindRule(bindSet, selNodes[j]) == value) //@todo this could be cached + continue add_loop; + } + + //add node + var node = this.$attrBindings["selection-constructor"] + && this.$getDataNode("selection-constructor", nodes[i]) + || apf.getCleanCopy(nodes[i]); + apf.xmldb.appendChild(pNode, node); + } + + //@todo above changes should be via the actiontracker + this.$isSelecting = false; + } + + return; + } + this.selection = this.$valueList; + } + else { + this.selected = null; + } + + if (!this.xmlRoot) { + if (!this.$buffered) { + var f; + this.addEventListener("afterload", f = function(){ + this.removeEventListener("afterload", f); + this.$propHandlers["selected"].call(this, value, prop); + delete this.$buffered; + }); + this.$buffered = true; + } + this[prop] = null; + return false; + } + + if (!value || typeof value != "object") { + //this[prop] = null; + + if (this.$attrBindings[prop]) { + //Execute the selection query + nodes = this.$getDataNode(prop, this.xmlRoot); + if (nodes && (nodes.length || nodes.nodeType == 1)) { + this.setProperty("selection", nodes); + return; + } + + if (!nodes || nodes.length === 0) + return; + + //Current model, it's an init selection, we'll clear the bind + /*if (typeof value == "string" + && !this.$attrBindings[prop].cvalue.xpaths[0]) { + this.$removeAttrBind(prop); + }*/ + } + + if (!value) { + this.clearSelection(); + } + else { + this.select(value); + } + + return false; //Disable signalling the listeners to this property + } + else if (typeof value.length == "number") { + nodes = value; + if (!nodes.length) { + this.selected = null; + if (this.$valueList.length) { //dont clear selection when no selection exists (at prop init) + this.clearSelection(); + return false; //Disable signalling the listeners to this property + } + else return; + } + + //For when nodes are traverse nodes of this element + if (this.isTraverseNode(nodes[0]) + && apf.isChildOf(this.xmlRoot, nodes[0])) { + if (!this.multiselect) { + this.select(nodes[0]); + } + else { + //this[prop] = null; //?? + this.selectList(nodes); + } + return false; //Disable signalling the listeners to this property + } + + //if external model defined, loop through items and find mate by value + if (this.$attrBindings[prop]) { //Can assume an external model is in place + bindSet = this.$attrBindings["eachvalue"] + && "eachvalue" || this.$bindings["value"] + && "value" || this.$hasBindRule("caption") && "caption"; + + if (!bindSet) + throw new Error("Missing bind rule set: eachvalue, value or caption");//@todo apf3.0 make this into a proper error + + var tNodes = !this.each + ? this.getTraverseNodes() + : this.xmlRoot.selectNodes("//" + this.each.split("|").join("|//")); + + getValue = (c = this.$attrBindings["selection-unique"]) && c.cvalue; + var selList = []; + for (i = 0; i < nodes.length; i++) { + //Value is either determined by special property or in the + //same way as the value for the bound node. + value = getValue + ? getValue(nodes[i]) + : this.$applyBindRule(bindSet, nodes[i]); + + //Compare the value with the traverse nodes + for (j = 0; j < tNodes.length; j++) { + if (this.$applyBindRule(bindSet, tNodes[j]) == value) //@todo this could be cached + selList.push(tNodes[j]); + } + } + + //this[prop] = null; //??? + this.selectList(selList, true); //@todo noEvent to distinguish between user actions and not user actions... need to rethink this + return false; + } + + throw new Error("Show me which case this is"); + } + else if (this.$valueList.indexOf(value) == -1) { + //this.selected = null; + this.select(value); + } + }; + + + + this.$propHandlers["allowdeselect"] = function(value){ + if (value) { + var _self = this; + this.$container.onmousedown = function(e){ + if (!e) + e = event; + if (e.ctrlKey || e.shiftKey) + return; + + var srcElement = e.srcElement || e.target; + if (_self.allowdeselect && (srcElement == this + || srcElement.getAttribute(apf.xmldb.htmlIdTag))) + _self.clearSelection(); //hacky + } + } + else { + this.$container.onmousedown = null; + } + }; + + this.$propHandlers["ctrlselect"] = function(value){ + if (value != "enter") + this.ctrlselect = apf.isTrue(value); + } + + function fAutoselect(){ + this.selectAll(); + } + + this.$propHandlers["autoselect"] = function(value){ + if (value == "all" && this.multiselect) + this.addEventListener("afterload", fAutoselect); + else + this.removeEventListener("afterload", fAutoselect); + }; + + this.$propHandlers["multiselect"] = function(value){ + if (!value && this.$valueList.length > 1) + this.select(this.selected); + + //if (value) + //this.bufferselect = false; //@todo doesn't return to original value + }; + + // Select Bind class + + this.addEventListener("beforeselect", function(e){ + if (this.$bindings.selectable && !this.$getDataNode("selectable", e.selected)) + return false; + }, true); + + + + this.addEventListener("afterselect", function (e){ + + var combinedvalue = null; + + + //@todo refactor below + /*if (this.caret == this.selected || e.list && e.list.length > 1 && hasConnections) { + //Multiselect databinding handling... [experimental] + if (e.list && e.list.length > 1 && this.$getConnections().length) { //@todo this no work no more apf3.0 + var oEl = this.xmlRoot.ownerDocument.createElement(this.selected.tagName); + var attr = {}; + + //Fill basic nodes + var nodes = e.list[0].attributes; + for (var j = 0; j < nodes.length; j++) + attr[nodes[j].nodeName] = nodes[j].nodeValue; + + //Remove nodes + for (var prop, i = 1; i < e.list.length; i++) { + for (prop in attr) { + if (typeof attr[prop] != "string") continue; + + if (!e.list[i].getAttributeNode(prop)) + attr[prop] = undefined; + else if(e.list[i].getAttribute(prop) != attr[prop]) + attr[prop] = ""; + } + } + + //Set attributes + for (prop in attr) { + if (typeof attr[prop] != "string") continue; + oEl.setAttribute(prop, attr[prop]); + } + + //missing is childnodes... will implement later when needed... + + oEl.setAttribute(apf.xmldb.xmlIdTag, this.$uniqueId); + apf.MultiSelectServer.register(oEl.getAttribute(apf.xmldb.xmlIdTag), + oEl, e.list, this); + apf.xmldb.addNodeListener(oEl, apf.MultiSelectServer); + + combinedvalue = oEl; + } + }*/ + + + //Set caret property + this.setProperty("caret", e.caret); + + //Set selection length + if (this.sellength != e.selection.length) + this.setProperty("sellength", e.selection.length); + + //Set selection property + delete this.selection; + this.setProperty("selection", e.selection); + if (!e.selection.length) { + //Set selected property + this.setProperty("selected", e.selected); + + //Set value property + if (this.value) + this.setProperty("value", ""); + } + else { + //Set selected property + this.$chained = true; + if (!e.force && (!this.dataParent || !this.dataParent.parent.$chained)) { + var _self = this; + $setTimeout(function(){ + + if (_self.selected == e.selected) + _self.setProperty("selected", combinedvalue || e.selected); + + delete _self.$chained; + }, 10); + } + else { + + this.setProperty("selected", combinedvalue || e.selected); + + delete this.$chained; + } + + //Set value property + var valueRule = this.$attrBindings["eachvalue"] && "eachvalue" + || this.$bindings["value"] && "value" + || this.$hasBindRule("caption") && "caption"; + + if (valueRule) { + //@todo this will call the handler again - should be optimized + + this.$lastValue = this.$applyBindRule(valueRule, e.selected) + //this.$attrBindings["value"] && + if (this.$lastValue != + (valueRule != "value" && (this.xmlRoot + && this.$applyBindRule("value", this.xmlRoot, null, true)) + || this.value)) { + if (valueRule == "eachvalue" || this.xmlRoot != this) + this.change(this.$lastValue); + else + this.setProperty("value", this.$lastValue); + } + /*else { + this.setProperty("value", this.$lastValue); + }*/ + delete this.$lastValue; + } + } + + + //@todo this should be generalized and collapsed with setProperty + if (typeof apf.offline != "undefined" && apf.offline.state.enabled + && apf.offline.state.realtime) { //@todo please optimize + for (var sel = [], i = 0; i < e.selection.length; i++) + sel.push(apf.xmlToXpath(e.selection[i], null, true)); + + apf.offline.state.set(this, "selection", sel); + fSelState.call(this); + } + + + + }, true); + + + + function fSelState(){ + if (typeof apf.offline != "undefined" && apf.offline.state.enabled + && apf.offline.state.realtime) { + apf.offline.state.set(this, "selstate", + [this.caret + ? apf.xmlToXpath(this.caret, null, true) + : "", + this.selected + ? apf.xmlToXpath(this.selected, null, true) + : ""]); + } + } + + this.addEventListener("prop.caret", fSelState); + + +}).call(apf.MultiSelect.prototype = new apf.MultiselectBinding()); + + + +//@todo refactor below +/** + * @private + */ +/* +apf.MultiSelectServer = { + objects : {}, + + register : function(xmlId, xmlNode, selList, jNode){ + if (!this.$uniqueId) + this.$uniqueId = apf.all.push(this) - 1; + + this.objects[xmlId] = { + xml : xmlNode, + list : selList, + jNode : jNode + }; + }, + + $xmlUpdate : function(action, xmlNode, listenNode, UndoObj){ + if (action != "attribute") return; + + var data = this.objects[xmlNode.getAttribute(apf.xmldb.xmlIdTag)]; + if (!data) return; + + var nodes = xmlNode.attributes; + + for (var j = 0; j < data.list.length; j++) { + //data[j].setAttribute(UndoObj.name, xmlNode.getAttribute(UndoObj.name)); + apf.xmldb.setAttribute(data.list[j], UndoObj.name, + xmlNode.getAttribute(UndoObj.name)); + } + + //apf.xmldb.synchronize(); + } +}; +*/ + + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/core/baseclasses/childvalue.js)SIZE(3847)TIME(Mon, 10 Jan 2011 10:53:11 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + +apf.__CHILDVALUE__ = 1 << 27; + + +apf.ChildValue = function(){ + if (!this.$childProperty) + this.$childProperty = "value"; + + this.$regbase = this.$regbase | apf.__CHILDVALUE__; + + var f, re = /^[\s\S]*?>(<\?lm)?([\s\S]*?)(?:\?>)?<[^>]*?>$/; + this.addEventListener("DOMCharacterDataModified", f = function(e){ + if (e && (e.currentTarget == this + || e.currentTarget.nodeType == 2 && e.relatedNode == this) + || this.$amlDestroyed) + return; + + if (this.getAttribute(this.$childProperty)) + return; + + //Get value from xml (could also serialize children, but that is slower + var m = this.serialize().match(re), + v = m && m[2] || ""; + if (m && m[1]) + v = "{" + v + "}"; + + + if (v.indexOf("{") > -1 || v.indexOf("[") > -1) + this.$setDynamicProperty(this.$childProperty, v); + else + + if (this[this.$childProperty] != v) + this.setProperty(this.$childProperty, v); + }); + + //@todo Should be buffered + this.addEventListener("DOMAttrModified", f); + this.addEventListener("DOMNodeInserted", f); + this.addEventListener("DOMNodeRemoved", f); + + this.addEventListener("$skinchange", function(e){ + this.$propHandlers[this.$childProperty].call(this, this.caption || ""); + }); + + this.$init(function(){ + this.addEventListener("prop." + this.$childProperty, function(e){ + if (!e.value && !this.getAttributeNode(this.$childProperty)) + f.call(this); + }); + }); + + this.addEventListener("DOMNodeInsertedIntoDocument", function(e){ + var hasNoProp = typeof this[this.$childProperty] == "undefined"; + + //this.firstChild.nodeType != 7 && + if (hasNoProp + && !this.getElementsByTagNameNS(this.namespaceURI, "*", true).length + && (this.childNodes.length > 1 || this.firstChild + && (this.firstChild.nodeType == 1 + || this.firstChild.nodeValue.trim().length))) { + //Get value from xml (could also serialize children, but that is slower + var m = (this.$aml && this.$aml.xml || this.serialize()).match(re), + v = m && m[2] || ""; + if (m && m[1]) + v = "{" + v + "}"; + + + if (v.indexOf("{") > -1 || v.indexOf("[") > -1) + this.$setDynamicProperty(this.$childProperty, v); + else + + this.setProperty(this.$childProperty, apf.html_entity_decode(v)); //@todo should be xml entity decode + } + else if (hasNoProp) + this.$propHandlers[this.$childProperty].call(this, ""); + }); +}; + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/core/baseclasses/dataaction.js)SIZE(27664)TIME(Mon, 10 Jan 2011 10:53:11 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + +apf.__DATAACTION__ = 1 << 25; + + +/** + * Baseclass adding data action features to this element. + */ +apf.DataAction = function(){ + this.$regbase = this.$regbase | apf.__DATAACTION__; + + /**** Public Methods ****/ + + /** + * Gets the ActionTracker this element communicates with. + * + * @return {ActionTracker} + * @see element.smartbinding + */ + this.getActionTracker = function(ignoreMe){ + if (!apf.AmlNode) + return apf.window.$at; + + var pNode = this, tracker = ignoreMe ? null : this.$at; + if (!tracker && this.dataParent) + tracker = this.dataParent.parent.$at; //@todo apf3.0 change this to be recursive?? + + while (!tracker) { + if (!pNode.parentNode && !pNode.$parentNode) { + var model; + return (model = this.getModel && this.getModel(true)) && model.$at || apf.window.$at; + } + + tracker = (pNode = pNode.parentNode || pNode.$parentNode).$at; + } + return tracker; + }; + + + this.$lock = {}; + + + this.$actionsLog = {}; + this.$actions = false; + + /** + * @term locking {@link http://en.wikipedia.org/wiki/Lock_(computer_science) A lock} + * is a mechanism for enforcing limits on access to a resource in a + * multi-user environment. Locks are one way of enforcing concurrency + * control policies. Ajax.org Platform (apf) has support for locking in + * combination with {@link term.action action rules}. There are two + * types of locks; pessimistic and optimistic locks. Descriptions below are + * from {@link http://en.wikipedia.org/wiki/Lock_(computer_science) wikipedia}. + * + * Optimistic: + * This allows multiple concurrent users access to the database whilst the + * system keeps a copy of the initial-read made by each user. When a user + * wants to update a record, the application determines whether another user + * has changed the record since it was last read. The application does this + * by comparing the initial-read held in memory to the database record to + * verify any changes made to the record. Any discrepancies between the + * initial-read and the database record violates concurrency rules and hence + * causes the system to disregard any update request. An error message is + * generated and the user is asked to start the update process again. + * It improves database performance by reducing the amount of locking + * required, thereby reducing the load on the database server. It works + * efficiently with tables that require limited updates since no users are + * locked out. However, some updates may fail. The downside is constant + * update failures due to high volumes of update requests from multiple + * concurrent users - it can be frustrating for users. + * + * For optimistic locking apf can run as if there would be no locking. + * Changed data is sent to the server and is either successfully saved or + * not. When the action isn't changed and the server returns an error code + * the {@link element.actiontracker actiontracker} automatically + * reverts the change. + * + * Pessimistic: + * This is whereby a user who reads a record with the intention of updating + * it, places an exclusive lock on the record to prevent other users from + * manipulating it. This means no one else can manipulate that record until + * the user releases the lock. The downside is that users can be locked out + * for a long time thereby causing frustration. + * + * For pessimistic locking add the locking attribute to the {@link term.action action rules} + * that need it. The following example shows a lock request for a rename + * action on a file browser tree. + * + * + * + * The unlock variable is true when the lock needs to be released. This is + * done when the action was cancelled after getting a lock. For instance + * when the user presses escape while renaming. + * + * MultiUser: + * In multi user environments it can be handy + * to be signalled of changes by others within the application. For more + * information on this please look at {@link element.remote}. + * + * Remarks: + * During offline works pessimistic locks will always fail. If the application + * does not use {@link element.remote remote smart bindings} the developer + * should reload the part of the content for which the lock failed. See + * {@link baseclass.databinding.event.lockfailed}. + * + * Note: APF understands the status codes specified in RFC4918 for the locking implementation + * {@link http://tools.ietf.org/html/rfc4918#section-9.10.6} + */ + + /** + * Start the specified action, does optional locking and can be offline aware + * - or for optimistic locking it will record the timestamp (a setting + * ) + * - During offline work, optimistic locks will be handled by taking the + * timestamp of going offline + * - This method is always optional! The server should not expect locking to exist. + * + * @event locksuccess Fires when a lock request succeeds + * bubbles: yes + * object: + * {Number} state the return code of the lock request + * @event lockfailed Fires when a lock request failes + * bubbles: yes + * object: + * {Number} state the return code of the lock request + * @event unlocksuccess Fires when an unlock request succeeds + * bubbles: yes + * object: + * {Number} state the return code of the unlock request + * @event unlockfailed Fires when an unlock request fails + * bubbles: yes + * object: + * {Number} state the return code of the unlock request + */ + this.$startAction = function(name, xmlContext, fRollback){ + if (this.disabled || this.liveedit && name != "edit") + return false; + + var actionRule = this.$actions && this.$actions.getRule(name, xmlContext); + if (!actionRule && apf.config.autoDisableActions && this.$actions) { + + if (!xmlContext) { + apf.console.warn("Tried starting new action but no xml \ + context was specified."); + } + else { + apf.console.warn("Tried starting new action but no '" + name + + "' action rule was found."); + } + + + return false; + } + + var bHasOffline = typeof apf.offline != "undefined"; + + if (bHasOffline && !apf.offline.canTransact()) + return false; + + + if (this.dispatchEvent(name + "start", { + xmlContext: xmlContext + }) === false) + return false; + + + + //Requesting a lock, whilst we still have one open + if (this.$lock[name] && !this.$lock[name].stopped) { + + apf.console.warn("Starting new action whilst previous \ + action wasn't terminated:" + name); + + + this.$stopAction(); //or should we call: fRollback.call(this, xmlContext); + } + + //Check if we should attain a lock (when offline, we just pretend to get it) + var lockInstruction = actionRule ? actionRule.lock : null; + if ((bHasOffline && (!apf.offline.enabled || !apf.offline.onLine)) && lockInstruction) { + var curLock = this.$lock[name] = { + start : bHasOffline && !apf.offline.onLine + ? apf.offline.offlineTime + : new Date().getUTCTime(), + stopped : false, + xmlContext : xmlContext, + instr : lockInstruction, + rollback : fRollback + }, + _self = this; + + //Execute pessimistic locking request + apf.saveData(lockInstruction, { + xmlNode : xmlContext, + unlock : false, + callback : function(data, state, extra){ + if (state == apf.TIMEOUT && extra.retries < apf.maxHttpRetries) + return extra.tpModule.retry(extra.id); + + if (state == apf.SUCCESS) { + _self.dispatchEvent("locksuccess", apf.extend({ + state : extra.status, + bubbles : true + }, extra)); + + curLock.retrieved = true; //@todo Record timeout here... think of method + + //Action was apparently finished before the lock came in, cancelling lock + if (curLock.stopped) + _self.$stopAction(name, true, curLock); + + //That's it we're ready to go... + } + else { + if (curLock.stopped) //If the action has terminated we just let it go + return; //Do we want to take away the event from the developer?? + + //Cancel the action, because we didnt get a lock + fRollback.call(_self, xmlContext); + + _self.dispatchEvent("lockfailed", apf.extend({ + state : extra.status, + bubbles : true + }, extra)); + } + } + }); + } + + + this.$actionsLog[name] = xmlContext; + + return true; + }; + + + // @todo think about if this is only for rdb + this.addEventListener("xmlupdate", function(e){ + if (apf.xmldb.disableRDB != 2) + return; + + for (var name in this.$actionsLog) { + if (apf.isChildOf(this.$actionsLog[name], e.xmlNode, true)) { + //this.$stopAction(name, true); + this.$actionsLog[name].rollback.call(this, this.$actionsLog[name].xmlContext); + } + } + }); + + + this.$stopAction = function(name, isCancelled, curLock){ + delete this.$actionsLog[name]; + + + if (!curLock) + curLock = this.$lock[name]; + + if (curLock && !curLock.stopped) { + curLock.stopped = true; + + //The resource needs to unlock when the action is cancelled + if (isCancelled && curLock.retrieved) { + //Execute unlocking request + var _self = this; + apf.saveData(curLock.instr, { + xmlNode : curLock.xmlContext, + unlock : true, + callback : function(data, state, extra){ + if (state == apf.TIMEOUT && extra.retries < apf.maxHttpRetries) + return extra.tpModule.retry(extra.id); + + //Do we care if an unlock failed/succeeded? + _self.dispatchEvent( + (state == apf.SUCCESS + ? "unlocksuccess" + : "unlockfailed"), + apf.extend({ + state : extra.status, + bubbles : true + }, extra)); + } + }); + } + } + + return curLock; + + }; + + /** + * Executes an action using action rules set in the {@link element.actions actions element}. + * + * @param {String} atAction the name of the action to be performed by the ActionTracker. + * Possible values: + * setTextNode sets the first text node of an xml element. {@link core.xmldb.method.setTextNode} + * setAttribute sets the attribute of an xml element. {@link core.xmldb.method.setAttribute} + * removeAttribute removes an attribute from an xml element. {@link core.xmldb.method.removeAttribute} + * setAttributes sets multiple attribute on an xml element. Arguments are [xmlNode, Array] + * replaceNode replaces an xml child with another one. {@link core.xmldb.method.replaceNode} + * addChildNode adds a new xml node to a parent node. {@link core.xmldb.method.addChildNode} + * appendChild appends an xml node to a parent node. {@link core.xmldb.method.appendChild} + * moveNode moves an xml node from one parent to another. {@link core.xmldb.method.moveNode} + * removeNode removes a node from it's parent. {@link core.xmldb.method.removeNode} + * removeNodeList removes multiple nodes from their parent. {@link core.xmldb.method.removeNodeList} + * setValueByXpath sets the nodeValue of an xml node whiche is selected + * by an xpath statement. Arguments are [xmlNode, xpath, value] + * multicall calls multiple of these actions. Arguments is an array + * of argument arrays for these actions each with a func + * property which is the name of the action. + * @param {Array} args the arguments to the function specified + * in atAction. + * @param {String} action the name of the action rule defined in + * actions for this element. + * @param {XMLElement} xmlNode the context for the action rules. + * @param {Boolean} [noevent] whether or not to call events. + * @param {XMLElement} [contextNode] the context node for action processing + * (such as RPC calls). Usually the same + * as xmlNode + * @return {Boolean} specifies success or failure + * @see element.smartbinding + */ + this.$executeAction = function(atAction, args, action, xmlNode, noevent, contextNode, multiple){ + + if (typeof apf.offline != "undefined" && !apf.offline.canTransact()) + return false; + + + + apf.console.info("Executing action '" + action + "' for " + (this.name || "") + + " [" + (this.localName || "") + "]"); + + + //Get Rules from Array + var rule = this.$actions && this.$actions.getRule(action, xmlNode); + if (!rule && this.$actions && apf.config.autoDisableActions + && "action|change".indexOf(action) == -1) { + apf.console.warn("Could not execute action '" + action + "'. \ + No valid action rule was found and auto-disable-actions is enabled"); + + return false; + } + + + var curLock = this.$stopAction(action); + + + var newMultiple; + if (multiple) { + newMultiple = []; + for (var k = multiple.length - 1; k >= 0; k--) { + newMultiple.unshift({ + xmlActionNode : rule && rule[4], + amlNode : this, + selNode : multiple[k], + xmlNode : multiple[k] + }) + } + } + + //@todo apf3.0 Shouldn't the contextNode be made by the match + var ev = new apf.AmlEvent("before" + action.toLowerCase(), { + action : atAction, + args : args, + xmlActionNode : rule, + amlNode : this, + selNode : contextNode, + multiple : newMultiple || false + + ,timestamp : curLock + ? curLock.start + : new Date().getUTCTime() + + }); + + //Call Event and cancel if it returns false + if (!noevent) { + //Allow the action and arguments to be changed by the event + if (this.dispatchEvent(ev.name, null, ev) === false) + return false; + + delete ev.currentTarget; + } + + //Call ActionTracker and return ID of Action in Tracker + var at = this.getActionTracker(); + if (!at)// This only happens at destruction of apf + return UndoObj; + + var UndoObj = at.execute(ev); + ev.xmlNode = UndoObj.xmlNode; + ev.undoObj = UndoObj; + + //Call After Event + if (!noevent) { //@todo noevent is not implemented for before.. ??? + ev.name = "after" + action.toLowerCase(); + ev.cancelBubble = false; + delete ev.returnValue; + delete ev.currentTarget; + this.dispatchEvent(ev.name, null, ev); + } + + return UndoObj; + }; + + /** + * Executes an action based on the set name and the new value + * @param {String} atName the name of the action rule defined in actions for this element. + * @param {String} setName the name of the binding rule defined in bindings for this element. + * @param {XMLElement} xmlNode the xml element to which the rules are applied + * @param {String} value the new value of the node + */ + this.$executeSingleValue = function(atName, setName, xmlNode, value, getArgList){ + var xpath, args, rule = this.$getBindRule(setName, xmlNode); + + //recompile bindrule to create nodes + if (!rule) { + + if (this.$getBindRule(setName)) + throw new Error("There is no rule that matches the xml node for this operation.\ + Please make sure you are matching a node and using the value to \ + specify it's value : " + xmlNode.xml); //@todo make apf Error + else + + return false; + } + + var compiled; + ["valuematch", "match", "value"].each(function(type){ + if (!rule[type] || compiled) + return; + + compiled = rule["c" + type]; //cvaluematch || (rule.value ? rule.cvalue : rule.cmatch); + if (!compiled) + compiled = rule.compile(type); + + if (compiled.type != 3) + compiled = null; + }); + + + if (!compiled) + throw new Error("Cannot create from rule that isn't a single xpath"); //@todo make apf Error + + + var atAction, model, node, + sel = compiled.xpaths, //get first xpath + shouldLoad = false; + + if (sel[0] == "#" || sel[1] == "#") { + var m = (rule.cvalue3 || (rule.cvalue3 = apf.lm.compile(rule.value, { + xpathmode: 5 + })))(xmlNode); + + model = m.model && m.model.$isModel && m.model; + if (model) { + node = model.queryNode(m.xpath); + xmlNode = model.data; + } + else { + model = apf.xmldb.findModel(m.model); + node = m.model.selectSingleNode(m.xpath); + xmlNode = m.model; + } + + sel[1] = m.xpath; + } + else { + + model = sel[0] && apf.nameserver.get("model", sel[0]) || this.$model, + node = model + ? model.queryNode(sel[1]) + : (xmlNode || this.xmlRoot).selectSingleNode(sel[1]); + if (model && !xmlNode) + xmlNode = model.data; //@experimental, after changing this, please run test/test_rename_edge.html + + } + + if (node) { + if (apf.queryValue(node) == value) return; // Do nothing if value is unchanged + + atAction = (node.nodeType == 1 || node.nodeType == 3 + || node.nodeType == 4) ? "setTextNode" : "setAttribute"; + args = (node.nodeType == 1) + ? [node, value] + : (node.nodeType == 3 || node.nodeType == 4 + ? [node.parentNode, value] + : [node.ownerElement || node.selectSingleNode(".."), node.nodeName, value]); + } + else { + if (!this.$createModel) + return false; + + atAction = "setValueByXpath"; + xpath = sel[1]; + + if (!xmlNode) { + //Assuming this component is connnected to a model + if (!model) + model = this.getModel(); + if (model) { + if (!model.data) + model.load(""); + + xpath = (model.getXpathByAmlNode(this) || ".") + + (xpath && xpath != "." ? "/" + xpath : ""); + xmlNode = model.data; + } + else { + if (!this.dataParent) + return false; + + xmlNode = this.dataParent.parent.selected || this.dataParent.parent.xmlRoot; + if (!xmlNode) + return false; + + xpath = (this.dataParent.xpath || ".") + + (xpath && xpath != "." ? "/" + xpath : ""); + shouldLoad = true; + } + } + + args = [xmlNode, value, xpath]; + } + + if (getArgList) { + return { + action : atAction, + args : args + }; + } + + //Use Action Tracker + this.$executeAction(atAction, args, atName, xmlNode); + + if (shouldLoad) + this.load(xmlNode.selectSingleNode(xpath)); + }; + + /** + * Changes the value of this element. + * @action + * @param {String} [string] the new value of this element. + * @todo apf3.0 maybe not for multiselect?? - why is clearError handling not + * in setProperty for value + */ + this.change = function(value, force){ + + if (this.errBox && this.errBox.visible && this.isValid && this.isValid()) + this.clearError(); + + + + //Not databound + if (!this.xmlRoot && !this.$createModel || !(this.$mainBind == "value" + && this.hasFeature(apf.__MULTISELECT__) + ? this.$attrBindings["value"] + : this.$hasBindRule(this.$mainBind))) { + + if (!force && value === this.value + || this.dispatchEvent("beforechange", {value : value}) === false) + return false; + + //@todo in theory one could support actions + //@todo disabled below, because it gives unexpected behaviour when + //form elements are used for layout and other UI alterations + /*this.getActionTracker().execute({ + action : "setProperty", + args : [this, "value", value, false, true], + amlNode : this + });*/ + this.setProperty("value", value); + + return this.dispatchEvent("afterchange", {value : value}); + + } + + var valueRule = this.$attrBindings["eachvalue"] && "eachvalue" + || this.$bindings["value"] && "value" + || this.$hasBindRule("caption") && "caption"; + + if (value == (valueRule != "value" && (this.xmlRoot + && this.$applyBindRule("value", this.xmlRoot, null, true)) + || this.value)) + return false; + + this.$executeSingleValue("change", this.$mainBind, this.xmlRoot, value); + + }; + + this.$booleanProperties["render-root"] = true; + this.$supportedProperties.push("create-model", "actions"); + + /** + * @attribute {Boolean} create-model whether the model this element connects + * to is extended when the data pointed to does not exist. Defaults to true. + * Example: + * In this example a model is extended when the user enters information in + * the form elements. Because no model is specified for the form elements + * the first available model is chosen. At the start it doesn't have any + * data, this changes when for instance the name is filled in. A root node + * is created and under that a 'name' element with a textnode containing + * the entered text. + * + * + * Name + * + * + * Address + * + * + * Country + * + * Submit + * + * + */ + this.$propHandlers["create-model"] = function(value){ + this.$createModel = value; + }; + + this.addEventListener("DOMNodeInsertedIntoDocument", function(e){ + if (typeof this["create-model"] == "undefined" + && !this.$setInheritedAttribute("create-model")) { + this.$createModel = true; + } + }); +}; + +apf.config.$inheritProperties["create-model"] = 1; + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/core/baseclasses/cache.js)SIZE(12760)TIME(Mon, 10 Jan 2011 10:53:11 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + +apf.__CACHE__ = 1 << 2; + + + +/** + * All elements inheriting from this {@link term.baseclass baseclass} have caching features. It takes care of + * storing, retrieving and updating rendered data (in html form) + * to overcome the waiting time while rendering the contents every time the + * data is loaded. + * + * @constructor + * @baseclass + * @author Ruben Daniels (ruben AT ajax DOT org) + * @version %I%, %G% + * @since 0.4 + */ +apf.Cache = function(){ + /* ******************************************************************** + PROPERTIES + *********************************************************************/ + this.cache = {}; + this.$subTreeCacheContext = null; + + this.caching = true; + this.$regbase = this.$regbase | apf.__CACHE__; + + /* ******************************************************************** + PUBLIC METHODS + *********************************************************************/ + + this.addEventListener("$load", function(e){ + if (!this.caching || e.forceNoCache) + return; + + // retrieve the cacheId + if (!this.cacheId) { + this.cacheId = this.$generateCacheId && this.$generateCacheId(e.xmlNode) + || e.xmlNode.getAttribute(apf.xmldb.xmlIdTag) + || apf.xmldb.nodeConnect(apf.xmldb.getXmlDocId(e.xmlNode), e.xmlNode);//e.xmlNode + } + + // Retrieve cached version of document if available + var fromCache = getCache.call(this, this.cacheId, e.xmlNode); + if (fromCache) { + if (fromCache == -1 || !this.getTraverseNodes) + return (e.returnValue = false); + + var nodes = this.getTraverseNodes(); + + //Information needs to be passed to the followers... even when cached... + if (nodes.length && this.autoselect) + this.select(nodes[0], null, null, null, true); + else if (this.clearSelection) + this.clearSelection(); //@todo apf3.0 was setProperty("selected", null + + if (!nodes.length) { + // Remove message notifying user the control is without data + this.$removeClearMessage(); + this.$setClearMessage(this["empty-message"], "empty"); + } + + + //@todo move this to getCache?? + if (nodes.length != this.length) + this.setProperty("length", nodes.length); + + + return false; + } + }); + + this.addEventListener("$clear", function(){ + if (!this.caching) + return; + + /* + Check if we borrowed an HTMLElement + We should return it where it came from + + note: There is a potential that we can't find the exact location + to put it back. We should then look at it's position in the xml. + (but since I'm lazy it's not doing this right now) + There might also be problems when removing the xmlroot + */ + if (this.hasFeature(apf.__MULTISELECT__) + && this.$subTreeCacheContext && this.$subTreeCacheContext.oHtml) { + if (this.renderRoot) { + this.$subTreeCacheContext.parentNode.insertBefore( + this.$subTreeCacheContext.oHtml, this.$subTreeCacheContext.beforeNode); + } + else { + var container = this.$subTreeCacheContext.container || this.$container; + while (container.childNodes.length) + this.$subTreeCacheContext.oHtml.appendChild(container.childNodes[0]); + } + + this.documentId = this.xmlRoot = this.cacheId = this.$subTreeCacheContext = null; + } + else { + /* If the current item was loaded whilst offline, we won't cache + * anything + */ + if (this.$loadedWhenOffline) { + this.$loadedWhenOffline = false; + } + else { + // Here we cache the current part + var fragment = this.$getCurrentFragment(); + if (!fragment) return;//this.$setClearMessage(this["empty-message"]); + + fragment.documentId = this.documentId; + fragment.xmlRoot = this.xmlRoot; + + if (this.cacheId || this.xmlRoot) + setCache.call(this, this.cacheId || + this.xmlRoot.getAttribute(apf.xmldb.xmlIdTag) || "doc" + + this.xmlRoot.getAttribute(apf.xmldb.xmlDocTag), fragment); + } + } + }); + + /** + * Checks the cache for a cached item by ID. If the ID is found the + * representation is loaded from cache and set active. + * + * @param {String} id the id of the cache element which is looked up. + * @param {Object} xmlNode + * @return {Boolean} + * Possible values: + * true the cache element is found and set active + * false otherwise + * @see baseclass.databinding.method.load + * @private + */ + function getCache(id, xmlNode){ + /* + Let's check if the requested source is actually + a sub tree of an already rendered part + */ + + if (xmlNode && this.hasFeature(apf.__MULTISELECT__) && this.$isTreeArch) { + var cacheItem, + htmlId = xmlNode.getAttribute(apf.xmldb.xmlIdTag) + "|" + this.$uniqueId, + node = this.$pHtmlDoc.getElementById(htmlId); + if (node) + cacheItem = id ? false : this.$container; //@todo what is the purpose of this statement? + else { + for (var prop in this.cache) { + if (this.cache[prop] && this.cache[prop].nodeType) { + node = this.cache[prop].getElementById(htmlId); + if (node) { + cacheItem = id ? prop : this.cache[prop]; //@todo what is the purpose of this statement? + break; + } + } + } + } + + if (cacheItem && !this.cache[id]) { + /* + Ok so it is, let's borrow it for a while + We can't clone it, because the updates will + get ambiguous, so we have to put it back later + */ + var oHtml = this.$findHtmlNode( + xmlNode.getAttribute(apf.xmldb.xmlIdTag) + "|" + this.$uniqueId); + this.$subTreeCacheContext = { + oHtml : oHtml, + parentNode : oHtml.parentNode, + beforeNode : oHtml.nextSibling, + cacheItem : cacheItem + }; + + this.documentId = apf.xmldb.getXmlDocId(xmlNode); + this.cacheId = id; + this.xmlRoot = xmlNode; + + //Load html + if (this.renderRoot) + this.$container.appendChild(oHtml); + else { + while (oHtml.childNodes.length) + this.$container.appendChild(oHtml.childNodes[0]); + } + + return true; + } + } + + + //Checking Cache... + if (!this.cache[id]) return false; + + //Get Fragment and clear Cache Item + var fragment = this.cache[id]; + + this.documentId = fragment.documentId; + this.cacheId = id; + this.xmlRoot = xmlNode;//fragment.xmlRoot; + + + this.setProperty("root", this.xmlRoot); + + + this.clearCacheItem(id); + + this.$setCurrentFragment(fragment); + + return true; + } + + /** + * Sets cache element and it's ID + * + * @param {String} id the id of the cache element to be stored. + * @param {DocumentFragment} fragment the data to be stored. + * @private + */ + function setCache(id, fragment){ + if (!this.caching) return; + + this.cache[id] = fragment; + } + + /** + * Finds HTML presentation node in cache by ID + * + * @param {String} id the id of the HTMLElement which is looked up. + * @return {HTMLElement} the HTMLElement found. When no element is found, null is returned. + */ + this.$findHtmlNode = function(id){ + var node = this.$pHtmlDoc.getElementById(id); + if (node) return node; + + for (var prop in this.cache) { + if (this.cache[prop] && this.cache[prop].nodeType) { + node = this.cache[prop].getElementById(id); + if (node) return node; + } + } + + return null; + }; + + /** + * Removes an item from the cache. + * + * @param {String} id the id of the HTMLElement which is looked up. + * @param {Boolean} [remove] whether to destroy the Fragment. + * @see baseclass.databinding.method.clear + * @private + */ + this.clearCacheItem = function(id, remove){ + this.cache[id].documentId = + this.cache[id].cacheId = + this.cache[id].xmlRoot = null; + + if (remove) + apf.destroyHtmlNode(this.cache[id]); + + this.cache[id] = null; + }; + + /** + * Removes all items from the cache + * + * @see baseclass.databinding.method.clearCacheItem + * @private + */ + this.clearAllCache = function(){ + for (var prop in this.cache) { + if (this.cache[prop]) + this.clearCacheItem(prop, true); + } + }; + + /** + * Gets the cache item by it's id + * + * @param {String} id the id of the HTMLElement which is looked up. + * @see baseclass.databinding.method.clearCacheItem + * @private + */ + this.getCacheItem = function(id){ + return this.cache[id]; + }; + + /** + * Checks whether a cache item exists by the specified id + * + * @param {String} id the id of the cache item to check. + * @see baseclass.databinding.method.clearCacheItem + * @private + */ + this.$isCached = function(id){ + return this.cache[id] || this.cacheId == id ? true : false; + }; + + if (!this.$getCurrentFragment) { + this.$getCurrentFragment = function(){ + var fragment = this.$container.ownerDocument.createDocumentFragment(); + + while (this.$container.childNodes.length) { + fragment.appendChild(this.$container.childNodes[0]); + } + + return fragment; + }; + + this.$setCurrentFragment = function(fragment){ + this.$container.appendChild(fragment); + + if (!apf.window.hasFocus(this) && this.blur) + this.blur(); + }; + } + + /** + * @attribute {Boolean} caching whether caching is enabled for this element. + */ + this.$booleanProperties["caching"] = true; + this.$supportedProperties.push("caching"); + + this.addEventListener("DOMNodeRemovedFromDocument", function(e){ + //Remove all cached Items + this.clearAllCache(); + }); +}; + +apf.GuiElement.propHandlers["caching"] = function(value) { + if (!apf.isTrue(value)) return; + + if (!this.hasFeature(apf.__CACHE__)) + this.implement(apf.Cache); +}; + + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/core/baseclasses/rename.js)SIZE(14376)TIME(Mon, 10 Jan 2011 10:53:11 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + +apf.__RENAME__ = 1 << 10; + + + +/** + * All elements inheriting from this {@link term.baseclass baseclass} have the rename features. Rename is triggered by + * pressing F2 on an item or by clicking once on an already selected item. This + * will show an input element in place where the user can change the name of the + * item to a new one. When the caption is changed the {@link term.datanode data node} is + * changed accordingly. + * Example: + * This example shows a list containing products. Only products that have the + * editable attribute set to 1 can be renamed by the user. + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * @event stoprename Fires when a rename action is cancelled. + * + * @constructor + * @baseclass + * @author Ruben Daniels (ruben AT ajax DOT org) + * @version %I%, %G% + * @since 0.5 + */ +apf.Rename = function(){ + this.$regbase = this.$regbase|apf.__RENAME__; + + this.canrename = false; + this.$renameSubject = + this.renameTimer = + this.lastCursor = null; + + /** + * @attribute {Boolean} rename whether the user can start renaming rendered nodes in this element. + */ + this.$booleanProperties["canrename"] = true; + this.$booleanProperties["autorename"] = true; + this.$supportedProperties.push("canrename", "autorename"); + + + this.$propHandlers["autorename"] = function(value){ + if (value) { + this.reselectable = true; + this.bufferselect = false; + this.addEventListener("afterselect", $afterselect); + this.addEventListener("keydown", $keydown); + } + else { + this.removeEventListener("afterselect", $afterselect); + this.removeEventListener("keydown", $keydown); + } + } + + function $afterselect(){ + var _self = this; + $setTimeout(function(){ + if (_self.hasFocus()) + _self.startRename(); + }, 20); + } + + function $keydown(e){ + if (!this.renaming && apf.isCharacter(e.keyCode)) + this.startRename(); + } + + this.$isContentEditable = function(e){ + if (this.renaming && this.autorename) + return true; + } + + + /** + * Changes the data presented as the caption of a specified {@link term.datanode data node}. + * If none is specified the indicated node is used. + * + * @action + * @param {XMLElement} xmlNode the element to change the caption of. + * @param {String} value the value to set as the caption of the {@link term.datanode data node}. + */ + this.rename = function(xmlNode, value){ + if (!xmlNode) + xmlNode = this.caret || this.selected; + + if (!xmlNode) return; + + this.$executeSingleValue("rename", "caption", xmlNode, value); + }; + + /** + * Starts the rename process with a delay, allowing for cancellation when + * necesary. Cancellation is necesary for instance, when double click was + * intended or a dragdrop operation. + * + */ + this.startDelayedRename = function(e, time, userAction){ + if (e && (e.button == 2 || e.ctrlKey || e.shiftKey) + || userAction && this.disabled) + return; + + clearTimeout(this.renameTimer); + this.renameTimer = $setTimeout('apf.lookup(' + + this.$uniqueId + ').startRename()', time || 400); + }; + + /** + * Starts the rename process by displaying an input box at the position + * of the item that can be renamed by the user. + * + */ + this.startRename = function(force, startEmpty, userAction){ + if (!force && (this.renaming || !this.canrename + || !this.$startAction("rename", this.caret + || this.selected, this.stopRename)) + || userAction && this.disabled) + return false; + + if (!this.hasFocus()) + this.focus(null, null, true); + + clearTimeout(this.renameTimer); + + var elCaption = this.$getCaptionElement + ? this.$getCaptionElement() + : this.$caret || this.$selected; + + if (!elCaption) + return this.stopRename(); + + this.renaming = true; + this.$renameSubject = this.caret || this.selected; + + var wdt = elCaption.offsetWidth; + this.lastCursor = elCaption.style.cursor; + elCaption.style.cursor = "text"; + elCaption.parentNode.replaceChild(this.$txt, elCaption); + elCaption.host = this; + + if (apf.isTrue(this.$getOption("main", "scalerename"))) { + var diff = apf.getWidthDiff(this.$txt); + this.$txt.style.width = (wdt - diff - 3) + "px"; + } + + this.$replacedNode = elCaption; + var xmlNode = this.$getCaptionXml + ? this.$getCaptionXml(this.$renameSubject) + : this.$getDataNode("caption", this.$renameSubject); + + //xmlNode.nodeType >= 2 && xmlNode.nodeType <= 4 + value = startEmpty || !xmlNode + ? "" + : (xmlNode.nodeType != 1 + ? unescape(xmlNode.nodeValue) //decodeURI( - throws an error when using % in a non expected way + : (apf.isOnlyChild(xmlNode.firstChild, [3,4]) + ? apf.queryValue(xmlNode) + : this.$applyBindRule("caption", this.$renameSubject))) || ""; + + if (apf.hasContentEditable) { + if (this.$multiLineRename) + this.$txt.innerHTML = apf.htmlCleaner.prepare(value.trim() + .replace(//g, ">") + .replace(/\n/g, "
    ")); + else + this.$txt.innerHTML = value.replace(/" || ""; + } + else + this.$txt.value = value; + + this.$txt.unselectable = "Off"; + this.$txt.host = this; + + //this.$txt.focus(); + var txt = this.$txt; + var f = function(){ + try { + txt.focus(); + txt.select(); + } + catch(e) {} + }; + if (apf.isIE) f() + else setTimeout(f); + }; + + /** + * Stop renaming process and change the data according to the set value. + * Cancel the renaming process without changing data. + * + */ + this.stopRename = function(contextXml, success){ + clearTimeout(this.renameTimer); + + if (!this.renaming || contextXml && contextXml != this.$renameSubject + || !this.$replacedNode) + return false; + + this.renaming = false; + + if (this.$txt.parentNode && this.$txt.parentNode.nodeType == 1) { + if (apf.isIE8 || apf.isIE7Emulate) + this.$txt.blur(); + + this.$txt.parentNode.replaceChild(this.$replacedNode, this.$txt); + } + + if (this.$replacedNode) { + this.$replacedNode.style.cursor = this.lastCursor || ""; + this.$replacedNode.host = null; + } + + //apf.hasContentEditable ?? + if (this.$multiLineRename) { + var value = apf.html_entity_decode( + apf.htmlCleaner.parse(this.$txt.innerHTML, true) + .replace(/
    /g, "") + .replace(/<\/?p>/g, "")); + } + else { + var value = this.$txt[apf.hasContentEditable ? "innerText" : "value"] + .replace(/<.*?nobr>/gi, "").replace(/\n$/, ""); //last replace is for chrome + } + + if (!success || (this.$validateRename && !this.$validateRename(value))) { + this.dispatchEvent("stoprename"); + this.$stopAction("rename"); + } + else { + if (this.$replacedNode) + this.$replacedNode.innerHTML = value.replace(/"); + //this.$selected.innerHTML = this.$txt.innerHTML; + this.rename(this.$renameSubject, value); + } + + if (!this.renaming) { + this.$renameSubject = null; + this.$replacedNode = null; + this.$txt.style.width = ""; + } + + return true; + }; + + + this.addEventListener("keydown", function(e){ + var key = e.keyCode; + + if (this.renaming) { + if (key == 27 || this.$multiLineRename && e.ctrlKey && key == 13 + || !this.$multiLineRename && key == 13) { + this.stopRename(null, key == 13 && !this.$autocomplete); + e.cancelBubble = true; + return false; + } + else if (apf.hasContentEditableContainerBug && key == 8 + && this.$txt.innerHTML == "
    ") { + e.preventDefault(); + } + + return; + } + + //F2 + if (key == 113) { + if (this.$tempsel) + this.$selectTemp(); + + if (this.caret != this.selected) { + if (this.multiselect || this.isSelected(this.caret)) { + this.selected = this.caret; + this.$selected = this.$caret; + } + else + this.select(this.caret, true); + } + + this.startRename(); + + return false; + } + }, true); + + + this.addEventListener("DOMNodeRemovedFromDocument", function(e){ + this.$txt.refCount--; + + if (!this.$txt.refCount) { + this.$txt.host = + this.$txt.onmouseover = + this.$txt.onmousedown = + this.$txt.select = + this.$txt.onfocus = + this.$txt.onblur = null; + } + this.$txt = null; + }); + + this.$init(apf.Rename.initEditableArea); +}; + +apf.Rename.initEditableArea = function(){ + if (!(this.$txt = document.getElementById("txt_rename"))) { + if (apf.hasContentEditable) { + this.$txt = document.createElement("DIV"); + this.$txt.contentEditable = true; + if (apf.isIE6) + this.$txt.style.width = "1px"; + //this.$txt.canHaveHTML = false; + } + else { + this.$txt = document.createElement("input"); + this.$txt.id = "txt_rename"; + this.$txt.autocomplete = false; + } + + + //if (apf.hasFocusBug) + //apf.sanitizeTextbox(this.$txt); + + + this.$txt.refCount = 0; + this.$txt.id = "txt_rename"; + //this.$txt.style.whiteSpace = "nowrap"; + apf.importCssString("#txt_rename{white-space:nowrap}"); + this.$txt.onselectstart = function(e){ + (e || event).cancelBubble = true; + }; + + this.$txt.onmouseover = + this.$txt.onmouseout = + this.$txt.oncontextmenu = + //this.$txt.onkeydown = + this.$txt.onmouseup = + this.$txt.onmousedown = function(e){ + apf.stopPropagation(e || event) + }; + + this.$txt.onkeyup = function(e){ + //(e || event).cancelBubble = true; + + if (!this.host.$autocomplete) + return; + + this.host.$lookup(this[apf.hasContentEditable ? "innerHTML" : "value"]); + } + + var sel; + this.$txt.select = function(){ + if (!apf.hasMsRangeObject) { + (sel || (sel = new apf.selection())).selectNode(this); + return; + } + + var r = document.selection.createRange(); + //r.moveEnd("character", this.$ext.innerText.length); + try { + r.moveToElementText(this); + + if (apf.isFalse(this.host.$getOption("main", "selectrename")) + || typeof this.host.$renameStartCollapse != "undefined") //@todo please deprecate renameStartCollapse + r.collapse(this.host.$renameStartCollapse); + } catch(e) {} //BUG!!!! + + r.select(); + }; + + + if (apf.hasFocusBug) { + this.$txt.onfocus = function(){ + if (apf.window) + apf.window.$focusfix2(); + }; + } + + + this.$txt.onblur = function(){ + //if (apf.isGecko) + //return; //bug in firefox calling onblur too much + //if (apf.isChrome && !arguments.callee.caller) + //return; + + + if (apf.hasFocusBug) + apf.window.$blurfix(); + + + if (this.host.$autocomplete) + return; + + this.host.stopRename(null, true); + }; + } + + this.$txt.refCount++; +} + + + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/core/baseclasses/a11y.js)SIZE(5144)TIME(Mon, 10 Jan 2011 10:53:11 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + +apf.__ALIGNMENT__ = 1 << 29; + + + +/** + * All elements inheriting from this {@link term.baseclass baseclass} have + * accessibility features. + * + * @constructor + * @baseclass + * @author Mike de Boer (mike AT javeline DOT com) + * @version %I%, %G% + * @since 3.0 + * + * @default_private + */ +apf.A11y = function(){ + this.setRole = function(oNode, sRole) { + if (!oNode) + throw new Error(); + if (!apf.A11y.ROLES[sRole]) + throw new Error(); + + oNode.setAttribute("role", sRole); + }; + + this.setWidgetAttr = function(oNode, sAttr, mValue) { + var rel = apf.A11y.ATTR_WIDGETS[sAttr]; + + if (!rel) + throw new Error("attr does not exist"); + if ((typeof rel == "boolean" && typeof mValue != "boolean") + || (typeof rel == "number" && typeof mValue != "number") + || ((typeof rel == "string" || apf.isArray(rel)) && typeof mValue != "string")) + throw new Error("invalid type"); + + oNode.setAttribute("aria-" + sAttr, mValue); + }; + + this.updateLiveRegion = function(oNode, sAttr, mValue) { + var rel = apf.A11y.ATTR_LIVEREGION[sAttr]; + + if (!rel) + throw new Error("attr does not exist"); + if ((typeof rel == "boolean" && typeof mValue != "boolean") + || (apf.isArray(rel) && typeof mValue != "string")) + throw new Error("invalid type"); + + if (oNode) { + oNode.setAttribute("aria-" + sAttr, mValue); + } + else { + // create a hidden element, cache it and set the attributes on that + // element. + } + }; + + this.setDragDropAttr = function(oNode, sAttr, mValue) { + var rel = apf.A11y.ATTR_LIVEREGION[sAttr]; + + if (!rel) + throw new Error("attr does not exist"); + if ((typeof rel == "boolean" && typeof mValue != "boolean") + || (apf.isArray(rel) && typeof mValue != "string")) + throw new Error("invalid type"); + + oNode.setAttribute("aria-" + sAttr, mValue); + }; + + this.setRelationAttr = function() { + var args = Array.prototype.slice.call(arguments), + oNode = args.shift(), + sAttr = args.shift(), + sVal = args.join(" "); // space delimited list of values (for example IDs) + + if (!apf.A11y.ATTR_RELATIONS[sAttr]) + throw new Error(); + + oNode.setAttribute("aria-" + sAttr, sVal); + }; +}; + +apf.A11y.ROLES = { + "alert":1, "alertdialog":1, "application":1, "article":1, "banner":1, "button":1, + "checkbox":1, "columnheader":1, "combobox":1, "complementary":1, "contentinfo":1, + "definition":1, "dialog":1, "directory":1, "grid":1, "gridcell":1, "group:":1, + "heading":1, "img":1, "link":1, "list":1, "listbox":1, "listitem":1, "log":1, + "main":1, "marquee":1, "math":1, "menu":1, "menubar":1, "menuitem":1, + "menuitemcheckbox":1, "menuitemradio":1, "navigation": 1, "note":1, "option":1, + "presentation":1, "progressbar":1, "radio":1, "radiogroup":1, "region":1, + "row":1, "rowheader":1, "search":1, "seperator":1, "slider":1, "slinbutton":1, + "status":1, "tab":1, "tablist":1, "tabpanel":1, "textbox":1, "timer":1, + "toolbar":1, "tooltip":1, "tree":1, "treegrid":1, "treeitem":1 +}; + +apf.A11y.ATTR_WIDGETS = { + "autocomplete":true, "checked":true, "disabled":true, "expanded":true, + "haspopup":true, "hidden":true, "invalid":["grammar", "false", "spelling", + "true"], "level":1, "multiline":true, "multiselectable":true, "pressed": + ["true", "false", "pressed"], "readonly":true, "required":true, + "selected":true, "sort":["ascending", "descending", "none", "other"], + "valuemax":1, "valuemin":1, "valuenow":1, "valuetext":"string" +}; + +apf.A11y.ATTR_LIVEREGION = { + "atomic":true, "busy":true, "live":["off", "polite", "assertive"], + "relevant":["additions", "removals", "text", "all", "additions text"] +}; + +apf.A11y.ATTR_DRAGDROP = { + "dropeffect":["copy", "move", "reference", "execute", "popup", "none"], + "grabbed":true +}; + +apf.A11y.ATTR_RELATIONS = { + "activedescendant":{}, "controls":{}, "describedby":{}, "flowto":{}, + "label":"1", "labelledby":{}, "owns":{}, "posinset":1, "setsize":1 +}; + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/core/baseclasses/basebutton.js)SIZE(10562)TIME(Mon, 10 Jan 2011 10:53:11 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + + +/** + * Baseclass of an element that has one or two states and can be clicked on to + * trigger an action. (i.e. {@link element.button} or {@link element.checkbox}). + * + * @constructor + * @baseclass + * @author Abe Ginner + * @version %I%, %G% + * @since 0.8 + * + * @event click Fires when the user presses a mousebutton while over this element and then let's the mousebutton go. + */ +apf.BaseButton = function(){ + this.$init(true); +}; + +(function() { + + this.implement(apf.ChildValue); + + + this.$refKeyDown = // Number of keys pressed. + this.$refMouseDown = 0; // Mouse button down? + this.$mouseOver = // Mouse hovering over the button? + this.$mouseLeft = false; // Has the mouse left the control since pressing the button. + + /**** Properties and Attributes ****/ + + /** + * @attribute {string} background sets a multistate background. The arguments + * are seperated by pipes '|' and are in the order of: + * 'imagefilename|mapdirection|nrofstates|imagesize' + * The mapdirection argument may have the value of 'vertical' or 'horizontal'. + * The nrofstates argument specifies the number of states the iconfile contains: + * 1 - normal + * 2 - normal, hover + * 3 - normal, hover, down + * 4 - normal, hover, down, disabled + * The imagesize argument specifies how high or wide each icon is inside the + * map, depending of the mapdirection argument. + * + * Example: + * A 3 state picture where each state is 16px high, vertically spaced + * + * background="threestates.gif|vertical|3|16" + * + */ + this.$propHandlers["background"] = function(value){ + var oNode = this.$getLayoutNode("main", "background", this.$ext); + + if (!oNode) + return apf.console.warn("No background defined in the Button skin", "button"); + + + if (value) { + var b = value.split("|"); + this.$background = b.concat(["vertical", 2, 16].slice(b.length - 1)); + + oNode.style.backgroundImage = "url(" + this.mediaPath + b[0] + ")"; + oNode.style.backgroundRepeat = "no-repeat"; + } + else { + oNode.style.backgroundImage = ""; + oNode.style.backgroundRepeat = ""; + this.$background = null; + } + }; + + /**** Keyboard Support ****/ + + + this.addEventListener("keydown", function(e){ + var key = e.keyCode; + //var ctrlKey = e.ctrlKey; << UNUSED + //var shiftKey = e.shiftKey; << UNUSED + + switch (key) { + case 13: + if (this.localName != "checkbox") + this.$ext.onmouseup(e.htmlEvent, true); + break; + case 32: + if (!e.htmlEvent.repeat) { // Only when first pressed, not on autorepeat. + this.$refKeyDown++; + this.$updateState(e.htmlEvent); + } + return false; + } + }, true); + + this.addEventListener("keyup", function(e){ + var key = e.keyCode; + + switch (key) { + case 32: + this.$refKeyDown--; + + if (this.$refKeyDown < 0) { + this.$refKeyDown = 0; + return false; + } + + if (this.$refKeyDown + this.$refMouseDown == 0 && !this.disabled) + this.$ext.onmouseup(e, true); + + this.$updateState(e); + return false; + } + }, true); + + + /**** Private state handling methods ****/ + + this.states = { + "Out" : 1, + "Over" : 2, + "Down" : 3 + }; + + this.$updateState = function(e, strEvent) { + if (e.reset) { //this.disabled || + this.$refKeyDown = 0; + this.$refMouseDown = 0; + this.$mouseOver = false; + return false; + } + + if (this.$refKeyDown > 0 + || (this.$refMouseDown > 0 && this.$mouseOver) + || (this.isBoolean && this.value)) { + this.$setState("Down", e, strEvent); + } + else if (this.$mouseOver) { + this.$setState("Over", e, strEvent); + } + else + this.$setState("Out", e, strEvent); + }; + + this.$setupEvents = function() { + if (this.editable) + return; + + var _self = this; + + this.$ext.onmousedown = function(e) { + e = e || window.event; + + if (_self.$notfromext && (e.srcElement || e.target) == this) + return; + + _self.$refMouseDown = 1; + _self.$mouseLeft = false; + + if (_self.disabled) + return; + + if (!apf.isIE) { // && (apf.isGecko || !_self.submenu) Causes a focus problem for menus + if (_self.value) + apf.stopEvent(e); + else + apf.cancelBubble(e); + } + + _self.$updateState(e, "mousedown"); + }; + + this.$ext.onmouseup = function(e, force) { + e = e || window.event; + //if (e) e.cancelBubble = true; + + if (_self.disabled || !force && (!_self.$mouseOver || !_self.$refMouseDown)) + return; + + _self.$refMouseDown = 0; + _self.$updateState(e, "mouseup"); + + // If this is coming from a mouse click, we shouldn't have left the button. + if (_self.disabled || (e && e.type == "click" && _self.$mouseLeft == true)) + return false; + + // If there are still buttons down, this is not a real click. + if (_self.$refMouseDown + _self.$refKeyDown) + return false; + + if (_self.$clickHandler && _self.$clickHandler()) + _self.$updateState (e || event, "click"); + else + _self.dispatchEvent("click", {htmlEvent : e}); + + return false; + }; + + this.$ext.onmousemove = function(e) { + if ((!_self.$mouseOver || _self.$mouseOver == 2)) { + e = e || window.event; + + if (_self.$notfromext && (e.srcElement || e.target) == this) + return; + + _self.$mouseOver = true; + + if (!_self.disabled) + _self.$updateState(e, "mouseover"); + } + }; + + this.$ext.onmouseout = function(e) { + e = e || window.event; + + //Check if the mouse out is meant for us + var tEl = e.explicitOriginalTarget || e.toElement; + if (this == tEl || apf.isChildOf(this, tEl)) + return; + + _self.$mouseOver = false; + _self.$refMouseDown = 0; + _self.$mouseLeft = true; + + if (!_self.disabled) + _self.$updateState(e, "mouseout"); + }; + + + if (apf.isIphone) + apf.iphone.linkEvents(this.$ext, true); + + + if (apf.hasClickFastBug) + this.$ext.ondblclick = this.$ext.onmouseup; + }; + + this.$doBgSwitch = function(nr){ + if (this.background && (this.$background[2] >= nr || nr == 4)) { + if (nr == 4) + nr = this.$background[2] + 1; + + var strBG = this.$background[1] == "vertical" + ? "0 -" + (parseInt(this.$background[3]) * (nr - 1)) + "px" + : "-" + (parseInt(this.$background[3]) * (nr - 1)) + "px 0"; + + this.$getLayoutNode("main", "background", + this.$ext).style.backgroundPosition = strBG; + } + }; + + /**** Focus Handling ****/ + + this.$focus = function(){ + if (!this.$ext) + return; + + this.$setStyleClass(this.$ext, this.$baseCSSname + "Focus"); + }; + + this.$blur = function(e){ + if (!this.$ext) + return; //FIREFOX BUG! + + this.$setStyleClass(this.$ext, "", [this.$baseCSSname + "Focus"]); + /*this.$refKeyDown = 0; + this.$refMouseDown = 0; + this.$mouseLeft = true;*/ + + + /*if (this.submenu) { + if (this.value) { + this.$setState("Down", {}, "mousedown"); + this.$hideMenu(); + } + }*/ + + + if (e) + this.$updateState({});//, "onblur" + }; + + this.addEventListener("prop.disabled", function(e){ + this.$refKeyDown = + this.$refMouseDown = 0; + //this.$mouseOver = + //this.$mouseLeft = false; + }); + + /*** Clearing potential memory leaks ****/ + + this.$destroy = function(skinChange){ + if (!skinChange && this.$ext) { + this.$ext.onmousedown = this.$ext.onmouseup = this.$ext.onmouseover = + this.$ext.onmouseout = this.$ext.onclick = this.$ext.ondblclick = null; + } + }; + +}).call(apf.BaseButton.prototype = new apf.StandardBinding()); + + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/core/baseclasses/baselist.js)SIZE(38995)TIME(Mon, 10 Jan 2011 10:53:11 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + + +/** + * Baseclass of elements that allows the user to select one or more items + * out of a list. + * + * @constructor + * @baseclass + * + * @inherits apf.MultiSelect + * @inherits apf.Cache + * @inherits apf.DataAction + * @inherits apf.XForms + * + * @author Ruben Daniels (ruben AT ajax DOT org) + * @version %I%, %G% + * @since 0.8 + * @default_private + * + * @binding caption Determines the caption of a node. + * @binding icon Determines the icon of a node. This binding rule is used + * to determine the icon displayed when using a list skin. The {@link baseclass.baselist.binding.image image binding} + * is used to determine the image in the thumbnail skin. + * @binding image Determines the image of a node. This binding rule is used + * to determine the image displayed when using a thumbnail skin. The {@link baseclass.baselist.binding.icon icon binding} + * is used to determine the icon in the list skin. + * Example: + * In this example the image url is read from the thumbnail attribute of the data node. + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * @binding css Determines a css class for a node. + * Example: + * In this example a node is bold when the folder contains unread messages: + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * @binding tooltip Determines the tooltip of a node. + * @event notunique Fires when the more attribute is set and an item is added that has a caption that already exists in the list. + * object: + * {String} value the value that was entered. + */ +apf.BaseList = function(){ + this.$init(true); + + + this.$dynCssClasses = []; + + + this.listNodes = []; +}; + +(function() { + + this.implement( + + apf.Cache, + + + apf.DataAction, + + + apf.K + ); + + + /**** Properties and Attributes ****/ + + this.$focussable = true; // This object can get the focus + this.$isWindowContainer = -1; + + this.multiselect = true; // Initially Disable MultiSelect + + /** + * @attribute {String} fill the set of items that should be loaded into this + * element. A start and an end seperated by a -. + * Example: + * This example loads a list with items starting at 1980 and ending at 2050. + * + * + * + * + * + * + * + */ + this.$propHandlers["fill"] = function(value){ + if (value) + this.loadFillData(this.getAttribute("fill")); + else + this.clear(); + }; + + + + /** + * @attribute {String} mode Sets the way this element interacts with the user. + * Possible values: + * check the user can select a single item from this element. The selected item is indicated. + * radio the user can select multiple items from this element. Each selected item is indicated. + */ + this.$mode = 0; + this.$propHandlers["mode"] = function(value){ + if ("check|radio".indexOf(value) > -1) { + if (!this.hasFeature(apf.__MULTICHECK__)) + this.implement(apf.MultiCheck); + + this.addEventListener("afterrename", $afterRenameMode); //what does this do? + + this.multicheck = value == "check"; //radio is single + this.$mode = this.multicheck ? 1 : 2; + } + else { + //@todo undo actionRules setting + this.removeEventListener("afterrename", $afterRenameMode); + //@todo unimplement?? + this.$mode = 0; + } + }; + + //@todo apf3.0 retest this completely + function $afterRenameMode(){ + } + + + + /**** Keyboard support ****/ + + + + //Handler for a plane list + this.$keyHandler = function(e){ + var key = e.keyCode, + ctrlKey = e.ctrlKey, + shiftKey = e.shiftKey, + selHtml = this.$caret || this.$selected; + + if (e.returnValue == -1 || !selHtml || this.renaming) //@todo how about allowdeselect? + return; + + var selXml = this.caret || this.selected, + oExt = this.$ext, + // variables used in the switch statement below: + node, margin, items, lines, hasScroll, hasScrollX, hasScrollY; + + switch (key) { + case 13: + if (this.$tempsel) + this.$selectTemp(); + + if (this.ctrlselect == "enter") + this.select(this.caret, true); + + this.choose(this.selected); + break; + case 32: + if (ctrlKey || !this.isSelected(this.caret)) + this.select(this.caret, ctrlKey); + break; + case 109: + case 46: + //DELETE + if (this.disableremove) + return; + + if (this.$tempsel) + this.$selectTemp(); + + this.remove(); + break; + case 36: + //HOME + if (this.hasFeature(apf.__VIRTUALVIEWPORT__)) + this.viewport.change(0, null, true, true); + + this.select(this.getFirstTraverseNode(), false, shiftKey); + this.$container.scrollTop = 0; + break; + case 35: + //END + if (this.hasFeature(apf.__VIRTUALVIEWPORT__)) + this.viewport.change(this.viewport.length, null, true, true); + + this.select(this.getLastTraverseNode(), false, shiftKey); + this.$container.scrollTop = this.$container.scrollHeight; + break; + case 107: + //+ + if (this.more) + this.startMore(); + break; + case 37: + //LEFT + if (!selXml && !this.$tempsel) + return; + + node = this.$tempsel + ? apf.xmldb.getNode(this.$tempsel) + : selXml; + margin = apf.getBox(apf.getStyle(selHtml, "margin")); + items = selHtml.offsetWidth + ? Math.floor((oExt.offsetWidth + - (hasScroll ? 15 : 0)) / (selHtml.offsetWidth + + margin[1] + margin[3])) + : 1; + + //margin = apf.getBox(apf.getStyle(selHtml, "margin")); + + node = this.getNextTraverseSelected(node, false); + if (node) + this.$setTempSelected(node, ctrlKey, shiftKey, true); + else + return; + + selHtml = apf.xmldb.findHtmlNode(node, this); + if (selHtml.offsetTop < oExt.scrollTop) { + oExt.scrollTop = Array.prototype.indexOf.call(this.getTraverseNodes(), node) < items + ? 0 + : selHtml.offsetTop - margin[0]; + } + break; + case 38: + //UP + if (!selXml && !this.$tempsel) + return; + + node = this.$tempsel + ? apf.xmldb.getNode(this.$tempsel) + : selXml; + + margin = apf.getBox(apf.getStyle(selHtml, "margin")); + hasScroll = oExt.scrollHeight > oExt.offsetHeight; + items = selHtml.offsetWidth + ? Math.floor((oExt.offsetWidth + - (hasScroll ? 15 : 0)) / (selHtml.offsetWidth + + margin[1] + margin[3])) + : 1; + + node = this.getNextTraverseSelected(node, false, items); + if (node) + this.$setTempSelected (node, ctrlKey, shiftKey, true); + else + return; + + selHtml = apf.xmldb.findHtmlNode(node, this); + if (selHtml.offsetTop < oExt.scrollTop) { + oExt.scrollTop = Array.prototype.indexOf.call(this.getTraverseNodes(), node) < items + ? 0 + : selHtml.offsetTop - margin[0]; + } + break; + case 39: + //RIGHT + if (!selXml && !this.$tempsel) + return; + + node = this.$tempsel + ? apf.xmldb.getNode(this.$tempsel) + : selXml; + margin = apf.getBox(apf.getStyle(selHtml, "margin")); + node = this.getNextTraverseSelected(node, true); + if (node) + this.$setTempSelected (node, ctrlKey, shiftKey); + else + return; + + selHtml = apf.xmldb.findHtmlNode(node, this); + if (selHtml.offsetTop + selHtml.offsetHeight + > oExt.scrollTop + oExt.offsetHeight) { + oExt.scrollTop = selHtml.offsetTop + - oExt.offsetHeight + selHtml.offsetHeight + + margin[0]; + } + break; + case 40: + //DOWN + if (!selXml && !this.$tempsel) + return; + + node = this.$tempsel + ? apf.xmldb.getNode(this.$tempsel) + : selXml; + + margin = apf.getBox(apf.getStyle(selHtml, "margin")); + hasScroll = oExt.scrollHeight > oExt.offsetHeight; + items = selHtml.offsetWidth + ? Math.floor((oExt.offsetWidth + - (hasScroll ? 15 : 0)) / (selHtml.offsetWidth + + margin[1] + margin[3])) + : 1; + + node = this.getNextTraverseSelected(node, true, items); + if (node) + this.$setTempSelected (node, ctrlKey, shiftKey); + else + return; + + selHtml = apf.xmldb.findHtmlNode(node, this); + if (selHtml.offsetTop + selHtml.offsetHeight + > oExt.scrollTop + oExt.offsetHeight) { // - (hasScroll ? 10 : 0) + oExt.scrollTop = selHtml.offsetTop + - oExt.offsetHeight + selHtml.offsetHeight + + margin[0]; //+ (hasScroll ? 10 : 0) + } + break; + case 33: + //PGUP + if (!selXml && !this.$tempsel) + return; + + node = this.$tempsel + ? apf.xmldb.getNode(this.$tempsel) + : selXml; + + margin = apf.getBox(apf.getStyle(selHtml, "margin")); + hasScrollY = oExt.scrollHeight > oExt.offsetHeight; + hasScrollX = oExt.scrollWidth > oExt.offsetWidth; + items = Math.floor((oExt.offsetWidth + - (hasScrollY ? 15 : 0)) / (selHtml.offsetWidth + + margin[1] + margin[3])); + lines = Math.floor((oExt.offsetHeight + - (hasScrollX ? 15 : 0)) / (selHtml.offsetHeight + + margin[0] + margin[2])); + + node = this.getNextTraverseSelected(node, false, items * lines); + if (!node) + node = this.getFirstTraverseNode(); + if (node) + this.$setTempSelected (node, ctrlKey, shiftKey, true); + else + return; + + selHtml = apf.xmldb.findHtmlNode(node, this); + if (selHtml.offsetTop < oExt.scrollTop) { + oExt.scrollTop = Array.prototype.indexOf.call(this.getTraverseNodes(), node) < items + ? 0 + : selHtml.offsetTop - margin[0]; + } + break; + case 34: + //PGDN + if (!selXml && !this.$tempsel) + return; + + node = this.$tempsel + ? apf.xmldb.getNode(this.$tempsel) + : selXml; + + margin = apf.getBox(apf.getStyle(selHtml, "margin")); + hasScrollY = oExt.scrollHeight > oExt.offsetHeight; + hasScrollX = oExt.scrollWidth > oExt.offsetWidth; + items = Math.floor((oExt.offsetWidth - (hasScrollY ? 15 : 0)) + / (selHtml.offsetWidth + margin[1] + margin[3])); + lines = Math.floor((oExt.offsetHeight - (hasScrollX ? 15 : 0)) + / (selHtml.offsetHeight + margin[0] + margin[2])); + + node = this.getNextTraverseSelected(selXml, true, items * lines); + if (!node) + node = this.getLastTraverseNode(); + if (node) + this.$setTempSelected (node, ctrlKey, shiftKey); + else + return; + + selHtml = apf.xmldb.findHtmlNode(node, this); + if (selHtml.offsetTop + selHtml.offsetHeight + > oExt.scrollTop + oExt.offsetHeight) { // - (hasScrollY ? 10 : 0) + oExt.scrollTop = selHtml.offsetTop + - oExt.offsetHeight + selHtml.offsetHeight + + margin[0]; //+ 10 + (hasScrollY ? 10 : 0) + } + break; + + default: + if (key == 65 && ctrlKey) { + this.selectAll(); + } + else if (this.$hasBindRule("caption")) { + if (!this.xmlRoot || this.autorename) return; + + //this should move to a onkeypress based function + if (!this.lookup || new Date().getTime() + - this.lookup.date.getTime() > 300) { + this.lookup = { + str : "", + date : new Date() + }; + } + + this.lookup.str += String.fromCharCode(key); + + var nodes = this.getTraverseNodes(); //@todo start at current indicator + for (var v, i = 0; i < nodes.length; i++) { + v = this.$applyBindRule("caption", nodes[i]); + if (v && v.substr(0, this.lookup.str.length) + .toUpperCase() == this.lookup.str) { + + if (!this.isSelected(nodes[i])) { + this.select(nodes[i]); + } + + if (selHtml) { + this.$container.scrollTop = selHtml.offsetTop + - (this.$container.offsetHeight + - selHtml.offsetHeight) / 2; + } + return; + } + } + return; + } + break; + } + + this.lookup = null; + return false; + }; + + + + /**** Private databinding functions ****/ + + this.$deInitNode = function(xmlNode, htmlNode){ + if (!htmlNode) return; + + //Remove htmlNodes from tree + htmlNode.parentNode.removeChild(htmlNode); + }; + + this.$updateNode = function(xmlNode, htmlNode, noModifier){ + //Update Identity (Look) + var elIcon = this.$getLayoutNode("item", "icon", htmlNode); + + if (elIcon) { + if (elIcon.nodeType == 1) { + elIcon.style.backgroundImage = "url(" + + apf.getAbsolutePath(this.iconPath, + this.$applyBindRule("icon", xmlNode)) + ")"; + } + else { + elIcon.nodeValue = apf.getAbsolutePath(this.iconPath, + this.$applyBindRule("icon", xmlNode)); + } + } + else { + //.style.backgroundImage = "url(" + this.$applyBindRule("image", xmlNode) + ")"; + var elImage = this.$getLayoutNode("item", "image", htmlNode); + if (elImage) { + if (elImage.nodeType == 1) { + elImage.style.backgroundImage = "url(" + + apf.getAbsolutePath(apf.hostPath, + this.$applyBindRule("image", xmlNode)) + ")"; + } + else { + elImage.nodeValue = apf.getAbsolutePath(apf.hostPath, + this.$applyBindRule("image", xmlNode)); + } + } + } + + var elCaption = this.$getLayoutNode("item", "caption", htmlNode); + if (elCaption) { + if (elCaption.nodeType == 1) { + + if (!this.$cbindings.caption || !this.$cbindings.caption.hasAml) + + elCaption.innerHTML = this.$applyBindRule("caption", xmlNode); + } + else + elCaption.nodeValue = this.$applyBindRule("caption", xmlNode); + } + + + //@todo + + + htmlNode.title = this.$applyBindRule("title", xmlNode) || ""; + + + var cssClass = this.$applyBindRule("css", xmlNode); + + if (cssClass || this.$dynCssClasses.length) { + this.$setStyleClass(htmlNode, cssClass, this.$dynCssClasses); + if (cssClass && !this.$dynCssClasses.contains(cssClass)) { + this.$dynCssClasses.push(cssClass); + } + } + + + if (!noModifier && this.$updateModifier) + this.$updateModifier(xmlNode, htmlNode); + }; + + this.$moveNode = function(xmlNode, htmlNode){ + if (!htmlNode) return; + + var oPHtmlNode = htmlNode.parentNode; + var nNode = this.getNextTraverse(xmlNode); //@todo could optimize because getTraverseNodes returns array indexOf + var beforeNode = nNode + ? apf.xmldb.findHtmlNode(nNode, this) + : null; + + oPHtmlNode.insertBefore(htmlNode, beforeNode); + //if(this.emptyMessage && !oPHtmlNode.childNodes.length) this.setEmpty(oPHtmlNode); + }; + + this.$add = function(xmlNode, Lid, xmlParentNode, htmlParentNode, beforeNode){ + //Build Row + this.$getNewContext("item"); + var oItem = this.$getLayoutNode("item"), + elSelect = this.$getLayoutNode("item", "select"), + elIcon = this.$getLayoutNode("item", "icon"), + elImage = this.$getLayoutNode("item", "image"), + //elCheckbox = this.$getLayoutNode("item", "checkbox"), // NOT USED + elCaption = this.$getLayoutNode("item", "caption"); + + oItem.setAttribute("id", Lid); + + elSelect.setAttribute("onmouseover", "var o = apf.lookup(" + this.$uniqueId + + "); o.$setStyleClass(this, 'hover', null, true);"); + elSelect.setAttribute("onselectstart", "return false;"); + elSelect.setAttribute("style", (elSelect.getAttribute("style") || "") + + ";user-select:none;-moz-user-select:none;-webkit-user-select:none;"); + + if (this.hasFeature(apf.__RENAME__) || this.hasFeature(apf.__DRAGDROP__)) { + elSelect.setAttribute("ondblclick", "var o = apf.lookup(" + this.$uniqueId + "); " + + + "o.stopRename();" + + + " o.choose()"); + elSelect.setAttribute("onmouseout", "var o = apf.lookup(" + this.$uniqueId + ");\ + o.$setStyleClass(this, '', ['hover'], true);\ + this.hasPassedDown = false;"); + elSelect.setAttribute(this.itemSelectEvent || "onmousedown", + 'var o = apf.lookup(' + this.$uniqueId + ');\ + var xmlNode = apf.xmldb.findXmlNode(this);\ + var isSelected = o.isSelected(xmlNode);\ + this.hasPassedDown = true;\ + if (!o.renaming && o.hasFocus() && isSelected == 1) \ + this.dorename = true;\ + if (!o.hasFeature(apf.__DRAGDROP__) || !isSelected && !event.ctrlKey)\ + o.select(this, event.ctrlKey, event.shiftKey, -1)'); + elSelect.setAttribute("onmouseup", 'if (!this.hasPassedDown) return;\ + var o = apf.lookup(' + this.$uniqueId + ');' + + + 'if (o.hasFeature(apf.__RENAME__) && this.dorename)\ + o.startDelayedRename(event, null, true);' + + + 'this.dorename = false;\ + var xmlNode = apf.xmldb.findXmlNode(this);\ + var isSelected = o.isSelected(xmlNode);\ + if (o.hasFeature(apf.__DRAGDROP__))\ + o.select(this, event.ctrlKey, event.shiftKey, -1)'); + } //@todo add DRAGDROP ifdefs + else { + elSelect.setAttribute("onmouseout", "apf.setStyleClass(this, '', ['hover']);"); + elSelect.setAttribute("ondblclick", 'var o = apf.lookup(' + + this.$uniqueId + '); o.choose(null, true)'); + elSelect.setAttribute(this.itemSelectEvent + || "onmousedown", 'var o = apf.lookup(' + this.$uniqueId + + '); o.select(this, event.ctrlKey, event.shiftKey, -1)'); + } + + + if (this.$listGrid) { + oItem.setAttribute("onmouseover", + oItem.getAttribute("onmouseover") + 'var o = apf.lookup(' + this.$uniqueId + ');o.$selectSeries(event);'); + } + + + + if (this.$mode) { + var elCheck = this.$getLayoutNode("item", "check"); + if (elCheck) { + elCheck.setAttribute("onmousedown", + "var o = apf.lookup(" + this.$uniqueId + ");\ + o.checkToggle(this, true);\o.$skipSelect = true;"); + + if (apf.isTrue(this.$applyBindRule("checked", xmlNode))) { + this.$checkedList.push(xmlNode); + this.$setStyleClass(oItem, "checked"); + } + else if (this.isChecked(xmlNode)) + this.$setStyleClass(oItem, "checked"); + } + else { + + throw new Error(apf.formatErrorString(0, this, + "Could not find check attribute", + 'Maybe the attribute check is missing from your skin file:\ + \ +
    \ +
    \ + \ + ')); + + return false; + } + } + + + //Setup Nodes Identity (Look) + if (elIcon) { + if (elIcon.nodeType == 1) { + elIcon.setAttribute("style", "background-image:url(" + + apf.getAbsolutePath(this.iconPath, this.$applyBindRule("icon", xmlNode)) + + ")"); + } + else { + elIcon.nodeValue = apf.getAbsolutePath(this.iconPath, + this.$applyBindRule("icon", xmlNode)); + } + } + else if (elImage) { + if (elImage.nodeType == 1) { + if ((elImage.tagName || "").toLowerCase() == "img") { + elImage.setAttribute("src", apf.getAbsolutePath(apf.hostPath, this.$applyBindRule("image", xmlNode))); + } + else { + elImage.setAttribute("style", "background-image:url(" + + apf.getAbsolutePath(apf.hostPath, this.$applyBindRule("image", xmlNode)) + + ")"); + } + } + else { + if (apf.isSafariOld) { //@todo this should be changed... blrgh.. + var p = elImage.ownerElement.parentNode, + img = p.appendChild(p.ownerDocument.createElement("img")); + img.setAttribute("src", + apf.getAbsolutePath(apf.hostPath, this.$applyBindRule("image", xmlNode))); + } + else { + elImage.nodeValue = + apf.getAbsolutePath(apf.hostPath, this.$applyBindRule("image", xmlNode)); + } + } + } + + if (elCaption) { + + if (elCaption.nodeType == 1 + && this.$cbindings.caption && this.$cbindings.caption.hasAml){ + var q = (this.$amlBindQueue || (this.$amlBindQueue = {})); + + if (elCaption == oItem) { + apf.setNodeValue(elCaption, ""); + var span = elCaption.appendChild(elCaption.ownerDocument.createElement("span")); + if (apf.isIE) + span.appendChild(elCaption.ownerDocument.createTextNode(" ")); + span.setAttribute("id", "placeholder_" + this.$uniqueId + + "_" + ((q.caption || (q.caption = [])).push(xmlNode) - 1)); + } + else { + elCaption.setAttribute("id", "placeholder_" + this.$uniqueId + + "_" + ((q.caption || (q.caption = [])).push(xmlNode) - 1)); + apf.setNodeValue(elCaption, ""); + } + } + else + + { + apf.setNodeValue(elCaption, + this.$applyBindRule("caption", xmlNode)); + } + } + oItem.setAttribute("title", this.$applyBindRule("tooltip", xmlNode) || ""); + + + var cssClass = this.$applyBindRule("css", xmlNode); + if (cssClass) { + this.$setStyleClass(oItem, cssClass); + if (cssClass) + this.$dynCssClasses.push(cssClass); + } + + + if (this.$addModifier && + this.$addModifier(xmlNode, oItem, htmlParentNode, beforeNode) === false) + return; + + if (htmlParentNode) + apf.insertHtmlNode(oItem, htmlParentNode, beforeNode); + else + this.listNodes.push(oItem); + }; + + this.addEventListener("$skinchange", function(e){ + if (this.more) + delete this.moreItem; + }); + + this.$fill = function(){ + if (this.more && !this.moreItem) { + this.$getNewContext("item"); + var Item = this.$getLayoutNode("item"), + elCaption = this.$getLayoutNode("item", "caption"), + elSelect = this.$getLayoutNode("item", "select"); + + Item.setAttribute("class", this.$baseCSSname + "More"); + elSelect.setAttribute("onmousedown", 'var o = apf.lookup(' + this.$uniqueId + + ');o.clearSelection();o.$setStyleClass(this, "more_down", null, true);'); + elSelect.setAttribute("onmouseout", 'apf.lookup(' + this.$uniqueId + + ').$setStyleClass(this, "", ["more_down"], true);'); + elSelect.setAttribute("onmouseup", 'apf.lookup(' + this.$uniqueId + + ').startMore(this, true)'); + + if (elCaption) + apf.setNodeValue(elCaption, + this.more.match(/caption:(.*)(;|$)/i)[1]); + this.listNodes.push(Item); + } + + apf.insertHtmlNodes(this.listNodes, this.$container); + this.listNodes.length = 0; + + if (this.more && !this.moreItem) { + this.moreItem = this.$container.lastChild; + } + + }; + + /** + * Adds a new item to the list and lets the users type in the new name. + * This functionality is especially useful in the interface when + * {@link element.list.attribute.mode} is set to check or radio. For instance in a form. + * @see element.list.attribute.more + */ + this.startMore = function(o, userAction){ + if (userAction && this.disabled) + return; + + this.$setStyleClass(o, "", ["more_down"]); + + var xmlNode; + if (!this.$actions["add"]) { + if (this.each && !this.each.match(/[\/\[]/)) { + xmlNode = "<" + this.each + (this.each.match(/^a:/) + ? " xmlns:a='" + apf.ns.aml + "'" + : "") + " custom='1' />"; + } + else { + + apf.console.warn("No add action rule is defined for element while more='true'."); + /*throw new Error(apf.formatErrorString(0, this, + "Could not start more", + "No add action rule is defined for this component", + this.$aml));*/ + + //return false; + xmlNode = ""; + } + } + + this.add(xmlNode, null, null, function(addedNode){ + this.select(addedNode, null, null, null, null, true); + if (this.morePos == "begin") + this.$container.insertBefore(this.moreItem, this.$container.firstChild); + else + this.$container.appendChild(this.moreItem); + + var undoLastAction = function(){ + this.getActionTracker().undo(this.autoselect ? 2 : 1); + + this.removeEventListener("stoprename", undoLastAction); + this.removeEventListener("beforerename", removeSetRenameEvent); + this.removeEventListener("afterrename", afterRename); + } + var afterRename = function(){ + //this.select(addedNode); + this.removeEventListener("afterrename", afterRename); + }; + var removeSetRenameEvent = function(e){ + this.removeEventListener("stoprename", undoLastAction); + this.removeEventListener("beforerename", removeSetRenameEvent); + + //There is already a choice with the same value + var xmlNode = this.findXmlNodeByValue(e.args[1]); + if (xmlNode || !e.args[1]) { + if (e.args[1] && this.dispatchEvent("notunique", { + value : e.args[1] + }) === false) { + this.startRename(); + + this.addEventListener("stoprename", undoLastAction); + this.addEventListener("beforerename", removeSetRenameEvent); + } + else { + this.removeEventListener("afterrename", afterRename); + + this.getActionTracker().undo();//this.autoselect ? 2 : 1); + if (!this.isSelected(xmlNode)) + this.select(xmlNode); + } + + return false; + } + }; + + this.addEventListener("stoprename", undoLastAction); + this.addEventListener("beforerename", removeSetRenameEvent); + this.addEventListener("afterrename", afterRename); + + + this.startDelayedRename({}, 1); + + }); + }; + + /**** Selection ****/ + + this.$calcSelectRange = function(xmlStartNode, xmlEndNode){ + var r = [], + nodes = this.hasFeature(apf.__VIRTUALVIEWPORT__) + ? this.xmlRoot.selectNodes(this.each) + : this.getTraverseNodes(), + f, i; + for (f = false, i = 0; i < nodes.length; i++) { + if (nodes[i] == xmlStartNode) + f = true; + if (f) + r.push(nodes[i]); + if (nodes[i] == xmlEndNode) + f = false; + } + + if (!r.length || f) { + r = []; + for (f = false, i = nodes.length - 1; i >= 0; i--) { + if (nodes[i] == xmlStartNode) + f = true; + if (f) + r.push(nodes[i]); + if (nodes[i] == xmlEndNode) + f = false; + } + } + + return r; + }; + + this.$selectDefault = function(XMLRoot){ + this.select(this.getTraverseNodes()[0], null, null, null, true); + }; + + /** + * Generates a list of items based on a string. + * @param {String} str the description of the items. Items are seperated by a comma (,). Ranges are specified by a start and end value seperated by a dash (-). + * Example: + * This example loads a list with items starting at 1980 and ending at 2050. + * + * lst.loadFillData("1980-2050"); + * lst.loadFillData("red,green,blue,white"); + * lst.loadFillData("None,100-110,1000-1100"); + * lst.loadFillData("1-10"); // 1 2 3 4 etc + * lst.loadFillData("01-10"); //01, 02, 03, 04, etc + * + */ + this.loadFillData = function(str){ + var len, start, end, parts = str.splitSafe(","), data = []; + + for (var p, part, i = 0; i < parts.length; i++) { + if ((part = parts[i]).match(/^\d+-\d+$/)) { + p = part.split("-"); + start = parseInt(p[0]); + end = parseInt(p[1]); + + if (p[0].length == p[1].length) { + len = Math.max(p[0].length, p[1].length); + for (var j = start; j < end + 1; j++) { + data.push("" + (j + "").pad(len, "0") + ""); + } + } + else { + for (var j = start; j < end + 1; j++) { + data.push("" + j + ""); + } + } + } + else { + data.push("" + part + ""); + } + } + + //@todo this is all an ugly hack (copied from item.js line 486) + //this.$preventDataLoad = true;//@todo apf3.0 add remove for this + + this.$initingModel = true; + + this.each = "item"; + this.$setDynamicProperty("caption", "[label/text()|@caption|text()]"); + this.$setDynamicProperty("eachvalue", "[value/text()|@value|text()]"); + this.$canLoadDataAttr = false; + + this.load("" + data.join("") + ""); + }; + +}).call(apf.BaseList.prototype = new apf.MultiSelect()); + + + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/core/baseclasses/basesimple.js)SIZE(1729)TIME(Mon, 10 Jan 2011 10:53:11 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + +/** + * Baseclass of a simple element. This are usually displaying elements + * (i.e. {@link element.label}, {@link element.picture}) + * + * @constructor + * @baseclass + * + * @inherits apf.StandardBinding + * @inherits apf.DataAction + * + * @author Ruben Daniels (ruben AT ajax DOT org) + * @version %I%, %G% + * @since 0.8 + */ +apf.BaseSimple = function(){ + this.$init(true); +}; + +(function() { + + this.implement(apf.DataAction); + + + this.getValue = function(){ + return this.value; + }; + +}).call(apf.BaseSimple.prototype = new apf.StandardBinding()); + + + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/core/baseclasses/basestatebuttons.js)SIZE(27242)TIME(Mon, 10 Jan 2011 10:53:11 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + +/** + * @constructor + * @baseclass + * + * @author Ruben Daniels (ruben AT ajax DOT org) + * @version %I%, %G% + * @since 0.8 + */ +apf.BaseStateButtons = function(){ + this.state = "normal"; + this.edit = false; + + var actions = { + "min" : ["minimized", "minimize", "restore"], + "max" : ["maximized", "maximize", "restore"], + "edit" : ["edit", "edit", "closeedit"], + "close" : ["closed", "close", "show"] + }; + this.$lastheight = null; + this.$lastpos = null; + + this.$lastState = {"normal":true}; + this.$booleanProperties["animate"] = true; + this.$supportedProperties.push("buttons", "animate", "state"); + + /** + * Close the window. It can be reopened by using {@link baseclass.guielement.method.show} + * Call-chaining is supported. + * @todo show should unset closed + */ + this.close = function(){ + this.setProperty("state", this.state.split("|") + .pushUnique("closed").join("|"), false, true); + return this; + }; + + /** + * Minimize the window. The window will become the height of the title of + * the window. + * Call-chaining is supported. + */ + this.minimize = function(){ + this.setProperty("state", this.state.split("|") + .remove("maximized") + .remove("normal") + .pushUnique("minimized").join("|"), false, true); + return this; + }; + + /** + * Maximize the window. The window will become the width and height of the + * browser window. + * Call-chaining is supported. + */ + this.maximize = function(){ + this.setProperty("state", this.state.split("|") + .remove("minimized") + .remove("normal") + .pushUnique("maximized").join("|"), false, true); + return this; + }; + + /** + * Restore the size of the window. The window will become the width and + * height it had before it was minimized or maximized. + * Call-chaining is supported. + */ + this.restore = function(){ + this.setProperty("state", this.state.split("|") + .remove("minimized") + .remove("maximized") + .pushUnique("normal").join("|"), false, true); + return this; + }; + + /** + * Set the window into edit state. The configuration panel is shown. + * Call-chaining is supported. + */ + this.edit = function(value){ + this.setProperty("state", this.state.split("|") + .pushUnique("edit").join("|"), false, true); + return this; + }; + + /** + * Removes the edit state of this window. The configuration panel is hidden. + * Call-chaining is supported. + */ + this.closeedit = function(value){ + this.setProperty("state", this.state.split("|") + .remove("edit").join("|"), false, true); + return this; + }; + + this.$toggle = function(type){ + var c = actions[type][0]; + this[actions[type][this.state.indexOf(c) > -1 ? 2 : 1]](); + }; + + this.$propHandlers["refparent"] = function(value){ + if (typeof value == "string") + this.$refParent = self[value] && self[value].$ext || document.getElementById(value); + else this.$refParent = value; + } + + this.$propHandlers["maxconf"] = function(value){ + this.$maxconf = value.splitSafe(","); + } + + /** + * @attribute {String} state the state of the window. The state can be a + * combination of multiple states seperated by a pipe '|' character. + * Possible values: + * normal The window has it's normal size and position. Default value. + * minimized The window is minimized. + * maximized The window is maximized. + * edit The window is in the edit state. + * closed The window is closed. + */ + this.$propHandlers["state"] = function(value, prop, force, reenter, noanim){ + var _self = this; + if (!this.$amlLoaded) { //@todo I still think this is weird and should not be needed + apf.queue.add("state" + this.$uniqueId, function(){ + _self.$propHandlers["state"].call(_self, value, prop, force, reenter, noanim); + }); + return; + } + + if (value == 0) + value = "normal"; + + var i, pNode, position, l, t, + o = {}, + s = value.split("|"), + lastState = this.$lastState, + styleClass = []; + + for (i = 0; i < s.length; i++) + o[s[i]] = true; + o.value = value; + + if (!o.maximized && !o.minimized) + o.normal = true; + + if (!reenter && this.dispatchEvent("beforestatechange", { + from : lastState, + to : o}) === false) { + this.state = lastState.value; + return false; + } + + //Closed state + if (o.closed == this.visible) {//change detected + this.setProperty("visible", !o["closed"], false, true); + //@todo difference is, we're not clearing the other states, check the docking example + } + + //Restore state + if (o.normal != lastState.normal + || !o.normal && (o.minimized != lastState.minimized + || o.maximized != lastState.maximized)) { + + if (this.$lastheight) // this.aData && this.aData.hidden == 3 ?? + this.$ext.style.height = this.$lastheight;//(this.$lastheight - apf.getHeightDiff(this.$ext)) + "px"; + + if (this.$lastpos) { + apf.plane.hide(this.$uniqueId); + + if (this.animate && !noanim) { + //Pre remove paused event because of not having onresize + //if (apf.hasSingleRszEvent) + //delete apf.layout.onresize[apf.layout.getHtmlId(this.$pHtmlNode)]; + + var htmlNode = this.$ext; + position = apf.getStyle(htmlNode, "position"); + if (position != "absolute") { + l = parseInt(apf.getStyle(htmlNode, "left")) || 0; + t = parseInt(apf.getStyle(htmlNode, "top")) || 0; + } + else { + l = htmlNode.offsetLeft; + t = htmlNode.offsetTop; + } + + this.animstate = 1; + apf.tween.multi(htmlNode, { + steps : 15, + anim : apf.tween.easeInOutCubic, + interval : 10, + tweens : [ + {type: "left", from: l, to: this.$lastpos.px[0]}, + {type: "top", from: t, to: this.$lastpos.px[1]}, + {type: "width", from: this.$ext.offsetWidth, + to: this.$lastpos.px[2]}, + {type: "height", from: this.$ext.offsetHeight, + to: this.$lastpos.px[3]} + ], + oneach : function(){ + + if (apf.hasSingleRszEvent) + apf.layout.forceResize(_self.$int); + + }, + onfinish : function(){ + _self.$lastpos.parentNode.insertBefore(_self.$ext, _self.$lastpos.beforeNode); + + if (_self.$placeHolder) + _self.$placeHolder.parentNode.removeChild(_self.$placeHolder); + + _self.$propHandlers["state"].call(_self, value, null, + null, true, true); + } + }); + + return; + } + else if (!this.animate) { + apf.plane.hide(this.$uniqueId, true); + + _self.$lastpos.parentNode.insertBefore(_self.$ext, _self.$lastpos.beforeNode); + + if (_self.$placeHolder) + _self.$placeHolder.parentNode.removeChild(_self.$placeHolder); + } + + this.$ext.style.position = this.$lastpos.pos; + this.$ext.style.left = this.$lastpos.css[0]; + this.$ext.style.top = this.$lastpos.css[1]; + this.$ext.style.width = this.$lastpos.css[2]; + this.$ext.style.height = this.$lastpos.css[3]; + + pNode = this.$lastpos.parentNode; + pNode.style.width = this.$lastpos.parent[0]; + pNode.style.height = this.$lastpos.parent[1]; + pNode.style.overflow = this.$lastpos.parent[2]; + } + + + if (this.aData && this.aData.restore) + this.aData.restore(); + + + + if (apf.layout) + apf.layout.play(this.$pHtmlNode); + + + this.$lastheight = this.$lastpos = null; + + if (o.normal) + styleClass.push("", + this.$baseCSSname + "Max", + this.$baseCSSname + "Min"); + } + + if (o.minimized != lastState.minimized) { + if (o.minimized) { + styleClass.unshift( + this.$baseCSSname + "Min", + this.$baseCSSname + "Max", + this.$baseCSSname + "Edit"); + + + if (this.aData && this.aData.minimize) + this.aData.minimize(this.collapsedHeight); + + + if (!this.aData || !this.aData.minimize) { + this.$lastheight = apf.getStyle(this.$ext, "height");//this.$ext.offsetHeight; + + this.$ext.style.height = Math.max(0, this.collapsedHeight + - apf.getHeightDiff(this.$ext)) + "px"; + } + + if (this.hasFocus()) + apf.window.moveNext(null, this, true); + //else if(apf.document.activeElement) + //apf.document.activeElement.$focus({mouse: true}); + } + else { + styleClass.push(this.$baseCSSname + "Min"); + + $setTimeout(function(){ + apf.window.$focusLast(_self); + }); + } + } + + if (o.maximized != lastState.maximized) { + if (o.maximized) { + styleClass.unshift( + this.$baseCSSname + "Max", + this.$baseCSSname + "Min", + this.$baseCSSname + "Edit"); + + pNode = this.$refParent; + if (!pNode) + pNode = (this.$ext.offsetParent == document.body + ? document.documentElement + : this.$ext.parentNode); + + this.animstate = 0; + var hasAnimated = false, htmlNode = this.$ext; + + var position = apf.getStyle(htmlNode, "position"); + if (position == "absolute") { + pNode.style.overflow = "hidden"; + l = htmlNode.offsetLeft; + t = htmlNode.offsetTop; + } + else { + var pos = apf.getAbsolutePosition(htmlNode); //pNode + l = pos[0];//parseInt(apf.getStyle(htmlNode, "left")) || 0; + t = pos[1];//parseInt(apf.getStyle(htmlNode, "top")) || 0; + } + + this.$lastpos = { + css : [this.$ext.style.left, this.$ext.style.top, + this.$ext.style.width, this.$ext.style.height, + this.$ext.style.margin, this.$ext.style.zIndex], + px : [l, t, this.$ext.offsetWidth, + this.$ext.offsetHeight], + parent : [pNode.style.width, pNode.style.height, + pNode.style.overflow], + pos : htmlNode.style.position, + parentNode : pNode, + beforeNode : this.$ext.nextSibling + }; + + if (this.parentNode.$layout) { + if (!this.$placeHolder) + this.$placeHolder = document.createElement("div"); + this.$placeHolder.style.position = this.$lastpos.pos; + this.$placeHolder.style.left = this.$lastpos.css[0]; + this.$placeHolder.style.top = this.$lastpos.css[1]; + this.$placeHolder.style.width = this.$lastpos.px[2] + "px"; + this.$placeHolder.style.height = this.$lastpos.px[3] + "px"; + this.$placeHolder.style.margin = this.$lastpos.css[4]; + this.$placeHolder.style.zIndex = this.$lastpos.css[5]; + this.$pHtmlNode.insertBefore(this.$placeHolder, this.$ext); + + htmlNode.style.position = "absolute"; + } + + document.body.appendChild(htmlNode); + htmlNode.style.left = l + "px"; + htmlNode.style.top = t + "px"; + + function setMax(){ + //While animating dont execute this function + if (_self.animstate) + return; + + var w, h, pos, box, pDiff; + if (_self.maxconf) { + w = _self.$maxconf[0]; + h = _self.$maxconf[1]; + + pos = [_self.$maxconf[2] == "center" + ? (apf.getWindowWidth() - w)/2 + : _self.$maxconf[2], + _self.$maxconf[3] == "center" + ? (apf.getWindowHeight() - h)/3 + : _self.$maxconf[3]]; + } + else { + w = !apf.isIE && pNode == document.documentElement + ? window.innerWidth + : pNode.offsetWidth, + h = !apf.isIE && pNode == document.documentElement + ? window.innerHeight + : pNode.offsetHeight; + } + + if (!pos) { + pos = pNode != htmlNode.offsetParent + ? apf.getAbsolutePosition(pNode, htmlNode.offsetParent) + : [0, 0]; + } + + if (position != "absolute") { + var diff = apf.getDiff(pNode); + w -= diff[0] + (!_self.$refParent && apf.isIE8 ? 4 : 0);//@todo dirty hack! + h -= diff[0] + (!_self.$refParent && apf.isIE8 ? 4 : 0);//@todo dirty hack! + } + //@todo dirty hack! + else if (!_self.$refParent && apf.isIE8) { + w -= 4; + h -= 4; + } + + box = _self.$refParent ? [0,0,0,0] : marginBox; + pDiff = apf.getDiff(pNode); + + pNode.style.width = (pNode.offsetWidth - pDiff[0]) + "px"; + pNode.style.height = (pNode.offsetHeight - pDiff[1]) + "px"; + + if (!hasAnimated && _self.$maxconf && _self.$maxconf[4]) + apf.plane.show(htmlNode, false, null, null, { + color : _self.$maxconf[4], + opacity : _self.$maxconf[5], + animate : _self.animate, + protect : _self.$uniqueId + }); + + if (_self.animate && !hasAnimated) { + _self.animstate = 1; + hasAnimated = true; + apf.tween.multi(htmlNode, { + steps : 15, + anim : apf.tween.easeInOutCubic, + interval : 10, + tweens : [ + {type: "left", from: l, to: pos[0] - box[3]}, + {type: "top", from: t, to: pos[1] - box[0]}, + {type: "width", from: _self.$lastpos.px[2], + to: (w + box[1] + box[3] - apf.getWidthDiff(_self.$ext))}, + {type: "height", from: _self.$lastpos.px[3], + to: (h + box[0] + box[2] - apf.getHeightDiff(_self.$ext))} + ], + oneach : function(){ + + if (apf.hasSingleRszEvent) + apf.layout.forceResize(_self.$int); + + }, + onfinish : function(){ + _self.animstate = 0; + + _self.dispatchEvent("afterstatechange", { + from : lastState, + to : o + }); + + + if (apf.hasSingleRszEvent) + apf.layout.forceResize(_self.$int); + + } + }); + } + else if (!_self.animstate) { + htmlNode.style.left = (pos[0] - box[3]) + "px"; + htmlNode.style.top = (pos[1] - box[0]) + "px"; + + var diff = apf.getDiff(_self.$ext); + htmlNode.style.width = (w + - diff[0] + box[1] + box[3]) + "px"; + htmlNode.style.height = (h + - diff[1] + box[0] + box[2]) + "px"; + } + } + + + if (apf.layout) + apf.layout.pause(this.$pHtmlNode, setMax); + + } + else { + styleClass.push(this.$baseCSSname + "Max"); + } + } + + if (o.edit != lastState.edit) { + if (o.edit) { + styleClass.unshift( + this.$baseCSSname + "Edit", + this.$baseCSSname + "Max", + this.$baseCSSname + "Min"); + + if (this.btnedit) + oButtons.edit.innerHTML = "close"; //hack + + this.dispatchEvent('editstart'); + } + else { + if (this.dispatchEvent('editstop') === false) + return false; + + styleClass.push(this.$baseCSSname + "Edit"); + if (styleClass.length == 1) + styleClass.unshift(""); + + if (this.btnedit) + oButtons.edit.innerHTML = "edit"; //hack + } + } + + if (styleClass.length || o.closed != lastState.closed) { + if (styleClass.length) + this.$setStyleClass(this.$ext, styleClass.shift(), styleClass); + + if (o.edit) { //@todo apf3.0 + this.dispatchEvent("prop.visible", {value:true}); + + if (_self.oSettings) + apf.layout.forceResize(_self.oSettings); + + } + + //@todo research why this is not symmetrical + if (!o.maximized || !this.animate || lastState.maximized && _self.animate) { + _self.dispatchEvent("afterstatechange", { + from : lastState, + to : o}); + } + + this.$lastState = o; + + + if (this.aData && !o.maximized) { //@todo is this the most optimal position? + this.$purgeAlignment(); + } + + + + if (!this.animate && apf.hasSingleRszEvent && apf.layout) + apf.layout.forceResize(_self.$int); + + } + }; + + var marginBox, hordiff, verdiff, oButtons = {} + /** + * @attribute {String} buttons the buttons that the window displays. This + * can be multiple values seperated by a pipe '|' character. + * Possible values: + * min The button that minimizes the window. + * max The button that maximizes the window. + * close The button that closes the window. + * edit The button that puts the window into the edit state. + */ + this.$propHandlers["buttons"] = function(value){ + + if (apf.isIphone) return; + + if (!this.$hasLayoutNode("button")) + return; + + var buttons = value && (value = value.replace(/(\|)\||\|$/, "$1")).split("|") || [], + nodes = this.$buttons.childNodes, + re = value && new RegExp("(" + value + ")"), + found = {}, + idleNodes = []; + + //Check if we can 'remove' buttons + for (var i = 0; i < nodes.length; i++) { + if (nodes[i].nodeType != 1 || nodes[i].tagName != "DIV") //@todo temp hack + continue; + + if (nodes[i].getAttribute("button") && (!value + || !nodes[i].className || !nodes[i].className.match(re))) { + nodes[i].style.display = "none"; + this.$setStyleClass(nodes[i], "", ["min", "max", "close", "edit"]); + idleNodes.push(nodes[i]); + } + else { + found[RegExp.$1] = nodes[i]; + } + } + + //Create new buttons if needed + for (i = 0; i < buttons.length; i++) { + if (!buttons[i]) + continue; + + if (found[buttons[i]]) { + this.$buttons.insertBefore(found[buttons[i]], this.$buttons.firstChild); + continue; + } + + var btn = idleNodes.pop(); + if (!btn) { + this.$getNewContext("button"); + btn = this.$getLayoutNode("button"); + btn.setAttribute("button", "button"); + setButtonEvents.call(this, btn); + btn = apf.insertHtmlNode(btn, this.$buttons); + } + + this.$setStyleClass(btn, buttons[i], ["min", "max", "close", "edit"]); + btn.onclick = new Function("apf.lookup(" + this.$uniqueId + ").$toggle('" + + buttons[i] + "')"); + btn.style.display = "block"; + oButtons[buttons[i]] = btn; + this.$buttons.insertBefore(btn, this.$buttons.firstChild); + } + + marginBox = apf.getBox(apf.getStyle(this.$ext, "borderWidth")); + }; + + function setButtonEvents(btn){ + //@todo can this cancelBubble just go? + //event.cancelBubble = true; \ + btn.setAttribute("onmousedown", + "var o = apf.all[" + this.$uniqueId + "];\ + o.$setStyleClass(this, 'down', null, true);\ + apf.cancelBubble(event, o); \ + var o = apf.findHost(this).$ext;\ + if (o.onmousedown) o.onmousedown(event);\ + apf.cancelBubble(event, o);\ + apf.window.$mousedown(event);"); + btn.setAttribute("onmouseup", + "var o = apf.all[" + this.$uniqueId + "];\ + o.$setStyleClass(this, '', ['down'], true);"); + btn.setAttribute("onmouseover", + "var o = apf.all[" + this.$uniqueId + "];\ + o.$setStyleClass(this, 'hover', null, true);"); + btn.setAttribute("onmouseout", + "var o = apf.all[" + this.$uniqueId + "];\ + o.$setStyleClass(this, '', ['hover', 'down'], true);"); + btn.setAttribute("ondblclick", "apf.stopPropagation(event);"); + } + + this.$initButtons = function(oExt){ + this.animate = apf.enableAnim; + + this.collapsedHeight = this.$getOption("Main", "collapsed-height"); + + var oButtons = this.$getLayoutNode("main", "buttons", oExt); + if (!oButtons || apf.isIphone || !this.getAttribute("buttons") + || !this.$hasLayoutNode("button")) + return; + + var len = (this.getAttribute("buttons") || "").split("|").length; + for (var btn, i = 0; i < len; i++) { + this.$getNewContext("button"); + btn = oButtons.appendChild(this.$getLayoutNode("button")); + btn.setAttribute("button", "button"); + setButtonEvents.call(this, btn); + } + }; + + this.addEventListener("DOMNodeRemovedFromDocument", function(e){ + for (var name in oButtons) { + oButtons[name].onclick = null; + } + }); +}; + + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/core/baseclasses/basetab.js)SIZE(56420)TIME(Mon, 10 Jan 2011 10:53:11 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + + +/** + * Baseclass of a paged element. + * + * @constructor + * @baseclass + * @allowchild page + * @author Ruben Daniels (ruben AT ajax DOT org) + * @version %I%, %G% + * @since 0.8 + * + * @event beforeswitch Fires before this element switches to another page. + * cancelable: Prevents the page to become active. + * object: + * {Mixed} previous the name or number of the current page. + * {Number} previousId the number of the current page. + * {apf.page} previousPage the current page. + * {Mixed} next the name or number of the page the will become active. + * {Number} nextId the number of the page the will become active. + * {apf.page} nextPage the page the will become active. + * @event afterswitch Fires after this element has switched to another page. + * object: + * {Mixed} previous the name or number of the previous page. + * {Number} previousId the number of the previous page. + * {apf.page} previousPage the previous page. + * {Mixed} next the name or number of the current page. + * {Number} nextId the number of the the current page. + * {apf.page} nextPage the the current page. + */ +apf.BaseTab = function(){ + this.$init(true); +}; + +(function() { + this.isPaged = true; + this.$focussable = apf.KEYBOARD; + this.length = 0; + this.isLoading = {}; + this.inited = + this.ready = false; + this.$scroll = true; + + /** + * Sets the current page of this element. + * @param {mixed} page the name of numer of the page which is made active. + * @param {Function} callback the function called after setting the page. Especially handy when using the src attribute. + */ + this.set = function(page, callback, noEvent){ + if (noEvent || this.src && !this.$findPage(page, {})) { + return this.$propHandlers["activepage"].call( + this, page, null, null, callback, noEvent); + } + + if (callback && this.activepage == page) + return callback(this.getPage(page)); + + this.$lastCallback = callback; + this.setProperty("activepage", page); + }; + + /**** Properties and Attributes ****/ + + this.$supportedProperties.push("activepage", "activepagenr", "length", + "src", "loading", "trans-in", "trans-out"); + + /** + * @attribute {Number} activepagenr the child number of the active page. + * Example: + * This example uses property binding to maintain consistency between a + * dropdown which is used as a menu, and a pages element + * + * + * Home + * General + * Advanced + * + * + * + * + *

    Home Page

    + *
    + * + *

    General Page

    + *
    + * + *

    Advanced Page

    + *
    + *
    + *
    + */ + this.$propHandlers["activepagenr"] = + + /** + * @attribute {String} activepage the name of the active page. + * Example: + * + * + * + * ... + * + * + * ... + * + * + * ... + * + * + * + */ + this.$propHandlers["activepage"] = function(next, prop, force, callback, noEvent){ + if (!this.inited || apf.isNot(next)) return; + + if (!callback) { + callback = this.$lastCallback; + delete this.$lastCallback; + } + + var page, info = {}; + page = this.$findPage(next, info); + + if (!page) { + if (this.src) { + if (this.isLoading[next]) + return; + + if (this.$findPage("loading", {})) + this.$propHandlers["activepage"].call(this, "loading"); + + this.setProperty("loading", true); + this.isLoading[next] = true; + + page = this.ownerDocument.createElementNS(apf.ns.apf, "page"); + page.setAttribute("id", next); + this.appendChild(page); + + var _self = this; + page.insertMarkup(this.src, { + page : next, + //@todo apf3.0 change callback arguments in xinclude + callback : function(options){ + delete _self.isLoading[next]; + + if (!options.xmlNode) { + var oError = new Error(apf.formatErrorString(0, null, + "Loading new page", "Could not load new page: " + + _self.src)); + + _self.setProperty("loading", false); + + if (this.dispatchEvent("error", apf.extend({ + error : oError, + bubbles : true + }, options)) === false) + return true; + + throw oError; + } + else { + //for success + _self.setProperty("activepage", next); + + //Needs to be after set + if (callback) + callback(options.amlNode); + + _self.setProperty("loading", false); + } + } + }); + return; + } + + + apf.console.warn("Setting tab page which doesn't exist, \ + referenced by name: '" + next + "'"); + + + return false; + } + + if (page.parentNode != this) { + + apf.console.warn("Setting active page on page component which \ + isn't a child of this tab component. Cancelling."); + + + return false; + } + + if (!page.visible || page.disabled) { + + apf.console.warn("Setting active page on page component which \ + is not visible or disabled. Cancelling."); + + + return false; + } + + //If page is given as first argument, let's use its position + if (next.tagName) { + next = info.position; + this.activepage = page.name || next;//page.type || + } + + //Call the onbeforeswitch event; + if (!noEvent) { + var oEvent = { + previous : this.activepage, + previousId : this.activepagenr, + previousPage : this.$activepage, + next : next, + nextId : info.position, + nextPage : page + }; + + if (this.dispatchEvent("beforeswitch", oEvent) === false) { + //Loader support + if (this.hideLoader) + this.hideLoader(); + + return false; + } + } + + //Maintain an activepagenr property (not reentrant) + this.activepagenr = info.position; + this.setProperty("activepagenr", info.position); + + //Deactivate the current page, if any, and activate the new one + if (this.$activepage) + this.$activepage.$deactivate(); + + page.$activate(); + + + if (page["trans-in"] || this.$activepage && this.$activepage["trans-out"]) + this.transition(page, page["trans-in"] || "normal", + this.$activepage, this.$activepage && this.$activepage["trans-out"] || "normal"); + + + this.$activepage = page; + + //this.scrollIntoView(page); + + + //Loader support + if (this.hideLoader) { + if (page.$rendered !== false) { + this.hideLoader(); + } + else { + //Delayed rendering support + page.addEventListener("afterrender", function(){ + this.parentNode.hideLoader(); + }); + } + } + + if (!noEvent) { + if (page.$rendered !== false) + this.dispatchEvent("afterswitch", oEvent); + else { + //Delayed rendering support + page.addEventListener("afterrender", function(){ + this.parentNode.dispatchEvent("afterswitch", oEvent); + }); + } + } + + if (typeof callback == "function") + callback(page); + + return true; + }; + + /** + * @attribute {String} buttons the modifier for tab page buttons, seperated by a | character + * Possible values: + * close the button has a close button inside it. + * scale the buttons are scaled to make room for more buttons. + * scroll when the buttons take too much space scroll buttons are displayed. + */ + this.$propHandlers["buttons"] = function(value){ + //this.buttons = value; + this.$scale = value.indexOf("scale") > -1; + this.$scroll = !this.$scale; + + + //@todo skin change + //@todo buttons on the side + if (this.$scale) { + this.$maxBtnWidth = parseInt(this.$getOption("button", "maxwidth")) || 150; + this.$minBtnWidth = parseInt(this.$getOption("button", "minwidth")) || 10; + this.$setStyleClass(this.$buttons, "scale"); + this.addEventListener("resize", scalersz); + + this.minwidth = this.$minBtnWidth * this.getPages().length + 10; + this.$ext.style.minWidth = Math.max(0, this.minwidth - apf.getWidthDiff(this.$ext)) + "px"; + } + else { + this.$setStyleClass(this.$buttons, "", ["scale"]); + this.removeEventListener("resize", scalersz); + } + + }; + + + var btnMoHandler; + this.$scaleinit = function(node, type, callback){ + var pg = this.getPages(); + var l = pg.length; + this.minwidth = this.$minBtnWidth * l + 10; //@todo padding + margin of button container + this.$ext.style.minWidth = Math.max(0, this.minwidth - apf.getWidthDiff(this.$ext)) + "px"; + + if (!type) + return scalersz.call(this); + + if (this.$control && this.$control.type != "remove" && this.$control.stop) + this.$control.stop(); + + var _self = this; + var anim = { + steps : type == "remove" ? 8 : 8, + control : this.$control = {}, + anim : apf.tween.EASEOUT, + interval : 10, + tweens : [], + oHtml : node, + onfinish : function(){ + if (!node) + return; + + if (type == "add") + node.dispatchEvent("afteropen"); + }, + onstop : function(){ + if (!node) + return; + + if (type == "add") + node.dispatchEvent("afteropen"); + else if (type == "remove") + node.dispatchEvent("afterclose"); + } + //oneach : function(){alert(1);} + }; + this.$control.type = type; + + if (type == "add") { + var htmlNode = node.$button; + htmlNode.style.width = this.$minBtnWidth + "px"; + if (pg.length) { + scalersz.call(this, null, node); + this.$buildScaleAnim(anim, pg, null, true); + } + } + else if (type == "sync") { + this.$buildScaleAnim(anim, pg); + } + else if (type == "remove") { + anim.onfinish = function(){ + callback(); + + if (_self.$waitForMouseOut == 2) { + apf.removeListener(_self.$buttons, "mouseout", btnMoHandler); + delete _self.$waitForMouseOut; + _self.$scaleinit(null, "sync"); + } + else if (isLast) + delete _self.$waitForMouseOut; + + node.dispatchEvent("afterclose"); + } + anim.onstop = function(){ + apf.setOpacity(html, 1); + } + + var html = node.$button; + anim.tweens.push({ + oHtml : html, + type : "width", + from : html.offsetWidth - apf.getWidthDiff(html), + to : 0 + }); + var over = apf.getWidthDiff(html) + this.$btnMargin; + if (over) + anim.tweens.push({ + oHtml : html, + type : "marginLeft", + from : 0, + to : -1 * over + }); + anim.tweens.push({ + oHtml : html, + type : "fade", + from : 1, + to : 0 + }); + + var isLast = pg[pg.length - 1] == node; + if (isLast) + this.$buildScaleAnim(anim, pg, node); + + //Set activetab if the current one is lost + if (this.$activepage == node) { + var ln = node.nextSibling; + while (ln && (!ln.$first || !ln.visible)) + ln = ln.nextSibling; + var rn = node.previousSibling; + while (rn && (!rn.$last || !rn.visible)) + rn = rn.previousSibling; + if (ln || rn) + this.set(ln || rn); + } + + this.$waitForMouseOut = true; + if (!isLast) { + if (!btnMoHandler) { + var _self = this; + function btnMoHandler(e){ + var pos = apf.getAbsolutePosition(this); + if (e.clientX <= pos[0] || e.clientY <= pos[1] + || e.clientX >= pos[0] + this.offsetWidth + || e.clientY >= pos[1] + this.offsetHeight) { + apf.removeListener(_self.$buttons, "mouseout", btnMoHandler); + if (_self.$control.state == apf.tween.STOPPED) { + delete _self.$waitForMouseOut; + _self.$scaleinit(null, "sync"); + } + else if (_self.$waitForMouseOut) + _self.$waitForMouseOut = 2; + } + } + } + + apf.addListener(_self.$buttons, "mouseout", btnMoHandler); + } + } + + if (anim.tweens.length) + apf.tween.multi(this, anim); + } + + this.$buildScaleAnim = function(anim, pg, excl, add){ + if (excl) { + pg = pg.slice(); + pg.remove(excl); + } + if (!pg.length) + return; + + var cw = this.$buttons.offsetWidth - apf.getWidthDiff(this.$buttons);//apf.getHtmlInnerWidth(this.$ext); + var l = pg.length; + var bw = Math.min(cw/l, this.$maxBtnWidth); + var re = Math.round((bw % 1) * 10); + for (var wd, html, s, i = 0; i < l - 1; i++) { + s = Math.max(this.$minBtnWidth, round[i < re ? 1 : 0](bw)); + cw -= s; + html = pg[i].$button, wd = apf.getWidthDiff(html); + anim.tweens.push({ + oHtml : html, + type : "width", + from : html.offsetWidth - wd, + to : s - wd - this.$btnMargin + }); + } + html = pg[l - 1].$button, wd = apf.getWidthDiff(html); + anim.tweens.push({ + oHtml : html, + type : "width", + from : html.offsetWidth - wd, // - (add ? 3 : 0) + to : Math.max(this.$minBtnWidth, + Math.min(cw, this.$maxBtnWidth)) - this.$btnMargin - wd + }); + } + + var round = [Math.floor, Math.ceil]; + function scalersz(e, excl){ + if (!this.length || this.$waitForMouseOut + || this.$control && this.$control.state == apf.tween.RUNNING) { + //@todo queue call here to after anim + return; + } + + if (this.$btnMargin == undefined) + this.$btnMargin = apf.getMargin(this.getPage().$button)[0]; + + var pg = this.getPages(); + if (excl) + pg.remove(excl); + if (!pg.length) + return; + + var cw = this.$buttons.offsetWidth - apf.getWidthDiff(this.$buttons) + - (excl ? excl.$button.offsetWidth + this.$btnMargin: 0);//apf.getHtmlInnerWidth(this.$ext); + var l = pg.length; + var bw = Math.min(cw/l, this.$maxBtnWidth); + var re = Math.round((bw % 1) * 10); + for (var s, i = 0; i < l - 1; i++) { + s = Math.max(this.$minBtnWidth, round[i < re ? 1 : 0](bw)); + cw -= s; + if (!pg[i].$button) continue; + pg[i].$button.style.width = (s - apf.getWidthDiff(pg[i].$button) - this.$btnMargin) + "px"; + } + if (!pg[l - 1].$button) return; + pg[l - 1].$button.style.width = (Math.max(this.$minBtnWidth, + Math.min(cw, this.$maxBtnWidth)) + - this.$btnMargin + - apf.getWidthDiff(pg[l - 1].$button)) + "px"; + } + + + /**** Public methods ****/ + + + this.transition = function(pageIn, animIn, pageOut, animOut){ + var _self = this; + + if (!this.$transInfo) { + this.$int.style.overflow = "hidden"; + + this.$transInfo = { + start : function(){ + var h = _self.$ext; + this.size = [h.style.width, h.style.height]; + var d = apf.getDiff(h); + h.style.width = (h.offsetWidth - d[0]) + "px"; + h.style.height = (h.offsetHeight - d[1]) + "px"; + + //@todo start anims + if (this["in"]) { + var h = this["in"].oHtml.$ext; + var d = apf.getDiff(h); + h.style.width = (_self.$int.offsetWidth - d[0]) + "px"; + h.style.height = (_self.$int.offsetHeight - d[1]) + "px"; + h.style.display = "block"; + apf.tween.multi(h, this["in"]); + } + if (this["out"]) { + var h = this["out"].oHtml.$ext; + var d = apf.getDiff(h); + h.style.width = (_self.$int.offsetWidth - d[0]) + "px"; + h.style.height = (_self.$int.offsetHeight - d[1]) + "px"; + h.style.display = "block"; + apf.tween.multi(h, this["out"]); + } + }, + + stop : function(){ + if (this["in"] && this["in"].control.stop) + this["in"].control.stop(); + if (this["out"] && this["out"].control.stop) + this["out"].control.stop(); + }, + + finish : function(){ + //@todo buffer calls with timeout + var h = _self.$ext; + h.style.width = this.size[0]; //@todo possibly anim to new size + h.style.height = this.size[1]; + + if (this["in"]) { + var h = this["in"].oHtml.$ext; + h.style.width = this["in"].size[0]; + h.style.height = this["in"].size[1]; + h.style.display = ""; + h.style.position = ""; + h.style.zIndex = ""; + h.style.left = ""; + h.style.top = ""; + apf.setOpacity(h, 1); + delete this["in"]; + } + if (this["out"]) { + var h = this["out"].oHtml.$ext; + h.style.width = this["out"].size[0]; + h.style.height = this["out"].size[1]; + h.style.display = ""; + h.style.position = ""; + h.style.zIndex = ""; + h.style.left = ""; + h.style.top = ""; + apf.setOpacity(h, 1); + delete this["out"]; + } + + _self.oPages.style.width = ""; + _self.oPages.style.height = ""; + } + }; + } + + //stop + this.$transInfo.stop(); + + var d = apf.getDiff(this.oPages); + this.oPages.style.width = (this.oPages.offsetWidth - d[0]) + "px"; + this.oPages.style.height = (this.oPages.offsetHeight - d[1]) + "px"; + + var preventNext = this.$createAnim(pageIn, animIn, false, pageOut); + if (preventNext !== false && pageOut) + this.$createAnim(pageOut, animOut, true, pageIn); + + setTimeout(function(){ + _self.$transInfo.start(); + }); + } + + this.$cube = { + "left" : [-1, "offsetWidth", "left", "getHtmlInnerWidth"], + "right" : [1, "offsetWidth", "left", "getHtmlInnerWidth"], + "top" : [-1, "offsetHeight", "top", "getHtmlInnerHeight"], + "bottom" : [1, "offsetHeight", "top", "getHtmlInnerHeight"] + } + + this.$createAnim = function(page, animType, out, pageOut){ + var _self = this; + + //create new anim + var anim = { + steps : apf.isIE ? 15 : 25, + control : {}, + anim : out ? apf.tween.EASEOUT : apf.tween.EASEOUT, + interval : 10, + tweens : [], + oHtml : page, + size : [page.$ext.style.width, page.$ext.style.height], + onfinish : function(){ + _self.$transInfo.finish(out); + } + }; + this.$transInfo[out ? "out" : "in"] = anim; + + var from, to, h = page.$ext; + h.style.zIndex = out ? 10 : 20; + h.style.position = "absolute"; + h.style.left = 0; + h.style.top = 0; + h.style.display = "block"; + + animType = animType.split("-"); + switch (animType[0]) { + case "fade": + anim.anim = apf.tween.NORMAL; + if (out) h.style.zIndex = 30; + anim.tweens.push( + out + ? {type: "fade", from: 1, to: 0} + : {type: "fade", from: 0, to: 1} + ); + break; + case "slide": + var info = this.$cube[animType[1]] + from = 0; + to = info[0] * h[info[1]]; + if (!out) + h.style[info[2]] = from + "px"; + + anim.tweens.push({type: info[2], from: out ? from : to, to: out ? to : from}); + //else etc + break; + case "push": + var info = this.$cube[animType[1]] + var h2 = pageOut.$ext; + + if (out) { + if (this.$transInfo["in"]) + this.$transInfo["in"].tweens = []; //prevent in animation + } + else + this.$createAnim(pageOut, "normal", true); + + var hInt = apf[info[3]](this.$int); + + var from1 = info[0] * hInt;//h[info[1]]; + var to1 = 0; + + var from2 = 0; + var to2 = -1 * info[0] * hInt;//h2[info[1]]; + + if (out) + h2.style[info[2]] = to2 + "px"; + else + h.style[info[2]] = from1 + "px"; + + anim.tweens.push({oHtml: h, type: [info[2]], from: out ? to1 : from1, to: out ? from1 : to1}); + anim.tweens.push({oHtml: h2, type: [info[2]], from: out ? to2 : from2, to: out ? from2 : to2}); + + return false; + case "normal": + break; + default: + throw new Error("Unknown animation type:" + animType[0]); //@todo make into proper apf3.0 error + } + } + + + /** + * Retrieves an array of all the page elements of this element. + */ + this.getPages = function(){ + var r = [], nodes = this.childNodes; + for (var i = 0, l = nodes.length; i < l; i++) { + if ("page|case".indexOf(nodes[i].localName) > -1 && nodes[i].visible !== false) + r.push(nodes[i]); + } + return r; + }; + + /** + * Retrieves a page element by it's name or child number + * @param {mixed} nameOrId the name or child number of the page element to retrieve. + * @return {Page} the found page element. + */ + this.getPage = function(nameOrId){ + if (apf.isNot(nameOrId)) + return this.$activepage; + else + return this.$findPage(nameOrId); + }; + + /** + * Add a new page element + * @param {String} [caption] the text displayed on the button of the page. + * @param {String} [name] the name of the page which is can be referenced by. + * @return {page} the created page element. + */ + this.add = function(caption, name, type, before, callback){ + var page = this.ownerDocument.createElementNS(apf.ns.aml, "page"); + if (name) + page.setAttribute("id", name); + if (type) + page.setAttribute("type", type); + if (caption) + page.setAttribute("caption", caption); + + if (callback) + callback(page); + + this.insertBefore(page, before); + + + //this.scrollIntoView(page); + + return page; + }; + + /** + * Removes a page element from this element. This function destroys ALL children + * of this page. To simple remove the page from the DOM tree use the + * removeNode() method. + * + * @param {mixed} nameOrId the name or child number of the page element to remove. + * @return {Page} the removed page element. + */ + this.remove = function(nameOrId, force){ + var page = typeof nameOrId == "object" + ? nameOrId + : this.$findPage(nameOrId); + if (!page) + return false; + + var e = {page: page}; + if (typeof force == "object") { + e.htmlEvent = force; + force = false; + } + + if (!force && this.dispatchEvent("close", e) === false) + return; + + + if (this.$scale) { + this.$scaleinit(page, "remove", function(){ + //page.removeNode(); + page.destroy(true, true); + }); + } + else + + { + //page.removeNode(); + page.destroy(true, true); + page.dispatchEvent("afterclose"); + + + //@todo this is wrong, we can also use removeChild + //this.setScrollerState(); + + } + + return page; + }; + + + /* + var SCROLLANIM = { + scrollOn : false, + steps : 15, + interval : 10, + size : 0, + left : 0, + control : { + stop : false + }, + stopHandle: function() { + bAnimating = false; + } + }, + SCROLL_OFF = 0x0001, + SCROLL_HOVER = 0x0002, + SCROLL_DOWN = 0x0004, + SCROLL_DIS = 0x0008, + SCROLL_L_STATE = SCROLL_OFF, + SCROLL_R_STATE = SCROLL_OFF, + SCROLL_LEFT = 0x0001, + SCROLL_RIGHT = 0x0002, + SCROLL_BOTH = 0x0004, + bAnimating = false, + scrollTimer = null, + keepScrolling = false, + globalDir = SCROLL_LEFT; +*/ + function getButtonsWidth() { + var cId = "cache_" + this.$buttons.childNodes.length; + if (SCROLLANIM[cId]) + return SCROLLANIM[cId]; + + var iWidth = 0; + for (var i = 0, l = this.$buttons.childNodes.length; i < l; i++) { + if (typeof this.$buttons.childNodes[i].offsetWidth != "undefined") + iWidth += this.$buttons.childNodes[i].offsetWidth; + } + + return SCROLLANIM[cId] = iWidth; + } + + function setButtonState(dir, state) { + var bBoth = dir & SCROLL_BOTH; + if (bBoth) + dir = SCROLL_LEFT; + var oBtn = this[dir & SCROLL_LEFT ? "oLeftScroll" : "oRightScroll"]; + if (!(state & SCROLL_DIS)) { + if (dir & SCROLL_LEFT) + SCROLL_L_STATE = state; + else + SCROLL_R_STATE = state; + } + + if (state & SCROLL_OFF) + apf.setStyleClass(oBtn, "", ["disabled", "hover", "down"]); + else if (state & SCROLL_HOVER) + apf.setStyleClass(oBtn, "hover", ["disabled", "down"]); + else if (state & SCROLL_DOWN) + apf.setStyleClass(oBtn, "down", ["disabled", "hover"]); + else if (state & SCROLL_DIS) + apf.setStyleClass(oBtn, "disabled", ["hover", "down"]); + + if (bBoth) + setButtonState(SCROLL_RIGHT, state); + } + + /** + * Set the state scroller buttons: enabled, disabled or completely hidden, + * depending on the state of the tab buttons + * + * @param {Boolean} [bOn] Indicates whether to turn the scroll buttons on or off + * @param {Number} [iBtns] Specifies the buttons to set the state of. Can be SCROLL_LEFT, SCROLL_RIGHT or SCROLL_BOTH + * @type {void} + */ + this.setScrollerState = function(bOn, iBtns) { + if (!this.ready || !this.$hasButtons || !this.oScroller) return; + + if (typeof bOn == "undefined") { + var scrollerWidth = this.oScroller.offsetWidth + || parseInt(apf.getStyle(this.oScroller, "width").replace(/(px|em|%)/, "")); + bOn = ((getButtonsWidth.call(this) + scrollerWidth) > this.$ext.offsetWidth); + iBtns = SCROLL_BOTH; + } + + if (iBtns & SCROLL_BOTH && bOn !== SCROLLANIM.scrollOn) { + // in case of HIDING the scroller: check if the anim stuff has reverted + SCROLLANIM.scrollOn = bOn; + if (!bOn) { + this.$buttons.style.left = SCROLLANIM.left + "px"; + this.oScroller.style.display = "none"; + } + //else + // TODO: scroll active tab into view if it becomes hidden beneath scroller node(s) + } + else { + this.oScroller.style.display = ""; + } + + this.oScroller.style.display = (iBtns & SCROLL_BOTH && !bOn) + ? "none" + : ""; + if (typeof iBtns == "undefined") + iBtns = SCROLL_BOTH; + if (!bOn) { + if ((iBtns & SCROLL_LEFT) || (iBtns & SCROLL_BOTH)) + setButtonState.call(this, SCROLL_LEFT, SCROLL_DIS); + if ((iBtns & SCROLL_RIGHT) || (iBtns & SCROLL_BOTH)) + setButtonState.call(this, SCROLL_RIGHT, SCROLL_DIS); + } + }; + + /** + * Corrects the state of the scroller buttons when the state of external + * components change, like on a resize event of a window. + * + * @type {void} + */ + this.correctScrollState = function() { + if (!this.ready || !this.$hasButtons || !this.oScroller) return; + this.setScrollerState(); + }; + + /** + * Retrieves the utmost left or right boundaries of the tab buttons strip that + * can be scrolled to. The tabs cannot scroll any further than these boundaries + * + * @param {Number} dir Determines which boundary side to look at; SCROLL_LEFT or SCROLL_RIGHT + * @param {Boolan} [useCache] Used only when tabs are draggable. Not implemented. + * @type {Number} + */ + function getAnimationBoundary(dir, useCache) { + if (SCROLLANIM.size <= 0) { + SCROLLANIM.left = this.$buttons.offsetLeft; + SCROLLANIM.size = Math.round(this.firstChild.$button.offsetWidth); + } + if (dir & SCROLL_LEFT) { + return SCROLLANIM.left; + } + else if (dir & SCROLL_RIGHT) { + // TODO: support Drag n Drop of tabs... + //if (typeof useCache == "undefined") useCache = false; + //if (!tabcontrol.drag) tabcontrol.drag = {}; + //if (useCache && tabcontrol.drag.boundCache) + // return tabcontrol.drag.boundCache; + var oNode = this.$buttons.childNodes[this.$buttons.childNodes.length - 1]; + + return this.$ext.offsetWidth - (oNode.offsetLeft + oNode.offsetWidth + + (this.oScroller.offsetWidth + 4));// used to be tabcontrol.drag.boundCache; + } + } + + /** + * Event handler; executed when the user pressed one of the two scroll buttons + * (left or right one). If the tab-buttons strip may/ can be scrolled, the + * respective behavior is called. + * + * @param {Event} e Event object, usually a mousedown event from a scroller-button + * @param {Number} dir Direction to scroll; SCROLL_LEFT or SCROLL_RIGHT + * @type {void} + */ + this.scroll = function(e, dir) { + if (!this.ready || !this.$hasButtons || !this.oScroller) return; + if (!e) + e = window.event; + if (typeof e["type"] == "unknown") //scope expired (prolly GC'ed) + e = {type: "click"}; + if (bAnimating && e.type != "dblclick") return; + bAnimating = true; + + if (typeof dir == "undefined") + dir = SCROLL_LEFT; + + //apf.tween.clearQueue(this.$buttons, true); + var iCurrentLeft = this.$buttons.offsetLeft, + size = e["delta"] ? Math.round(e.delta * 36) : SCROLLANIM.size, + //get maximum left offset for either direction + iBoundary = getAnimationBoundary.call(this, dir), + _self = this; + if (dir & SCROLL_LEFT) { + setButtonState(SCROLL_LEFT, SCROLL_DOWN); + setButtonState(SCROLL_RIGHT, SCROLL_OFF); + if (iCurrentLeft === iBoundary) { + this.setScrollerState(false, SCROLL_LEFT); + return apf.tween.single(this.$buttons, { + steps : SCROLLANIM.steps, + interval: 20, + from : iCurrentLeft, + to : iCurrentLeft + 12, + type : "left", + anim : apf.tween.EASEOUT, + onstop : SCROLLANIM.stopHandle, + onfinish: function(oNode) { + apf.tween.single(oNode, { + steps : SCROLLANIM.steps, + interval: SCROLLANIM.interval, + from : iCurrentLeft + 12, + to : iCurrentLeft, + type : "left", + anim : apf.tween.EASEIN, + onstop : SCROLLANIM.stopHandle, + onfinish: function() { + bAnimating = false; + if (e.name == "mousescroll") + setButtonState(SCROLL_LEFT, SCROLL_OFF); + } + }); + } + }); + } + //one scroll animation scrolls by a SCROLLANIM.size px. + var iTargetLeft = iCurrentLeft + (e.type == "dblclick" ? size * 3 : size); + if (iTargetLeft > iBoundary) + iTargetLeft = iBoundary; + + if (iTargetLeft === iBoundary) + this.setScrollerState(false, SCROLL_LEFT); + this.setScrollerState(true, SCROLL_RIGHT); + + //start animated scroll to the left + apf.tween.single(this.$buttons, { + steps : SCROLLANIM.steps, + interval: SCROLLANIM.interval, + control : SCROLLANIM.control, + from : iCurrentLeft, + to : iTargetLeft, + type : "left", + anim : apf.tween.NORMAL, + onstop : SCROLLANIM.stopHandle, + onfinish: function() { + bAnimating = false; + if (e.name == "mousescroll") + setButtonState(SCROLL_LEFT, SCROLL_OFF); + if (keepScrolling) + _self.scroll(e, globalDir); + } + }); + } + else if (dir & SCROLL_RIGHT) { + this.setScrollerState(true); + setButtonState(SCROLL_RIGHT, SCROLL_DOWN); + setButtonState(SCROLL_LEFT, SCROLL_OFF); + if (iCurrentLeft === iBoundary) { + this.setScrollerState(false, SCROLL_RIGHT); + return apf.tween.single(this.$buttons, { + steps : SCROLLANIM.steps, + interval: 20, + from : iCurrentLeft, + to : iCurrentLeft - 24, + type : "left", + anim : apf.tween.EASEOUT, + onstop : SCROLLANIM.stopHandle, + onfinish: function(oNode, options) { + apf.tween.single(oNode, { + steps : SCROLLANIM.steps, + interval: SCROLLANIM.interval, + from : iCurrentLeft - 24, + to : iCurrentLeft, + type : "left", + anim : apf.tween.EASEIN, + onstop : SCROLLANIM.stopHandle, + onfinish: function() { + bAnimating = false; + if (e.name == "mousescroll") + setButtonState(SCROLL_RIGHT, SCROLL_OFF); + } + }); + } + }); + } + //one scroll animation scrolls by a SCROLLANIM.size px. + var iTargetLeft = iCurrentLeft - (e.type == "dblclick" ? size * 3 : size); + //make sure we don't scroll more to the right than the + //maximum left: + if (iTargetLeft < iBoundary) + iTargetLeft = iBoundary; + //start animated scroll to the right + apf.tween.single(this.$buttons, { + steps : SCROLLANIM.steps, + interval: SCROLLANIM.interval, + control : SCROLLANIM.control, + from : iCurrentLeft, + to : iTargetLeft, + type : "left", + anim : apf.tween.NORMAL, + onstop : SCROLLANIM.stopHandle, + onfinish: function() { + bAnimating = false; + if (e.name == "mousescroll") + setButtonState(SCROLL_RIGHT, SCROLL_OFF); + if (keepScrolling) + _self.scroll(e, globalDir); + } + }); + } + }; + + /** + * If a tabpage is outside of the users' view, this function scrolls that + * tabpage into view smoothly. + * + * @param {page} oPage The page to scroll into view + * @type {void} + */ + this.scrollIntoView = function(oPage) { + bAnimating = false; + if (!this.ready || !this.$hasButtons || !this.oScroller || !oPage.$drawn) + return; + bAnimating = true; + if (this.$buttons.offsetWidth < this.$ext.offsetWidth) + return this.setScrollerState(false); + + var iTabLeft = oPage.$button.offsetLeft, + iTabWidth = oPage.$button.offsetWidth, + iCurrentLeft = this.$buttons.offsetLeft; + + if (SCROLLANIM.size <= 0) { + SCROLLANIM.left = this.$buttons.offsetLeft; + var p = this.firstChild; + while (!p.$button) + p = p.nextSibling; + SCROLLANIM.size = Math.round(p.$button.offsetWidth); + } + this.$buttons.style.left = iCurrentLeft; + + var iRealWidth = this.$ext.offsetWidth, + iScrollCorr = this.oScroller.offsetWidth + 4, + iTargetLeft = null, + dir; + + if ((iTabLeft + iTabWidth) > ((iRealWidth - iScrollCorr) - iCurrentLeft)) { //scroll to the right + iTargetLeft = (-(iTabLeft - SCROLLANIM.left) + + (iRealWidth - iTabWidth - iScrollCorr)); + dir = SCROLL_RIGHT; + } + else if ((iCurrentLeft + iTabLeft) < SCROLLANIM.left) { //sroll to the left + iTargetLeft = SCROLLANIM.left - iTabLeft; + dir = SCROLL_LEFT; + } + + if (iTargetLeft !== null) { + this.setScrollerState(true); + setButtonState(SCROLL_RIGHT, dir & SCROLL_RIGHT ? SCROLL_DOWN : SCROLL_OFF); + setButtonState(SCROLL_LEFT, dir & SCROLL_LEFT ? SCROLL_DOWN : SCROLL_OFF); + apf.tween.clearQueue(this.$buttons, true); + + apf.tween.single(this.$buttons, { + steps : SCROLLANIM.steps, + interval: SCROLLANIM.interval, + from : iCurrentLeft, + to : iTargetLeft, + type : "left", + anim : apf.tween.NORMAL, + onstop : SCROLLANIM.stopHandle, + onfinish: function() { + bAnimating = false; + setButtonState(SCROLL_RIGHT, SCROLL_OFF); + setButtonState(SCROLL_LEFT, SCROLL_OFF); + } + }); + } + else + bAnimating = false; + }; + + + + /**** DOM Hooks ****/ + + this.addEventListener("DOMNodeRemoved", function(e){ + var amlNode = e.currentTarget; + if (e.$doOnlyAdmin || e.relatedNode != this + || amlNode.localName != "page") + return; + + var ln = amlNode.nextSibling; + while (ln && (!ln.$first || !ln.visible)) + ln = ln.nextSibling; + var rn = amlNode.previousSibling; + while (rn && (!rn.$last || !rn.visible)) + rn = rn.previousSibling; + + if (this.firstChild == amlNode && ln) + ln && ln.$first(); + if (this.lastChild == amlNode && rn) + rn && rn.$last(); + + if (this.$activepage == amlNode) { + if (ln || rn) + this.set(ln || rn); + else { + amlNode.$deactivate(); + + + //this.setScrollerState(); + + this.$activepage = + this.activepage = + this.activepagenr = null; + this.setProperty("activepage", null); + } + } + else { + + //if (this.$scroll) + //this.setScrollerState(); + + + if (this.$scale) + this.$scaleinit(); + + } + + + this.setProperty("length", this.childNodes.length); + + }); + + this.addEventListener("DOMNodeInserted",function(e){ + var amlNode = e.currentTarget; + + if (amlNode.localName != "page" || e.relatedNode != this || amlNode.nodeType != 1) + return; + + if (!e.$beforeNode) { + var lastChild, pg = this.getPages(); + if (lastChild = pg[pg.length - 1]) + lastChild.$last(true); + amlNode.$last(); + } + + var p = this.getPage(0); //@todo $beforeNode doesnt have to be a page + if (!p || e.$beforeNode == p) { + if (p) + p.$first(true); + amlNode.$first(); + } + + if (this.$activepage) { + var info = {}; + this.$findPage(this.$activepage, info); + + if (this.activepagenr != info.position) { + if (parseInt(this.activepage) == this.activepage) { + this.activepage = info.position; + this.setProperty("activepage", info.position); + } + this.activepagenr = info.position; + this.setProperty("activepagenr", info.position); + } + } + else if (!this.$activepage) + this.set(amlNode); + + + if (this.$scale && amlNode.visible) + this.$scaleinit(amlNode, "add"); + else + + { + amlNode.dispatchEvent("afteropen"); + } + + + this.setProperty("length", this.childNodes.length); + + }); + + /**** Private state handling functions ****/ + + this.$findPage = function(nameOrId, info){ + var node, nodes = this.childNodes; + + if (nameOrId.localName) { + for (var t = 0, i = 0, l = nodes.length; i < l; i++) { + node = nodes[i]; + if ("page|case".indexOf(node.localName) > -1 && (++t) && node == nameOrId) { + if (info) + info.position = t - 1; + return node; + } + } + } + else { + for (var t = 0, i = 0, l = nodes.length; i < l; i++) { + node = nodes[i]; + if ("page|case".indexOf(node.localName) > -1 && (t++ == nameOrId + || node.name == nameOrId)) { + if (info) + info.position = t - 1; + return node; + } + } + } + + return null; + }; + + this.$enable = function(){ + var nodes = this.childNodes; + for (var i = 0, l = nodes.length; i < l; i++) { + if (nodes[i].enable) + nodes[i].enable(); + } + }; + + this.$disable = function(){ + var nodes = this.childNodes; + for (var i = 0, l = nodes.length; i < l; i++) { + if (nodes[i].disable) + nodes[i].disable(); + } + }; + + /**** Keyboard support ****/ + + + + this.addEventListener("keydown", function(e){ + if (!this.$hasButtons) + return; + + var page, + key = e.keyCode; + + switch (key) { + case 9: + break; + case 13: + break; + case 32: + break; + case 37: //LEFT + page = this.getPage().previousSibling; + while(page && (page.nodeType != 1 + || "page|case".indexOf(page.localName) == -1 || !page.visible)) { + page = page.previousSibling; + } + + if (page) + this.setProperty("activepage", page); + break; + case 39: //RIGHT + page = this.getPage().nextSibling; + while(page && (page.nodeType != 1 + || "page|case".indexOf(page.localName) == -1 || !page.visible)) { + page = page.nextSibling; + } + + if (page) + this.setProperty("activepage", page); + break; + default: + return; + } + //return false; + }, true); + + + + /**** Init ****/ + + this.$loadChildren = function(callback){ + var page = false, + _self = this, + i, j, l, node, nodes; + + this.inited = true; + + if (this.$hasButtons) { + this.$buttons = this.$getLayoutNode("main", "buttons", this.$ext); + this.$buttons.setAttribute("id", this.$uniqueId + "_buttons"); + } + + this.oPages = this.$getLayoutNode("main", "pages", this.$ext); + + + // add scroller node(s) + /*this.oScroller = this.$getLayoutNode("main", "scroller", this.oPages); + if (this.oScroller) { + function startTimer(e, dir) { + clearTimeout(scrollTimer); + globalDir = dir; + scrollTimer = $setTimeout(function() { + keepScrolling = true; + _self.scroll(e, dir); + }, 500); + } + function stopTimer() { + clearTimeout(scrollTimer); + keepScrolling = false; + } + + this.oScroller.onmouseout = function(e) { + SCROLLANIM.control.stop = true; + setButtonState(SCROLL_BOTH, SCROLL_OFF); + }; + + + /*apf.addEventListener("mousescroll", function(e) { + var found = (e.target == _self.$buttons); + while (!found && e.target != document.body) { + e.target = e.target.offsetParent; + found = (e.target == _self.$buttons); + } + if (!found) return; + var dir = e.delta > 0 ? SCROLL_LEFT : SCROLL_RIGHT; + e.delta = Math.abs(e.delta); + _self.scroll(e, dir); + });* / + + + this.oLeftScroll = apf.getNode(this.oScroller, [0]); + this.oRightScroll = apf.getNode(this.oScroller, [1]); + + ["oLeftScroll", "oRightScroll"].forEach(function(sBtn) { + var dir = sBtn == "oLeftScroll" ? SCROLL_LEFT : SCROLL_RIGHT, + revDir = sBtn == "oLeftScroll" ? SCROLL_RIGHT : SCROLL_LEFT; + + _self[sBtn].ondbclick = + _self[sBtn].onmousedown = function(e) { + SCROLLANIM.control.stop = false; + var state = dir & SCROLL_LEFT ? SCROLL_L_STATE : SCROLL_R_STATE; + if (this.className.indexOf("disabled") != -1 + || state & SCROLL_DOWN) return; + e = e || event; + _self.scroll(e, dir); + startTimer(e, dir); + if (!apf.isSafariOld) + this.onmouseout(); + }; + _self[sBtn].onmouseover = function() { + SCROLLANIM.control.stop = false; + var state = dir & SCROLL_LEFT ? SCROLL_L_STATE : SCROLL_R_STATE; + if (this.className.indexOf("disabled") != -1 + || state & SCROLL_DOWN) return; + setButtonState(dir, SCROLL_HOVER); + setButtonState(revDir, SCROLL_OFF); + globalDir = dir; + }; + _self[sBtn].onmouseout = function() { + var state = dir & SCROLL_LEFT ? SCROLL_L_STATE : SCROLL_R_STATE; + if (this.className.indexOf("disabled") != -1 + || state & SCROLL_DOWN) return; + setButtonState(dir, SCROLL_OFF); + }; + _self[sBtn].onmouseup = function() { + if (this.className.indexOf("disabled") == -1) { + setButtonState(dir, SCROLL_OFF); + } + stopTimer(); + SCROLLANIM.control.stop = true; + }; + }); + } + + + apf.layout.setRules(this.$ext, this.$uniqueId + "_tabscroller", + "var o = apf.all[" + this.$uniqueId + "]; o && o.correctScrollState()"); + apf.layout.queue(this.$ext);*/ + + + + //Skin changing support + if (this.$int) { + //apf.AmlParser.replaceNode(this.oPages, oPages); + this.$int = this.oPages; + page = true; + + //@todo apf3.0 skin change? + nodes = this.childNodes; + for (i = 0; i < nodes.length; i++) { + node = nodes[i]; + if(node.nodeType != 1) + continue; + node.$draw(true); + if(node.$skinchange) + node.$skinchange(); + node.$loadAml(); + } + } + else { + this.$int = this.oPages; + + //Build children + nodes = this.getPages(); + if (nodes.length) { + nodes[0].$first(); + (node = nodes[nodes.length - 1]).$last(); + } + } + + //Set active page + if (node) { + this.activepage = (typeof this.activepage != "undefined" + ? this.activepage + : this.activepagenr) || 0; + page = this.getPage(this.activepage); + if (page.render != "runtime" || page.$rendered) + this.$propHandlers.activepage.call(this, this.activepage); + } + else { + this.isPages = false; + } + + + this.setProperty("length", j); + + + this.ready = true; + + /*window.setTimeout(function() { + _self.setScrollerState(); + }, 0);*/ + + + if (!this.activepage && this.getAttribute("src")) { + this.src = this.getAttribute("src"); + this.$propHandlers["activepage"].call(this); + } + }; + + this.$destroy = function(bSkinChange) { + if (bSkinChange || !this.oScroller) + return; + + + /*apf.layout.removeRule(this.$ext, this.$uniqueId + "_tabscroller"); + + [this.oLeftScroll, this.oRightScroll].forEach(function(oBtn) { + oBtn.onmousedown = oBtn.ondblclick = oBtn.onmouseover = + oBtn.onmouseout = oBtn.onmouseup = null; + });*/ + + }; +}).call(apf.BaseTab.prototype = new apf.Presentation()); + + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/core/baseclasses/basetree.js)SIZE(48162)TIME(Mon, 10 Jan 2011 10:53:11 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + + +/** + * Baseclass of elements that allows the user to select one or more items + * from a tree based element. + * + * @constructor + * @baseclass + * + * @inherits apf.XForms + * @inherits apf.Cache + * @inherits apf.DataAction + * @inherits apf.Rename + * + * @author Ruben Daniels (ruben AT ajax DOT org) + * @version %I%, %G% + * @since 0.8 + * @default_private + * + */ +apf.BaseTree = function(){ + this.$init(true); + + + this.$dynCssClasses = []; + + + this.$nodes = []; +}; + +(function() { + + this.implement( + + + apf.Rename, + + + apf.DataAction, + + + apf.Cache, + + apf.K + ); + + + /**** Properties and Attributes ****/ + + //Options + this.$isTreeArch = true; // This element has a tree architecture. + this.$focussable = true; // This object can get the focus. + this.multiselect = false; // Initially multiselect is disabled. + this.bufferselect = true; + + this.startcollapsed = true; + this.$animType = apf.tween.NORMAL; + this.$animOpenStep = 3; + this.$animCloseStep = 1; + this.$animSpeed = 10; + + var HAS_CHILD = 1 << 1, + IS_CLOSED = 1 << 2, + IS_LAST = 1 << 3, + IS_ROOT = 1 << 4; + + var treeState = this.$treeState = {}; + this.$treeState[0] = ""; + this.$treeState[HAS_CHILD] = "min"; + this.$treeState[HAS_CHILD | IS_CLOSED] = "plus"; + this.$treeState[IS_LAST] = "last"; + this.$treeState[IS_LAST | HAS_CHILD] = "minlast"; + this.$treeState[IS_LAST | HAS_CHILD | IS_CLOSED] = "pluslast"; + this.$treeState[IS_ROOT] = "root"; + + /**** Properties and Attributes ****/ + + /** + * @attribute {Boolean} openadd whether the tree expands the parent to which a node is added. Defaults to true. + * @attribute {Boolean} startcollapsed whether the tree collapses all nodes that contain children on load. Defaults to true. + * @attribute {Boolean} nocollapse whether the user cannot collapse a node. Defaults to false. + * @attribute {Boolean} singleopen whether the tree will expand a node by a single click. Defaults to false. + * @attribute {Boolean} prerender whether the tree will render all the nodes at load. Defaults to true. + */ + this.$booleanProperties["openadd"] = true; + this.$booleanProperties["startcollapsed"] = true; + this.$booleanProperties["nocollapse"] = true; + this.$booleanProperties["singleopen"] = true; + this.$booleanProperties["prerender"] = true; + this.$booleanProperties["removecontainer"] = true; + + this.$supportedProperties.push("openadd", "startcollapsed", "nocollapse", + "singleopen", "prerender", "removecontainer"); + + this.openadd = true; + this.startcollapsed = 1; + this.prerender = true; + + /**** Public Methods ****/ + + /** + * Expands all items in the tree + */ + this.expandAll = function(){ + if (!this.xmlRoot) + return; + + var xpath = this.each.split('|') + .join('[' + this.each.replace(/\|/g, " or ") + ']|.//'), + _self = this; + (function(node){ + var nodes = node.selectNodes(xpath); + //for (var i = nodes.length - 1; i >= 0; i--) { + for (var o, i = 0; i < nodes.length; i++) { + if (o = apf.xmldb.getHtmlNode(nodes[i], _self)) + _self.slideToggle(o, 1, true); + arguments.callee(nodes[i]); + } + })(this.xmlRoot); + }; + + /** + * Collapses all items in the tree + */ + this.collapseAll = function(){ + if (!this.xmlRoot) + return; + + var pNodes = this.xmlRoot.selectNodes(".//" + this.each + .split('|').join('[' + this.each.replace(/\|/g, " or ") + ']|.//')); + + for (var o, i = pNodes.length - 1; i >=0; i--) { + if (o = apf.xmldb.getHtmlNode(pNodes[i], this)) + this.slideToggle(o, 2, true); + } + }; + + /** + * Selects a node and expands each parent of it. + */ + this.expandAndSelect = function(xmlNode) { + if (!this.xmlRoot) + return; + + var _self = this; + if ((function _recur(loopNode){ + var pNode = _self.getTraverseParent(loopNode); + if (pNode == _self.xmlRoot) + return true; + + if (!pNode || _recur(pNode) === false) + return false; + + _self.slideToggle(apf.xmldb.getHtmlNode(pNode, _self), 1, true); + })(xmlNode) !== false) + this.select(xmlNode); + } + + this.expandList = function(pathList, callback){ + pathList.sort(); + var root = this.xmlRoot, _self = this; + apf.asyncForEach(pathList, + function(item, next){ + var paths = item.split("/"); + var lastNode = root;//root.selectSingleNode(paths.shift()); + + //var lastPath = paths.pop(); + apf.asyncForEach(paths, + function(part, next2, index) { + apf.queue.empty(); + //This timeout is here to workaround a bug in chrome7 (and perhaps earlier) + setTimeout(function(){ + var xmlNode = (lastNode || root).selectSingleNode(part); + if (xmlNode) { + //if (index == paths.length - 1) + //return _self.select(xmlNode); + + lastNode = xmlNode; + _self.slideToggle(apf.xmldb.getHtmlNode(xmlNode, _self), 1, true, null, function(){ + next2(); + }); + } + else { + _self.slideToggle(apf.xmldb.getHtmlNode(lastNode, _self), 1, true, null, function(){ + lastNode = lastNode.selectSingleNode(part); + if (!lastNode) + next2(true); + else + next2(); + }); + } + },100); + }, function(err){ + //if (!err) { + next(); + //} + } + ); + }, + function(err){ + if (callback) + callback(); + } + ); + } + + /** + * @notimplemented + * @todo who's volunteering? + * @private + */ + this.selectPath = function(path){}; + + /**** Sliding functions ****/ + + /** + * @private + */ + this.slideToggle = function(htmlNode, force, immediate, userAction, callback){ + if (this.nocollapse || userAction && this.disabled) + return; + + if (!htmlNode) + htmlNode = this.$selected; + + if (!htmlNode) + return callback && callback(); + + var id = htmlNode.getAttribute(apf.xmldb.htmlIdTag); + while (!id && htmlNode.parentNode) + id = (htmlNode = htmlNode.parentNode) + .getAttribute(apf.xmldb.htmlIdTag); + + var container = this.$getLayoutNode("item", "container", htmlNode); + if (!container) return; + + if (apf.getStyle(container, "display") == "block") { + if (force == 1) { + if (callback) callback(); + return; + } + htmlNode.className = htmlNode.className.replace(/min/, "plus"); + this.slideClose(container, apf.xmldb.getNode(htmlNode), immediate, callback); + } + else { + if (force == 2) { + if (callback) callback(); + return; + } + htmlNode.className = htmlNode.className.replace(/plus/, "min"); + this.slideOpen(container, apf.xmldb.getNode(htmlNode), immediate, callback); + } + }; + + this.isCollapsed = function(xmlNode){ + return (apf.getStyle(this.$getLayoutNode("item", "container", + apf.xmldb.getHtmlNode(xmlNode, this)), "display") == "none"); + } + + var lastOpened = {}; + /** + * @event expand Fires when a tree leaf is expanded from collapsed view to + * reveal its children leaves. + * @private + */ + this.slideOpen = function(container, xmlNode, immediate, callback){ + if (!xmlNode) + xmlNode = this.selected; + + var htmlNode = apf.xmldb.getHtmlNode(xmlNode, this); + if (!container) + container = this.$findContainer(htmlNode); + + //We don't slide open elements without children. + if (!container.childNodes.length && !this.getTraverseNodes(xmlNode).length) + return callback && callback(); + + if (this.singleopen) { + var pNode = this.getTraverseParent(xmlNode), + p = (pNode || this.xmlRoot).getAttribute(apf.xmldb.xmlIdTag); + if (lastOpened[p] && lastOpened[p][1] != xmlNode + && this.getTraverseParent(lastOpened[p][1]) == pNode) + this.slideToggle(lastOpened[p][0], 2);//lastOpened[p][1]); + lastOpened[p] = [htmlNode, xmlNode]; + } + + if (!this.nocollapse) + container.style.display = "block"; + + if (!this.prerender && this.$hasLoadStatus(xmlNode, "potential") + && !container.childNodes.length) { + this.$extend(xmlNode, container, immediate, callback); + return; + } + + if (immediate || container.scrollHeight > 1000) { + if (!this.nocollapse && container != this.$container) { + container.style.height = "auto"; + container.style.overflow = "visible"; + } + + if (this.$hasLoadStatus(xmlNode, "potential")) + return this.$extend(xmlNode, container, immediate, callback); + + this.dispatchEvent("expand", {xmlNode: xmlNode}); + return callback && callback(); + } + + var _self = this; + var prevHeight = container.style.height; + container.style.overflow = "visible"; + if (!apf.isIE7) { + container.style.height = apf.hasHeightAutoDrawBug ? "100%" : "auto"; + } + var height = container.scrollHeight; + container.style.overflow = "hidden"; + container.style.height = prevHeight; + + apf.tween.single(container, { + type : 'scrollheight', + from : container.offsetHeight, + to : height, + anim : this.$animType, + steps : this.$animOpenStep, + interval: this.$animSpeed, + onfinish: function(container){ + if (xmlNode && _self.$hasLoadStatus(xmlNode, "potential")) { + $setTimeout(function(){ + if (container != this.$container) { + container.style.height = container.scrollHeight + "px"; + container.style.overflow = "hidden"; + } + _self.$extend(xmlNode, container, null, callback); + }); + if (container != this.$container) { + if (!apf.isIE7) { + container.style.height = apf.hasHeightAutoDrawBug ? "100%" : "auto"; + } + container.style.overflow = "visible"; + } + } + else if (container != this.$container) { + container.style.overflow = "visible"; + if (!apf.isIE7) { + container.style.height = apf.hasHeightAutoDrawBug ? "100%" : "auto"; + } + } + _self.dispatchEvent("expand", {xmlNode: xmlNode}); + } + }); + }; + + /** + * @event collapse Fires when a tree leaf is collapsed from expanded view to + * conceal its children leaves. + * @private + */ + this.slideClose = function(container, xmlNode, immediate){ + if (this.nocollapse) + return; + + if (!xmlNode) + xmlNode = this.selected; + + if (this.singleopen) { + var p = (this.getTraverseParent(xmlNode) || this.xmlRoot) + .getAttribute(apf.xmldb.xmlIdTag); + lastOpened[p] = null; + } + + if (!container) { + var htmlNode = apf.xmldb.getHtmlNode(xmlNode, this); + container = this.$findContainer(htmlNode); + } + + if (container != this.$container) { + container.style.height = container.offsetHeight; + container.style.overflow = "hidden"; + } + + if (immediate) { + if (container != this.$container) + container.style.height = 0; + container.style.display = "none"; + this.dispatchEvent("collapse", {xmlNode: xmlNode}); + return; + } + + var _self = this; + apf.tween.single(container, { + type : 'scrollheight', + from : container.scrollHeight, + to : 0, + anim : this.$animType, + steps : this.$animCloseStep, + interval: this.$animSpeed, + onfinish: function(container, data){ + container.style.display = "none"; + _self.dispatchEvent("collapse", {xmlNode: xmlNode}); + } + }); + }; + + /**** Databinding Support ****/ + + //@todo apf3.x refactor + this.$add = function(xmlNode, Lid, xmlParentNode, htmlParentNode, beforeNode, isLast, depth, nextNode, action){ + if (this.$isTreeArch && this.$needsDepth && typeof depth == "undefined") { + var loopNode = xmlParentNode; depth = 0; + while(loopNode != this.xmlRoot) { + depth++; + loopNode = loopNode.parentNode; + } + } + + var loadChildren = this.$getBindRule("insert", xmlNode) ? true : false, + traverseNodes = this.getTraverseNodes(xmlNode), + hasTraverseNodes = traverseNodes.length ? true : false, + hasChildren = loadChildren || hasTraverseNodes, + startcollapsed = this.$hasBindRule("collapsed") + ? (this.$getDataNode("collapsed", xmlNode) ? true : false) + : (this.$hasBindRule("expanded") + ? (this.$getDataNode("expanded", xmlNode) ? false : true) + : this.startcollapsed), + state = (hasChildren ? HAS_CHILD : 0) | (startcollapsed && hasChildren + || loadChildren ? IS_CLOSED : 0) | (isLast ? IS_LAST : 0), + + htmlNode = this.$initNode(xmlNode, state, Lid, depth), + container = this.$getLayoutNode("item", "container", htmlNode), + eachLength; + + if (!startcollapsed && !this.nocollapse) + container.setAttribute("style", "overflow:visible;height:auto;display:block;"); + + var msg, removeContainer = (!this.removecontainer || hasChildren); + + //TEMP on for dynamic subloading + if (!hasChildren || loadChildren) + container.setAttribute("style", "display:none;"); + + //Dynamic SubLoading (Insertion) of SubTree + if (!this.prerender) + eachLength = traverseNodes.length; + + if (hasChildren && !this.prerender && eachLength > 2 && startcollapsed + || loadChildren && (!this.$hasLoadStatus(xmlNode) + || this.$hasLoadStatus(xmlNode, "potential"))) + this.$setLoading(xmlNode, container); + else if (!hasTraverseNodes && (msg = this.$applyBindRule("empty", xmlNode))) { + this.$setEmptyMessage(container, msg); + } + + if ((!htmlParentNode || htmlParentNode == this.$container) + && xmlParentNode == this.xmlRoot && !beforeNode + || action == "insert") { + this.$nodes.push(htmlNode); + if (!apf.isChildOf(htmlNode, container, true) && removeContainer) + this.$nodes.push(container); + + if (action != "insert") { + this.$setStyleClass(htmlNode, "root"); + this.$setStyleClass(container, "root"); + } + } + else { + if (!htmlParentNode) { + htmlParentNode = apf.xmldb.getHtmlNode(xmlNode.parentNode, this); + htmlParentNode = htmlParentNode + ? this.$getLayoutNode("item", "container", htmlParentNode) + : this.$container; + } + + if (htmlParentNode == this.$container) { + this.$setStyleClass(htmlNode, "root"); + this.$setStyleClass(container, "root"); + } + + var next; + if (!beforeNode && (next = this.getNextTraverse(xmlNode))) + beforeNode = apf.xmldb.getHtmlNode(next, this); + if (beforeNode && beforeNode.parentNode != htmlParentNode) + beforeNode = null; + + if (htmlParentNode.style + && this.getTraverseNodes(xmlNode.parentNode).length == 1) + this.$removeEmptyMessage(htmlParentNode); + + //alert("|" + htmlNode.nodeType + "-" + htmlParentNode.nodeType + "-" + beforeNode + ":" + container.nodeType); + //Insert Node into Tree + if (htmlParentNode.style) { + var isChildOfHtmlNode = !apf.isChildOf(htmlNode, container, true) + htmlNode = apf.insertHtmlNode(htmlNode, htmlParentNode, beforeNode); + if (isChildOfHtmlNode && removeContainer) + var container = apf.insertHtmlNode(container, + htmlParentNode, beforeNode); + else + var container = this.$getLayoutNode("item", "container", htmlNode); + } + else { + htmlParentNode.insertBefore(htmlNode, beforeNode); + if (!apf.isChildOf(htmlNode, container, true) && removeContainer) + htmlParentNode.insertBefore(container, beforeNode); + } + + //Fix parent if child is added to drawn parentNode + if (htmlParentNode.style) { + if (this.openadd && htmlParentNode != this.$container + && htmlParentNode.style.display != "block") + this.slideOpen(htmlParentNode, xmlParentNode, true); + + if (!this.$fillParent) + this.$fillParent = xmlParentNode; + + var next = nextNode == undefined ? this.getNextTraverse(xmlNode, true) : nextNode; + + var html; + if (next && (html = apf.xmldb.getHtmlNode(next, this))) //should use each here + this.$fixItem(next, html); + } + } + + if ((this.prerender || eachLength < 3 || !startcollapsed) && (xmlNode.namespaceURI != apf.ns.apf || xmlNode.localName != "item")) { + this.$addNodes(xmlNode, container, false, null, null, (depth || 0) + 1); //checkChildren ??? + } + /*else { + this.$setLoadStatus(xmlNode, "potential"); + }*/ + + return container; + }; + + this.$fill = function(){ + if (this.$useiframe) + this.$pHtmlDoc = this.oDoc; + + if (this.$nodes.length) { + apf.insertHtmlNodes(this.$nodes, this.$fillParentHtml || this.$container); + this.$nodes.length = 0; + delete this.$fillParentHtml; + } + + if (this.$fillParent) { + this.$fixItem(this.$fillParent, apf.xmldb.getHtmlNode(this.$fillParent, this)); + delete this.$fillParent; + } + }; + + this.$getParentNode = function(htmlNode){ + return htmlNode + ? this.$getLayoutNode("item", "container", htmlNode) + : this.$container; + }; + + this.$fixItem = function(xmlNode, htmlNode, isDeleting, oneLeft, noChildren){ + if (!htmlNode) return; + + if (isDeleting) { + //if isLast fix previousSibling + var prevSib; + if (prevSib = this.getNextTraverse(xmlNode, true)) + this.$fixItem(prevSib, this.$findHtmlNode(prevSib + .getAttribute(apf.xmldb.xmlIdTag) + "|" + + this.$uniqueId), null, true); + + //if no sibling fix parent + if (!this.emptyMessage && xmlNode.parentNode.selectNodes(this.each).length == 1) + this.$fixItem(xmlNode.parentNode, this.$findHtmlNode( + xmlNode.parentNode.getAttribute(apf.xmldb.xmlIdTag) + + "|" + this.$uniqueId), null, false, true); + } + else { + var container = this.$getLayoutNode("item", "container", htmlNode), + hasChildren = false; + if (noChildren) + hasChildren = false; + else if (this.getTraverseNodes(xmlNode).length > 0) + hasChildren = true; + else if (this.$hasLoadStatus(xmlNode, "potential")) + hasChildren = true; + else + hasChildren = false; + + var isClosed = hasChildren && htmlNode.className.indexOf("plus") > -1;//container.style.display != "block", + isLast = this.getNextTraverse(xmlNode, null, oneLeft ? 2 : 1) + ? false + : true, + state = (hasChildren ? HAS_CHILD : 0) + | (isClosed ? IS_CLOSED : 0) | (isLast ? IS_LAST : 0); + this.$setStyleClass(this.$getLayoutNode("item", "class", htmlNode), + treeState[state], ["min", "plus", "last", "minlast", "pluslast"]); + this.$setStyleClass(this.$getLayoutNode("item", "container", htmlNode), + treeState[state], ["min", "plus", "last", "minlast", "pluslast"]); + + if(this.$getLayoutNode("item", "openclose", htmlNode)) + this.$getLayoutNode("item", "openclose", htmlNode) + .setAttribute("children", hasChildren); + + if (container) { + if (!hasChildren) + container.style.display = "none"; + else if (!isClosed) + container.style.display = "block"; + } + } + }; + + this.$deInitNode = function(xmlNode, htmlNode){ + //Lookup container + var containerNode = this.$getLayoutNode("item", "container", htmlNode), + pContainer = htmlNode.parentNode; + + //Remove htmlNodes from tree + containerNode.parentNode.removeChild(containerNode); + pContainer.removeChild(htmlNode); + + //Datagrid?? + if (this.$withContainer) + htmlNode.parentNode.removeChild(htmlNode.nextSibling); + + //Fix Images (+, - and lines) + if (xmlNode.parentNode != this.xmlRoot) + this.$fixItem(xmlNode, htmlNode, true); + + var msg; + if (!pContainer.childNodes.length && (msg = this.$applyBindRule("empty", xmlNode))) + this.$setEmptyMessage(pContainer, msg); + + //Fix look (tree thing) + this.$fixItem(xmlNode, htmlNode, true); + //this.$fixItem(xmlNode.parentNode, apf.xmldb.findHtmlNode(xmlNode.parentNode, this)); + /*throw new Error(); + if(xmlNode.previousSibling) //should use each here + this.$fixItem(xmlNode.previousSibling, apf.xmldb.findHtmlNode(xmlNode.previousSibling, this));*/ + }; + + this.$moveNode = function(xmlNode, htmlNode, oldXmlParent){ + if (!self.apf.debug && !htmlNode) + return; + + var container; + if (this.$hasLoadStatus(xmlNode.parentNode, "potential")) { + container = this.$getLayoutNode("item", "container", htmlNode); + htmlNode.parentNode.removeChild(htmlNode); + container.parentNode.removeChild(container); + this.$extend(xmlNode.parentNode); + return; + } + + var oPHtmlNode = htmlNode.parentNode, + tParent = this.getTraverseParent(xmlNode), + pHtmlNode = apf.xmldb.getHtmlNode(tParent, this), + //if(!pHtmlNode) return; + + nSibling = this.getNextTraverse(xmlNode), + beforeNode = nSibling + ? apf.xmldb.getHtmlNode(nSibling, this) + : null, + pContainer = pHtmlNode + ? this.$getLayoutNode("item", "container", pHtmlNode) + : this.$container; + + container = this.$getLayoutNode("item", "container", htmlNode); + + if (pContainer != oPHtmlNode && this.getTraverseNodes(xmlNode.parentNode).length == 1) + this.$removeEmptyMessage(pContainer); + + pContainer.insertBefore(htmlNode, beforeNode); + if (container) + pContainer.insertBefore(container, beforeNode); + + /*if (!this.startcollapsed) { + pContainer.style.display = "block"; + pContainer.style.height = "auto"; + }*/ + + var msg; + if (!this.getTraverseNodes(oldXmlParent).length && (msg = this.$applyBindRule("empty", oldXmlParent))) + this.$setEmptyMessage(oPHtmlNode, msg); + +// if (this.openadd && pHtmlNode != this.$container && pContainer.style.display != "block") +// this.slideOpen(pContainer, pHtmlNode, true); + + //Fix look (tree thing) + this.$fixItem(xmlNode, htmlNode); + + this.$fixItem(tParent, apf.xmldb.getHtmlNode(tParent, this)); + this.$updateNode(oldXmlParent, apf.xmldb.getHtmlNode(oldXmlParent, this)); + var next; + if (next = this.getNextTraverse(xmlNode, true)) { //should use each here + this.$fixItem(next, apf.xmldb.getHtmlNode(next, this)); + } + }; + + //??? + this.$setLoading = function(xmlNode, container){ + this.$setLoadStatus(xmlNode, "potential"); + + var len = this.getTraverseNodes(xmlNode).length; + if (!len || len > 20) { + this.$getNewContext("loading"); + apf.insertHtmlNode(this.$getLayoutNode("loading"), container); + } + }; + + //??? + this.$removeLoading = function(htmlNode){ + if (!htmlNode) return; + this.$getLayoutNode("item", "container", htmlNode).innerHTML = ""; + }; + + //check databinding for how this is normally implemented + this.$extend = function(xmlNode, container, immediate, callback){ + if (!this.$hasLoadStatus(xmlNode, "potential")) + return; + + var rule = this.$getBindRule("insert", xmlNode), + xmlContext = rule && rule.match + ? (rule.cmatch || rule.compile("match"))(xmlNode) + : xmlNode; + + if (rule && xmlContext) { + this.$setLoadStatus(xmlNode, "loading"); + + if (rule.get) { + + if (!apf.offline.onLine) { + apf.offline.transactions.actionNotAllowed(); + this.slideClose(container, xmlNode); + return; + } + + + this.getModel().$insertFrom(rule.getAttribute("get"), { + xmlNode : xmlContext, + insertPoint : xmlContext, + amlNode : this, + callback : callback + }); + } + else { + if (this.$applyBindRule("insert", xmlNode)) + this.insert(data, {insertPoint: xmlContext}); + } + } + else if (!this.prerender) { + this.$setLoadStatus(xmlNode, "loaded"); + this.$removeLoading(apf.xmldb.getHtmlNode(xmlNode, this)); + xmlUpdateHandler.call(this, { + action : "insert", + xmlNode : xmlNode, + result : this.$addNodes(xmlNode, container, true), //checkChildren ??? + anim : !immediate + }); + } + }; + + function xmlUpdateHandler(e){ + /* + Display the animation if the item added is + * Not in the cache + - Being insterted using xmlUpdate + - there is at least 1 child inserted + */ + + if (e.action == "move-away") + this.$fixItem(e.xmlNode, apf.xmldb.findHtmlNode(e.xmlNode, this), true); + + if (e.action != "insert") return; + + var htmlNode = this.$findHtmlNode(e.xmlNode.getAttribute( + apf.xmldb.xmlIdTag) + "|" + this.$uniqueId); + if (!htmlNode) return; + + //this.$hasLoadStatus(e.xmlNode, "loading") + if (e.action == "insert" && e.result.length > 0) { + if (this.$hasLoadStatus(e.xmlNode, "loaded", true)) { + var container = this.$getLayoutNode("item", "container", htmlNode); + this.slideOpen(container, e.xmlNode);//, e.$anim ? false : true + } + else if (this.$hasLoadStatus(e.xmlNode, "potential", true)) { + this.$setLoadStatus(e.xmlNode, "loaded"); + } + } + else + this.$fixItem(e.xmlNode, htmlNode); + + //Can this be removed?? (because it was added in the insert function) + //if (this.$hasLoadStatus(e.xmlNode, "loading")) + //this.$setLoadStatus(e.xmlNode, "loaded"); + } + + this.addEventListener("xmlupdate", xmlUpdateHandler); + + /**** Keyboard Support ****/ + + + this.addEventListener("beforerename", function(){ + if (this.$tempsel) { + clearTimeout(this.timer); + this.select(this.$tempsel); + + this.$tempsel = null; + this.timer = null; + } + }); + + + + this.addEventListener("keydown", function(e){ + var key = e.keyCode, + ctrlKey = e.ctrlKey, + shiftKey = e.shiftKey, + selHtml = this.$caret || this.$selected, + pos, top, el, node, nodes, sNode, pNode, container; + + if (e.returnValue == -1 || !selHtml || this.renaming) //@todo how about allowdeselect? + return; + + var selXml = this.caret || this.selected, + oExt = this.$container; + + switch (key) { + case 13: + if (this.$tempsel) + this.$selectTemp(); + + if (this.ctrlselect == "enter") + this.select(this.caret, true); + + this.choose(selHtml); + break; + case 32: + if (this.$tempsel) + this.$selectTemp(); + + + if (this.$mode && !ctrlKey) { + var sel = this.getSelection(); + if (!sel.length || !this.multiselect) + this.checkToggle(this.caret, true); + else + this.checkList(sel, this.isChecked(this.selected), true, false, true); + } + else + + if (ctrlKey || !this.isSelected(this.caret)) + this.select(this.caret, true); + return false; + case 46: + if (this.$tempsel) + this.$selectTemp(); + + //DELETE + //this.remove(); + this.remove(this.caret); //this.mode != "check" + break; + case 36: + //HOME + this.$setTempSelected(this.getFirstTraverseNode(), false, shiftKey); + oExt.scrollTop = 0; + return false; + case 35: + //END + var lastNode = this.getLastTraverseNode(); + while (!this.isCollapsed(lastNode)) + lastNode = this.getLastTraverseNode(lastNode); + + this.$setTempSelected(lastNode, false, shiftKey, true); + oExt.scrollTop = oExt.scrollHeight; + return false; + case 37: + //LEFT + if (this.$tempsel) + this.$selectTemp(); + if (this.caret.selectSingleNode(this.each) + && !this.isCollapsed(this.caret)) + this.slideToggle(this.$caret || this.$selected, 2) + else if ((pNode = this.getTraverseParent(this.caret)) + && pNode != this.xmlRoot) + this.select(pNode) + return false; + case 107: //+ + case 187: //+ + case 39: + //RIGHT + if (this.$tempsel) + this.$selectTemp(); + + if (this.$hasLoadStatus(this.caret, "potential") + || this.getFirstTraverseNode(this.caret)) + this.slideToggle(this.$caret || this.$selected, 1) + break; + case 109: + case 189: + //- + if (this.getFirstTraverseNode(this.caret)) + this.slideToggle(this.$caret || this.$selected, 2) + break; + case 38: + //UP + if (!selXml && !this.$tempsel) + return; + + node = this.$tempsel + ? apf.xmldb.getNode(this.$tempsel) + : selXml; + + sNode = this.getNextTraverse(node, true); + if (sNode) { + nodes = this.getTraverseNodes(sNode); + + do { + container = this.$getLayoutNode("item", "container", + this.$findHtmlNode(apf.xmldb.getID(sNode, this))); + if (container && apf.getStyle(container, "display") == "block" + && nodes.length) { + sNode = nodes[nodes.length-1]; + } + else { + break; + } + } + while (sNode && (nodes = this.getTraverseNodes(sNode)).length); + } + else if (this.getTraverseParent(node) == this.xmlRoot) { + this.dispatchEvent("selecttop"); + return; + } + else + sNode = this.getTraverseParent(node); + + if (sNode && sNode.nodeType == 1) + this.$setTempSelected(sNode, ctrlKey, shiftKey, true); + else + return false; + + selHtml = apf.xmldb.getHtmlNode(sNode, this); + top = apf.getAbsolutePosition(selHtml, this.$container)[1] + - (selHtml.offsetHeight); + if (top <= oExt.scrollTop) + oExt.scrollTop = top; + + return false; + case 40: + //DOWN + if (!selXml && !this.$tempsel) + return; + + node = this.$tempsel + ? apf.xmldb.getNode(this.$tempsel) + : selXml; + + sNode = this.getFirstTraverseNode(node); + if (sNode) { + container = this.$getLayoutNode("item", "container", + this.$findHtmlNode(apf.xmldb.getID(node, this))); + if (container && apf.getStyle(container, "display") != "block") + sNode = null; + } + + while (!sNode) { + pNode = this.getTraverseParent(node); + if (!pNode) break; + + var i = 0; + nodes = this.getTraverseNodes(pNode); + while (nodes[i] && nodes[i] != node) + i++; + sNode = nodes[i+1]; + node = pNode; + } + + if (sNode && sNode.nodeType == 1) + this.$setTempSelected(sNode, ctrlKey, shiftKey); + else + return false; + + selHtml = apf.xmldb.getHtmlNode(sNode, this); + top = apf.getAbsolutePosition(selHtml, this.$container)[1] + + (selHtml.offsetHeight); + if (top > oExt.scrollTop + oExt.offsetHeight) + oExt.scrollTop = top - oExt.offsetHeight; + return false; + case 33: //@todo + //PGUP + pos = apf.getAbsolutePosition(this.$container); + el = document.elementFromPoint(pos[0] + this.$container.offsetWidth + - 2, pos[1] + 2); + sNode = apf.xmldb.findXmlNode(el); + if (sNode == this.selected) { + oExt.scrollTop -= oExt.offsetHeight - apf.getHeightDiff(oExt); + el = document.elementFromPoint(pos[0] + this.$container.offsetWidth + - 2, pos[1] + 2); + sNode = apf.xmldb.findXmlNode(el); + } + this.select(sNode); + + selHtml = apf.xmldb.getHtmlNode(sNode, this); + top = apf.getAbsolutePosition(selHtml, this.$container)[1] + - (selHtml.offsetHeight); + if (top <= oExt.scrollTop) + oExt.scrollTop = top; + break; + case 34: //@todo + //PGDN + pos = apf.getAbsolutePosition(this.$container); + el = document.elementFromPoint(pos[0] + this.$container.offsetWidth + - 2, pos[1] + this.$ext.offsetHeight - 4); + sNode = apf.xmldb.findXmlNode(el); + if (sNode == this.selected) { + oExt.scrollTop += oExt.offsetHeight - apf.getHeightDiff(oExt); + el = document.elementFromPoint(pos[0] + this.$container.offsetWidth + - 2, pos[1] + this.$ext.offsetHeight - 4); + sNode = apf.xmldb.findXmlNode(el); + } + this.select(sNode); + + selHtml = apf.xmldb.getHtmlNode(sNode, this); + top = apf.getAbsolutePosition(selHtml, this.$container)[1] + + (selHtml.offsetHeight); + if (top > oExt.scrollTop + oExt.offsetHeight) + oExt.scrollTop = top - oExt.offsetHeight; + break; + default: + if (this.celledit) { + if (!ctrlKey && !e.altKey && (key > 46 && key < 112 || key > 123)) + this.startRename(null, true); + return; + } + else if (key == 65 && ctrlKey) { + this.selectAll(); + return false; + } + //@todo make this work with the sorted column + else if (this.caption || (this.bindingRules || {})["caption"]) { + if (!this.xmlRoot) return; + + //this should move to a onkeypress based function + if (!this.lookup || new Date().getTime() + - this.lookup.date.getTime() > 300) + this.lookup = { + str : "", + date : new Date() + }; + + this.lookup.str += String.fromCharCode(key); + + var nodes = this.getTraverseNodes(); //@todo start at current indicator + for (var v, i = 0; i < nodes.length; i++) { + v = this.$applyBindRule("caption", nodes[i]); + if (v && v.substr(0, this.lookup.str.length) + .toUpperCase() == this.lookup.str) { + + if (!this.isSelected(nodes[i])) + this.select(nodes[i]); + + if (selHtml) + this.$container.scrollTop = selHtml.offsetTop + - (this.$container.offsetHeight + - selHtml.offsetHeight) / 2; + return; + } + } + return; + } + break; + } + }, true); + + + /**** Rename Support ****/ + + + this.$getCaptionElement = function(){ + if (!this.$selected) return false; + var x = this.$getLayoutNode("item", "caption", this.$selected); + return x.nodeType == 1 ? x : x.parentNode; + }; + + + /**** Selection Support ****/ + /* + nodes = this.hasFeature(apf.__VIRTUALVIEWPORT__) + ? this.xmlRoot.selectNodes(this.$isTreeArch + ? this.each + : ".//" + this.each.split('|').join('|.//')) + : + */ + this.$calcSelectRange = function(xmlStartNode, xmlEndNode){ + var r = [], + f = false, + i = 0, + pNodeStart = this.getTraverseParent(xmlStartNode), + pNodeEnd = this.getTraverseParent(xmlEndNode), + nodes = this.getTraverseNodes(pNodeStart); + + for (; i < nodes.length; i++) { + if (nodes[i] == xmlStartNode) + f = true; + if (f) + r.push(nodes[i]); + if (nodes[i] == xmlEndNode) + f = false; + } + + if (!r.length || f) { + r = []; + for (f = false, i = nodes.length - 1; i >= 0; i--) { + if (nodes[i] == xmlStartNode) + f = true; + if (f) + r.push(nodes[i]); + if (nodes[i] == xmlEndNode) + f = false; + } + } + + return r; + }; + + this.$findContainer = function(htmlNode){ + return this.$getLayoutNode("item", "container", htmlNode); + }; + + this.$selectDefault = function(xmlNode){ + if (this.select(this.getFirstTraverseNode(xmlNode), null, null, null, true)) { + return true; + } + else { + var nodes = this.getTraverseNodes(xmlNode); + for (var i = 0; i < nodes.length; i++) { + if (this.$selectDefault(nodes[i])) + return true; + } + } + }; + + this.$setEmptyMessage = function(htmlNode, msg){ + this.$getNewContext("empty"); + var xmlEmpty = this.$getLayoutNode("empty"); + if (!xmlEmpty) return; + + var empty = apf.insertHtmlNode(xmlEmpty, htmlNode); + empty.setAttribute("empty", "true"); + var caption = this.$getLayoutNode("empty", "caption", empty); + + if (caption) + apf.setNodeValue(caption, msg || ""); + + if (htmlNode.style) + this.slideOpen(htmlNode, null, true); + else if (this.nocollapse) + htmlNode.setAttribute("style", "display:inline-block;"); + else + htmlNode.setAttribute("style", "overflow:visible;height:auto;display:block;"); + + } + + this.$removeEmptyMessage = function(htmlNode){ + var cNode = htmlNode.firstElementChild || htmlNode.firstChild; + if (!cNode) + return; + + do { + if (cNode.getAttribute && cNode.getAttribute("empty")) { //@todo hack + htmlNode.removeChild(cNode); + return; + } + cNode = cNode.nextSibling; + } + while(cNode); + } + + /**** Init ****/ + + /** + * @event click Fires when the user presses a mousebutton while over this + * element and then let's the mousebutton go. + * @see baseclass.multiselect.event.beforeselect + * @see baseclass.multiselect.event.afterselect + * @see baseclass.multiselect.event.beforechoose + * @see baseclass.multiselect.event.afterchoose + */ + this.$drawBase = function(){ + //@todo apf3.0 checkmode, radiomode + /*if (!this.getAttribute("skin")) { + var mode = this.getAttribute("mode"); + this.skinName = null; + this.skin = mode + "tree"; + this.$loadSkin(); + }*/ + + //Build Main Skin + this.$ext = this.$getExternal(); + this.$container = this.$getLayoutNode("main", "container", this.$ext); + this.opencloseaction = this.$getOption("main", "openclose"); + + //Need fix... + //this.$ext.style.MozUserSelect = "none"; + + if (apf.hasCssUpdateScrollbarBug && !this.mode) + this.$fixScrollBug(); + + var _self = this; + this.$ext.onclick = function(e){ + _self.dispatchEvent("click", {htmlEvent : e || event}); + }; + this.$ext.onmousedown = function(e){ + _self.dispatchEvent("mousedown", {htmlEvent : e || event}); + }; + this.$ext.onmouseover = function(e){ + _self.dispatchEvent("mouseover", {htmlEvent : e || event}); + }; + this.$ext.onmousemove = function(e){ + _self.dispatchEvent("mousemove", {htmlEvent : e || event}); + }; + }; + + this.addEventListener("DOMNodeInsertedIntoDocument", function(){ + if (this.nocollapse) + this.startcollapsed = false; + else if (this.startcollapsed === 1) + this.startcollapsed = !apf.isFalse(this.$getOption("main", "startcollapsed")); + }); + + this.addEventListener("DOMNodeRemovedFromDocument", function(){ + this.$ext.onclick = null; + + apf.destroyHtmlNode(this.oDrag); + this.oDrag = null; + }); + +}).call(apf.BaseTree.prototype = new apf.MultiSelect()); + + + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/core/baseclasses/delayedrender.js)SIZE(5243)TIME(Mon, 10 Jan 2011 10:53:11 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + +apf.__DELAYEDRENDER__ = 1 << 11 + + + +/** + * All elements inheriting from this {@link term.baseclass baseclass} have delayed + * rendering features. Any element that is (partially) hidden at startup has the + * possibility to delay rendering it's childNodes by setting render="runtime" on + * the element. These elements include window, tab, pages, form and container. + * For instance a Tab page in a container is initally hidden and does not + * need to be rendered. When the tab button is pressed to activate the page + * the page is rendered and then displayed. This can dramatically decrease + * the startup time of the application. + * Example: + * In this example the button isn't rendered until the advanced tab becomes active. + * + * + * + * ... + * + * + * OK + * + * + * + * + * @event beforerender Fires before elements are rendered. Use this event to display a loader. + * cancelable: Prevents rendering of the childNodes + * @event afterrender Fires after elements are rendered. User this event to hide a loader. + * + * @attribute {String} render when the contents of this element is rendered. + * Possible values: + * init elements are rendered during init of the application. + * runtime elements are rendered when the user requests them. + * @attribute {Boolean} use-render-delay whether there's a short delay between showing this element and rendering it's contents. + * Possible values: + * true The elements are rendered immediately + * false There is a delay between showing this element and the actual rendering, + * allowing the browsers' render engine to draw (for instance a loader). + * + * @constructor + * @baseclass + * @author Ruben Daniels (ruben AT ajax DOT org) + * @version %I%, %G% + * @since 0.8.9 + */ +apf.DelayedRender = function(){ + this.$regbase = this.$regbase | apf.__DELAYEDRENDER__; + this.$rendered = false; + + /** + * Renders the children of this element. + * + * @param {Boolean} [usedelay] whether a delay is added between calling + * this function and the actual rendering. This allows the browsers' + * render engine to draw (for instance a loader). + */ + this.$render = function(usedelay){ + if (this.$rendered) + return; + + if (this.dispatchEvent("beforerender") === false) + return; + + if (this["render-delay"] || usedelay) + $setTimeout("apf.lookup(" + this.$uniqueId + ").$renderparse()", 10); + else + this.$renderparse(); + }; + + this.$renderparse = function(){ + if (this.$rendered) + return; + + // Hide render pass from sight for inner callstack + // redrawing browsers like firefox + this.$ext.style.visibility = "hidden"; + + var domParser = this.ownerDocument.$domParser; + domParser.parseFromXml(this.$aml, { + amlNode : this, + doc : this.ownerDocument, + //nodelay : true, + delayedRender : true + }); + domParser.$continueParsing(this); + + this.$rendered = true; + + this.dispatchEvent("afterrender"); + + this.$ext.style.visibility = ""; + }; + + var f; + this.addEventListener("prop.visible", f = function(){ + if (arguments[0].value) { + + this.$render(); + + + this.removeEventListener("prop.visible", f); + } + }); +}; + +apf.GuiElement.propHandlers["render"] = function(value) { + if (!this.hasFeature(apf.__DELAYEDRENDER__) && value == "runtime") { + this.implement(apf.DelayedRender); + + if (this.localName != "page") { + this.visible = false; + this.$ext.style.display = "none"; + } + + if (typeof this["render-delay"] == "undefined") + this.$setInheritedAttribute("render-delay"); + } +}; + +apf.config.$inheritProperties["render-delay"] = 1; + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/core/baseclasses/dragdrop.js)SIZE(54759)TIME(Mon, 10 Jan 2011 10:53:11 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + +apf.__DRAGDROP__ = 1 << 5; + + + +/** + * All elements inheriting from this {@link term.baseclass baseclass} have drag&drop + * features. This baseclass operates on the bound data of this element. + * When a rendered item is dragged and dropped the bound data is moved or + * copied from one element to another, or to the same element but at a different + * position. This is possible because the rendered item has a + * {@link term.smartbinding bidirectional connection} to the data. Drag&drop can + * be turned on with a simple boolean, or by specifying detailed rules to set + * which data can be dragged and dropped and where. + * + * Example: + * This is a simple example, enabling drag&drop for a list. + * + * + * + * + * Example: + * This example shows a smartbinding that represents files and folders. It uses + * {@link term.datainstruction data instruction} to tell communicat to the webdav + * server when an item is copied or moved. + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * @event dragdata Fires before a drag&drop operation is started to determine the data that is dragged. + * object: + * {XMLElement} data the default data for the drag&drop operation + * @event dragstart Fires before a drag operation is started. + * cancelable: Prevents the drag operation to start. + * object: + * {XMLElement} data the data for the drag&drop operation + * {XMLElement} selection the selection at the start of the drag operation + * {HTMLElement} indicator the html element that is shown while dragging the data + * {AMLElement} host the aml source element. + * @event dragover Fires when the users drags over this aml element. + * cancelable: Prevents the possibility to drop. + * object: + * {XMLElement} data the data for the drag&drop operation + * {XMLElement} selection the selection at the start of the drag operation + * {HTMLElement} indicator the html element that is shown while dragging the data + * {AMLElement} host the aml source element. + * @event dragout Fires when the user moves away from this aml element. + * object: + * {XMLElement} data the data for the drag&drop operation + * {XMLElement} selection the selection at the start of the drag operation + * {HTMLElement} indicator the html element that is shown while dragging the data + * {AMLElement} host the aml source element. + * @event dragdrop Fires when the user drops an item on this aml element. + * cancelable: Prevents the possibility to drop. + * object: + * {XMLElement} data the data for the drag&drop operation + * {XMLElement} selection the selection at the start of the drag operation + * {HTMLElement} indicator the html element that is shown while dragging the data + * {AMLElement} host the aml source element. + * {Boolean} candrop whether the data can be inserted at the point hovered over by the user + * + * @see element.drag + * @see element.drop + * @see element.dragdrop + * + * @define dragdrop + * @allowchild drop, drag + * @define drag Determines whether a {@link term.datanode data node} can + * be dragged from this element. + * Example: + * This example shows a small mail application. The tree element displays a root + * node, accounts and folders in a tree. The datagrid contains the mails. This + * rule specifies which data nodes can be dropped where. Folders can be dropped + * in folders and accounts. Mails can be dropped in folders. + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * @attribute {String} match an xpath statement querying the + * {@link term.datanode data node} that is + * dragged. If the query matches a node it + * is allowed to be dropped. The xpath is + * automatically prefixed by 'self::'. + * @attribute {String} copy a javascript expression that determines + * whether the dragged element is a copy or + * a move. Use event.ctrlKey to use the Ctrl + * key to determine whether the element is copied. + * + * @define drop Determines whether a {@link term.datanode data node} can + * be dropped on a data node bound to this element. + * + * @attribute {String} match an xpath statement querying the + * {@link term.datanode data node} that is + * dragged. If the query matches a node it + * is allowed to be dropped. The xpath is + * automatically prefixed by 'self::'. + * @attribute {String} target an xpath statement determining the new + * parent of the dropped {@link term.datanode data node}. + * The xpath is automatically prefixed by 'self::'. + * @attribute {String} action the action to perform when the + * {@link term.datanode data node} is inserted. + * Possible values: + * tree-append Appends the {@link term.datanode data node} to the element it's dropped on. + * list-append Appends the {@link term.datanode data node} to the root element of this element. + * insert-before Inserts the {@link term.datanode data node} before the elements it's dropped on. + * @attribute {String} copy a javascript expression that determines + * whether the drop is a copy or a move. + * Use event.ctrlKey to use the Ctrl key to + * determine whether the element is copied. + */ +/** + * @constructor + * @baseclass + * @author Ruben Daniels (ruben AT ajax DOT org) + * @version %I%, %G% + * @since 0.5 + */ +apf.DragDrop = function(){ + this.$regbase = this.$regbase | apf.__DRAGDROP__; + + this.$dragInited = false; + + /* ********************** + Actions + ***********************/ + + /** + * Copies a {@link term.datanode data node} to the bound data of this element. + * + * @action + * @param {XMLElement} xmlNode the {@link term.datanode data node} which is copied. + * @param {XMLElement} pNode the new parent element of the copied + * {@link term.datanode data node}. If none + * specified the root element of the data + * loaded in this element is used. + * @param {XMLElement} [beforeNode] the position where the {@link term.datanode data node} + * is inserted. + */ + this.copy = function(nodeList, pNode, beforeNode, isMove){ + if (nodeList.nodeType) + nodeList = [nodeList]; + + var exec, + changes = [], + i = 0, + l = nodeList.length; + for (; i < l; i++) { + changes.push({ + action : isMove ? "moveNode" : "appendChild", + args : [pNode, isMove + ? nodeList[i] + : nodeList[i] = nodeList[i].cloneNode(true), beforeNode] + }); + } + + if (this.$actions[(isMove ? "movegroup" : "copygroup")]) { + exec = this.$executeAction("multicall", changes, + (isMove ? "movegroup" : "copygroup"), nodeList[0]); + } + else { + exec = this.$executeAction("multicall", changes, + (isMove ? "move" : "copy"), nodeList[0], null, null, + nodeList.length > 1 ? nodeList : null); + } + + if (exec !== false) + return nodeList; + + return false; + }; + + /** + * Moves a {@link term.datanode data node} to the bound data of this element. + * + * @action + * @param {XMLElement} xmlNode the {@link term.datanode data node} which is copied. + * @param {XMLElement} pNode the new parent element of the moved + * {@link term.datanode data node}. If none + * specified the root element of the data + * loaded in this element is used. + * @param {XMLElement} [beforeNode] the position where the + * {@link term.datanode data node} is inserted. + */ + this.move = function(nodeList, pNode, beforeNode){ + return this.copy(nodeList, pNode, beforeNode, true); + }; + + /** + * Determines whether the user is allowed to drag the passed + * {@link term.datanode data node}. The decision is made based on the + * {@link element.drag drag} and {@link element.drag drag} + * rules. These elements determine when a data node can be dropped on + * another data node. For instance, imagine a mail application with a root + * node, accounts and folders in a tree, and mails in a datagrid. The rules + * would specify you can drag&drop folders within an account, and emails between + * folders, but not on accounts or the root. + * + * @param {XMLElement} dataNode the {@link term.datanode data node} subject to the test. + * @return {Boolean} result of the test + * @see baseclass.dragdrop.method.isDragAllowed + */ + this.isDragAllowed = function(x, data){ + + if (typeof apf.offline != "undefined" && !apf.offline.canTransact()) + return false; + + + if (this.disabled || !x || !x.length || !x[0]) + return false; + + if (this.drag || this.dragcopy) { + if (data) + data.merge(x); + return true; + } + + /*var rules = this.$bindings["drag"] + || this.$attrBindings && this.$attrBindings["drag"]; + if (!rules || !rules.length) + return false;*/ + + var d, + ruleList = [], + j = 0, + l = x.length; + for (; j < l; j++) { + d = this.$getDataNode("drag", x[j], null, ruleList); + if (!d) return false; //It's all or nothing + if (data) + data.push(d); + } + + return ruleList.length ? ruleList : false; + }; + + /** + * Determines whether the user is allowed to dropped the passed + * {@link term.datanode data node}. The decision is made based on the + * {@link element.drag drag} and {@link element.drag drag} + * rules. These elements determine when a data node can be dropped on + * another data node. For instance, imagine a mail application with a root + * node, accounts and folders in a tree, and mails in a datagrid. The rules + * would specify you can drag&drop folders within an account, and emails between + * folders, but not on accounts or the root. + * + * @param {XMLElement} dataNode the {@link term.datanode data node} subject + * to the test. + * @param {XMLElement} target the {@link term.datanode data node} on which + * the dragged data node is dropped. + * @return {Boolean} result of the test + * @see baseclass.dragdrop.method.isDragAllowed + */ + this.isDropAllowed = function(x, target){ + + if(typeof apf.offline != "undefined" && !apf.offline.canTransact()) + return false; + + + if (this.disabled || !x || !x.length || !target) //!x[0] ??? + return false; + + var data, tgt, hasDropRule = this.$attrBindings && this.$attrBindings["drop"]; + if (this.drop && (!hasDropRule || hasDropRule.value == "true")) { + this.$setDynamicProperty("drop", this.hasFeature(apf.__MULTISELECT__) + ? "[" + this.each + "]" + : "[node()]"); //@todo apf3.0 make sure each is without {} + hasDropRule = true; + } + + if (hasDropRule) { + for (var j = 0, l = x.length; j < l; j++) { + data = this.$getDataNode("drop", x[j]); + if (!data) + break; + } + if (j == l && target && !apf.isChildOf(data, target, true)) + return [target, null]; + } + + var rules = this.$bindings["drop"]; + if (!rules || !rules.length) + return false; + + //@todo this can be optimized when needed + var rule, strTgt, + i = 0, + rl = rules.length; + for (; i < rl; i++) { + rule = this.$bindings.getRuleIndex("drop", i); + + for (var j = 0, l = x.length; j < l; j++) { + data = rule.cvalue ? rule.cvalue(x[j]) : rule.cmatch(x[j]); + if (!data) + break; + } + if (j != l) + continue; + + strTgt = rule.target;//node.getAttribute("target"); + if (!strTgt || strTgt == ".") { + //op = node.getAttribute("action") + //|| (this.$isTreeArch ? "tree-append" : "list-append"); + tgt = target;/*(op == "list-append" || target == this.xmlRoot + ? this.xmlRoot + : null);*/ + } + else { + tgt = (rule.ctarget || rule.compile("target"))(target); + } + + if (tgt && !apf.isChildOf(data, tgt, true)) + return [tgt, rule]; + } + + return false; + }; + + this.$dragDrop = function(xmlReceiver, xmlNodeList, rule, defaction, isParent, srcRule, event){ + // @todo apf3.0 action not known here yet... should be moved down? + if (action == "tree-append" && isParent) + return false; + + /* + Possibilities: + + tree-append [default]: xmlNode.appendChild(movedNode); + list-append : xmlNode.parentNode.appendChild(movedNode); + insert-before : xmlNode.parentNode.insertBefore(movedNode, xmlNode); + */ + var action = rule && rule.action;//node && node.getAttribute("action"); + + if (action) + action = (rule.caction || rule.compile("action"))(xmlNodeList[0]); + else + action = defaction; + + //copy convenience variables + var context = { + internal : apf.DragServer.dragdata.host == this, + ctrlKey : event.ctrlKey, + keyCode : event.keyCode + }, + //@todo apf3.0 below should actually be compileNode with with_options + ifcopy = rule && rule.copy;//.getAttribute("copy"); + + if (ifcopy) { + ifcopy = !apf.isFalse((rule.ccopy || rule.compile("copy"))(xmlNodeList[0], context)); + } + else if (typeof this.dragcopy == "boolean" || typeof this.dropcopy == "boolean") { //@todo apf3.0 boolean here? + if (this.dropcopy) { + ifcopy = this.dropcopy; + } + else if (this.dragcopy) { + ifcopy = event.ctrlKey; + } + else { + //@todo read this from src + var copyRule = this.$attrBindings && this.$attrBindings["dragcopy"]; + if (copyRule) { + ifcopy = !apf.isFalse((copyRule.cvalue2 + || copyRule.compile("value", { + withopt : true + }))(xmlNodeList[0], context)); + } + } + } + + if (!ifcopy) { //Implemented one copy is all copy + for (var i = 0, l = srcRule.length; i < l; i++) { + ifcopy = typeof srcRule[i] == "object" && srcRule[i].copy + ? !apf.isFalse((srcRule[i].ccopy || srcRule[i].compile("copy"))(xmlNodeList[0], context)) + : event.ctrlKey; + if (ifcopy) break; + } + } + + var sNode, + actRule = ifcopy ? "copy" : "move", + parentXpath = rule ? rule.getAttribute("parent") : null; //@todo apf3.0 Should be lm syntax + switch (action) { + case "list-append": + xmlReceiver = (isParent + ? xmlReceiver + : this.getTraverseParent(xmlReceiver)); + if (parentXpath) { + if (xmlReceiver.selectSingleNode(parentXpath)) + xmlReceiver = xmlReceiver.selectSingleNode(parentXpath); + else { + xmlReceiver.appendChild(xmlReceiver.ownerDocument.createElement(parentXpath)); + xmlReceiver = xmlReceiver.selectSingleNode(parentXpath); + } + } + sNode = this[actRule](xmlNodeList, xmlReceiver); + break; + case "insert-before": + sNode = isParent + ? this[actRule](xmlNodeList, xmlReceiver) + : this[actRule](xmlNodeList, xmlReceiver.parentNode, xmlReceiver); + break; + case "tree-append": + if (parentXpath) { + if (xmlReceiver.selectSingleNode(parentXpath)) + xmlReceiver = xmlReceiver.selectSingleNode(parentXpath); + else { + xmlReceiver.appendChild(xmlReceiver.ownerDocument.createElement(parentXpath)); + xmlReceiver = xmlReceiver.selectSingleNode(parentXpath); + } + } + sNode = this[actRule](xmlNodeList, xmlReceiver); + break; + } + + if (this.selectable && sNode) { + this.selectList(sNode);//, null, null, null, true); + this.setCaret(sNode[0]); + this.focus(); + } + + return sNode; + }; + + /* ********************** + Init + ***********************/ + + /** + * Loads the dragdrop rules from the dragdrop element + * + * @param {Array} rules the rules array created using {@link core.apf.method.getrules} + * @param {XMLElement} [node] the reference to the dragdrop element + * @see SmartBinding + * @private + */ + this.enableDragDrop = function(){ + + apf.console.info("Initializing Drag&Drop for " + this.localName + + "[" + (this.name || '') + "]"); + + + //Set cursors + //SHOULD come from skin + this.icoAllowed = "";//this.xmlDragDrop.getAttribute("allowed"); + this.icoDenied = "";//this.xmlDragDrop.getAttribute("denied"); + + //Setup External Object + this.$ext.dragdrop = false; + + var _self = this; + + this.$ext[apf.isIphone ? "ontouchstart" : "onmousedown"] = function(e){ + if (_self.disabled) + return; + + e = e || window.event; + + if (apf.isIphone) { + if (e.touches.length == 1) return; + var old_e = e; + e = e.touches[0]; + var pos = apf.getAbsolutePosition(e.target, this); + e.offsetX = pos[0]; + e.offsetY = pos[1]; + } + + + var fEl, + srcEl = e.originalTarget || e.srcElement || e.target, + multiselect = _self.hasFeature(apf.__MULTISELECT__); + if (multiselect && srcEl == _self.$container) + return; + _self.dragging = 0; + + try{ //Firefox can crash here because of some chrome permission issue + if (!apf.isIphone && _self.allowdeselect + && (srcEl == this || srcEl.getAttribute(apf.xmldb.htmlIdTag) + && _self.$getLayoutNode("item", "select", this) != this)) + return; //This broke making a selection with the mouse in rename: _self.clearSelection(); //@todo hacky - should detect what element has the select from the skin + }catch(e) {return;} + + //MultiSelect must have carret behaviour AND deselect at clicking white + if (_self.$findValueNode) + fEl = _self.$findValueNode(srcEl); + var el = (fEl + ? apf.xmldb.getNode(fEl) + : apf.xmldb.findXmlNode(srcEl)); + if (multiselect && (!_self.selected || !el || el == _self.xmlRoot)) + return; + + if (_self.isDragAllowed(multiselect ? _self.$getSelection() : el)) { + + if (apf.isIphone) + old_e.preventDefault(); + + + apf.DragServer.start(_self, srcEl, e); + } + + //e.cancelBubble = true; + }; + + this.$ext[apf.isIphone ? "ontouchmove" : "onmousemove"] = function(e){ + if (this.host.dragging != 1 || _self.disabled) return; + }; + + + if (apf.isIphone) { + this.$ext.ontouchend = this.$ext.ontouchcancel = function(){ + if (_self.disabled) + return; + + this.host.dragging = 0; + }; + } + else + + { + this.$ext.onmouseup = function(){ + if (_self.disabled) + return; + + this.host.dragging = 0; + }; + + this.$ext.ondragcopy = + this.$ext.ondragstart = function(){ return false; }; + } + + if (document.elementFromPointAdd) + document.elementFromPointAdd(this.$ext); + + if (this.$initDragDrop && !this.$dragInited) { + this.$initDragDrop(); + this.$dragInited = 2; + } + else { + this.$dragInited = true; + } + }; + + function disableDragDrop(){ + this.$dragInited = false; //@todo solve oExt event conflicts + + + if (apf.isIphone) { + this.$ext.ontouchstart = this.$ext.ontouchmove + = this.$ext.ontouchend = this.$ext.ontouchcancel = null; + } + else + + { + this.$ext.onmousedown = this.$ext.onmousemove + = this.$ext.onmouseup = null; + } + + if (document.elementFromPointRemove) + document.elementFromPointRemove(this.$ext); + } + + this.implement( + + this.hasFeature(apf.__MULTISELECT__) + ? apf.MultiselectDragDrop : + + apf.StandardDragDrop); + + //this.$booleanProperties["drag"] = true; + //this.$booleanProperties["dragcopy"] = true; + this.$supportedProperties.push("drop", "drag", "dragcopy"); + + /** + * @attribute {Boolean} drag whether the element allows dragging of it's items. + * Example: + * + * + * item 1 + * item 2 + * item 3 + * + * + * @attribute {Boolean} dragcopy whether dragged items are copied. + * Example: + * + * + * + * + * + * + * + * + * + * + * + * Example: + * Items are only copied when the user holds the Ctrl key + * + * + * item 1 + * item 2 + * item 3 + * + * + * @attribute {Boolean} drop whether the element allows items to be dropped. + * Example: + * + * + * item 1 + * item 2 + * item 3 + * + * + * @attribute {String} dragdrop the name of the dragdrop element for this element. + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + */ + this.$propHandlers["dragcopy"] = + this.$propHandlers["dropcopy"] = + this.$propHandlers["drag"] = + this.$propHandlers["drop"] = function(value, prop){ + this[prop] = apf.isTrue(value); + + if (this.$dragInited && prop == "drag" && value && this.$dragInited != 2) { + this.$initDragDrop(); + this.$dragInited = 2; + return; + } + + if (prop == "dragcopy" || prop == "dropcopy") + return; + + if (!value && !this.drag && !this.drop && !this.$bindings + && (this.$attrBindings && (!this.$attrBindings["drag"] || !this.$attrBindings["drop"]))) + disableDragDrop.call(this); + else if (value && !this.$dragInited) + this.enableDragDrop(); + }; + + this.addEventListener("DOMNodeRemovedFromDocument", function(e){ + disableDragDrop.call(this); + + if (this.oDrag) { + apf.destroyHtmlNode(this.oDrag); + this.oDrag = null; + } + }); +}; + +apf.GuiElement.propHandlers["dragcopy"] = +apf.GuiElement.propHandlers["dropcopy"] = +apf.GuiElement.propHandlers["drop"] = +apf.GuiElement.propHandlers["drag"] = function(value, prop) { + if (!apf.isFalse(value)) { + if (!this.hasFeature(apf.__DRAGDROP__)) { + this.implement(apf.DragDrop); + this.enableDragDrop(); + } + + this[prop] = apf.isTrue(value); + } +}; + +/** + * Central object for dragdrop handling. + * @private + */ +apf.DragServer = { + Init : function(){ + + if (apf.isIphone) { + this.ontouchmove = this.onmousemove; + this.ontouchend = this.ontouchcancel = this.onmouseup; + } + + + apf.addEventListener("hotkey", function(e){ + if (apf.window.dragging && e.keyCode == 27) { + if (document.body.lastHost && document.body.lastHost.dragOut) + document.body.lastHost.dragOut(apf.dragHost); + + return apf.DragServer.stopdrag(); + } + }); + }, + + start : function(amlNode, srcEl, e){ + if (document.elementFromPointReset) + document.elementFromPointReset(); + + amlNode.dragging = 1; + + var d = window.document; + d = (!d.compatMode || d.compatMode == 'CSS1Compat') + ? d.html || d.documentElement + : d.body + + var scrollX = (apf.isIE ? d.scrollLeft : window.pageXOffset), + scrollY = (apf.isIE ? d.scrollTop : window.pageYOffset), + oParent = amlNode.$ext.offsetParent, + pos + while (oParent && oParent != d && oParent.tagName != "BODY") { + scrollX -= oParent.scrollLeft; + scrollY -= oParent.scrollTop; + oParent = oParent.offsetParent; + } + + //The coordinates need to be relative to the html element that + //represents the xml data node. + var loopEl = srcEl, lastId; + while (loopEl && loopEl.nodeType == 1 + && !(lastId = loopEl.getAttribute(apf.xmldb.htmlIdTag))) { + loopEl = loopEl.parentNode; + } + if (!lastId) + return; + pos = apf.getAbsolutePosition(loopEl); + + //Set coordinates object + apf.DragServer.coordinates = { + srcElement : srcEl, + doc : d, + scrollX : scrollX, + scrollY : scrollY, + offsetX : e.clientX - pos[0], + offsetY : e.clientY - pos[1], + clientX : e.pageX ? e.pageX - window.pageXOffset : e.clientX, + clientY : e.pageY ? e.pageY - window.pageYOffset : e.clientY + }; + + //Create Drag Data Object + var selection = amlNode.hasFeature(apf.__MULTISELECT__) + ? amlNode.getSelection() + : [amlNode.xmlRoot], + data = [], + srcRules = amlNode.isDragAllowed(selection, data); + if (!srcRules) return; + + if (amlNode.hasEventListener("dragdata")) + data = amlNode.dispatchEvent("dragdata", {data : data}); + + /*for(var i = 0, l = data.length; i < l; i++) { + data[i] = apf.getCleanCopy(data[i]); + }*/ + + this.dragdata = { + rules : srcRules, + selection : selection, + data : data, + indicator : amlNode.$showDragIndicator(selection, this.coordinates), + host : amlNode + }; + + //EVENT - cancelable: ondragstart + if (amlNode.dispatchEvent("dragstart", this.dragdata) === false) + return false;//(this.amlNode.$tempsel ? select(this.amlNode.$tempsel) : false); + + amlNode.dragging = 2; + + apf.dragMode = true; + document.onmousemove = this.onmousemove; + document.onmouseup = this.onmouseup; + }, + + stop : function(runEvent, success){ + if (this.last) this.dragout(); + + //Reset Objects + this.dragdata.host.dragging = 0; + this.dragdata.host.$hideDragIndicator(success); + + /*if (runEvent && this.dragdata.host.$dragstop) + this.dragdata.host.$dragstop();*/ + + apf.dragMode = false; + document.onmousemove = + document.onmouseup = null; + + this.dragdata = null; + }, + + dragover : function(o, el, e){ + e = e || window.event; + + //@todo optimize by not checking the same node dragged over twice in a row + var fEl; + if (o.$findValueNode) + fEl = o.$findValueNode(el); + + if (this.lastFel && this.lastFel == fEl + || !this.lastFel && this.last == o) //optimization + return; + + //Check Permission + var elSel = (fEl + ? apf.xmldb.getNode(fEl) + : apf.xmldb.findXmlNode(el)), + candrop = o.isDropAllowed && o.xmlRoot + ? o.isDropAllowed(this.dragdata.data, elSel || o.xmlRoot) + : apf.isTrue(apf.getInheritedAttribute(o, "", function(p){ + if (p.drop) { + o = p; + if (o == apf.DragServer.last) + return false; + return true; + } + })); + + if (this.last && this.last != o) + this.dragout(this.last, e); + + this.last = o; + this.lastFel = fEl; + + if (!candrop) + return; + + //EVENT - cancelable: ondragover + if (o.dispatchEvent("dragover", this.dragdata) === false) + candrop = false; + + //Set Cursor + var srcEl = e.originalTarget || e.srcElement || e.target; + /*srcEl.style.cursor = (candrop ? o.icoAllowed : o.icoDenied); + if (srcEl.onmouseout != this.m_out) { + srcEl.$onmouseout = srcEl.onmouseout; + srcEl.onmouseout = this.m_out; + } + o.$ext.style.cursor = (candrop ? o.icoAllowed : o.icoDenied);*/ + + //REQUIRED INTERFACE: __dragover() + if (o && o.$dragover) + o.$dragover(el, this.dragdata, candrop); + }, + + dragout : function(o, e){ + //if (this.last == o) + //return false; + + this.lastFel = null; + + //EVENT: ondragout + if (o) { + this.dragdata.htmlEvent = e; + o.dispatchEvent("dragout", this.dragdata); + } + + //REQUIRED INTERFACE: __dragout() + if (this.last && this.last.$dragout) + this.last.$dragout(null, this.dragdata); + + //Reset Cursor + //o.$ext.style.cursor = "default"; + this.last = null; + }, + + dragdrop : function(o, el, srcO, e){ + //Check Permission + var isParent, lastTop, + elSel = (o.$findValueNode + ? apf.xmldb.getNode(o.$findValueNode(el)) + : apf.xmldb.findXmlNode(el)), + candrop = (o.isDropAllowed && o.xmlRoot) + ? o.isDropAllowed(this.dragdata.data, elSel || o.xmlRoot) : false; + + if (this.dragdata.indicator) { + lastTop = this.dragdata.indicator.style.top; + this.dragdata.indicator.style.top = "10000px"; + } + + if (!candrop) + candrop = apf.isTrue(apf.getInheritedAttribute(o, "", function(p){ + if (p.drop) { + o = p; + return true; + } + })); + + //EVENT - cancelable: ondragdrop + if (candrop) { + if (o.dispatchEvent("dragdrop", apf.extend({candrop : candrop, htmlEvent : e, top: lastTop}, + this.dragdata)) === false) { + candrop = false; + } + else { + if (!o.xmlRoot) { + var m = o.getModel + ? o.getModel(true) + : + + apf.nameserver.get("model", o.model) + + if (m) + m.load(this.dragdata.data[0]) + //else warn?? + return true; + } + else { + var action = candrop[1] + && candrop[1].action + || (o.$isTreeArch ? "tree-append" : "list-append"); + if (action == "list-append" && (!o.$isTreeArch && o == this.dragdata.host)) + candrop = false; + } + } + } + + if (this.dragdata.indicator) + this.dragdata.indicator.style.top = lastTop; + + //Exit if not allowed + if (!candrop) { + this.dragout(o, e); + return false; + } + + if (o.$dragDrop) { + //Move XML + var rNode = o.$dragDrop(candrop[0], this.dragdata.data, candrop[1], + action, isParent || candrop[0] == o.xmlRoot, this.dragdata.rules, e); + this.dragdata.resultNode = rNode; + } + + if (o.$dragdrop) { + o.$dragdrop(el, apf.extend({ + htmlEvent : e, + xmlNode : rNode + }, this.dragdata), candrop); + } + + //Reset Cursor + //o.$ext.style.cursor = "default"; + this.last = null; + this.lastFel = null; + + return true; + }, + + /* ********************** + Mouse Movements + ***********************/ + + onmousemove : function(e){ + if (!apf.DragServer.dragdata) return; + e = e || window.event; + + if (apf.isIphone) { + e.preventDefault(); + if (!e.touches) + return apf.DragServer.stop(true); + e = e.touches[0]; + } + + + var dragdata = apf.DragServer.dragdata, + c = { + clientX: e.pageX ? e.pageX - window.pageXOffset : e.clientX, + clientY: e.pageY ? e.pageY - window.pageYOffset : e.clientY + }; + + if (!dragdata.started + && Math.abs(apf.DragServer.coordinates.clientX - c.clientX) < 6 + && Math.abs(apf.DragServer.coordinates.clientY - c.clientY) < 6) + return; + + if (!dragdata.started) { + if (dragdata.host.$dragstart) + dragdata.host.$dragstart(null, dragdata); + dragdata.started = true; + } + + //dragdata.indicator.style.top = e.clientY+"px"; + //dragdata.indicator.style.left = e.clientX+"px"; + + var storeIndicatorTopPos = dragdata.indicator.style.top; + //console.log("INDICATOR BEFORE: "+dragdata.indicator.style.top+" "+dragdata.indicator.style.left); + //get Element at x, y + dragdata.indicator.style.display = "block"; + if (dragdata.indicator) + dragdata.indicator.style.top = "10000px"; + + apf.DragServer.dragdata.x = e.pageX ? e.pageX - (!apf.isIE + ? window.pageXOffset + : 0) : c.clientX; + apf.DragServer.dragdata.y = e.pageY ? e.pageY - (!apf.isIE + ? window.pageYOffset + : 0) : c.clientY; + var el = document.elementFromPoint(apf.DragServer.dragdata.x, + apf.DragServer.dragdata.y); + if (!el) { + el = document.elementFromPoint(apf.DragServer.dragdata.x, + apf.DragServer.dragdata.y); + } + + dragdata.indicator.style.top = storeIndicatorTopPos; + //console.log("INDICATOR AFTER: "+dragdata.indicator.style.top+" " + //+dragdata.indicator.style.left+" "+apf.DragServer.dragdata.x+" "+apf.DragServer.dragdata.y); + //Set Indicator + dragdata.host.$moveDragIndicator(c); + + //get element and call events + var receiver = apf.findHost(el); + + //Run Events + if (receiver) + apf.DragServer.dragover(receiver, el, e); + else if (apf.DragServer.last) + apf.DragServer.dragout(apf.DragServer.last, e); + + apf.DragServer.lastTime = new Date().getTime(); + }, + + onmouseup : function(e){ + e = e || window.event; + + if (apf.isIphone) { + e.preventDefault(); + if (!e.changedTouches) + return apf.DragServer.stop(true); + e = e.changedTouches[0]; + } + + + var c = { + clientX: e.pageX ? e.pageX - window.pageXOffset : e.clientX, + clientY: e.pageY ? e.pageY - window.pageYOffset : e.clientY + }; + + if (!apf.DragServer.dragdata.started + && Math.abs(apf.DragServer.coordinates.clientX - c.clientX) < 6 + && Math.abs(apf.DragServer.coordinates.clientY - c.clientY) < 6) { + apf.DragServer.stop(true) + return; + } + + //get Element at x, y + var indicator = apf.DragServer.dragdata.indicator, + storeIndicatorTopPos = indicator.style.top; + //apf.console.info("INDICATOR UP BEFORE: "+indicator.style.top+" "+indicator.style.left); + if (indicator) + indicator.style.top = "10000px"; + + apf.DragServer.dragdata.x = e.pageX ? e.pageX - (!apf.isIE + ? window.pageXOffset + : 0) : c.clientX; + apf.DragServer.dragdata.y = e.pageY ? e.pageY - (!apf.isIE + ? window.pageYOffset + : 0) : c.clientY; + + var el = document.elementFromPoint(apf.DragServer.dragdata.x, + apf.DragServer.dragdata.y); + if (!el) { + el = document.elementFromPoint(apf.DragServer.dragdata.x, + apf.DragServer.dragdata.y); + } + + indicator.style.top = storeIndicatorTopPos; + //apf.console.info("INDICATOR UP AFTER: "+indicator.style.top+" "+indicator.style.left); + + //get element and call events + var host = apf.findHost(el); + + //Run Events + if (apf.DragServer.host && host != apf.DragServer.host) + apf.DragServer.dragout(apf.DragServer.host, e); + var success = apf.DragServer.dragdrop(host, el, apf.DragServer.dragdata.host, e); + apf.DragServer.stop(true, success); + } +}; + +/** + * @private + */ +apf.MultiselectDragDrop = function() { + /**** Drag & Drop ****/ + + this.diffX = + this.diffY = 0; + this.multiple = false; + this.lastDragNode = null; + this.lastel = null; + + this.$showDragIndicator = function(sel, e){ + this.multiple = sel.length > 1; + + if (this.multiple) { + this.diffX = e.scrollX; + this.diffY = e.scrollY; + } + else { + this.diffX = -1 * e.offsetX; + this.diffY = -1 * e.offsetY; + } + + var prefix = this.oDrag.className.split(" ")[0] + //@todo the class should be removed here + this.$setStyleClass(this.oDrag, (this.multiple + ? prefix + "_multiple" : "") + (this["class"] ? " " + this["class"] : ""), [prefix + "_multiple"]); + + if (this.multiple) { + document.body.appendChild(this.oDrag); + return this.oDrag; + } + else if (this.localName == "datagrid") { + if (this.lastDragNode) + apf.destroyHtmlNode(this.lastDragNode); + + sel = this.$selected || this.$caret; + var oDrag = sel.cloneNode(true); + oDrag.removeAttribute("onmousedown"); oDrag.onmousedown = null; + oDrag.removeAttribute("onmouseup"); oDrag.onmouseup = null; + oDrag.removeAttribute("onmouseout"); oDrag.onmouseout = null; + oDrag.removeAttribute("ondblclick"); oDrag.ondblclick = null; + document.body.appendChild(oDrag); + + oDrag.style.position = "absolute"; + oDrag.style.width = sel.offsetWidth + "px"; + oDrag.style.display = "none"; + oDrag.removeAttribute("id"); + + this.$setStyleClass(oDrag, "draggrid"); + var nodes = sel.childNodes; + var dragnodes = oDrag.childNodes; + for (var i = nodes.length - 1; i >= 0; i--) { + if (dragnodes[i].nodeType == 1) + dragnodes[i].style.width = apf.getStyle(nodes[i], "width"); + } + //@todo apf3.0 remove all the event handlers of the children. + return (this.lastDragNode = oDrag); + } + else { + var sel = this.$selected || this.$caret, + width = apf.getStyle(this.oDrag, "width"); + + if (!width || width == "auto") + this.oDrag.style.width = (sel.offsetWidth - apf.getWidthDiff(this.oDrag)) + "px"; + this.$updateNode(this.selected, this.oDrag); + } + + apf.window.zManager.set("drag", this.oDrag); + + return this.oDrag; + }; + + this.$hideDragIndicator = function(success){ + var oDrag = this.lastDragNode || this.oDrag, _self = this; + if (!this.multiple && !success && oDrag.style.display == "block") { + if (!this.$selected && !this.$caret) + return; + + var pos = apf.getAbsolutePosition(this.$selected || this.$caret); + apf.tween.multi(oDrag, { + anim : apf.tween.easeInOutCubic, + steps : apf.isIE ? 15 : 20, + interval : 15, + tweens : [ + {type: "left", from: oDrag.offsetLeft, to: pos[0]}, + {type: "top", from: oDrag.offsetTop, to: pos[1]} + ], + onfinish : function(){ + if (_self.lastDragNode) { + apf.destroyHtmlNode(_self.lastDragNode); + _self.lastDragNode = null; + } + else { + _self.oDrag.style.display = "none"; + } + } + }); + } + else if (this.lastDragNode) { + apf.destroyHtmlNode(this.lastDragNode); + this.lastDragNode = null; + } + else { + this.oDrag.style.display = "none"; + } + }; + + this.$moveDragIndicator = function(e){ + var oDrag = this.lastDragNode || this.oDrag; + oDrag.style.left = (e.clientX + this.diffX) + "px";// - this.oDrag.startX + oDrag.style.top = (e.clientY + this.diffY + (this.multiple ? 15 : 0)) + "px";// - this.oDrag.startY + }; + + this.addEventListener("$skinchange", function(){ + this.$initDragDrop(); + }); + + this.$initDragDrop = function(){ + if (!this.$hasLayoutNode("dragindicator")) + return; + + this.oDrag = apf.insertHtmlNode( + this.$getLayoutNode("dragindicator"), document.body); + + apf.window.zManager.set("drag", this.oDrag); + + this.oDrag.style.position = "absolute"; + this.oDrag.style.cursor = "default"; + this.oDrag.style.display = "none"; + }; + + this.$findValueNode = function(el){ + if (!el) return null; + + while(el && el.nodeType == 1 + && !el.getAttribute(apf.xmldb.htmlIdTag)) { + if (this.$isTreeArch && el.previousSibling + && el.previousSibling.nodeType == 1) //@todo hack!! apf3.0 fix this. + el = el.previousSibling; + else + el = el.parentNode; + } + + return (el && el.nodeType == 1 && el.getAttribute(apf.xmldb.htmlIdTag)) + ? el + : null; + }; + + + this.$dragout = function(el, dragdata, extra){ + if (this.lastel) + this.$setStyleClass(this.lastel, "", ["dragDenied", "dragInsert", + "dragAppend", "selected", "indicate"]); + + var sel = this.$getSelection(true); + for (var i = 0, l = sel.length; i < l; i++) + this.$setStyleClass(sel[i], "selected", ["dragDenied", + "dragInsert", "dragAppend", "indicate"]); + + this.$setStyleClass(this.$ext, "", [this.$baseCSSname + "Drop"]); + + this.lastel = null; + }; + + if (!this.$dragdrop) + this.$dragdrop = this.$dragout; + + this.$dragover = function(el, dragdata, extra){ + this.$setStyleClass(this.$ext, this.$baseCSSname + "Drop"); + + var sel = this.$getSelection(true); + for (var i = 0, l = sel.length; i < l; i++) + this.$setStyleClass(sel[i], "", ["dragDenied", + "dragInsert", "dragAppend", "selected", "indicate"]); + + if (this.lastel) + this.$setStyleClass(this.lastel, "", ["dragDenied", + "dragInsert", "dragAppend", "selected", "indicate"]); + + var action = extra[1] && extra[1].action; + this.lastel = this.$findValueNode(el); + if (this.$isTreeArch && action == "list-append") { + var htmlNode = apf.xmldb.findHtmlNode(this.getTraverseParent(apf.xmldb.getNode(this.lastel)), this); + + this.lastel = htmlNode + ? this.$getLayoutNode("item", "container", htmlNode) + : this.$container; + + this.$setStyleClass(this.lastel, "dragInsert"); + } + else { + this.$setStyleClass(this.lastel, extra + ? (action == "insert-before" + ? "dragInsert" + : "dragAppend") + : "dragDenied"); + } + }; + +}; + +/** + * @private + */ +apf.StandardDragDrop = function() { + this.$showDragIndicator = function(sel, e){ + var x = e.offsetX + 22, + y = e.offsetY; + + this.oDrag.startX = x; + this.oDrag.startY = y; + + + document.body.appendChild(this.oDrag); + //this.oDrag.getElementsByTagName("DIV")[0].innerHTML = this.selected.innerHTML; + //this.oDrag.getElementsByTagName("IMG")[0].src = this.selected.parentNode.parentNode.childNodes[1].firstChild.src; + var oInt = this.$getLayoutNode("main", "caption", this.oDrag); + if (oInt.nodeType != 1) + oInt = oInt.parentNode; + + oInt.innerHTML = this.$applyBindRule("caption", this.xmlRoot) || ""; + + return this.oDrag; + }; + + this.$hideDragIndicator = function(){ + this.oDrag.style.display = "none"; + }; + + this.$moveDragIndicator = function(e){ + this.oDrag.style.left = (e.clientX - this.oDrag.startX + + document.documentElement.scrollLeft) + "px"; + this.oDrag.style.top = (e.clientY - this.oDrag.startY + + document.documentElement.scrollTop) + "px"; + }; + + //@todo falsely assuming only attributes are used for non multiselect widgets + this.$initDragDrop = function(){ + if (!this.getAttribute("drag")) + return; + + this.oDrag = document.body.appendChild(this.$ext.cloneNode(true)); + + apf.window.zManager.set("drag", this.oDrag); + + this.oDrag.style.position = "absolute"; + this.oDrag.style.cursor = "default"; + this.oDrag.style.filter = "progid:DXImageTransform.Microsoft.Alpha(opacity=50)"; + this.oDrag.style.MozOpacity = 0.5; + this.oDrag.style.opacity = 0.5; + this.oDrag.style.display = "none"; + }; +}; + +apf.DragServer.Init(); + + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/core/baseclasses/focussable.js)SIZE(3405)TIME(Mon, 10 Jan 2011 10:53:11 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + +apf.__FOCUSSABLE__ = 1 << 26; + + +apf.Focussable = function(){ + this.$regbase = this.$regbase | apf.__FOCUSSABLE__; + if (this.disabled == undefined) + this.disabled = false; + + /** + * Sets the position in the list that determines the sequence + * of elements when using the tab key to move between them. + * Call-chaining is supported. + * @param {Number} tabindex the position in the list + */ + this.setTabIndex = function(tabindex){ + apf.window.$removeFocus(this); + apf.window.$addFocus(this, tabindex); + return this; + }; + + /** + * Gives this element the focus. This means that keyboard events + * are send to this element. + */ + this.focus = function(noset, e, nofix){ + if (!noset) { + if (this.$isWindowContainer > -1) { + apf.window.$focusLast(this, e, true); + } + else { + apf.window.$focus(this, e); + + + if (!nofix && apf.hasFocusBug) + apf.window.$focusfix(); + + } + + return this; + } + + if (this.$focus && !this.editable && (!e || !e.mouse || this.$focussable == apf.KEYBOARD_MOUSE)) + this.$focus(e); + + this.dispatchEvent("focus", apf.extend({ + bubbles : true + }, e)); + return this; + }; + + /** + * Removes the focus from this element. + * Call-chaining is supported. + */ + this.blur = function(noset, e){ + + if ((e && !apf.isChildOf(e.fromElement, e.toElement)) && apf.popup.isShowing(this.$uniqueId)) + apf.popup.forceHide(); //This should be put in a more general position + + + if (this.$blur) + this.$blur(e); + + if (!noset) + apf.window.$blur(this); + + this.dispatchEvent("blur", apf.extend({ + bubbles : !e || !e.cancelBubble + }, e)); + return this; + }; + + /** + * Determines whether this element has the focus + * @returns {Boolean} indicating whether this element has the focus + */ + this.hasFocus = function(){ + return apf.document.activeElement == this || this.$isWindowContainer + && (apf.document.activeElement || {}).$focusParent == this; + }; +}; + + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/core/baseclasses/interactive.js)SIZE(30337)TIME(Mon, 10 Jan 2011 10:53:11 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + +apf.__INTERACTIVE__ = 1 << 21; + + + +/** + * All elements inheriting from this {@link term.baseclass baseclass} have interactive features, making an + * element draggable and resizable. + * Example: + * + * + * + * + * @attribute {Boolean} draggable whether an element is draggable. The user will + * able to move the element around while holding the mouse button down on the + * element. + * Example: + * + * + * + * @attribute {Boolean} resizable whether an element is resizable. The user will able + * to resize the element by grabbing one of the four edges of the element and + * pulling it in either direction. Grabbing the corners allows users to + * resize horizontally and vertically at the same time. The right bottom corner + * is special, because it offers an especially big grab area. The size of this + * area can be configured in the skin of the element. + * Example: + * + * + * + * @attribute {Number} minwidth the minimum horizontal size the element can get when resizing. + * @attribute {Number} minheight the minimum vertical size the element can get when resizing. + * @attribute {Number} maxwidth the maximum horizontal size the element can get when resizing. + * @attribute {Number} maxheight the maximum vertical size the element can get when resizing. + * + * @event drag Fires when the widget has been dragged. + * @event resizestart Fires before the widget is resized. + * cancelable: Prevents this resize action to start. + * object: + * {String} type the type of resize. This is a combination of the four directions, n, s, e, w. + * @event resize Fires when the widget has been resized. + * + * @constructor + * + * @author Ruben Daniels (ruben AT ajax DOT org) + * @version %I%, %G% + * @since 1.0 + * + * @see element.appsettings.attribute.outline + * @see element.appsettings.attribute.resize-outline + * @see element.appsettings.attribute.drag-outline + */ +apf.Interactive = function(){ + var nX, nY, rX, rY, startPos, lastCursor = null, l, t, r, b, lMax, tMax, lMin, + tMin, w, h, we, no, ea, so, rszborder, rszcorner, marginBox, + verdiff, hordiff, _self = this, posAbs, oX, oY, overThreshold, + dragOutline, resizeOutline, myPos, startGeo; + + this.$regbase = this.$regbase | apf.__INTERACTIVE__; + + this.$dragStart = function(e, reparent){ + var nativeEvent = e || event; + if (!reparent && nativeEvent.button == 2) + return; + + + if (!reparent) { + var f; + var ev = e || { + clientX: event.clientX, + clientY: event.clientY, + ctrlKey: event.ctrlKey + }; + var o = nativeEvent.srcElement || this; + apf.addEventListener("mousedown", f = function(){ + dragStart.call(o, ev, reparent); + apf.removeEventListener("mousedown", f); + }); + apf.cancelBubble(nativeEvent, nativeEvent.srcElement || this); + } + else + + { + dragStart.apply(nativeEvent.srcElement || this, arguments); + } + } + + this.$propHandlers["draggable"] = function(value){ + if (apf.isFalse(value)) + this.draggable = value = false; + else if (apf.isTrue(value)) + this.draggable = value = true; + + var o = this.editable ? this.$ext : this.oDrag || this.$ext; + if (value) + apf.addListener(o, "mousedown", this.$dragStart); + else + apf.removeListener(o, "mousedown", this.$dragStart); + + //deprecated?? + if (o.interactive & 1) + return; + o.interactive = (o.interactive||0)+1; + + //this.$ext.style.position = "absolute"; + }; + + this.$propHandlers["resizable"] = function(value){ + if (apf.isFalse(value)) + this.resizable = value = false; + else if (apf.isTrue(value)) + this.resizable = value = true; + + this.$ext.style.cursor = ""; + + var o = this.oResize || this.$ext; + if (o.interactive & 2) + return; + + if (!_self.editable) { + apf.addListener(o, "mousedown", function(){ + resizeStart.apply(o, arguments); + }); + + apf.addListener(o, "mousemove", function(){ + resizeIndicate.apply(o, arguments); + }); + } + + o.interactive = (o.interactive||0)+2; + + //this.$ext.style.position = "absolute"; + + rszborder = this.$getOption && parseInt(this.$getOption("Main", "resize-border")) || 3; + rszcorner = this.$getOption && parseInt(this.$getOption("Main", "resize-corner")) || 12; + marginBox = apf.getBox(apf.getStyle(this.$ext, "borderWidth")); + }; + + /* + this.$propHandlers["minwidth"] = + this.$propHandlers["maxwidth"] = + this.$propHandlers["minheight"] = + this.$propHandlers["maxheight"] = function(value, prop){ + if (this.aData) + this.aData[prop] = parseInt(value); + } + if (this.aData) { + this.aData.minwidth = this.minwidth; + this.aData.minheight = this.minheight; + }*/ + + this.$cancelInteractive = function(){ + document.onmouseup(null, true); + } + + function dragStart(e, reparent){ + if (!e) e = event; + + if (!reparent && (!_self.draggable || apf.dragMode))//_self.editable || + return; + + + dragOutline = _self.dragOutline == true || apf.config.dragOutline; + + + if (_self.dispatchEvent("beforedragstart", {htmlEvent: e}) === false) + return; + + apf.dragMode = true; + if (reparent) { + _self.dispatchEvent("beforedrag") + overThreshold = true; + } + else + overThreshold = false; + + + apf.popup.forceHide(); + + + posAbs = "absolute|fixed".indexOf(apf.getStyle(_self.$ext, "position")) > -1; + if (!posAbs) { + _self.$ext.style.position = posAbs //(posAbs = _self.dragSelection) + ? "absolute" : "relative"; + } + if (_self.editable) + posAbs = true; + + //@todo not for docking + + if (posAbs && !_self.aData) { + apf.plane.show(dragOutline + ? oOutline + : _self.$ext, e.reappend);//, true + } + + + var ext = (reparent || oOutline.self) && dragOutline //little dirty hack to detect outline set by visualselect + ? oOutline + : _self.$ext; + var pos = posAbs + ? apf.getAbsolutePosition(ext, ext.offsetParent, true) + : [parseInt(apf.getStyle(ext, "left")) || 0, + parseInt(apf.getStyle(ext, "top")) || 0]; + + + startGeo = [ext.style.left, ext.style.top, ext.style.right, + ext.style.bottom, ext.style.width, ext.style.height]; + + + nX = pos[0] - (oX = e.clientX); + nY = pos[1] - (oY = e.clientY); + + //if (_self.hasFeature && _self.hasFeature(apf.__ANCHORING__)) + //_self.$disableAnchoring(); + + if (!(reparent || oOutline.self)) { + + if (posAbs && dragOutline) { + oOutline.className = "drag"; + + var diffOutline = apf.getDiff(oOutline); + _self.$ext.offsetParent.appendChild(oOutline); + oOutline.style.left = pos[0] + "px"; + oOutline.style.top = pos[1] + "px"; + oOutline.style.width = (_self.$ext.offsetWidth - diffOutline[0]) + "px"; + oOutline.style.height = (_self.$ext.offsetHeight - diffOutline[1]) + "px"; + + if (_self.editable) + oOutline.style.display = "block"; + } + else + + { + if (_self.$ext.style.right) { + _self.$ext.style.left = pos[0] + "px"; + _self.$ext.style.right = ""; + } + if (_self.$ext.style.bottom) { + _self.$ext.style.top = pos[1] + "px"; + _self.$ext.style.bottom = ""; + } + } + } + + document.onmousemove = dragMove; + document.onmouseup = function(e, cancel){ + document.onmousemove = document.onmouseup = null; + + + if (posAbs && !_self.aData) + apf.plane.hide(); + + + var htmlNode = dragOutline + ? oOutline + : _self.$ext; + + if (overThreshold && !_self.$multidrag) { + + if (cancel) { + var ext = _self.$ext; + ext.style.left = startGeo[0]; + ext.style.top = startGeo[1]; + ext.style.right = startGeo[2]; + ext.style.bottom = startGeo[3]; + ext.style.width = startGeo[4]; + ext.style.height = startGeo[5]; + + if (_self.dispatchEvent) + _self.dispatchEvent("dragcancel"); + } + else + + + if (_self.setProperty) { + updateProperties(); + } + else if (dragOutline) { + _self.$ext.style.left = l + "px"; + _self.$ext.style.top = t + "px"; + } + } + + l = t = w = h = null; + + if (!posAbs) + _self.$ext.style.position = "relative"; + + if (_self.showdragging) + apf.setStyleClass(_self.$ext, "", ["dragging"]); + + if (posAbs && dragOutline && !oOutline.self) //little dirty hack to detect outline set by visualselect + oOutline.style.display = "none"; + + apf.dragMode = false; + + if (!cancel && _self.dispatchEvent && overThreshold) + _self.dispatchEvent("afterdrag", { + htmlNode : htmlNode + }); + }; + + if (reparent) + document.onmousemove(e); + //else if (apf.isIE) + //apf.window.$mousedown(e); + + return false; + }; + + function dragMove(e){ + if(!e) e = event; + + //if (_self.dragSelection) + //overThreshold = true; + + if (!overThreshold && _self.showdragging) + apf.setStyleClass(_self.$ext, "dragging"); + + // usability rule: start dragging ONLY when mouse pointer has moved delta x pixels + var dx = e.clientX - oX, + dy = e.clientY - oY, + distance; + + if (!overThreshold + && (distance = dx*dx > dy*dy ? dx : dy) * distance < 2) + return; + + //Drag outline support + else if (!overThreshold) { + if (dragOutline + && oOutline.style.display != "block") + oOutline.style.display = "block"; + + if (_self.dispatchEvent && _self.dispatchEvent("beforedrag") === false) { + document.onmouseup(); + return; + } + } + + var oHtml = dragOutline + ? oOutline + : _self.$ext; + + oHtml.style.left = (l = e.clientX + nX) + "px"; + oHtml.style.top = (t = e.clientY + nY) + "px"; + + if (_self.realtime) { + var change = _self.$stick = {}; + _self.$showDrag(l, t, oHtml, e, change); + + if (typeof change.l != "undefined") + l = change.l, oHtml.style.left = l + "px"; + if (typeof change.t != "undefined") + t = change.t, oHtml.style.top = t + "px"; + } + + overThreshold = true; + }; + + this.$resizeStart = resizeStart; + function resizeStart(e, options){ + if (!e) e = event; + + //|| _self.editable + if (!_self.resizable + || String(_self.height).indexOf("%") > -1 && _self.parentNode.localName == "vbox" //can't resize percentage based for now + || String(_self.width).indexOf("%") > -1 && _self.parentNode.localName == "hbox") //can't resize percentage based for now + return; + + + resizeOutline = !(_self.resizeOutline == false || !apf.config.resizeOutline); + + + var ext = _self.$ext; + if (!resizeOutline) { + var diff = apf.getDiff(ext); + hordiff = diff[0]; + verdiff = diff[1]; + } + + //@todo This is probably not gen purpose + startPos = apf.getAbsolutePosition(ext);//, ext.offsetParent); + startPos.push(ext.offsetWidth); + startPos.push(ext.offsetHeight); + myPos = apf.getAbsolutePosition(ext, ext.offsetParent, true); + + + startGeo = [ext.style.left, ext.style.top, ext.style.right, + ext.style.bottom, ext.style.width, ext.style.height]; + + + var sLeft = 0, + sTop = 0, + x = (oX = e.clientX) - startPos[0] + sLeft + document.documentElement.scrollLeft, + y = (oY = e.clientY) - startPos[1] + sTop + document.documentElement.scrollTop, + resizeType; + + if (options && options.resizeType) { + posAbs = "absolute|fixed".indexOf(apf.getStyle(ext, "position")) > -1; + resizeType = options.resizeType; + } + else { + resizeType = getResizeType.call(ext, x, y); + } + rX = x; + rY = y; + + if (!resizeType) + return; + + if (_self.dispatchEvent && _self.dispatchEvent("beforeresize", { + type : resizeType, + setType : function(type){ + resizeType = type; + } + }) === false) { + //if (apf.isIE) + //apf.window.$mousedown(e); //@todo is this necessary? + return; + } + + + apf.popup.forceHide(); + + + //if (_self.hasFeature && _self.hasFeature(apf.__ANCHORING__)) + //_self.$disableAnchoring(); + + apf.dragMode = true; + overThreshold = false; + + we = resizeType.indexOf("w") > -1; + no = resizeType.indexOf("n") > -1; + ea = resizeType.indexOf("e") > -1; + so = resizeType.indexOf("s") > -1; + + if (!_self.minwidth) _self.minwidth = 0; + if (!_self.minheight) _self.minheight = 0; + if (!_self.maxwidth) _self.maxwidth = 10000; + if (!_self.maxheight) _self.maxheight = 10000; + + if (posAbs) { + lMax = myPos[0] + startPos[2]; + tMax = myPos[1] + startPos[3]; + lMin = myPos[0] + startPos[2]; + tMin = myPos[1] + startPos[3]; + } + + + if (posAbs) { + apf.plane.show(resizeOutline + ? oOutline + : ext);//, true + } + + + + if (resizeOutline) { + oOutline.className = "resize"; + var diffOutline = apf.getDiff(oOutline); + hordiff = diffOutline[0]; + verdiff = diffOutline[1]; + + //ext.parentNode.appendChild(oOutline); + oOutline.style.left = startPos[0] + "px"; + oOutline.style.top = startPos[1] + "px"; + oOutline.style.width = (ext.offsetWidth - hordiff) + "px"; + oOutline.style.height = (ext.offsetHeight - verdiff) + "px"; + oOutline.style.display = "block"; + } + else + + { + if (ext.style.right) { + ext.style.left = myPos[0] + "px"; + //ext.style.right = ""; + } + if (ext.style.bottom) { + ext.style.top = myPos[1] + "px"; + //ext.style.bottom = ""; + } + } + + if (!options || !options.nocursor) { + if (lastCursor === null) + lastCursor = document.body.style.cursor;//apf.getStyle(document.body, "cursor"); + document.body.style.cursor = resizeType + "-resize"; + } + + document.onmousemove = resizeMove; + document.onmouseup = function(e, cancel){ + document.onmousemove = document.onmouseup = null; + + + if (posAbs) + apf.plane.hide(); + + + clearTimeout(timer); + + if (resizeOutline) { + var diff = apf.getDiff(_self.$ext); + hordiff = diff[0]; + verdiff = diff[1]; + } + + + if (cancel) { + var ext = _self.$ext; + ext.style.left = startGeo[0]; + ext.style.top = startGeo[1]; + ext.style.right = startGeo[2]; + ext.style.bottom = startGeo[3]; + ext.style.width = startGeo[4]; + ext.style.height = startGeo[5]; + + if (_self.dispatchEvent) + _self.dispatchEvent("resizecancel"); + } + else + + doResize(e || event, true); + + if (_self.setProperty) + updateProperties(); + + document.body.style.cursor = lastCursor || ""; + lastCursor = null; + + if (resizeOutline) + oOutline.style.display = "none"; + + apf.dragMode = false; + + if (!cancel && _self.dispatchEvent) + _self.dispatchEvent("afterresize", { + l: l, t: t, w: w+hordiff, h: h+verdiff + }); + + l = t = w = h = null; + }; + + //if (apf.isIE) + //apf.window.$mousedown(e); + + return false; + } + + function updateProperties(left, top, width, height, hdiff, vdiff, right, bottom){ + if (typeof left == "undefined") { + left = l, top = t, width = w, height = h, + vdiff = verdiff, hdiff = hordiff; + } + else posAbs = true; + + var hasLeft = _self.left || _self.left === 0; + var hasRight = _self.right || _self.right === 0; + var hasBottom = _self.bottom || _self.bottom === 0; + var hasTop = _self.top || _self.top === 0; + + if (posAbs) { + var htmlNode = oOutline.style.display == "block" + ? oOutline + : _self.$ext; + + if (hasRight && !(right || right === 0)) + right = apf.getHtmlRight(htmlNode); + + if (hasBottom && !(bottom || bottom === 0)) + bottom = apf.getHtmlBottom(htmlNode); + + if (hasRight) { + _self.setProperty("right", right, 0, _self.editable); + if (!_self.left) + htmlNode.style.left = ""; + } + + if (hasBottom) { + _self.setProperty("bottom", bottom, 0, _self.editable); + if (!_self.top) + htmlNode.style.top = ""; + } + + if ((left || left === 0) && (!hasRight || hasLeft)) + _self.setProperty("left", left, 0, _self.editable); + if ((top || top === 0) && (!hasBottom || hasTop)) + _self.setProperty("top", top, 0, _self.editable); + } + + if (hdiff != undefined && width && (!hasLeft || !hasRight)) + _self.setProperty("width", width + hdiff, 0, _self.editable) + if (vdiff != undefined && height && (!hasTop || !hasBottom)) + _self.setProperty("height", height + vdiff, 0, _self.editable); + } + this.$updateProperties = updateProperties; + + var min = Math.min, max = Math.max, lastTime, timer; + function resizeMove(e){ + if(!e) e = event; + + //if (!e.button) + //return this.onmouseup(); + + // usability rule: start dragging ONLY when mouse pointer has moved delta x pixels + /*var dx = e.clientX - oX, + dy = e.clientY - oY, + distance; + + if (!overThreshold + && (distance = dx*dx > dy*dy ? dx : dy) * distance < 4) + return;*/ + + clearTimeout(timer); + if (lastTime && new Date().getTime() + - lastTime < (resizeOutline ? 6 : apf.mouseEventBuffer)) { + var z = { + clientX: e.clientX, + clientY: e.clientY + } + timer = setTimeout(function(){ + doResize(z); + }, 10); + return; + } + lastTime = new Date().getTime(); + + doResize(e); + + //overThreshold = true; + } + + function doResize(e, force){ + var oHtml = resizeOutline && !force + ? oOutline + : _self.$ext; + + var sLeft = document.documentElement.scrollLeft, + sTop = document.documentElement.scrollTop; + + if (we) { + if (posAbs) + oHtml.style.left = (l = max((lMin - _self.maxwidth), + min((lMax - _self.minwidth), + myPos[0] + e.clientX - oX + sLeft))) + "px"; + oHtml.style.width = (w = min(_self.maxwidth - hordiff, + max(hordiff, _self.minwidth, + startPos[2] - (e.clientX - oX) + sLeft + ) - hordiff)) + "px"; //@todo + } + + if (no) { + if (posAbs) + oHtml.style.top = (t = max((tMin - _self.maxheight), + min((tMax - _self.minheight), + myPos[1] + e.clientY - oY + sTop))) + "px"; + oHtml.style.height = (h = min(_self.maxheight - verdiff, + max(verdiff, _self.minheight, + startPos[3] - (e.clientY - oY) + sTop + ) - verdiff)) + "px"; //@todo + } + + if (ea) + oHtml.style.width = (w = min(_self.maxwidth - hordiff, + max(hordiff, _self.minwidth, + e.clientX - startPos[0] + (startPos[2] - rX) + sLeft) + - hordiff)) + "px"; + + if (so) + oHtml.style.height = (h = min(_self.maxheight - verdiff, + max(verdiff, _self.minheight, + e.clientY - startPos[1] + (startPos[3] - rY) + sTop) + - verdiff)) + "px"; + + //@todo apf3.0 this is execution wise inefficient + if (_self.parentNode && _self.parentNode.localName == "table") { + updateProperties(); + apf.layout.processQueue(); + } + + if (_self.realtime) { + var change = _self.$stick = {}; + + //@todo calc l and t once at start of resize (subtract borders) + _self.$showResize(l || apf.getHtmlLeft(oHtml), t || apf.getHtmlTop(oHtml), + w && w + hordiff || oHtml.offsetWidth, + h && h + verdiff || oHtml.offsetHeight, e, change, we, no, ea, so); + + if (posAbs && we && typeof change.l != "undefined") + oHtml.style.left = (l = max((lMin - _self.maxwidth), min((lMax - _self.minwidth), change.l))) + "px"; + + if (posAbs && no && typeof change.t != "undefined") + oHtml.style.top = (t = max((tMin - _self.maxheight), min((tMax - _self.minheight), change.t))) + "px"; + + if (typeof change.w != "undefined") + oHtml.style.width = (w = min(_self.maxwidth - hordiff, + max(hordiff, _self.minwidth, + change.w) - hordiff)) + "px"; + if (typeof change.h != "undefined") + oHtml.style.height = (h = min(_self.maxheight - verdiff, + max(verdiff, _self.minheight, + change.h) - verdiff)) + "px"; + } + + + if (apf.hasSingleRszEvent) + apf.layout.forceResize(_self.$int); + + } + + function getResizeType(x, y){ + var cursor = "", + tcursor = ""; + posAbs = "absolute|fixed".indexOf(apf.getStyle(_self.$ext, "position")) > -1; + + if (_self.resizable == true || _self.resizable == "vertical") { + if (y < rszborder + marginBox[0]) + cursor = posAbs ? "n" : ""; + else if (y > this.offsetHeight - rszborder) //marginBox[0] - marginBox[2] - + cursor = "s"; + else if (y > this.offsetHeight - rszcorner) //marginBox[0] - marginBox[2] - + tcursor = "s"; + } + + if (_self.resizable == true || _self.resizable == "horizontal") { + if (x < (cursor ? rszcorner : rszborder) + marginBox[0]) + cursor += tcursor + (posAbs ? "w" : ""); + else if (x > this.offsetWidth - (cursor || tcursor ? rszcorner : rszborder)) //marginBox[1] - marginBox[3] - + cursor += tcursor + "e"; + } + + return cursor; + } + + var originalCursor; + function resizeIndicate(e){ + if(!e) e = event; + + if (!_self.resizable || _self.editable || document.onmousemove) + return; + + //@todo This is probably not gen purpose + var pos = apf.getAbsolutePosition(_self.$ext),//, _self.$ext.offsetParent + sLeft = 0, + sTop = 0, + x = e.clientX - pos[0] + sLeft + document.documentElement.scrollLeft, + y = e.clientY - pos[1] + sTop + document.documentElement.scrollTop; + + if (!originalCursor) + originalCursor = apf.getStyle(this, "cursor"); + + var cursor = getResizeType.call(_self.$ext, x, y); + this.style.cursor = cursor + ? cursor + "-resize" + : originalCursor || "default"; + }; + + + var oOutline; + function initOutline(e){ + var doc = this.$pHtmlDoc || document; + oOutline = doc.getElementById("apf_outline"); + if (!oOutline) { + oOutline = doc.body.appendChild(doc.createElement("div")); + + oOutline.refCount = 0; + oOutline.setAttribute("id", "apf_outline"); + + oOutline.style.position = "absolute"; + oOutline.style.display = "none"; + //oOutline.style.zIndex = 2000000; + apf.window.zManager.set("drag", oOutline); + oOutline.host = false; + } + oOutline.refCount++ + } + + if (this.addEventListener && this.hasFeature(apf.__AMLNODE__) && !this.$amlLoaded) { + if (document.body) + initOutline.call(this); + else + this.addEventListener("DOMNodeInsertedIntoDocument", initOutline); + } + else { + this.$pHtmlDoc = document; + initOutline.call(this); + } + + this.$setOutline = function(o){ + oOutline = o; + } + + + /*this.addEventListener("DOMNodeRemovedFromDocument", function(e){ + oOutline.refCount--; + + if (!oOutline.refCount) { + //destroy + } + });*/ +}; + +apf.GuiElement.propHandlers["resizable"] = function(value){ + this.implement(apf.Interactive); + this.$propHandlers["resizable"].apply(this, arguments); +} + +apf.GuiElement.propHandlers["draggable"] = function(value){ + this.implement(apf.Interactive); + this.$propHandlers["draggable"].apply(this, arguments); +}; + +apf.Init.run("interactive"); + + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/core/baseclasses/media.js)SIZE(18898)TIME(Mon, 10 Jan 2011 10:53:11 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + +apf.__MEDIA__ = 1 << 20; + + + +/** + * All elements inheriting from this {@link term.baseclass baseclass} have media node features and dynamics. + * @see http://www.whatwg.org/specs/web-apps/current-work/multipage/video.html#media7 + * + * @attribute {Boolean} seeking + * @attribute {Boolean} autoplay + * @attribute {Boolean} controls + * @attribute {Boolean} ready + * @attribute {Number} bufferedBytes + * @attribute {Number} totalBytes + * + * @constructor + * @baseclass + * @author Mike de Boer + * @version %I%, %G% + * @since 1.0 + */ +apf.Media = function(){ + this.$init(true); +}; + +(function() { + this.$regbase = this.$regbase | apf.__MEDIA__; + + this.muted = false; + + this.$booleanProperties["paused"] = true; + this.$booleanProperties["muted"] = true; + this.$booleanProperties["seeking"] = true; + this.$booleanProperties["autoplay"] = true; + this.$booleanProperties["controls"] = true; + this.$booleanProperties["ready"] = true; + + this.$supportedProperties.push("position", "networkState", "readyState", + "progress", "buffered", "bufferedBytes", "totalBytes", "currentTime", + "paused", "seeking", "volume", "type", "src", "autoplay", "controls"); + + this.$mainBind = "src"; + this.$sources = []; + this.$nomedia = null; + this.$amlTimer = null; + this.$loadTimer = null; + this.$posTimer = null; + this.$volTimer = null; + + /** + * @attribute {Number} readyState + */ + this.$propHandlers["readyState"] = function(value){ //in seconds + if (this.readyState !== value) + this.readyState = value; + if (value == apf.Media.HAVE_NOTHING) { + + apf.console.error("Unable to open medium with URL '" + this.src + + "'. Please check if the URL you entered as src is pointing to \ + a valid resource."); + + + var oError = this.MediaError("Unable to open medium with URL '" + this.src + + "'. Please check if the URL you entered as src is pointing to \ + a valid resource."); + if (this.dispatchEvent("havenothing", { + error : oError, + bubbles : true + }) === false) + throw oError; + } + else if (value == apf.Media.HAVE_CURRENT_DATA) + this.dispatchEvent("havecurrentdata"); + else if (value == apf.Media.HAVE_FUTURE_DATA) + this.dispatchEvent("havefuturedata"); + else if (value == apf.Media.HAVE_ENOUGH_DATA) { + this.dispatchEvent("haveenoughdata"); + this.setProperty("ready", true); + } + }; + + /** + * @attribute {Object} bufferedBytes + * Object: + * {Number} start + * {Number} end + * {Number} total + */ + this.$propHandlers["bufferedBytes"] = function(value) { + this.setProperty("progress", this.totalBytes + ? value.end / this.totalBytes + : 0); + }; + + /** + * @attribute {Number} position + */ + this.$propHandlers["position"] = function(value){ + clearTimeout(this.$posTimer); + if (this.duration <= 0 || !this.seek) return; + + var _self = this; + this.$posTimer = $setTimeout(function() { + // first, check if the seek action doesn't go beyond the download + // progress of the media element. + if (value >= _self.progress) + value = _self.progress - 0.05; + + var isPlaying = !_self.paused; + if (isPlaying) + _self.pause(); + + if (value < 0) + value = 0; + else if (value > 1) + value = 1; + + _self.seek(Math.round(value * _self.duration)); + + _self.setProperty("paused", !isPlaying); + }); + }; + + /** + * @attribute {Number} currentTime + */ + this.$propHandlers["currentTime"] = function(value){ //in milliseconds + if (value >= 0 && this.seek) + this.seek(value); + }; + + /** + * @attribute {Number} volume + */ + this.$propHandlers["volume"] = function(value){ + if (!this.player) return; + + if (value < 0) + throw this.MediaError("Attempt to set the volume to a negative value '" + value + "'"); + + + if (value < 1 && value > 0) + value = value * 100; + + if (this.setVolume) + this.setVolume(value); + if (value > 0 && this.muted) + this.setProperty("muted", false); + }; + + this.oldVolume = null; + + /** + * @attribute {Boolean} muted + */ + this.$propHandlers["muted"] = function(value){ + if (!this.player || !this.setVolume) return; + + if (value) { //mute the player + this.oldVolume = this.volume; + this.setVolume(0); + } + else + this.setVolume(this.oldVolume || 20); + }; + + /** + * @attribute {Boolean} paused + */ + this.$propHandlers["paused"] = function(value){ + if (!this.player) return; + + this.paused = apf.isTrue(value); + if (this.paused) + this.player.pause(); + else + this.player.play(); + }; + + /** + * @attribute {String} type + */ + this.$propHandlers["type"] = function(value){ + if (this.$loadTimer) return; + + var _self = this; + this.$loadTimer = window.setTimeout(function() { + reload.call(_self); + }); + }; + + /** + * @attribute {String} src + */ + this.$propHandlers["src"] = function(value){ + //@todo for mike: please check if this is the best behaviour for setting an empty value + if (this.$loadTimer || !value) return; + + var oUrl = new apf.url(value); + this.src = oUrl.uri; + + + if (oUrl.protocol == "file") + apf.console.warn("Media player: the medium with URL '" + this.src + "'\n" + + "will be loaded through the 'file://' protocol.\nThis can " + + "cause the medium to not load and/ or play.", "media"); + else if (!oUrl.isSameLocation()) + apf.console.warn("Media player: the medium with URL '" + this.src + "'\n" + + "does not have the same origin as your web application.\nThis can " + + "cause the medium to not load and/ or play.", "media"); + + + if (this.src != this.currentSrc && this.networkState !== apf.Media.LOADING) { + var type = this.$guessType(this.src); + if (type == this.type) { + reset.call(this); + this.loadMedia(); + } + else { + this.type = type; + var _self = this; + this.$loadTimer = window.setTimeout(function() { + reload.call(_self); + }); + } + } + }; + + /** + * @attribute {Object} ID3 + */ + this.$propHandlers["ID3"] = function(value){ + if (!this.player) return; + // usually this feature is only made available BY media as getters + if (typeof this.player.setID3 == "function") + this.player.setID3(value); + }; + + /**** DOM Hooks ****/ + + this.addEventListener("AMLRemove", function(doOnlyAdmin){ + + apf.console.log("Media: removing node..."); + + reset.call(this); + }); + + this.addEventListener("AMLReparent", function(beforeNode, pNode, withinParent){ + if (!this.$amlLoaded) + return; + + + apf.console.log("Media: reparenting - " + beforeNode + ", " + pNode); + + + this.$draw(); + reload.call(this, true); + }); + + function reset() { + this.setProperty("networkState", apf.Media.NETWORK_EMPTY); + //this.setProperty("readyState", apf.Media.HAVE_NOTHING); + this.setProperty("ready", false); + //this.setProperty("buffered", {start: 0, end: 0, length: 0}); + //this.setProperty("bufferedBytes", {start: 0, end: 0, length: 0}); + this.buffered = {start: 0, end: 0, length: 0}; + this.bufferedBytes = {start: 0, end: 0, length: 0}; + this.totalBytes = 0; + this.setProperty("progress", 0); + //this.setProperty("totalBytes", 0); + + this.setProperty("seeking", false); + this.setProperty("paused", true); + this.setProperty("position", 0); + this.currentTime = this.duration = 0; + this.played = this.seekable = null; + this.ended = false; + + this.start = this.end = this.loopStart = this.loopEnd = + this.playCount = this.currentLoop = 0; + this.controls = this.muted = false; + } + + function reload(bNoReset) { + + apf.console.log("Media: reloading medium with mimetype '" + this.type + "'"); + + + window.clearTimeout(this.$loadTimer); + this.$loadTimer = null; + + if (!bNoReset) + reset.call(this); + + this.$destroy(true); //bRuntime = true + + this.playerType = this.$getPlayerType(this.type); + + // sanity checking + if (!this.playerType || !this.$isSupported()) { + this.$ext.innerHTML = this.notSupported; + return; + } + + this.$initPlayer(); + } + + /** + * Returns an error state related to media + * + */ + this.MediaError = function(sMsg) { + return new Error(apf.formatErrorString(0, this, "Media", sMsg)); + }; + + // network state + this.src = this.currentSrc = null; + this.networkState = apf.Media.NETWORK_EMPTY; //default state + this.bufferingRate = 0; + this.bufferingThrottled = false; + //TimeRanges container {start: Function(idx):Float, end: Function(idx):Float, length: n} + this.buffered = {start: 0, end: 0, length: 0}; + //ByteRanges container {start: Function(idx):Number, end: Function(idx):Number, length: n} + this.bufferedBytes = {start: 0, end: 0, length: 0}; + this.totalBytes = 0; + this.volume = 100; + + this.loadMedia = function() { + //must be overridden by the component + }; + + // ready state + this.readyState = apf.Media.HAVE_NOTHING; + this.seeking = false; + + // playback state + this.currentTime = this.duration = 0; + this.paused = true; + this.defaultPlaybackRate = this.playbackRate = 0; + this.played = null; // TimeRanges container + this.seekable = null; // TimeRanges container + this.ended = this.autoplay = false; + + /** + * @see http://www.whatwg.org/specs/web-apps/current-work/multipage/video.html#dom-navigator-canplaytype + */ + this.canPlayType = function(sType) { + if (this.$getPlayerType) { + var sPlayer = this.$getPlayerType(sType); + if (!sPlayer || !this.$isSupported(sPlayer)) + return "no"; + if (sPlayer.indexOf("Wmp") != -1) + return "maybe"; + return "probably"; //we're sooo confident ;) + } + + return "no"; + }; + + /** + * Starts playing the media requested + * + */ + this.play = function() { + this.setProperty("paused", false); + }; + + /** + * Pauses playing the media requested + * + */ + this.pause = function() { + this.setProperty("paused", true); + }; + + // looping + this.start = this.end = this.loopStart = this.loopEnd = + this.playCount = this.currentLoop = 0; + + // cue ranges + this.addCueRange = function(sClassName, sId, iStart, iEnd, bPauseOnExit, fEnterCallback, fExitCallback) { + //to be overridden by the component + }; + + this.removeCueRanges = function(sClassName) { + //to be overridden by the component + }; + + /** + * Return a counter as you commonly see in front panels of CD/ DVD players + * + * @link http://php.net/strftime + * @param {Number} iMillis Amount of milliseconds to transform in a counter + * @param {String} sFormat Format of the counter is the form of PHP's strftime function: + * %H - hour as a decimal number using a 24-hour clock (range 00 to 23) + * %M - minute as a decimal number + * %S - second as a decimal number + * %Q - Millisecond as decimal (000-999) + * %n - newline character + * %t - tab character + * %T - current time, equal to %H:%M:%S + * %% - a literal `%' character + * @param {Boolean} bReverse Show the counter in reverse notation (countdown) + * @type {String} + */ + this.getCounter = function(iMillis, sFormat, bReverse) { + // for String.pad, 'apf.PAD_LEFT' is implicit + if (bReverse) + iMillis = iMillis - this.duration; + var iSeconds = Math.round(Math.abs(iMillis / 1000)), + sHours = String(Math.round(Math.abs(iSeconds / 60 / 60))).pad(2, "0"), + sMinutes = String(Math.round(Math.abs(iSeconds / 60))).pad(2, "0"), + sSeconds = String(iSeconds).pad(2, "0"), + sMillis = String(Math.round(Math.abs(iMillis % 1000))).pad(3, "0"); + return (bReverse ? "- " : "") + sFormat.replace(/\%T/g, "%H:%M:%S") + .replace(/\%[a-zA-Z\%]/g, function(sMatch) { + switch (sMatch) { + case "%H": + return sHours; + case "%M": + return sMinutes; + case "%S": + return sSeconds; + case "%Q": + return sMillis; + case "%n": + return "\n"; + case "%t": + return "\t"; + case "%%": + return "%"; + } + }); + }; + + /** + * Set the source for a media element by going through all the <source> + * child elements of the <audio> or <video> node, searching for + * a valid source media file that is playable by one of our plugins. + * The 'src' and 'type' attributes respectively have precedence over any + * <source> element. + * It also parses the <nomedia> tag that specifies what text or HTML to + * display when a medium is not supported and/ or playable. + * + * @param {XmlDomElement} [aml] Parent Aml node of the player + * @return {Boolean} Tells the client that no supported/ playable source file was found + */ + this.setSource = function() { + if (!this.src) { // no direct src-attribute set + var src, type, oSources = this.$sources; + // iterate through all the tags from left to right + for (var i = 0, j = oSources.length; i < j; i++) { + src = oSources[i].src; + if (!src) continue; + type = oSources[i].type; + if (!type) // auto-detect type, based on file extension + type = this.$guessType(src); + if (this.canPlayType(type) != "no") { + // yay! we found a type that we can play for the client + this.src = src; + this.type = type; + break; //escape! + } + } + } + else if (!this.type) { + this.type = this.$guessType(this.src); + if (this.canPlayType(this.type) == "no") + return false; + } + + return (this.src && this.type); + }; + + this.$addSource = function(amlNode) { + clearTimeout(this.$amlTimer); + + if (amlNode.localName != "source"){ + + throw new Error(apf.formatErrorString(0, this, + "Parsing Media node", + "Found element which is not a source element", this)); + + return false; + } + + this.$sources.pushUnique(amlNode); + + var _self = this; + this.$amlTimer = $setTimeout(function() { + clearTimeout(_self.$amlTimer); + _self.dispatchEvent("AMLMediaReady"); + }); + }; + + this.$removeSource = function(amlNode) { + this.$sources.remove(amlNode); + }; + + this.addEventListener("DOMNodeInserted", function(e){ + if (e.relatedNode != this || e.currentTarget.nodeType != 1) //@todo shouldn't this check for localName? + return; + + this.$addSource(e.currentTarget); + }); + + this.addEventListener("DOMNodeRemoved", function(e){ + var node = e.currentTarget; + // we support two levels deep: + if (!(node.parentNode == this || node.parentNode.parentNode == this)) + return; + + this.$removeSource(node); + }); + + +}).call(apf.Media.prototype = new apf.StandardBinding()); + + +apf.nomedia = function(struct, tagName) { + this.$init(tagName || "nomedia", apf.NODE_HIDDEN, struct); +}; + +(function() { + this.addEventListener("DOMNodeInsertedIntoDocument", function() { + this.parentNode.notSupported = + apf.getXmlString(this.$aml).replace(/<\/?a:nomedia[^>]*>/g, ""); + }); +}).call(apf.nomedia.prototype = new apf.AmlElement()); + +apf.aml.setElement("nomedia", apf.nomedia); + +// network state (.networkState) +apf.Media.NETWORK_EMPTY = 0; +apf.Media.NETWORK_IDLE = 1; +apf.Media.NETWORK_LOADING = 2; +apf.Media.NETWORK_LOADED = 3; + +// ready state (.readyState) +apf.Media.HAVE_NOTHING = 0; +apf.Media.HAVE_METADATA = 1; +apf.Media.HAVE_SOME_DATA = 2; //wtf?? +apf.Media.HAVE_CURRENT_DATA = 3; +apf.Media.HAVE_FUTURE_DATA = 4; +apf.Media.HAVE_ENOUGH_DATA = 5; + + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/core/baseclasses/multicheck.js)SIZE(16543)TIME(Mon, 10 Jan 2011 10:53:11 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + +apf.__MULTICHECK__ = 1 << 22; + + + +/** + * All elements inheriting from this {@link term.baseclass baseclass} have checkable items. + * + * @constructor + * @baseclass + * @author Ruben Daniels (ruben AT ajax DOT org) + * @version %I%, %G% + * @since 3.0 + * + * @todo type detection, errors (see functions in multiselect) + */ +apf.MultiCheck = function(){ + this.$regbase = this.$regbase | apf.__MULTICHECK__; + + /**** Properties ****/ + + this.multicheck = true; + this.checklength = 0; + this.$checkedList = []; + + /**** Public Methods ****/ + + /** + * Checks a single, or set of. + * The checking can be visually represented in this element. + * The element can be checked, partialy checked or unchecked + * + * @param {mixed} xmlNode the identifier to determine the selection. + * @return {Boolean} whether the selection could not be made + * + * @event beforecheck Fires before a check is made + * + * @event aftercheck Fires after a check is made + * + */ + this.check = function(xmlNode, userAction){ + if (userAction && this.disabled + || this.$checkedList.indexOf(xmlNode) > -1) + return; + + if (userAction + && this.$executeSingleValue("check", "checked", xmlNode, "true") !== false) + return; + + if (this.dispatchEvent("beforecheck", {xmlNode : xmlNode}) === false) + return false; + + if (!this.multicheck && this.$checkedList.length) + this.clearChecked(true); + + this.$checkedList.push(xmlNode); + + + if (this.$isTreeArch) { + //Children + var nodes = xmlNode.selectNodes(".//" + + this.each.split("|").join("|.//")); + + this.checkList(nodes, null, true, true); + + //Parents + var all, pNode = this.getTraverseParent(xmlNode); + while(pNode && pNode != this.xmlRoot) { + nodes = this.getTraverseNodes(pNode); + + all = true; + for (var i = 0; i < nodes.length; i++) { + if (this.$checkedList.indexOf(nodes[i]) == -1) { + all = false; + break; + } + } + + apf.setStyleClass(apf.xmldb.getHtmlNode(pNode, this), + all ? "checked" + : "partial", ["partial", "checked"]); + + if (all) //logical assumption that parent cannot be selected at this point + this.$checkedList.push(pNode); + + pNode = this.getTraverseParent(pNode); + } + } + + + this.$setStyleClass(apf.xmldb.getHtmlNode(xmlNode, this), + "checked", ["partial"]); + + this.dispatchEvent("aftercheck", { + list : this.$checkedList, + xmlNode : xmlNode + }); + }; + + /** + * Unchecks a single, or set of. + * + * @param {mixed} xmlNode the identifier to determine the selection. + * @return {Boolean} whether the selection could be made + * + * @event beforeuncheck Fires before a uncheck is made + * + * @event afteruncheck Fires after a uncheck is made + * + */ + this.uncheck = function(xmlNode, userAction){ + if (userAction && this.disabled + || this.$checkedList.indexOf(xmlNode) == -1) + return; + + if (userAction + && this.$executeSingleValue("check", "checked", xmlNode, "false") !== false) + return; + + + if (this.$isTreeArch) + return this.checkList([xmlNode], true, true); + + + if (this.dispatchEvent("beforeuncheck", { + xmlNode : xmlNode + }) === false) + return false; + + this.$checkedList.remove(xmlNode); + this.$setStyleClass(apf.xmldb.getHtmlNode(xmlNode, this), + "", ["checked", "partial"]); + + this.dispatchEvent("afteruncheck", { + list : this.$checkedList, + xmlNode : xmlNode + }); + }; + + /** + * Toggles between check and uncheck a single, or set of. + * + * @param {mixed} xmlNode the identifier to determine the selection. + * + */ + this.checkToggle = function(xmlNode, userAction){ + if (userAction && this.disabled) + return; + + if (xmlNode.style) { + var htmlNode = xmlNode, + id = htmlNode.getAttribute(apf.xmldb.htmlIdTag); + while (!id && htmlNode.parentNode) + id = (htmlNode = htmlNode.parentNode) + .getAttribute(apf.xmldb.htmlIdTag); + xmlNode = apf.xmldb.getNode(htmlNode) + } + + if (this.$checkedList.indexOf(xmlNode) > -1) + this.uncheck(xmlNode, userAction); + else + this.check(xmlNode, userAction); + }; + + /** + * Checks a set of items + * + * @param {Array} xmlNodeList the {@link term.datanode data nodes} that will be selected. + * @param {boolean} uncheck + * @param {boolean} noClear + * @param {boolean} noEvent whether to not call any events + * @event beforecheck Fires before a check is made + * object: + * {XMLElement} xmlNode the {@link term.datanode data node} that will be deselected. + * @event aftercheck Fires after a check is made + * object: + * {XMLElement} xmlNode the {@link term.datanode data node} that is deselected. + */ + this.checkList = function(xmlNodeList, uncheck, noClear, noEvent, userAction){ + //if (apf.isIE < 8) + if (!xmlNodeList.indexOf) + xmlNodeList = apf.getArrayFromNodelist(xmlNodeList); + //@todo is this need for ie8 and/or other browsers + + if (userAction){ + if (this.disabled) + return; + + var changes = []; + for (var c, i = 0; i < xmlNodeList.length; i++) { + c = this.$executeSingleValue("check", "checked", xmlNodeList[i], uncheck ? "false" : "true", true) + if (c === false) break; + changes.push(c); + } + + if (changes.length) { + return this.$executeAction("multicall", changes, "checked", + xmlNodeList[0], null, null, + xmlNodeList.length > 1 ? xmlNodeList : null); + } + } + + if (userAction && this.disabled) return; + + if (!noEvent && this.dispatchEvent("beforecheck", { + list : xmlNodeList + }) === false) + return false; + + if (!uncheck && !noClear) + this.clearChecked(true); + + if (!this.multicheck) + xmlNodeList = [xmlNodeList[0]]; + + var i; + if (uncheck) { + for (i = xmlNodeList.length - 1; i >= 0; i--) { + this.$checkedList.remove(xmlNodeList[i]); + this.$setStyleClass( + apf.xmldb.getHtmlNode(xmlNodeList[i], this), "", ["checked"]); + } + } + else { + for (i = xmlNodeList.length - 1; i >= 0; i--) { + this.$checkedList.push(xmlNodeList[i]); + this.$setStyleClass( + apf.xmldb.getHtmlNode(xmlNodeList[i], this), "checked"); + } + } + + + if (!noEvent && this.$isTreeArch) { + var _self = this; + function recur(xmlNode, forceChange) { + var nodes = _self.getTraverseNodes(xmlNode); + if (!nodes.length) { + if (forceChange) { + if (uncheck) { + _self.$checkedList.remove(xmlNode); + _self.$setStyleClass(apf.xmldb.getHtmlNode(xmlNode, _self), + "", ["checked"]); + return 0; + } + else { + if (_self.$checkedList.indexOf(xmlNode) == -1) { + _self.$checkedList.push(xmlNode); + _self.$setStyleClass( + apf.xmldb.getHtmlNode(xmlNode, _self), "checked"); + } + return 1; + } + } + return _self.$checkedList.indexOf(xmlNode) > -1 ? 1 : 0; + } + + var isInList = _self.$checkedList.indexOf(xmlNode) != -1, + shouldBeChanged = forceChange + || xmlNodeList.indexOf(xmlNode) > -1 && (uncheck + ? !isInList + : isInList), + all = true, + none = true, + partial = false, + isChecked; + for (var i = nodes.length - 1; i >= 0; i--) { + isChecked = recur(nodes[i], shouldBeChanged); + if (isChecked) { + none = false; + if (!partial && isChecked == 2) { + partial = true; + //break; + } + } + else + all = false; + if (!all && !none) { + partial = true; + //break; + } + } + + if (xmlNode == _self.xmlRoot) + return; + + if (all) { + if (!isInList) { + _self.$checkedList.push(xmlNode); + apf.setStyleClass(apf.xmldb.getHtmlNode(xmlNode, _self), + "checked", ["partial"]); + } + } + else{ + if (isInList) + _self.$checkedList.remove(xmlNode); + + apf.setStyleClass(apf.xmldb.getHtmlNode(xmlNode, _self), + partial ? "partial" : "", ["partial", "checked"]); + } + + return all ? 1 : (none ? 0 : 2); + } + + recur(this.xmlRoot) + } + + + if (!noEvent) + this.dispatchEvent("aftercheck", { + list : xmlNodeList + }); + }; + + /** + * Removes the selection of one or more checked nodes. + * + * @param {Boolean} [singleNode] whether to only deselect the indicated node + * @param {Boolean} [noEvent] whether to not call any events + * @event beforeuncheck Fires before a uncheck is made + * object: + * {XMLElement} xmlNode the {@link term.datanode data node} that will be deselected. + * @event afteruncheck Fires after a uncheck is made + * object: + * {XMLElement} xmlNode the {@link term.datanode data node} that is deselected. + */ + this.clearChecked = function(noEvent){ + if (!noEvent && this.dispatchEvent("beforeuncheck", { + xmlNode : this.$checkedList + }) === false) + return false; + + for (var i = this.$checkedList.length - 1; i >= 0; i--) { + this.$setStyleClass( + apf.xmldb.getHtmlNode(this.$checkedList[i], this), "", ["checked"]); + } + + this.$checkedList.length = 0; + + if (!noEvent) { + this.dispatchEvent("afteruncheck", { + list : this.$checkedList + }); + } + }; + + /** + * Determines whether a node is checked. + * + * @param {XMLElement} xmlNode The {@link term.datanode data node} to be checked. + * @return {Boolean} whether the element is selected. + */ + this.isChecked = function(xmlNode){ + return this.$checkedList.indexOf(xmlNode) > -1; + }; + + /** + * Retrieves an array or a document fragment containing all the checked + * {@link term.datanode data nodes} from this element. + * + * @param {Boolean} [xmldoc] whether the method should return a document fragment. + * @return {mixed} the selection of this element. + */ + this.getChecked = function(xmldoc){ + var i, r; + if (xmldoc) { + r = this.xmlRoot + ? this.xmlRoot.ownerDocument.createDocumentFragment() + : apf.getXmlDom().createDocumentFragment(); + for (i = 0; i < this.$checkedList.length; i++) + apf.xmldb.cleanNode(r.appendChild( + this.$checkedList[i].cloneNode(true))); + } + else { + for (r = [], i = 0; i < this.$checkedList.length; i++) + r.push(this.$checkedList[i]); + } + + return r; + }; + + /** + * Checks all the {@link term.eachnode each nodes} of this element + * + */ + this.checkAll = function(userAction){ + if (!this.multicheck || userAction && this.disabled || !this.xmlRoot) + return; + + var nodes = this.$isTreeArch + ? this.xmlRoot.selectNodes(".//" + + this.each.split("|").join("|.//")) + : this.getTraverseNodes(); + + this.checkList(nodes); + }; + + this.addEventListener("beforeload", function(){ + if (!this.$hasBindRule("checked")) //only reset state when check state isnt recorded + this.clearChecked(true); + }); + + this.addEventListener("afterload", function(){ + if (!this.$hasBindRule("checked") && this.$checkedList.length) //only reset state when check state isnt recorded + this.checkList(this.$checkedList, false, true, false); //@todo could be optimized (no event calling) + }); + + this.addEventListener("xmlupdate", function(e){ + if (e.action == "synchronize" || e.action == "update") { + //@todo list support! + var c1 = apf.isTrue(this.$applyBindRule("checked", e.xmlNode)); + var c2 = this.isChecked(e.xmlNode); + if (c1 != c2) { + if (c1) { + this.check(e.xmlNode); + } + else { + this.uncheck(e.xmlNode); + } + } + } + }); + + + this.addEventListener("aftercheck", function(){ + //@todo inconsistent because setting this is in event callback + if (this.checklength != this.$checkedList.length) + this.setProperty("checklength", this.$checkedList.length); + }); + + this.addEventListener("afteruncheck", function(){ + //@todo inconsistent because setting this is in event callback + if (this.checklength != this.$checkedList.length) + this.setProperty("checklength", this.$checkedList.length); + }); + +}; + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/core/baseclasses/teleport.js)SIZE(8790)TIME(Mon, 10 Jan 2011 10:53:11 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + + +/** + * Element which specifies the ways the application can communicate to remote + * data sources. + * Example: + * Example of the {@link teleport.cgi rpc module with the cgi protocol}. + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * //This function is called when the search returns + * function processSearch(data, state, extra){ + * alert(data) + * } + * + * //Execute a search for the product car + * comm.searchProduct('car', 10); + * + * + * Example: + * Example of the {@link teleport.soap rpc module with the soap protocol}. + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * //This function is called when the search returns + * function processSearch(data, state, extra){ + * alert(data) + * } + * + * //Execute a search for the product car + * comm.searchProduct('car', 10); + * + * + * Example: + * Writing to a file with a WebDAV connector + * + * + * + * + * // write the text 'bar' to a file on the server called 'foo.txt' + * myWebDAV.write('http://my-webdav-server.com/dav_files/foo.txt', 'bar'); + * + * + * Example: + * XMPP connector with new message notification + * + * + * + * + * // This function is called when a message has arrived + * function messageReceived(from){ + * alert('Received message from ' + from); + * } + * + * // Send a message to John + * myXMPP.sendMessage('john@my-jabber-server.com', 'A test message', '', + * apf.xmpp.MSG_CHAT); + * + * + * + * @attribute {String} url the location of the server that is + * recipient of the rpc messages. + * @attribute {String} [route-server] String specifying the url to the route script. + * Remarks: + * The route script will receive the route information in 3 extra headers: + * X-Route-Request - Containing the destination url.
    + * X-Proxy-Request - Containing the destination url.
    + * X-Compress-Response - Set to 'gzip'.
    + * @attribute {Boolean} [autoroute] whether the call should be routed + * through a proxy when a permission + * error occurs due to the same domein policy. + * @attribute {Number} [timeout] the number of milliseconds after + * which the call is considered timed out. + * + * + * @define teleport + * @addnode global + * @allowchild {teleport} + * + * @default_private + */ +apf.Teleport = function(){ + this.$init(true); +}; + +apf.__TELEPORT__ = 1 << 28; + +(function() { + this.$parsePrio = "002"; + + this.$regbase = this.$regbase | apf.__TELEPORT__; + + this.$booleanProperties["autoroute"] = true; + + this.$supportedProperties.push("url", "timeout", "protocol", "route-server", + "autoroute"); + + this.$propHandlers["url"] = function(value) { + var url = new apf.url(value); + + // do some extra startup/ syntax error checking + if (!url.protocol) { + return apf.console.error(apf.formatErrorString(0, this, + "Communication (Teleport) initialization error", + "Invalid server url provided: '" + value + "'")); + } + + this.$host = url.host; + this.$rootPath = url.path; + this.$server = value.replace(new RegExp(this.$rootPath + "$"), ""); + }; + + this.$propHandlers["timeout"] = function(value) { + this.timeout = parseInt(value) || 10000; + }; + + this.$propHandlers["protocol"] = function(value) { + var proto = value.toLowerCase(); + if (!apf[proto]) { + throw new Error(apf.formatErrorString(1025, null, "Teleport baseclass", + "Could not find Ajax.org Teleport RPC Component '" + proto + "'", this)); + } + this.implement(apf[proto]); + }; + + /** + * Returns a string representation of this object. + */ + this.toString = function(){ + return "[Ajax.org Teleport Component : " + (this.name || "") + + " (" + this.type + ")]"; + }; + + this.addEventListener("DOMNodeInsertedIntoDocument", function() { + var error; + if (this.type && this.type == "socket") { + // Implement Socket Module + if (!apf.socket) + error = "Socket"; + else + this.implement(apf.socket); + } + else { + // Implement HTTP Module + if (!apf.http) + error = "HTTP"; + else + this.implement(apf.http); + } + if (error) { + throw new Error(apf.formatErrorString(1024, null, "Teleport baseclass", + "Could not find Ajax.org Teleport " + error + " Component", this.$aml)); + } + + if (this.id) + apf.$asyncObjects[this.id] = 1; + }); +}).call(apf.Teleport.prototype = new apf.AmlElement()); + + +apf.teleportLog = function(extra){ + var xml, request = extra.method + " " + extra.url + " HTTP/1.1\n\n" + extra.data; + + this.setXml = function(pNode){ + if (!xml) { + var doc = pNode.ownerDocument; + xml = doc.createElement(extra.tp.localName || extra.type || "http"); + xml.appendChild(doc.createElement("request")).appendChild(doc.createTextNode(request || "-")); + xml.appendChild(doc.createElement("response")).appendChild(doc.createTextNode(response || "-")); + } + + apf.xmldb.appendChild(pNode, xml); + } + + this.request = function(headers){ + request = request.replace(/\n\n/, "\n" + headers.join("\n") + "\n\n"); + + if (xml) + apf.setQueryValue(xml, "request/text()", request); + + this.request = function(){} + } + + var response = ""; + this.response = function(extra){ + try { + var headers = extra.http.getAllResponseHeaders(); + response = "HTTP/1.1 " + extra.status + " " + extra.statusText + "\n" + + (headers ? headers + "\n" : "\n") + + extra.http.responseText; + + if (xml) + apf.setQueryValue(xml, "response/text()", response); + } catch(ex) {} + } +} + + + + +apf.Init.run("teleport"); + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/core/baseclasses/transaction.js)SIZE(23494)TIME(Mon, 10 Jan 2011 10:53:11 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + +apf.__TRANSACTION__ = 1 << 3; + + + +/** + * All elements inheriting from this {@link term.baseclass baseclass} have transaction support. A transaction is a + * set of changes to data which are treated as one change. When one of the + * changes in the set fails, all the changes will be cancelled. In the case of + * a gui this is mostly relevant when a user decides to cancel after + * making several changes. A good example are the well known property windows + * with an ok, cancel and apply button. + * + * When a user edits data, for instance user information, all the changes are + * seen as one edit and put on the undo stack as a single action. Thus clicking + * undo will undo the entire transaction, not just the last change done by that + * user in the edit window. Transaction support both optimistic and pessimistic + * locking. For more information on the latter see the first example below. + * Example: + * This example shows a list with one item. When double clicked on the item + * a window shows that allows the user to edit the properties of this item. + * When the window is closed the changes are committed to the xml data. If the + * user presses cancel the changes are discarded. By pressing the 'add new item' + * button the same window appears which allows the user to add a new item. All + * changes made by the user are also sent to the original data source via + * rpc calls. When the user starts editing an existing item a lock is requested. + * This is not necesary for transaction support, but this example shows that it + * is possible. When the lock fails the window will close. By hooking the + * 'lockfail' event the user can be notified of the reason. For more information + * see {@link term.locking}. + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * message + * + * + * + * + * Add new item + * + * + * Name + * + * Subject + * + * + * Message + * + * + * OK + * Cancel + * Apply + * + * + * + * @constructor + * @baseclass + * + * @inherits apf.StandardBinding + * @inherits apf.DataAction + * + * @event transactionconflict Fires when data in a transaction is being updated by an external process. + * + * @author Ruben Daniels (ruben AT ajax DOT org) + * @version %I%, %G% + * @since 0.8.9 + */ +apf.Transaction = function(){ + this.$regbase = this.$regbase | apf.__TRANSACTION__; + + this.$addParent = + this.$transactionNode = + this.$transactionSubject = + this.$originalNode = + this.$inTransaction = + this.$lastAction = null; + + this.$supportedProperties.push("autoshow"); + + /** + * @attribute {Boolean} autoshow whether this element is shown when a transaction begins. + */ + this.$booleanProperties["autoshow"] = true; + + /** + * Commits a started transaction. This will trigger an update or add action. + * forked copy of template data. + * + * @todo check what's up with actiontracker usage... + * @bug when a commit is cancelled using the onbeforecommit event, the + * state of the element becomes undefined. + */ + this.commit = function(repeat){ + if (!this.$inTransaction) + return false; + + if (!this.$validgroup && this.validgroup) + this.$validgroup = self[this.validgroup]; + + if (this.$validgroup && !this.$validgroup.isValid()) + return false; + + var returnValue = true; + if (!this.$at.undolength) { + if (repeat) + return false; + + this.$at.purge(); + this.$inTransaction = false; + + this.load(this.$originalNode); + this.$helperModel.reset(); + + returnValue = false; + } + else { + + apf.console.info("Committing transaction on " + this.localName + "[" + this.name + "]"); + + + this.$at.reset();//purge(); + this.$inTransaction = false; + + //@todo recursive + this.$transactionNode.removeAttribute(apf.xmldb.xmlListenTag) + + if (this.$lastAction == "add") { + //Use ActionTracker :: this.xmlData.selectSingleNode("DataBinding/@select") ? o.xmlRoot : o.selected + if (this.$transactionSubject.$executeAction("appendChild", + [this.$addParent, this.$transactionNode], "add", this.$transactionNode) + && this.$transactionSubject.hasFeature(apf.__MULTISELECT__)) { + this.$transactionSubject.select(this.$transactionNode); + } + + this.$transactionSubject = null; + } + else { + //Use ActionTracker + //getTraverseParent(o.selected) || o.xmlRoot + var at = this.$at; + this.$at = this.dataParent + ? this.dataParent.parent.getActionTracker() + : null;//self[this.getAttribute("actiontracker")];//this.dataParent.parent.getActionTracker(); + + this.$transactionSubject.$executeAction("replaceNode", [this.$originalNode, this.$transactionNode], + "update", this.$transactionNode); + + this.$at = at; + + //this.load(this.$transactionNode); + } + } + + this.$transactionNode = null; + this.$addParent = null; + this.$originalNode = null; + + if (this.autoshow) { + if (this.autoshow == -1) + this.autoshow = true; + else + this.hide(); + } + + return returnValue; + }; + + /** + * Rolls back the started transaction. + */ + this.rollback = function(noLoad){ + if (!this.$inTransaction) + return; + + + apf.console.info("Rolling back transaction on " + this.localName + "[" + this.name + "]"); + + + if (this.$at) { + if (this.rpcMode == "realtime") + this.$at.undo(-1); + + this.$at.reset(); + } + //this.xmldb.reset(); + + this.$transactionNode = null; //prevent from restarting the transaction in load + this.$addParent = null; + + //Cleanup + if (!noLoad) + this.load(this.$originalNode); + + this.$helperModel.reset(); + + this.$stopAction(this.$lastAction, true); + + this.$originalNode = null; + this.$inTransaction = false; + + if (this.autoshow) { + if (this.autoshow == -1) + this.autoshow = true; + else + this.hide(); + } + }; + + /** + * Starts a transaction for this element. This is either an add or update. + * @param {String} strAction the type of transaction to start + * Possible values: + * add the transaction is started to add a new {@link term.datanode data node}. + * update the transaction is started to update an existing {@link term.datanode data node}. + * @param {XMLElement} xmlNode + * @param {XMLElement} parentXmlNode + * @param {AMLElement} dataParent + */ + this.begin = function(strAction, xmlNode, parentXmlNode, dataParent){ + if (this.$inTransaction) { + /*throw new Error(apf.formatErrorString(0, this, + "Starting Transaction", + "Cannot start a transaction without committing or rolling \ + back previously started transaction.", this.oldRoot));*/ + + + apf.console.warn("Rolling back transaction, while starting a new one"); + + + if (this.autoshow) + this.autoshow = -1; + this.rollback(); + } + + + apf.console.info("Beginning transaction on " + this.localName + "[" + this.name + "]"); + + + //Add should look at dataParent and take selection or xmlRoot + //winMail.dataParent.parent.xmlRoot + + var _self = this; + this.$lastAction = strAction; + + if (!this.$lastAction) { + this.$lastAction = this.xmlRoot && "update" || "add"; + /*this.actionRules && (this.actionRules.add + ? "add" + : (this.actionRules.update + ? "update" + : null)) || this.xmlRoot && "update";*/ + } + + + if (!this.$lastAction) { + throw new Error(apf.formatErrorString(0, this, + "Starting Transaction", + "Could not determine whether to add or update.")); + } + + + //Determines the actiontracker to integrate the grouped action into + if (dataParent) + this.$setDynamicProperty("model", "[" + dataParent.id + ".selected]"); //@todo what if it doesn't have an id + + if (xmlNode && this.$lastAction == "update") { + this.xmlRoot = xmlNode; + //this.$inTransaction = -1; //Prevent load from triggering a new transaction + //this.load(xmlNode); + } + + /* + * @todo: + * create actiontracker based on data id, destroy actiontracker on cancel/commit - thus being able to implement editor feature natively + * Multiple transactions can exist at the same time in the same container, but on different data + * .cancel(xmlNode) .apply(xmlNode) + * .list(); // returns a list of all started transactions + * Add undo/redo methods to winMultiEdit + * Route undolength/redolength properties + * Setting replaceat="start" or replaceat="end" + */ + if (!this.$at) { + this.$at = new apf.actiontracker(); + var propListen = function(e){ + _self.setProperty(e.prop, e.value); + }; + this.$at.addEventListener("prop.undolength", propListen); + this.setProperty("undolength", 0); + this.$at.addEventListener("prop.redolength", propListen); + this.setProperty("redolength", 0); + } + if (!this.$helperModel) { + this.$helperModel = new apf.model(); + this.$helperModel["save-original"] = true; + this.$helperModel.load(""); + } + + this.$transactionNode = null; + this.$addParent = null; + this.$originalNode = this.xmlRoot; + + + if (typeof apf.offline != "undefined" && !apf.offline.canTransact()) + return false; + + + this.$inTransaction = true; + function begin(){ + + if (!this.$transactionNode) { + throw new Error(apf.formatErrorString(0, this, + "Starting transaction", + "Missing transaction node. Cannot start transaction. \ + This error is unrecoverable.")); + } + + + this.$inTransaction = -1; + this.$helperModel.data.appendChild(this.$transactionNode);//apf.xmldb.cleanNode()); + this.load(this.$helperModel.data.firstChild); + this.$inTransaction = true; + + if (this.disabled) + this.enable(); + + if (this.autoshow) { + if (this.autoshow == -1) + this.autoshow = true; + else + this.show(); + } + } + + //Determine data parent + dataParent = this.dataParent && this.dataParent.parent; + + if (!dataParent || !dataParent.$actions + || !dataParent.$actions[this.$lastAction]) { + dataParent = this; + } + + //Add + if (this.$lastAction == "add") { + //Check for add rule on data parent + var rule, actionRules = dataParent.$actions; + if (actionRules) { + if (xmlNode && xmlNode.nodeType) + rule = actionRules.getRule("add", xmlNode); + else if (typeof xmlNode == "string") { + if (xmlNode.trim().charAt(0) == "<") { + xmlNode = apf.getXml(xmlNode); + rule = actionRules.getRule("add", xmlNode) + } + else { + var rules = actionRules.$rules["add"]; + for (var i = 0, l = rules.length; i < l; i++) { + if (rules[i].getAttribute("type") == xmlNode) { + xmlNode = null; + rule = rules[i]; + break; + } + } + } + } + + if (!rule) + rule = (dataParent.$actions["add"] || {})[0]; + } + else + rule = null; + + + if (typeof apf.offline != "undefined" && !apf.offline.onLine + && !rule.get) + return false; + + + //Run the add code (copy from multiselect) but don't add until commit + var refNode = this.$isTreeArch ? this.selected || this.xmlRoot : this.xmlRoot; + var callback = function(addXmlNode, state, extra){ + if (state != apf.SUCCESS) { + var oError; + + oError = new Error(apf.formatErrorString(1032, dataParent, + "Loading xml data", + "Could not add data for control " + dataParent.name + + "[" + dataParent.tagName + "] \nUrl: " + extra.url + + "\nInfo: " + extra.message + "\n\n" + xmlNode)); + + if (extra.tpModule.retryTimeout(extra, state, dataParent, oError) === true) + return true; + + throw oError; + } + + /*if (apf.supportNamespaces && node.namespaceURI == apf.ns.xhtml) { + node = apf.getXml(node.xml.replace(/xmlns\=\"[^"]*\"/g, "")); + //@todo import here for webkit? + }*/ + + if (typeof addXmlNode != "object") + addXmlNode = apf.getXmlDom(addXmlNode).documentElement; + if (addXmlNode.getAttribute(apf.xmldb.xmlIdTag)) + addXmlNode.setAttribute(apf.xmldb.xmlIdTag, ""); + + if (!dataParent.$startAction("add", addXmlNode, _self.rollback)) + return false; + + var actionNode = (dataParent.$actions && + dataParent.$actions.getRule("add", dataParent.$isTreeArch + ? dataParent.selected + : dataParent.xmlRoot) || {})[2]; + + if (parentXmlNode) { + _self.$addParent = parentXmlNode; + } + else if (actionNode && actionNode.getAttribute("parent")) { + _self.$addParent = dataParent.xmlRoot + .selectSingleNode(actionNode.getAttribute("parent")); + } + else { + _self.$addParent = dataParent.$isTreeArch + ? dataParent.selected || dataParent.xmlRoot + : dataParent.xmlRoot + } + + if (!_self.$addParent) + _self.$addParent = dataParent.xmlRoot || dataParent.getModel(true).data; + + if (apf.isWebkit && _self.$addParent.ownerDocument != addXmlNode.ownerDocument) + addXmlNode = _self.$addParent.ownerDocument.importNode(addXmlNode, true); //Safari issue not auto importing nodes + + _self.$transactionNode = addXmlNode; + _self.$transactionSubject = dataParent; + begin.call(_self); + } + + if (xmlNode) + return callback(xmlNode, apf.SUCCESS); + else { + if (rule && rule.get) + return apf.getData(rule.get, {xmlNode: refNode, callback: callback}) + else { + + throw new Error(apf.formatErrorString(0, this, + "Starting transaction", + "Missing add rule for transaction")); + + } + } + } + + //Update + else { + if (!dataParent.$startAction(this.$lastAction, this.xmlRoot, this.rollback)) + return false; + + this.$transactionSubject = dataParent; + this.$transactionNode = this.$originalNode.cloneNode(true);//xmldb.cleanNode(this.xmlRoot.cloneNode(true)); + //xmlNode.removeAttribute(xmldb.xmlIdTag); + + //@todo rename listening attributes + begin.call(this); + } + }; + + //Transaction nodes can always load data + this.$canLoadData = function(){ + return true; + } + + //Prevent model inheritance to the children + this.addEventListener("prop.model", function(e){ + return false; + }); + + //Prevent clear dynamic + this.clear = function(){ + this.documentId = this.xmlRoot = this.cacheId = null; + } + + //No need to restart the transaction when the same node is loaded + this.addEventListener("beforeload", function(e){ + var xmlNode = e.xmlNode; + + //@todo apf3.0 test if this can be enabled again + //if (this.$originalNode == xmlNode) + //return false; + + if (this.$inTransaction == -1) + return; + + if (this.$inTransaction) { + if (this.$transactionNode && xmlNode != this.$transactionNode) { + if (this.autoshow) + this.autoshow = -1; + + this.rollback(true); + } + else return; + } + + if (this.autoshow) + this.autoshow = -1; + + if (this.begin("update", xmlNode) !== false) + return false; + }); + + //hmm really? + //@todo what to do here? check original cloned node??? + /*this.addEventListener("xmlupdate", function(e){ + if (this.$inTransaction) { + this.dispatchEvent("transactionconflict", { + action : e.action, + xmlNode: e.xmlNode, + UndoObj: e.UndoObj, + bubbles : true + }); + } + });*/ + + //@todo add when not update??? + /*this.watch("visible", function(id, oldval, newval){ + if (!this.xmlRoot || oldval == newval) + return; + + if (newval) { + if (!this.$inTransaction) + this.begin(); + } + else { + if (this.$inTransaction) + this.rollback(); + } + });*/ +} + +/** + * @attribute {Boolean} transaction Whether this element provides transaction + * support for all it's children. + * @see baseclass.transaction + */ +apf.GuiElement.propHandlers["transaction"] = function(value){ + if (!(this.transaction = apf.isTrue(value))) + return; + + if (!this.hasFeature(apf.__DATABINDING__)) + this.implement(apf.StandardBinding); + + if (!this.hasFeature(apf.__DATAACTION__)) { + this.implement(apf.DataAction); + + if (this.actions) + this.$propHandlers["actions"].call(this, this.actions, "actions"); + } + + if (!this.hasFeature(apf.__TRANSACTION__)) { + this.implement(apf.Transaction); + + if (!this.validgroup) { + this.$validgroup = new apf.ValidationGroup(); + this.$validgroup.register(this); + } + + if (!this.id) + this.setProperty("id", this.localName + "_" + this.$uniqueId); + + var attr = this.attributes.getNamedItem("model"); + if (!attr) //@todo find a way to not have to add a model + this.attributes.push(attr = new apf.AmlAttr(this, "model", null)); + attr.inheritedValue = "{" + this.id + ".root}"; + + if (typeof this.autoshow == "undefined" + && (this.localName == "modalwindow" || this.localName == "window")) + this.autoshow = true; + } +} + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/core/baseclasses/virtualviewport.js)SIZE(28823)TIME(Mon, 10 Jan 2011 10:53:11 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + +apf.__VIRTUALVIEWPORT__ = 1 << 19; + + + +/** + * All elements inheriting from this {@link term.baseclass baseclass} can have a virtual viewport. + * + * @experimental This code has never been run. + * @constructor + * @baseclass + * @private + * + * @author Ruben Daniels (ruben AT ajax DOT org) & Mike de Boer + * @version %I%, %G% + * @since 1.0 + */ +apf.VirtualViewport = function(){ + this.$init(true); + + this.$regbase = this.$regbase | apf.__VIRTUALVIEWPORT__; + + this.virtualVTimer = null; + this._xmlUpdate = this.$xmlUpdate; + + apf.setStyleClass(this.$ext, "virtual"); + + this.$deInitNode = function(xmlNode, htmlNode){ + /* + Not the htmlNode is deleted, but the viewport is rerendered from this node on. + If viewport is too high either the render starting point is adjusted and + a complete rerender is requested, or the last empty elements are hidden + */ + this.viewport.redraw();//very unoptimized + }; + + this.$moveNode = function(xmlNode, htmlNode){ + /* + Do a remove when removed from current viewport + Do an add when moved to current viewport + Do a redraw from the first of either when both in viewport + */ + this.viewport.redraw();//very unoptimized + }; + + this.emptyNode = apf.xmldb.getXml(""); + this.$addEmpty = this.$add; + this.$add = function(xmlNode, Lid, xmlParentNode, htmlParentNode, beforeNode){ + //find new slot + var htmlNode = this.$pHtmlDoc.getElementById(Lid); + + if(!htmlNode) + return; + + //execute update + this.$updateNode(xmlNode, htmlNode);//, noModifier); + }; + + this.$fill = function(){ + + }; + + this.addEventListener("$load", function(){ + if (!this.viewport.limit) + this.viewport.limit = 1; + }); + + this.clear = function(nomsg, do_event){ + if (this.clearSelection) + this.clearSelection(!do_event); + + this.documentId = this.xmlRoot = this.cacheId = null; + + if (!nomsg) { + this.viewport.offset = 0; + this.viewport.length = 0; + this.viewport.resize(0); + this.viewport.sb.update(); + + this.$setClearMessage(this["empty-message"]); + } + else if(this.$removeClearMessage) + this.$removeClearMessage(); + + this.viewport.cache = null; + }; + + var _self = this; + this.viewport = { + offset : 0, + limit : 2, + length : 0, + sb : new apf.scrollbar(), + host : this, + cache : null, + + inited : false, + draw : function(){ + this.inited = true; + var limit = this.limit; this.limit = 0; + this.resize(limit, true); + }, + + redraw : function(){ + this.change(this.offset); + }, + + // set id's of xml to the viewport + prepare : function(){ + if (!this.inited) + this.draw(); + + var nodes = _self.getTraverseNodes(); + if (!nodes) + return; + + var docId = apf.xmldb.getXmlDocId(_self.xmlRoot), + hNodes = _self.$container.childNodes; + for (var j = 0, i = 0, l = hNodes.length; i < l; i++) { + if (hNodes[i].nodeType != 1) continue; + + hNodes[i].style.display = (j >= nodes.length) ? "none" : "block"; //Will ruin tables & lists + + apf.xmldb.nodeConnect(docId, nodes[j], hNodes[i], _self); + j++; + } + }, + + /** + * @note This function only supports single dimension items (also no grid, like thumbnails) + */ + resize : function(limit, updateScrollbar){ + this.cache = null; + + var i; + //Viewport shrinks + if (limit < this.limit) { + var nodes = _self.$container.childNodes; + for (i = nodes.length - 1; i >= 0; i--) { + if (nodes[i].nodeType != 1) continue; + _self.$container.removeChild(nodes[i]); + if (--this.limit == limit) break; + } + } + //Viewport grows + else if (limit > this.limit) { + for (i = this.limit; i < limit; i++) { + _self.$addEmpty(_self.emptyNode, "", _self.xmlRoot, _self.$container); + } + } + else + return; + + this.limit = limit; + + if (updateScrollbar) + this.sb.update(this.$container); + }, + + findNewLimit : function(scrollTop){ + var oHtml = _self.$container; + + if (!scrollTop) + scrollTop = oHtml.scrollTop; + + if (!_self.xmlRoot || oHtml.lastChild && oHtml.lastChild.style.display == "none") + return; + + //Grow + if (!oHtml.lastChild || oHtml.lastChild.offsetTop + oHtml.lastChild.offsetHeight <= oHtml.offsetHeight + scrollTop) { + var Lid, xmlNode, nodes, sel = _self.$getSelection(); + while (this.limit < this.length - 1 && (!oHtml.lastChild || oHtml.lastChild.offsetTop + oHtml.lastChild.offsetHeight <= oHtml.offsetHeight + scrollTop)) { + this.limit++; + + nodes = _self.getTraverseNodes(); + if (nodes.length < this.limit) { + this.limit = nodes.length; + break; + } + + xmlNode = nodes[nodes.length - 1]; + Lid = apf.xmldb.nodeConnect(_self.documentId, xmlNode, null, _self); + _self.$addEmpty(xmlNode, Lid, _self.xmlRoot, oHtml); + if (sel.indexOf(xmlNode) > -1) + _self.$select(oHtml.lastChild); + else + _self.$deselect(oHtml.lastChild); + } + } + //Shrink + else if (oHtml.lastChild && oHtml.lastChild.offsetTop > oHtml.offsetHeight + scrollTop) { + var lastChild; + while (this.limit > 2 && (lastChild = oHtml.lastChild).offsetTop > oHtml.offsetHeight + scrollTop) { + _self.$container.removeChild(lastChild); + this.limit--; + } + } + + if (!this.initialLimit) + this.initialLimit = this.limit; + }, + + /** + * @todo This method should be optimized by checking if there is + * overlap between the new offset and the old one + */ + change : function(offset, limit, updateScrollbar, noScroll){ + var offsetN; + + if (offset < 0) + offset = 0; + + if (offset > this.length - this.limit - 1) + offsetN = Math.floor(this.length - this.limit - 1); + else + offsetN = Math.floor(offset); + + if (!limit) + limit = this.limit; + + //var offsetN = Math.floor(offset); + + this.cache = null; + var diff = offsetN - this.offset, + oldLimit = this.limit; + if (diff * diff >= this.limit*this.limit) //there is no overlap + diff = false; + this.offset = offsetN; + + if (diff > 0) { //get last node before resize + var lastNode = _self.$container.lastChild; + if (lastNode.nodeType != 1) + lastNode = lastNode.previousSibling; + } + + /*if (limit && this.limit != limit) + this.resize(limit, updateScrollbar); + else */ + if (updateScrollbar) { + this.sb.$curValue = this.offset / (this.length - this.limit - 1); + this.sb.updatePos(); + } + + //this.viewport.prepare(); + + //Traverse through XMLTree + //var nodes = this.$addNodes(this.xmlRoot, this.$container, null, this.renderRoot); + var nodes = _self.getTraverseNodes(); + if (!nodes) + return; + + if (nodes.length < this.limit) { + if (offset > 0) + alert("shouldnt get here"); + else + this.resize(nodes.length); + } + + var docId = apf.xmldb.getXmlDocId(_self.xmlRoot), + hNodes = _self.$container.childNodes, + xmlNode, htmlNode, xmlPos, sel, len, j, i; + + //remove nodes from the beginning + if (diff > 0) { + xmlPos = oldLimit - diff; + len = hNodes.length, + sel = _self.$getSelection(); + for (j = 0, i = 0; j < diff && i < len; i++) { + htmlNode = _self.$container.firstChild; + if (htmlNode.nodeType == 1) { + j++; + xmlNode = nodes[xmlPos++]; + if (xmlNode) { + apf.xmldb.nodeConnect(docId, xmlNode, htmlNode, _self); + _self.$updateNode(xmlNode, htmlNode);//, noModifier); + if (sel.indexOf(xmlNode) > -1) + _self.$select(htmlNode); + else + _self.$deselect(htmlNode); + htmlNode.style.display = "block"; + } + else { + htmlNode.style.display = "none"; + } + } + + _self.$container.appendChild(htmlNode); + } + + //var lastNode = nodes[oldLimit - diff - 1] + } + //remove nodes from the end + else if (diff < 0) { + diff = diff * -1; + xmlPos = 0; //should be adjusted for changing limit + sel = _self.$getSelection(); + for (j = 0, i = hNodes.length-1; j < diff && i >= 0; i++) { + htmlNode = _self.$container.lastChild; + if (htmlNode.nodeType == 1) { + j++; + xmlNode = nodes[xmlPos++]; + apf.xmldb.nodeConnect(docId, xmlNode, htmlNode, _self); + _self.$updateNode(xmlNode, htmlNode);//, noModifier); + if (sel.indexOf(xmlNode) > -1) + _self.$select(htmlNode); + else + _self.$deselect(htmlNode); + htmlNode.style.display = "block"; + } + + _self.$container.insertBefore(htmlNode, _self.$container.firstChild); + } + } + //Recalc all nodes + else if (diff === false){ + len = hNodes.length; + sel = _self.$getSelection(); + for (j = 0, i = 0; i < len; i++) { + htmlNode = hNodes[i]; + if (htmlNode.nodeType == 1) { + xmlNode = nodes[j++]; + apf.xmldb.nodeConnect(docId, xmlNode, htmlNode, _self); + _self.$updateNode(xmlNode, htmlNode);//, noModifier); + if (sel.indexOf(xmlNode) > -1) + _self.$select(htmlNode); + else + _self.$deselect(htmlNode); + } + } + } + + if (!noScroll) { + if (offset >= this.length - this.initialLimit) { + diff = offset - (this.length - this.initialLimit) + 2; + _self.$container.scrollTop = (_self.$container.scrollHeight - _self.$container.offsetHeight) * (diff / 2); + } + else { + var scrollTop = (offset % 1) * _self.$container.firstChild.offsetHeight;//(diff/limit) * _self.$container.offsetHeight; + this.findNewLimit(scrollTop); + _self.$container.scrollTop = scrollTop; + } + + if (updateScrollbar) + this.sb.update(); + + return; + } + + //Build HTML + //_self.$fill(nodes); + + /*if (_self.$selected) { + _self.$deselect(_self.$selected); + _self.$selected = null; + } + + if (_self.selected && _self.$isInViewport(_self.selected)) + _self.select(_self.selected);*/ + } + }; + + this.viewport.sb.parentNode = new apf.Class().$init(); + this.viewport.sb.parentNode.$container = this.$pHtmlNode; + this.viewport.sb.dispatchEvent("DOMNodeInsertedIntoDocument"); + + //this.$container.style.paddingLeft = this.viewport.sb.$ext.offsetWidth + "px"; + + //this.viewport.sb.realtime = false;//!apf.isIE; + this.viewport.sb.attach(this.$container, this.viewport, function(timed, pos){ + var vp = _self.viewport; + + if (vp.sb.realtime || !timed) { + var l = vp.length - vp.initialLimit; + if (l == 0) + _self.$container.scrollTop = pos * (_self.$container.scrollHeight - _self.$container.offsetHeight); + else + vp.change(l * pos, vp.limit, false); + } + else { + clearTimeout(this.virtualVTimer); + this.virtualVTimer = $setTimeout(function(){ + vp.change(Math.round((vp.length - vp.initialLimit) * pos), vp.limit, false); + }, 300); + } + }); + + /* @todo + * - Fix bug in optimization + * - Fix flickering with larger viewport + * - Get templates to work + * - Firefox has problems with the scrollbar + * / Fix scrolling of items bigger than viewport (limit is too tight sometimes) + * - Improve pgup/pgdown + * - Fix multigrid lists (thumbnail) + * - Fix FF html conversion (insertHtmlNodes) + * - Optimize grow function to use fill + */ + + apf.layout.setRules(this.$container, "scrollbar", "\ + var s = apf.all[" + this.viewport.sb.$uniqueId + "];\ + s.update();\ + ", true); + apf.layout.queue(this.$container); + + this.$isInViewport = function(xmlNode, struct){ + /*var marker = xmlNode.selectSingleNode("preceding-sibling::a_marker"); + var start = marker ? marker.getAttribute("end") : 0; + + if(!struct && this.viewport.offset + this.viewport.limit < start + 1) + return false; + + var position = start; + var nodes = (marker || xmlNode).selectNodes("following-sibling::" + + this.each.split("|").join("following-sibling::")); + + for (var i = 0; i < nodes.length; i++) { + ++position; + if (nodes[i] == xmlNode) + break; + } + + if(struct) struct.position = position; + + if(this.viewport.offset > position + || this.viewport.offset + this.viewport.limit < position) + return false; + + return true;*/ + var nodes = this.getTraverseNodes(); + for (var i = 0, l = nodes.length; i < l; i++){ + if (nodes[i] == xmlNode) + return true; + } + + return false; + }; + + this.scrollTo = function(xmlNode, last){ + var sPos = {}; + this.$isInViewport(xmlNode, sPos); + this.viewport.change(sPos.position + (last ? this.viewport.limit - 1 : 0)); + }; + + /** + * @todo this one should be optimized + */ + this.getFirstTraverseNode = function(xmlNode){ + return this.getTraverseNodes(xmlNode)[0]; + }; + + /** + * @private + */ + this.$clearVirtualDataset = function(parentNode){ + var nodes = parentNode.childNodes; + for (var i = nodes.length - 1; i >= 0; i--) + parentNode.removeChild(nodes[i]); + }; + + /** + * @private + */ + this.$createVirtualDataset = function(xmlNode, length, docId) { + var marker = xmlNode.selectSingleNode("a_marker") + || xmlNode.appendChild(xmlNode.ownerDocument.createElement("a_marker")); + marker.setAttribute("start", "0"); + + if (length) { + marker.setAttribute("end", length); + marker.setAttribute("reserved", ++this.nodeCount[docId]); + this.nodeCount[docId] += length; + } + }; + + this.$xmlUpdate = function(){ + this.viewport.cache = null; + this.viewport.length = this.xmlRoot.selectNodes(this.each).length; //@todo fix this for virtual length + this.viewport.sb.update(this.$container); + this._xmlUpdate.apply(this, arguments); + }; + + this.$load = function(XMLRoot){ + //Add listener to XMLRoot Node + apf.xmldb.addNodeListener(XMLRoot, this); + + //Reserve here a set of nodeConnect id's and add them to our initial marker + //Init virtual dataset here + + if (!this.renderRoot && !this.getTraverseNodes(XMLRoot).length) + return this.clear("loading"); + + //Initialize virtual dataset if load rule exists + if (this.$hasBindRule("load")) + this.$createVirtualDataset(XMLRoot); + + //Prepare viewport + this.viewport.cache = null; + this.viewport.length = this.xmlRoot.selectNodes(this.each).length + 1; //@todo fix this for virtual length + this.viewport.prepare(); + + //Traverse through XMLTree + var nodes = this.$addNodes(XMLRoot, null, null, this.renderRoot); + + this.viewport.sb.update(this.$container); + + //Build HTML + //this.$fill(nodes); + + //Select First Child + if (this.selectable) { + if (this.autoselect) { + if (nodes.length) + this.$selectDefault(XMLRoot); + + else + this.setProperty("selected", null); //@todo review this + + } + else { + this.clearSelection(true); + var xmlNode = this.getFirstTraverseNode(); //should this be moved to the clearSelection function? + if (xmlNode) + this.setCaret(xmlNode); + + this.setProperty("selected", null); //@todo review this + this.setProperty("chosen", null); + + } + } + + if (this.$focussable) + apf.window.hasFocus(this) ? this.$focus() : this.$blur(); + }; + + this.$loadSubData = function(){}; //We use the same process for subloading, it shouldn't be done twice + + /** + * @example + */ + this.$loadPartialData = function(marker, start, length){ + //We should have a queing system here, disabled the check for now + //if (this.$hasLoadStatus(xmlRootNode)) return; + + var loadNode, rule = this.$getBindRule("load", xmlRootNode); + if (rule && (!rule[1] || rule[1](xmlRootNode))) { + this.$setLoadStatus(xmlRootNode, "loading"); + + var mdl = this.getModel(true); + + if (!mdl) + throw new Error("Could not find model"); + + if (!rule.getAttribute("total")) { + throw new Error(apf.formatErrorString(this, "Loading data", "Error in load rule. Missing total xpath. Expecting ")) + } + + + mdl.$insertFrom(rule.getAttribute("get"), { + + ascending : this.$sort ? this.$sort.get().ascending : true, + + xmlNode : loadNode, + documentId : this.documentId, //or should xmldb find this itself + marker : marker, + start : start, + length : length, + insertPoint : this.xmlRoot, + amlNode : this, + callback : function(xmlNode){ + + _self.setProperty("root", _self.xmlRoot); + + + var length = parseInt(apf.queryValue(xmlNode, + rule.getAttribute("total"))); + + if (_self.viewport.length != length) { + _self.viewport.length = length; + + this.$createVirtualDataset(_self.xmlRoot, + _self.viewport.length, _self.documentId); + } + } + }); + } + }; + + //Consider moving these functions to the xmldatabase selectByXpath(xpath, from, length); + function fillList(len, list, from){ + for (var i = 0; i < len; i++) + list.push(_self.documentId + "|" + (from+i)); + } + + function buildList(markers, markerId, distance, xml) { + var marker, nodes, start, + vlen = this.viewport.limit, + list = []; + + //Count from 0 + if (markerId == -1) { + nodes = xml.selectNodes(_self.each); + start = 0; + marker = markers[0]; + } + else { + //Count back from end of marker + if (distance < 0) { + fillList(Math.abs(distance), list, + parseInt(marker.getAttribute("reserved")) + parseInt(marker.getAttribute("end")) + - parseInt(marker.getAttribute("start")) + distance); + + distance = 0; + _self.$loadPartialData(marker); + + if (list.length == vlen) + return list; + } + + nodes = markers[markerId].selectNodes("following-sibling::" + + this.each.split("|").join("following-sibling::")); + start = markers[markerId].getAttribute("end"); + marker = markers[++markerId]; + } + + do { + //Add found nodes + var loop = Math.min(marker.getAttribute("start") - start, vlen);//, nodes.length + for (var i = distance; i < loop; i++) + list.push(nodes[i]); + + if (list.length == vlen) + break; + + //Add empty nodes + var mlen = parseInt(marker.getAttribute("end")) - parseInt(marker.getAttribute("start")); + fillList(Math.min(mlen, vlen - list.length), list, parseInt(marker.getAttribute("reserved"))); + + //Add code here to trigger download of this missing info + _self.$loadPartialData(marker); + + start = parseInt(marker.getAttribute("end")); + marker = markers[++markerId]; + distance = 0; + } + while (list.length < vlen && marker); + + _self.viewport.cache = list; + return list; + } + + /** + * Retrieves a nodelist containing the {@link term.datanode data nodes} which are rendered by + * this element (see each nodes, see {@link baseclass.multiselectbinding.binding.each}). + * + * @param {XMLElement} [xmlNode] the parent element on which the each query is applied. + */ + this.getTraverseNodes = function(xmlNode){ + if (!this.xmlRoot) + return; + + if (this.viewport.cache) + return this.viewport.cache; + + var start = this.viewport.offset + 1, + end = start + this.viewport.limit; + + //caching statement here + + var markers = (xmlNode || this.xmlRoot).selectNodes("a_marker"); + + //Special case for fully loaded virtual dataset + if (!markers.length) { + var list = (xmlNode || this.xmlRoot).selectNodes("(" + + this.each + ")[position() >= " + start + + " and position() < " + (end) + "]"); + + + return this.$sort ? this.$sort.apply(list) : list; + + } + + for (var i = 0; i < markers.length; i++) { + //Looking for marker that (partially) exceeds viewport's current position + if (markers[i].getAttribute("end") < start) { + //If this is the last marker, count from here + if (i == markers.length - 1) + return buildList(markers, i, start - markers[i].getAttribute("end"), + (xmlNode || this.xmlRoot)); + + continue; + } + + //There is overlap AND begin is IN marker + if (markers[i].getAttribute("start") - end <= 0 + && start >= markers[i].getAttribute("start")) + return buildList(markers, i, start - markers[i].getAttribute("end"), + (xmlNode || this.xmlRoot)); + + //Marker is after viewport, there is no overlap + else if (markers[i-1]) //Lets check the previous marker, if there is one + return buildList(markers, i-1, start - markers[i-1].getAttribute("end"), + (xmlNode || this.xmlRoot)); + + //We have to count from the beginning + else + return buildList(markers, -1, start, (xmlNode || this.xmlRoot)); + } + }; + + var baseNTS = this.getNextTraverseSelected; + this.getNextTraverseSelected = function(xmlNode, up, count){ + if (!xmlNode) + xmlNode = this.selected; + if (!count) + count = 1; + + var node = baseNTS.call(this, xmlNode, up, count); + if (node && node != xmlNode) + return node; + + //@todo treeArch support + var nodes = this.getTraverseNodes(), i = 0; + while (nodes[i] && nodes[i] != xmlNode) + i++; + + if (up) + i = -1 * (nodes.length - i - 1); + + this.viewport.change(Math.max(0, this.viewport.offset + i + + (up ? count : -1 * count)), null, true, true); + + nodes = this.getTraverseNodes(); + return nodes[up ? nodes.length - 1 : 0]; + }; + + //@todo keyboard handlers for pgup/pgdown should measure items instead of assuming fixed height + + //Init + this.caching = false; //for now, because the implications are unknown +}; + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/core/baseclasses/xforms.js)SIZE(9367)TIME(Mon, 10 Jan 2011 10:53:11 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + +apf.__XFORMS__ = 1 << 17; + + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/core/baseclasses/contenteditable/clipboard.js)SIZE(2951)TIME(Fri, 11 Feb 2011 16:49:57 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + + +apf.clipboard = new apf.Class().$init(); +apf.clipboard.store = null; +apf.clipboard.empty = true; +apf.clipboard.copied = null; +apf.clipboard.put = function(item){ + this.store = item; + this.setProperty("empty", item ? false : true); +}; +apf.clipboard.clear = function(){ + this.setProperty("empty", true); +} +apf.clipboard.get = function(){ + return this.store; +}; + + +apf.clipboard.$highlightSelection = function(amlNode, nodes, unset){ + for (var i = 0, l = nodes.length; i < l; i++) { + apf.setStyleClass(apf.xmldb.getHtmlNode(nodes[i], amlNode), (unset ? '' : 'cut'), ['cut']); + } +} +apf.clipboard.copySelection = function(amlNode){ + var nodes = this.get() || []; + this.$highlightSelection(amlNode, nodes, true); + this.put(amlNode.getSelection().map(function (node) { + return apf.xmldb.getCleanCopy(node); + })); + this.copied = true; +}; +apf.clipboard.cutSelection = function(amlNode){ + var nodes = this.get() || []; + this.$highlightSelection(amlNode, nodes, true); + this.put(nodes = amlNode.getSelection()); + this.$highlightSelection(amlNode, nodes); + this.copied = false; +}; +apf.clipboard.pasteSelection = function(amlNode, selected){ + var nodes = this.get(); + if (!nodes) return; + + if (!selected) + selected = amlNode.selected || amlNode.getFirstTraverseNode(); + + if (amlNode.hasFeature(apf.__DRAGDROP__)) + amlNode.copy(nodes, selected, undefined, !this.copied); + else { + if (nodes[0].parentNode) { + for (var i = 0, l = nodes.length; i < l; i++) { + apf.xmldb.moveNode(selected, nodes[i]); + } + } + else { + for (var i = 0, l = nodes.length; i < l; i++) { + apf.xmldb.appendChild(selected, nodes[i]); + } + } + } + + this.$highlightSelection(amlNode, nodes, true); + amlNode.selectList(nodes); +}; + + + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/core/baseclasses/contenteditable/commands.js)SIZE(30488)TIME(Mon, 10 Jan 2011 10:53:11 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + +apf.ContentEditable.commands = (function(){ + var STATE = 1; + var VALUE = 2; + var ENABL = 3; + var INDET = 4; + + var commands = {}; + + var addType; + commands["add"] = function(sel, showUI, options, query){ + switch(query){ + case STATE: return false; + case VALUE: return "false"; + case ENABL: return true; + case INDET: return false; + } + + //@todo node creation should become a lot simpler, api wise... + if (typeof addType == "object") { + var amlNode = apf.getXml(""); + //var amlNode = addType.ownerDocument.createElement("a:application"); + amlNode.appendChild(addType.cloneNode(true)); + } + else//apf.document.createElementNS(apf.ns.apf, addType), + var amlNode = apf.getXml(""); + + if (options.skinset && options.skinset != "default") + amlNode.firstChild.setAttribute("skinset", options.skinset); + + var htmlNode = options.htmlNode, + parentNode = options.parentNode; + if (!parentNode) { + parentNode = (apf.document.activeElement || apf.document.documentElement); + if (parentNode.getPage) { + parentNode = parentNode.getPage(); + } + else { + while (parentNode && !parentNode.canHaveChildren) + parentNode = parentNode.parentNode; + } + options.parentNode = parentNode; + } + + //if (!parentNode) debugger; + + var domParser = apf.document.$domParser; + amlNode = domParser.parseFromXml(amlNode, {doc: this}).firstChild.firstChild; + + if (!options.ignorePos) { + var pos = apf.getAbsolutePosition(parentNode.$int, null, true); + amlNode.setAttribute("left", htmlNode.offsetLeft - pos[0]); + amlNode.setAttribute("top", htmlNode.offsetTop - pos[1]); + } + + //@todo deprecate this + if (amlNode.$getOption) { + var minwidth = amlNode.minwidth + || parseInt(amlNode.$getOption("main", "minwidth")) || 5, + minheight = amlNode.minheight + || parseInt(amlNode.$getOption("main", "minheight")) || 5, + maxwidth = amlNode.maxwidth + || parseInt(amlNode.$getOption("main", "maxwidth")) || 10000, + maxheight = amlNode.maxheight + || parseInt(amlNode.$getOption("main", "maxheight")) || 10000; + } + + if (!options.ignorePos) { + amlNode.setAttribute("width", Math.min(maxwidth, Math.max(minwidth, + htmlNode.offsetWidth))); + amlNode.setAttribute("height", Math.min(maxheight, + Math.max(minheight, htmlNode.offsetHeight))); + } + else { + if (options.left || options.left === 0) { + amlNode.setAttribute("left", options.left); + amlNode.setAttribute("top", options.top); + } + if (options.width || options.width === 0) + amlNode.setAttribute("width", options.width); + if (options.height || options.height === 0) + amlNode.setAttribute("height", options.height); + } + + var name, n = amlNode.localName.uCaseFirst(), i = 0; + do{ + name = n + ++i; + } while (self[name]); + amlNode.setAttribute("id", name); + + var hasProp = rename.getEditableProp(amlNode); + if (hasProp) { + var prop = hasProp[1]; + amlNode.setAttribute(prop, name); + } + + if (options.userInteraction) + amlNode.$adding = true; + + parentNode.insertBefore(amlNode, options.beforeNode); + amlNode.setProperty("editable", true); + + + apf.layout.processQueue(); + + + if (!options.userInteraction) + apf.document.getSelection().$selectList([amlNode]); + + options.addedNode = amlNode; + + //@todo hack! + //trTools.select(trTools.queryNode("//node()[@name='Arrow']")); + }; + + commands["contextmenu"] = function(sel, showUI, options, query){ + switch(query){ + case STATE: return false; + case VALUE: return "false"; + case ENABL: return true; + case INDET: return false; + } + + if (options.amlNode.ownerDocument.dispatchEvent("contextmenu", { + x : options.htmlEvent.x, + y : options.htmlEvent.y, + currentTarget : options.amlNode, + bubbles : true + }) !== false && showUI) { + //@todo create UI? + mnuContentEditable.display(options.htmlEvent.x, + options.htmlEvent.y, null, options.amlNode); + } + }; + + var rename = { + getInput : function(){ + apf.Rename.initEditableArea.call(this); + + var txt = this.$txt; + txt.className = "editable" + txt.style.height = "auto"; + txt.onscroll = function(){ + return false; + } + txt.host = this; + + return txt; + }, + + stopRename : function(n, success){ + if (success && this.renaming) { + var doc = this.$renameElement.ownerDocument; + commands.begin.call(doc); + this.stop(null, true); + commands.commit.call(doc); + } + else this.stop(); + }, + + getEditableProp : function (el){ + var htmlNode, prop; + if (el.$childProperty) { + prop = el.$childProperty; + } + else if (el.$editableCaption) { + prop = el.$editableCaption[0]; + } + else if (el.$getEditableCaption) { + var info = el.$getEditableCaption(); + if (!info) + return; + prop = info[1]; + htmlNode = info[0]; + } + else { + return; + } + + if (!htmlNode) + htmlNode = el.$getLayoutNode("main", prop, el.$ext); + + return [htmlNode, prop]; + }, + + start : function(el){ + var info = this.getEditableProp(el); + if (!info) return; + + htmlNode = info[0]; + this.$editableProp = info[1]; + + if (!htmlNode) { + if (el.getPage) + this.start(el.getPage()); + return; + } + + this.$renameHtml = htmlNode; + this.$renameElement = el; + this.$renameContents = (htmlNode.nodeType == 1 + ? htmlNode.innerHTML + : htmlNode.nodeValue); + + var txt = this.getInput(); + + if (htmlNode.nodeType == 1) { + htmlNode.innerHTML = ""; + htmlNode.appendChild(txt); + } + else { + htmlNode.parentNode.replaceChild(txt, htmlNode); + } + + if (apf.hasContentEditable) { + txt.innerHTML = this.$renameContents.replace(/" || ""; + } + else + txt.value = this.$renameContents; + + txt.unselectable = "Off"; + + //this.$txt.focus(); + var f = function(){ + try { + txt.focus(); + txt.select(); + } + catch(e) {} + }; + if (apf.isIE) f() + else setTimeout(f); + + this.renaming = true; + }, + + stop : function(x, success){ + if (!this.renaming) + return; + + this.renaming = false; + + var htmlNode = this.$renameHtml; + if (htmlNode.nodeType == 1) + htmlNode.removeChild(this.$txt); + else + this.$txt.parentNode.replaceChild(htmlNode, this.$txt); + + var value = typeof success == "string" + ? success + : (apf.hasContentEditable + ? this.$txt.innerText + : this.$txt.value) + .replace(/<.*?nobr>/gi, "").replace(/\n$/, ""); //last replace is for chrome; + + var el = this.$renameElement; + var prop = this.$editableProp; + + if (success && el[prop] != value) { + el.setAttribute(prop, value); + } + else { + if (htmlNode.nodeType == 1) + htmlNode.innerHTML = this.$renameContents; + else + htmlNode.nodeValue = this.$renameContents; + } + + htmlNode.parentNode.scrollLeft = 0; + + //this.regrab(); + + this.$editableProp = + this.$renameHtml = + this.$renameElement = + this.$renameContents = null; + } + }; + commands["rename"] = function(sel, showUI, value, query){ + switch(query){ + case STATE: return rename.renaming; + case VALUE: return String(rename.renaming); + case ENABL: return rename.getEditableProp(value || sel[0]); + case INDET: return false; + } + + //Start inline renaming of the caption/title/label etc + if (showUI) { + if (sel.length) + rename.start(sel[0]); + } + //Set caption/title/label etc with the value specified + else { + rename.stop(null, value); + } + }; + + var mode, vcinited; + commands["mode"] = function(sel, showUI, value, query){ + switch(query){ + case STATE: return mode || "default"; + case VALUE: return mode || "default"; + case ENABL: return true; + case INDET: return false; + } + + //@todo solve this in the UI + if (value == "select") { + mode = value; + this.$getSelectRect().activate(); + } + else if (value && value.mode == "connect") { + mode = "connect"; + var vc = this.$getVisualConnect(); + vc.setMode("draw"); + vc.activate(value.event,value.timeout); + + if (!vcinited) { + this.$getVisualConnect().onchangemode = function(e){ + if (e.mode != mode) { + if (e.mode == "element") mode = "connect-element" + else if (e.mode == "all") mode = "connect-all"; + else if (e.mode == "draw") mode = "connect"; + else if (e.mode == "draw-started") mode = "connecting"; + } + } + } + } + else if (value && value.mode == "connect-element") { + mode = value.mode; + var vc = this.$getVisualConnect(); + vc.setMode("element"); + vc.activate(value.event,value.timeout); + } + else if (value && value.mode == "connect-all") { + mode = value.mode; + var vc = this.$getVisualConnect(); + vc.setMode("all"); + vc.activate(value.event,value.timeout); + } + else if (value && value.mode == "add") { + mode = "add"; + var e = value.ev; + var pos = apf.getAbsolutePosition(e.indicator); + (value.dragserver || apf.DragServer).stop(null, true); + + addType = value.value; + var opt = { + //htmlNode : q, + userInteraction : true, + ignorePos : true, + parentNode : value.parent || apf.document.body, + skinset : apf.getInheritedAttribute(value.parent || apf.document.body, "skinset") + }; + + commands.begin.call(this); + commands["add"].call(this, null, null, opt); + var amlNode = opt.addedNode; + amlNode.$adding = true; + + if (value.parent) { + var ppos = apf.getAbsolutePosition(value.parent.$int); + pos[0] -= ppos[0]; + pos[1] -= ppos[1]; + } + + amlNode.$ext.style.left = (amlNode.left = pos[0]) + "px"; + amlNode.$ext.style.top = (amlNode.top = pos[1]) + "px"; + amlNode.$ext.style.position = "absolute"; + + amlNode.dragOutline = false; + amlNode.$dragStart({ + reappend : true, + clientX : e.htmlEvent.clientX, + clientY : e.htmlEvent.clientY + }, true, true); + //amlNode.dragOutline = true; + + addType = null; + } + else { + mode = value.mode || value; + this.$getSelectRect().deactivate(); + this.$getVisualConnect().deactivate(); + } + }; + + commands["select"] = function(sel, showUI, options, query){ + switch(query){ + case STATE: return false; + case VALUE: return "false"; + case ENABL: return true; + case INDET: return false; + } + + var htmlNode = options.htmlNode, + parentNode = options.parentNode; + if (!parentNode) { + parentNode = (apf.document.$getVisualSelect().getLastSelection()[0] || apf.document.documentElement); + if (parentNode.getPage) { + parentNode = parentNode.getPage(); + } + else { + while (parentNode && !parentNode.canHaveChildren) + parentNode = parentNode.parentNode; + } + options.parentNode = parentNode; + } + + var nodes = apf.document.getElementsByTagName("*"); + var htmlParent = htmlNode.parentNode; + var left = apf.getHtmlLeft(htmlNode); + var top = apf.getHtmlTop(htmlNode); + var right = left + htmlNode.offsetWidth; + var bottom = top + htmlNode.offsetHeight; + var first = true, ext, selList = []; + var refParent; + for (var i = 0; i < nodes.length; i++) { + if (!(ext = nodes[i].$ext)) + continue; + + var pos = apf.getAbsolutePosition(ext, htmlParent); + if (pos[0] > left && pos[0] + ext.offsetWidth < right + && pos[1] > top && pos[1] + ext.offsetHeight < bottom) { + if (!refParent) refParent = nodes[i].parentNode; + else if (refParent != nodes[i].parentNode) continue; + selList.push(nodes[i]); + first = false; + } + } + + if (selList.length) + apf.document.getSelection().$selectList(selList); + + //trTools.select(trTools.queryNode("//node()[@name='Arrow']")); + }; + + commands["remove"] = function(sel, showUI, value, query){ + switch(query){ + case STATE: return false; + case VALUE: return "false"; + case ENABL: return true; + case INDET: return false; + } + + var pNode = sel[0].parentNode; + sel.each(function(sel) { + sel.removeNode(); + }); + + var s = pNode.ownerDocument.getSelection(); + s.$selectList([apf.document.activeElement && apf.document.activeElement.editable + ? apf.document.activeElement + : (pNode.editable ? pNode : pNode.firstChild)]); + }; + + commands["each"] = function(sel, showUI, func, query){ + switch(query){ + case STATE: return false; + case VALUE: return "false"; + case ENABL: return true; + case INDET: return false; + } + + sel.each(func); + }; + + commands["undo"] = function(sel, showUI, value, query){ + var um = apf.window.undoManager; + switch(query){ + case STATE: return um.undolength; + case VALUE: return String(um.undolength); + case ENABL: return um.undolength > 0; + case INDET: return false; + } + + um.undo(parseInt(value) || null); + }; + + commands["redo"] = function(sel, showUI, value, query){ + var um = apf.window.undoManager; + switch(query){ + case STATE: return um.redolength; + case VALUE: return String(um.redolength); + case ENABL: return um.redolength > 0; + case INDET: return false; + } + + um.redo(parseInt(value) || null); + }; + + commands["cut"] = function(sel, showUI, value, query){ + switch(query){ + case STATE: return apf.clipboard.store ? true : false; + case VALUE: return apf.clipboard.store; + case ENABL: return true; + case INDET: return false; + } + + commands["copy"].call(this, sel, showUI); + commands["remove"].call(this, sel, showUI); + }; + + commands["copy"] = function(sel, showUI, value, query){ + switch(query){ + case STATE: return apf.clipboard.store ? true : false; + case VALUE: return apf.clipboard.store; + case ENABL: return true; + case INDET: return false; + } + + var nodes = []; + sel.each(function(item){ + nodes.push(item.cloneNode(true)); + }); + + apf.clipboard.put(nodes); + }; + + commands["paste"] = function(sel, showUI, value, query){ + switch(query){ + case STATE: return apf.clipboard.store ? true : false; + case VALUE: return apf.clipboard.store; + case ENABL: return true; + case INDET: return false; + } + + var content = apf.clipboard.get(); + if (!content) return; + + var pNode = sel.length > 1 ? sel[0].parentNode : sel[0]; + + if (typeof content == "string") { + sel.insertMarkup(content); + } + else if (content.dataType == apf.ARRAY) { + if (pNode.localName == "table") //@todo not generic enough + command["resetgeo"].call(this, sel, showUI); + + //Init selection + var docsel = this.getSelection(); + docsel.removeAllRanges(); + + //Copy nodes and add to selection + var _self = this; + content.each(function(item){ + docsel.addRange(new apf.AmlRange(_self)).selectNode( + pNode.appendChild(item.cloneNode(true))); + }); + } + else if (content.$regbase) { + sel.appendChild(content.cloneNode(true)) + } + else { + alert("Could not paste content"); + } + + + //@todo more general place for this? + apf.layout.processQueue(); + + }; + + commands["duplicate"] = function(sel, showUI, value, query){ + switch(query){ + case STATE: return false; + case VALUE: return "false"; + case ENABL: return true; + case INDET: return false; + } + + //Init selection + var docsel = this.getSelection(); + docsel.removeAllRanges(); + + //Copy nodes and add to selection + sel.each(function(item){ + docsel.addRange(new apf.AmlRange(item)).selectNode( + item.parentNode.appendChild(apf.getCleanCopy(item))); + }); + + /*apf.ContentEditable.execCommand("resetgeo", { + sel: nodes + });*/ + + + //@todo more general place for this? + apf.layout.processQueue(); + + }; + + commands["resetgeo"] = function(sel, showUI, options, query){ + switch(query){ + case STATE: return false; + case VALUE: return "false"; + case ENABL: return true; + case INDET: return false; + } + + if (!sel.length) + return; + + var props = ["left", "top", "right", "bottom", "anchors"]; + + if ("vbox|hbox|table".indexOf(sel[0].parentNode.localName) == -1) + props.push("align"); + if (!options || !options.keepwidth) + props.push("width"); + if (!options || !options.keepheight) + props.push("height"); + + sel.each(function(sel){ + props.each(function(item){ + if (sel[item]) + sel.setAttribute(item, ""); + }); + + //@todo this should be done by align/anchoring + sel.$ext.style.position = ""; + }); + + sel[0].ownerDocument.$getVisualSelect().updateGeo(); + }; + + commands["property"] = function(sel, showUI, options, query){ + switch(query){ + case STATE: return false; + case VALUE: return "false"; + case ENABL: return true; + case INDET: return false; + } + + sel.each(function(sel) { + sel.setAttribute(options.name, options.value); + }); + + sel[0].ownerDocument.$getVisualSelect().updateGeo(); //Possibly not best place for this + }; + + //@todo should keep an ordered list of zIndexes and reset all... + commands["back"] = function(sel, showUI, options, query){ + switch(query){ + case STATE: return false; + case VALUE: return "false"; + case ENABL: return true; + case INDET: return false; + } + + sel.each(function(sel) { + if (sel.zindex != 0) + sel.setAttribute("zindex", 0); + }); + }; + + //@todo should keep an ordered list of zIndexes and reset all... + commands["backward"] = function(sel, showUI, options, query){ + switch(query){ + case STATE: return false; + case VALUE: return "false"; + case ENABL: return true; + case INDET: return false; + } + + sel.each(function(sel) { + if (sel.zindex != 0) + sel.setAttribute("zindex", (sel.zindex || 1) - 1); + }); + }; + + //@todo should keep an ordered list of zIndexes and reset all... + commands["front"] = function(sel, showUI, options, query){ + switch(query){ + case STATE: return false; + case VALUE: return "false"; + case ENABL: return true; + case INDET: return false; + } + + sel.each(function(sel) { + if (sel.zindex != 100000) + sel.setAttribute("zindex", 100000); + }); + }; + + //@todo should keep an ordered list of zIndexes and reset all... + commands["forward"] = function(sel, showUI, options, query){ + switch(query){ + case STATE: return false; + case VALUE: return "false"; + case ENABL: return true; + case INDET: return false; + } + + sel.each(function(sel) { + if (sel.zindex != 100000) + sel.setAttribute("zindex", (sel.zindex || 0) + 1); + }); + }; + + //@todo not implemented + commands["align"] = function(sel, showUI, options, query){ + switch(query){ + case STATE: return false; + case VALUE: return "false"; + case ENABL: return true; + case INDET: return false; + } + + apf.ContentEditable.execCommand("property", { + name: "align", value: options.to + }); + if ("left|middle|right".indexOf(options.to) > -1) + apf.ContentEditable.execCommand("property", { + name: "height", value: "" + }); + if ("top|middle|bottom".indexOf(options.to) > -1) + apf.ContentEditable.execCommand("property", { + name: "width", value: "" + }); + + sel[0].ownerDocument.$getVisualSelect().updateGeo(); + }; + + var sortFn = { + "hbox": function(a, b){ + return (a.$ext.offsetLeft - b.$ext.offsetLeft); + }, + "vbox": function(a, b){ + return (a.$ext.offsetTop - b.$ext.offsetTop); + }, + "table": function(a, b){ + var dH = a.$ext.offsetTop - b.$ext.offsetTop; + if (Math.abs(dH)/b.$ext.offsetHeight < 0.9) + return (a.$ext.offsetLeft - b.$ext.offsetLeft); + return dH; + } + } + commands["surround"] = function(sel, showUI, options, query){ + switch(query){ + case STATE: return false; + case VALUE: return "false"; + case ENABL: return true; + case INDET: return false; + } + + var pNode = sel[0].parentNode; + var pos = [100000,100000,0,0]; + for (var i = 0; i < sel.length; i++) { + var oEl = sel[i].$ext; + var opos = apf.getAbsolutePosition(oEl, pNode.$int); + if (opos[0] < pos[0]) pos[0] = opos[0]; + if (opos[1] < pos[1]) pos[1] = opos[1]; + if (opos[0] + oEl.offsetWidth > pos[2]) pos[2] = opos[0] + oEl.offsetWidth; + if (opos[1] + oEl.offsetHeight > pos[3]) pos[3] = opos[1] + oEl.offsetHeight; + } + pos[2] -= pos[0]; + pos[3] -= pos[1]; + + addType = options.to; + + //Spatial sort selection + sel.sort(sortFn[addType]); + + //Reset position + var isInLayout = "vbox|hbox|table".indexOf(pNode.localName) > -1; + commands["resetgeo"].call(this, sel, false, { + keepwidth : !isInLayout && options.to == "hbox", + keepheight : !isInLayout && options.to == "vbox" + }); + + //Add container + var opt = { + parentNode : pNode, + beforeNode : sel[0], + ignorePos : true, + left : pos[0], + top : pos[1] + }; + if (pNode.localName != "table") { + if (pNode.localName != "vbox") + opt.width = pos[2]; + if (pNode.localName != "hbox") + opt.height = pos[3]; + } + + commands["add"].call(this, null, false, opt); + + if (isInLayout && options.to != "table") { + sel.each(function(sel) { + if (isInLayout) { + if (options.to == "hbox" && pNode.localName == "vbox") + sel.setAttribute("width", sel.height); + if (options.to == "vbox" && pNode.localName == "hbox") + sel.setAttribute("height", sel.width); + } + }); + } + + //Add selection + sel.each(function(sel) { + opt.addedNode.appendChild(sel); + }); + + this.getSelection().$selectList([opt.addedNode]); + + + //@todo more general + apf.layout.processQueue(); + + + }; + + //@todo not implemented + commands["convert"] = function(sel, showUI, options, query){ + switch(query){ + case STATE: return false; + case VALUE: return "false"; + case ENABL: return true; + case INDET: return false; + } + }; + + //@todo not implemented + commands["lock"] = function(sel, showUI, options, query){ + switch(query){ + case STATE: return false; + case VALUE: return "false"; + case ENABL: return true; + case INDET: return false; + } + }; + + //@todo not implemented + commands["unlock"] = function(sel, showUI, options, query){ + switch(query){ + case STATE: return false; + case VALUE: return "false"; + case ENABL: return true; + case INDET: return false; + } + }; + + commands.begin = function(sel, showUI, bClear){ + apf.window.undoManager.begin(this.documentElement, bClear); + }; + + commands.rollback = function(){ + apf.window.undoManager.rollback(this.documentElement); + }; + + commands.commit = function(){ + apf.window.undoManager.commit(this.documentElement); + }; + + commands.pause = function(sel, showUI, bContinue){ + apf.window.undoManager.pause(this.documentElement, bContinue); + }; + + apf.AmlDocument.prototype.$commands = apf.extend( + apf.AmlDocument.prototype.$commands, + commands + ); +})(); + + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/core/baseclasses/contenteditable/interactive.js)SIZE(57362)TIME(Mon, 10 Jan 2011 10:53:11 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + + +/* + Adds complex resize/drag behaviour for ContentEditable +*/ +(function(){ + function isCoord(n){ + return n || n === 0; + } + + function coord(n, other){ + return n || n === 0 ? n : other; + } + + var dragIndicator1, dragIndicator2, dragIndicator3, dragIndicator4, + dragIndicator5, inited = false, indicators = [], outline, dragOutline; + function init(){ + var div, classes = [null, + "dragindicate", "dragindicate indicate_common indicate_horizontal", + "dragindicate indicate_common", "dragindicate indicate_common indicate_horizontal", + "dragindicate dragreparent", "dragindicate dragelsort", "dragindicate dragelsort" + ]; + for (var i = 1; i < 8; i++) { + div = document.body.appendChild(document.createElement("div")); + div.style.position = "absolute"; + div.className = classes[i]; + div.style.zIndex = 1000000; + //@todo use apf.window.zManager.set("drag", div); + div.style.display = "none"; + div.host = false; + eval("dragIndicator" + i + " = indicators[" + (i-1) + "] = div;"); //ahum... + } + outline = dragOutline = document.getElementById("apf_outline"); + + inited = true; + } + + var lastReparent, movePosition; + function doReparentDrag(el, amlNode, e){ + if (lastReparent == amlNode) + return; + + var htmlNode = el.dragOutline ? outline : el.$ext; + var pHtmlNode = amlNode.$int; + var isBody = pHtmlNode.tagName == "BODY"; + + var pos1 = isBody ? [0,0] : apf.getAbsolutePosition(pHtmlNode); + pos1[2] = isBody ? apf.getWindowWidth() : pHtmlNode.offsetWidth; + pos1[3] = isBody ? apf.getWindowHeight() : pHtmlNode.offsetHeight; + var lastPHtmlNode = lastReparent ? lastReparent.$int : el.$ext.parentNode; + var pos2 = lastPHtmlNode.tagName == "BODY" + ? [0,0] : apf.getAbsolutePosition(lastPHtmlNode, null, true); + + amlNode.$int.appendChild(htmlNode); + + hideIndicators(); + + dragIndicator5.style.left = pos1[0] + "px"; + dragIndicator5.style.top = pos1[1] + "px"; + var diff = apf.getDiff(dragIndicator5); + dragIndicator5.style.width = (pos1[2] - diff[0]) + "px"; + dragIndicator5.style.height = (pos1[3] - diff[1]) + "px"; + dragIndicator5.style.display = "block"; + + apf.tween.single(dragIndicator5, { + type : "fade", + from : 0, + to : 1, + anim : apf.tween.easeInSine, + steps : 5 + }); + + htmlNode.style.left = (apf.getHtmlLeft(htmlNode) - (pos1[0] - pos2[0])) + "px"; + htmlNode.style.top = (apf.getHtmlTop(htmlNode) - (pos1[1] - pos2[1])) + "px"; + + lastReparent = amlNode; + //el.$ext.onmousedown(e, true); + el.$dragStart(e, true); + } + + //@todo these functions should be improved to work on a map of the objects, so that it doesnt depend on hovering + var lastAmlNode; + var showDrag = { + vbox : function(l, t, htmlNode, e){ + if (e) { + var prevTop = htmlNode.style.top; + var el = this, plane = apf.plane.get(); + plane.plane.style.top = + htmlNode.style.top = "-2000px"; + var amlNode = apf.findHost(document.elementFromPoint(e.clientX, e.clientY)); + plane.plane.style.top = 0; + htmlNode.style.top = prevTop; + + if (amlNode && amlNode != el && (amlNode.parentNode == (lastReparent || el.parentNode))) { + var ext1 = amlNode.$altExt || amlNode.$ext; + var ext2 = el.$altExt || el.$ext; + var h = ext1.offsetHeight; + var pos = apf.getAbsolutePosition(ext1); + if (pos[1] + (h/2) > e.clientY) { + if (ext1.previousSibling != ext2) { + //ext1.parentNode.insertBefore(ext2, ext1); + //_self.regrab(); + dragIndicator1.style.display = "block"; + dragIndicator1.style.top = (pos[1] - dragIndicator1.offsetHeight) + "px"; + dragIndicator1.style.left = (pos[0]) + "px"; + dragIndicator1.style.width = (ext1.offsetWidth - apf.getWidthDiff(dragIndicator1)) + "px"; + dragIndicator1.style.height = amlNode.parentNode.padding + "px"; + movePosition = {where: "before", amlNode: amlNode} + } + else { + dragIndicator1.style.display = "none"; + movePosition = null; + } + } + else { + if (ext1.nextSibling != ext2) { + //ext1.parentNode.insertBefore(ext2, ext1.nextSibling); + //_self.regrab(); + dragIndicator1.style.display = "block"; + dragIndicator1.style.top = (pos[1] + h) + "px"; + dragIndicator1.style.left = (pos[0]) + "px"; + dragIndicator1.style.width = (ext1.offsetWidth - apf.getWidthDiff(dragIndicator1)) + "px"; + dragIndicator1.style.height = amlNode.parentNode.padding + "px"; + movePosition = {where: "after", amlNode: amlNode} + } + else { + dragIndicator1.style.display = "none"; + movePosition = null; + } + } + } + else + dragIndicator1.style.display = "none"; + } + }, + + hbox : function(l, t, htmlNode, e){ + if (e) { + var prevTop = htmlNode.style.top; + var el = this, plane = apf.plane.get(); + plane.plane.style.top = + htmlNode.style.top = "-2000px"; + var amlNode = apf.findHost(document.elementFromPoint(e.clientX, e.clientY)); + plane.plane.style.top = 0; + htmlNode.style.top = prevTop; + + showDrag.reparent.call(this, amlNode, el, e); + + if (amlNode && amlNode != el && (amlNode.parentNode == (lastReparent || el.parentNode))) { + var ext1 = amlNode.$altExt || amlNode.$ext; + var ext2 = el.$altExt || el.$ext; + var h = ext1.offsetWidth; + var pos = apf.getAbsolutePosition(ext1); + if (pos[0] + (h/2) > e.clientX) { + if (ext1.previousSibling != ext2) { + dragIndicator1.style.display = "block"; + dragIndicator1.style.top = (pos[1]) + "px"; + dragIndicator1.style.left = (pos[0] - dragIndicator1.offsetWidth) + "px"; + dragIndicator1.style.width = amlNode.parentNode.padding + "px"; + dragIndicator1.style.height = (ext1.offsetHeight - apf.getHeightDiff(dragIndicator1)) + "px"; + movePosition = {where: "before", amlNode: amlNode} + } + else { + dragIndicator1.style.display = "none"; + movePosition = null; + } + } + else { + if (ext1.nextSibling != ext2) { + dragIndicator1.style.display = "block"; + dragIndicator1.style.top = (pos[1]) + "px"; + dragIndicator1.style.left = (pos[0] + h) + "px"; + dragIndicator1.style.width = amlNode.parentNode.padding + "px"; + dragIndicator1.style.height = (ext1.offsetHeight - apf.getHeightDiff(dragIndicator1)) + "px"; + movePosition = {where: "after", amlNode: amlNode} + } + else { + dragIndicator1.style.display = "none"; + movePosition = null; + } + } + } + else { + dragIndicator1.style.display = "none"; + movePosition = null; + } + } + }, + + table : function(l, t, htmlNode, e){ + if (e) { + var prevTop = htmlNode.style.top, plane = apf.plane.get() + var el = this; + dragIndicator1.style.top = + plane.plane.style.top = + htmlNode.style.top = "-2000px"; + var amlNode = apf.findHost(document.elementFromPoint(e.clientX, e.clientY)); + plane.plane.style.top = 0; + htmlNode.style.top = prevTop; + + showDrag.reparent.call(this, amlNode, el, e); + + if (amlNode && amlNode != el && (amlNode.parentNode == (lastReparent || el.parentNode))) { + var ext1 = amlNode.$altExt || amlNode.$ext; + var ext2 = el.$altExt || el.$ext; + var pos = apf.getAbsolutePosition(ext1); + if (ext1.previousSibling != ext2) { + dragIndicator1.style.display = "block"; + dragIndicator1.style.top = pos[1] + "px"; + dragIndicator1.style.left = pos[0] + "px"; + dragIndicator1.style.width = (amlNode.$ext.offsetWidth - apf.getWidthDiff(dragIndicator1)) + "px"; + dragIndicator1.style.height = (amlNode.$ext.offsetHeight - apf.getHeightDiff(dragIndicator1)) + "px"; + movePosition = {where: "before", amlNode: amlNode} + } + else { + dragIndicator1.style.display = "none"; + movePosition = null; + } + } + else + dragIndicator1.style.display = "none"; + } + }, + + reparent : function(amlNode, el, e){ + var htmlNode = el.dragOutline ? outline : el.$ext; + + while (amlNode && !amlNode.$int) + amlNode = amlNode.parentNode; + + if (!amlNode.editable) + return; + + if (lastAmlNode && lastAmlNode[0] == amlNode + && Math.abs(e.clientX - lastAmlNode[2]) < 2 + && Math.abs(e.clientY - lastAmlNode[3]) < 2) { + //do Nothing + + } + else { + if (lastAmlNode) + clearTimeout(lastAmlNode[4]); + + var plane = apf.plane.get() + if (el && amlNode != el && amlNode.$int + && htmlNode.parentNode != amlNode.$int + && (htmlNode.parentNode != plane.plane || amlNode.$int != document.body) + && !apf.isChildOf(el.$ext, amlNode.$int, true)) { + var ev = {clientX: e.clientX, clientY: e.clientY, ctrlKey: e.ctrlKey} + lastAmlNode = [amlNode, new Date().getTime(), e.clientX, e.clientY, + setTimeout(function(){ + doReparentDrag(el, amlNode, ev); + }, 300)]; + + //if (el.$adding) + //doReparentDrag(el, amlNode, e); + } + else + lastAmlNode = []; + } + }, + + common : function(l, t, htmlNode, e, change, snapDiff){ + var oOutline = this.$ext; + var prevTop = htmlNode.style.top; + + var el = this, plane = apf.plane.get(); + dragIndicator1.style.top = + plane.plane.style.top = + htmlNode.style.top = "-2000px"; + var amlNode = apf.findHost(document.elementFromPoint(e.clientX, e.clientY)); + plane.plane.style.top = 0; + htmlNode.style.top = prevTop; + + showDrag.reparent.call(this, amlNode, el, e); + + var d = dragInfo; + showDrag.common_resize.call(this, l, t, d.width, + d.height, e, change, true, true, true, true, true); + + apf.config.setProperty("x", change.l || (change.l === 0 ? 0 : apf.getHtmlLeft(oOutline))); + apf.config.setProperty("y", change.t || (change.t === 0 ? 0 : apf.getHtmlTop(oOutline))); + apf.config.setProperty("w", d.width); + apf.config.setProperty("h", d.height); + }, + + //@todo temporarily disabled middle guides for resize + common_resize : function(l, t, w, h, e, change, we, no, ea, so, isDrag, sd){ + var snapDiff = sd || 8; + var lpos = false, tpos = false, loffset = 0, toffset = 0; + var wpos = false, hpos = false; + var force, d = dragInfo; + + if (e.ctrlKey) { + hideIndicators(null, true); + return; + } + + if (isDrag && e.shiftKey) { + var dt = d.top - t; + var dl = d.left - l; + var corner = (Math.atan(Math.abs(dt) / Math.abs(dl)) * 2) / Math.PI; //normalize corner + if (corner < 1/3) { //horizontal + change.st = t = d.top; + } + else if (corner < 2/3) { //diagonal + if (l > d.left && t < d.top || l < d.left && t > d.top) { + var min = Math.min(Math.abs(dl), Math.abs(dt)); + change.l = d.left + (dl < 0 ? 1 : -1) * min; + change.t = d.top + (dt < 0 ? 1 : -1) * min; + } + else { + var min = Math.min(Math.abs(dl), Math.abs(dt)); + change.l = d.left + (dl < 0 ? 1 : -1) * min; + change.t = d.top + (dt < 0 ? 1 : -1) * min; + } + dragIndicator1.style.display = "none"; + dragIndicator2.style.display = "none"; + return; + } + else { //vertical + change.sl = l = d.left; + } + force = true; + } + + //Container + if (apf.config.snapcontainer) { + var c = d.container; + + //Left + if (Math.abs(l) < snapDiff) { + change.l = 0; + change.lsticky = 5; + } + if (Math.abs(c[2] - l - w) < snapDiff) { + if (change.l == undefined) + change.l = c[2] - w; + change.rsticky = 5; + } + + //Top + if (Math.abs(t) < snapDiff) { + change.t = 0; + change.tsticky = 5; + } + if (Math.abs(c[3] - t - h) < snapDiff) { + if (change.t == undefined) + change.t = c[3] - h; + change.bsticky = 5; + } + + if (!isDrag) { + //Width + if (Math.abs(c[2] - w - l) < (ea ? snapDiff : 1)) { + change.w = c[2] - l; + change.rsticky = 5; + } + + //Height + if (Math.abs(c[3] - h - t) < (so ? snapDiff : 1)) { + change.h = c[3] - t; + change.bsticky = 5; + } + } + } + + //Elements - X + if (d.xl.length && (typeof change.l == "undefined" || force)) { + //Left + for (var i = 0, il = d.xl.length; i < il; i++) { + if (Math.abs(d.xl[i] - l) < (we ? snapDiff : 1)) { + change.l = lpos = d.xl[i]; + change.loffset = loffset = -2; + change.lsticky = i == 0; + break; + } + } + + //Right + if (lpos === false) { + for (var i = 0, il = d.xr.length; i < il; i++) { + if (Math.abs(d.xr[i] - l - w) < (ea ? snapDiff : 1)) { + change.l = (lpos = d.xr[i]) - w; + change.loffset = loffset = 0; + change.rsticky = i == 0; + break; + } + } + } + + //Middle + if (lpos === false && isDrag) { + for (var i = 0, il = d.xm.length; i < il; i++) { + if (Math.abs(d.xm[i] - l - d.cwidth) < snapDiff) { + change.l = (lpos = d.xm[i]) - d.cwidth; + break; + } + } + } + } + + if (d.xr.length && (typeof change.w == "undefined") && change.rsticky != 5) { + for (var i = 0, il = d.xr.length; i < il; i++) { + if (Math.abs(d.xr[i] - w - (!isDrag && ea || isDrag ? change.l || l : l)) < + (!ea || isDrag && typeof change.l != "undefined" ? 1 : snapDiff)) { + if (!isDrag) + change.w = (wpos = d.xr[i]) - l; + else + wpos = d.xr[i]; + change.rsticky = i == 0; + break; + } + } + } + + //Elements - Y + if (d.yl.length && (typeof change.t == "undefined" || force)) { + for (var i = 0, il = d.yl.length; i < il; i++) { + if (Math.abs(d.yl[i] - t) < (no ? snapDiff : 1)) { + change.t = tpos = d.yl[i]; + change.toffset = toffset = -2; + change.tsticky = i == 0; + break; + } + } + + if (tpos === false) { + for (var i = 0, il = d.yr.length; i < il; i++) { + if (Math.abs(d.yr[i] - t - h) < (so ? snapDiff : 1)) { + change.t = (tpos = d.yr[i]) - h; + change.toffset = toffset = 0; + change.bsticky = i == 0; + break; + } + } + } + + if (tpos === false && isDrag) { + for (var i = 0, il = d.ym.length; i < il; i++) { + if (Math.abs(d.ym[i] - t - d.cheight) < snapDiff) { + change.t = (tpos = d.ym[i]) - d.cheight; + change.toffset = toffset = -1; + break; + } + } + } + } + + if (d.yr.length && (typeof change.h == "undefined") && change.bsticky != 5) { + for (var i = 0, il = d.yr.length; i < il; i++) { + if (Math.abs(d.yr[i] - h - (!isDrag && so || isDrag ? change.t || t : t)) < + (!so || isDrag && typeof change.t != "undefined" ? 1 : snapDiff)) { + if (!isDrag) + change.h = (hpos = d.yr[i]) - t; + else + hpos = d.yr[i]; + change.bsticky = i == 0; + break; + } + } + } + + //Elements - Opposite sides - X + var oppDiff = 5, oloffset, olpos = false, tdiff; + if (d.xl.length && change.l !== 0) { + //Left + if (!change.lsticky) { + for (var i = 0, il = d.xr.length; i < il; i++) { + tdiff = (Math.max(0, (change.l || l) - d.xr[i]) || 10000) - oppDiff; + if ((sd ? Math.abs(tdiff) : tdiff) < (we ? snapDiff : 1)) { + change.ol = (olpos = d.xr[i]) + oppDiff; + change.olpos = olpos + oppDiff; + change.olEl = d.els[i]; + change.oloffset = oloffset = 0; + break; + } + } + } + + //Right + if (!change.rsticky) { + for (var i = 0, il = d.xl.length; i < il; i++) { + tdiff = (Math.max(0, d.xl[i] - (change.l || l) - w) || 10000) - oppDiff; + if ((sd ? Math.abs(tdiff) : tdiff) < (ea ? snapDiff : 1)) { + change.or = (olpos = d.xl[i] - oppDiff) - w; + change.orpos = d.container[2] - olpos; + change.orEl = d.els[i]; + change.oloffset = oloffset = 0; + break; + } + } + } + } + + //Elements - Opposite sides - Y + var otoffset, otpos = false; + if (d.yl.length && change.t !== 0) { + if (!change.tsticky) { + for (var i = 0, il = d.yr.length; i < il; i++) { + tdiff = Math.abs((Math.max(0, (change.t || t) - d.yr[i]) || 10000) - oppDiff); + if ((sd ? Math.abs(tdiff) : tdiff) < (no ? snapDiff : 1)) { + change.ot = (otpos = d.yr[i]) + oppDiff; + change.otpos = otpos + oppDiff; + change.otEl = d.els[i]; + change.otoffset = otoffset = 0; + break; + } + } + } + + if (!change.bsticky) { + for (var i = 0, il = d.yl.length; i < il; i++) { + tdiff = Math.abs((Math.max(0, d.yl[i] - (change.t || t) - h) || 10000) - oppDiff); + if ((sd ? Math.abs(tdiff) : tdiff) < (so ? snapDiff : 1)) { + change.ob = (otpos = d.yl[i] - oppDiff) - h; + change.obpos = d.container[3] - otpos; + change.obEl = d.els[i]; + change.otoffset = otoffset = 0; + break; + } + } + } + } + + //Grid + if (apf.config.snapgrid) { + var r, gs = apf.config.gridsize; + if (tpos === false) + change.t = t - (r = t % gs) + (r/gs > 0.5 ? gs : 0); + if (lpos === false) + change.l = l - (r = l % gs) + (r/gs > 0.5 ? gs : 0); + if (!we && hpos === false) + change.w = w - (r = w % gs) + (r/gs > 0.5 ? gs : 0); + if (!no && wpos === false) + change.h = h - (r = h % gs) + (r/gs > 0.5 ? gs : 0); + } + + if (lpos !== false && (olpos === false || change.or)) { + dragIndicator1.style.left = (lpos + loffset + d.container[0]) + "px"; + dragIndicator1.style.top = d.container[1] + "px"; + dragIndicator1.style.height = (d.container[3]) + "px" + dragIndicator1.style.display = "block"; + dragIndicator1.style.borderWidth = "0 0 0 2px"; + } + else + dragIndicator1.style.display = "none"; + + if (tpos !== false && (otpos === false || change.ob)) { + dragIndicator2.style.left = (d.container[0]) + "px"; + dragIndicator2.style.top = (tpos + toffset + d.container[1]) + "px"; + dragIndicator2.style.width = (d.container[2]) + "px" + dragIndicator2.style.display = "block"; + dragIndicator2.style.borderWidth = "2px 0 0"; + } + else + dragIndicator2.style.display = "none"; + + if (wpos !== false) { + dragIndicator3.style.left = (wpos + d.container[0]) + "px"; + dragIndicator3.style.top = d.container[1] + "px"; + dragIndicator3.style.height = (d.container[3]) + "px" + dragIndicator3.style.display = "block"; + dragIndicator3.style.borderWidth = "0 0 0 2px"; + } + else { + dragIndicator3.style.display = "none"; + } + + if (hpos !== false) { + dragIndicator4.style.left = (d.container[0]) + "px"; + dragIndicator4.style.top = (hpos + d.container[1]) + "px"; + dragIndicator4.style.width = (d.container[2]) + "px" + dragIndicator4.style.display = "block"; + dragIndicator4.style.borderWidth = "2px 0 0"; + } + else + dragIndicator4.style.display = "none"; + + if (olpos !== false) { + dragIndicator6.style.left = (olpos + oloffset + d.container[0]) + "px"; + dragIndicator6.style.top = d.container[1] + "px"; + dragIndicator6.style.height = (d.container[3]) + "px" + dragIndicator6.style.display = "block"; + dragIndicator6.style.borderWidth = "0 0 0 " + oppDiff + "px"; + } + else + dragIndicator6.style.display = "none"; + + if (otpos !== false) { + dragIndicator7.style.left = (d.container[0]) + "px"; + dragIndicator7.style.top = (otpos + otoffset + d.container[1]) + "px"; + dragIndicator7.style.width = (d.container[2]) + "px" + dragIndicator7.style.display = "block"; + dragIndicator7.style.borderWidth = oppDiff + "px 0 0"; + } + else + dragIndicator7.style.display = "none"; + + //Correction + if (!isDrag) { + if (!we) change.l = l; + if (!no) change.t = t; + if (we) { + if (change.ol != undefined) { + change.w = d.width + (d.left - change.ol); + change.l = change.ol; + } + else if (change.l != undefined) + change.w = d.width + (d.left - change.l); + } + else if (ea && change.or != undefined) + change.w = olpos - d.left; + if (no) { + if (change.ot != undefined) { + change.h = d.height + (d.top - change.ot); + change.t = change.ot; + } + else if (change.t != undefined) + change.h = d.height + (d.top - change.t); + } + else if (so && change.ob != undefined) + change.h = otpos - d.top; + } + else { + if (olpos != false) + change.l = change.ol || change.or; + if (otpos != false) + change.t = change.ot || change.ob; + } + + if (isDrag && e.shiftKey) { + if (change.sl) change.l = change.sl; + if (change.st) change.t = change.st; + } + } + }; + + var dragInfo; + function setDragInfo(el, pEl, isDrag) { + apf.setStyleClass(dragIndicator1, "indicate_common", + ["indicate_vbox", "indicate_hbox", "indicate_table", "indicate_common"]); + + if (pEl.localName == "html") + pEl = pEl.ownerDocument.body; + + var container = pEl.$int; + var isBody = pEl.$int.tagName == "BODY"; + var htmlEl = isDrag && el.dragOutline ? outline : el.$ext; + var d = dragInfo = { + left : apf.getHtmlLeft(htmlEl), + top : apf.getHtmlTop(htmlEl), + width : htmlEl.offsetWidth, + height : htmlEl.offsetHeight + } + d.cwidth = Math.round(d.width/2); + d.cheight = Math.round(d.height/2); + + //Container + d.container = isBody ? [0,0] : apf.getAbsolutePosition(container, null, true); //@todo should check for position relative.. + if (isBody) var m = apf.getMargin(container); + d.container.push( + (isBody ? apf.getWindowWidth() : apf.getHtmlInnerWidth(container)), + (isBody ? apf.getWindowHeight() : apf.getHtmlInnerHeight(container))); + + //Elements + var els = pEl.getElementsByTagName("*", true); //Fetch all siblings incl me + var xl = d.xl = [], yl = d.yl = [], curel; + var xm = d.xm = [], ym = d.ym = []; + var xr = d.xr = [], yr = d.yr = [], dels = d.els = []; + + //Container element + if (apf.config.snapcontainer) { + xl.push(10); + xr.push(d.container[2] - 10); + yl.push(10); + yr.push(d.container[3] - 10); + + dels.push(d.container); + } + + if (apf.config.snapelement) { + var selected = apf.document.$getVisualSelect().getLastSelection();//apf.document.getSelection().$getNodeList(); //@todo maybe optimize by requesting from visualselect + + var q = apf.getBorderOffset(container); + var bLeft = q[0]; + var bTop = q[1]; + for (var l, t, h, w, i = 0, il = els.length; i < il; i++) { + if (selected.indexOf(curel = els[i]) > -1 || !curel.$ext) + continue; + + l = curel.$ext.offsetLeft - bLeft; + xl.push(l); + xm.push(l + Math.round((w = curel.$ext.offsetWidth)/2)); + xr.push(l + w); + + t = curel.$ext.offsetTop - bTop; + yl.push(t); + ym.push(t + Math.round((h = curel.$ext.offsetHeight)/2)); + yr.push(t + h); + + dels.push(curel); + } + } + } + + //the ob values could also be computed dynamically based on bottom + height + //of element, that will give cleanup issues though + //@todo when sticking to a non container guide, and the guide is created + //from a node that is anchored, then the anchor of that side should be copied + function setStickyEdges(el, extra){ + var s = el.$stick, d = dragInfo, t = el.$stuck || (el.$stuck = [false, false, false, false]); + var setOpp = false; + + if (!apf.config.snapcontainer) + return; + + if (isCoord(s.orpos) && !s.orEl.left) { + if (!t[1] && !isCoord(el.right)) + t[1] = true; + el.setAttribute("right", s.orpos); + setOpp = true; + } + else if (isCoord(s.w) && s.rsticky) { + if (!isCoord(el.right)) { + el.setAttribute("right", d.container[2] - coord(s.l, d.left) - s.w); + t[1] = true; + } + setOpp = true; + } + if (isCoord(s.l)) { + if (isCoord(s.olpos) && !s.olEl.right) { + el.setAttribute("left", s.olpos); + t[3] = true; + } + else if (s.lsticky && !isCoord(el.left)) { //Left + el.setAttribute("left", s.l); + t[3] = true; + } + + if (!setOpp) { + if (s.rsticky) { + if (!t[1] && !isCoord(el.right)) + t[1] = true; + el.setAttribute("right", d.container[2] - s.l - coord(s.w, d.width)); + setOpp = true; + } + else if (t[1] && isCoord(el.right) && (isCoord(s.l) || s.lsticky)) { + if (!isCoord(el.left)) { + el.setAttribute("left", s.l); + t[3] = true; + } + el.removeAttribute("right"); + el.setAttribute("width", extra && extra.w || el.$ext.offsetWidth); + t[1] = false; + } + } + } + if (t[1] && isCoord(el.left) && (setOpp || isCoord(el.right)) + && !(s.rsticky || (isCoord(s.orpos) && !s.orEl.left))) { + el.removeAttribute("right"); + el.setAttribute("width", extra && extra.w || el.$ext.offsetWidth); + t[1] = false; + } + else if (t[3] && isCoord(el.left) && (setOpp || isCoord(el.right)) + && !(s.lsticky || (isCoord(s.olpos) && !s.olEl.right))) { + el.removeAttribute("left"); + el.setAttribute("width", extra && extra.w || el.$ext.offsetWidth); + t[3] = false; + } + else if (t[1] && (setOpp || isCoord(el.right)) + && !(s.rsticky || (isCoord(s.orpos) && !s.orEl.left))) { + el.setAttribute("left", extra && extra.l || el.$ext.offsetLeft); + t[3] = true; + el.removeAttribute("right"); + el.setAttribute("width", extra && extra.w || el.$ext.offsetWidth); + t[1] = false; + } + + var setOpp = false; + if (isCoord(s.obpos) && !s.obEl.top) { + if (!t[2] && !isCoord(el.bottom)) + t[2] = true; + el.setAttribute("bottom", s.obpos); + setOpp = true; + } + else if (isCoord(s.h) && s.bsticky) { + if (!isCoord(el.bottom)) { + el.setAttribute("bottom", d.container[3] - coord(s.t, d.top) - s.h); + t[2] = true; + } + setOpp = true; + } + if (isCoord(s.t)) { + if (isCoord(s.otpos) && !s.otEl.bottom) { + el.setAttribute("top", s.otpos); + t[0] = true; + } + else if (s.tsticky && !isCoord(el.top)) { //Top + el.setAttribute("top", s.t); + t[0] = true; + } + + if (!setOpp) { + if (s.bsticky) { + if (!t[2] && !isCoord(el.bottom)) + t[2] = true; + el.setAttribute("bottom", d.container[3] - s.t - coord(s.h, d.height)); + setOpp = true; + } + else if (t[2] && isCoord(el.bottom) && (isCoord(s.t) || s.tsticky)) { + if (!isCoord(el.top)) { + el.setAttribute("top", s.t); + t[0] = true; + } + el.removeAttribute("bottom"); + el.setAttribute("height", extra && extra.h || el.$ext.offsetHeight); + t[2] = false; + } + } + } + if (t[2] && isCoord(el.top) && (setOpp || isCoord(el.bottom)) + && !(s.bsticky || (isCoord(s.obpos) && !s.obEl.top))) { + el.removeAttribute("bottom"); + el.setAttribute("height", extra && extra.h || el.$ext.offsetHeight); + t[2] = false; + } + else if (t[0] && isCoord(el.top) && (setOpp || isCoord(el.bottom)) + && !(s.tsticky || (isCoord(s.otpos) && !s.otEl.bottom))) { + el.removeAttribute("top"); + el.setAttribute("height", extra && extra.h || el.$ext.offsetHeight); + t[0] = false; + } + else if (t[2] && (setOpp || isCoord(el.bottom)) + && !(s.bsticky || (isCoord(s.obpos) && !s.obEl.top))) { + el.setAttribute("top", extra && extra.t || el.$ext.offsetTop); + t[0] = true; + el.removeAttribute("bottom"); + el.setAttribute("height", extra && extra.h || el.$ext.offsetHeight); + t[2] = false; + } + } + + var control = {stop:apf.K}; + function hideIndicators(animate, exclude5){ + if (!dragIndicator1) + return; + + if (animate) { + control = {}; + + var tweens = []; + for (var i = 0; i < indicators.length; i ++) { + if (indicators[i].style.display == "block") { + tweens.push({ + oHtml : indicators[i], + type : "fade", + from : 1, + to : 0 + }); + } + } + + function done(){ + for (var i = 0; i < tweens.length; i++) { + tweens[i].oHtml.style.filter = ""; + tweens[i].oHtml.style.display = "none"; + } + } + + apf.tween.multi(dragIndicator1, { + anim : apf.tween.easeInQuad, + tweens : tweens, + steps : 20, + control : control, + onfinish : done, + onstop : done + }); + } + else { + dragIndicator1.style.display = "none"; + dragIndicator2.style.display = "none"; + dragIndicator3.style.display = "none"; + dragIndicator4.style.display = "none"; + if (!exclude5) + dragIndicator5.style.display = "none"; + dragIndicator6.style.display = "none"; + dragIndicator7.style.display = "none"; + } + } + + var lastPos; + function beforedragstart(e){ + //Prevent dragging when this node isn't selected + var selection = apf.document.$getVisualSelect().getLastSelection(); + + outline = selection.length > 1 && !e.htmlEvent.ctrlKey + ? apf.document.$getVisualSelect().$getOutline() + : dragOutline; + + this.$setOutline(outline); + } + + function beforedrag(e, reparent, add){ + var name, pEl; + + var selection = apf.document.$getVisualSelect().getLastSelection(); + if ((this.$multidrag = selection.length > 1) + && selection.indexOf(this) == -1) + return false; + + if (outline != (selection.length > 1 + ? apf.document.$getVisualSelect().$getOutline() + : dragOutline)) + return false; //@todo this is a small hack to prevent dragging with the wrong outline. We should rethink the outline situation. + + control.stop(); + + if (lastReparent && !add) { + pEl = lastReparent;//apf.findHost(el.$pHtmlDoc.getElementById("apf_outline").parentNode); + name = pEl.localName; + } + else { + if (!e) e = event; + name = (pEl = this.parentNode).localName; + } + + if (!lastPos) + lastPos = apf.getAbsolutePosition(outline, pEl.localName == "table" ? pEl.$ext : outline.offsetParent); + + if ("vbox|hbox|table".indexOf(name) > -1) { + this.realtime = true; + + apf.setStyleClass(dragIndicator1, "indicate_" + name, + ["indicate_vbox", "indicate_hbox", "indicate_table", "indicate_common"]); + + dragIndicator1.style.borderWidth = ""; + this.$showDrag = showDrag[pEl.localName]; + } + else if (pEl.editable || add || this.$adding) { + this.realtime = true; + + setDragInfo(this, pEl, true); + + this.$showDrag = showDrag.common; + } + else + this.realtime = false; + + this.ownerDocument.execCommand("begin"); + } + + function afterdrag(e){ + if (lastAmlNode) { + clearTimeout(lastAmlNode[4]); + lastAmlNode = null; + } + lastReparent = null; + + var el = this; + var htmlNode = el.dragOutline ? outline : el.$ext; + + delete this.$multidrag; + + var l, t, w, h, selected, prevParent, pNode; + if (el.$adding) { + //this.$ext = oOutline; + selected = [el]; + prevParent = + pNode = apf.findHost(htmlNode.parentNode); + } + else { + selected = apf.document.$getVisualSelect().getLastSelection(); + prevParent = selected[0].parentNode; + pNode = apf.findHost(htmlNode.parentNode); + } + + //Set the coordinates if not dropped into a layout node + if (selected.length > 1 && lastPos + && "vbox|hbox|table".indexOf(pNode.localName) == -1) { + var deltaX = apf.getHtmlLeft(htmlNode) - lastPos[0]; + var deltaY = apf.getHtmlTop(htmlNode) - lastPos[1]; + var pWidth = outline.parentNode.tagName == "BODY" + ? apf.getWindowWidth() + : apf.getHtmlInnerWidth(outline.parentNode); + var pHeight = outline.parentNode.tagName == "BODY" + ? apf.getWindowHeight() + : apf.getHtmlInnerHeight(outline.parentNode); + + if (deltaX || deltaY + || "vbox|hbox|table".indexOf(prevParent.localName) > -1) { + var isTable = prevParent.localName == "table"; + for (var n, i = 0; i < selected.length; i++) { + n = selected[i]; + var diff = apf.getDiff(n.$ext); + + if (isTable) + var itemPos = apf.getAbsolutePosition(n.$ext, prevParent.$ext); + n.$updateProperties( + l = (isTable ? itemPos[0] : apf.getHtmlLeft(n.$ext)) + deltaX, + t = (isTable ? itemPos[1] : apf.getHtmlTop(n.$ext)) + deltaY, + (w = n.$ext.offsetWidth) - diff[0], + (h = n.$ext.offsetHeight) - diff[1], diff[0], diff[1], + Math.max(0, pWidth - l - w), + Math.max(0, pHeight - t - h)); + showDrag.common_resize.call(n, l, t, w, h, {}, + n.$stick = {}, true, true, true, true, true, 1); + dragInfo.left = l; + dragInfo.top = t; + dragInfo.width = w; + dragInfo.height = h; + setStickyEdges(n, {t:t,l:l,h:h,w:w}); + delete n.$stick; + } + } + } + + if (movePosition) { + if (el.$adding || htmlNode.parentNode != el.$ext.parentNode) { + //@todo review this... strange things + + //@todo this shouldnt be here but done automatically by vbox/hbox/table + for (var i = 0; i < selected.length; i++) { + selected[i].$ext.style.left = + selected[i].$ext.style.top = + selected[i].$ext.style.right = + selected[i].$ext.style.width = + selected[i].$ext.style.height = + selected[i].$ext.style.bottom = ""; + selected[i].$ext.style.position = "static"; + } + } + //else + //var pNode = el.parentNode; + + if ("vbox|hbox|table".indexOf(pNode.localName) > -1) { + for (var i = 0; i < selected.length; i++) { + pNode.insertBefore(selected[i], movePosition.where == "after" + ? movePosition.amlNode.nextSibling + : movePosition.amlNode); + } + } + movePosition = null; + } + else { + //reparent happened + if (el.$adding || htmlNode.parentNode != el.$ext.parentNode) { + if (el.$adding) { + el.removeNode(); + el.ownerDocument.execCommand("begin", null, true); + } + + for (var i = 0; i < selected.length; i++) { + pNode.appendChild(selected[i]); + if (!el.$adding) + setDefaultStuck(selected[i]); + //selected[i].$stuck = [false, false, false, false]; //reset stickyness in new context + } + } + + if (el.$stick) { + if (selected.length == 1) { + setStickyEdges(el); + delete el.$stick; + } + } + } + + apf.document.getSelection().$selectList(selected); + if (selected.indexOf(apf.document.activeElement) == -1) + selected[0].focus(); + + hideIndicators(); + + this.ownerDocument.execCommand("commit"); + + apf.layout.processQueue(); + + //oOutline.style.display = "none"; + + if (el.$adding) { + delete el.$adding; + el.dragOutline = true; + el.focus(); + } + else { + this.ownerDocument.$getVisualSelect().updateGeo(); + } + + lastPos = null; + }; + + function beforeresize(e){ + var type = e.type; + var name = this.parentNode.localName; + + control.stop(); + + if (name == "vbox") { + var pack = this.align || this.parentNode.pack; + if (pack != "middle") { + var xType = pack == "start" ? "n" : "s"; + type = type.replace(xType, ""); + type = type.replace("w", ""); + if (!type) return; + } + + this.realtime = true; + this.$showResize = function(l, t, w, h, e, c, we, no, ea, so) { + if (h && (no || so)) + this.setProperty("height", h); + if (w && (ea || we)) + this.setProperty("width", w); + apf.layout.processQueue(); + } + } + else if (name == "hbox") { + var pack = this.align || this.parentNode.pack; + if (pack != "middle") { + var xType = pack == "start" ? "w" : "e"; + type = type.replace(xType, ""); + type = type.replace("n", ""); + if (!type) return; + } + + this.realtime = true; + this.$showResize = function(l, t, w, h) { + this.setProperty("width", w); + if (h) + this.setProperty("height", h); + apf.layout.processQueue(); + } + } + else if (this.parentNode.editable && name != "table") { + this.realtime = true; + + setDragInfo(this, this.parentNode); + + this.$showResize = showDrag.common_resize; + } + else + this.realtime = false; + + if ("hbox|vbox|table".indexOf(name) == -1 + && apf.getStyle(this.$ext, "position") != "absolute") { //ignoring fixed for now... + this.$ext.style.width = (this.$ext.offsetWidth - apf.getWidthDiff(this.$ext)) + "px"; + this.$ext.style.height = (this.$ext.offsetHeight - apf.getHeightDiff(this.$ext)) + "px"; + /*this.$ext.style.left = apf.getHtmlLeft(this.$ext) + "px"; + this.$ext.style.top = apf.getHtmlTop(this.$ext) + "px"; + this.$ext.style.position = "absolute";*/ + } + + //@todo move everything below into vbox/table/anchoring + var m, edge; + if (name == "vbox") { + this.maxheight = Math.min(this.maxheight || 10000, + this.parentNode.$ext.offsetHeight + - apf.getVerBorders(this.parentNode.$ext) + - (this.parentNode.$hasPerc ? this.parentNode.$totalPerc * 5 : 0) + - this.parentNode.$totalFixed + + this.$ext.offsetHeight); + + if (this.margin) + m = apf.getBox(this.margin); + if (this.parentNode.edge) + edge = apf.getBox(this.parentNode.edge); + this.maxwidth = Math.min(this.maxwidth || 10000, + this.parentNode.$ext.offsetWidth + - apf.getHorBorders(this.parentNode.$ext) + - (edge ? edge[1] + edge[3] : 0) + - (m ? m[1] + m[3] : 0)); + } + else if (name == "hbox") { + //if (this.parentNode.$hasPerc) { + this.maxwidth = Math.min(this.maxwidth || 10000, + this.parentNode.$ext.offsetWidth + - apf.getHorBorders(this.parentNode.$ext) + - (this.parentNode.$hasPerc ? this.parentNode.$totalPerc * 5 : 0) + - this.parentNode.$totalFixed + + this.$ext.offsetWidth); + //} + + if (this.margin) + m = apf.getBox(this.margin); + if (this.parentNode.edge) + edge = apf.getBox(this.parentNode.edge); + this.maxheight = Math.min(this.maxheight || 10000, + this.parentNode.$ext.offsetHeight + - apf.getVerBorders(this.parentNode.$ext) + - (edge ? edge[0] + edge[2] : 0) + - (m ? m[0] + m[2] : 0)); + } + + e.setType(name == "table" ? "s" : type); + }; + + function afterresize(e){ + //this.$ext = oOutline; + + hideIndicators(); + + var name = this.parentNode.localName; + + //Sizing the edge will stick it to the side + if (name == "hbox") { + if (this.maxheight == this.$ext.offsetHeight && this.height) + this.setAttribute("height", ""); + } + //Sizing the edge will stick it to the side + else if (name == "vbox") { + if (this.maxwidth == this.$ext.offsetWidth && this.width) + this.setAttribute("width", ""); + } + //Sizing the edge will stick it to the side + else if (this.$stick) { + setStickyEdges(this, e); //@todo bugs with placing toolbar in ipad skin + } + this.$stick = null; + + this.ownerDocument.execCommand("commit"); + + //apf.layout.processQueue(); + this.ownerDocument.$getVisualSelect().$finishResize(); + }; + + function cancel(){ + hideIndicators(); + + this.ownerDocument.execCommand("rollback"); + this.ownerDocument.$getVisualSelect().$finishResize(); + } + + function keydown(e){ + if (!apf.document.$getVisualSelect) + return; + + var selected = apf.document.$getVisualSelect().getLastSelection();//apf.document.getSelection().$getNodeList(); //@todo maybe optimize by requesting from visualselect + if (!selected.length) + return; + + //@todo this should be solved in the capturing phase + if (apf.document.queryCommandState("rename")) + return; + + var name = selected[0].parentNode.localName; + if ("vbox|hbox|table".indexOf(name) > -1) + return; + + var dirX = 0, dirY = 0; + switch(e.keyCode) { + case 38: //UP + dirY = -1; + break; + case 40: //DOWN + dirY = 1; + break; + case 39: //RIGHT + dirX = 1; + break; + case 37: //LEFT + dirX = -1; + break; + default: + return; + } + + control.stop(); + + var l, t, w, h, n = selected[0], ext, d; + n.ownerDocument.execCommand("begin"); //@todo Use a system here to combine these commits as well as possible + for (var i = 0, il = selected.length; i < il; i++) { + n = selected[i]; + ext = n.$ext; + d = apf.getDiff(ext); + + n.$updateProperties( + l = apf.getHtmlLeft(ext) + (dirX * (e.ctrlKey ? 10 : (e.shiftKey ? 30 : 1))), + t = apf.getHtmlTop(ext) + (dirY * (e.ctrlKey ? 10 : (e.shiftKey ? 30 : 1))), + (w = ext.offsetWidth) - d[0], + (h = ext.offsetHeight) - d[1], d[0], d[1]); + } + n.ownerDocument.execCommand("commit"); //@todo Use a system here to combine these commits as well as possible + + apf.layout.processQueue(); + n.ownerDocument.$getVisualSelect().updateGeo(); + + if (selected.length > 1) { + var vOutline = n.ownerDocument.$getVisualSelect().$getOutline(); + l = apf.getHtmlLeft(vOutline); + t = apf.getHtmlTop(vOutline); + w = vOutline.offsetWidth; + h = vOutline.offsetheight; + } + + setDragInfo(n, n.parentNode, true); + showDrag.common_resize.call(n, l, t, w, h, {}, + {}, true, true, true, true, true, 1); + + hideIndicators(true); + } + apf.addEventListener("keydown", keydown); + + function setDefaultStuck(amlNode){ + amlNode.$stuck = [ + amlNode.$adding || amlNode.top && !amlNode.bottom || amlNode.top == 10 || amlNode.top === 0, + amlNode.right && !amlNode.left || amlNode.right == 10 || amlNode.right === 0, + amlNode.bottom && !amlNode.top || amlNode.bottom == 10 || amlNode.bottom === 0, + amlNode.$adding || amlNode.left && !amlNode.right || amlNode.left == 10 || amlNode.left === 0]; + var s = amlNode.$stuck; + if (s[0] && s[1] && s[2] && s[3]) + amlNode.$stuck = [false, false, false, false]; + } + + apf.ContentEditable.addInteraction = function(amlNode){ + if (!inited) + init(); + + amlNode.addEventListener("beforedragstart", beforedragstart); + amlNode.addEventListener("beforedrag", beforedrag); + amlNode.addEventListener("beforeresize", beforeresize); + amlNode.addEventListener("afterdrag", afterdrag); + amlNode.addEventListener("afterresize", afterresize); + amlNode.addEventListener("resizecancel", cancel); + amlNode.addEventListener("dragcancel", cancel); + + setDefaultStuck(amlNode); + } + + apf.ContentEditable.removeInteraction = function(amlNode){ + amlNode.removeEventListener("beforedragstart", beforedragstart); + amlNode.removeEventListener("beforedrag", beforedrag); + amlNode.removeEventListener("beforeresize", beforeresize); + amlNode.removeEventListener("afterdrag", afterdrag); + amlNode.removeEventListener("afterresize", afterresize); + amlNode.removeEventListener("resizecancel", cancel); + amlNode.removeEventListener("dragcancel", cancel); + + delete amlNode.$stuck; + } +})(); + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/core/baseclasses/contenteditable/selectrect.js)SIZE(5678)TIME(Mon, 10 Jan 2011 10:53:11 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + +/** + * @private + */ +apf.selectrect = function (){ + var active; + var p1 = document.body.appendChild(document.createElement("div")), + p2 = document.body.appendChild(document.createElement("div")), + q = document.body.appendChild(document.createElement("div")), + _self = this, + startX, startY; + p1.className = "pointer_left"; + p2.className = "pointer_right"; + q.className = "new_element"; + + this.activate = function(){ + if (active) return; + active = true; + + document.onmousemove = function(e){ + if (!e) e = event; + + p1.style.width = (Math.abs(e.clientX) || 1) + "px"; + p1.style.height = (Math.abs(e.clientY) || 1) + "px"; + + p2.style.width = (Math.abs(document.documentElement.offsetWidth + - e.clientX - 5) || 1) + "px"; + p2.style.height = (Math.abs(document.documentElement.offsetHeight + - e.clientY - 5) || 1) + "px"; + + if (q.style.display == "block"){ + var wt = e.clientX - startX - 1, + ht = e.clientY - startY - 1, + min = Math.min(wt, ht); + //if (e.shiftKey) + //wt = ht = min; + + q.style.width = (wt < 0 ? -1 * (wt - 1) : (wt || 1)) + "px"; + q.style.height = (ht < 0 ? -1 * (ht - 1) : (ht || 1)) + "px"; + + q.style.left = wt < 0 ? "" : (startX) + "px"; + q.style.right = wt < 0 + ? (document.documentElement.offsetWidth - startX - 4) + "px" + : ""; + + q.style.bottom = ht < 0 + ? (document.documentElement.offsetHeight - startY - 4) + "px" + : ""; + q.style.top = ht < 0 ? "" : (startY) + "px"; + + apf.config.setProperty("x", apf.getHtmlLeft(q)); + apf.config.setProperty("y", apf.getHtmlTop(q)); + apf.config.setProperty("w", q.offsetWidth); + apf.config.setProperty("h", q.offsetHeight); + } + else { + apf.config.setProperty("x", e.clientX); + apf.config.setProperty("y", e.clientY); + apf.config.setProperty("w", ""); + apf.config.setProperty("h", ""); + } + } + + document.onmousedown = function(e){ + if (!e) e = event; + //if ((e.srcElement || e.target) == document.body) + //return false; + + p1.style.top = "-2000px"; + p2.style.top = "-2000px"; + var el = document.elementFromPoint(e.clientX, e.clientY); + var amlNode = apf.findHost(el); + + p1.style.top = ""; + p2.style.top = ""; + + if (amlNode) { + while (amlNode && !amlNode.$int) + amlNode = amlNode.parentNode; + } + if (!amlNode) + amlNode = apf.document.documentElement; + + if (!amlNode.editable) + return; + + //apf.ContentEditable.resize.grab(amlNode); + + q.style.display = "block"; + q.style.left = (startX = event.clientX) + "px"; + q.style.top = (startY = event.clientY) + "px"; + q.style.width = q.style.height = "1px"; + + apf.dragMode = true; + }; + + document.onmouseup = function(){ + if (q.offsetWidth > 10 && q.offsetHeight > 10) { + if (apf.document.queryCommandValue("mode") == "select") { + apf.document.execCommand("select", false, {htmlNode: q}); + } + else { + apf.document.execCommand("add", false, {htmlNode: q}); + } + _self.deactivate(); + } + + q.style.display = "none"; + startX = false; + startY = false; + + apf.dragMode = false; + }; + + p1.style.display = + p2.style.display = "block"; + document.body.style.cursor = + document.documentElement.style.cursor = "crosshair"; + }; + + this.deactivate = function(){ + active = false; + document.onmousemove = null; + document.onmousedown = null; + document.onmouseup = null; + + p1.style.display = + p2.style.display = + q.style.display = "none"; + document.body.style.cursor = + document.documentElement.style.cursor = ""; + }; +}; + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/core/baseclasses/contenteditable/visualconnect.js)SIZE(36914)TIME(Mon, 10 Jan 2011 10:53:11 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + +/** + * @private + */ +apf.visualConnect = function (sel){ + /* + * modes: + * draw : mode to create a new connection + * element : mode for displaying connections of selected element + * all : mode for displaying connections of all elements + */ + var lineMode = "draw"; // current lineMode of visualConnect + + var active, div, cTemplate; // visualconnect is active + var fromEl, toEl; // selected 'from element' and 'to element' during draw mode + var fromAtt, toAtt; // selected attribute of 'from' and 'to' element; + var connections; // connections that are drawn + var attMenu; // menu with attributes of element that appears when creating connection + var ignoreFromAtts = ["id"]; // attributes for from element to ignore in attMenu + var ignoreToAtts = ["for"]; // attributes for to elements to ignore in attMenu + var _self = this; + var prevSelection; + + // init draw api + var width = document.body.clientWidth; + //@todo adjust height to browser window height? + var height = 800;//document.body.clientHeight; + var paintGroup = apf.vector.group({w:width,h:height,z:1000}); + + var paintRect = paintGroup.rect({ + sw: 1, + s: "#24a3f4", + f: "#24a3f4", + o: 0.2 + }); + var paintLine = paintGroup.shape({ + p: "", + sw: 1.5, + s: "#24a3f4", + f: "#24a3f4" + }); + var paintConnections = paintGroup.shape({ + p: "", + sw: 1.5, + s: "#24a3f4", + f: "#24a3f4" + }); + + this.setMode = function(mode) { + lineMode = mode; + + if (this.onchangemode) + this.onchangemode({mode: mode}); + }; + + this.activate = function(e, timeout){ + if (active) + return; + + active = true + //document.getElementById("log").innerHTML += "activated
    "; + var _self = this; + var drawPath = [], connectionPath = [], hNode, pos, selection = sel.$getNodeList(), lines = []; + var timer, lastTime; + var isDrawing = false; + + apf.addEventListener("vcpropchange", function(e) { + if (connections[e.obj.fromEl.id] && connections[e.obj.fromEl.id][e.obj.toEl.id]) + var el1 = e.obj.fromEl.id, el2 = e.obj.toEl.id; + else + var el1 = e.obj.toEl.id, el2 = e.obj.fromEl.id; + + for (var maxBoxWidth = 0, maxLeftWidth = 0, i = 0, l = connections[el1][el2].length; i < l; i++) { + if ((c=connections[el1][el2][i].conn).getMaxLeftWidth() > maxLeftWidth) + maxLeftWidth = c.getMaxLeftWidth(); + if (c.getMaxBoxWidth() > maxBoxWidth) + maxBoxWidth = c.getMaxBoxWidth(); + } + + + for (i = 0, l = connections[el1][el2].length; i < l; i++) { + //(c=connections[el1][el2][i].conn).$lblFromEl.style.width = ((maxLeftWidth-4) - c.$lblAttMenu.offsetWidth) + "px"; + + (c=connections[el1][el2][i].conn).$fromBox.style.width = maxLeftWidth + 20 + "px"; + //c.$lblFromEl.style.width = (maxLeftWidth - c.$lblAttMenu.offsetWidth) + "px"; + + c.$ext.style.width = maxBoxWidth + 90 + "px"; + } + + }); + apf.addEventListener("vcdelete", function(e) { + e.el.style.display = "none"; + }); + apf.addEventListener("vcmoveselection", function(e) { + sel.$selectList(selection = [e.target]); + createConnections(selection); + showConnections(); + }); + + apf.plane.show(); + + div = document.body.appendChild(document.createElement("div")); + div.style.display = "block"; + div.style.position = "absolute"; + div.style.left = "0px"; + div.style.top = "0px"; + div.style.zIndex = 100000001; + + //@todo use apf.window.zManager.set("drag", this.panel); +/* + var showAllTimer = setTimeout(function(){ + // lets show the drawing till someone clicks and then its gone + // lets create some random lines + var n = []; + path = []; + for(var i = 0;i<100;i++){ + var sx = ~~(Math.random()*600), sy = ~~(Math.random()*600), ex = ~~(Math.random()*600), ey = ~~(Math.random()*600); + path.push(paintGroup.circlePath(sx,sy,3,3),"M",sx,sy,"L",ex,ey,paintGroup.circlePath(ex,ey,3,3)); + } + + paintLine.style({p: path.join(" ")}); + paintGroup.style({v:1}); + paintGroup.repaint(); + }, timeout); +*/ + + switch (lineMode) { + case "draw": + startDraw(e); + break; + case "element": + if (selection.length) { + createConnections(selection); + showConnections(); + } + break; + case "all": + var all = []; + for (var el, i = 0, l = apf.all.length; i < l; i++) { + if ((el=apf.all[i]).$ext && el.prefix == "a") all.push(apf.all[i]); + } + if (createConnections(all)) + selection = all; + showConnections(); + break; + } + + function createConnections(elements) { + connections = null; + if (elements.length){ + // get all elements + var all = []; + for (var el, i = 0, l = apf.all.length; i < l; i++) { + if ((el=apf.all[i]).$ext && el.prefix == "a") all.push(apf.all[i]) + } + + // element as source element + //@todo use .$funcHandlers hash table to find the connections .value = "{blah.value + bli.value}" + for (var al, attrs, el, i = 0, l = elements.length; i < l; i++) { + for (var fromAt in (attrs=(el=elements[i]).$funcHandlers)) { + for (var at, targetList = [], ai = 0, al = attrs[fromAt].length; ai < al; ai++) { + targetList.push({ + el : (at=attrs[fromAt][ai]).amlNode, + at : at.prop + }); + } + createConnection(el, fromAt, targetList); + } + /* + for (var val, targetEl, targetAttr, split, j = 0, jl = elements[i].attributes.length; j < jl; j++) { + // @todo regex search of "{" + if ((val = elements[i].attributes[j].value).toString().charAt(0) == "{" && val.toString().charAt(val.length-1) == "}") { + // check if value is attribute of an element + if (targetEl = apf.document.getElementById((split=val.split("."))[0].substr(1))) { + targetAttr = split[1].substr(0, split[1].length-1); + createConnection(elements[i], targetEl, elements[i].attributes[j].name, targetAttr, val); + } + } + } + + // check all elements if attribute of any element points to attribute of selected element + for (var ei = 0, el = all.length; ei < el; ei++) { + if (all[ei] == elements[i]) continue; + for (var ai = 0, al = all[ei].attributes.length; ai < al; ai++) { + // check for value with string {...} + if ((val = all[ei].attributes[ai].value).toString().charAt(0) == "{" && val.toString().charAt(val.length-1) == "}") { + if ((targetEl = apf.document.getElementById((split=val.split("."))[0].substr(1))) == elements[i]) { + targetAttr = split[1].substr(0, split[1].length-1); + createConnection(all[ei], targetEl, all[ei].attributes[ai].name, targetAttr, val); + } + } + } + } + */ + } + //debugger; + } + + var found = false; + for (var id in connections) { + found = true; + } + + return found; + } + + function showConnections() { + if (connections) { + drawConnections(); + paintConnections.style({p: connectionPath.join(" ")}); + + apf.plane.show(); + paintGroup.style({v:1}); + paintGroup.repaint(); + } + else { + alert("no connections"); +// paintGroup.style({v:0}); +// paintGroup.repaint(); + + // message "no connections" + +// _self.deactivate(); + } + } + + function startDraw(e){ + apf.dragMode = true; //prevents selection + + fromEl = null; + toEl = null; + } + + function stopDraw(e){ + apf.dragMode = true; + apf.plane.hide(); + + paintGroup.style({v:0}); + paintGroup.repaint(); + + var htmlNode = document.elementFromPoint(e.clientX, e.clientY); + var amlNode = apf.findHost(htmlNode); + // target amlNode found, create connection + + if (amlNode && amlNode.editable && selection.indexOf(amlNode) == -1 && amlNode.tagName != "html") { + toEl = amlNode; + + // draw connection line + var hNode, x, y, w, h, pos; + var from = { + x : (x=(pos=apf.getAbsolutePosition((hNode=fromEl.$ext)))[0]), + y : (y=pos[1]), + w : (w=hNode.offsetWidth), + h : (h=hNode.offsetHeight), + c : [Math.round(x+w/2), Math.round(y+h/2)] // center of element + } + var to = { + x : (x=(pos=apf.getAbsolutePosition((hNode=toEl.$ext)))[0]), + y : (y=pos[1]), + w : (w=hNode.offsetWidth), + h : (h=hNode.offsetHeight), + c : [Math.round(x+w/2), Math.round(y+h/2)] // center of element + } + var pos1 = from.c, pos2 = to.c; + + paintGroup.style({v:1}); + paintLine.style({p: [ + "M",pos1[0],pos1[1],"L",pos2[0],pos2[1], + paintGroup.circlePath(pos2[0],pos2[1],1,1)].join(" ") + }); + paintGroup.repaint(); + + + var x = e.clientX, y = e.clientY; + + if (!toEl.attributes.length) return; + for (var name, attList = [], i = 0, l = toEl.attributes.length; i < l; i++) { + if (ignoreToAtts.indexOf((name = toEl.attributes[i].name)) > -1) continue; + attList.push(new apf.item({ + caption: name + })); + } + + attMenu = new apf.menu({ + htmlNode : div, + id : "attMenu", + childNodes : attList + }); + + setTimeout(function(e){ + attMenu.display(x, y, true); + }); + + attMenu.addEventListener("mousedown", function(e) { + //apf.console.info("mousedown on attMenu"); + (e||event).cancelBubble = true; + }); + attMenu.addEventListener("mouseup", function(e) { + //apf.console.info("mouseup on attMenu"); + (e||event).cancelBubble = true; + }); + attMenu.addEventListener("itemclick", function(e) { + sel.$selectList(selection = [fromEl]); + apf.dragMode = true; //prevents selection + toAtt = e.value; + attMenu.setProperty("visible", false); + fromEl.setAttribute(fromAtt, "{" + toEl.id + "." + toAtt + "}"); + //_self.setMode("element"); + + paintLine.style({p:""}); // remove drawLine + + if (createConnections([fromEl])) + showConnections(); + + //apf.cancelBubble((e || event), attMenu); + }); + } + isDrawing = false; + + //_self.deactivate(); + } + + function updateDraw(e){ + apf.plane.hide(); + paintGroup.style({v:0}); + paintGroup.repaint(); + + var htmlNode = document.elementFromPoint(e.clientX, e.clientY); + var amlNode = apf.findHost(htmlNode); + if (amlNode && amlNode.editable && selection.indexOf(amlNode) == -1) { + htmlNode = amlNode.$ext; + var pos = apf.getAbsolutePosition(htmlNode); + paintRect.style({ + x: pos[0], + y: pos[1], + w: htmlNode.offsetWidth, + h: htmlNode.offsetHeight + }); + } + else { + paintRect.style({ + w: 0, h: 0 + }); + } + + var drawPath = []; + for (var i = 0, il = selection.length; i < il; i++) { + hNode = selection[i].$ext; + pos = apf.getAbsolutePosition(hNode); + var sx = ~~(pos[0] + (hNode.offsetWidth/2)), sy = ~~(pos[1] + (hNode.offsetHeight/2)), ex = e.clientX, ey = e.clientY; + drawPath.push("M",sx,sy,"L",ex,ey,paintGroup.circlePath(sx,sy,3,3),paintGroup.circlePath(ex,ey,3,3)); + } + paintLine.style({p: drawPath.join(" ")}); + + apf.plane.show(); + paintGroup.style({v:1}); + paintGroup.repaint(); + } + + document.onmousemove = function(e){ + if (!e) e = event; + + clearTimeout(timer); + if (lastTime && new Date().getTime() + - lastTime < apf.mouseEventBuffer) { + var z = { + clientX: e.clientX, + clientY: e.clientY + } + timer = setTimeout(function(){ + //@todo + }, 10); + return; + } + lastTime = new Date().getTime(); + + if(isDrawing) + updateDraw(e); + + } + + document.onmousedown = function(e){ + if (!e) e = event; + var amlNode; + + //clearTimeout(showAllTimer); + if (lineMode == "element") { +/* + if (selection.length) { + if (createConnections(selection)) { + paintGroup.style({v:0}); + paintGroup.repaint(); + + showConnections(); + } + } +*/ + } + else if (lineMode == "draw" && !fromEl) { + apf.plane.hide(); + var htmlNode = document.elementFromPoint(e.clientX, e.clientY); + amlNode = apf.findHost(htmlNode); + if (amlNode) { + //apf.console.info("mousedown in draw mode"); + fromEl = amlNode; + sel.$selectList(selection = [fromEl]); + + var x = e.clientX, y = e.clientY; + + if (!fromEl.attributes.length) return; + for (var name, attList = [], i = 0, l = fromEl.attributes.length; i < l; i++) { + if (ignoreFromAtts.indexOf((name = fromEl.attributes[i].name)) > -1) continue + attList.push(new apf.item({ + caption: name + })); + } + + attMenu = new apf.menu({ + htmlNode : div, + id : "attMenu", + childNodes : attList + }); + + setTimeout(function(e){ + attMenu.display(x, y, true); + }); + + attMenu.addEventListener("itemclick", function(e) { + sel.$selectList(selection = [fromEl]); + fromAtt = e.value; + attMenu.setProperty("visible", false); + isDrawing = true; + }); + } + + _self.setMode("draw-started"); + //debugger; + apf.plane.show(); + + + } else { + if (attMenu.visible) + attMenu.setProperty("visible", false); + if (fromEl) { + stopDraw(e); + } + } + }; + + document.onmouseup = function(e){ + // lets see if we should stop drawing + //if (!isDrawing) + //apf.dragMode = false; //prevents selection + } + + /*document.onkeydown = function(e) { + e = e || event; + + // Esc key + if (e.keyCode == 27) { + fromEl = toEl = fromAtt = toAtt = null; + lineMode = null; + _self.deactivate(); + } + }*/ + + // create new connection + function createConnection(el1, at1, targetList) { + if (!(el1.id && at1 && targetList.length)) return; + + var pos, x, y, w, h; + + // simple connection + if (targetList.length == 1) { + var el2 = targetList[0].el; + var at2 = targetList[0].at; + var from = { + x : (x=(pos=apf.getAbsolutePosition((hNode=el1.$ext)))[0]), + y : (y=pos[1]), + w : (w=hNode.offsetWidth), + h : (h=hNode.offsetHeight), + t : [Math.round(x+w/2), y], + b : [Math.round(x+w/2), y+h], + l : [x, Math.round(y+h/2)], + r : [x+w, Math.round(y+h/2)], + c : [Math.round(x+w/2), Math.round(y+h/2)] // center of element + } + var to = { + x : (x=(pos=apf.getAbsolutePosition((hNode=el2.$ext)))[0]), + y : (y=pos[1]), + w : (w=hNode.offsetWidth), + h : (h=hNode.offsetHeight), + t : [Math.round(x+w/2), y], + b : [Math.round(x+w/2), y+h], + l : [x, Math.round(y+h/2)], + r : [x+w, Math.round(y+h/2)], + c : [Math.round(x+w/2), Math.round(y+h/2)] // center of element + } + + // check + var conn = { + from : { + el : el1, + at : at1, + pos : from.c + }, + to : { + el : el2, + at : at2, + pos : to.c + } + } + + // set value + var val; + if (val = el2.getAttribute(at2)) { + conn.val = val; + } + else { + conn.val = "{"+el2.id+"."+at2+"}"; + conn.readonly = true; + } + + var fromId, toId + if (!connections) connections = {}; + if (!connections[(fromId=el1.id)]) connections[fromId] = {}; + if (!connections[fromId][toId]) + connections[fromId][toId] = [conn]; + else + connections[fromId][toId].push(conn); + } + // complex connection + else { + return; + } + } + + function drawConnections() { + connectionPath = []; + + // reset div + if (div) document.body.removeChild(div); + div = document.body.appendChild(document.createElement("div")); + div.style.display = "block"; + div.style.position = "absolute"; + div.style.left = "0px"; + div.style.top = "0px"; + div.style.width = width + "px"; + div.style.height = height + "px"; + + //div.onmousedown = div.onmouseup = function(e) { + //(e||event).cancelBubble = true; + //} + + div.style.zIndex = 100000001; + + for (var id in connections) { + for (var id2 in connections[id]) { + var curConnections = connections[id][id2]; + + for (var c, cType, connEdit, connDivs = [], maxBoxWidth = 0, maxLeftWidth = 0, centerPos, pos1, pos2, i = 0, l = curConnections.length; i < l; i++) { + // default positions for lines, start and end + pos1 = (c=curConnections[i]).from.pos; + pos2 = c.to.pos; + // calculate center of line + centerPos = [Math.round((pos1[0]+pos2[0])/2), Math.round((pos1[1]+pos2[1])/2)]; + + // set type of connection: simple, complex or readonly + if (c.readonly) + cType = "readonly"; + + connEdit = new connectEdit(div, c.from.el, c.to.el, c.from.at, c.to.at, c.val, cType); + div.appendChild(connEdit.$ext); + + + connections[id][id2][i].conn = connEdit; + + if (connEdit.getMaxLeftWidth() > maxLeftWidth) { + maxLeftWidth = connEdit.getMaxLeftWidth(); + //debugger; + } + if (connEdit.getMaxBoxWidth() > maxBoxWidth) { + maxBoxWidth = connEdit.getMaxBoxWidth(); + } + + connDivs.push(connEdit); + + // even number of connections + var linePadding = 4; + centerPos = (l % 2 == 0) + ? [centerPos[0] - (connEdit.$box.offsetWidth+10)/2, centerPos[1] - l/2*connEdit.$box.offsetHeight*i] + : centerPos = [centerPos[0] - (connEdit.$box.offsetWidth+10)/2, centerPos[1] - (l+1)/2*connEdit.$box.offsetHeight*(i+0.5)] + + centerPos[0] = (centerPos[0] > 0) ? centerPos[0] : 0; + centerPos[1] = (centerPos[1] > 0) ? centerPos[1] : 0; + + connEdit.$ext.style.display = "none"; + connEdit.$ext.style.top = centerPos[1] + "px"; + connEdit.$ext.style.left = centerPos[0] + "px"; + + // draw line + connectionPath.push( + //paintGroup.circlePath(pos1[0],pos1[1],1,1), + "M",pos1[0],pos1[1],"L",pos2[0],pos2[1], + paintGroup.circlePath(pos2[0],pos2[1],1,1) + ); + } + + // loop through the divs and align children properly + for (var c, container, spans, cDiv, i = 0, l = connDivs.length; i < l; i++) { + // make div visible + (c=connDivs[i]).$ext.style.display = "block"; + c.$fromBox.style.width = maxLeftWidth + 20 + "px"; + //c.$lblFromEl.style.width = (maxLeftWidth - c.$lblAttMenu.offsetWidth) + "px"; + c.$ext.style.width = maxBoxWidth + 90 + "px"; + } + } + } + } + }; + + this.deactivate = function(){ + if (!active) return; + //if (lineMode) return; + active = false; + + var selection; + if (selection = sel.$getNodeList()) + prevSelection = selection; + + document.onmousedown = + document.onmousemove = + document.onmouseup = null; + //document.onkeydown = null; + + apf.dragMode = false; + + apf.plane.hide(); + paintLine.style({p:""}); + paintConnections.style({p:""}); + paintRect.style({w:0,h:0}); + paintGroup.style({v:0}); + paintGroup.repaint(); + if (div) div.style.display = "none"; + if (div) document.body.removeChild(div); + }; +}; + + +function connectEdit(container, fromEl, toEl, fromAt, toAt, val, type){ + this.container = container; + this.fromEl = fromEl; + this.toEl = toEl; + this.fromAt = fromAt; + this.toAt = toAt; + this.value = val; + + this.type = type || "simple"; + + this.draw(); +}; + +(function(){ + this.ignoreFromAtts = ["id"]; // attributes for from element to ignore in attMenu + this.ignoreToAtts = ["for"]; // attributes for to elements to ignore in attMenu + + this.$getInput = function(){ + apf.Rename.initEditableArea.call(this); + + var txt = this.$txt; + txt.host = this; + return txt; + }; + + this.startEdit = function(el){ + htmlNode = this.$value; + var value = this.value; + + var txt = this.$getInput(); + + htmlNode.innerHTML = ""; + htmlNode.appendChild(txt); + + if (apf.hasContentEditable) { + txt.innerHTML = value.replace(/" || ""; + } + else + txt.value = value; + + txt.unselectable = "Off"; + + //this.$txt.focus(); + var f = function(){ + try { + txt.focus(); + txt.select(); + } + catch(e) {} + }; + if (apf.isIE) f() + else setTimeout(f); + + this.renaming = true; + }, + + this.stopRename = + this.stopEdit = function(x, success){ + if (!this.renaming) + return; + + this.renaming = false; + + var htmlNode = this.$value; + htmlNode.removeChild(this.$txt); + + var value = typeof success == "string" + ? success + : (apf.hasContentEditable + ? this.$txt.innerText + : this.$txt.value) + .replace(/<.*?nobr>/gi, "").replace(/\n$/, ""); //last replace is for chrome; + + if (success && this.value != value) { + this.setPropValue(value); + } + else { + if (htmlNode.nodeType == 1) + htmlNode.innerHTML = this.value; + else + htmlNode.nodeValue = this.value; + } + + htmlNode.parentNode.scrollLeft = 0; + } + + this.setPropName = function(name){ + this.fromAt = name; + this.$lblAttMenu.innerHTML = this.fromAt; + } + + this.setPropValue = function(value){ + this.value = value; + this.$lblVal.innerHTML = this.value; + } + + this.draw = function(name, prop, value){ + var _self = this; + + this.$ext = document.createElement("div"); + this.$ext.setAttribute("class", "atchart_box"); + this.$ext.setAttribute("style", "width:285px;color:#000000;font-family:Tahoma;font-size:12px;height:29px;min-width:30px;min-height:29px;max-height:29px;overflow:hidden;cursor:default;position:absolute;"); + + this.$ext.innerHTML = '
    Button.caption
    =""
    '; + this.$ext.onmousedown = this.$ext.onmouseup = function(e) { + (e||event).cancelBubble = true; + } + + var divs, spans; + // delBtn + this.$delBtn = (divs=this.$ext.getElementsByTagName("div"))[1]; + if (this.type == "readonly") this.$delBtn.style.display = "none"; + + + this.$box = divs[3]; + this.$fromBox = this.$box.getElementsByTagName("div")[0] + this.$lblFromEl = (spans=this.$fromBox.getElementsByTagName("span"))[0]; + this.$lblFromEl.innerHTML = this.fromEl.id + "."; + + this.$attMenu = spans[1]; + this.$lblAttMenu = this.$attMenu.firstChild; + this.setPropName(this.fromAt); + + if (this.type != "readonly") { + this.$delBtn.onmousedown = function(e) { + (e||event).cancelBubble = true; + } + this.$delBtn.onmouseup = function(e) { + _self.fromEl.setAttribute(_self.fromAt, ''); + apf.dispatchEvent("vcdelete", {el:_self.$ext}); + + (e||event).cancelBubble = true; + } + + this.$attMenu.onmousedown = function(e) { + (e||event).cancelBubble = true; + } + this.$attMenu.onmouseup = function(e) { + var e = e || event; + //debugger; + for (var name, attList = [], ai = 0, al = _self.fromEl.attributes.length; ai < al; ai++) { + if (_self.ignoreFromAtts.indexOf((name = _self.fromEl.attributes[ai].name)) > -1) continue; + attList.push(new apf.item({ + caption: name + })); + } + + attMenu = new apf.menu({ + htmlNode : _self.$ext, + id : "attMenu", + childNodes : attList + }); + + var pos = apf.getAbsolutePosition(this); + + var x = pos[0], y = pos[1] + this.offsetHeight; + setTimeout(function(){ + attMenu.display(x, y, true); + + //attMenu.display(x-attMenu.$ext.offsetWidth, y); + attMenu.$ext.style.left = x; + attMenu.$ext.style.zIndex = 100000002; + + // select attribute in menu + apf.popup.cache[apf.popup.last].content.onmousedown = function(e) { + var newAt = attMenu.$selected.caption; + + if (_self.fromAt == newAt) return; + // set new attr + attMenu.setProperty("visible", false); + + _self.fromEl.setAttribute(_self.fromAt, ""); + _self.setPropName(newAt); + _self.fromEl.setAttribute(newAt, _self.value); + + + (e||event).cancelBubble = true; + + _self.container.onmousedown = _self.container.onmouseup = function(e) { + _self.container.onmousedown = _self.container.onmouseup = null; + apf.dispatchEvent("vcpropchange", {obj:_self}); + (e || event).cancelBubble = true; + } + //document.onmouseup = null; + } + + }); + + (e||event).cancelBubble = true; + } + } + + this.$lblVal = this.$box.getElementsByTagName("label")[0]; + this.setPropValue(this.value); + + if (this.type != "readonly") + this.$inputVal = this.createInput(); + + if (this.type == "readonly") { + this.$ext.onmousedown = function(e) { + apf.dispatchEvent("vcmoveselection", {target: _self.toEl}); + (e||event).cancelBubble = true; + } + this.$ext.onmouseup = function(e) { + (e||event).cancelBubble = true; + } + + } + } + + this.createInput = function() { + var inputVal = document.createElement("input"); + inputVal.setAttribute("type", "text"); + inputVal.setAttribute("class", "section5"); + inputVal.setAttribute("value", this.$lblVal.innerHTML); + inputVal.style.width = (this.$lblVal.offsetWidth-2 > 0 ? this.$lblVal.offsetWidth-2 : 0) + "px"; + inputVal.onkeydown = inputVal.onkeypress = inputVal.onmousedown = inputVal.onmouseup = function(e) { + (e||event).cancelBubble = true; + } + inputVal.onblur = function() { + this.hasFocus = false; + this.onkeydown = this.onkeypress = this.onkeyup = null; + _self.$lblVal.setAttribute("value", this.value); + _self.$lblVal.getAttribute("el").setAttribute(lblVal.getAttribute("at"), this.value); + + if (createConnections(selection)) + showConnections(); + + (e||event).cancelBubble = true; + } + inputVal.onkeyup = function(e) { + if ((e||event).keyCode == 13) { + _self.$lblVal.setAttribute("value", "x"+this.value); + _self.$lblVal.getAttribute("el").setAttribute(lblVal.getAttribute("at"), this.value); + //debugger; + if (createConnections(selection)) + showConnections(); + } + (e||event).cancelBubble = true; + } + inputVal.onfocus = function(e) { + this.hasFocus = true; + } + inputVal.onmouseout = function(e) { + if (this.hasFocus) return; + $this.inputVal.replaceNode(lblVal, inputVal); + } + + return inputVal; + } + + this.getMaxLeftWidth = function() { + return this.$lblFromEl.offsetWidth + this.$lblAttMenu.offsetWidth; + } + this.getMaxBoxWidth = function() { + return this.$lblFromEl.offsetWidth + this.$lblAttMenu.offsetWidth + this.$lblVal.offsetWidth; + } + +}).call(connectEdit.prototype = new apf.Class()); + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/core/baseclasses/contenteditable/visualselect.js)SIZE(18159)TIME(Mon, 10 Jan 2011 10:53:11 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + + +apf.visualSelect = function(selection){ + this.$init(); + + var _self = this; + selection.addEventListener("update", function(){ + _self.updateSelection(); + }); + this.$selection = selection; +}; + +(function(){ + var nodes = [], + pos = ["n", "ne", "e", "se", "s", "sw", "w", "nw"], + div, oOutline, + inited = false + + function init(){ + while (nodes.length != 8) { + div = document.body.appendChild(document.createElement("div")); + div.className = "idegrabber"; + div.style.display = "none"; + div.onmousedown = mousedown; + div.self = this; + div.host = false; + + nodes.push(div); + nodes[(div.type = pos.pop())] = div; + } + + oOutline = document.body.appendChild(document.createElement("div")); + oOutline.className = "multiselect_container"; + oOutline.host = false; + oOutline.self = this; + + oOutline.onmouseup = + oOutline.onmousedown = function(e){ + if (!e) e = event; + + var prevTop = this.style.top; + this.style.top = "-10000px"; + var el = document.elementFromPoint(e.clientX, e.clientY); + this.style.top = prevTop; + apf.fireEvent(el, (e.type || e.name), e); + + e.cancelBubble = true; + } + + //@todo this should be cleaned up + /*var _self = this; + apf.window.addEventListener("focus", function(e){ + if (lastSelection && lastSelection.length) + _self.show(); + }); + apf.window.addEventListener("blur", function(e){ + _self.hide(); + });*/ + + inited = true; + } + + this.$getOutline = function(){ + return oOutline; + } + + var selected = [], anchors, + size = 7, + margin = -1; + + this.show = function(){ + if (!inited) + init.call(this); + + this.visible = true; + for (var i = 0; i < nodes.length; i++) + nodes[i].style.display = ""; + oOutline.style.display = ""; + + if (lastSelection) + this.updateGeo(); + }; + + this.hide = function(){ + if (!inited) + return; + + this.visible = false; + for (var i = 0; i < nodes.length; i++) + nodes[i].style.display = "none"; + oOutline.style.display = "none"; + }; + + this.$finishResize = function(noGeo){ + apf.setStyleClass(resizing, "", ["idegrabber_resizing"]); + resizing = false; + + if (lastSelection.length == 1) + this.updateGeo(false, true); + } + + var lastSelection = []; + this.updateSelection = function(){ + var nodes = this.$selection.$getNodeList(); + + if (!inited) + init.call(this); + + + for (var s, i = 0, l = lastSelection.length; i < l; i++) { + if (nodes.indexOf(s = lastSelection[i]) == -1) { + apf.layout.removeRule(s.$ext, "visualselect"); + apf.layout.activateRules(s.$ext); + } + } + for (i = 0, l = nodes.length; i < l; i++) { + if (lastSelection.indexOf(s = nodes[i]) == -1) { + apf.layout.setRules(s.$ext, "visualselect", "apf.all[" + + this.$uniqueId + "].updateGeo()", true); + apf.layout.queue(s.$ext); + } + } + + if (s.$ext.parentNode.tagName == "BODY") { + apf.layout.setRules(document.documentElement, "visualselect", + "apf.all[" + this.$uniqueId + "].updateGeo()", true); + apf.layout.queue(document.documentElement); + } + else { + apf.layout.removeRule(document.documentElement, "visualselect"); + apf.layout.activateRules(document.documentElement); + } + + lastSelection = nodes; + + if (!nodes.length) + return this.hide(); + else if (nodes.length > 1) + oOutline.innerHTML = Array(nodes.length + 1).join("
    "); + + this.updateGeo(); + + //Show + if (!this.visible) + this.show(); + } + + this.getLastSelection = function(){ + return lastSelection; + } + + var lastPos, txt, recursion; + this.updateGeo = function(force, onlyUpdateAnchors){ + if (recursion) + return; + + var selection = lastSelection; + if (!selection.length) + return; + + recursion = true; + + //Position + if (selection.length == 1) { + var sel = selection[0]; + if (!sel.parentNode) { + sel.$focusParent.focus(); + return (recursion = false); + } + + if (sel.$adding) + return (recursion = false); + + var anchors = selection.length == 1 + ? (sel.$anchors && sel.$anchors.length + ? sel.$anchors + : [sel.top, sel.right, sel.bottom, sel.left]) + : []; + + //!apf.dragMode && + if (anchors && !resizing && !this.renaming) { + var pel = sel.parentNode; + var name = pel.localName; + var exclPNode = "table|vbox|hbox".indexOf(name) > -1; + var fullDisabled = name == "vbox" && String(sel.height).indexOf("%") > -1 + || name == "hbox" && String(sel.width).indexOf("%") > -1; + + var pack = sel.align || pel.pack; + + apf.setStyleClass(nodes.n, fullDisabled || name == "table" + || name == "vbox" && pack == "start" || name == "hbox" + ? "idegrabber_disabled" + : (!exclPNode && (anchors[0] || anchors[0] === 0) + ? "idegrabber_selected" + : ""), ["idegrabber_selected", "idegrabber_disabled"]); + apf.setStyleClass(nodes.e, fullDisabled || name == "table" + || name == "hbox" && pack == "end" + ? "idegrabber_disabled" + : (!exclPNode && (anchors[1] || anchors[1] === 0) + ? "idegrabber_selected" + : ""), ["idegrabber_selected", "idegrabber_disabled"]); + apf.setStyleClass(nodes.s, fullDisabled + || name == "vbox" && pack == "end" + ? "idegrabber_disabled" + : (!exclPNode && (anchors[2] || anchors[2] === 0) + ? "idegrabber_selected" + : ""), ["idegrabber_selected", "idegrabber_disabled"]); + apf.setStyleClass(nodes.w, fullDisabled || name == "table" + || name == "vbox" || name == "hbox" && pack == "start" + ? "idegrabber_disabled" + : (!exclPNode && ( anchors[3] || anchors[3] === 0) + ? "idegrabber_selected" + : ""), ["idegrabber_selected", "idegrabber_disabled"]); + + apf.setStyleClass(nodes.nw, fullDisabled || name == "table" + || name == "vbox" && pack == "start" || name == "hbox" + ? "idegrabber_disabled" : "", ["idegrabber_disabled"]); + apf.setStyleClass(nodes.ne, fullDisabled || name == "table" + || name == "hbox" && pack == "end" + ? "idegrabber_disabled" : "", ["idegrabber_disabled"]); + apf.setStyleClass(nodes.sw, fullDisabled + || name == "vbox" && pack == "end" + ? "idegrabber_disabled" : "", ["idegrabber_disabled"]); + apf.setStyleClass(nodes.se, fullDisabled + ? "idegrabber_disabled" : "", ["idegrabber_disabled"]); + + this.$selection.dispatchEvent("update-content", {sel: sel}); + } + + var oHtml = sel.$ext;//this.$ext; + + oOutline.style.display = "none"; + + if (!oHtml.offsetParent || onlyUpdateAnchors) + return (recursion = false); //@error + + //oHtml.offsetParent.appendChild(oOutline); + if (apf.isIE) //@notice this solves an IE drawing bug + oHtml.parentNode.appendChild(txt || (txt = document.createTextNode(""))); + + var pos = apf.getAbsolutePosition(oHtml); + pos.push(oHtml.offsetWidth, oHtml.offsetHeight); + } + else { + var oHtml, opos, pos = [100000,100000,0,0]; + for (var i = 0; i < selection.length; i++) { + if (!selection[i].parentNode) { + this.$selection.removeRange(this.$selection.getRangeAt(i)); + return (recursion = false); + } + + oHtml = selection[i].$ext; + opos = apf.getAbsolutePosition(oHtml); //@notice in IE this calls onresize, make this function reentrant, fixed by using the recursion variable + + if (opos[0] < pos[0]) pos[0] = opos[0]; + if (opos[1] < pos[1]) pos[1] = opos[1]; + if (opos[0] + oHtml.offsetWidth > pos[2]) pos[2] = opos[0] + oHtml.offsetWidth; + if (opos[1] + oHtml.offsetHeight > pos[3]) pos[3] = opos[1] + oHtml.offsetHeight; + } + + if (!oHtml.offsetParent) + return (recursion = false); //@error + + //if (apf.isIE) //@notice this solves an IE drawing bug + oHtml.offsetParent.appendChild(oOutline); + //oHtml.parentNode.appendChild(txt || (txt = document.createTextNode(""))); + + pos[2] -= pos[0]; + pos[3] -= pos[1]; + + for (i = 0, l = nodes.length; i < l; i++) + apf.setStyleClass(nodes[i], "idegrabber_disabled", + ["idegrabber_selected", "idegrabber_disabled"]); + + var diff = apf.getDiff(oOutline), ppos = oHtml.offsetParent.tagName == "BODY" //@todo can we generalize this for ce2 use? + ? [0,0] + : apf.getAbsolutePosition(oHtml.offsetParent, null, true); + oOutline.style.left = (pos[0] - ppos[0]) + "px"; + oOutline.style.top = (pos[1] - ppos[1]) + "px"; + oOutline.style.width = Math.max(0, pos[2] - diff[0]) + "px"; + oOutline.style.height = Math.max(0, pos[3] - diff[1]) + "px"; + + oOutline.style.display = "block"; + + if (selection.length > 1) { + var ext, html, epos; + for (i = 0, l = selection.length; i < l; i++) { + ext = selection[i].$ext; + if (!ext.offsetWidth && !ext.offsetHeight) + continue; + epos = apf.getAbsolutePosition(ext); + html = oOutline.childNodes[i]; + html.style.left = (epos[0] - pos[0]) + "px"; + html.style.top = (epos[1] - pos[1]) + "px"; + html.style.width = (ext.offsetWidth - 2) + "px"; + html.style.height = (ext.offsetHeight - 2) + "px"; + } + } + } + + /*if (oOutline.parentNode.tagName == "BODY") { + var ppos = apf.getAbsolutePosition(document.documentElement, null, true); + ppos[0] += (apf.isIE ? 2 : 0); + ppos[1] += (apf.isIE ? 2 : 0); + } + else + var ppos = apf.getAbsolutePosition(oOutline.parentNode, null, true); + + lastPos = pos.slice(); + lastPos[0] -= ppos[0]; + lastPos[1] -= ppos[1]; + + var x, y, w, h, diff = apf.getDiff(oOutline); + oOutline.style.left = (x = lastPos[0]) + "px"; + oOutline.style.top = (y = lastPos[1]) + "px"; + oOutline.style.width = (w = Math.max(0, pos[2] - diff[0])) + "px"; + oOutline.style.height = (h = Math.max(0, pos[3] - diff[1])) + "px"; + + apf.config.setProperty("x", x); + apf.config.setProperty("y", y); + apf.config.setProperty("w", w); + apf.config.setProperty("h", h);*/ + + //Middle ones (hor) + nodes.s.style.left = + nodes.n.style.left = (pos[0] + (pos[2] - size)/2) + "px"; + + //Middle ones (ver) + nodes.e.style.top = + nodes.w.style.top = (pos[1] + (pos[3] - size)/2) + "px"; + + //Top + nodes.nw.style.top = + nodes.ne.style.top = + nodes.n.style.top = (pos[1] - size - margin) + "px"; + + //Left + nodes.sw.style.left = + nodes.nw.style.left = + nodes.w.style.left = (pos[0] - size - margin) + "px"; + + //Right + nodes.ne.style.left = + nodes.se.style.left = + nodes.e.style.left = (pos[0] + pos[2] + margin) + "px"; + + //Bottom + nodes.se.style.top = + nodes.sw.style.top = + nodes.s.style.top = (pos[1] + pos[3] + margin) + "px"; + + recursion = false; + }; + + var resizing, map = {"e":"right", "w":"left", "n":"top", "s":"bottom"}; + function mousedown(e){ + if (!e) e = event; + + var docsel = this.self.$selection; + if (e.button == 2 || docsel.rangeCount > 1) + return; + + var sel = docsel.$getFirstNode(); + var doc = docsel.$ownerDocument; + var name = sel.parentNode.localName; + var type = this.type; + + doc.$commands.begin.call(doc); + + if (e.ctrlKey && type.length == 1) { + if (sel.$anchors && sel.$anchors.length) { + var anchors = sel.$anchors; + sel.setAttribute("anchors", null); + sel.setAttribute("top", anchors[0]); + sel.setAttribute("right", anchors[1]); + sel.setAttribute("bottom", anchors[2]); + sel.setAttribute("left", anchors[3]); + } + + var value = sel[map[type]]; + apf.setStyleClass(this, !value && value !== 0 + ? "idegrabber_selected" : "", ["idegrabber_selected"]); + + var pHtmlNode = sel.$ext.offsetParent; + if (pHtmlNode.tagName == "BODY") + pHtmlNode = document.documentElement; + + var curWidth = sel.$ext.offsetWidth; + var curHeight = sel.$ext.offsetHeight; + + var prop = map[type]; + if (sel[prop] || sel[prop] === 0) { + if (prop == "right" && !sel.left && sel.left !== 0) + sel.setAttribute("left", apf.getHtmlLeft(sel.$ext)); + else if (prop == "bottom" && !sel.top && sel.top !== 0) + sel.setAttribute("top", apf.getHtmlTop(sel.$ext)); + else if (prop == "left" && !sel.right && sel.right !== 0) + sel.setAttribute("right", apf.getHtmlRight(sel.$ext)); + else if (prop == "top" && !sel.bottom && sel.bottom !== 0) + sel.setAttribute("bottom", apf.getHtmlBottom(sel.$ext)); + + sel.removeAttribute(prop); + } + else { + switch(type) { + case "e": + sel.setAttribute("right", apf.getHtmlRight(sel.$ext)); + break; + case "w": + sel.setAttribute("left", apf.getHtmlLeft(sel.$ext)); + break; + case "n": + sel.setAttribute("top", apf.getHtmlTop(sel.$ext)); + break; + case "s": + sel.setAttribute("bottom", apf.getHtmlBottom(sel.$ext)); + break; + } + } + + if (!sel.left && sel.left !== 0 || !sel.right && sel.right !== 0) + sel.setAttribute("width", curWidth); + else + sel.removeAttribute("width"); + + if (!sel.top && sel.right !== 0 || !sel.bottom && sel.bottom !== 0) + sel.setAttribute("height", curHeight); + else + sel.removeAttribute("height"); + + doc.$commands.commit.call(doc); + + this.self.updateGeo(); + } + else { + sel.$resizeStart(e || event, { + resizeType : type, + nocursor : true + }); + + resizing = this; + apf.setStyleClass(this, "idegrabber_resizing"); + } + } +}).call(apf.visualSelect.prototype = new apf.Class()); + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/core/window-o3.js)SIZE(5461)TIME(Mon, 10 Jan 2011 10:53:11 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/core/window.js)SIZE(50515)TIME(Mon, 10 Jan 2011 10:53:11 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + + +/** + * Object representing the window of the aml application. The semantic is + * similar to that of a window in the browser, except that this window is not + * the same as the javascript global object. It handles the focussing within + * the document and several other events such as exit and the keyboard events. + * + * @event blur Fires when the browser window looses focus. + * @event focus Fires when the browser window receives focus. + * + * @constructor + * @inherits apf.Class + * @default_private + * + * @author Ruben Daniels (ruben AT ajax DOT org) + * @version %I%, %G% + * @since 0.8 + */ +apf.window = function(){ + this.$uniqueId = apf.all.push(this); + this.apf = apf; + + /** + * Returns a string representation of this object. + */ + this.toString = function(){ + return "[apf.window]"; + }; + + /** + * Retrieves the primary {@link element.actiontracker action tracker} of the application. + */ + this.getActionTracker = function(){ + return this.$at + }; + + /** + * @private + */ + this.loadCodeFile = function(url){ + //if(apf.isWebkit) return; + if (self[url]) + apf.importClass(self[url], true, this.win); + else + apf.include(url);//, this.document); + }; + + + /** + * Flashes the task bar. This can be useful to signal the user that an + * important event has occured. Only works in internet explorer under + * certain conditions. + */ + this.flash = function(){ + if (apf.window.hasFocus() || apf.isIphone) + return; + + if (apf.isDeskrun) { + jdwin.Flash(); + } + else if (apf.isIE) { + if (!this.popup) + this.popup = window.createPopup(); + + if (apf.window.stopFlash) + return; + + state += "x" + + function doFlash(nopopup) { + if (apf.window.hasFocus()) + return; + + window.focus(); + + function doPopup() { + if (apf.window.hasFocus()) + return; + + this.popup.hide(); + this.popup.show(0, 0, 0, 0, document.body); + this.popup.document.write(""); + this.popup.document.focus(); + + clearInterval(this.flashTimer); + this.flashTimer = setInterval(function(){ + if (!apf.window.popup.isOpen + || !apf.window.popup.document.p.isOpen) { + clearInterval(apf.window.flashTimer); + + if (!apf.window.hasFocus()) { + apf.window.popup.hide(); + document.body.focus(); + state = "d"; + determineAction(); + } + //when faster might have timing error + } + }, 10); + } + + if (nopopup) + $setTimeout(function(){ + doPopup.call(apf.window) + }, 10); + else + doPopup.call(apf.window); + } + + if ("TEXTAREA|INPUT|SELECT".indexOf(document.activeElement.tagName) > -1) { + document.activeElement.blur(); + document.body.focus(); + apf.window.stopFlash = true; + $setTimeout(function(){ + doFlash.call(apf.window, true); + apf.window.stopFlash = false; + }, 10); + } + else { + doFlash.call(apf.window); + } + } + }; + + + /** + * Show the browser window. + */ + this.show = function(){ + if (apf.isDeskrun) + jdwin.Show(); + }; + + /** + * Hide the browser window. + */ + this.hide = function(){ + if (apf.isDeskrun) { + jdwin.Hide(); + } + else { + this.loaded = false; + if (this.win) + this.win.close(); + } + }; + + /** + * Focus the browser window. + */ + this.focus = function(){ + if (apf.isDeskrun) + jdwin.SetFocus(); + else + window.focus(); + }; + + /** + * Set the icon of the browser window. + * @param {String} url the location of the .ico file. + */ + this.setIcon = function(url){ + if (apf.isDeskrun) + jdwin.icon = parseInt(url) == url ? parseInt(url) : url; + }; + + /** + * Set the title of the browser window. + * @param {String} value the new title of the window. + */ + this.setTitle = function(value){ + this.title = value || ""; + + if (apf.isDeskrun) + jdwin.caption = value; + else + document.title = (value || ""); + }; + + /** + * @private + */ + this.loadAml = function(x){ + if (x[apf.TAGNAME] == "deskrun") + this.loadDeskRun(x); + /*else { + + }*/ + }; + + + var jdwin = apf.isDeskrun ? window.external : null, + jdshell = apf.isDeskrun ? jdwin.shell : null; + + /** + * @private + */ + this.loadDeskRun = function(q){ + jdwin.style = q.getAttribute("style") + || "ismain|taskbar|btn-close|btn-max|btn-min|resizable"; + + apf.config.drRegName = q.getAttribute("record"); + if (q.getAttribute("minwidth")) + jdwin.setMin(q.getAttribute("minwidth"), q.getAttribute("minheight")); + if (q.getAttribute("record") + && jdshell.RegGet(apf.config.drRegName + "/window")) { + var winpos = jdshell.RegGet(apf.config.drRegName + "/window"); + if (winpos) { + winpos = winpos.split(","); + window.external.width = Math.max(q.getAttribute("minwidth"), + Math.min(parseInt(winpos[2]), + window.external.shell.GetSysValue("deskwidth"))); + window.external.height = Math.max(q.getAttribute("minheight"), + Math.min(parseInt(winpos[3]), + window.external.shell.GetSysValue("deskheight"))); + window.external.left = Math.max(0, Math.min(parseInt(winpos[0]), + screen.width - window.external.width)); + window.external.top = Math.max(0, Math.min(parseInt(winpos[1]), + screen.height - window.external.height)); + } + } + else { + jdwin.left = q.getAttribute("left") || 200; + jdwin.top = q.getAttribute("top") || 200; + jdwin.width = q.getAttribute("width") || 800; + jdwin.height = q.getAttribute("height") || 600; + } + + jdwin.caption = q.getAttribute("caption") || "DeskRun"; + jdwin.icon = q.getAttribute("icon") || 100; + + var ct = $xmlns(q, "context", apf.ns.aml); + if (ct.length) { + ct = ct[0]; + if (!apf.config.tray) + apf.config.tray = window.external.CreateWidget("trayicon") + var tray = apf.config.tray; + + tray.icon = q.getAttribute("tray") || 100; + tray.tip = q.getAttribute("tooltip") || "DeskRun"; + tray.PopupClear(); + tray.PopupItemAdd("Exit", 3); + tray.PopupItemAdd("SEP", function(){}); + + var nodes = ct.childNodes; + for (var i = nodes.length - 1; i >= 0; i--) { + if (nodes[i].nodeType != 1) + continue; + + if (nodes[i][apf.TAGNAME] == "divider") { + tray.PopupItemAdd("SEP", function(){}); + } + else { + tray.PopupItemAdd(apf.queryValue(nodes[i], "."), + nodes[i].getAttribute("href") + ? new Function("window.open('" + nodes[i].getAttribute("href") + "')") + : new Function(nodes[i].getAttribute("onclick"))); + } + } + } + + jdwin.shell.debug = apf.debug ? 7 : 0; + jdwin.Show(); + jdwin.SetFocus(); + }; + + + /**** Focus Internals ****/ + + + this.vManager = new apf.visibilitymanager(); + + + + this.zManager = new apf.zmanager(); + + + + + this.$tabList = []; + + this.$addFocus = function(amlNode, tabindex, isAdmin){ + if (!isAdmin) { + amlNode.addEventListener("DOMNodeInserted", moveFocus); + amlNode.addEventListener("DOMNodeRemoved", removeFocus); + + if (amlNode.$isWindowContainer > -2) { + amlNode.addEventListener("focus", trackChildFocus); + amlNode.addEventListener("blur", trackChildFocus); + + amlNode.$focusParent = amlNode; + + if (amlNode.$isWindowContainer > -1) { + if (!amlNode.$tabList) + amlNode.$tabList = [amlNode]; + + this.$tabList.push(amlNode); + return; + } + else { + amlNode.$tabList = [amlNode]; + } + } + } + + var fParent = findFocusParent(amlNode), + list = fParent.$tabList; + + + if (list[tabindex]) { + apf.console.warn("Aml node already exist for tabindex " + tabindex + + ". Will insert " + amlNode.tagName + " [" + + (amlNode.name || "") + "] before existing one"); + } + + + if (!amlNode.$isWindowContainer) + amlNode.$focusParent = fParent; + else + amlNode.$focusParent2 = fParent; + + if (list[tabindex]) + list.insertIndex(amlNode, tabindex); + else + list.push(amlNode); + }; + + this.$removeFocus = function(amlNode){ + if (!amlNode.$focusParent) + return; + + amlNode.$focusParent.$tabList.remove(amlNode); + + if (!amlNode.$isWindowContainer) { + amlNode.removeEventListener("DOMNodeInserted", moveFocus); + amlNode.removeEventListener("DOMNodeRemoved", removeFocus); + } + + if (amlNode.$isWindowContainer > -2) { + amlNode.removeEventListener("focus", trackChildFocus); + amlNode.removeEventListener("blur", trackChildFocus); + } + }; + + var focusLoopDetect; + this.$focus = function(amlNode, e, force){ + var aEl = this.document.activeElement; + if (aEl == amlNode && !force) + return; //or maybe when force do $focus + + + var hadAlreadyFocus = aEl == amlNode; + + + this.$settingFocus = amlNode; + + if (!e) + e = {}; + + e.toElement = amlNode; + e.fromElement = aEl; + + if (aEl && aEl != amlNode && focusLoopDetect != aEl) { + focusLoopDetect = aEl; + + aEl.blur(true, e); + + + + if (focusLoopDetect != aEl) + return false; + } + + (apf.activeElement = this.document.activeElement = amlNode).focus(true, e); + + this.$settingFocus = null; + + apf.dispatchEvent("movefocus", { + toElement : amlNode + }); + + + + + if (!hadAlreadyFocus) + apf.console.info("Focus given to " + amlNode.localName + + " [" + (amlNode.name || "") + "]"); + + + + if (typeof apf.offline != "undefined" && apf.offline.state.enabled + && apf.offline.state.realtime) + apf.offline.state.set(this, "focus", amlNode.name || amlNode.$uniqueId); + + }; + + this.$blur = function(amlNode){ + var aEl = this.document.activeElement; + if (aEl != amlNode) + return false; + + + apf.console.info(aEl.localName + " [" + + (aEl.name || "") + "] was blurred."); + + + aEl.$focusParent.$lastFocussed = null; + apf.activeElement = this.document.activeElement = null; + + apf.dispatchEvent("movefocus", { + fromElement : amlNode + }); + + + }; + + var lastFocusParent; + + this.$focusDefault = function(amlNode, e){ + var fParent = findFocusParent(amlNode); + this.$focusLast(fParent, e); + }; + + this.$focusRoot = function(e){ + var docEl = apf.document.documentElement; + if (this.$focusLast(docEl, e) === false) { + //docEl.$lastFocussed = null; + //this.moveNext(null, apf.document.documentElement, true, e); + } + }; + + this.$focusLast = function(amlNode, e, ignoreVisible){ + var lf = amlNode.$lastFocussed; + + if (lf && lf.parentNode && lf.$focussable === true + && (ignoreVisible || lf.$ext.offsetHeight)) { + this.$focus(lf, e, true); + } + else { //Let's find the object to focus first + var next, node = amlNode, skip; + while (node) { + if (!skip && node.focussable !== false && node.$focussable === true && !node.$tabList + && (ignoreVisible || node.$ext && node.$ext.offsetHeight) && node.disabled < 1) { + this.$focus(node, e, true); + break; + } + + //Walk sub tree + if ((next = !skip && node.firstChild || !(skip = false) && node.nextSibling)) { + node = next; + if (node.$isWindowContainer > 0) + skip = true; + } + else if (node == amlNode) { + if (node.$isWindowContainer) + this.$focus(node, e, true); + return; + } + else { + do { + node = node.parentNode; + } while (node && !node.nextSibling && node != amlNode + && !node.$isWindowContainer) + + if (node == amlNode) { + if (node.$isWindowContainer) + this.$focus(node, e, true); + return; //do nothing + } + + if (node) { + if (node.$isWindowContainer) { + this.$focus(node, e, true); + break; + } + + node = node.nextSibling; + } + } + } + + if (!node) + this.$focus(apf.document.documentElement);//return false;// + + /*@todo get this back from SVN + var node, list = amlNode.$tabList; + for (var i = 0; i < list.length; i++) { + node = list[i]; + if (node.focussable !== false && node.$focussable === true + && (ignoreVisible || node.$ext.offsetHeight)) { + this.$focus(node, e, true); + return; + } + } + + this.$focus(apf.document.documentElement);*/ + } + }; + + function trackChildFocus(e){ + if (e.name == "blur") { + if (e.srcElement != this && this.$blur) + this.$blur(); + return; + } + + if (e.srcElement != this && this.$focus && (!e || !e.mouse || this.$focussable == apf.KEYBOARD_MOUSE)) + this.$focus(); + + if (e.srcElement == this || e.trackedChild) { + e.trackedChild = true; + return; + } + + this.$lastFocussed = e.srcElement; + + if (this.localName && this.localName.indexOf("window") > -1) + e.trackedChild = true; + } + + function findFocusParent(amlNode){ + var node = amlNode; + do { + node = node.parentNode; + } while(node && !node.$isWindowContainer); + //(!node.$focussable || node.focussable === false) + + return node || apf.document.documentElement; + } + + //Dom handler + //@todo make this look at the dom tree insertion point to determine tabindex + function moveFocus(e){ + if (e && e.currentTarget != this) + return; + + if (this.$isWindowContainer) + apf.window.$tabList.pushUnique(this); + else + apf.window.$addFocus(this, this.tabindex, true) + } + + //Dom handler + function removeFocus(e){ + if (e && (e.currentTarget != this || e.$doOnlyAdmin)) + return; + + //@todo apf3.0 this should be fixed by adding domremovenode events to all children + var list = this.$focusParent.$tabList; + var nodes = this.childNodes; + for (var i = 0, l = nodes.length; i < l; i++) { + list.remove(nodes[i]); //@todo assuming no windows here + } + + if (apf.document.activeElement == this) + apf.window.moveNext(); + + if (this.$isWindowContainer) { + apf.window.$tabList.remove(this); //@todo this can't be right + return; + } + + if (!this.$focusParent) + return; + + list.remove(this); + //this.$focusParent = null; //@experimental to not execute this + } + + /**** Focus API ****/ + + /** + * Determines whether a given aml element has the focus. + * @param {AMLElement} the element to check + * @returns {Boolean} whether the element has focus. + */ + this.hasFocus = function(amlNode){ + return this.document.activeElement == amlNode; + }; + + /** + * @private + */ + this.moveNext = function(shiftKey, relObject, switchWindows, e){ + if (switchWindows && apf.document.activeElement) { + var p = apf.document.activeElement.$focusParent; + if (p.visible && p.modal) + return false; + } + + var dir, start, next, + amlNode = relObject || apf.document.activeElement, + fParent = amlNode + ? (switchWindows && amlNode.$isWindowContainer + && amlNode.$isWindowContainer != -1 + ? apf.window + : e && e.innerList ? amlNode.$focusParent : amlNode.$focusParent2 || amlNode.$focusParent) + : apf.document.documentElement, + list = fParent.$tabList; + + if (amlNode && (switchWindows || amlNode != apf.document.documentElement)) { + start = (list || []).indexOf(amlNode); + if (start == -1) { + + apf.console.warn("Moving focus from element which isn't in the list\ + of it's parent. This should never happen."); + + + return; + } + } + else { + start = -1; + } + + if (this.document.activeElement && this.document.activeElement == amlNode + && list.length == 1 || list.length == 0) + return false; + + dir = (shiftKey ? -1 : 1); + next = start; + if (start < 0) + start = 0; + do { + next += dir; + + if (next >= list.length) + next = 0; + else if (next < 0) + next = list.length - 1; + + if (start == next && amlNode) { + if (list[0].$isWindowContainer) + this.$focus(list[0], e); + + return false; //No visible enabled element was found + } + + amlNode = list[next]; + } + while (!amlNode + || amlNode.disabled > 0 + || amlNode == apf.document.activeElement + || (switchWindows ? !amlNode.visible : amlNode.$ext && !amlNode.$ext.offsetHeight) + || amlNode.focussable === false + || switchWindows && !amlNode.$tabList.length); + + if (fParent == apf.window && amlNode.$isWindowContainer != -2) { + this.$focusLast(amlNode, {mouse:true}, switchWindows); + } + else { + (e || (e = {})).shiftKey = shiftKey; + this.$focus(amlNode, e); + } + + + }; + + /** + * @private + */ + this.focusDefault = function(){ + + if (typeof apf.offline != "undefined" && apf.offline.state.enabled) { + var node, id = apf.offline.state.get(this, "focus"); + + if (id == -1) + return this.$focusRoot(); + + if (id) + node = self[id] || apf.lookup(id); + + if (node) { + if (!node.$focussable) { + + apf.console.warn("Invalid offline state detected. The " + + "application was probably changed in " + + "between sessions. Resetting offline state " + + "and rebooting."); + + + apf.offline.clear(); + apf.offline.reboot(); + } + else { + this.$focus(node); + return; + } + } + } + + + if (this.moveNext() === false) + this.moveNext(null, apf.document.documentElement, true) + }; + + + + /**** Set Window Events ****/ + + apf.addListener(window, "beforeunload", function(){ + return apf.dispatchEvent("exit"); + }); + + //@todo apf3.x why is this loaded twice + apf.addListener(window, "unload", function(){ + if (!apf) + return; + + apf.window.isExiting = true; + apf.window.destroy(); + }); + + + + var timer, state = "", last = ""; + this.$focusfix = function(){ + + if (apf.isIphone) return; + + state += "a"; + clearTimeout(timer); + $setTimeout("window.focus();"); + timer = $setTimeout(determineAction); + }; + + this.$focusfix2 = function(){ + + if (apf.isIphone) return; + + state += "b"; + clearTimeout(timer); + timer = $setTimeout(determineAction); + }; + + this.$blurfix = function(){ + + if (apf.isIphone) return; + + state += "c"; + clearTimeout(timer); + timer = $setTimeout(determineAction); + }; + + function determineAction(){ + clearTimeout(timer); + + //apf.console.info(state); + if (state == "e" || state == "c" + || state.charAt(0) == "x" && !state.match(/eb$/) + || state == "ce" || state == "de") { //|| state == "ae" + if (last != "blur") { + last = "blur"; + apf.window.dispatchEvent("blur"); + //apf.console.warn("blur"); + } + } + else { + if (last != "focus") { + last = "focus"; + apf.window.dispatchEvent("focus"); + //apf.console.warn("focus"); + } + } + + state = ""; + timer = null; + } + + apf.addListener(window, "focus", this.$focusevent = function(){ + + if (apf.isIphone) + return apf.window.dispatchEvent("focus"); + + if (apf.hasFocusBug) { + state += "d"; + clearTimeout(timer); + timer = $setTimeout(determineAction); + } + else { + clearTimeout(iframeFixTimer) + iframeFix.newState = "focus"; + //apf.console.warn("win-focus"); + iframeFixTimer = $setTimeout(iframeFix, 10); + } + }); + + apf.addListener(window, "blur", this.$blurevent = function(){ + if (!apf) return; + + + if (apf.isIphone) + return apf.window.dispatchEvent("blur"); + + if (apf.hasFocusBug) { + state += "e"; + clearTimeout(timer); + timer = $setTimeout(determineAction); + } + else { + clearTimeout(iframeFixTimer) + iframeFix.newState = "blur"; + //apf.console.warn("win-blur"); + iframeFixTimer = $setTimeout(iframeFix, 10); + } + }); + + var iframeFixTimer; + function iframeFix(){ + clearTimeout(iframeFixTimer); + + var newState = iframeFix.newState; + if (last == newState) + return; + + last = newState; + + apf.dispatchEvent(last); + //apf.console.warn(last); + } + + this.hasFocus = function(){ + return (last == "focus"); + }; + + + + /**** Keyboard and Focus Handling ****/ + + apf.addListener(document, "contextmenu", function(e){ + if (!e) + e = event; + + + var pos, ev, + amlNode = apf.findHost(e.srcElement || e.target) + || apf.document.activeElement + || apf.document && apf.document.documentElement; + + if (amlNode && amlNode.localName == "menu") //The menu is already visible + return false; + + + //if (amlNode && amlNode.localName == "menu") + //amlNode = amlNode.parentNode; + + if (apf.contextMenuKeyboard) { + if (amlNode) { + pos = amlNode.selected + ? apf.getAbsolutePosition(amlNode.$selected) + : apf.getAbsolutePosition(amlNode.$ext || amlNode.$pHtmlNode); + } + else { + pos = [0, 0]; + } + + ev = { + x : pos[0] + 10 + document.documentElement.scrollLeft, + y : pos[1] + 10 + document.documentElement.scrollTop, + amlNode : amlNode, + htmlEvent : e + } + } + else { + if (e.htmlEvent) { + ev = e; + } + else { + ev = { //@todo probably have to deduct the border of the window + x : e.clientX + document.documentElement.scrollLeft, + y : e.clientY + document.documentElement.scrollTop, + htmlEvent : e + } + } + } + + ev.bubbles = true; //@todo discuss this, are we ok with bubbling? + + apf.contextMenuKeyboard = null; + + if ((amlNode || apf).dispatchEvent("contextmenu", ev) === false + || ev.returnValue === false) { + if (e.preventDefault) + e.preventDefault(); + return false; + } + + + if (apf.config.disableRightClick) { + if (e.preventDefault) + e.preventDefault(); + return false; + } + }); + + apf.addListener(document, "mouseup", function(e){ + if (!e) e = event; + + apf.dispatchEvent("mouseup", { + htmlEvent : e + }); + }); + + var ta = {"INPUT":1, "TEXTAREA":1, "SELECT":1}; + apf.addListener(document, "mousedown", this.$mousedown = function(e){ + if (!e) e = event; + var p, + amlNode = apf.findHost(e.srcElement || e.target); + /*cEditable = amlNode && amlNode.liveedit + + || (amlNode && amlNode.hasFeature(apf.__LIVEEDIT__)) + + ;*/ + + + if (apf.popup.last && (!amlNode || apf.popup.last != amlNode.$uniqueId) + && apf.popup.cache[apf.popup.last] + && !apf.isChildOf(apf.popup.cache[apf.popup.last].content, e.srcElement || e.target, true)) + apf.popup.forceHide(); + + + if (amlNode === false) + amlNode = apf.document.activeElement; + + + //Make sure the user cannot leave a modal window + if ((!amlNode || ((!amlNode.$focussable || amlNode.focussable === false) + && amlNode.canHaveChildren != 2 && !amlNode.$focusParent)) + && apf.config.allowBlur) { + lastFocusParent = null; + if (apf.document.activeElement) + apf.document.activeElement.blur(); + } + else if (amlNode) { //@todo check this for documentElement apf3.0 + if ((p = apf.document.activeElement + && apf.document.activeElement.$focusParent || lastFocusParent) + && p.visible && p.modal && amlNode.$focusParent != p + && amlNode.$isWindowContainer != -1) { + apf.window.$focusLast(p, {mouse: true, ctrlKey: e.ctrlKey}); + } + else if (!amlNode && apf.document.activeElement) { + apf.window.$focusRoot(); + } + else if (amlNode.$isWindowContainer == -1) { + if (amlNode.$tabList.length) + apf.window.moveNext(null, amlNode.$tabList[0], null, {mouse: true, innerList: true}); + else + apf.window.$focus(amlNode); + } + else if ((amlNode.disabled == undefined || amlNode.disabled < 1) + && amlNode.focussable !== false) { + if (amlNode.$focussable) { // === apf.KEYBOARD_MOUSE + apf.window.$focus(amlNode, {mouse: true, ctrlKey: e.ctrlKey}); + } + else if (amlNode.canHaveChildren == 2) { + if (!apf.config.allowBlur || !apf.document.activeElement + || apf.document.activeElement.$focusParent != amlNode) + apf.window.$focusLast(amlNode, {mouse: true, ctrlKey: e.ctrlKey}); + } + else { + if (!apf.config.allowBlur || amlNode != apf.document.documentElement) + apf.window.$focusDefault(amlNode, {mouse: true, ctrlKey: e.ctrlKey}); + } + } + else { + apf.window.$focusDefault(amlNode, {mouse: true, ctrlKey: e.ctrlKey}); + } + + + if (apf.hasFocusBug) { + var isTextInput = (ta[e.srcElement.tagName] + || e.srcElement.isContentEditable) && !e.srcElement.disabled + || amlNode.$isTextInput + && amlNode.$isTextInput(e) && amlNode.disabled < 1; + + if (!amlNode || !isTextInput) + apf.window.$focusfix(); + } + else if (!last) { + apf.window.$focusevent(); + } + + } + + + apf.dispatchEvent("mousedown", { + htmlEvent : e, + amlNode : amlNode || apf.document.documentElement + }); + + //Non IE/ iPhone selection handling + if (apf.isIE || apf.isIphone) + return; + + var canSelect = !((!apf.document + && (!apf.isParsingPartial || amlNode) + || apf.dragMode) && !ta[e.target.tagName]); + + if (canSelect && amlNode) { + if (!e.target && e.srcElement) + e.target = {}; + var isTextInput = (ta[e.target.tagName] + || e.target.contentEditable == "true") && !e.target.disabled //@todo apf3.0 need to loop here? + || amlNode.$isTextInput + && amlNode.$isTextInput(e) && amlNode.disabled < 1; + + //(!amlNode.canHaveChildren || !apf.isChildOf(amlNode.$int, e.srcElement)) + if (!apf.config.allowSelect && !isTextInput + && amlNode.nodeType != amlNode.NODE_PROCESSING_INSTRUCTION + && !amlNode.textselect) //&& (!amlNode.$int || amlNode.$focussable) //getElementsByTagNameNS(apf.ns.xhtml, "*").length + canSelect = false; + } + + if (!canSelect && e.button != 2) { // && !cEditable + if (e.preventDefault) + e.preventDefault(); + + try{ + if (document.activeElement && document.activeElement.contentEditable == "true") //@todo apf3.0 need to loop here? + document.activeElement.blur(); + }catch(e){} + } + }); + + //IE selection handling + apf.addListener(document, "selectstart", function(e){ + if (!e) e = event; + + var amlNode = apf.findHost(e.srcElement); + var canSelect = !(!apf.document + && (!apf.isParsingPartial || amlNode) + || apf.dragMode); + + if (canSelect) { + //(!amlNode.canHaveChildren || !apf.isChildOf(amlNode.$int, e.srcElement)) + if (!apf.config.allowSelect + && (amlNode && amlNode.nodeType != amlNode.NODE_PROCESSING_INSTRUCTION + && !amlNode.textselect)) //&& !amlNode.$int // getElementsByTagNameNS(apf.ns.xhtml, "*").length + canSelect = false; + } + + if (!canSelect) { + e.returnValue = false; + return false; + } + }); + + // Keyboard forwarding to focussed object + apf.addListener(document, "keyup", this.$keyup = function(e){ + if (!e) e = event; + + + var ev = { + keyCode : e.keyCode, + ctrlKey : e.ctrlKey, + shiftKey : e.shiftKey, + altKey : e.altkey, + htmlEvent: e, + bubbles : true //@todo is this much slower? + }; + + var aEl = apf.document && apf.document.activeElement; + if ((aEl && !aEl.disableKeyboard + ? aEl.dispatchEvent("keyup", ev) + : apf.dispatchEvent("keyup", ev)) === false) { + apf.preventDefault(e); + return false; + } + + }); + + + var wheel = this.$mousewheel = function wheel(e) { + if (!e) + e = event; + + var delta = null; + if (e.wheelDelta) { + delta = e.wheelDelta / 120; + if (apf.isOpera) + delta *= -1; + } + else if (e.detail) { + delta = -e.detail / 3; + } + + if (delta !== null) { + //Fix for scrolling too much + if (apf.isIE) { + var el = e.srcElement || e.target; + while (el && el.scrollHeight <= el.offsetHeight) + el = el.parentNode || el.$parentNode; + + if (el && el.nodeType == 9) + el = el.documentElement; + + if (!el || el.nodeType != 1) return; + + if (el && el.tagName == "BODY" && "auto|scroll".indexOf(apf.getStyle(el, "overflowY")) == -1) + el = document.documentElement; + + if (el && "auto|scroll".indexOf(apf.getStyle(el, "overflowY")) > -1) { + var max, dist = 0.35 * el.offsetHeight * delta; + if (delta < 0) { + if (el && el.scrollTop >= (max = el.scrollHeight - el.offsetHeight + apf.getVerBorders(el)) + dist) { + el.scrollTop = max; + e.returnValue = false; + } + } + else { + if (el && el.scrollTop <= dist) { + el.scrollTop = 0; + e.returnValue = false; + } + } + } + } + + var ev = { + delta : delta, + target : e.target || e.srcElement, + button : e.button, + ctrlKey : e.ctrlKey, + shiftKey : e.shiftKey, + altKey : e.altKey, + bubbles : true, + htmlEvent : e + }; + + var amlNode = apf.findHost(e.srcElement || e.target); + var res = (amlNode || apf).dispatchEvent("mousescroll", ev); + if (res === false || ev.returnValue === false) { + if (e.preventDefault) + e.preventDefault(); + + e.returnValue = false; + } + } + } + + if (document.addEventListener) + document.addEventListener('DOMMouseScroll', wheel, false); + + window.onmousewheel = + document.onmousewheel = wheel; //@todo 2 keer events?? + + + //var browserNavKeys = {32:1,33:1,34:1,35:1,36:1,37:1,38:1,39:1,40:1} + + apf.addListener(document, "keyup", function(e){ + e = e || event; + + if (e.ctrlKey && e.keyCode == 9 && apf.document.activeElement) { + var w = apf.document.activeElement.$focusParent; + if (w.modal) { + if (e.preventDefault) + e.preventDefault(); + return false; + } + + apf.window.moveNext(e.shiftKey, + apf.document.activeElement.$focusParent, true); + + w = apf.document.activeElement.$focusParent; + if (w && w.bringToFront) + w.bringToFront(); + + if (e.preventDefault) + e.preventDefault(); + return false; + } + }); + + //@todo optimize this function + apf.addListener(document, "keydown", this.$keydown = function(e){ + e = e || event; + + + if (e.keyCode == 120 || e.ctrlKey && e.altKey && e.keyCode == 68) { + apf.$debugwin.activate(); + } + + + + if (e.keyCode == 93) + apf.contextMenuKeyboard = true; + + + var amlNode = apf.document.activeElement, //apf.findHost(e.srcElement || e.target), + htmlNode = (e.explicitOriginalTarget || e.srcElement || e.target), + isTextInput = (ta[htmlNode.tagName] + || htmlNode.contentEditable || htmlNode.contentEditable == "true") //@todo apf3.0 need to loop here? + && !htmlNode.disabled + || amlNode && amlNode.$isTextInput + && amlNode.$isTextInput(e) && amlNode.disabled < 1; + + + //@todo move this to appsettings and use with_hotkey + var o, + ctrlKey = apf.isMac ? e.metaKey : e.ctrlKey; + if (!isTextInput && apf.config.undokeys && ctrlKey) { + //Ctrl-Z - Undo + if (e.keyCode == 90) { + o = apf.document.activeElement; + while (o && !o.getActionTracker && !o.$at) + o = o.parentNode; + if (!o) o = apf.window; + (o.$at || o.getActionTracker()).undo(); + } + //Ctrl-Y - Redo + else if (e.keyCode == 89) { + o = apf.document.activeElement; + while (o && !o.getActionTracker && !o.$at) + o = o.parentNode; + if (!o) o = apf.window; + (o.$at || o.getActionTracker()).redo(); + } + } + + + var eInfo = { + ctrlKey : e.ctrlKey, + metaKey : e.metaKey, + shiftKey : e.shiftKey, + altKey : e.altKey, + keyCode : e.keyCode, + htmlEvent : e, + isTextInput: isTextInput, + bubbles : true + }; + + delete eInfo.currentTarget; + + //Keyboard forwarding to focussed object + var aEl = amlNode; //isTextInput ? amlNode : + if ((aEl && !aEl.disableKeyboard && !aEl.editable + ? aEl.dispatchEvent("keydown", eInfo) + : apf.dispatchEvent("keydown", eInfo)) === false) { + apf.stopEvent(e); + if (apf.canDisableKeyCodes) { + try { + e.keyCode = 0; + } + catch(e) {} + } + return false; + } + + //Focus handling + else if ((!apf.config.disableTabbing || apf.document.activeElement) && e.keyCode == 9) { + //Window focus handling + if (e.ctrlKey && apf.document.activeElement) { + var w = apf.document.activeElement.$focusParent; + if (w.modal) { + if (e.preventDefault) + e.preventDefault(); + return false; + } + + apf.window.moveNext(e.shiftKey, + apf.document.activeElement.$focusParent, true); + + w = apf.document.activeElement.$focusParent; + if (w && w.bringToFront) + w.bringToFront(); + } + //Element focus handling + else if(!apf.document.activeElement || apf.document.activeElement.tagName != "menu") { + apf.window.moveNext(e.shiftKey); + } + + if (e.preventDefault) + e.preventDefault(); + return false; + } + + + //Disable backspace behaviour triggering the backbutton behaviour + var altKey = apf.isMac ? e.metaKey : e.altKey; + if (apf.config.disableBackspace + && e.keyCode == 8// || (altKey && (e.keyCode == 37 || e.keyCode == 39))) + && !isTextInput) { + if (apf.canDisableKeyCodes) { + try { + e.keyCode = 0; + } + catch(e) {} + } + e.returnValue = false; + } + + //Disable space behaviour of scrolling down the page + /*if(Application.disableSpace && e.keyCode == 32 && e.srcElement.tagName.toLowerCase() != "input"){ + e.keyCode = 0; + e.returnValue = false; + }*/ + + //Disable F5 refresh behaviour + if (apf.config.disableF5 && (e.keyCode == 116 || e.keyCode == 117)) { + if (apf.canDisableKeyCodes) { + try { + e.keyCode = 0; + } + catch(e) {} + } + else { + e.preventDefault(); + e.stopPropagation(); + } + //return false; + } + + + /*if (browserNavKeys[e.keyCode] && apf.document.activeElement + && apf.config.autoDisableNavKeys) + e.returnValue = false;*/ + + if (e.keyCode == 27) + e.returnValue = false; + + if (!apf.config.allowSelect + && e.shiftKey && (e.keyCode > 32 && e.keyCode < 41) + && !isTextInput) { + e.returnValue = false; + } + + //apf.dispatchEvent("keydown", null, eInfo); + + if (e.returnValue === false && e.preventDefault) + e.preventDefault(); + + return e.returnValue; + + }); + + apf.document = {}; + this.init = function(strAml){ + + if (apf.actiontracker) { + this.$at = new apf.actiontracker(); + this.$at.name = "default"; + + apf.nameserver.register("actiontracker", "default", this.$at); + + } + + + this.undoManager = new apf.actiontracker(); + + + + + apf.console.info("Start parsing main application"); + + + apf.Latometer.start(); + + + //Put this in callback in between the two phases + + /*XForms and lazy devs support + if (!nodes.length && !apf.skins.skins["default"] && apf.autoLoadSkin) { + apf.console.warn("No skin file found, attempting to autoload the \ + default skin file: skins.xml"); + apf.loadAmlInclude(null, doSync, "skins.xml", true); + }*/ + + + this.$domParser = new apf.DOMParser(); + this.document = apf.document = this.$domParser.parseFromString(strAml, + "text/xml", { + + timeout : apf.config.initdelay, + + callback : function(doc){ + //@todo apf3.0 + + //Call the onload event (prevent recursion) + if (apf.parsed != 2) { + //@todo apf3.0 onload is being called too often + var inital = apf.parsed; + apf.parsed = 2; + apf.dispatchEvent("parse", { //@todo apf3.0 document + initial : inital + }); + apf.parsed = true; + } + + if (!apf.loaded) { + + if (apf.isDeskrun) + apf.window.deskrun.Show(); + + + + //Set the default selected element + if (!apf.document.activeElement && (!apf.config.allowBlur + || apf.document.documentElement + && apf.document.documentElement.editable)) + apf.window.focusDefault(); + + + apf.loaded = true; + $setTimeout(function() { + apf.dispatchEvent("load"); + apf.addEventListener("$event.load", function(cb){ + cb(); + }); + }); + } + + //END OF ENTIRE APPLICATION STARTUP + + + apf.console.info("Initialization finished"); + + + + apf.Latometer.end(); + apf.Latometer.addPoint("Total load time"); + apf.Latometer.start(true); + + } + }); //async + }; + + + var lastFocusElement; + this.addEventListener("focus", function(e){ + if (!apf.document.activeElement && lastFocusParent && !apf.isIphone) { + lastFocusElement.focus(); + /* + if (lastFocusParent.$isWindowContainer < 0) { + if (lastFocusParent.$tabList.length) + apf.window.moveNext(null, lastFocusParent.$tabList[0]); + else + apf.window.$focus(lastFocusParent); + } + else + apf.window.$focusLast(lastFocusParent);*/ + } + }); + this.addEventListener("blur", function(e){ + if (!apf.document.activeElement || apf.isIphone) + return; + + apf.document.activeElement.blur(true, {srcElement: this});//, {cancelBubble: true} + lastFocusParent = apf.document.activeElement.$focusParent; + lastFocusElement = apf.document.activeElement; + apf.activeElement = apf.document.activeElement = null; + }); + this.getLastActiveElement = function(){ + return apf.activeElement || lastFocusElement; + } + + + /** + * @private + */ + this.destroy = function(){ + this.$at = null; + + apf.unload(this); + + apf = + this.win = + this.window = + this.document = null; + + //@todo this is not needed... maybe use apf.removeListener + window.onfocus = + window.onerror = + window.onunload = + window.onbeforeunload = + window.onbeforeprint = + window.onafterprint = + window.onmousewheel = + window.onblur = null; + + //@todo use apf.removeEvent + + document.oncontextmenu = + document.onmousedown = + document.onmousemove = + document.onmouseup = + document.onmousewheel = + document.onkeyup = + document.onkeydown = null + + if (document.body) { + document.body.onmousedown = + document.body.onmousemove = + document.body.onmouseup = null; + + document.body.innerHTML = ""; + } + }; +}; +apf.window.prototype = new apf.Class().$init(); +apf.window = new apf.window(); + + +/** + * @private + */ +apf.sanitizeTextbox = function(oTxt){ + if (!apf.hasFocusBug) + return; + + oTxt.onfocus = function(){ + if (apf.window) + apf.window.$focusfix2(); + }; + + oTxt.onblur = function(){ + if (apf.window) + apf.window.$blurfix(); + }; +}; + + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/core/browsers/gears.js)SIZE(1391)TIME(Mon, 10 Jan 2011 10:53:11 GMT)*/ + + +/** + * @private + */ +apf.initGears = function(){ + // summary: + // factory method to get a Google Gears plugin instance to + // expose in the browser runtime environment, if present + var factory, results; + + var gearsObj = apf.nameserver.get("google", "gears"); + if(gearsObj) + return gearsObj; // already defined elsewhere + + if (typeof GearsFactory != "undefined") { // Firefox + factory = new GearsFactory(); + } + else { + if(apf.isIE){ + // IE + try { + factory = new ActiveXObject("Gears.Factory"); + } + catch(e) { + // ok to squelch; there's no gears factory. move on. + } + } + else if(navigator.mimeTypes["application/x-googlegears"]) { + // Safari? + factory = document.createElement("object"); + factory.setAttribute("type", "application/x-googlegears"); + factory.setAttribute("width", 0); + factory.setAttribute("height", 0); + factory.style.display = "none"; + document.documentElement.appendChild(factory); + } + } + + // still nothing? + if (!factory) + return null; + + return apf.nameserver.register("google", "gears", factory); + +}; + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/core/browsers/gecko.js)SIZE(6753)TIME(Mon, 10 Jan 2011 10:53:11 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + +/** + * Compatibility layer for Gecko based browsers. + * @private + */ +apf.runGecko = function(){ + if (apf.runNonIe) + apf.runNonIe(); + + /* *************************************************************************** + XSLT + ****************************************************************************/ + + + //XMLDocument.selectNodes + HTMLDocument.prototype.selectNodes = XMLDocument.prototype.selectNodes = function(sExpr, contextNode){ + try { + var oResult = this.evaluate(sExpr, (contextNode || this), + this.createNSResolver(this.documentElement), + 7, null); //XpathResult.ORDERED_NODE_ITERATOR_TYPE + } + catch(ex) { + var msg = ex.message; + if (ex.code == ex.INVALID_EXPRESSION_ERR) + msg = msg.replace(/the expression/i, "'" + sExpr + "'"); + throw new Error(ex.lineNumber, "XPath error: " + msg); + } + + var nodeList = new Array(oResult.snapshotLength); + nodeList.expr = sExpr; + for (var i = nodeList.length - 1; i >= 0; i--) + nodeList[i] = oResult.snapshotItem(i); + return nodeList; + }; + + //Element.selectNodes + Text.prototype.selectNodes = + Attr.prototype.selectNodes = + Element.prototype.selectNodes = function(sExpr){ + return this.ownerDocument.selectNodes(sExpr, this); + }; + + //XMLDocument.selectSingleNode + HTMLDocument.prototype.selectSingleNode = + XMLDocument.prototype.selectSingleNode = function(sExpr, contextNode){ + try { + var oResult = this.evaluate(sExpr, (contextNode || this), + this.createNSResolver(this.documentElement), + 9, null); //XpathResult.FIRST_ORDERED_NODE_TYPE + } + catch(ex) { + var msg = ex.message; + if (ex.code == ex.INVALID_EXPRESSION_ERR) + msg = msg.replace(/the expression/i, "'" + sExpr + "'"); + throw new Error(ex.lineNumber, "XPath error: " + msg); + } + + return oResult.singleNodeValue; + }; + + //Element.selectSingleNode + Text.prototype.selectSingleNode = + Attr.prototype.selectSingleNode = + Element.prototype.selectSingleNode = function(sExpr){ + return this.ownerDocument.selectSingleNode(sExpr, this); + }; + + + + var serializer = new XMLSerializer(); + var o = document.createElement("div"); + apf.insertHtmlNodes = function(nodeList, htmlNode, beforeNode, s) { + var frag, l, node, i; + if (nodeList) { + frag = document.createDocumentFragment(); + for (i = nodeList.length - 1; i >= 0; i--) { + node = nodeList[i]; + frag.insertBefore(node, frag.firstChild); + } + } + + o.innerHTML = typeof s == "string" ? s : apf.html_entity_decode(serializer.serializeToString(frag)) + .replace(/<([^>]+)\/>/g, "<$1>"); + + frag = document.createDocumentFragment(); + for (i = 0, l = o.childNodes.length; i < l; i++) { + node = o.childNodes[0]; + frag.appendChild(node); + } + + if (beforeNode) + htmlNode.insertBefore(frag, beforeNode); + htmlNode.appendChild(frag); + }; + + apf.insertHtmlNode = function(xmlNode, htmlNode, beforeNode, s) { + if (htmlNode.nodeType != 11 && !htmlNode.style) + return htmlNode.appendChild(xmlNode); + + if (!s) { + s = apf.html_entity_decode(xmlNode.serialize + ? xmlNode.serialize(true) + : ((xmlNode.nodeType == 3 || xmlNode.nodeType == 4 || xmlNode.nodeType == 2) + ? xmlNode.nodeValue + : serializer.serializeToString(xmlNode))); + } + + o.innerHTML = s.replace(/<([^>]+)\/>/g, "<$1>"); + + if (beforeNode) + htmlNode.insertBefore(o.firstChild, beforeNode); + else + htmlNode.appendChild(o.firstChild); + + return beforeNode ? beforeNode.previousSibling : htmlNode.lastChild; + }; + + /* ******** Error Compatibility ********************************************** + Error Object like IE + ****************************************************************************/ + function Error(nr, msg){ + + if (!apf.$debugwin.nativedebug) + apf.$debugwin.errorHandler(msg, "", 0); + + + this.message = msg; + this.nr = nr; + } + + apf.getHtmlLeft = function(oHtml){ + return (oHtml.offsetLeft + + (parseInt(apf.getStyle(oHtml.parentNode, "borderLeftWidth")) || 0)); + }; + + apf.getHtmlRight = function(oHtml){ + var p; + return (((p = oHtml.offsetParent).tagName == "BODY" + ? apf.getWindowWidth() + : p.offsetWidth) + - oHtml.offsetLeft - oHtml.offsetWidth + - (2 * (parseInt(apf.getStyle(p, "borderLeftWidth")) || 0)) + - (parseInt(apf.getStyle(p, "borderRightWidth")) || 0)); + }; + + apf.getHtmlTop = function(oHtml){ + return (oHtml.offsetTop + + (parseInt(apf.getStyle(oHtml.parentNode, "borderTopWidth")) || 0)); + }; + + apf.getHtmlBottom = function(oHtml){ + var p; + return (((p = oHtml.offsetParent).tagName == "BODY" + ? apf.getWindowHeight() + : p.offsetHeight) + - oHtml.offsetTop - oHtml.offsetHeight + - (2 * (parseInt(apf.getStyle(p, "borderTopWidth")) || 0)) + - (parseInt(apf.getStyle(p, "borderBottomWidth")) || 0)); + }; + + apf.getBorderOffset = function(oHtml){ + return [-1 * (parseInt(apf.getStyle(oHtml, "borderLeftWidth")) || 0), + -1 * (parseInt(apf.getStyle(oHtml, "borderTopWidth")) || 0)]; + }; +} + + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/core/browsers/ie.js)SIZE(14081)TIME(Mon, 10 Jan 2011 10:53:11 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + +/** + * Compatibility layer for Internet Explorer browsers. + * @private + */ +apf.runIE = function(){ + /* ******** XML Compatibility ************************************************ + Extensions to the xmldb + ****************************************************************************/ + var hasIE7Security = false, + hasIESecurity = false; + + if (self.XMLHttpRequest) + try { + new XMLHttpRequest() + } + catch (e) { + hasIE7Security = true + } + try { + new ActiveXObject("microsoft.XMLHTTP") + } + catch (e) { + hasIESecurity = true + } + + + + if (hasIESecurity) + apf.importClass(runTpIframe, true, self); + + + + apf.getHttpReq = hasIESecurity + ? function(){ + if (apf.availHTTP.length) + return apf.availHTTP.pop(); + + + //if(apf.isDeskrun && !self.useNativeHttp) + // return jdshell.CreateComponent("XMLHTTP"); + + + return new XMLHttpRequest(); + } + : function(){ + if (apf.availHTTP.length) + return apf.availHTTP.pop(); + + + //if(apf.isDeskrun && !apf.useNativeHttp) + // return jdshell.CreateComponent("XMLHTTP"); + + + return new ActiveXObject("microsoft.XMLHTTP"); + }; + + apf.getXmlDom = hasIESecurity + ? function(message, noError){ + var xmlParser = getDOMParser(message, noError); + return xmlParser; + } + : function(message, noError, preserveWhiteSpaces){ + var xmlParser = new ActiveXObject("microsoft.XMLDOM"); + xmlParser.setProperty("SelectionLanguage", "XPath"); + if (preserveWhiteSpaces) + xmlParser.preserveWhiteSpace = true; + + if (message) { + if (apf.cantParseXmlDefinition) + message = message.replace(/\] \]/g, "] ]") + .replace(/^<\?[^>]*\?>/, "");//replace xml definition for IE5.0 + + xmlParser.loadXML(message); + + + if (xmlParser.parseError != 0 && apf.xmldb && apf.isJson(message)) { + try { + xmlParser = apf.json2Xml(message, noError); + } + catch(e) { + throw new Error(apf.formatErrorString(1051, null, + "JSON to XML conversion error occurred."+e.message, + "\nSource Text : " + message.replace(/\t/gi, " "))); + } + } + else + + if (!noError) + this.xmlParseError(xmlParser); + } + + return xmlParser; + }; + + apf.xmlParseError = function(xml){ + var xmlParseError = xml.parseError; + if (xmlParseError != 0) { + /* + http://msdn.microsoft.com/library/en-us/xmlsdk30/htm/xmobjpmexmldomparseerror.asp?frame=true + + errorCode Contains the error code of the last parse error. Read-only. + filepos Contains the absolute file position where the error occurred. Read-only. + line Specifies the line number that contains the error. Read-only. + linepos Contains the character position within the line where the error occurred. Read-only. + reason Explains the reason for the error. Read-only. + srcText Returns the full text of the line containing the error. Read-only. + url Contains the URL of the XML document containing the last error. Read-only. + */ + throw new Error(apf.formatErrorString(1050, null, + "XML Parse error on line " + xmlParseError.line, + xmlParseError.reason + "Source Text:\n" + + xmlParseError.srcText.replace(/\t/gi, " ") + )); + } + + return xml; + }; + + /** + * This method retrieves the current value of a property on a HTML element + * @param {HTMLElement} el the element to read the property from + * @param {String} prop the property to read + * @returns {String} + */ + apf.getStyle = function(el, prop) { + return el.currentStyle[prop]; + }; + + apf.insertHtmlNodes = function(nodeList, htmlNode, beforeNode, s){ + var str; + if (nodeList) { + for (str = [], i = 0, l = nodeList.length; i < l; i++) + str[i] = nodeList[i].xml; + } + str = s || apf.html_entity_decode(str.join("")); + + if (apf.isIE < 7) + str = str.replace(/style="background-image:([^"]*)"/g, + "find='$1' style='background-image:$1'"); + + try { + (beforeNode || htmlNode).insertAdjacentHTML(beforeNode + ? "beforebegin" + : "beforeend", str); + } + catch (e) { + //IE table hack + document.body.insertAdjacentHTML("beforeend", "" + + str + "
    "); + + var x = document.body.lastChild.firstChild.firstChild; + for (i = x.childNodes.length - 1; i >= 0; i--) + htmlNode.appendChild(x.childNodes[apf.hasDynamicItemList ? 0 : i]); + } + + //Fix IE image loading bug + if (apf.isIE < 7) { + $setTimeout(function(){ + var nodes = htmlNode.getElementsByTagName("*"); + for (var s, i = 0, l = nodes.length; i < l; i++) { + if (s = nodes[i].getAttribute("find")) + nodes[i].style.backgroundImage = s.trim(); //@todo apf3.0 why is this needed? + } + }); + } + }; + + /* I have no idea what below code should do + + if (pNode.nodeType == 11) { + id = xmlNode.getAttribute("id"); + if (!id) + throw new Error(apf.formatErrorString(1049, null, "xmldb", "Inserting Cache Item in Document Fragment without an ID")); + + document.body.insertAdjacentHTML(beforeNode ? "beforebegin" : "beforeend", strHTML); + pNode.appendChild(document.getElementById(id)); + }*/ + apf.insertHtmlNode = function(xmlNode, htmlNode, beforeNode, str){ + if (htmlNode.nodeType != 11 && !htmlNode.style) + return htmlNode.appendChild(xmlNode); + + var pNode = beforeNode || htmlNode; + + if (!str) + str = apf.html_entity_decode(xmlNode.serialize + ? xmlNode.serialize(true) + : xmlNode.xml || xmlNode.outerHTML || xmlNode.nodeValue); + try { + pNode.insertAdjacentHTML(beforeNode + ? "beforeBegin" + : "beforeEnd", str); + } + catch(e) { + + apf.console.warn("Warning found block element inside a " + + pNode.tagName + + " element. Rendering will give unexpected results"); + + + pNode.insertAdjacentHTML("afterEnd", str); + return pNode.nextSibling; + } + + if (beforeNode) + return beforeNode.previousSibling; + else + return htmlNode.lastChild.nodeType == 1 + ? htmlNode.lastChild + : htmlNode.lastChild.previousSibling; + + }; + + apf.getHtmlLeft = function(oHtml){ + return (oHtml.offsetLeft + - (apf.isIE > 7 && parseInt(oHtml.parentNode.currentStyle["borderLeftWidth"]) || 0)); + }; + + apf.getHtmlRight = function(oHtml){ + var p; + return (((p = oHtml.offsetParent).tagName == "BODY" + ? apf.getWindowWidth() + : p.offsetWidth) + - oHtml.offsetLeft - oHtml.offsetWidth + - (apf.isIE < 8 && parseInt(p.currentStyle["borderLeftWidth"]) || 0) + - (parseInt(p.currentStyle["borderRightWidth"]) || 0)); + }; + + apf.getHtmlTop = function(oHtml){ + return (oHtml.offsetTop + - (apf.isIE > 7 && parseInt(oHtml.offsetParent.currentStyle["borderTopWidth"]) || 0)); + }; + + apf.getHtmlBottom = function(oHtml){ + var p; + return (((p = oHtml.offsetParent).tagName == "BODY" + ? apf.getWindowHeight() + : p.offsetHeight) + - oHtml.offsetTop - oHtml.offsetHeight + - (apf.isIE < 8 && parseInt(p.currentStyle["borderTopWidth"]) || 0) + - (parseInt(p.currentStyle["borderBottomidth"]) || 0)); + }; + + apf.getBorderOffset = function(oHtml){ + return apf.isIE < 8 && [0,0] || [parseInt(oHtml.currentStyle["borderLeftWidth"]) || 0, + parseInt(oHtml.currentStyle["borderTopWidth"]) || 0] + }; + + apf.getOpacity = function(oHtml) { + return parseInt(((oHtml.currentStyle["filter"] || "").match(/alpha\(opacity=(\d*)\)/) || [0,0])[1]) / 100; + }; + + apf.setOpacity = function(oHtml, value){ + oHtml.style.filter = value == 1 + ? "" + : "alpha(opacity=" + Math.round(value * 100) + ")"; + }; + + + /** + * @private + */ + apf.popup2 = { + cache: {}, + setContent: function(cacheId, content, style, width, height){ + if (!this.popup) + this.init(); + + this.cache[cacheId] = { + content: content, + style : style, + width : width, + height : height + }; + if (content.parentNode) + content.parentNode.removeChild(content); + if (style) + apf.importCssString(style, this.popup.document); + + return this.popup.document; + }, + + removeContent: function(cacheId){ + this.cache[cacheId] = null; + delete this.cache[cacheId]; + }, + + init: function(){ + this.popup = window.createPopup(); + + this.popup.document.write('\ + \ + \ + \ + \ + \ + \ + '); + + var c = apf; + this.popup.document.body.onmousemove = function(){ + this.c = c + } + }, + + show: function(cacheId, x, y, animate, ref, width, height, callback){ + if (!this.popup) + this.init(); + var o = this.cache[cacheId]; + //if(this.last != cacheId) + this.popup.document.body.innerHTML = o.content.outerHTML; + + if (animate) { + var iVal, steps = 7, i = 0, popup = this.popup; + iVal = setInterval(function(){ + var value = ++i * ((height || o.height) / steps); + popup.show(x, y, width || o.width, value, ref); + popup.document.body.firstChild.style.marginTop + = (i - steps - 1) * ((height || o.height) / steps); + if (i > steps) { + clearInterval(iVal) + callback(popup.document.body.firstChild); + } + }, 10); + } + else { + this.popup.show(x, y, width || o.width, height || o.height, ref); + } + + this.last = cacheId; + }, + + hide: function(){ + if (this.popup) + this.popup.hide(); + }, + + forceHide: function(){ + if (this.last) + apf.lookup(this.last).dispatchEvent("popuphide"); + }, + + destroy: function(){ + if (!this.popup) + return; + this.popup.document.body.c = null; + this.popup.document.body.onmouseover = null; + } + }; + + + + apf.importClass(apf.runXpath, true, self); + +} + + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/core/browsers/iphone.js)SIZE(11827)TIME(Mon, 10 Jan 2011 10:53:11 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + +/** + * @private + */ +apf.runIphone = function() { + if (!apf.isIphone) return; + + $setTimeout(function() { + + apf.importCssString( + 'html, body {\ + margin: 0;\ + font-family: Helvetica;\ + background: #fff;\ + color: #000000;\ + overflow-x: hidden;\ + -webkit-user-select: none;\ + -webkit-text-size-adjust: none;\ + -webkit-touch-callout: none;\ + }\ + body > *:not(.toolbar) {\ + min-height: 372px;\ + }\ + body[orient="landscape"] > *:not(.toolbar) {\ + min-height: 268px;\ + }\ + body > *[selected="true"] {\ + display: block;\ + }', "screen"); + + + var head = document.getElementsByTagName("head")[0]; + if (apf.config.iphoneIcon) { + var link = document.createElement("link"); + link.setAttribute("rel", "apple-touch-icon" + + (apf.config.iphoneIconIsGlossy ? "" : "-precomposed")); + link.setAttribute("href", "apf.config.iphoneIcon"); + head.appendChild(link); + } + + function appendMeta(name, content) { + var meta = document.createElement("meta"); + meta.setAttribute("name", name); + meta.setAttribute("content", content); + head.appendChild(meta); + } + + if (apf.config.iphoneFixedViewport) { + appendMeta("viewport", + "width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0;"); + } + + if (apf.config.iphoneFullScreen) { + appendMeta("apple-mobile-web-app-capable", "yes"); + + if (apf.config.iphoneStatusBar) + appendMeta("apple-mobile-web-app-status-bar-style", + "apf.config.iphoneStatusBar"); + } + }); + + var hasOrientationEvent = false, + currentWidth = 0, + portraitVal = "portrait", + landscapeVal = "landscape", + checkTimer = null; + + apf.addDomLoadEvent(function() { + $setTimeout(checkOrientAndLocation, 0); + checkTimer = setInterval(checkOrientAndLocation, 300); + }); + + function orientChangeHandler() { + switch(window.orientation) { + case 0: + setOrientation(portraitVal); + break; + case 90: + case -90: + setOrientation(landscapeVal); + break; + } + } + + if (typeof window.onorientationchange == "object") { + window.onorientationchange = orientChangeHandler; + hasOrientationEvent = true; + $setTimeout(orientChangeHandler, 0); + } + + function checkOrientAndLocation() { + if (!hasOrientationEvent) { + if (window.innerWidth != currentWidth) { + currentWidth = window.innerWidth; + var orient = currentWidth == 320 ? portraitVal : landscapeVal; + setOrientation(orient); + } + } + + /*if (location.hash != currentHash) { + var pageId = location.hash.substr(hashPrefix.length); + iui.showPageById(pageId); + }*/ + } + + function setOrientation(orient) { + document.body.setAttribute("orient", orient); + $setTimeout("scrollTo(0,1)", 100); + } + + /* register event listeners: + * - touchstart (skip) + * - touchmove (skip) + * - touchend (skip) + * - touchcancel (skip) + * - gesturestart + * - gesturechange + * - gestureend + * - orientationchange + */ + ["gesturestart", "gesturechange", "gestureend", + "orientationchange"].forEach(function(type) { + document["on" + type] = function(evt) { + if (apf.dispatchEvent) + apf.dispatchEvent(type, evt); + }; + }); + + apf.iphone = { + titleNode : null, + + linkEvents: function(el, bClick) { + return; + el[bClick ? "onclick" : "ontouchstart"] = function(evt) { + if (!evt.touches || evt.touches.length != 1) return; + + var e = evt.touches[0]; + if (typeof this.onmousedown == "function") { + this.onmousedown(e); + if (this != document) + return false; + } + }; + + el.ontouchmove = function(evt) { + if (!evt.touches || evt.touches.length != 1) return; + + var e = evt.touches[0]; + if (typeof this.onmousemove == "function") { + this.onmousemove(e); + if (this != document) + return false; + } + }; + + var _touching = false; + + el.ontouchend = el.ontouchcancel = function(evt) { + if (_touching) return; + + var e = evt.touches && evt.touches.length + ? evt.touches[0] + : evt.changedTouches[0]; + if (!e) return; + + _touching = true; + $setTimeout(function() { _touching = false; }); + if (typeof this.onmouseup == "function") { + this.onmouseup(e); + if (this != document) + return false; + } + }; + + return this; + }, + nav: { + sections : null, + active : null, + def : "home", + divideChar : "/", + levelTwoChar : "-", + + go: function(where, noanim) { + var i, p, _self = apf.iphone.nav; + _self.update(); + + if (!(p = _self.sections[where.page])) return; + + scrollTo(0, 1); + apf.dispatchEvent("pagechange", where); + + var sTitle = p.getAttribute("title"); + if (apf.iphone.titleNode && sTitle) + apf.iphone.titleNode.innerHTML = sTitle; + + if (noanim) { + for (i in _self.sections) + _self.sections[i].hide(); + p.show(); + } + else { + for (i in _self.sections) { + if (!_self.sections[i].visible || i == where.page) + continue; + var section = _self.sections[i]; + section.setProperty("zindex", 0); + apf.tween.single(section.$ext, { + steps : 5, + interval: 10, + from : section.$ext.offsetLeft, + to : (where.index < 0) ? 1000 : -1000, + type : "left", + anim : apf.tween.EASEOUT, + onfinish: function() { + section.setProperty("visible", false); + } + }); + } + + var pad = 10, + el = p.$ext, + iFrom = (where.index < 0) + ? -(el.offsetWidth) - pad + : window.innerWidth + el.offsetLeft + pad; + p.setProperty("visible", true); + p.setProperty("zindex", apf.all.length + 1); + + apf.tween.single(el, { + steps : 5, + interval: 10, + from : iFrom, + to : 0, + type : "left", + anim : apf.tween.EASEIN + }); + + } + }, + + update: function(force) { + if (this.sections && !force) return; + this.sections = {}; + for (var i in window) { + if (window[i] && window[i]["tagName"] + && window[i].tagName == "section") + this.sections[i] = window[i]; + } + } + } + }; + + $setTimeout(function() { + apf.addEventListener("hashchange", apf.iphone.nav.go); + if (location.href.match(/#(.*)$/)) + apf.history.init(decodeURI(RegExp.$1)); + else if (apf._GET.page) + apf.history.init(apf._GET.page); + else + apf.history.init(); + }); + + // make sure that document event link to mouse events already. Since the + // document object on top of the event bubble chain, it will probably also + // be hooked by other APF elements. + //apf.iphone.linkEvents(document); + document.ontouchstart = function(evt) { + if (!evt.touches || evt.touches.length != 1) return; + + var e = evt.touches[0], + el = e.target, + amlNode = apf.findHost(e.target); + if (!amlNode) return; + + while (typeof el["onmousedown"] != "function" && el != document.body) + el = el.parentNode; + if (typeof el.onmousedown == "function") { + if (typeof el.onmouseover == "function") + el.onmouseover(e); + else if (typeof el.onmousemove == "function") + el.onmousemove(e); + el.onmousedown(e); + return false; + } + }; + + document.ontouchmove = function(evt) { + if (!evt.touches || evt.touches.length != 1) return; + + var e = evt.touches[0], + el = e.target, + amlNode = apf.findHost(e.target); + if (!amlNode) return; + + while (typeof el["onmousemove"] != "function" && el != document.body) + el = el.parentNode; + if (typeof el.onmousemove == "function") { + el.onmousemove(e); + return false; + } + else if (typeof document["onmousemove"] == "function") { + return document.onmousemove(e); + } + }; + + document.ontouchend = document.ontouchcancel = function(evt) { + var e = evt.touches && evt.touches.length + ? evt.touches[0] + : evt.changedTouches[0]; + if (!e) return; + var el = e.target, + amlNode = apf.findHost(e.target); + if (!amlNode) return; + + while (typeof el["onmouseup"] != "function" && el != document.body) + el = el.parentNode; + if (typeof el.onmouseup == "function") { + if (typeof el.onmouseout == "function") + el.onmouseout(e); + el.onmouseup(e, true); + return false; + } + else if (typeof document["onmouseup"] == "function") { + return document.onmouseup(e); + } + }; +}; + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/core/browsers/non_ie.js)SIZE(24329)TIME(Mon, 10 Jan 2011 10:53:11 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + +/** + * @private + */ +apf.runNonIe = function (){ + + + DocumentFragment.prototype.getElementById = function(id){ + return this.childNodes.length ? this.childNodes[0].ownerDocument.getElementById(id) : null; + }; + + + + /**** XML Serialization ****/ + if (XMLDocument.prototype.__defineGetter__) { + //XMLDocument.xml + XMLDocument.prototype.__defineGetter__("xml", function(){ + return (new XMLSerializer()).serializeToString(this); + }); + XMLDocument.prototype.__defineSetter__("xml", function(){ + throw new Error(apf.formatErrorString(1042, null, "XML serializer", "Invalid assignment on read-only property 'xml'.")); + }); + + //Node.xml + Node.prototype.__defineGetter__("xml", function(){ + if (this.nodeType == 3 || this.nodeType == 4 || this.nodeType == 2) + return this.nodeValue; + return (new XMLSerializer()).serializeToString(this); + }); + + //Node.xml + Element.prototype.__defineGetter__("xml", function(){ + return (new XMLSerializer()).serializeToString(this); + }); + } + + /* ******** HTML Interfaces ************************************************** + insertAdjacentHTML(), insertAdjacentText() and insertAdjacentElement() + ****************************************************************************/ + if (typeof HTMLElement!="undefined") { + if (!HTMLElement.prototype.insertAdjacentElement) { + Text.prototype.insertAdjacentElement = + HTMLElement.prototype.insertAdjacentElement = function(where,parsedNode){ + switch (where.toLowerCase()) { + case "beforebegin": + this.parentNode.insertBefore(parsedNode,this); + break; + case "afterbegin": + this.insertBefore(parsedNode,this.firstChild); + break; + case "beforeend": + this.appendChild(parsedNode); + break; + case "afterend": + if (this.nextSibling) + this.parentNode.insertBefore(parsedNode,this.nextSibling); + else + this.parentNode.appendChild(parsedNode); + break; + } + }; + } + + if (!HTMLElement.prototype.insertAdjacentHTML) { + Text.prototype.insertAdjacentHTML = + HTMLElement.prototype.insertAdjacentHTML = function(where,htmlStr){ + var r = this.ownerDocument.createRange(); + r.setStartBefore(apf.isWebkit + ? document.body + : (self.document ? document.body : this)); + var parsedHTML = r.createContextualFragment(htmlStr); + this.insertAdjacentElement(where, parsedHTML); + }; + } + + if (!HTMLBodyElement.prototype.insertAdjacentHTML) //apf.isWebkit) + HTMLBodyElement.prototype.insertAdjacentHTML = HTMLElement.prototype.insertAdjacentHTML; + + if (!HTMLElement.prototype.insertAdjacentText) { + Text.prototype.insertAdjacentText = + HTMLElement.prototype.insertAdjacentText = function(where,txtStr){ + var parsedText = document.createTextNode(txtStr); + this.insertAdjacentElement(where,parsedText); + }; + } + + //HTMLElement.removeNode + HTMLElement.prototype.removeNode = function(){ + if (!this.parentNode) return; + + this.parentNode.removeChild(this); + }; + + //Currently only supported by Gecko + if (HTMLElement.prototype.__defineSetter__) { + //HTMLElement.innerText + HTMLElement.prototype.__defineSetter__("innerText", function(sText){ + var s = "" + sText; + this.innerHTML = s.replace(/\&/g, "&") + .replace(//g, ">"); + }); + + HTMLElement.prototype.__defineGetter__("innerText", function(){ + return this.innerHTML.replace(/<[^>]+>/g,"") + .replace(/\s\s+/g, " ").replace(/^\s+|\s+$/g, " "); + }); + + HTMLElement.prototype.__defineGetter__("outerHTML", function(){ + return (new XMLSerializer()).serializeToString(this); + }); + } + } + + /* ******** XML Compatibility ************************************************ + Giving the Mozilla XML Parser the same interface as IE's Parser + ****************************************************************************/ + var ASYNCNOTSUPPORTED = false; + + //Test if Async is supported + try { + XMLDocument.prototype.async = true; + ASYNCNOTSUPPORTED = true; + } catch(e) {/*trap*/} + + Document.prototype.onreadystatechange = null; + Document.prototype.parseError = 0; + + Array.prototype.item = function(i){return this[i];}; + Array.prototype.expr = ""; + + /*try{ + XMLDocument.prototype.readyState = 0; + }catch(e){}*/ + + XMLDocument.prototype.$clearDOM = function(){ + while (this.hasChildNodes()) + this.removeChild(this.firstChild); + }; + + XMLDocument.prototype.$copyDOM = function(oDoc){ + this.$clearDOM(); + + if (oDoc.nodeType == 9 || oDoc.nodeType == 11) { + var oNodes = oDoc.childNodes; + + for (var i = 0; i < oNodes.length; i++) + this.appendChild(this.importNode(oNodes[i], true)); + } + else if (oDoc.nodeType == 1) + this.appendChild(this.importNode(oDoc, true)); + }; + + //XMLDocument.loadXML(); + XMLDocument.prototype.loadXML = function(strXML){ + apf.xmldb.setReadyState(this, 1); + var sOldXML = this.xml || this.serialize(); + var oDoc = (new DOMParser()).parseFromString(strXML, "text/xml"); + apf.xmldb.setReadyState(this, 2); + this.$copyDOM(oDoc); + apf.xmldb.setReadyState(this, 3); + apf.xmldb.loadHandler(this); + return sOldXML; + }; + + Node.prototype.getElementById = function(id){}; + + HTMLElement.prototype.replaceNode = + Element.prototype.replaceNode = function(xmlNode){ + if (!this.parentNode) return; + + this.parentNode.insertBefore(xmlNode, this); + this.parentNode.removeChild(this); + }; + + //XMLDocument.load + XMLDocument.prototype.$load = XMLDocument.prototype.load; + XMLDocument.prototype.load = function(sURI){ + var oDoc = document.implementation.createDocument("", "", null); + oDoc.$copyDOM(this); + this.parseError = 0; + apf.xmldb.setReadyState(this, 1); + + try { + if (this.async == false && ASYNCNOTSUPPORTED) { + var tmp = new XMLHttpRequest(); + tmp.open("GET", sURI, false); + tmp.overrideMimeType("text/xml"); + tmp.send(null); + apf.xmldb.setReadyState(this, 2); + this.$copyDOM(tmp.responseXML); + apf.xmldb.setReadyState(this, 3); + } else + this.$load(sURI); + } + catch (objException) { + this.parseError = -1; + } + finally { + apf.xmldb.loadHandler(this); + } + + return oDoc; + }; + + + + + + //Element.transformNodeToObject + Element.prototype.transformNodeToObject = function(xslDoc, oResult){ + var oDoc = document.implementation.createDocument("", "", null); + oDoc.$copyDOM(this); + oDoc.transformNodeToObject(xslDoc, oResult); + }; + + //Document.transformNodeToObject + Document.prototype.transformNodeToObject = function(xslDoc, oResult){ + var xsltProcessor = null; + try { + xsltProcessor = new XSLTProcessor(); + + if (xsltProcessor.reset) { + // new nsIXSLTProcessor is available + xslDoc = apf.getXmlDom(xslDoc.xml || xslDoc.serialize()); + xsltProcessor.importStylesheet(xslDoc); + var newFragment = xsltProcessor.transformToFragment(this, oResult); + oResult.$copyDOM(newFragment); + } + else { + // only nsIXSLTProcessorObsolete is available + xsltProcessor.transformDocument(this, xslDoc, oResult, null); + } + } + catch(e) { + if (xslDoc && oResult) + throw new Error(apf.formatErrorString(1043, null, "XSLT Transformation", "Failed to transform document. \nInfo : " + e)); + else if (!xslDoc) + throw new Error(apf.formatErrorString(1044, null, "XSLT Transformation", "No Stylesheet Document was provided. \nInfo : " + e)); + else if (!oResult) + throw new Error(apf.formatErrorString(1045, null, "XSLT Transformation", "No Result Document was provided. \nInfo : " + e)); + else if (xsltProcessor == null) + throw new Error(apf.formatErrorString(1046, null, "XSLT Transformation", "Could not instantiate an XSLTProcessor object. \nInfo : " + e)); + else + throw e; + } + }; + + //Element.transformNode + Element.prototype.transformNode = function(xslDoc){ + return apf.getXmlDom(this.xml || this.serialize()) + .transformNode(xslDoc); + }; + + //Document.transformNode + Document.prototype.transformNode = function(xslDoc){ + var xsltProcessor = new XSLTProcessor(); + xslDoc = apf.getXmlDom(xslDoc.xml || xslDoc.serialize()); + xsltProcessor.importStylesheet(xslDoc); + var newFragment = xsltProcessor.transformToFragment(this, + document.implementation.createDocument("", "", null)); + + return newFragment.xml || newFragment.serialize() + + /*try{ + var serializer = new XMLSerializer(); + str = serializer.serializeToString(out); + } + catch(e){ + throw new Error("---- APF Error ----\nProcess : XSLT Transformation\nMessage : Failed to serialize result document. \nInfo : " + e); + } + + return str;*/ + }; + + + + /** + * This method retrieves the current value of a property on a HTML element + * @param {HTMLElement} el the element to read the property from + * @param {String} prop the property to read + * @returns {String} + */ + var getStyle = apf.getStyle = function(el, prop) { + try{ + return (window.getComputedStyle(el, "") || {})[prop] || ""; + }catch(e){} + }; + + //XMLDocument.setProperty + HTMLDocument.prototype.setProperty = + XMLDocument.prototype.setProperty = function(x,y){}; + + /* ******** XML Compatibility ************************************************ + Extensions to the xmldb + ****************************************************************************/ + apf.getHttpReq = function(){ + if (apf.availHTTP.length) + return apf.availHTTP.pop(); + return new XMLHttpRequest(); + }; + + apf.getXmlDom = function(message, noError, preserveWhiteSpaces){ + var xmlParser; + if (message) { + if (preserveWhiteSpaces === false) + message = message.replace(/>[\s\n\r]*<"); + + xmlParser = new DOMParser(); + xmlParser = xmlParser.parseFromString(message, "text/xml"); + + + //xmlParser.documentElement.tagName == "parsererror" + if (xmlParser.getElementsByTagName("parsererror").length && apf.xmldb + && apf.isJson(message)) { + try { + xmlParser = apf.json2Xml(message, noError); + } + catch(e) { + throw new Error(apf.formatErrorString(1051, null, + "JSON to XML conversion error occurred.", + "\nSource Text : " + message.replace(/\t/gi, " "))); + } + } + else + + if (!noError) + this.xmlParseError(xmlParser); + } + else { + xmlParser = document.implementation.createDocument("", "", null); + } + + return xmlParser; + }; + + apf.xmlParseError = function(xml){ + //if (xml.documentElement.tagName == "parsererror") { + if (xml.getElementsByTagName("parsererror").length) { + var str = xml.documentElement.firstChild.nodeValue.split("\n"), + linenr = str[2].match(/\w+ (\d+)/)[1], + message = str[0].replace(/\w+ \w+ \w+: (.*)/, "$1"), + + srcText = xml.documentElement.lastChild.firstChild.nodeValue;//.split("\n")[0]; + + throw new Error(apf.formatErrorString(1050, null, + "XML Parse Error on line " + linenr, message + + "\nSource Text : " + srcText.replace(/\t/gi, " "))); + } + + return xml; + }; + + + apf.xmldb.setReadyState = function(oDoc, iReadyState) { + oDoc.readyState = iReadyState; + if (oDoc.onreadystatechange != null && typeof oDoc.onreadystatechange == "function") + oDoc.onreadystatechange(); + }; + + apf.xmldb.loadHandler = function(oDoc){ + if (!oDoc.documentElement || oDoc.documentElement.tagName == "parsererror") + oDoc.parseError = -1; + + apf.xmldb.setReadyState(oDoc, 4); + }; + + // + //Fix XML Data-Island Support Problem with Form Tag + apf.Init.add(function(){ + var i, nodes = document.getElementsByTagName("form"); + for (i = 0; i < nodes.length; i++) + nodes[i].removeNode(); + nodes = document.getElementsByTagName("xml"); + for(i = 0; i < nodes.length; i++) + nodes[i].removeNode(); + nodes = null; + }); + + /*window.onerror = function(message, filename, linenr){ + if(++ERROR_COUNT > MAXMSG) return; + filename = filename ? filename.match(/\/([^\/]*)$/)[1] : "[Mozilla Library]"; + new Error("---- APF Error ----\nProcess : Javascript code in '" + filename + "'\nLine : " + linenr + "\nMessage : " + message); + return false; + }*/ + + if (document.body) + document.body.focus = function(){}; + + + + if (!document.elementFromPoint) { + Document.prototype.elementFromPointRemove = function(el){ + if (!this.RegElements) return; + + this.RegElements.remove(el); + }; + + Document.prototype.elementFromPointAdd = function(el){ + if (!this.RegElements) + this.RegElements = []; + this.RegElements.push(el); + }; + + Document.prototype.elementFromPointReset = function(RegElements){ + //define globals + FoundValue = []; + FoundNode = null; + LastFoundAbs = document.documentElement; + }; + + Document.prototype.elementFromPoint = function(x, y){ + // Optimization, Keeping last found node makes it ignore all lower levels + // when there is no possibility of changing positions and zIndexes + /*if(self.FoundNode){ + var sx = getElementPosX(FoundNode); + var sy = getElementPosY(FoundNode); + var ex = sx + FoundNode.offsetWidth; var ey = sy + FoundNode.offsetHeight; + } + if(!self.FoundNode || !(x > sx && x < ex && y > sy && y < ey))*/ + document.elementFromPointReset(); + + // Optimization only looking at registered nodes + if (this.RegElements) { + var calc_z = -1, + i, calc, n, sx, sy, ex, ey, z + for (calc_z = -1, calc, i = 0; i < this.RegElements.length; i++) { + n = this.RegElements[i]; + if (getStyle(n, "display") == "none") continue; + + sx = getElementPosX(n); + sy = getElementPosY(n); + ex = sx + n.offsetWidth; + ey = sy + n.offsetHeight; + + if (x > sx && x < ex && y > sy && y < ey) { + z = getElementZindex(n); + if (z > calc_z) { //equal z-indexes not supported + calc = [n, x, y, sx, sy]; + calc_z = z; + } + } + } + + if (calc) { + efpi(calc[0], calc[1], calc[2], 0, FoundValue, calc[3], calc[4]); + if (!FoundNode) { + FoundNode = calc[0]; + LastFoundAbs = calc[0]; + FoundValue = [calc_z]; + } + } + } + + if (!this.RegElements || !this.RegElements.length) + efpi(document.body, x, y, 0, [], getElementPosX(document.body), + getElementPosY(document.body)); + + return FoundNode; + }; + + function efpi(from, x, y, CurIndex, CurValue, px, py){ + var StartValue = CurValue, + StartIndex = CurIndex, + //Loop through childNodes + nodes = from.childNodes, + n, i, z, sx, sy, ex, ey, isAbs, isHidden, inSpace; + for (n, i = 0; i < from.childNodes.length; i++) { + n = from.childNodes[i]; + if (n.nodeType == 1 && getStyle(n, "display") != "none" && n.offsetParent) { + sx = px + n.offsetLeft - n.offsetParent.scrollLeft;//getElementPosX(n); + sy = py + n.offsetTop - n.offsetParent.scrollTop;//getElementPosY(n); + ex = sx + n.offsetWidth; + ey = sy + n.offsetHeight; + + //if(Child is position absolute/relative and overflow == "hidden" && !inSpace) continue; + isAbs = getStyle(n, "position"); + isAbs = (isAbs == "absolute") || (isAbs == "relative"); + isHidden = getStyle(n, "overflow") == "hidden"; + inSpace = (x > sx && x < ex && y > sy && y < ey); + + if (isAbs && isHidden && !inSpace) continue; + + CurIndex = StartIndex; + CurValue = StartValue.copy(); + + //if (Child is position absolute/relative and has zIndex) or overflow == "hidden" + z = parseInt(getStyle(n, "zIndex")) || 0; + if (isAbs && (z || z == 0) || isHidden) { + //if(!is position absolute/relative) zIndex = 0 + if (!isAbs) z = 0; + + //if zIndex >= FoundValue[CurIndex] + if (z >= (FoundValue[CurIndex] || 0)) { + //if zIndex > CurValue[CurIndex]; + if (z > (CurValue[CurIndex] || 0)) { + //CurValue = StartValue.copy(); + + //set CurValue[CurIndex] = zIndex + CurValue[CurIndex] = z; + } + + CurIndex++; + + //if(inSpace && CurIndex >= FoundValue.length) + if (inSpace && CurIndex >= FoundValue.length) { + //Set FoundNode is currentNode + FoundNode = n; + //Set FoundValue is CurValue + FoundValue = CurValue;//.copy(); + + LastFoundAbs = n; + } + } + else + continue; //Ignore this treedepth + } + else if(inSpace && CurIndex >= FoundValue.length){ + //else if CurValue[CurIndex] continue; //Ignore this treedepth + //else if(CurValue[CurIndex]) continue; + + //Set FoundNode is currentNode + FoundNode = n; + //Set FoundValue is CurValue + FoundValue = CurValue;//.copy(); + } + + //loop through childnodes recursively + efpi(n, x, y, CurIndex, CurValue, isAbs ? sx : px, isAbs ? sy : py) + } + } + } + + function getElementPosY(myObj){ + return myObj.offsetTop + parseInt(apf.getStyle(myObj, "borderTopWidth")) + + (myObj.offsetParent ? getElementPosY(myObj.offsetParent) : 0); + } + + function getElementPosX(myObj){ + return myObj.offsetLeft + parseInt(apf.getStyle(myObj, "borderLeftWidth")) + + (myObj.offsetParent ? getElementPosX(myObj.offsetParent) : 0); + } + + function getElementZindex(myObj){ + //This is not quite sufficient and should be changed + var z = 0, n, p = myObj; + while (p && p.nodeType == 1) { + z = Math.max(z, parseInt(getStyle(p, "zIndex")) || -1); + p = p.parentNode; + } + return z; + } + } + + + + apf.getOpacity = function(oHtml) { + return apf.getStyle(oHtml, "opacity"); + }; + + apf.setOpacity = function(oHtml, value){ + oHtml.style.opacity = value; + }; +} + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/core/browsers/o3.js)SIZE(9159)TIME(Mon, 10 Jan 2011 10:53:11 GMT)*/ + + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/core/browsers/opera.js)SIZE(6576)TIME(Mon, 10 Jan 2011 10:53:11 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + +/** + * Compatibility layer for Opera browsers. + * @private + */ +apf.runOpera = function (){ + if (apf.runNonIe) + apf.runNonIe(); + + /* *************************************************************************** + XML Serialization + ****************************************************************************/ + //XMLDocument.xml + + //Node.xml + /*Node.prototype.serialize = function(){ + return (new XMLSerializer()).serializeToString(this); + } + //Node.xml + + Node.prototype.serialize = + XMLDocument.prototype.serialize = + Element.prototype.serialize = function(){ + return (new XMLSerializer()).serializeToString(this); + };*/ + + + + //XMLDocument.selectNodes + Document.prototype.selectNodes = + XMLDocument.prototype.selectNodes = + HTMLDocument.prototype.selectNodes = function(sExpr, contextNode){ + var oResult = this.evaluate(sExpr, (contextNode ? contextNode : this), + this.createNSResolver(this.documentElement), + XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null); + var nodeList = new Array(oResult.snapshotLength); + nodeList.expr = sExpr; + for (var i = 0; i < nodeList.length; i++) + nodeList[i] = oResult.snapshotItem(i); + return nodeList; + }; + + //Element.selectNodes + Text.prototype.selectNodes = + Attr.prototype.selectNodes = + Element.prototype.selectNodes = function(sExpr){ + var doc = this.ownerDocument; + if (!doc.selectSingleNode) { + doc.selectSingleNode = HTMLDocument.prototype.selectSingleNode; + doc.selectNodes = HTMLDocument.prototype.selectNodes; + } + + if (doc.selectNodes) + return doc.selectNodes(sExpr, this); + else { + throw new Error(apf.formatErrorString(1047, null, "XPath Selection", + "Method selectNodes is only supported by XML Nodes")); + } + }; + + //XMLDocument.selectSingleNode + Document.prototype.selectSingleNode = + XMLDocument.prototype.selectSingleNode = + HTMLDocument.prototype.selectSingleNode = function(sExpr, contextNode){ + var nodeList = this.selectNodes(sExpr + "[1]", contextNode ? contextNode : null); + return nodeList.length > 0 ? nodeList[0] : null; + }; + + //Element.selectSingleNode + Text.prototype.selectSingleNode = + Attr.prototype.selectSingleNode = + Element.prototype.selectSingleNode = function(sExpr){ + var doc = this.ownerDocument; + if (!doc.selectSingleNode) { + doc.selectSingleNode = HTMLDocument.prototype.selectSingleNode; + doc.selectNodes = HTMLDocument.prototype.selectNodes; + } + + if (doc.selectSingleNode) { + return doc.selectSingleNode(sExpr, this); + } + else { + throw new Error(apf.formatErrorString(1048, null, "XPath Selection", + "Method selectSingleNode is only supported by XML Nodes. \nInfo : " + e)); + } + }; + + + + var serializer = new XMLSerializer(); + apf.insertHtmlNodes = function(nodeList, htmlNode, beforeNode, s) { + var node, frag, i, l; + if (nodeList) { + frag = document.createDocumentFragment(); + i = 0; + l = nodeList.length; + for (; i < l; i++) { + if (!(node = nodeList[i])) continue; + frag.appendChild(node); + } + } + + (beforeNode || htmlNode).insertAdjacentHTML(beforeNode + ? "beforebegin" + : "beforeend", s || apf.html_entity_decode(serializer.serializeToString(frag)).replace(/<([^>]+)\/>/g, "<$1>")); + }; + + apf.insertHtmlNode = function(xmlNode, htmlNode, beforeNode, s) { + if (htmlNode.nodeType != 11 && !htmlNode.style) + return htmlNode.appendChild(xmlNode); + + if (!s) { + s = apf.html_entity_decode(xmlNode.serialize + ? xmlNode.serialize(true) + : ((xmlNode.nodeType == 3 || xmlNode.nodeType == 4 || xmlNode.nodeType == 2) + ? xmlNode.nodeValue + : serializer.serializeToString(xmlNode))); + } + + (beforeNode || htmlNode).insertAdjacentHTML(beforeNode + ? "beforebegin" + : "beforeend", s.replace(/<([^>]+)\/>/g, "<$1>")); + + return beforeNode ? beforeNode.previousSibling : htmlNode.lastChild; + }; + + apf.getHtmlLeft = function(oHtml){ + return (oHtml.offsetLeft + - (parseInt(apf.getStyle(oHtml.parentNode, "borderLeftWidth")) || 0)); + }; + + apf.getHtmlRight = function(oHtml){ + var p; + return (((p = oHtml.offsetParent).tagName == "BODY" + ? apf.getWindowWidth() + : p.offsetWidth) + - oHtml.offsetLeft - oHtml.offsetWidth + - (parseInt(apf.getStyle(p, "borderRightWidth")) || 0)); + }; + + apf.getHtmlTop = function(oHtml){ + return (oHtml.offsetTop + - (parseInt(apf.getStyle(oHtml.offsetParent, "borderTopWidth")) || 0)); + }; + + apf.getHtmlBottom = function(oHtml){ + var p; + return (((p = oHtml.offsetParent).tagName == "BODY" + ? apf.getWindowHeight() + : p.offsetHeight) + - oHtml.offsetTop - oHtml.offsetHeight + - (parseInt(apf.getStyle(p, "borderBottomWidth")) || 0)); + }; + + apf.getBorderOffset = function(oHtml){ + return [parseInt(apf.getStyle(oHtml, "borderLeftWidth")) || 0, + parseInt(apf.getStyle(oHtml, "borderTopWidth")) || 0] + }; +}; + + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/core/browsers/webkit.js)SIZE(7728)TIME(Mon, 10 Jan 2011 10:53:11 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + +/** + * Compatibility layer for Webkit based browsers. + * @private + */ +apf.runWebkit = function(){ + + if (!apf.isChrome) { + var setTimeoutSafari = window.setTimeout; + self.lookupSafariCall = []; + $setTimeout = function(call, time){ + if (typeof call == "string") + return setTimeoutSafari(call, time); + return setTimeoutSafari("self.lookupSafariCall[" + + (self.lookupSafariCall.push(call) - 1) + "]()", time); + } + + if (apf.isSafariOld) { + HTMLHtmlElement = document.createElement("html").constructor; + Node = HTMLElement = {}; + HTMLElement.prototype = HTMLHtmlElement.apf.__proto__.apf.__proto__; + HTMLDocument = Document = document.constructor; + var x = new DOMParser(); + XMLDocument = x.constructor; + Element = x.parseFromString("", "text/xml").documentElement.constructor; + x = null; + } + if (!XMLDocument.prototype.__defineGetter__) { + Document.prototype.serialize = + Node.prototype.serialize = + XMLDocument.prototype.serialize = function(){ + return (new XMLSerializer()).serializeToString(this); + }; + } + } + + + + + + + HTMLDocument.prototype.selectNodes = XMLDocument.prototype.selectNodes = function(sExpr, contextNode){ + if (sExpr.substr(0,2) == "//") + sExpr = "." + sExpr; + + try { + var oResult = this.evaluate(sExpr, (contextNode || this), + this.createNSResolver(this.documentElement), + 7, null);//XPathResult.ORDERED_NODE_SNAPSHOT_TYPE + } + catch(ex) { + try { + var oResult = this.evaluate("child::" + sExpr, (contextNode || this), + this.createNSResolver(this.documentElement), + 7, null);//XPathResult.ORDERED_NODE_SNAPSHOT_TYPE + } + catch(ex) { + throw new Error("XPath error: " + ex.message + "\nLine: " + ex.lineNumber + "\nExpression: '" + sExpr + "'"); + } + } + + var nodeList = new Array(oResult.snapshotLength); + nodeList.expr = sExpr; + for (var i = nodeList.length - 1; i >= 0; i--) + nodeList[i] = oResult.snapshotItem(i); + return nodeList; + }; + + //Element.selectNodes + Text.prototype.selectNodes = + Attr.prototype.selectNodes = + Element.prototype.selectNodes = function(sExpr){ + return this.ownerDocument.selectNodes(sExpr, this); + }; + + //XMLDocument.selectSingleNode + HTMLDocument.prototype.selectSingleNode = XMLDocument.prototype.selectSingleNode = function(sExpr, contextNode){ + var nodeList = this.selectNodes(sExpr, contextNode || null); + return nodeList[0] || null; + }; + + //Element.selectSingleNode + Text.prototype.selectSingleNode = + Attr.prototype.selectSingleNode = + Element.prototype.selectSingleNode = function(sExpr){ + return this.ownerDocument.selectSingleNode(sExpr, this); + }; + + + + var serializer = new XMLSerializer(); + apf.insertHtmlNodes = function(nodeList, htmlNode, beforeNode, s) { + var node, frag, a, i, l; + if (nodeList) { + frag = document.createDocumentFragment(); + a = [], i = 0, l = nodeList.length; + for (; i < l; i++) { + if (!(node = nodeList[i])) continue; + frag.appendChild(node); + } + } + + (beforeNode || htmlNode).insertAdjacentHTML(beforeNode + ? "beforebegin" + : "beforeend", s || apf.html_entity_decode(serializer.serializeToString(frag)) + .replace(/<([^>]+)\/>/g, "<$1>")); + }; + + apf.insertHtmlNode = function(xmlNode, htmlNode, beforeNode, s) { + if (htmlNode.nodeType != 11 && !htmlNode.style) + return htmlNode.appendChild(xmlNode); + + if (!s) { + s = apf.html_entity_decode(xmlNode.serialize + ? xmlNode.serialize(true) + : ((xmlNode.nodeType == 3 || xmlNode.nodeType == 4 || xmlNode.nodeType == 2) + ? xmlNode.nodeValue + : serializer.serializeToString(xmlNode))); + } + + (beforeNode || htmlNode).insertAdjacentHTML(beforeNode + ? "beforebegin" + : "beforeend", s.match(/<(IMG|LINK|META|BR|HR|BASEFONT)[^\/>]*/i) ? s.replace(/<([^>]+)\/>/g, "<$1 />") : s.replace(/<([^>]+)\/>/g, "<$1>")); + + return beforeNode ? beforeNode.previousSibling : htmlNode.lastChild; + }; + + apf.getHtmlLeft = function(oHtml){ + return oHtml.offsetLeft; + }; + + apf.getHtmlRight = function(oHtml){ + var p; + return (((p = oHtml.offsetParent).tagName == "BODY" + ? apf.getWindowWidth() + : p.offsetWidth) + - oHtml.offsetLeft - oHtml.offsetWidth + - (parseInt(apf.getStyle(p, "borderLeftWidth")) || 0) + - (parseInt(apf.getStyle(p, "borderRightWidth")) || 0)); + }; + + apf.getHtmlTop = function(oHtml){ + return oHtml.offsetTop + }; + + apf.getHtmlBottom = function(oHtml){ + var p; + return (((p = oHtml.offsetParent).tagName == "BODY" + ? apf.getWindowHeight() + : p.offsetHeight) + - oHtml.offsetTop - oHtml.offsetHeight + - (parseInt(apf.getStyle(p, "borderTopWidth")) || 0) + - (parseInt(apf.getStyle(p, "borderBottomWidth")) || 0)); + }; + + apf.getBorderOffset = function(oHtml){ + return [0,0]; + }; + + if (apf.runNonIe) + apf.runNonIe(); +}; + + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/core/browsers/node/XMLHttpRequest.js)SIZE(6419)TIME(Mon, 10 Jan 2011 10:53:11 GMT)*/ + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/core/crypto/barrett.js)SIZE(2650)TIME(Mon, 10 Jan 2011 10:53:11 GMT)*/ + +/** + * Crypt.Barrett, a class for performing Barrett modular reduction computations in + * JavaScript. + * + * Requires BigInt.js. + * + * Copyright 2004-2005 David Shapiro. + * + * You may use, re-use, abuse, copy, and modify this code to your liking, but + * please keep this header. + * + * Thanks! + * + * @author Dave Shapiro + */ + + + +/** + * A class for performing Barrett modular reduction computations in JavaScript. + * + * @param {apf.crypto.BigInt} m + */ +apf.crypto.Barrett = function(){this.init.apply(this, arguments);}; +apf.crypto.Barrett.prototype = { + init: function(m) { + this.modulus = apf.crypto.BigInt.copy(m); + this.k = apf.crypto.BigInt.highIndex(this.modulus) + 1; + var b2k = new apf.crypto.BigInt.construct(); + b2k.digits[2 * this.k] = 1; // b2k = b^(2k) + this.mu = apf.crypto.BigInt.divide(b2k, this.modulus); + this.bkplus1 = new apf.crypto.BigInt.construct(); + this.bkplus1.digits[this.k + 1] = 1; // bkplus1 = b^(k+1) + }, + modulo: function(x) { + var q1 = apf.crypto.BigInt.divideByRadixPower(x, this.k - 1); + var q2 = apf.crypto.BigInt.multiply(q1, this.mu); + var q3 = apf.crypto.BigInt.divideByRadixPower(q2, this.k + 1); + var r1 = apf.crypto.BigInt.moduloByRadixPower(x, this.k + 1); + var r2term = apf.crypto.BigInt.multiply(q3, this.modulus); + var r2 = apf.crypto.BigInt.moduloByRadixPower(r2term, this.k + 1); + var r = apf.crypto.BigInt.subtract(r1, r2); + if (r.isNeg) { + r = apf.crypto.BigInt.add(r, this.bkplus1); + } + var rgtem = apf.crypto.BigInt.compare(r, this.modulus) >= 0; + while (rgtem) { + r = apf.crypto.BigInt.subtract(r, this.modulus); + rgtem = apf.crypto.BigInt.compare(r, this.modulus) >= 0; + } + return r; + }, + multiplyMod: function(x, y) { + /* + * x = this.modulo(x); + * y = this.modulo(y); + */ + var xy = apf.crypto.BigInt.multiply(x, y); + return this.modulo(xy); + }, + powMod: function(x, y) { + var result = new apf.crypto.BigInt.construct(); + result.digits[0] = 1; + var a = x; + var k = y; + while (true) { + if ((k.digits[0] & 1) != 0) result = this.multiplyMod(result, a); + k = apf.crypto.BigInt.shiftRight(k, 1); + if (k.digits[0] == 0 && apf.crypto.BigInt.highIndex(k) == 0) break; + a = this.multiplyMod(a, a); + } + return result; + } +}; + + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/core/crypto/base64.js)SIZE(6758)TIME(Mon, 10 Jan 2011 10:53:11 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + + +apf.crypto.Base64 = (function() { + + var b64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; + + // public method for encoding + function encode(data) { + var o1, o2, o3, h1, h2, h3, h4, bits, i = 0, ac = 0, enc = "", + tmp_arr = []; + + if (!data) + return data; + + data = apf.crypto.UTF8.encode(data + ""); + + do { // pack three octets into four hexets + o1 = data.charCodeAt(i++); + o2 = data.charCodeAt(i++); + o3 = data.charCodeAt(i++); + + bits = o1 << 16 | o2 << 8 | o3; + + h1 = bits >> 18 & 0x3f; + h2 = bits >> 12 & 0x3f; + h3 = bits >> 6 & 0x3f; + h4 = bits & 0x3f; + + // use hexets to index into b64, and append result to encoded string + tmp_arr[ac++] = b64.charAt(h1) + b64.charAt(h2) + b64.charAt(h3) + + b64.charAt(h4); + } + while (i < data.length); + + enc = tmp_arr.join(""); + + switch (data.length % 3) { + case 1: + enc = enc.slice(0, -2) + '=='; + break; + case 2: + enc = enc.slice(0, -1) + '='; + break; + } + + return enc; + } + + // public method for decoding + function decode(data) { + var o1, o2, o3, h1, h2, h3, h4, bits, i = 0, ac = 0, tmp_arr = []; + + if (!data) { + return data; + } + + data += ""; + + do { // unpack four hexets into three octets using index points in b64 + h1 = b64.indexOf(data.charAt(i++)); + h2 = b64.indexOf(data.charAt(i++)); + h3 = b64.indexOf(data.charAt(i++)); + h4 = b64.indexOf(data.charAt(i++)); + + bits = h1 << 18 | h2 << 12 | h3 << 6 | h4; + + o1 = bits>>16 & 0xff; + o2 = bits>>8 & 0xff; + o3 = bits & 0xff; + + if (h3 == 64) + tmp_arr[ac++] = String.fromCharCode(o1); + else if (h4 == 64) + tmp_arr[ac++] = String.fromCharCode(o1, o2); + else + tmp_arr[ac++] = String.fromCharCode(o1, o2, o3); + } + while (i < data.length); + + return apf.crypto.UTF8.decode(tmp_arr.join("")); + } + + return { + decode: decode, + encode: encode + }; + +})(); + +apf.crypto.UTF8 = { + // private method for UTF-8 encoding + encode : function (string) { + // Encodes an ISO-8859-1 string to UTF-8 + // + // version: 905.1217 + // discuss at: http://phpjs.org/functions/utf8_encode + // + original by: Webtoolkit.info (http://www.webtoolkit.info/) + // + improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net) + // + improved by: sowberry + // + tweaked by: Jack + // + bugfixed by: Onno Marsman + // + improved by: Yves Sucaet + // + bugfixed by: Onno Marsman + // * example 1: utf8_encode('Kevin van Zonneveld'); + // * returns 1: 'Kevin van Zonneveld' + string = (string + "").replace(/\r\n/g, "\n").replace(/\r/g, "\n"); + + var tmp_arr = [], + start = 0, + end = 0, + c1, enc; + + for (var n = 0, l = string.length; n < l; n++) { + c1 = string.charCodeAt(n); + enc = null; + + if (c1 < 128) { + end++; + } + else if ((c1 > 127) && (c1 < 2048)) { + enc = String.fromCharCode((c1 >> 6) | 192) + + String.fromCharCode((c1 & 63) | 128); + } + else { + enc = String.fromCharCode((c1 >> 12) | 224) + + String.fromCharCode(((c1 >> 6) & 63) | 128) + + String.fromCharCode((c1 & 63) | 128); + } + if (enc !== null) { + if (end > start) + tmp_arr.push(string.substring(start, end)); + tmp_arr.push(enc); + start = end = n + 1; + } + } + + if (end > start) + tmp_arr.push(string.substring(start, string.length)); + + return tmp_arr.join(""); + }, + + // private method for UTF-8 decoding + decode : function (str_data) { + // Converts a UTF-8 encoded string to ISO-8859-1 + // + // version: 905.3122 + // discuss at: http://phpjs.org/functions/utf8_decode + // + original by: Webtoolkit.info (http://www.webtoolkit.info/) + // + input by: Aman Gupta + // + improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net) + // + improved by: Norman "zEh" Fuchs + // + bugfixed by: hitwork + // + bugfixed by: Onno Marsman + // + input by: Brett Zamir (http://brett-zamir.me) + // + bugfixed by: Kevin van Zonneveld (http://kevin.vanzonneveld.net) + // * example 1: utf8_decode('Kevin van Zonneveld'); + // * returns 1: 'Kevin van Zonneveld' + var tmp_arr = [], i = 0, ac = 0, c1 = 0, c2 = 0, c3 = 0; + + str_data += ""; + + while (i < str_data.length) { + c1 = str_data.charCodeAt(i); + if (c1 < 128) { + tmp_arr[ac++] = String.fromCharCode(c1); + i++; + } + else if ((c1 > 191) && (c1 < 224)) { + c2 = str_data.charCodeAt(i+1); + tmp_arr[ac++] = String.fromCharCode(((c1 & 31) << 6) | (c2 & 63)); + i += 2; + } + else { + c2 = str_data.charCodeAt(i+1); + c3 = str_data.charCodeAt(i+2); + tmp_arr[ac++] = String.fromCharCode(((c1 & 15) << 12) + | ((c2 & 63) << 6) | (c3 & 63)); + i += 3; + } + } + + return tmp_arr.join(''); + } + +}; + + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/core/crypto/bigint.js)SIZE(20439)TIME(Mon, 10 Jan 2011 10:53:11 GMT)*/ + +/** + * BigInt, a suite of routines for performing multiple-precision arithmetic in + * JavaScript. + * + * Copyright 1998-2005 David Shapiro. + * + * You may use, re-use, abuse, + * copy, and modify this code to your liking, but please keep this header. + * Thanks! + * + * @author Dave Shapiro + * @author Ian Bunning + * + * IMPORTANT THING: Be sure to set maxDigits according to your precision + * needs. Use the setMaxDigits() function to do this. See comments below. + * + * Tweaked by Ian Bunning + * Alterations: + * Fix bug in function biFromHex(s) to allow + * parsing of strings of length != 0 (mod 4) + * + * Changes made by Dave Shapiro as of 12/30/2004: + * + * The BigInt() constructor doesn't take a string anymore. If you want to + * create a BigInt from a string, use biFromDecimal() for base-10 + * representations, biFromHex() for base-16 representations, or + * biFromString() for base-2-to-36 representations. + * + * biFromArray() has been removed. Use biCopy() instead, passing a BigInt + * instead of an array. + * + * The BigInt() constructor now only constructs a zeroed-out array. + * Alternatively, if you pass , it won't construct any array. See the + * biCopy() method for an example of this. + * + * Be sure to set maxDigits depending on your precision needs. The default + * zeroed-out array ZERO_ARRAY is constructed inside the setMaxDigits() + * function. So use this function to set the variable. DON'T JUST SET THE + * VALUE. USE THE FUNCTION. + * + * ZERO_ARRAY exists to hopefully speed up construction of BigInts(). By + * precalculating the zero array, we can just use slice(0) to make copies of + * it. Presumably this calls faster native code, as opposed to setting the + * elements one at a time. I have not done any timing tests to verify this + * claim. + * Max number = 10^16 - 2 = 9999999999999998; + * 2^53 = 9007199254740992; + */ + + + +apf.crypto.BigInt = (function() { + var biRadixBase = 2; + var biRadixBits = 16; + var bitsPerDigit = biRadixBits; + var biRadix = 1 << 16; // = 2^16 = 65536 + var biHalfRadix = biRadix >>> 1; + var biRadixSquared = biRadix * biRadix; + var maxDigitVal = biRadix - 1; + var maxInteger = 9999999999999998; + + /* + * maxDigits: + * Change this to accommodate your largest number size. Use setMaxDigits() + * to change it! + * + * In general, if you're working with numbers of size N bits, you'll need 2*N + * bits of storage. Each digit holds 16 bits. So, a 1024-bit key will need + * + * 1024 * 2 / 16 = 128 digits of storage. + */ + + var maxDigits; + var ZERO_ARRAY; + var bigZero, bigOne; + + function setMaxDigits(value) { + maxDigits = value; + ZERO_ARRAY = new Array(maxDigits); + for (var iza = 0; iza < ZERO_ARRAY.length; iza++) ZERO_ARRAY[iza] = 0; + bigZero = new BigInt(); + bigOne = new BigInt(); + bigOne.digits[0] = 1; + } + + setMaxDigits(20); + + // The maximum number of digits in base 10 you can convert to an + // integer without JavaScript throwing up on you. + var dpl10 = 15; + // lr10 = 10 ^ dpl10 + var lr10 = biFromNumber(1000000000000000); + + function BigInt(flag) { + if (typeof flag == "boolean" && flag == true) { + this.digits = null; + } else { + this.digits = ZERO_ARRAY.slice(0); + } + this.isNeg = false; + } + + function biFromDecimal(s) { + var isNeg = s.charAt(0) == '-'; + var i = isNeg ? 1 : 0; + var result; + // Skip leading zeros. + while (i < s.length && s.charAt(i) == '0') ++i; + if (i == s.length) { + result = new BigInt(); + } else { + var digitCount = s.length - i; + var fgl = digitCount % dpl10; + if (fgl == 0) fgl = dpl10; + result = biFromNumber(Number(s.substr(i, fgl))); + i += fgl; + while (i < s.length) { + result = biAdd(biMultiply(result, lr10), + biFromNumber(Number(s.substr(i, dpl10)))); + i += dpl10; + } + result.isNeg = isNeg; + } + return result; + } + + function biCopy(bi) { + var result = new BigInt(true); + result.digits = bi.digits.slice(0); + result.isNeg = bi.isNeg; + return result; + } + + function biFromNumber(i) { + var result = new BigInt(); + result.isNeg = i < 0; + i = Math.abs(i); + var j = 0; + while (i > 0) { + result.digits[j++] = i & maxDigitVal; + i = Math.floor(i / biRadix); + } + return result; + } + + function reverseStr(s) { + var result = ""; + for (var i = s.length - 1; i > -1; --i) { + result += s.charAt(i); + } + return result; + } + + var hexatrigesimalToChar = new Array( + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', + 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', + 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', + 'u', 'v', 'w', 'x', 'y', 'z' + ); + + function biToString(x, radix) { + // 2 <= radix <= 36 + var b = new BigInt(); + b.digits[0] = radix; + var digit, + qr = biDivideModulo(x, b), + result = hexatrigesimalToChar[qr[1].digits[0]]; + while (biCompare(qr[0], bigZero) == 1) { + qr = biDivideModulo(qr[0], b); + digit = qr[1].digits[0]; + result += hexatrigesimalToChar[qr[1].digits[0]]; + } + return (x.isNeg ? "-" : "") + reverseStr(result); + } + + function biToDecimal(x) { + var b = new BigInt(); + b.digits[0] = 10; + var qr = biDivideModulo(x, b); + var result = String(qr[1].digits[0]); + while (biCompare(qr[0], bigZero) == 1) { + qr = biDivideModulo(qr[0], b); + result += String(qr[1].digits[0]); + } + return (x.isNeg ? "-" : "") + reverseStr(result); + } + + var hexToChar = new Array('0', '1', '2', '3', '4', '5', '6', '7', '8', '9', + 'a', 'b', 'c', 'd', 'e', 'f'); + + function digitToHex(n) { + var i, + mask = 0xf, + result = ""; + for (i = 0; i < 4; ++i) { + result += hexToChar[n & mask]; + n >>>= 4; + } + return reverseStr(result); + } + + function biToHex(x) { + var result = ""; + var n = biHighIndex(x); + for (var i = biHighIndex(x); i > -1; --i) { + result += digitToHex(x.digits[i]); + } + return result; + } + + function charToHex(c) { + var ZERO = 48; + var NINE = ZERO + 9; + var littleA = 97; + var littleZ = littleA + 25; + var bigA = 65; + var bigZ = 65 + 25; + var result; + + if (c >= ZERO && c <= NINE) { + result = c - ZERO; + } else if (c >= bigA && c <= bigZ) { + result = 10 + c - bigA; + } else if (c >= littleA && c <= littleZ) { + result = 10 + c - littleA; + } else { + result = 0; + } + return result; + } + + function hexToDigit(s) { + var result = 0; + var sl = Math.min(s.length, 4); + for (var i = 0; i < sl; ++i) { + result <<= 4; + result |= charToHex(s.charCodeAt(i)) + } + return result; + } + + function biFromHex(s) { + var result = new BigInt(); + var sl = s.length; + for (var i = sl, j = 0; i > 0; i -= 4, ++j) { + result.digits[j] = hexToDigit(s.substr(Math.max(i - 4, 0), Math.min(i, 4))); + } + return result; + } + + function biFromString(s, radix) { + var isNeg = s.charAt(0) == '-'; + var istop = isNeg ? 1 : 0; + var result = new BigInt(); + var place = new BigInt(); + place.digits[0] = 1; // radix^0 + for (var i = s.length - 1; i >= istop; i--) { + var c = s.charCodeAt(i); + var digit = charToHex(c); + var biDigit = biMultiplyDigit(place, digit); + result = biAdd(result, biDigit); + place = biMultiplyDigit(place, radix); + } + result.isNeg = isNeg; + return result; + } + + function biDump(b) { + return (b.isNeg ? "-" : "") + b.digits.join(" "); + } + + function biAdd(x, y) { + var result; + + if (x.isNeg != y.isNeg) { + y.isNeg = !y.isNeg; + result = biSubtract(x, y); + y.isNeg = !y.isNeg; + } else { + result = new BigInt(); + var c = 0; + var n; + for (var i = 0; i < x.digits.length; ++i) { + n = x.digits[i] + y.digits[i] + c; + result.digits[i] = n % biRadix; + c = Number(n >= biRadix); + } + result.isNeg = x.isNeg; + } + return result; + } + + function biSubtract(x, y) { + var result; + if (x.isNeg != y.isNeg) { + y.isNeg = !y.isNeg; + result = biAdd(x, y); + y.isNeg = !y.isNeg; + } else { + result = new BigInt(); + var n, c; + c = 0; + for (var i = 0; i < x.digits.length; ++i) { + n = x.digits[i] - y.digits[i] + c; + result.digits[i] = n % biRadix; + // Stupid non-conforming modulus operation. + if (result.digits[i] < 0) result.digits[i] += biRadix; + c = 0 - Number(n < 0); + } + // Fix up the negative sign, if any. + if (c == -1) { + c = 0; + for (var i = 0; i < x.digits.length; ++i) { + n = 0 - result.digits[i] + c; + result.digits[i] = n % biRadix; + // Stupid non-conforming modulus operation. + if (result.digits[i] < 0) result.digits[i] += biRadix; + c = 0 - Number(n < 0); + } + // Result is opposite sign of arguments. + result.isNeg = !x.isNeg; + } else { + // Result is same sign. + result.isNeg = x.isNeg; + } + } + return result; + } + + function biHighIndex(x) { + var result = x.digits.length - 1; + while (result > 0 && x.digits[result] == 0) --result; + return result; + } + + function biNumBits(x) { + var n = biHighIndex(x); + var d = x.digits[n]; + var m = (n + 1) * bitsPerDigit; + var result; + for (result = m; result > m - bitsPerDigit; --result) { + if ((d & 0x8000) != 0) break; + d <<= 1; + } + return result; + } + + function biMultiply(x, y) { + var result = new BigInt(); + var c; + var n = biHighIndex(x); + var t = biHighIndex(y); + var u, uv, k, i, j; + + for (i = 0; i <= t; ++i) { + c = 0; + k = i; + for (j = 0; j <= n; ++j, ++k) { + uv = result.digits[k] + x.digits[j] * y.digits[i] + c; + result.digits[k] = uv & maxDigitVal; + c = uv >>> biRadixBits; + //c = Math.floor(uv / biRadix); + } + result.digits[i + n + 1] = c; + } + // Someone give me a logical xor, please. + result.isNeg = x.isNeg != y.isNeg; + return result; + } + + function biMultiplyDigit(x, y) { + var n, c, uv, + result = new BigInt(); + n = biHighIndex(x); + c = 0; + for (var j = 0; j <= n; ++j) { + uv = result.digits[j] + x.digits[j] * y + c; + result.digits[j] = uv & maxDigitVal; + c = uv >>> biRadixBits; + //c = Math.floor(uv / biRadix); + } + result.digits[1 + n] = c; + return result; + } + + function arrayCopy(src, srcStart, dest, destStart, n) { + var m = Math.min(srcStart + n, src.length); + for (var i = srcStart, j = destStart; i < m; ++i, ++j) { + dest[j] = src[i]; + } + } + + var highBitMasks = new Array(0x0000, 0x8000, 0xC000, 0xE000, 0xF000, 0xF800, + 0xFC00, 0xFE00, 0xFF00, 0xFF80, 0xFFC0, 0xFFE0, + 0xFFF0, 0xFFF8, 0xFFFC, 0xFFFE, 0xFFFF); + + function biShiftLeft(x, n) { + var digitCount = Math.floor(n / bitsPerDigit); + var result = new BigInt(); + arrayCopy(x.digits, 0, result.digits, digitCount, + result.digits.length - digitCount); + var bits = n % bitsPerDigit; + var rightBits = bitsPerDigit - bits; + for (var i = result.digits.length - 1, i1 = i - 1; i > 0; --i, --i1) { + result.digits[i] = ((result.digits[i] << bits) & maxDigitVal) | + ((result.digits[i1] & highBitMasks[bits]) >>> + (rightBits)); + } + result.digits[0] = ((result.digits[i] << bits) & maxDigitVal); + result.isNeg = x.isNeg; + return result; + } + + var lowBitMasks = new Array(0x0000, 0x0001, 0x0003, 0x0007, 0x000F, 0x001F, + 0x003F, 0x007F, 0x00FF, 0x01FF, 0x03FF, 0x07FF, + 0x0FFF, 0x1FFF, 0x3FFF, 0x7FFF, 0xFFFF); + + function biShiftRight(x, n) { + var digitCount = Math.floor(n / bitsPerDigit); + var result = new BigInt(); + arrayCopy(x.digits, digitCount, result.digits, 0, + x.digits.length - digitCount); + var bits = n % bitsPerDigit; + var leftBits = bitsPerDigit - bits; + for (var i = 0, i1 = i + 1; i < result.digits.length - 1; ++i, ++i1) { + result.digits[i] = (result.digits[i] >>> bits) | + ((result.digits[i1] & lowBitMasks[bits]) << leftBits); + } + result.digits[result.digits.length - 1] >>>= bits; + result.isNeg = x.isNeg; + return result; + } + + function biMultiplyByRadixPower(x, n) { + var result = new BigInt(); + arrayCopy(x.digits, 0, result.digits, n, result.digits.length - n); + return result; + } + + function biDivideByRadixPower(x, n) { + var result = new BigInt(); + arrayCopy(x.digits, n, result.digits, 0, result.digits.length - n); + return result; + } + + function biModuloByRadixPower(x, n) { + var result = new BigInt(); + arrayCopy(x.digits, 0, result.digits, 0, n); + return result; + } + + function biCompare(x, y) { + if (x.isNeg != y.isNeg) { + return 1 - 2 * Number(x.isNeg); + } + for (var i = x.digits.length - 1; i >= 0; --i) { + if (x.digits[i] != y.digits[i]) { + if (x.isNeg) { + return 1 - 2 * Number(x.digits[i] > y.digits[i]); + } else { + return 1 - 2 * Number(x.digits[i] < y.digits[i]); + } + } + } + return 0; + } + + function biDivideModulo(x, y) { + var nb = biNumBits(x); + var tb = biNumBits(y); + var origYIsNeg = y.isNeg; + var q, r; + if (nb < tb) { + // |x| < |y| + if (x.isNeg) { + q = biCopy(bigOne); + q.isNeg = !y.isNeg; + x.isNeg = false; + y.isNeg = false; + r = biSubtract(y, x); + // Restore signs, 'cause they're references. + x.isNeg = true; + y.isNeg = origYIsNeg; + } else { + q = new BigInt(); + r = biCopy(x); + } + return new Array(q, r); + } + + q = new BigInt(); + r = x; + + // Normalize Y. + var t = Math.ceil(tb / bitsPerDigit) - 1; + var lambda = 0; + while (y.digits[t] < biHalfRadix) { + y = biShiftLeft(y, 1); + ++lambda; + ++tb; + t = Math.ceil(tb / bitsPerDigit) - 1; + } + // Shift r over to keep the quotient constant. We'll shift the + // remainder back at the end. + r = biShiftLeft(r, lambda); + nb += lambda; // Update the bit count for x. + var n = Math.ceil(nb / bitsPerDigit) - 1; + + var b = biMultiplyByRadixPower(y, n - t); + while (biCompare(r, b) != -1) { + ++q.digits[n - t]; + r = biSubtract(r, b); + } + for (var i = n; i > t; --i) { + var ri = (i >= r.digits.length) ? 0 : r.digits[i]; + var ri1 = (i - 1 >= r.digits.length) ? 0 : r.digits[i - 1]; + var ri2 = (i - 2 >= r.digits.length) ? 0 : r.digits[i - 2]; + var yt = (t >= y.digits.length) ? 0 : y.digits[t]; + var yt1 = (t - 1 >= y.digits.length) ? 0 : y.digits[t - 1]; + if (ri == yt) { + q.digits[i - t - 1] = maxDigitVal; + } else { + q.digits[i - t - 1] = Math.floor((ri * biRadix + ri1) / yt); + } + + var c1 = q.digits[i - t - 1] * ((yt * biRadix) + yt1); + var c2 = (ri * biRadixSquared) + ((ri1 * biRadix) + ri2); + while (c1 > c2) { + --q.digits[i - t - 1]; + c1 = q.digits[i - t - 1] * ((yt * biRadix) | yt1); + c2 = (ri * biRadix * biRadix) + ((ri1 * biRadix) + ri2); + } + + b = biMultiplyByRadixPower(y, i - t - 1); + r = biSubtract(r, biMultiplyDigit(b, q.digits[i - t - 1])); + if (r.isNeg) { + r = biAdd(r, b); + --q.digits[i - t - 1]; + } + } + r = biShiftRight(r, lambda); + // Fiddle with the signs and stuff to make sure that 0 <= r < y. + q.isNeg = x.isNeg != origYIsNeg; + if (x.isNeg) { + if (origYIsNeg) { + q = biAdd(q, bigOne); + } else { + q = biSubtract(q, bigOne); + } + y = biShiftRight(y, lambda); + r = biSubtract(y, r); + } + // Check for the unbelievably stupid degenerate case of r == -0. + if (r.digits[0] == 0 && biHighIndex(r) == 0) r.isNeg = false; + + return new Array(q, r); + } + + function biDivide(x, y) { + return biDivideModulo(x, y)[0]; + } + + function biModulo(x, y) { + return biDivideModulo(x, y)[1]; + } + + function biMultiplyMod(x, y, m) { + return biModulo(biMultiply(x, y), m); + } + + function biPow(x, y) { + var result = bigOne; + var a = x; + while (true) { + if ((y & 1) != 0) result = biMultiply(result, a); + y >>= 1; + if (y == 0) break; + a = biMultiply(a, a); + } + return result; + } + + function biPowMod(x, y, m) { + var result = bigOne; + var a = x; + var k = y; + while (true) { + if ((k.digits[0] & 1) != 0) result = biMultiplyMod(result, a, m); + k = biShiftRight(k, 1); + if (k.digits[0] == 0 && biHighIndex(k) == 0) break; + a = biMultiplyMod(a, a, m); + } + return result; + } + + //publish public methods: + return { + construct: BigInt, + setMaxDigits: setMaxDigits, + fromDecimal: biFromDecimal, + copy: biCopy, + fromNumber: biFromNumber, + toString: biToString, + toDecimal: biToDecimal, + toHex: biToHex, + fromHex: biFromHex, + fromString: biFromString, + dump: biDump, + add: biAdd, + subtract: biSubtract, + highIndex: biHighIndex, + numBits: biNumBits, + multiply: biMultiply, + shiftLeft: biShiftLeft, + shiftRight: biShiftRight, + compare: biCompare, + pow: biPow, + powMod: biPowMod, + divide: biDivide, + divideByRadixPower: biDivideByRadixPower, + moduloByRadixPower: biModuloByRadixPower + }; +})(); + + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/core/crypto/blowfish.js)SIZE(26046)TIME(Mon, 10 Jan 2011 10:53:11 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + + +/* + * Copyright (C) 2005 - Stephen Griffin, i-code.co.uk + * Copyright (C) 2005 - Raphael Derosso Pereira - thyAPI adaptation + * + */ + +(function(global) { + +if (typeof apf.crypto == "undefined") apf.crypto = {}; + +function stringtoints(instring){ + var binary = []; + var i, ch; + + for (i = 0; i < instring.length; i++) { + ch = instring.charCodeAt(i); + binary[i >> 2] |= ch << (3 - i % 4) * 8; + } + return binary; +} + +function intstostring(intvalues){ + var outstring = new String(); + + for (var i = 0; i < intvalues.length; i++) { + outstring += String.fromCharCode(intvalues[i] >>> 24, + (intvalues[i] >>> 16) & 0xff, (intvalues[i] >>> 8) & 0xff, + intvalues[i] & 0xff); + } + return outstring; +} + +function base64tobin(codestring){ + var temp, i = 0; + var binary = []; + + while (i < codestring.length * 6) { + temp = codestring.charCodeAt(i / 6); + + if (temp > 47 && temp < 58) + temp -= 48; + if (temp > 62 && temp < 91) + temp -= 53; + if (temp > 96 && temp < 123) + temp -= 59; + + switch (i & 0x1f) { //i%32 + case 0: + binary[i >> 5] = temp; + i += 6; + break; + case 28: + binary[i >> 5] = (binary[i >> 5] << 4) | (temp >> 2); + i += 4; + binary[i >> 5] = temp & 0x03; + i += 2; + break; + case 30: + binary[i >> 5] = (binary[i >> 5] << 2) | (temp >> 4); + i += 2; + binary[i >> 5] = temp & 0x0f; + i += 4; + break; + default: + binary[i >> 5] = (binary[i >> 5] << 6) | temp; + i += 6; + } + } + return binary; +} + +function base64encode(binary){ + var temp, x, + bincount = 0, + codestring = "", + chars = "0123456789?@ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; + + while (bincount < binary.length) { + for (x = 0; x < 2; x++) { + temp = binary[bincount] >>> 26; + codestring += chars.charAt(temp); + temp = (binary[bincount] >>> 20) & 0x3f; + codestring += chars.charAt(temp); + + temp = (binary[bincount] >>> 14) & 0x3f; + codestring += chars.charAt(temp); + + temp = (binary[bincount] >>> 8) & 0x3f; + codestring += chars.charAt(temp); + + temp = (binary[bincount] >>> 2) & 0x3f; + codestring += chars.charAt(temp); + + temp = (binary[bincount++] & 0x3) << 4; + temp = temp | (binary[bincount] >>> 28); + codestring += chars.charAt(temp); + + temp = (binary[bincount] >>> 22) & 0x3f; + codestring += chars.charAt(temp); + + temp = (binary[bincount] >>> 16) & 0x3f; + codestring += chars.charAt(temp); + + temp = (binary[bincount] >>> 10) & 0x3f; + codestring += chars.charAt(temp); + + temp = (binary[bincount] >>> 4) & 0x3f; + codestring += chars.charAt(temp); + + temp = (binary[bincount++] & 0xf) << 2; + temp = temp | (binary[bincount] >>> 30); + codestring += chars.charAt(temp); + + temp = (binary[bincount] >>> 24) & 0x3f; + codestring += chars.charAt(temp); + + temp = (binary[bincount] >>> 18) & 0x3f; + codestring += chars.charAt(temp); + + temp = (binary[bincount] >>> 12) & 0x3f; + codestring += chars.charAt(temp); + + temp = (binary[bincount] >>> 6) & 0x3f; + codestring += chars.charAt(temp); + + temp = binary[bincount++] & 0x3f; + codestring += chars.charAt(temp); + } + } + return codestring; +} + +/** + * Class: Blowfish + * + * Blowfish cypher class. + * + * Algorithm gently taken from i-code.co.uk. Thanks Stephen! + * + */ +apf.crypto.Blowfish = function(){this.init.apply(this, arguments);}; +apf.crypto.Blowfish.prototype = { + init: function(m){ + this.P; + this.S; + this.previous_xHi = 0; + this.previous_xLo = 0; + }, + + /** + * Encodes the text passed with key + * + * @param {String} str The text to be encoded + * @param {String} pass The key to be used to encode the text + * @return Base64 encoded string + * @type String + */ + encode: function(str, pass){ + var ciphertext = new String(""), + IV = new Array(), + x; + + for (x = 0; x < 4; x++) { + if (typeof(this.customRand) == 'function') { + IV[x] = this.customRand(); + } else { + IV[x] = Math.floor(Math.random() * 0xFFFFFFFF); + } + } + + var key = this.passtokey(pass, IV[2], IV[3], IV[4]); + IV[4] = key[14];//hashHi + IV[5] = key[15];//hashLo + this.initialise(key, IV); + + var binary = stringtoints(str); + this.encipher_array(binary); + + return { + code: base64encode(binary), + init: base64encode(IV) + }; + }, + + /** + * Decodes a base64 encoded string to the original one, if key is correct + * + * @param {String} code The cyphered code + * @param {String} pass The key to be used + * @param {Number} init The base64 initialization int + */ + decode: function(code, pass, init){ + var initialints = base64tobin(init); + var key = this.passtokey(pass, initialints[2], initialints[3]); + + if ((initialints[4] == key[14]) && (initialints[5] == key[15])) { //check password hash + this.initialise(key, initialints); + var bincode = base64tobin(code); + this.decipher(bincode); + return intstostring(bincode); + } + + return null; + }, + + setConstants: function(){ + var s0 = [0xD1310BA6, 0x98DFB5AC, 0x2FFD72DB, 0xD01ADFB7, 0xB8E1AFED, + 0x6A267E96, 0xBA7C9045, 0xF12C7F99, 0x24A19947, 0xB3916CF7, 0x0801F2E2, + 0x858EFC16, 0x636920D8, 0x71574E69, 0xA458FEA3, 0xF4933D7E, 0x0D95748F, + 0x728EB658, 0x718BCD58, 0x82154AEE, 0x7B54A41D, 0xC25A59B5, 0x9C30D539, + 0x2AF26013, 0xC5D1B023, 0x286085F0, 0xCA417918, 0xB8DB38EF, 0x8E79DCB0, + 0x603A180E, 0x6C9E0E8B, 0xB01E8A3E, 0xD71577C1, 0xBD314B27, 0x78AF2FDA, + 0x55605C60, 0xE65525F3, 0xAA55AB94, 0x57489862, 0x63E81440, 0x55CA396A, + 0x2AAB10B6, 0xB4CC5C34, 0x1141E8CE, 0xA15486AF, 0x7C72E993, 0xB3EE1411, + 0x636FBC2A, 0x2BA9C55D, 0x741831F6, 0xCE5C3E16, 0x9B87931E, 0xAFD6BA33, + 0x6C24CF5C, 0x7A325381, 0x28958677, 0x3B8F4898, 0x6B4BB9AF, 0xC4BFE81B, + 0x66282193, 0x61D809CC, 0xFB21A991, 0x487CAC60, 0x5DEC8032, 0xEF845D5D, + 0xE98575B1, 0xDC262302, 0xEB651B88, 0x23893E81, 0xD396ACC5, 0x0F6D6FF3, + 0x83F44239, 0x2E0B4482, 0xA4842004, 0x69C8F04A, 0x9E1F9B5E, 0x21C66842, + 0xF6E96C9A, 0x670C9C61, 0xABD388F0, 0x6A51A0D2, 0xD8542F68, 0x960FA728, + 0xAB5133A3, 0x6EEF0B6C, 0x137A3BE4, 0xBA3BF050, 0x7EFB2A98, 0xA1F1651D, + 0x39AF0176, 0x66CA593E, 0x82430E88, 0x8CEE8619, 0x456F9FB4, 0x7D84A5C3, + 0x3B8B5EBE, 0xE06F75D8, 0x85C12073, 0x401A449F, 0x56C16AA6, 0x4ED3AA62, + 0x363F7706, 0x1BFEDF72, 0x429B023D, 0x37D0D724, 0xD00A1248, 0xDB0FEAD3, + 0x49F1C09B, 0x075372C9, 0x80991B7B, 0x25D479D8, 0xF6E8DEF7, 0xE3FE501A, + 0xB6794C3B, 0x976CE0BD, 0x04C006BA, 0xC1A94FB6, 0x409F60C4, 0x5E5C9EC2, + 0x196A2463, 0x68FB6FAF, 0x3E6C53B5, 0x1339B2EB, 0x3B52EC6F, 0x6DFC511F, + 0x9B30952C, 0xCC814544, 0xAF5EBD09, 0xBEE3D004, 0xDE334AFD, 0x660F2807, + 0x192E4BB3, 0xC0CBA857, 0x45C8740F, 0xD20B5F39, 0xB9D3FBDB, 0x5579C0BD, + 0x1A60320A, 0xD6A100C6, 0x402C7279, 0x679F25FE, 0xFB1FA3CC, 0x8EA5E9F8, + 0xDB3222F8, 0x3C7516DF, 0xFD616B15, 0x2F501EC8, 0xAD0552AB, 0x323DB5FA, + 0xFD238760, 0x53317B48, 0x3E00DF82, 0x9E5C57BB, 0xCA6F8CA0, 0x1A87562E, + 0xDF1769DB, 0xD542A8F6, 0x287EFFC3, 0xAC6732C6, 0x8C4F5573, 0x695B27B0, + 0xBBCA58C8, 0xE1FFA35D, 0xB8F011A0, 0x10FA3D98, 0xFD2183B8, 0x4AFCB56C, + 0x2DD1D35B, 0x9A53E479, 0xB6F84565, 0xD28E49BC, 0x4BFB9790, 0xE1DDF2DA, + 0xA4CB7E33, 0x62FB1341, 0xCEE4C6E8, 0xEF20CADA, 0x36774C01, 0xD07E9EFE, + 0x2BF11FB4, 0x95DBDA4D, 0xAE909198, 0xEAAD8E71, 0x6B93D5A0, 0xD08ED1D0, + 0xAFC725E0, 0x8E3C5B2F, 0x8E7594B7, 0x8FF6E2FB, 0xF2122B64, 0x8888B812, + 0x900DF01C, 0x4FAD5EA0, 0x688FC31C, 0xD1CFF191, 0xB3A8C1AD, 0x2F2F2218, + 0xBE0E1777, 0xEA752DFE, 0x8B021FA1, 0xE5A0CC0F, 0xB56F74E8, 0x18ACF3D6, + 0xCE89E299, 0xB4A84FE0, 0xFD13E0B7, 0x7CC43B81, 0xD2ADA8D9, 0x165FA266, + 0x80957705, 0x93CC7314, 0x211A1477, 0xE6AD2065, 0x77B5FA86, 0xC75442F5, + 0xFB9D35CF, 0xEBCDAF0C, 0x7B3E89A0, 0xD6411BD3, 0xAE1E7E49, 0x00250E2D, + 0x2071B35E, 0x226800BB, 0x57B8E0AF, 0x2464369B, 0xF009B91E, 0x5563911D, + 0x59DFA6AA, 0x78C14389, 0xD95A537F, 0x207D5BA2, 0x02E5B9C5, 0x83260376, + 0x6295CFA9, 0x11C81968, 0x4E734A41, 0xB3472DCA, 0x7B14A94A, 0x1B510052, + 0x9A532915, 0xD60F573F, 0xBC9BC6E4, 0x2B60A476, 0x81E67400, 0x08BA6FB5, + 0x571BE91F, 0xF296EC6B, 0x2A0DD915, 0xB6636521, 0xE7B9F9B6, 0xFF34052E, + 0xC5855664, 0x53B02D5D, 0xA99F8FA1, 0x08BA4799, 0x6E85076A]; + + var s1 = [0x4B7A70E9, 0xB5B32944, 0xDB75092E, 0xC4192623, 0xAD6EA6B0, 0x49A7DF7D, + 0x9CEE60B8, 0x8FEDB266, 0xECAA8C71, 0x699A17FF, 0x5664526C, 0xC2B19EE1, + 0x193602A5, 0x75094C29, 0xA0591340, 0xE4183A3E, 0x3F54989A, 0x5B429D65, + 0x6B8FE4D6, 0x99F73FD6, 0xA1D29C07, 0xEFE830F5, 0x4D2D38E6, 0xF0255DC1, + 0x4CDD2086, 0x8470EB26, 0x6382E9C6, 0x021ECC5E, 0x09686B3F, 0x3EBAEFC9, + 0x3C971814, 0x6B6A70A1, 0x687F3584, 0x52A0E286, 0xB79C5305, 0xAA500737, + 0x3E07841C, 0x7FDEAE5C, 0x8E7D44EC, 0x5716F2B8, 0xB03ADA37, 0xF0500C0D, + 0xF01C1F04, 0x0200B3FF, 0xAE0CF51A, 0x3CB574B2, 0x25837A58, 0xDC0921BD, + 0xD19113F9, 0x7CA92FF6, 0x94324773, 0x22F54701, 0x3AE5E581, 0x37C2DADC, + 0xC8B57634, 0x9AF3DDA7, 0xA9446146, 0x0FD0030E, 0xECC8C73E, 0xA4751E41, + 0xE238CD99, 0x3BEA0E2F, 0x3280BBA1, 0x183EB331, 0x4E548B38, 0x4F6DB908, + 0x6F420D03, 0xF60A04BF, 0x2CB81290, 0x24977C79, 0x5679B072, 0xBCAF89AF, + 0xDE9A771F, 0xD9930810, 0xB38BAE12, 0xDCCF3F2E, 0x5512721F, 0x2E6B7124, + 0x501ADDE6, 0x9F84CD87, 0x7A584718, 0x7408DA17, 0xBC9F9ABC, 0xE94B7D8C, + 0xEC7AEC3A, 0xDB851DFA, 0x63094366, 0xC464C3D2, 0xEF1C1847, 0x3215D908, + 0xDD433B37, 0x24C2BA16, 0x12A14D43, 0x2A65C451, 0x50940002, 0x133AE4DD, + 0x71DFF89E, 0x10314E55, 0x81AC77D6, 0x5F11199B, 0x043556F1, 0xD7A3C76B, + 0x3C11183B, 0x5924A509, 0xF28FE6ED, 0x97F1FBFA, 0x9EBABF2C, 0x1E153C6E, + 0x86E34570, 0xEAE96FB1, 0x860E5E0A, 0x5A3E2AB3, 0x771FE71C, 0x4E3D06FA, + 0x2965DCB9, 0x99E71D0F, 0x803E89D6, 0x5266C825, 0x2E4CC978, 0x9C10B36A, + 0xC6150EBA, 0x94E2EA78, 0xA5FC3C53, 0x1E0A2DF4, 0xF2F74EA7, 0x361D2B3D, + 0x1939260F, 0x19C27960, 0x5223A708, 0xF71312B6, 0xEBADFE6E, 0xEAC31F66, + 0xE3BC4595, 0xA67BC883, 0xB17F37D1, 0x018CFF28, 0xC332DDEF, 0xBE6C5AA5, + 0x65582185, 0x68AB9802, 0xEECEA50F, 0xDB2F953B, 0x2AEF7DAD, 0x5B6E2F84, + 0x1521B628, 0x29076170, 0xECDD4775, 0x619F1510, 0x13CCA830, 0xEB61BD96, + 0x0334FE1E, 0xAA0363CF, 0xB5735C90, 0x4C70A239, 0xD59E9E0B, 0xCBAADE14, + 0xEECC86BC, 0x60622CA7, 0x9CAB5CAB, 0xB2F3846E, 0x648B1EAF, 0x19BDF0CA, + 0xA02369B9, 0x655ABB50, 0x40685A32, 0x3C2AB4B3, 0x319EE9D5, 0xC021B8F7, + 0x9B540B19, 0x875FA099, 0x95F7997E, 0x623D7DA8, 0xF837889A, 0x97E32D77, + 0x11ED935F, 0x16681281, 0x0E358829, 0xC7E61FD6, 0x96DEDFA1, 0x7858BA99, + 0x57F584A5, 0x1B227263, 0x9B83C3FF, 0x1AC24696, 0xCDB30AEB, 0x532E3054, + 0x8FD948E4, 0x6DBC3128, 0x58EBF2EF, 0x34C6FFEA, 0xFE28ED61, 0xEE7C3C73, + 0x5D4A14D9, 0xE864B7E3, 0x42105D14, 0x203E13E0, 0x45EEE2B6, 0xA3AAABEA, + 0xDB6C4F15, 0xFACB4FD0, 0xC742F442, 0xEF6ABBB5, 0x654F3B1D, 0x41CD2105, + 0xD81E799E, 0x86854DC7, 0xE44B476A, 0x3D816250, 0xCF62A1F2, 0x5B8D2646, + 0xFC8883A0, 0xC1C7B6A3, 0x7F1524C3, 0x69CB7492, 0x47848A0B, 0x5692B285, + 0x095BBF00, 0xAD19489D, 0x1462B174, 0x23820E00, 0x58428D2A, 0x0C55F5EA, + 0x1DADF43E, 0x233F7061, 0x3372F092, 0x8D937E41, 0xD65FECF1, 0x6C223BDB, + 0x7CDE3759, 0xCBEE7460, 0x4085F2A7, 0xCE77326E, 0xA6078084, 0x19F8509E, + 0xE8EFD855, 0x61D99735, 0xA969A7AA, 0xC50C06C2, 0x5A04ABFC, 0x800BCADC, + 0x9E447A2E, 0xC3453484, 0xFDD56705, 0x0E1E9EC9, 0xDB73DBD3, 0x105588CD, + 0x675FDA79, 0xE3674340, 0xC5C43465, 0x713E38D8, 0x3D28F89E, 0xF16DFF20, + 0x153E21E7, 0x8FB03D4A, 0xE6E39F2B, 0xDB83ADF7]; + + var s2 = [0xE93D5A68, 0x948140F7, 0xF64C261C, 0x94692934, 0x411520F7, 0x7602D4F7, + 0xBCF46B2E, 0xD4A20068, 0xD4082471, 0x3320F46A, 0x43B7D4B7, 0x500061AF, + 0x1E39F62E, 0x97244546, 0x14214F74, 0xBF8B8840, 0x4D95FC1D, 0x96B591AF, + 0x70F4DDD3, 0x66A02F45, 0xBFBC09EC, 0x03BD9785, 0x7FAC6DD0, 0x31CB8504, + 0x96EB27B3, 0x55FD3941, 0xDA2547E6, 0xABCA0A9A, 0x28507825, 0x530429F4, + 0x0A2C86DA, 0xE9B66DFB, 0x68DC1462, 0xD7486900, 0x680EC0A4, 0x27A18DEE, + 0x4F3FFEA2, 0xE887AD8C, 0xB58CE006, 0x7AF4D6B6, 0xAACE1E7C, 0xD3375FEC, + 0xCE78A399, 0x406B2A42, 0x20FE9E35, 0xD9F385B9, 0xEE39D7AB, 0x3B124E8B, + 0x1DC9FAF7, 0x4B6D1856, 0x26A36631, 0xEAE397B2, 0x3A6EFA74, 0xDD5B4332, + 0x6841E7F7, 0xCA7820FB, 0xFB0AF54E, 0xD8FEB397, 0x454056AC, 0xBA489527, + 0x55533A3A, 0x20838D87, 0xFE6BA9B7, 0xD096954B, 0x55A867BC, 0xA1159A58, + 0xCCA92963, 0x99E1DB33, 0xA62A4A56, 0x3F3125F9, 0x5EF47E1C, 0x9029317C, + 0xFDF8E802, 0x04272F70, 0x80BB155C, 0x05282CE3, 0x95C11548, 0xE4C66D22, + 0x48C1133F, 0xC70F86DC, 0x07F9C9EE, 0x41041F0F, 0x404779A4, 0x5D886E17, + 0x325F51EB, 0xD59BC0D1, 0xF2BCC18F, 0x41113564, 0x257B7834, 0x602A9C60, + 0xDFF8E8A3, 0x1F636C1B, 0x0E12B4C2, 0x02E1329E, 0xAF664FD1, 0xCAD18115, + 0x6B2395E0, 0x333E92E1, 0x3B240B62, 0xEEBEB922, 0x85B2A20E, 0xE6BA0D99, + 0xDE720C8C, 0x2DA2F728, 0xD0127845, 0x95B794FD, 0x647D0862, 0xE7CCF5F0, + 0x5449A36F, 0x877D48FA, 0xC39DFD27, 0xF33E8D1E, 0x0A476341, 0x992EFF74, + 0x3A6F6EAB, 0xF4F8FD37, 0xA812DC60, 0xA1EBDDF8, 0x991BE14C, 0xDB6E6B0D, + 0xC67B5510, 0x6D672C37, 0x2765D43B, 0xDCD0E804, 0xF1290DC7, 0xCC00FFA3, + 0xB5390F92, 0x690FED0B, 0x667B9FFB, 0xCEDB7D9C, 0xA091CF0B, 0xD9155EA3, + 0xBB132F88, 0x515BAD24, 0x7B9479BF, 0x763BD6EB, 0x37392EB3, 0xCC115979, + 0x8026E297, 0xF42E312D, 0x6842ADA7, 0xC66A2B3B, 0x12754CCC, 0x782EF11C, + 0x6A124237, 0xB79251E7, 0x06A1BBE6, 0x4BFB6350, 0x1A6B1018, 0x11CAEDFA, + 0x3D25BDD8, 0xE2E1C3C9, 0x44421659, 0x0A121386, 0xD90CEC6E, 0xD5ABEA2A, + 0x64AF674E, 0xDA86A85F, 0xBEBFE988, 0x64E4C3FE, 0x9DBC8057, 0xF0F7C086, + 0x60787BF8, 0x6003604D, 0xD1FD8346, 0xF6381FB0, 0x7745AE04, 0xD736FCCC, + 0x83426B33, 0xF01EAB71, 0xB0804187, 0x3C005E5F, 0x77A057BE, 0xBDE8AE24, + 0x55464299, 0xBF582E61, 0x4E58F48F, 0xF2DDFDA2, 0xF474EF38, 0x8789BDC2, + 0x5366F9C3, 0xC8B38E74, 0xB475F255, 0x46FCD9B9, 0x7AEB2661, 0x8B1DDF84, + 0x846A0E79, 0x915F95E2, 0x466E598E, 0x20B45770, 0x8CD55591, 0xC902DE4C, + 0xB90BACE1, 0xBB8205D0, 0x11A86248, 0x7574A99E, 0xB77F19B6, 0xE0A9DC09, + 0x662D09A1, 0xC4324633, 0xE85A1F02, 0x09F0BE8C, 0x4A99A025, 0x1D6EFE10, + 0x1AB93D1D, 0x0BA5A4DF, 0xA186F20F, 0x2868F169, 0xDCB7DA83, 0x573906FE, + 0xA1E2CE9B, 0x4FCD7F52, 0x50115E01, 0xA70683FA, 0xA002B5C4, 0x0DE6D027, + 0x9AF88C27, 0x773F8641, 0xC3604C06, 0x61A806B5, 0xF0177A28, 0xC0F586E0, + 0x006058AA, 0x30DC7D62, 0x11E69ED7, 0x2338EA63, 0x53C2DD94, 0xC2C21634, + 0xBBCBEE56, 0x90BCB6DE, 0xEBFC7DA1, 0xCE591D76, 0x6F05E409, 0x4B7C0188, + 0x39720A3D, 0x7C927C24, 0x86E3725F, 0x724D9DB9, 0x1AC15BB4, 0xD39EB8FC, + 0xED545578, 0x08FCA5B5, 0xD83D7CD3, 0x4DAD0FC4, 0x1E50EF5E, 0xB161E6F8, + 0xA28514D9, 0x6C51133C, 0x6FD5C7E7, 0x56E14EC4, 0x362ABFCE, 0xDDC6C837, + 0xD79A3234, 0x92638212, 0x670EFA8E, 0x406000E0]; + + var s3 = [0x3A39CE37, 0xD3FAF5CF, 0xABC27737, 0x5AC52D1B, 0x5CB0679E, 0x4FA33742, + 0xD3822740, 0x99BC9BBE, 0xD5118E9D, 0xBF0F7315, 0xD62D1C7E, 0xC700C47B, + 0xB78C1B6B, 0x21A19045, 0xB26EB1BE, 0x6A366EB4, 0x5748AB2F, 0xBC946E79, + 0xC6A376D2, 0x6549C2C8, 0x530FF8EE, 0x468DDE7D, 0xD5730A1D, 0x4CD04DC6, + 0x2939BBDB, 0xA9BA4650, 0xAC9526E8, 0xBE5EE304, 0xA1FAD5F0, 0x6A2D519A, + 0x63EF8CE2, 0x9A86EE22, 0xC089C2B8, 0x43242EF6, 0xA51E03AA, 0x9CF2D0A4, + 0x83C061BA, 0x9BE96A4D, 0x8FE51550, 0xBA645BD6, 0x2826A2F9, 0xA73A3AE1, + 0x4BA99586, 0xEF5562E9, 0xC72FEFD3, 0xF752F7DA, 0x3F046F69, 0x77FA0A59, + 0x80E4A915, 0x87B08601, 0x9B09E6AD, 0x3B3EE593, 0xE990FD5A, 0x9E34D797, + 0x2CF0B7D9, 0x022B8B51, 0x96D5AC3A, 0x017DA67D, 0xD1CF3ED6, 0x7C7D2D28, + 0x1F9F25CF, 0xADF2B89B, 0x5AD6B472, 0x5A88F54C, 0xE029AC71, 0xE019A5E6, + 0x47B0ACFD, 0xED93FA9B, 0xE8D3C48D, 0x283B57CC, 0xF8D56629, 0x79132E28, + 0x785F0191, 0xED756055, 0xF7960E44, 0xE3D35E8C, 0x15056DD4, 0x88F46DBA, + 0x03A16125, 0x0564F0BD, 0xC3EB9E15, 0x3C9057A2, 0x97271AEC, 0xA93A072A, + 0x1B3F6D9B, 0x1E6321F5, 0xF59C66FB, 0x26DCF319, 0x7533D928, 0xB155FDF5, + 0x03563482, 0x8ABA3CBB, 0x28517711, 0xC20AD9F8, 0xABCC5167, 0xCCAD925F, + 0x4DE81751, 0x3830DC8E, 0x379D5862, 0x9320F991, 0xEA7A90C2, 0xFB3E7BCE, + 0x5121CE64, 0x774FBE32, 0xA8B6E37E, 0xC3293D46, 0x48DE5369, 0x6413E680, + 0xA2AE0810, 0xDD6DB224, 0x69852DFD, 0x09072166, 0xB39A460A, 0x6445C0DD, + 0x586CDECF, 0x1C20C8AE, 0x5BBEF7DD, 0x1B588D40, 0xCCD2017F, 0x6BB4E3BB, + 0xDDA26A7E, 0x3A59FF45, 0x3E350A44, 0xBCB4CDD5, 0x72EACEA8, 0xFA6484BB, + 0x8D6612AE, 0xBF3C6F47, 0xD29BE463, 0x542F5D9E, 0xAEC2771B, 0xF64E6370, + 0x740E0D8D, 0xE75B1357, 0xF8721671, 0xAF537D5D, 0x4040CB08, 0x4EB4E2CC, + 0x34D2466A, 0x0115AF84, 0xE1B00428, 0x95983A1D, 0x06B89FB4, 0xCE6EA048, + 0x6F3F3B82, 0x3520AB82, 0x011A1D4B, 0x277227F8, 0x611560B1, 0xE7933FDC, + 0xBB3A792B, 0x344525BD, 0xA08839E1, 0x51CE794B, 0x2F32C9B7, 0xA01FBAC9, + 0xE01CC87E, 0xBCC7D1F6, 0xCF0111C3, 0xA1E8AAC7, 0x1A908749, 0xD44FBD9A, + 0xD0DADECB, 0xD50ADA38, 0x0339C32A, 0xC6913667, 0x8DF9317C, 0xE0B12B4F, + 0xF79E59B7, 0x43F5BB3A, 0xF2D519FF, 0x27D9459C, 0xBF97222C, 0x15E6FC2A, + 0x0F91FC71, 0x9B941525, 0xFAE59361, 0xCEB69CEB, 0xC2A86459, 0x12BAA8D1, + 0xB6C1075E, 0xE3056A0C, 0x10D25065, 0xCB03A442, 0xE0EC6E0E, 0x1698DB3B, + 0x4C98A0BE, 0x3278E964, 0x9F1F9532, 0xE0D392DF, 0xD3A0342B, 0x8971F21E, + 0x1B0A7441, 0x4BA3348C, 0xC5BE7120, 0xC37632D8, 0xDF359F8D, 0x9B992F2E, + 0xE60B6F47, 0x0FE3F11D, 0xE54CDA54, 0x1EDAD891, 0xCE6279CF, 0xCD3E7E6F, + 0x1618B166, 0xFD2C1D05, 0x848FD2C5, 0xF6FB2299, 0xF523F357, 0xA6327623, + 0x93A83531, 0x56CCCD02, 0xACF08162, 0x5A75EBB5, 0x6E163697, 0x88D273CC, + 0xDE966292, 0x81B949D0, 0x4C50901B, 0x71C65614, 0xE6C6C7BD, 0x327A140A, + 0x45E1D006, 0xC3F27B9A, 0xC9AA53FD, 0x62A80F00, 0xBB25BFE2, 0x35BDD2F6, + 0x71126905, 0xB2040222, 0xB6CBCF7C, 0xCD769C2B, 0x53113EC0, 0x1640E3D3, + 0x38ABBD60, 0x2547ADF0, 0xBA38209C, 0xF746CE76, 0x77AFA1C5, 0x20756060, + 0x85CBFE4E, 0x8AE88DD8, 0x7AAAF9B0, 0x4CF9AA7E, 0x1948C25C, 0x02FB8A8C, + 0x01C36AE4, 0xD6EBE1F9, 0x90D4F869, 0xA65CDEA0, 0x3F09252D, 0xC208E69F, + 0xB74E6132, 0xCE77E25B, 0x578FDFE3, 0x3AC372E6]; + + this.S = [s0, s1, s2, s3]; + + this.P = [0x243F6A88, 0x85A308D3, 0x13198A2E, 0x03707344, 0xA4093822, 0x299F31D0, + 0x082EFA98, 0xEC4E6C89, 0x452821E6, 0x38D01377, 0xBE5466CF, 0x34E90C6C, + 0xC0AC29B7, 0xC97C50DD, 0x3F84D5B5, 0xB5470917, 0x9216D5D9, 0x8979FB1B]; + }, + + encipher: function(x){ + var xHi = x[0]; + var xLo = x[1]; + var Round = 0; + + xHi ^= this.P[0]; + + while (Round < 16) { + xLo ^= (((this.S[0][xHi >>> 24] + this.S[1][(xHi >>> 16) & 0x0ff]) + ^ this.S[2][(xHi >>> 8) & 0x0ff]) + this.S[3][xHi & 0x0ff]) ^ this.P[++Round]; + xHi ^= (((this.S[0][xLo >>> 24] + this.S[1][(xLo >>> 16) & 0x0ff]) + ^ this.S[2][(xLo >>> 8) & 0x0ff]) + this.S[3][xLo & 0x0ff]) ^ this.P[++Round]; + } + + xLo ^= this.P[16 + 1]; + + x[0] = xLo; + x[1] = xHi; + }, + + encipher_array: function(x){ + var i, count, xHi, xLo, Round, temp; + + for (i = 0; (i < x.length) || (x.length % 6); i += 2) { + xHi = x[i]; + xLo = x[i + 1]; + + xHi ^= this.previous_xHi; + xLo ^= this.previous_xLo; + + xHi ^= this.P[0]; + + Round = 0; + + while (Round < 16) { + xLo ^= (((this.S[0][xHi >>> 24] + this.S[1][(xHi >>> 16) & 0x0ff]) + ^ this.S[2][(xHi >>> 8) & 0x0ff]) + this.S[3][xHi & 0x0ff]) ^ this.P[++Round]; + xHi ^= (((this.S[0][xLo >>> 24] + this.S[1][(xLo >>> 16) & 0x0ff]) + ^ this.S[2][(xLo >>> 8) & 0x0ff]) + this.S[3][xLo & 0x0ff]) ^ this.P[++Round]; + } + + xLo ^= this.P[17]; + + this.previous_xHi = xLo; + this.previous_xLo = xHi; + + x[i + 1] = xHi; + x[i] = xLo; + } + }, + + decipher: function(x){ + var i, xHi, xLo, Round, temp; + + for (i = 0; i < x.length; i += 2) { + xHi = x[i]; + xLo = x[i + 1]; + + xHi ^= this.P[17]; + + Round = 16; + + while (Round > 0) { + xLo ^= (((this.S[0][xHi >>> 24] + this.S[1][(xHi >>> 16) & 0xff]) + ^ this.S[2][(xHi >>> 8) & 0xff]) + this.S[3][xHi & 0xff]) ^ this.P[Round--]; + xHi ^= (((this.S[0][xLo >>> 24] + this.S[1][(xLo >>> 16) & 0xff]) + ^ this.S[2][(xLo >>> 8) & 0xff]) + this.S[3][xLo & 0xff]) ^ this.P[Round--]; + } + + xLo ^= this.P[0]; + + temp = x[i]; + x[i] = xLo ^ this.previous_xHi; + this.previous_xHi = temp; + + temp = x[i + 1]; + x[i + 1] = xHi ^ this.previous_xLo; + this.previous_xLo = temp; + } + }, + + initialise: function(key, IV){ + var i, j, k, data; + var block = new Array(2); + + this.previous_xHi = IV[0]; + this.previous_xLo = IV[1]; + + this.setConstants(); + + for (j = 0, i = 0; i < 16 + 2; ++i) { + this.P[i] = this.P[i] ^ key[j]; + j = (j + 1) % 14; + } + + for (i = 0; i < 16 + 2; i += 2) { + this.encipher(block); + + this.P[i] = block[0]; + this.P[i + 1] = block[1]; + } + + for (i = 0; i < 4; ++i) { + for (j = 0; j < 256; j += 2) { + this.encipher(block); + this.S[i][j] = block[0]; + this.S[i][j + 1] = block[1]; + } + } + }, + + passtokey: function(pass, HiIV, LoIV){ + var binarypassword = stringtoints(pass); + var block = new Array(2); + var key = new Array(16); + var i = 0, j; + + block[0] = HiIV; + block[1] = LoIV; + + do { + this.initialise(key, block, 16); + + for (j = 0; j < 16;) { + block[0] ^= binarypassword[i]; + + this.encipher(block); + + key[j++] ^= block[0]; + key[j++] ^= block[1]; + } + } while (++i < binarypassword.length) + + return key; + } +}; + +})(this); + + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/core/crypto/md4.js)SIZE(9799)TIME(Mon, 10 Jan 2011 10:53:11 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + + +apf.crypto.MD4 = { + /* + * Configurable variables. You may need to tweak these to be compatible with + * the server-side, but the defaults work in most cases. + */ + hexcase: 0, /* hex output format. 0 - lowercase; 1 - uppercase */ + b64pad : "", /* base-64 pad character. "=" for strict RFC compliance */ + chrsz : 8, /* bits per input character. 8 - ASCII; 16 - Unicode */ + + /* + * These are the functions you'll usually want to call + */ + hex_md4: function(s){ + return this.binl2hex(this.core_md4(this.str2binl(s), s.length * this.chrsz)); + }, + + b64_md4: function(s){ + return this.binl2b64(this.core_md4(this.str2binl(s), s.length * this.chrsz)); + }, + + str_md4: function(s){ + return this.binl2str(this.core_md4(this.str2binl(s), s.length * this.chrsz)); + }, + + hex_hmac_md4: function(key, data){ + return this.binl2hex(this.core_hmac_md4(key, data)); + }, + + b64_hmac_md4: function(key, data){ + return this.binl2b64(this.core_hmac_md4(key, data)); + }, + + str_hmac_md4: function(key, data){ + return this.binl2str(this.core_hmac_md4(key, data)); + }, + + /** + * Perform a simple self-test to see if the VM is working + */ + md4_vm_test: function(){ + return this.hex_md4("abc") == "a448017aaf21d8525fc10ae87aa6729d"; + }, + + /** + * Calculate the MD4 of an array of little-endian words, and a bit length + */ + core_md4: function(x, len){ + /* append padding */ + x[len >> 5] |= 0x80 << (len % 32); + x[(((len + 64) >>> 9) << 4) + 14] = len; + + var a = 1732584193; + var b = -271733879; + var c = -1732584194; + var d = 271733878; + + for (var i = 0; i < x.length; i += 16) { + var olda = a; + var oldb = b; + var oldc = c; + var oldd = d; + + a = this.md4_ff(a, b, c, d, x[i + 0], 3); + d = this.md4_ff(d, a, b, c, x[i + 1], 7); + c = this.md4_ff(c, d, a, b, x[i + 2], 11); + b = this.md4_ff(b, c, d, a, x[i + 3], 19); + a = this.md4_ff(a, b, c, d, x[i + 4], 3); + d = this.md4_ff(d, a, b, c, x[i + 5], 7); + c = this.md4_ff(c, d, a, b, x[i + 6], 11); + b = this.md4_ff(b, c, d, a, x[i + 7], 19); + a = this.md4_ff(a, b, c, d, x[i + 8], 3); + d = this.md4_ff(d, a, b, c, x[i + 9], 7); + c = this.md4_ff(c, d, a, b, x[i + 10], 11); + b = this.md4_ff(b, c, d, a, x[i + 11], 19); + a = this.md4_ff(a, b, c, d, x[i + 12], 3); + d = this.md4_ff(d, a, b, c, x[i + 13], 7); + c = this.md4_ff(c, d, a, b, x[i + 14], 11); + b = this.md4_ff(b, c, d, a, x[i + 15], 19); + + a = this.md4_gg(a, b, c, d, x[i + 0], 3); + d = this.md4_gg(d, a, b, c, x[i + 4], 5); + c = this.md4_gg(c, d, a, b, x[i + 8], 9); + b = this.md4_gg(b, c, d, a, x[i + 12], 13); + a = this.md4_gg(a, b, c, d, x[i + 1], 3); + d = this.md4_gg(d, a, b, c, x[i + 5], 5); + c = this.md4_gg(c, d, a, b, x[i + 9], 9); + b = this.md4_gg(b, c, d, a, x[i + 13], 13); + a = this.md4_gg(a, b, c, d, x[i + 2], 3); + d = this.md4_gg(d, a, b, c, x[i + 6], 5); + c = this.md4_gg(c, d, a, b, x[i + 10], 9); + b = this.md4_gg(b, c, d, a, x[i + 14], 13); + a = this.md4_gg(a, b, c, d, x[i + 3], 3); + d = this.md4_gg(d, a, b, c, x[i + 7], 5); + c = this.md4_gg(c, d, a, b, x[i + 11], 9); + b = this.md4_gg(b, c, d, a, x[i + 15], 13); + + a = this.md4_hh(a, b, c, d, x[i + 0], 3); + d = this.md4_hh(d, a, b, c, x[i + 8], 9); + c = this.md4_hh(c, d, a, b, x[i + 4], 11); + b = this.md4_hh(b, c, d, a, x[i + 12], 15); + a = this.md4_hh(a, b, c, d, x[i + 2], 3); + d = this.md4_hh(d, a, b, c, x[i + 10], 9); + c = this.md4_hh(c, d, a, b, x[i + 6], 11); + b = this.md4_hh(b, c, d, a, x[i + 14], 15); + a = this.md4_hh(a, b, c, d, x[i + 1], 3); + d = this.md4_hh(d, a, b, c, x[i + 9], 9); + c = this.md4_hh(c, d, a, b, x[i + 5], 11); + b = this.md4_hh(b, c, d, a, x[i + 13], 15); + a = this.md4_hh(a, b, c, d, x[i + 3], 3); + d = this.md4_hh(d, a, b, c, x[i + 11], 9); + c = this.md4_hh(c, d, a, b, x[i + 7], 11); + b = this.md4_hh(b, c, d, a, x[i + 15], 15); + + a = this.safe_add(a, olda); + b = this.safe_add(b, oldb); + c = this.safe_add(c, oldc); + d = this.safe_add(d, oldd); + + } + return Array(a, b, c, d); + + }, + + /* + * These functions implement the basic operation for each round of the + * algorithm. + */ + md4_cmn: function(q, a, b, x, s, t){ + return this.safe_add(rol(this.safe_add(this.safe_add(a, q), this.safe_add(x, t)), s), b); + }, + + md4_ff: function(a, b, c, d, x, s){ + return this.md4_cmn((b & c) | ((~ b) & d), a, 0, x, s, 0); + }, + + md4_gg: function(a, b, c, d, x, s){ + return this.md4_cmn((b & c) | (b & d) | (c & d), a, 0, x, s, 1518500249); + }, + + md4_hh: function(a, b, c, d, x, s){ + return this.md4_cmn(b ^ c ^ d, a, 0, x, s, 1859775393); + }, + + /** + * Calculate the HMAC-MD4, of a key and some data + */ + core_hmac_md4: function(key, data){ + var bkey = this.str2binl(key); + if (bkey.length > 16) + bkey = this.core_md4(bkey, key.length * this.chrsz); + + var ipad = Array(16), opad = Array(16); + for (var i = 0; i < 16; i++) { + ipad[i] = bkey[i] ^ 0x36363636; + opad[i] = bkey[i] ^ 0x5C5C5C5C; + } + + var hash = this.core_md4(ipad.concat(this.str2binl(data)), 512 + data.length * this.chrsz); + return this.core_md4(opad.concat(hash), 512 + 128); + }, + + /** + * Add integers, wrapping at 2^32. This uses 16-bit operations internally + * to work around bugs in some JS interpreters. + */ + safe_add: function(x, y){ + var lsw = (x & 0xFFFF) + (y & 0xFFFF); + var msw = (x >> 16) + (y >> 16) + (lsw >> 16); + return (msw << 16) | (lsw & 0xFFFF); + }, + + /** + * Bitwise rotate a 32-bit number to the left. + */ + rol: function(num, cnt){ + return (num << cnt) | (num >>> (32 - cnt)); + }, + + /** + * Convert a string to an array of little-endian words + * If chrsz is ASCII, characters >255 have their hi-byte silently ignored. + */ + str2binl: function(str){ + var bin = Array(); + var mask = (1 << this.chrsz) - 1; + for (var i = 0; i < str.length * this.chrsz; i += this.chrsz) + bin[i >> 5] |= (str.charCodeAt(i / this.chrsz) & mask) << (i % 32); + return bin; + }, + + /** + * Convert an array of little-endian words to a string + */ + binl2str: function(bin){ + var str = ""; + var mask = (1 << this.chrsz) - 1; + for (var i = 0; i < bin.length * 32; i += this.chrsz) + str += String.fromCharCode((bin[i >> 5] >>> (i % 32)) & mask); + return str; + }, + + /** + * Convert an array of little-endian words to a hex string. + */ + binl2hex: function(binarray){ + var hex_tab = this.hexcase ? "0123456789ABCDEF" : "0123456789abcdef"; + var str = ""; + for (var i = 0; i < binarray.length * 4; i++) { + str += hex_tab.charAt((binarray[i >> 2] >> ((i % 4) * 8 + 4)) & 0xF) + + hex_tab.charAt((binarray[i >> 2] >> ((i % 4) * 8)) & 0xF); + } + return str; + }, + + /** + * Convert an array of little-endian words to a base-64 string + */ + binl2b64: function(binarray){ + var tab = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + var str = ""; + for (var i = 0; i < binarray.length * 4; i += 3) { + var triplet = (((binarray[i >> 2] >> 8 * (i % 4)) & 0xFF) << 16) | + (((binarray[i + 1 >> 2] >> 8 * ((i + 1) % 4)) & 0xFF) << 8) | + ((binarray[i + 2 >> 2] >> 8 * ((i + 2) % 4)) & 0xFF); + for (var j = 0; j < 4; j++) { + if (i * 8 + j * 6 > binarray.length * 32) + str += this.b64pad; + else + str += tab.charAt((triplet >> 6 * (3 - j)) & 0x3F); + } + } + return str; + } +}; + + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/core/crypto/md5.js)SIZE(10997)TIME(Mon, 10 Jan 2011 10:53:11 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + + +apf.crypto.MD5 = { + /* + * Configurable variables. You may need to tweak these to be compatible with + * the server-side, but the defaults work in most cases. + */ + hexcase: 0, /* hex output format. 0 - lowercase; 1 - uppercase */ + b64pad : "", /* base-64 pad character. "=" for strict RFC compliance */ + chrsz : 8, /* bits per input character. 8 - ASCII; 16 - Unicode */ + + /** + * These are the functions you'll usually want to call + * They take string arguments and return either hex or base-64 encoded strings + * + * Example: + * var hash = apf.crypto.MD5.hex_md5("uzza"); //fddb7463a72e6b000abf631f558cf034 + */ + + hex_md5: function(s) { + return this.binl2hex(this.core_md5(this.str2binl(s), s.length * this.chrsz)); + }, + b64_md5: function(s) { + return this.binl2b64(this.core_md5(this.str2binl(s), s.length * this.chrsz)); + }, + str_md5: function(s) { + return this.binl2str(this.core_md5(this.str2binl(s), s.length * this.chrsz)); + }, + hex_hmac_md5: function(key, data) { + return this.binl2hex(this.core_hmac_md5(key, data)); + }, + b64_hmac_md5: function(key, data) { + return this.binl2b64(this.core_hmac_md5(key, data)); + }, + str_hmac_md5: function(key, data) { + return this.binl2str(this.core_hmac_md5(key, data)); + }, + /** + * Calculate the MD5 of an array of little-endian words, and a bit length + */ + core_md5: function(x, len) { + /* append padding */ + x[len >> 5] |= 0x80 << ((len) % 32); + x[(((len + 64) >>> 9) << 4) + 14] = len; + + var a = 1732584193, b = -271733879, c = -1732584194, d = 271733878; + + for(var i = 0; i < x.length; i += 16) { + var olda = a, oldb = b, oldc = c, oldd = d; + + a = this.md5_ff(a, b, c, d, x[i+ 0], 7 , -680876936); + d = this.md5_ff(d, a, b, c, x[i+ 1], 12, -389564586); + c = this.md5_ff(c, d, a, b, x[i+ 2], 17, 606105819); + b = this.md5_ff(b, c, d, a, x[i+ 3], 22, -1044525330); + a = this.md5_ff(a, b, c, d, x[i+ 4], 7 , -176418897); + d = this.md5_ff(d, a, b, c, x[i+ 5], 12, 1200080426); + c = this.md5_ff(c, d, a, b, x[i+ 6], 17, -1473231341); + b = this.md5_ff(b, c, d, a, x[i+ 7], 22, -45705983); + a = this.md5_ff(a, b, c, d, x[i+ 8], 7 , 1770035416); + d = this.md5_ff(d, a, b, c, x[i+ 9], 12, -1958414417); + c = this.md5_ff(c, d, a, b, x[i+10], 17, -42063); + b = this.md5_ff(b, c, d, a, x[i+11], 22, -1990404162); + a = this.md5_ff(a, b, c, d, x[i+12], 7 , 1804603682); + d = this.md5_ff(d, a, b, c, x[i+13], 12, -40341101); + c = this.md5_ff(c, d, a, b, x[i+14], 17, -1502002290); + b = this.md5_ff(b, c, d, a, x[i+15], 22, 1236535329); + + a = this.md5_gg(a, b, c, d, x[i+ 1], 5 , -165796510); + d = this.md5_gg(d, a, b, c, x[i+ 6], 9 , -1069501632); + c = this.md5_gg(c, d, a, b, x[i+11], 14, 643717713); + b = this.md5_gg(b, c, d, a, x[i+ 0], 20, -373897302); + a = this.md5_gg(a, b, c, d, x[i+ 5], 5 , -701558691); + d = this.md5_gg(d, a, b, c, x[i+10], 9 , 38016083); + c = this.md5_gg(c, d, a, b, x[i+15], 14, -660478335); + b = this.md5_gg(b, c, d, a, x[i+ 4], 20, -405537848); + a = this.md5_gg(a, b, c, d, x[i+ 9], 5 , 568446438); + d = this.md5_gg(d, a, b, c, x[i+14], 9 , -1019803690); + c = this.md5_gg(c, d, a, b, x[i+ 3], 14, -187363961); + b = this.md5_gg(b, c, d, a, x[i+ 8], 20, 1163531501); + a = this.md5_gg(a, b, c, d, x[i+13], 5 , -1444681467); + d = this.md5_gg(d, a, b, c, x[i+ 2], 9 , -51403784); + c = this.md5_gg(c, d, a, b, x[i+ 7], 14, 1735328473); + b = this.md5_gg(b, c, d, a, x[i+12], 20, -1926607734); + + a = this.md5_hh(a, b, c, d, x[i+ 5], 4 , -378558); + d = this.md5_hh(d, a, b, c, x[i+ 8], 11, -2022574463); + c = this.md5_hh(c, d, a, b, x[i+11], 16, 1839030562); + b = this.md5_hh(b, c, d, a, x[i+14], 23, -35309556); + a = this.md5_hh(a, b, c, d, x[i+ 1], 4 , -1530992060); + d = this.md5_hh(d, a, b, c, x[i+ 4], 11, 1272893353); + c = this.md5_hh(c, d, a, b, x[i+ 7], 16, -155497632); + b = this.md5_hh(b, c, d, a, x[i+10], 23, -1094730640); + a = this.md5_hh(a, b, c, d, x[i+13], 4 , 681279174); + d = this.md5_hh(d, a, b, c, x[i+ 0], 11, -358537222); + c = this.md5_hh(c, d, a, b, x[i+ 3], 16, -722521979); + b = this.md5_hh(b, c, d, a, x[i+ 6], 23, 76029189); + a = this.md5_hh(a, b, c, d, x[i+ 9], 4 , -640364487); + d = this.md5_hh(d, a, b, c, x[i+12], 11, -421815835); + c = this.md5_hh(c, d, a, b, x[i+15], 16, 530742520); + b = this.md5_hh(b, c, d, a, x[i+ 2], 23, -995338651); + + a = this.md5_ii(a, b, c, d, x[i+ 0], 6 , -198630844); + d = this.md5_ii(d, a, b, c, x[i+ 7], 10, 1126891415); + c = this.md5_ii(c, d, a, b, x[i+14], 15, -1416354905); + b = this.md5_ii(b, c, d, a, x[i+ 5], 21, -57434055); + a = this.md5_ii(a, b, c, d, x[i+12], 6 , 1700485571); + d = this.md5_ii(d, a, b, c, x[i+ 3], 10, -1894986606); + c = this.md5_ii(c, d, a, b, x[i+10], 15, -1051523); + b = this.md5_ii(b, c, d, a, x[i+ 1], 21, -2054922799); + a = this.md5_ii(a, b, c, d, x[i+ 8], 6 , 1873313359); + d = this.md5_ii(d, a, b, c, x[i+15], 10, -30611744); + c = this.md5_ii(c, d, a, b, x[i+ 6], 15, -1560198380); + b = this.md5_ii(b, c, d, a, x[i+13], 21, 1309151649); + a = this.md5_ii(a, b, c, d, x[i+ 4], 6 , -145523070); + d = this.md5_ii(d, a, b, c, x[i+11], 10, -1120210379); + c = this.md5_ii(c, d, a, b, x[i+ 2], 15, 718787259); + b = this.md5_ii(b, c, d, a, x[i+ 9], 21, -343485551); + + a = this.safe_add(a, olda); + b = this.safe_add(b, oldb); + c = this.safe_add(c, oldc); + d = this.safe_add(d, oldd); + } + return [a, b, c, d]; + }, + /* + * These functions implement the four basic operations the algorithm uses. + */ + md5_cmn: function(q, a, b, x, s, t) { + return this.safe_add(this.bit_rol(this.safe_add(this.safe_add(a, q), + this.safe_add(x, t)), s),b); + }, + md5_ff: function(a, b, c, d, x, s, t) { + return this.md5_cmn((b & c) | ((~b) & d), a, b, x, s, t); + }, + md5_gg: function(a, b, c, d, x, s, t) { + return this.md5_cmn((b & d) | (c & (~d)), a, b, x, s, t); + }, + md5_hh: function(a, b, c, d, x, s, t) { + return this.md5_cmn(b ^ c ^ d, a, b, x, s, t); + }, + md5_ii: function(a, b, c, d, x, s, t) { + return this.md5_cmn(c ^ (b | (~d)), a, b, x, s, t); + }, + /** + * Calculate the HMAC-MD5, of a key and some data + */ + core_hmac_md5: function(key, data) { + var bkey = this.str2binl(key), + ipad = Array(16), + opad = Array(16); + if (bkey.length > 16) + bkey = this.core_md5(bkey, key.length * this.chrsz); + + for (var i = 0; i < 16; i++) { + ipad[i] = bkey[i] ^ 0x36363636; + opad[i] = bkey[i] ^ 0x5C5C5C5C; + } + + return this.core_md5(opad.concat( + this.core_md5(ipad.concat(this.str2binl(data)), 512 + data.length * this.chrsz) + ), 512 + 128); + }, + /** + * Add integers, wrapping at 2^32. This uses 16-bit operations internally + * to work around bugs in some JS interpreters. + */ + safe_add: function(x, y) { + var lsw = (x & 0xFFFF) + (y & 0xFFFF), + msw = (x >> 16) + (y >> 16) + (lsw >> 16); + return (msw << 16) | (lsw & 0xFFFF); + }, + /** + * Bitwise rotate a 32-bit number to the left. + */ + bit_rol: function(num, cnt) { + return (num << cnt) | (num >>> (32 - cnt)); + }, + /** + * Convert a string to an array of little-endian words + * If chrsz is ASCII, characters >255 have their hi-byte silently ignored. + */ + str2binl: function(str) { + var bin = [], i, + mask = (1 << this.chrsz) - 1; + for (i = 0; i < str.length * this.chrsz; i += this.chrsz) + bin[i >> 5] |= (str.charCodeAt(i / this.chrsz) & mask) << (i%32); + return bin; + }, + /** + * Convert an array of little-endian words to a string + */ + binl2str: function(bin) { + var str = [], i, + mask = (1 << this.chrsz) - 1; + for (i = 0; i < bin.length * 32; i += this.chrsz) + str.push(String.fromCharCode((bin[i>>5] >>> (i % 32)) & mask)); + return str.join(""); + }, + /** + * Convert an array of little-endian words to a hex string. + */ + binl2hex: function(binarray) { + var hex_tab = this.hexcase ? "0123456789ABCDEF" : "0123456789abcdef", + str = [], i; + for (i = 0; i < binarray.length * 4; i++) { + str.push(hex_tab.charAt((binarray[i>>2] >> ((i%4)*8+4)) & 0xF) + + hex_tab.charAt((binarray[i>>2] >> ((i%4)*8 )) & 0xF)); + } + return str.join(""); + }, + /** + * Convert an array of little-endian words to a base-64 string + */ + binl2b64: function(binarray) { + var tab = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/", + str = [], i; + for(i = 0; i < binarray.length * 4; i += 3) { + var triplet = (((binarray[i >> 2] >> 8 * ( i %4)) & 0xFF) << 16) + | (((binarray[i+1 >> 2] >> 8 * ((i+1)%4)) & 0xFF) << 8 ) + | ((binarray[i+2 >> 2] >> 8 * ((i+2)%4)) & 0xFF); + for (var j = 0; j < 4; j++) { + if (i * 8 + j * 6 > binarray.length * 32) + str.push(this.b64pad); + else + str.push(tab.charAt((triplet >> 6*(3-j)) & 0x3F)); + } + } + return str.join(""); + } +}; + + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/core/crypto/rsa.js)SIZE(5048)TIME(Mon, 10 Jan 2011 10:53:11 GMT)*/ + +/** + * RSA, a suite of routines for performing RSA public-key computations in + * JavaScript. + * + * Requires BigInt.js and Barrett.js. + * + * Copyright 1998-2005 David Shapiro. + * + * You may use, re-use, abuse, copy, and modify this code to your liking, but + * please keep this header. + * + * Thanks! + * + * @author Dave Shapiro + */ + + + +apf.crypto.RSA = (function() { + function RSAKeyPair(encryptionExponent, decryptionExponent, modulus) { + this.e = apf.crypto.BigInt.fromHex(encryptionExponent); + this.d = apf.crypto.BigInt.fromHex(decryptionExponent); + this.m = apf.crypto.BigInt.fromHex(modulus); + /* + * We can do two bytes per digit, so + * chunkSize = 2 * (number of digits in modulus - 1). + * Since biHighIndex returns the high index, not the number of digits, 1 has + * already been subtracted. + */ + ////////////////////////////////// TYF + this.digitSize = 2 * apf.crypto.BigInt.highIndex(this.m) + 2; + this.chunkSize = this.digitSize - 11; // maximum, anything lower is fine + ////////////////////////////////// TYF + this.radix = 16; + this.barrett = new apf.crypto.Barrett(this.m); + } + + function twoDigit(n) { + return (n < 10 ? "0" : "") + String(n); + } + + function encryptedString(key, s) { + /* + * Altered by Rob Saunders (rob@robsaunders.net). New routine pads the + * string after it has been converted to an array. This fixes an + * incompatibility with Flash MX's ActionScript. + * Altered by Tang Yu Feng for interoperability with Microsoft's + * RSACryptoServiceProvider implementation. + */ + ////////////////////////////////// TYF + if (key.chunkSize > key.digitSize - 11) { + return "Error"; + } + ////////////////////////////////// TYF + var a = new Array(); + var sl = s.length; + + var i = 0; + while (i < sl) { + a[i] = s.charCodeAt(i); + i++; + } + + var al = a.length; + var result = ""; + var j, k, block; + for (i = 0; i < al; i += key.chunkSize) { + block = new apf.crypto.BigInt.construct(); + j = 0; + ////////////////////////////////// TYF + /* + * Add PKCS#1 v1.5 padding + * 0x00 || 0x02 || PseudoRandomNonZeroBytes || 0x00 || Message + * Variable a before padding must be of at most digitSize-11 + * That is for 3 marker bytes plus at least 8 random non-zero bytes + */ + var x; + var msgLength = (i+key.chunkSize)>al ? al%key.chunkSize : key.chunkSize; + + // Variable b with 0x00 || 0x02 at the highest index. + var b = new Array(); + for (x = 0; x < msgLength; x++) { + b[x] = a[i + msgLength - 1 - x]; + } + b[msgLength] = 0; // marker + var paddedSize = Math.max(8, key.digitSize - 3 - msgLength); + for (x = 0; x < paddedSize; x++) { + b[msgLength + 1 + x] = Math.floor(Math.random() * 254) + 1; // [1,255] + } + // It can be asserted that msgLength+paddedSize == key.digitSize-3 + b[key.digitSize - 2] = 2; // marker + b[key.digitSize - 1] = 0; // marker + + for (k = 0; k < key.digitSize; ++j) + { + block.digits[j] = b[k++]; + block.digits[j] += b[k++] << 8; + } + ////////////////////////////////// TYF + + var crypt = key.barrett.powMod(block, key.e); + var text = key.radix == 16 ? apf.crypto.BigInt.toHex(crypt) : apf.crypto.BigInt.toString(crypt, key.radix); + result += text + " "; + } + return result.substring(0, result.length - 1); // Remove last space. + } + + function decryptedString(key, s) { + var blocks = s.split(" "); + var result = ""; + var i, j, block; + for (i = 0; i < blocks.length; ++i) { + var bi; + if (key.radix == 16) { + bi = apf.crypto.BigInt.fromHex(blocks[i]); + } else { + bi = apf.crypto.BigInt.fromString(blocks[i], key.radix); + } + block = key.barrett.powMod(bi, key.d); + for (j = 0; j <= apf.crypto.BigInt.highIndex(block); ++j) { + result += String.fromCharCode(block.digits[j] & 255, + block.digits[j] >> 8); + } + } + // Remove trailing null, if any. + if (result.charCodeAt(result.length - 1) == 0) { + result = result.substring(0, result.length - 1); + } + return result; + } + + //publish public functions: + return { + F4: "10001", + E3: "3", + getKeyPair: RSAKeyPair, + twoDigit: twoDigit, + encrypt: encryptedString, + decrypt: decryptedString + }; +})(); + + + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/core/crypto/sha1.js)SIZE(5258)TIME(Mon, 10 Jan 2011 10:53:11 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + + +(function(global) { + +function rotate_left(n,s) { + var t4 = ( n<>>(32-s)); + return t4; +}; + +/* +function lsb_hex(val) { // Not in use; needed? + var str=""; + var i; + var vh; + var vl; + + for( i=0; i<=6; i+=2 ) { + vh = (val>>>(i*4+4))&0x0f; + vl = (val>>>(i*4))&0x0f; + str += vh.toString(16) + vl.toString(16); + } + return str; +}; +*/ + +function cvt_hex(val) { + var str=""; + var i; + var v; + + for( i=7; i>=0; i-- ) { + v = (val>>>(i*4))&0x0f; + str += v.toString(16); + } + return str; +}; + +global.SHA1 = function(str) { + // Calculate the sha1 hash of a string + // + // version: 905.3122 + // discuss at: http://phpjs.org/functions/sha1 + // + original by: Webtoolkit.info (http://www.webtoolkit.info/) + // + namespaced by: Michael White (http://getsprink.com) + // + input by: Brett Zamir (http://brett-zamir.me) + // + improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net) + // - depends on: utf8_encode + // * example 1: sha1('Kevin van Zonneveld'); + // * returns 1: '54916d2e62f65b3afa6e192e6a601cdbe5cb5897' + var blockstart, i, j, W = new Array(80), + H0 = 0x67452301, + H1 = 0xEFCDAB89, + H2 = 0x98BADCFE, + H3 = 0x10325476, + H4 = 0xC3D2E1F0, + A, B, C, D, E, temp; + + str = apf.crypto.UTF8.encode(str); + var str_len = str.length, + word_array = []; + + for(i = 0; i < str_len - 3; i += 4) { + j = str.charCodeAt(i) << 24 | str.charCodeAt(i + 1) << 16 | + str.charCodeAt(i + 2) << 8 | str.charCodeAt(i + 3); + word_array.push(j); + } + + switch (str_len % 4) { + case 0: + i = 0x080000000; + break; + case 1: + i = str.charCodeAt(str_len - 1) << 24 | 0x0800000; + break; + case 2: + i = str.charCodeAt(str_len - 2) << 24 | str.charCodeAt(str_len - 1) + << 16 | 0x08000; + break; + case 3: + i = str.charCodeAt(str_len - 3) << 24 | str.charCodeAt(str_len - 2) + << 16 | str.charCodeAt(str_len - 1) << 8 | 0x80; + break; + } + + word_array.push( i ); + + while((word_array.length % 16) != 14) + word_array.push( 0 ); + + word_array.push(str_len >>> 29); + word_array.push((str_len << 3) & 0x0ffffffff); + + for (blockstart = 0; blockstart < word_array.length; blockstart += 16) { + for (i = 0; i < 16; i++) + W[i] = word_array[blockstart + i]; + for (i = 16; i <= 79; i++) + W[i] = rotate_left(W[i - 3] ^ W[i - 8] ^ W[i - 14] ^ W[i - 16], 1); + + A = H0; + B = H1; + C = H2; + D = H3; + E = H4; + + for (i = 0; i <= 19; i++) { + temp = (rotate_left(A, 5) + ((B & C) | (~B & D)) + E + W[i] + + 0x5A827999) & 0x0ffffffff; + E = D; + D = C; + C = rotate_left(B, 30); + B = A; + A = temp; + } + + for (i = 20; i <= 39; i++) { + temp = (rotate_left(A, 5) + (B ^ C ^ D) + E + W[i] + 0x6ED9EBA1) + & 0x0ffffffff; + E = D; + D = C; + C = rotate_left(B, 30); + B = A; + A = temp; + } + + for (i = 40; i <= 59; i++) { + temp = (rotate_left(A, 5) + ((B & C) | (B & D) | (C & D)) + E + W[i] + + 0x8F1BBCDC) & 0x0ffffffff; + E = D; + D = C; + C = rotate_left(B, 30); + B = A; + A = temp; + } + + for (i = 60; i <= 79; i++) { + temp = (rotate_left(A, 5) + (B ^ C ^ D) + E + W[i] + 0xCA62C1D6) + & 0x0ffffffff; + E = D; + D = C; + C = rotate_left(B, 30); + B = A; + A = temp; + } + + H0 = (H0 + A) & 0x0ffffffff; + H1 = (H1 + B) & 0x0ffffffff; + H2 = (H2 + C) & 0x0ffffffff; + H3 = (H3 + D) & 0x0ffffffff; + H4 = (H4 + E) & 0x0ffffffff; + } + + temp = cvt_hex(H0) + cvt_hex(H1) + cvt_hex(H2) + cvt_hex(H3) + cvt_hex(H4); + return temp.toLowerCase(); +}; + +})(apf.crypto); + + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/core/debug/debug.js)SIZE(9811)TIME(Mon, 10 Jan 2011 10:53:11 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + + +/** + * Returns a string giving information on a javascript object. + * + * @param {mixed} obj the object to investigate + */ +apf.dump = +apf.vardump = function(obj, o, depth, stack){ + o = o || {}; + if(o.maxdepth === undefined)o.maxdepth = 99; + + if (apf.isWebkit) //@todo RIK please fix this issue. + return ""; + if (!obj) return obj + ""; + if (!stack) stack = ""; + if (!depth) depth = 0; + var str; + switch (obj.dataType) { + case apf.STRING: + return "\"" + (o.clip?(obj.length>o.clip?(obj.slice(0,o.clip)+"..."):obj):obj).replace(/[\"]/g,"'") + "\""; + case apf.NUMBER: + return obj; + case apf.BOOLEAN: + return (obj ? "true" : "false"); + case apf.DATE: + return "Date(\"" + obj + "\)"; + case apf.ARRAY: + if(obj[obj.length-2]=='$__vardump'){ + return "this"+obj[obj.length-1]; + } + obj.push('$__vardump',stack); + str = ["[ "]; + for (var i = 0; i < obj.length-2; i++) { + str.push( str.length>1?",":"", + (depth >= o.maxdepth ? typeof(obj[i]) : + apf.vardump(obj[i], o, depth + 1, stack+'['+i+']')) ); + } + str.push( " ]"); + obj.pop();obj.pop(); + return str.join(''); + default: + if (typeof obj == "function") + return "function"; + if (obj.nodeType !== undefined) + return o.xml?(str=obj.xml,o.clip?((str=str.replace(/\s*[\r\n]\s*/g,"")).length>o.clip?str.slice(0,o.clip)+"...>":str):str):("<" + obj.tagName+"../>") ; + //return depth == 0 ? "[ " + (obj.xml || obj.serialize()) + " ]" : "XML Element"; + if (depth >= o.maxdepth) + return "object"; + + //((typeof obj[prop]).match(/(function|object)/) ? RegExp.$1 : obj[prop]) + if (obj['$__vardump']) return "this"+obj['$__vardump']+""; + obj['$__vardump'] = stack; + str = ["{"+(o.clip?"":"\n")]; + for (var prop in obj) if(prop!='$__vardump'){ + if(o.clipobj && str.join('').length>o.clipobj){str.push( ", ..."); break;} + try { + var propname = prop; + if(str.length>1)str.push(o.clip?", ":",\n"); + str.push( o.clip?"":("\t".repeat(depth+1)), propname, ": ", + (depth >= o.maxdepth ? typeof(obj[prop]): + apf.vardump(obj[prop], o, depth + 1, stack+'.'+prop)) ); + } catch(e) { + str.push( o.clip?"":("\t".repeat(depth+1)) , prop , ": dumperror"); + } + } + str.push(o.clip?"":"\n", o.clip?"":("\t".repeat(depth)), "}"); + + function cleanup(obj){ + if(obj['$__vardump']!== undefined) + delete obj['$__vardump']; + else return; + for(var prop in obj){ + var v = obj[prop]; + if(typeof(v)=='object' && v) cleanup(v); + } + } + cleanup(obj); + + return str.join(''); + } +}; + +if (apf.isOpera) { + window.console = {}; + ["log", "debug", "info", "warn", "error"].forEach(function(type) { + window.console[type] = function() { + if (typeof arguments === "undefined") return null; + if (arguments.length === 1) { // single argument provided + opera.postError(type + ": " + arguments[0]); + return type + ": " + arguments[0]; + } + var s = arguments[0], + // string substitution patterns of firebug console + regexp = /%([sdifo])/g, + i = 0, + match = null; + // replace found matches with given arguments + while (match = regexp.exec(s)) { + s = s.replace(match[0], String(arguments[++i])); + } + // display log messages + var len = arguments.length; + while (len > i++) { + if (arguments[i]) { + s += ' '; + s += String(arguments[i]); + } + } + opera.postError(type + ": " + s); + }; + }); +} + +/** + * Returns a string giving more detailed informations on a javascript object. + * + * @param {mixed} obj the object to investigate + */ +apf.dump2 = +apf.vardump2 = function (obj, depth, recur, stack){ + if(!obj) return obj + ""; + if(!depth) depth = 0; + + switch(obj.dataType){ + case "string": return "\"" + obj + "\""; + case "number": return obj; + case "boolean": return obj ? "true" : "false"; + case "date": return "Date[" + new Date() + "]"; + case "array": + var str = "{\n"; + for(var i=0;i < obj.length;i++){ + str += " ".repeat(depth+1) + i + " => " + (!recur && depth > 0 ? typeof obj[i] : apf.vardump(obj[i], depth+1, !recur)) + "\n"; + } + str += " ".repeat(depth) + "}"; + + return str; + default: + if(typeof obj == "function") return "function"; + //if(obj.xml) return depth==0 ? "[ " + obj.xml + " ]" : "XML Element"; + if(obj.xml || obj.serialize) return depth==0 ? "[ " + (obj.xml || obj.serialize()) + " ]" : "XML Element"; + + if(!recur && depth>0) return "object"; + + //((typeof obj[prop]).match(/(function|object)/) ? RegExp.$1 : obj[prop]) + var str = "{\n"; + for(prop in obj){ + try{ + str += " ".repeat(depth+1) + prop + " => " + (!recur && depth > 0? typeof obj[prop] : apf.vardump(obj[prop], depth+1, !recur)) + "\n"; + }catch(e){ + str += " ".repeat(depth+1) + prop + " => [ERROR]\n"; + } + } + str += " ".repeat(depth) + "}"; + + return str; + } +} + +String.prototype.s = function(){ + return this.replace(/[\r\n]/g, ""); +} + +/** + * Alerts string giving information on a javascript object. + * This is older version of this function + * + * @param {mixed} obj the object to investigate + */ +apf.alert_r = function(obj, recur){ + alert(apf.vardump(obj, null, recur)); +} + +/** + * Alerts string giving information on a javascript object. + * + * @param {mixed} obj the object to investigate + */ +apf.alert_r2 = function(obj, recur){ + alert(apf.vardump2(obj, null, !recur)); +} + +/** + * Object timing the time between one point and another. + * + * @param {Boolean} nostart whether the profiler should start measuring at creation. + * @constructor + */ +apf.ProfilerClass = function(nostart){ + this.totalTime = 0; + + /** + * Starts the timer. + * @param {Boolean} clear resets the total time. + */ + this.start = function(clear){ + if (clear) this.totalTime = 0; + this.startTime = new Date().getTime(); + + this.isStarted = true; + } + + /** + * Stops the timer. + * @method + */ + this.stop = + this.end = function(){ + if (!this.startTime) return; + this.totalTime += new Date().getTime() - this.startTime; + this.isStarted = false; + } + + /** + * Sends the total time to the console. + * @param {String} msg Message displayed in the console. + */ + this.addPoint = function(msg){ + this.end(); + apf.console.time("[TIME] " + (msg || "Profiled Section") + ": " + this.totalTime + "ms"); + this.start(true); + } + + if (!nostart) + this.start(); +}; + +apf.Latometer = new apf.ProfilerClass(true);//backward compatibility + +if (self.navigator && navigator.userAgent.indexOf("Opera") != -1) { + window.console = {}; + ["log", "debug", "info", "warn", "error"].forEach(function(type) { + window.console[type] = function() { + if (typeof arguments === "undefined") return null; + if (arguments.length === 1) { // single argument provided + opera.postError(type + ": " + arguments[0]); + return type + ": " + arguments[0]; + } + var s = arguments[0], + // string substitution patterns of firebug console + regexp = /%([sdifo])/g, + i = 0, + match = null; + // replace found matches with given arguments + while (match = regexp.exec(s)) { + s = s.replace(match[0], String(arguments[++i])); + } + // display log messages + var len = arguments.length; + while (len > i++) { + if (arguments[i]) { + s += ' '; + s += String(arguments[i]); + } + } + opera.postError(type + ": " + s); + }; + }); +} + + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/core/debug/debugwin.js)SIZE(42735)TIME(Mon, 10 Jan 2011 10:53:11 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + + +function prettySize(size) { + if (size < 1024) + return size + " Bytes"; + else if (size < 1024*1024) + return Math.round(size/102.4)/10 + " KB"; + else if (size < 1024*1024*1024) + return Math.round(size/(1024*102.4))/10 + " MB"; +} + +apf.$debugwin = { + showtime : true, + nativedebug : apf.debug ? true : false, + highlighthover : true, + selecteditable : true, + + cache : (function(){ + apf.isDebugWindow = self.frameElement + && self.frameElement.isDebugWin > -1; + + return apf.isDebugWindow ? self.parent.apf.$debugwin.cache : []; + })(), + + apf : (function(){ + if (apf.isDebugWindow) {//assuming we are the debug window + var upapf = self.parent.apf; + + apf.xmldb.$xmlDocLut = upapf.xmldb.$xmlDocLut; + apf.xmldb.$listeners = upapf.xmldb.$listeners; + apf.xmldb.$nodeCount = upapf.xmldb.$nodeCount; + + (apf.$asyncObjects || (apf.$asyncObjects = {}))["$apf_ide_mdlProps"] = 1; + + return upapf; + } + + return apf; + })(), + + init : function(){ + this.updateLog(); + this.apf.addEventListener("debug", this.debugHandler); + + apf.importCssString(".console_date{display:inline}"); + + + apf.storage.init(); + + this.showtime = apf.storage.get("apfdebug_console_date") !== false; + this.nativedebug = apf.storage.get("apfdebug_debugger") == true; + this.highlighthover = apf.storage.get("apfdebug_highlight_hover") !== false; + this.selecteditable = apf.storage.get("apfdebug_select_editable") !== false; + + txtCode.setValue(apf.storage.get("jsexec") || ""); + codetype.setProperty("value", apf.storage.get("scriptype") || "Javascript"); + txtModel.setValue(apf.storage.get("mdlvalue") || ""); + + this.setNativeDebug(this.nativedebug); + + + itmShowtime.setAttribute("checked", this.showtime); + itmDebug.setAttribute("checked", this.nativedebug); + cbHighlightHover.setAttribute("checked", this.highlighthover); + cbSelectEditable.setAttribute("checked", this.selecteditable); + + $apf_ide_mdl.load(this.apf.document.documentElement);//"debugwin.html");// + + var _self = this, excl = {"DIV":1,"BLOCKQUOTE":1,"SPAN":1,"I":1}; + this.$mmouseover = function(e){ + if (!cbHighlightHover.checked || mnuData.visible) + return; + + var oHtml = e.htmlEvent.srcElement || e.htmlEvent.target; + var xmlNode = excl[oHtml.tagName] + ? null + : apf.xmldb.findXmlNode(oHtml); + + _self.apf.$debugwin.highlightAmlNode(xmlNode, !xmlNode); + //e.cancelBubble = true; + } + + this.$mmouseout = function(e){ + _self.apf.$debugwin.highlightAmlNode(null, true); + //(e || event).cancelBubble = true; + } + + $apf_ide_mdlProps.exec = function(method, args, callback, options){ + if (method == "getProperty" && args[0]) { + var xml, tag = args[0]; + if (xml = $apf_ide_mdlProps.queryNode(tag)) + return callback(xml, apf.SUCCESS); + + if (!options) + options = {}; + if (!options.callback) + options.callback = function(data, state, extra){ + if (state != apf.SUCCESS) { + $apf_ide_mdlProps.exec(method, ["empty"], callback, options); + tag = "empty"; + return false; + } + else + callback($apf_ide_mdlProps.queryNode(tag), state, extra); + } + this.insert("props/" + tag + ".xml", options); + } + }; + + apf.addEventListener("mousedown", function(){ + errBox.hide(); + }); + + tabDebug.$ext.onmousemove = function(e){ + if (!e) e = event; + document.body.style.cursor = e.clientY < 5 ? "s-resize" : ""; + } + + tabDebug.$ext.onmousedown = function(e){ + if (!e) e = event; + if (e.clientY < 5) + apf.$debugwin.apf.$debugwin.$startResize(e.clientY, apf); + } + + //@todo dirty hack! need to fix layout engine + browse.addEventListener("afterrender", function(){ + trTools.hide(); + }); + + mnuData.onitemclick = function(e){ + if (this.$lastObj) { + switch(e.value){ + case "doc": + apf.$debugwin.showDocs(this.$lastObj); + break; + case "remove": + mrkAml.remove(this.$lastObj); + break; + case "aml": + apf.$debugwin.showAmlNode(this.$lastObj); + break; + case "data": + apf.$debugwin.showXmlNode(this.$lastObj); + break; + case "obj": + apf.$debugwin.showObject(this.$lastObj); + break; + case "model-data": + apf.$debugwin.showXmlNode(this.$lastObj.data); + break; + case "root-data": + apf.$debugwin.showXmlNode(this.$lastObj.xmlRoot); + break; + case "sel-data": + apf.$debugwin.showXmlNode(this.$lastObj.selected); + break; + } + return; + }; + + switch(e.value){ + case "doc": + eval(mnuData.$lastCl.replace(/showObject|showXmlNode|showAmlNode/, "showDocs")); + break; + case "remove": + eval(mnuData.$lastCl.replace(/showObject|showXmlNode|showAmlNode/, "$removeNode")); + break; + case "aml": + eval(mnuData.$lastCl.replace(/showObject|showXmlNode|showAmlNode/, "showAmlNode")); + break; + case "data": + eval(mnuData.$lastCl.replace(/showObject|showXmlNode|showAmlNode/, "showXmlNode")); + break; + case "obj": + eval(mnuData.$lastCl.replace(/showObject|showXmlNode|showAmlNode/, "showObject")); + break; + case "model-data": + var hasData = eval(mnuData.$lastCl.replace(/showAmlNode/, "$hasAmlData")); + apf.$debugwin.showXmlNode(hasData[3]); + break; + case "root-data": + var hasData = eval(mnuData.$lastCl.replace(/showAmlNode/, "$hasAmlData")); + apf.$debugwin.showXmlNode(hasData[0]); + break; + case "sel-data": + var hasData = eval(mnuData.$lastCl.replace(/showAmlNode/, "$hasAmlData")); + apf.$debugwin.showXmlNode(hasData[1]); + break; + } + } + }, + + start : function(){ + if (!apf.isDebugWindow) + window.onerror = this.nativedebug ? null : this.errorHandler; + }, + + errorHandler : function(message, filename, linenr, isForced){ + if (!message) message = ""; + + if (!isForced) { + apf.$debugwin.apf.console.error( + (apf.$debugwin.apf != apf ? "[Debug Window Error]: " : "") + + "Error on line " + linenr + " of " + + apf.removePathContext(apf.hostPath, filename) + "\n" + message); + //.replace(/") + } + + if (apf.$debugwin.apf == apf) + apf.$debugwin.show(); + + return true; + }, + + updateLog : function(){ + apf_console.clear(); + apf_console.setValue(this.apf.console.getAll( + btnError.value, + btnWarn.value, + btnLog.value + )) + }, + + updateTeleportFilter : function(btn){ + var each = tlist.each.split("|"); + if (btn.value) + each.push(btn.firstChild.nodeValue.toLowerCase()); + else + each = each.remove(btn.firstChild.nodeValue.toLowerCase()); + + tlist.setAttribute("each", each.join("|") || "none"); + }, + + debugHandler : function(e){ + if ((self["btn" + e.type.uCaseFirst()] || btnLog).value || e.type == "custom") + apf_console.setValue(e.message); + + if (e.type == "error" && tabDebug.activepagenr != 0) { + errBox.setMessage(";" + e.message + ""); + errBox.show(); + } + }, + + showDocs : function(node) { + tabDebug.set("docs"); + //debugger; + //node.$regbase + }, + + showAmlNode : function(node){ + tabDebug.set(1); + pgBrowse.set(0); + + if (!mrkAml.xmlRoot) { + mrkAml.addEventListener("afterload", function(){ + apf.$debugwin.showAmlNode(node); + mrkAml.removeEventListener("afterload", arguments.callee); + }); + } + else { + //find in markupeditor, if not there, show in showObject + mrkAml.expandAndSelect(node); + + if (mrkAml.selected != node) + this.showObject(node, node.getAttribute("id") || ""); + } + }, + + toggleHighlight : function(debugwin, btn, options){ + if (document.onmousemove) { + document.onmousemove = + document.onmousedown = null; + return; + } + + var lastAmlNode; + document.onmousemove = function(e){ + if (apf.$debugwin.$hdiv) + apf.$debugwin.$hdiv.style.top = "10000px"; + + var x = (e || (e = event)).clientX; + var y = e.clientY; + var htmlNode = document.elementFromPoint(x, y); + + var amlNode = apf.findHost(htmlNode); + if (lastAmlNode != amlNode) + apf.$debugwin.highlightAmlNode(null, true); + + if (lastAmlNode = amlNode) + apf.$debugwin.highlightAmlNode(amlNode, false, true); + } + + document.onmousedown = function(e){ + var amlNode = lastAmlNode || apf.findHost((e || (e = event)).srcElement || e.target); + if (amlNode) { + if (options.value == "markup") + debugwin.showAmlNode(amlNode); + else if (options.value == "data") { + if (amlNode.xmlRoot) + debugwin.showXmlNode(amlNode.xmlRoot); + else + debugwin.showAmlNode(amlNode); + } + else if (options.value == "prop") + debugwin.showObject(amlNode); + + apf.$debugwin.highlightAmlNode(null, true); + btn.setValue(false); + + document.onmousemove = + document.onmousedown = null; + } + } + }, + + highlightAmlNode : function(node, remove, border) { + if (remove) { + if (this.$hdiv) + this.$hdiv.style.display = "none"; + return; + } + + if (!node) + return; + + if (!this.$hdiv) { + apf.importCssString( + ".apf_highlight_div{\ + background : #004eff;\ + position : absolute;\ + z-index : 1000000;\ + opacity : 0.3;\ + }\ + .apf_border_div{\ + background : url(images/spacer.gif);\ + position : absolute;\ + z-index : 1000000;\ + border : 1px solid blue;\ + }"); + + this.$hdiv = document.body.appendChild(document.createElement("div")); + this.$hdiv.style.display = "none"; + //apf.setStyleClass(this.$hdiv, "apf_highlight_div"); + } + + apf.setStyleClass(this.$hdiv, border + ? "apf_border_div" + : "apf_highlight_div", ["apf_border_div", "apf_highlight_div"]); + + if (false && node.tagName == "html") { + this.$hdiv.style.left = "0px"; + this.$hdiv.style.top = "0px"; + this.$hdiv.style.width = "100%"; + this.$hdiv.style.height = "100%"; + this.$hdiv.style.display = "block"; + } + else if (node.$ext && (node.$ext.offsetHeight || node.$ext.offsetWidth)) { + var pos = apf.getAbsolutePosition(node.$ext); + this.$hdiv.style.left = pos[0] + "px"; + this.$hdiv.style.top = pos[1] + "px"; + var diff = apf.getDiff(this.$hdiv); + this.$hdiv.style.width = (node.$ext.offsetWidth - diff[0]) + "px"; + this.$hdiv.style.height = (node.$ext.offsetHeight - diff[1]) + "px"; + this.$hdiv.style.display = "block"; + } + else { + this.$hdiv.style.display = "none"; + } + }, + + showObject : function(obj, id, name){ + tabDebug.set(1); + pgBrowse.set(2); + + if (!name && obj && obj.$regbase) { + name = obj.getAttribute("id") || "Aml Node"; + id = this.apf.$debugwin.cache.push(obj) - 1; + } + + txtCurObject.setValue(name); + trObject.clear("loading"); + + var _self = this; + setTimeout(function(){ + if (!id && id !== 0) + $apf_ide_mdlObject.load(_self.analyze(obj, name, name)); + else + $apf_ide_mdlObject.load(_self.analyze(_self.apf.$debugwin.cache[id], + "apf.$debugwin.cache[" + id + "]", name)); + }, 10); + }, + + types : ["Object", "Number", "Boolean", "String", "Array", "Date", "RegExp", "Function", "Object"], + domtypes : [null, "Element", "Attr", "Text", "CDataSection", + "EntityReference", "Entity", "ProcessingInstruction", "Comment", + "Document", "DocumentType", "DocumentFragment", "Notation"], + + calcName : function(xmlNode, useDisplay){ + var isMethod = xmlNode.tagName == "method"; + var name, loopNode = xmlNode, path = []; + do { + name = useDisplay + ? loopNode.getAttribute("display") || loopNode.getAttribute("name") + : loopNode.getAttribute("name"); + + if (!name) + break; + + path.unshift(!name.match(/^[a-z_\$][\w_\$]*$/i) + ? (parseInt(name) == name + ? "[" + name + "]" + : "[\"" + name.replace(/'/g, "\\'") + "\"]") + : name); + loopNode = loopNode.parentNode; + if (isMethod) { + loopNode = loopNode.parentNode; + isMethod = false; + } + } + while (loopNode && loopNode.nodeType == 1); + + if (path[0].charAt(0) == "[") + path[0] = path[0].substr(2, path[0].length - 4); + return path.join(".").replace(/\.\[/g, "["); + }, + + analyze : function(pNode, ref, displayName){ + var item, prop, o, obj; + + //if (!pNode) return pNode + ""; + + if (pNode && !pNode.dataType && pNode.nodeType && "item|method".indexOf(pNode.tagName) > -1) { + var name, loopNode = pNode.tagName == "method" ? pNode.parentNode : pNode, path = []; + do { + name = loopNode.getAttribute("name"); + if (!name) break; + path.unshift("['" + name.replace(/'/g, "\\'") + "']"); + loopNode = loopNode.parentNode; + } + while (loopNode && loopNode.nodeType == 1); + + path[0] = path[0].substr(2, path[0].length - 4); + o = self.parent.eval(path.join("")); + } + else { + o = pNode; + pNode = null; + } + + if (pNode && pNode.tagName == "method") { + var xml = apf.getXml(""); + var doc = xml.ownerDocument; + for (prop in o) { + if (typeof o[prop] == "function" && prop.charAt(0) != "$") { + item = xml.appendChild(doc.createElement("method")); + item.setAttribute("name", prop); + item.setAttribute("type", "Function"); + item.setAttribute("value", prop + o[prop].toString().match(/function\s*[\w-]*(\([\s\S]*?\))/)[1]); + } + } + return xml; + } + + var n; + if (!pNode) { + /*var xml = apf.getXml("").firstChild;*/ + var xml = apf.getXml(""); + n = {}; + n[displayName] = o; + if (typeof o == "object" || typeof o == "function" || o && o.dataType == apf.ARRAY) + n.$isSingleValue = true; + o = n; + } + else { + var xml = apf.getXml(""); + } + var doc = xml.ownerDocument; + + //Special case for IE XML elements + try {prop in o}catch(e){ + //@todo + return xml; + } + + for (prop in o) { + obj = o[prop]; + + if (prop.charAt(0) == "$" || prop.substr(0, 2) == "a_") //@todo this could be a setting + continue; + + if (typeof obj == "function" && (!n || obj != n[displayName]) && (o.dataType != apf.ARRAY || prop != parseInt(prop))) { + hasMethods = true; + continue; + } + + item = xml.appendChild(doc.createElement("item")); + item.setAttribute("name", prop); + + if (typeof obj == "undefined" || obj == null) { + item.setAttribute("value", String(obj)); + item.setAttribute("type", String(obj)); + item.setAttribute("numtype", 0); + continue; + } + + if (this.types[obj.dataType]) + item.setAttribute("type", this.types[obj.dataType]); + + var hasProperties = null; + try{for (hasProperties in obj) break;} + catch(e){hasProperties = true;} + item.setAttribute("numtype", obj.dataType || (hasProperties ? 8 : 0)); + + var str, hasMethods; + switch (obj.dataType) { + case apf.STRING: + if (obj.length > 10000) + item.setAttribute("value", "String of " + obj.length + " bytes (too large to display)"); + else + item.setAttribute("value", '"' + obj + .replace(/"/g, "\\\"") + .replace(/ -1) { + var prop, obj = self.parent.eval(name.replace(/\.([^\.\s]+)$/, "")); + if (obj && obj.$supportedProperties && obj.$supportedProperties.contains(prop = RegExp.$1)) { + obj.setProperty(prop, self.parent.eval(value)); + return; + } + } + + self.parent.eval(name + " = " + value); + + //@todo determine new type + } + catch(e) { + trObject.getActionTracker().undo(); + alert("Invalid Action: " + e.message); + //@todo undo + } + }, + + showXmlNode : function(xml){ + tabDebug.set(1); + pgBrowse.set(1); + + var lut = apf.xmldb.$xmlDocLut; + var doc = xml.ownerDocument.documentElement; + var id = xml.getAttribute(apf.xmldb.xmlIdTag); + if (!id) + id = apf.xmldb.nodeConnect(apf.xmldb.getXmlDocId(xml), xml); + var docId = id.split("\|")[0]; + + if (!lut[docId]) { + lut[docId] = doc; + } + else if (lut[docId] != doc) { + + var model1 = this.apf.nameserver.get("model", docId); + lut[docId] = doc; + + var model2 = apf.nameserver.get("model", docId); + apf.xmldb.getXmlDocId(doc, model2); + + if (model1) + apf.nameserver.register("model", docId, model1); + + } + + mrkData.load(xml); + }, + + $handleDataContext : function(ev, obj){ + if (obj) { + this.apf.$debugwin.highlightAmlNode(null, true); + + if (obj.$regbase) { + itInspAml.hide(); + itInspData.hide(); + itInspObj.show(); + itRemove.show(); + + var hasData = [obj.xmlRoot, obj.selected, obj.localName == "model" && obj.data]; + itInspRoot.setProperty("visible", hasData[0] ? 1 : 0); + itInspSel.setProperty("visible", hasData[1] ? 1 : 0); + div2.setProperty("visible", hasData[0] || hasData[1] || hasData[2] ? 1 : 0); + itInspModel.setProperty("visible", hasData[2] ? 1 : 0); + } + else { + itInspAml.hide(); + itInspData.hide(); + itInspModel.hide(); + itInspObj.show(); + + itInspRoot.hide(); + itInspSel.hide(); + div2.hide(); + } + + mnuData.$lastObj = obj; + mnuData.display(ev.x, ev.y); + return false; + } + + e = ev && ev.htmlEvent; + if (!e) return; + + var el = e.srcElement || e.target; + while (el.tagName != "A" && el.tagName != "DIV") + el = el.parentNode; + + if (el.tagName == "A") { + var cl = el.getAttribute("onclick"); + if (cl.indexOf("showObject") > -1) { + itInspAml.hide(); + itInspData.hide(); + itInspObj.show(); + itRemove.hide(); + itInspModel.hide(); + + itInspRoot.hide(); + itInspSel.hide(); + div2.hide(); + } + else if (cl.indexOf("showXmlNode") > -1) { + itInspAml.hide(); + itInspData.show(); + itInspObj.show(); + itRemove.hide(); + itInspModel.hide(); + + itInspRoot.hide(); + itInspSel.hide(); + div2.hide(); + } + else if (cl.indexOf("showAmlNode") > -1) { + itInspAml.show(); + itInspData.hide(); + itInspObj.show(); + itRemove.show(); + + var hasData = eval(cl.replace(/showAmlNode/, "$hasAmlData")); + itInspRoot.setProperty("visible", hasData[0] ? 1 : 0); + itInspSel.setProperty("visible", hasData[1] ? 1 : 0); + div2.setProperty("visible", hasData[0] || hasData[1] || hasData[2] ? 1 : 0); + itInspModel.setProperty("visible", hasData[2] ? 1 : 0); + } + + mnuData.$lastCl = cl; + mnuData.display(ev.x, ev.y); + return false; + } + }, + + $removeNode : function(amlNode){ + mrkAml.remove(this.$lastObj); + }, + + $hasAmlData : function(amlNode){ + return [amlNode.xmlRoot, amlNode.selected + , amlNode.localName == "model" && amlNode.data]; + }, + + jRunCode : function(code, scripttype, model){ + + apf.storage.put("jsexec", code); + apf.storage.put("scriptype", scripttype); + apf.storage.put("mdlvalue", model); + + var islm = scripttype == 'Live Markup'; + + this.apf.console.write(">>>
    " + + code.replace(/ /g, " ") + .replace(/\t/g, "   ") + .replace(/") + + "
    ", "custom", null, null, null, true); + + var _self = this; + var doIt = function(data, loaded){ + if (islm) { + var func = apf.$debugwin.apf.lm.compile(code, {parsecode : true}); + if (model && !loaded) { + + if (data = apf.$debugwin.apf.nameserver.get("model", model)) + data = data.data; + + if (!data) { + var data = _self.apf.getData(model, { + useXML : true, + callback : function(data, state, extra){ + if (state == apf.SUCCESS) { + doIt(data, true); + } + else { + _self.apf.console.error("Could not find resource '" + model + "'\nMessage:" + extra.message); + } + } + }) + return; + } + } + var x = func(data); + } + else { + var x = self.parent.eval(code); + } + + var s = _self.$serializeObject(x, code); + if (typeof s == "string") { + _self.apf.console.write(s, "custom", null, null, null, true); + } + else { + _self.apf.console.write(x + ? "Could not serialize object: " + s.message + : x, "error", null, null, null, true); + } + } + + if (apf.$debugwin.nativedebug) + doIt(); + else { + try{ + doIt(); + } + catch(e) { + this.apf.console.write(e.message, "error", null, null, null, true); + } + } + }, + + $serializeObject : function(x, code){ + if (x === null) + x = "null"; + else if (x === undefined) + x = "undefined"; + + //try { + var str; + if (x.nodeType && !x.style && (!x.$regbase || x.nodeType == 1 || x.nodeType == 7)) { + if (x.nodeType == 1 || x.nodeType == 7) { + if (x.serialize) //aml + str = "" + apf.highlightXml(x.serialize().split(">")[0] + ">").replace(/<\/?a(?:>| [^>]*>)/g, "") + ""; + //else if (x.style) //html + //str = x.outerHTML.replace(/") + else + str = "" + + apf.highlightXml(apf.getCleanCopy(x).xml.split(">")[0] + ">") + ""; + } + else + str = x.xml || x.serialize(); + + return str; + } + else if (typeof x == "object") { + if (x.dataType == apf.ARRAY) { + var out = ["Array { length: " + x.length]; + } + else { + var out = [x.toString(), "{"]; + for (prop in x) { + if (out.length == 5) { + out.push("more..."); + break; + } + if (typeof x[prop] != "function" && typeof x[prop] != "object" && x[prop] && prop.substr(0,1) != "$") + out.push(prop + "=" + x[prop] + ", "); + } + } + + return "" + + out.join(" ") + " }"; + } + else { + str = x.toString(); + + return str + .replace(/"); + } + /*}catch(e){ + return e; + }*/ + }, + + consoleTextHandler: function(e) { + if (e.keyCode == 9 && e.currentTarget == txtCode) { + txtCode.focus(); + e.cancelBubble = true; + return false; + } + else if(e.keyCode == 13 && e.ctrlKey) { + apf.$debugwin.jRunCode(txtCode.value, codetype.value, txtModel.value); + return false; + } + }, + + setShowTime : function(c){ + + apf.storage.put("apfdebug_console_date", c); + + apf.setStyleRule('.console_date', 'display', c ? 'inline' : 'none'); + this.showtime = c; + }, + + setNativeDebug : function(c, admin){ + if (!admin) { + + apf.storage.put("apfdebug_debugger", c); + + + this.apf.$debugwin.setNativeDebug(c, true); + } + else + window.onerror = c ? null : this.errorHandler; + + this.nativedebug = c; + }, + + setHighlightHover : function(c){ + + apf.storage.put("apfdebug_highlight_hover", c); + + this.highlighthover = c; + }, + + setSelectEditable : function(c){ + + apf.storage.put("apfdebug_select_editable", c); + + this.selecteditable = c; + }, + + firstEdit : true, + $setEditable : function(value){ + if (value) { + apf.document.documentElement + .setAttribute("editable", "true"); + } + else { + apf.document.documentElement + .removeAttribute("editable"); + } + }, + + setEditable : function(value){ + this.apf.$debugwin.$setEditable(value); + //if (!self.pgBrowse) return; + + if (value){ + tabDebug.set(1); + pgBrowse.set(0); + + pgBrowse.setAttribute("anchors", "61 0 0 0"); + mrkAml.setAttribute("border", "1 1 0 1"); + tbEdit.show(); + trTools.show(); + } + else if (self.pgBrowse) { + pgBrowse.setAttribute("anchors", "25 0 0 0"); + mrkAml.setAttribute("border", "1 1 0 0"); + tbEdit.hide(); + trTools.hide(); + } + }, + + first : true, + show : function(){ + if (apf.isDebugWindow) + return; + + if (apf.loadScreen) + apf.loadScreen.hide(); + + //Initialize css for showing debugwindow + if (this.first) { + var p, m, o; + if (apf.isIE) { + apf.setStyleRule("BODY", "overflow", "", 0); + + p = apf.getBox(apf.getStyle(document.body, "padding")); + m = apf.getBox(apf.getStyle(document.body, "margin")); + o = [apf.getStyle(document.documentElement, "overflow"), + apf.getStyle(document.documentElement, "overflowX"), + apf.getStyle(document.documentElement, "overflowY")]; + } + else { + p = [parseInt(apf.getStyle(document.body, "paddingTop")), + parseInt(apf.getStyle(document.body, "paddingRight")), + parseInt(apf.getStyle(document.body, "paddingBottom")), + parseInt(apf.getStyle(document.body, "paddingLeft"))]; + m = [parseInt(apf.getStyle(document.body, "marginTop")), + parseInt(apf.getStyle(document.body, "marginRight")), + parseInt(apf.getStyle(document.body, "marginBottom")), + parseInt(apf.getStyle(document.body, "marginLeft"))]; + o = [apf.getStyleRule("html", "overflow") || "auto", + apf.getStyleRule("html", "overflowX") || "auto", + apf.getStyleRule("html", "overflowY") || "auto"]; + } + + apf.importCssString("\ + html{\ + height : 100%;\ + overflow : hidden;\ + overflow-x : hidden;\ + overflow-y : hidden;\ + margin-bottom : " + (p[0] + m[0] + p[2] + m[2]) + "px;\ + }\ + body{\ + position : absolute;\ + left : 0;\ + top : 0;\ + right : 0;\ + bottom : 350px;\ + margin:0;\ + overflow : " + o[0] + ";\ + overflow-x : " + o[1] + ";\ + overflow-y : " + o[2] + ";\ + padding : " + (p[0] + m[0]) + "px " + + (p[1] + m[1]) + "px " + + (p[2] + m[2]) + "px " + + (p[3] + m[3]) + "px;\ + width : auto;\ + }\ + #apf_debugwin {\ + position: fixed;\ + bottom: 0px;\ + text-align: left;\ + height: 350px;\ + width : 100%;\ + left: 0px;\ + z-index: 100000000;\ + }\ + "); + document.documentElement.style.overflow = "hidden"; + this.first = false; + + //src='debugwin.html' + document.body.insertAdjacentHTML("beforeend", "
    ")).parentNode; + this.$ext.style.width = "100px"; + this.$ext.style.height = "100px"; + this.$browser = this.$ext.firstChild; + //this.$browser = this.$ext; + this.$browser.style.width = "100%"; + this.$browser.style.height = "100%"; + this.$browser.frameBorder = 0; + } + else { + this.$ext = parentNode.appendChild(document.createElement("iframe")); + this.$ext.style.width = "100px"; + this.$ext.style.height = "100px"; + this.$browser = this.$ext; + //this.$ext.style.border = "2px inset white"; + } + this.$ext.className = "apfbrowser" + + var _self = this; + apf.addListener(this.$browser, "load", function(){ + _self.dispatchEvent("load"); + }); + + apf.addListener(this.$browser, "error", function(){ + _self.dispatchEvent("error"); + }); + + //this.$browser = this.$ext.contentWindow.document.body; + this.$ext.host = this; + //this.$browser.host = this; + }; +}).call(apf.browser.prototype = new apf.GuiElement()); + +apf.aml.setElement("browser", apf.browser); + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/elements/button.js)SIZE(30579)TIME(Tue, 15 Feb 2011 11:54:45 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + + +/** + * Element displaying a clickable rectangle that visually confirms to the + * user when the area is clicked and then executes a command. + * + * @constructor + * @define button, submit, trigger, reset + * @addnode elements + * + * @author Ruben Daniels (ruben AT ajax DOT org) + * @version %I%, %G% + * @since 0.4 + * + * @inherits apf.BaseButton + */ +apf.submit = function(struct, tagName){ + this.$init(tagName || "submit", apf.NODE_VISIBLE, struct); +}; + +apf.trigger = function(struct, tagName){ + this.$init(tagName || "trigger", apf.NODE_VISIBLE, struct); +}; + +apf.reset = function(struct, tagName){ + this.$init(tagName || "reset", apf.NODE_VISIBLE, struct); +}; + +apf.button = function(struct, tagName){ + this.$init(tagName || "button", apf.NODE_VISIBLE, struct); +}; + +(function() { + this.$useExtraDiv; + this.$childProperty = "caption"; + this.$inited = false; + this.$isLeechingSkin = false; + this.$canLeechSkin = true; + + /**** Properties and Attributes ****/ + + this.$focussable = apf.KEYBOARD; // This object can get the focus + this.value = null; + + this.$init(function(){ + //@todo reparenting + var forceFocus, _self = this, lastDefaultParent; + this.$propHandlers["default"] = function(value){ + if (parseInt(value) != value) + value = apf.isTrue(value) ? 1 : 0; + + this["default"] = parseInt(value); + + if (!this.focussable && value || forceFocus) + this.setAttribute("focussable", forceFocus = value); + + if (lastDefaultParent) { + lastDefaultParent.removeEventListener("focus", setDefault); + lastDefaultParent.removeEventListener("blur", removeDefault); + } + + if (!value) + return; + + var pNode = this.parentNode; + while (pNode && !pNode.focussable && value--) + pNode = pNode.parentNode; + + //Currrently only support for parentNode, this might need to be expanded + if (pNode) { + pNode.addEventListener("focus", setDefault); + pNode.addEventListener("blur", removeDefault); + } + }; + + function setDefault(e){ + if (e.defaultButtonSet || e.returnValue === false) + return; + + e.defaultButtonSet = true; + + if (this.$useExtraDiv) + _self.$ext.appendChild(apf.button.$extradiv); + + _self.$setStyleClass(_self.$ext, _self.$baseCSSname + "Default"); + + if (e.srcElement != _self && _self.$focusParent) { + _self.$focusParent.addEventListener("keydown", btnKeyDown); + } + } + + function removeDefault(e){ + if (this.$useExtraDiv && apf.button.$extradiv.parentNode == _self.$ext) + _self.$ext.removeChild(apf.button.$extradiv); + + _self.$setStyleClass(_self.$ext, "", [_self.$baseCSSname + "Default"]); + + if (e.srcElement != _self && _self.$focusParent) { + _self.$focusParent.removeEventListener("keydown", btnKeyDown); + } + } + + function btnKeyDown(e){ + var ml; + + var f = apf.document.activeElement; + if (f) { + if (f.hasFeature(apf.__MULTISELECT__)) + return; + + ml = f.multiline; + } + + if (!_self.$ext.onmouseup) + return; + + if (ml && ml != "optional" && e.keyCode == 13 + && e.ctrlKey || (!ml || ml == "optional") + && e.keyCode == 13 && !e.ctrlKey && !e.shiftKey && !e.altKey) { + apf.preventDefault(e.htmlEvent); + _self.$ext.onmouseup(e.htmlEvent, true); + } + } + + this.addEventListener("focus", setDefault); + this.addEventListener("blur", removeDefault); + + this.$enable = function(){ + if (this["default"]) { + setDefault({}); + if (apf.document.activeElement) + apf.document.activeElement.focus(true); + } + + if (this.state && this.value) + this.$setState("Down", {}); + else if (this.$mouseOver) + this.$updateState({}, "mouseover"); + else + this.$doBgSwitch(1); + }; + + this.$disable = function(){ + if (this["default"]) + removeDefault({}); + + this.$doBgSwitch(4); + this.$setStyleClass(this.$ext, "", + [this.$baseCSSname + "Over", this.$baseCSSname + "Down"]); + }; + }); + + /** + * @attribute {String} icon the url from which the icon image is loaded. + * @attribute {Boolean} state whether this boolean is a multi state button. + * @attribute {String} value the initial value of a state button. + * @attribute {String} color the text color of the caption of this element. + * @attribute {String} caption the text displayed on this element indicating the action when the button is pressed. + * @attribute {String} action one of the default actions this button can perform when pressed. + * Possible values: + * undo Executes undo on the action tracker of the target element. + * redo Executes redo on the action tracker of the target element. + * remove Removes the selected node(s) of the target element. + * add Adds a node to the target element. + * rename Starts the rename function on the target element. + * login Calls log in on the auth element with the values of the textboxes of type username and password. + * logout Calls lot out on the auth element. + * submit Submits the data of a model specified as the target. + * ok Executes a commitTransaction() on the target element, and closes or hides that element. + * cancel Executes a rollbackTransaction() on the target element, and closes or hides that element. + * apply Executes a commitTransaction() on the target element. + * close Closes the target element. + * @attribute {String} target id of the element to apply the action to. Defaults to the parent container. + * @attribute {Number} default Search depth for which this button is the default action. 1 specifies the direct parent. 2 the parent of this parent. Et cetera. + * @attribute {String} submenu the name of the contextmenu to display when the button is pressed. + */ + //this.$booleanProperties["default"] = true; + this.$booleanProperties["state"] = true; + this.$supportedProperties.push("icon", "value", "tooltip", "state", + "color", "caption", "action", "target", "default", "submenu", "hotkey"); + + this.$propHandlers["icon"] = function(value){ + + if (!this.oIcon) + return apf.console.warn("No icon defined in the Button skin", "button"); + + + if (value) + this.$setStyleClass(this.$ext, this.$baseCSSname + "Icon"); + else + this.$setStyleClass(this.$ext, "", [this.$baseCSSname + "Icon"]); + + apf.skins.setIcon(this.oIcon, value, this.iconPath); + }; + + this.$propHandlers["value"] = function(value){ + if (!this.state && !this.submenu) + return; + + if (value === undefined) + value = !this.value; + this.value = value; + + if (this.value) + this.$setState("Down", {}); + else + this.$setState("Out", {}); + }; + + this.$propHandlers["state"] = function(value){ + if (value) + this.$setStateBehaviour(this.value); + else + this.$setNormalBehaviour(); + }; + + this.$propHandlers["color"] = function(value){ + if (this.oCaption) + this.oCaption.parentNode.style.color = value; + }; + + this.$propHandlers["caption"] = function(value){ + if (!this.oCaption) + return; + + if (value) + this.$setStyleClass(this.$ext, "", [this.$baseCSSname + "Empty"]); + else + this.$setStyleClass(this.$ext, this.$baseCSSname + "Empty"); + + if (this.oCaption.nodeType == 1) + this.oCaption.innerHTML = String(value || "").trim(); + else + this.oCaption.nodeValue = String(value || "").trim(); + }; + + + /** + * @attribute {String} hotkey the key combination a user can press + * to active the function of this element. Use any combination of + * Ctrl, Shift, Alt, F1-F12 and alphanumerical characters. Use a + * space, a minus or plus sign as a seperator. + * Example: + * + * Undo + * + */ + this.$propHandlers["hotkey"] = function(value){ + if (this.$hotkey) + apf.setNodeValue(this.$hotkey, value); + + if (this.$lastHotkey) + apf.removeHotkey(this.$lastHotkey); + + if (value) { + this.$lastHotkey = value; + var _self = this; + apf.registerHotkey(value, function(){ + //hmm not very scalable... + _self.$setState("Over", {}); + + $setTimeout(function(){ + _self.$setState("Out", {}); + }, 200); + + if (_self.$clickHandler && _self.$clickHandler()) + _self.$updateState (e || event, "click"); + else + _self.dispatchEvent("click"); + }); + } + + if (this.tooltip) + apf.GuiElement.propHandlers.tooltip.call(this, this.tooltip); + } + + + + + //@todo move this to menu.js + function menuKeyHandler(e){ + return; + var key = e.keyCode; + + var next, nr = apf.getChildNumber(this); + if (key == 37) { //left + next = nr == 0 + ? this.parentNode.childNodes.length - 1 + : nr - 1; + this.parentNode.childNodes[next].dispatchEvent("mouseover"); + } + else if (key == 39) { //right + next = (nr >= this.parentNode.childNodes.length - 1) + ? 0 + : nr + 1; + this.parentNode.childNodes[next].dispatchEvent("mouseover"); + } + } + + function menuDown(e){ + var menu = self[this.submenu], + $button1; + + this.value = !this.value; + + if (this.value) + this.$setState("Down", {}); + + + if (!menu) { + throw new Error(apf.formatErrorString(0, this, + "Showing submenu", + "Could not find submenu '" + this.submenu + "'")); + } + + + if (!this.value) { + menu.hide(); + this.$setState("Over", {}, "toolbarover"); + + if($button1 = this.parentNode.$button1) + $button1.$setState("Over", {}, "toolbarover"); + + this.parentNode.menuIsPressed = false; + if (this.parentNode.hasMoved) + this.value = false; + + if (apf.hasFocusBug) + apf.window.$focusfix(); + + return false; + } + + this.parentNode.menuIsPressed = this; + + //var pos = apf.getAbsolutePosition(this.$ext, menu.$ext.offsetParent); + menu.display(null, null, false, this, + null, null, this.$ext.offsetWidth - 2); + + this.parentNode.hasMoved = false; + + if (e) + apf.stopPropagation(e.htmlEvent); + + return false; + } + + function menuOver(){ + var menuPressed = this.parentNode.menuIsPressed; + + if (!menuPressed || menuPressed == this) + return; + + var menu = self[this.submenu]; + if(menu.getAttribute('pinned')) + return; + + menuPressed.setValue(false); + var oldMenu = self[menuPressed.submenu]; + oldMenu.$propHandlers["visible"].call(oldMenu, false, true);//.hide(); + + this.setValue(true); + this.parentNode.menuIsPressed = this; + + + if (!menu) { + throw new Error(apf.formatErrorString(0, this, + "Showing submenu", + "Could not find submenu '" + this.submenu + "'")); + } + + + var pos = apf.getAbsolutePosition(this.$ext, menu.$ext.offsetParent); + + menu.display(pos[0], + pos[1] + this.$ext.offsetHeight, true, this, + null, null, this.$ext.offsetWidth - 2); + + //apf.window.$focus(this); + this.$focus(); + + this.parentNode.hasMoved = true; + + return false; + } + + /** + * @attribute {string} submenu If this attribute is set, the button will + * function like a menu button + */ + this.$propHandlers["submenu"] = function(value){ + if (!value){ + if (this.value && this.parentNode) { + + try{ + + menuDown.call(this); + + }catch(ex){} + + } + + this.$focussable = true; + this.$setNormalBehaviour(); + this.removeEventListener("mousedown", menuDown); + this.removeEventListener("mouseover", menuOver); + this.removeEventListener("keydown", menuKeyHandler, true); + return; + } + + this.$focussable = false; + this.$setStateBehaviour(); + + this.addEventListener("mousedown", menuDown); + this.addEventListener("mouseover", menuOver); + this.addEventListener("keydown", menuKeyHandler, true); + }; + + + /**** Public Methods ****/ + + + + /** + * Sets the value of this element. This should be one of the values + * specified in the values attribute. + * @param {String} value the new value of this element + */ + this.setValue = function(value){ + this.setProperty("value", value, false, true); + }; + + this.showMenu = function(){ + if (this.submenu && !this.value) + menuDown.call(this); + } + + this.hideMenu = function(){ + if (this.submenu && this.value) + menuDown.call(this); + } + + /** + * Sets the text displayed as caption of this element. + * + * @param {String} value required The string to display. + * @see baseclass.validation + */ + this.setCaption = function(value){ + this.setProperty("caption", value, false, true); + }; + + /** + * Sets the URL of the icon displayed on this element. + * + * @param {String} value required The URL to the location of the icon. + * @see element.button + * @see element.modalwindow + */ + this.setIcon = function(url){ + this.setProperty("icon", url, false, true); + }; + + + + /**** Private state methods ****/ + + this.$setStateBehaviour = function(value){ + this.value = value || false; + this.isBoolean = true; + this.$setStyleClass(this.$ext, this.$baseCSSname + "Bool"); + + if (this.value) { + this.$setStyleClass(this.$ext, this.$baseCSSname + "Down"); + this.$doBgSwitch(this.states["Down"]); + } + }; + + this.$setNormalBehaviour = function(){ + this.value = null; + this.isBoolean = false; + this.$setStyleClass(this.$ext, "", [this.$baseCSSname + "Bool"]); + }; + + this.$setState = function(state, e, strEvent){ + var parentNode = this.parentNode; + //if (this.disabled) + //return; + + if (strEvent && this.dispatchEvent(strEvent, {htmlEvent: e}) === false) + return; + + if(parentNode && parentNode.$button2 && parentNode.$button2.value && !this.submenu) + return; + + this.$doBgSwitch(this.states[state]); + var bs = this.$baseCSSname; + this.$setStyleClass(this.$ext, (state != "Out" ? bs + state : ""), + [(this.value ? "" : bs + "Down"), bs + "Over"]); + + if (this.submenu) { + bs = this.$baseCSSname + "menu"; + this.$setStyleClass(this.$ext, (state != "Out" ? bs + state : ""), + [(this.value ? "" : bs + "Down"), bs + "Over"]); + } + + //if (state != "Down") + //e.cancelBubble = true; + }; + + this.$clickHandler = function(){ + // This handles the actual OnClick action. Return true to redraw the button. + if (this.isBoolean && !this.submenu) { + this.setProperty("value", !this.value); + return true; + } + }; + + + this.$submenu = function(hide, force){ + if (hide) { + this.setValue(false); + this.$setState("Out", {}, "mouseout"); + if(this.parentNode) + this.parentNode.menuIsPressed = false; + } + }; + + + /**** Init ****/ + + this.addEventListener("$skinchange", function(e){ + if (this.tooltip) + apf.GuiElement.propHandlers.tooltip.call(this, this.tooltip); + }); + + this.$draw = function(){ + var pNode, isToolbarButton = (pNode = this.parentNode) + && pNode.parentNode.localName == "toolbar"; + + if (isToolbarButton) { + if (typeof this.focussable == "undefined") + this.focussable = false; + + this.$focussable = apf.KEYBOARD; + } + + //Build Main Skin + this.$ext = this.$getExternal(); + this.oIcon = this.$getLayoutNode("main", "icon", this.$ext); + this.oCaption = this.$getLayoutNode("main", "caption", this.$ext); + + this.$useExtraDiv = apf.isTrue(this.$getOption("main", "extradiv")); + if (!apf.button.$extradiv && this.$useExtraDiv) { + (apf.button.$extradiv = document.createElement("div")) + .className = "extradiv" + } + + if (this.localName == "submit") + this.action = "submit"; + else if (this.localName == "reset") + this.action = "reset"; + + this.$setupEvents(); + }; + + + this.addEventListener("$skinchange", function(){ + if (this.caption) + this.$propHandlers["caption"].call(this, this.caption); + + if (this.icon) + this.$propHandlers["icon"].call(this, this.icon); + + this.$updateState({reset:1}); + //this.$blur(); + + //if (this.$focussable !== true && this.hasFocus()) + //apf.window.$focusLast(this.$focusParent); + }); + + + + //@todo solve how this works with XForms + this.addEventListener("click", function(e){ + var action = this.action; + + //#-ifdef __WITH_HTML5 + if (!action) + action = this.localName; + //#-endif + + var _self = this; + $setTimeout(function(){ + (apf.button.actions[action] || apf.K).call(_self); + }); + }); + + + +}).call(apf.button.prototype = new apf.BaseButton()); + +// submit, trigger, reset, button +apf.submit.prototype = +apf.trigger.prototype = +apf.reset.prototype = apf.button.prototype; + +apf.aml.setElement("submit", apf.submit); +apf.aml.setElement("trigger", apf.trigger); +apf.aml.setElement("reset", apf.reset); +apf.aml.setElement("button", apf.button); + + +apf.submit.action = +apf.trigger.actions = +apf.reset.actions = +apf.button.actions = { + + "undo" : function(action){ + var tracker; + if (this.target && self[this.target]) { + tracker = self[this.target].localName == "actiontracker" + ? self[this.target] + : self[this.target].getActionTracker(); + } + else { + var at, node = this; + while(node.parentNode) + at = (node = node.parentNode).$at; + } + + (tracker || apf.window.$at)[action || "undo"](); + }, + + "redo" : function(){ + apf.button.actions.undo.call(this, "redo"); + }, + + + + "remove" : function(){ + if (this.target && self[this.target]) + self[this.target].remove() + + else + apf.console.warn("Target to remove wasn't found or specified:'" + + this.target + "'"); + + }, + + "add" : function(){ + if (this.target && self[this.target]) + self[this.target].add() + + else + apf.console.warn("Target to add wasn't found or specified:'" + + this.target + "'"); + + }, + + "rename" : function(){ + if (this.target && self[this.target]) + self[this.target].startRename() + + else + apf.console.warn("Target to rename wasn't found or specified:'" + + this.target + "'"); + + }, + + + + "login" : function(){ + var parent = this.target && self[this.target] + ? self[this.target] + : this.parentNode; + + var vg = parent.$validgroup || new apf.ValidationGroup(); + if (!vg.childNodes.length) + vg.childNodes = parent.childNodes.slice(); + + var vars = {}; + function loopChildren(nodes){ + for (var node, i = 0, l = nodes.length; i < l; i++) { + node = nodes[i]; + + if (node.hasFeature(apf.__VALIDATION__) + && !node.$validgroup && !node.form) { + node.setProperty("validgroup", vg); + } + + if (node.type) + vars[node.type] = node.getValue(); + + if (vars.username && vars.password) + return; + + if (node.childNodes.length) + loopChildren(node.childNodes); + } + } + loopChildren(parent.childNodes); + + if (!vg.isValid()) + return; + + if (!vars.username || !vars.password) { + + throw new Error(apf.formatErrorString(0, this, + "Clicking the login button", + "Could not find the username or password box")); + + + return; + } + + var auth = this.ownerDocument.getElementsByTagNameNS(apf.ns.apf,"auth")[0]; + if (!auth) + return; + + auth.logIn(vars.username, vars.password); + //apf.auth.login(vars.username, vars.password); + }, + + "logout" : function(){ + var auth = this.ownerDocument.getElementsByTagNameNS(apf.ns.apf, "auth")[0]; + if (!auth) + return; + + auth.logOut(); + }, + + + + "submit" : function(doReset){ + var vg, model; + + var parent = this.target && self[this.target] + ? self[this.target] + : this.parentNode; + + if (parent.$isModel) + model = parent; + else { + if (!parent.$validgroup) { + parent.$validgroup = parent.validgroup + ? self[parent.validgroup] + : new apf.ValidationGroup(); + } + + vg = parent.$validgroup; + if (!vg.childNodes.length) + vg.childNodes = parent.childNodes.slice(); + + function loopChildren(nodes){ + for (var node, i = 0, l = nodes.length; i < l; i++) { + node = nodes[i]; + + if (node.getModel) { + model = node.getModel(); + if (model) + return false; + } + + if (node.childNodes.length) + if (loopChildren(node.childNodes) === false) + return false; + } + } + loopChildren(parent.childNodes); + + if (!model) { + model = apf.globalModel; + if (!model) { + + throw new Error(apf.formatErrorString(0, this, + "Finding a model to submit", + "Could not find a model to submit.")); + + + return; + } + } + } + + if (doReset) { + model.reset(); + return; + } + + if (vg && !vg.isValid()) + return; + + model.submit(); + }, + + "reset" : function(){ + apf.button.actions["submit"].call(this, true); + }, + + + + "ok" : function(){ + var node; + + if (this.target) { + node = self[this.target]; + } + else { + var node = this.parentNode; + while (node && !node.hasFeature(apf.__TRANSACTION__)) { + node = node.parentNode; + } + + if (node && !node.hasFeature(apf.__TRANSACTION__)) + return; + } + + if (node.commit() && node.close) + node.close(); + }, + + "cancel" : function(){ + var node; + + if (this.target) { + node = self[this.target]; + } + else { + var node = this.parentNode; + while (node && !node.hasFeature(apf.__TRANSACTION__)) { + node = node.parentNode; + } + + if (node && !node.hasFeature(apf.__TRANSACTION__)) + return; + } + + node.rollback(); + if (node.close) + node.close(); + }, + + "apply" : function(){ + var node; + + if (this.target) { + node = self[this.target]; + } + else { + var node = this.parentNode; + while (node && !node.hasFeature(apf.__TRANSACTION__)) { + node = node.parentNode; + } + + if (node && !node.hasFeature(apf.__TRANSACTION__)) + return; + } + + if (node.autoshow) + node.autoshow = -1; + if (node.commit(true)) + node.begin("update"); + }, + + + "close" : function(){ + var parent = this.target && self[this.target] + ? self[this.target] + : this.parentNode; + + while(parent && !parent.close) + parent = parent.parentNode; + + if (parent && parent.close) + parent.close(); + + else + apf.console.warn("Target to close wasn't found or specified:'" + + this.target + "'"); + + } +}; + + + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/elements/caldropdown.js)SIZE(36358)TIME(Mon, 10 Jan 2011 10:53:12 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + +/** + * Element displaying a calendar, ordered by week. It allows the user to choose + * the month and year for which to display the days. Calendar returns a date + * in chosen date format. Minimal size of calendar is 150px. + * + * Remarks: + * The language variables possible to use of this component: + * + * + * + * T + * Today + * + * + * + * + * @constructor + * @define caldropdown + * @addnode elements + * + * @author Lukasz Lipinski + * @version %I%, %G% + * @since 1.0 + * + * @inherits apf.DataAction + * + * @binding value Determines the way the value for the element is retrieved + * from the bound data. + * + * @attribute {String} output-format the format of the returned date; See {@link term.dateformat more about the date format}. + * @attribute {String} caption-format the format of the displayed date. Default is yyyy-mm-dd. See {@link term.dateformat more about the date format}. + * @attribute {String} default the default date set when the calendar is opened. + * Possible values: + * today calendar is set on today's date + * @attribute {String} value the date returned by calendar; should be in the format specified by the output-format attribute. + * + * @event slidedown Fires when the calendar slides open. + * cancelable: Prevents the calendar from sliding open + * @event slideup Fires when the calendar slides up. + * cancelable: Prevents the calendar from sliding up + * + * Example: + * Calendar component with date set on "Saint Nicholas Day" in iso date format + * + * + * + * + * Example: + * Sets the date based on data loaded into this component. + * + * + * + * + * + * + */ + +apf.caldropdown = function(struct, tagName){ + this.$animType = 1; + this.$animSteps = 5; + this.$animSpeed = 20; + this.$itemSelectEvent = "onmouseup"; + + /**** Properties and Attributes ****/ + + this.dragdrop = false; + this.reselectable = true; + this.$focussable = true; + this.autoselect = false; + this.multiselect = false; + + this.outputFormat = "yyyy-mm-dd"; + this.captionFormat = "yyyy-mm-dd"; + + this.$sliderHeight = 0; + this.isOpen = false; + + this.$calVars = { + day : null, + month : null, + year : null, + hours : 1, + minutes : 0, + seconds : 0, + currentMonth : null, + currentYear : null, + numberOfDays : null, + dayNumber : null, + + days : ["Sunday", "Monday", "Tuesday", "Wednesday", + "Thursday", "Friday", "Saturday"], + months : [{name : "January", number : 31}, + {name : "February", number : 28}, + {name : "March", number : 31}, + {name : "April", number : 30}, + {name : "May", number : 31}, + {name : "June", number : 30}, + {name : "July", number : 31}, + {name : "August", number : 31}, + {name : "September", number : 30}, + {name : "October", number : 31}, + {name : "November", number : 30}, + {name : "December", number : 31}] + }; + + this.$init(tagName || "caldropdown", apf.NODE_VISIBLE, struct); +}; + +(function() { + this.implement( + + apf.DataAction + + + //,apf.StandardBinding + + + ); + + this.$supportedProperties.push("initial-message", "output-format", + "default", "caption-format", "value"); + + /** + * @attribute {String} initial-message the message displayed by this element + * when it doesn't have a value set. This property is inherited from parent + * nodes. When none is found it is looked for on the appsettings element. + * + * @attribute {String} output-format style of returned date + * + * Possible values: + * d day of the month as digits, no leading zero for single-digit days + * dd day of the month as digits, leading zero for single-digit days + * ddd day of the week as a three-letter abbreviation + * dddd day of the week as its full name + * m month as digits, no leading zero for single-digit months + * mm month as digits, leading zero for single-digit months + * mmm month as a three-letter abbreviation + * mmmm month as its full name + * yy year as last two digits, leading zero for years less than 2010 + * yyyy year represented by four digits + * h hours, no leading zero for single-digit hours (12-hour clock) + * hh hours, leading zero for single-digit hours (12-hour clock) + * H hours, no leading zero for single-digit hours (24-hour clock) + * HH hours, leading zero for single-digit hours (24-hour clock) + * M minutes, no leading zero for single-digit minutes + * MM minutes, leading zero for single-digit minutes + * s seconds, no leading zero for single-digit seconds + * ss seconds, leading zero for single-digit seconds + */ + this.$propHandlers["output-format"] = function(value) { + this.outputFormat = value; + } + + /** + * @attribute {String} caption-format style of returned date + * + * Possible values + * d day of the month as digits, no leading zero for single-digit days + * dd day of the month as digits, leading zero for single-digit days + * ddd day of the week as a three-letter abbreviation + * dddd day of the week as its full name + * m month as digits, no leading zero for single-digit months + * mm month as digits, leading zero for single-digit months + * mmm month as a three-letter abbreviation + * mmmm month as its full name + * yy year as last two digits, leading zero for years less than 2010 + * yyyy year represented by four digits + * h hours, no leading zero for single-digit hours (12-hour clock) + * hh hours, leading zero for single-digit hours (12-hour clock) + * H hours, no leading zero for single-digit hours (24-hour clock) + * HH hours, leading zero for single-digit hours (24-hour clock) + * M minutes, no leading zero for single-digit minutes + * MM minutes, leading zero for single-digit minutes + * s seconds, no leading zero for single-digit seconds + * ss seconds, leading zero for single-digit seconds + */ + this.$propHandlers["caption-format"] = function(value) { + if (this.value) { + var c = this.$calVars; + this.$setLabel(new Date(c.year, c.month, c.day, c.hours, + c.minutes, c.seconds).format(this.captionFormat = value)); + } + else + this.captionFormat = value; + } + + this.$propHandlers["value"] = function(value) { + var c = this.$calVars; + var outputFormat = this.getAttribute("output-format"); + + if (outputFormat !== null && outputFormat != this.outputFormat) { + this.outputFormat = outputFormat; + } + + if (!value) { + this.$setLabel(); + return; + } + + var date = apf.date.getDateTime(value, this.outputFormat); + + + if (!date || isNaN(date)) { + return; + /*throw new Error(apf.formErrorString(this, "Parsing date", + "Invalid date: " + value));*/ + } + + + c.day = date.getDate(); + c.month = date.getMonth(); + c.year = date.getFullYear(); + c.hours = date.getHours(); + c.minutes = date.getMinutes(); + c.seconds = date.getSeconds(); + + this.value = value; + this.$setLabel(new Date(c.year, c.month, c.day, c.hours, + c.minutes, c.seconds).format(this.captionFormat)); + this.redraw(c.month, c.year); + } + + + /** + * @method + * + * @return {String} date indicated by calendar + */ + this.getValue = function(){ + return this.value; + } + + /** + * @method Sets calendar date + */ + this.setValue = function(value){ + this.setProperty("value", value, false, true); + } + + + /**** Keyboard Handling ****/ + + + this.addEventListener("keydown", function(e) { + e = e || event; + + var key = e.keyCode, + ctrlKey = e.ctrlKey, + shiftKey = e.shiftKey, + c = this.$calVars; + + switch (key) { + case 13: /* enter */ + this.selectDay(c.day); + this.slideUp(); + break; + + case 33: /* page up */ + this.nextMonth(); + break; + + case 34: /* page down */ + this.prevMonth(); + break; + + case 37: /* left arrow */ + if (ctrlKey) + this.prevMonth(); + else if (shiftKey) + this.prevYear(); + else { + if (c.day - 1 < 1) { + this.prevMonth(); + this.selectDay(c.months[c.currentMonth].number); + } + else { + this.selectDay(c.day - 1); + } + } + break; + + case 38: /* up arrow */ + if (ctrlKey) + this.slideUp(); + else { + if (c.day - 7 < 1) { + this.prevMonth(); + this.selectDay(c.months[c.currentMonth].number + c.day - 7); + } + else { + this.selectDay(c.day - 7); + } + } + break; + + case 39: /* right arrow */ + if (ctrlKey) + this.nextMonth(); + else if (shiftKey) + this.nextYear(); + else + this.selectDay(c.day + 1); + break; + + case 40: /* down arrow */ + if (ctrlKey) + this.slideDown(e); + else + this.selectDay(c.day + 7); + break; + + case 84: + if (ctrlKey) + this.today(); + return false; + break; + } + }, true); + + + /**** Public methods ****/ + + /** + * Toggles the visibility of the container with the calendar. It opens + * or closes container using a slide effect. + */ + this.slideToggle = function(e, userAction) { + if (!e) e = event; + if (userAction && this.disabled) + return; + + if (this.isOpen) + this.slideUp(); + else + this.slideDown(e); + }; + + /** + * Shows the container with the list elements using a slide effect. + */ + this.slideDown = function(e) { + + if (this.dispatchEvent("slidedown") === false) + return false; + + this.isOpen = true; + + this.oSlider.style.display = "block"; + this.oSlider.style[apf.supportOverflowComponent + ? "overflowY" + : "overflow"] = "hidden"; + + this.oSlider.style.display = ""; + this.$setStyleClass(this.$ext, this.$baseCSSname + "Down"); + + this.oSlider.style.height = (this.$sliderHeight > 0 + ? this.$sliderHeight - 1 + : 0) + "px"; + + this.oSlider.style.width = (this.$ext.offsetWidth > 0 + ? this.$ext.offsetWidth - 1 + : 0) + "px"; + + apf.caldropdown.cache["oSlider"].host = this; + + this.redraw(this.$calVars.month, this.$calVars.year); + + apf.popup.show(this.$uniqueId, { + x : 0, + y : this.$ext.offsetHeight, + animate : true, + ref : this.$ext, + width : this.$ext.offsetWidth + 1, + height : this.$sliderHeight, + callback: function(container) { + container.style[apf.supportOverflowComponent + ? "overflowY" + : "overflow"] = "hidden"; + } + }); + }; + + this.$getPageScroll = function() { + return [ + document.documentElement.scrollLeft || document.body.scrollLeft, + document.documentElement.scrollTop || document.body.scrollTop + ]; + } + + /** + * Hides the container with the calendar using a slide effect. + */ + this.slideUp = function() { + /*if (!this.isOpen) return false;*/ + if (this.dispatchEvent("slideup") === false) return false; + + this.isOpen = false; + if (this.selected) { + var htmlNode = apf.xmldb.findHtmlNode(this.selected, this); + if (htmlNode) this.$setStyleClass(htmlNode, '', ["hover"]); + } + + this.$setStyleClass(this.$ext, '', [this.$baseCSSname + "Down"]); + apf.popup.hide(); + return false; + }; + + /**** Private methods and event handlers ****/ + + this.$setLabel = function(value) { + this.oLabel.innerHTML = value || this["initial-message"] || ""; + + this.$setStyleClass(this.$ext, value ? "" : this.$baseCSSname + "Initial", + [!value ? "" : this.$baseCSSname + "Initial"]); + }; + + this.addEventListener("afterselect", function(e) { + if (!e) e = event; + + this.slideUp(); + if (!this.isOpen) + this.$setStyleClass(this.$ext, "", [this.$baseCSSname + "Over"]); + + this.$setLabel(e.selection.length + ? this.$applyBindRule("value", this.selected) + : "") + + + if (this.hasFeature(apf.__VALIDATION__) && this.form) { + this.validate(true); + } + + }); + + // Private functions + this.$blur = function() { + this.slideUp(); + //this.$ext.dispatchEvent("mouseout") + if (!this.isOpen) + this.$setStyleClass(this.$ext, "", [this.$baseCSSname + "Over"]) + //if(this.$ext.onmouseout) this.$ext.onmouseout(); + + this.$setStyleClass(this.$ext, "", [this.$baseCSSname + "Focus"]); + }; + + this.$focus = function(){ + apf.popup.forceHide(); + this.$setStyleClass(this.oFocus || this.$ext, this.$baseCSSname + "Focus"); + } + + this.$setClearMessage = function(msg) { + if (msg) + this.$setLabel(msg); + }; + + this.$removeClearMessage = function() { + this.$setLabel(""); + }; + + + this.addEventListener("slidedown", function() { + //THIS SHOULD BE UPDATED TO NEW SMARTBINDINGS + if (!this.form || !this.form.xmlActions || this.xmlRoot) + return; + var loadlist = this.form.xmlActions + .selectSingleNode("LoadList[@element='" + this.name + "']"); + if (!loadlist) return; + + this.form.processLoadRule(loadlist, true, [loadlist]); + + return false; + }); + + + this.addEventListener("popuphide", this.slideUp); + + var isLeapYear = function(year) { + return ((year % 4 === 0) && (year % 100 !== 0)) || (year % 400 === 0) + ? true + : false; + }; + + this.redraw = function(month, year) { + var c = this.$calVars; + c.currentMonth = month; + c.currentYear = year; + + var _width = this.$ext.offsetWidth, + temp = Math.floor((_width - 36) / 8) * 8 + 32 + - apf.getDiff(this.oNavigation)[0], + w_days = new Date(year, 0, 1).getDay(); + + if (temp >= 0) + this.oNavigation.style.width = temp + "px"; + + //var w_firstYearDay = new Date(year, 0, 1); + //var w_dayInWeek = w_firstYearDay.getDay(); + + for (i = 0; i <= month; i++) { + if (isLeapYear(year) && i == 1) + w_days++; + w_days += c.months[i].number; + } + + var w_weeks = Math.ceil(w_days / 7), + date = new Date(year, month); + + c.numberOfDays = c.months[date.getMonth()].number; + if (isLeapYear(year) && date.getMonth() == 1) + c.numberOfDays++; + + c.dayNumber = new Date(year, month, 1).getDay(); + var prevMonth = month == 0 ? 11 : month - 1, + prevMonthDays = c.months[prevMonth].number - c.dayNumber + 1, + nextMonthDays = 1, + rows = this.oNavigation.childNodes, + cells, + y; + + for (i = 0; i < rows.length; i++) { + if ((rows[i].className || "").indexOf("today") != -1) { + if (_width < 300) { + var shortTodayText = apf.language.getWord("sub.calendar.shortToday") || "T"; + rows[i].innerHTML = shortTodayText; + rows[i].style.width = "8px"; + rows[i].setAttribute("title", shortTodayText); + } + else { + var TodayText = apf.language.getWord("sub.calendar.today") || "Today"; + rows[i].innerHTML = TodayText; + rows[i].style.width = "32px"; + rows[i].setAttribute("title", TodayText); + } + } + else if ((rows[i].className || "").indexOf("status") != -1) { + if (_width >= 300) + rows[i].innerHTML = c.months[c.currentMonth].name + + " " + c.currentYear; + else { + rows[i].innerHTML = (c.currentMonth + 1) + "/" + c.currentYear; + } + } + } + + this.$sliderHeight = 22; //navigators bar height + temp = Math.floor((_width - 37) / 8); + var squareSize = temp > 0 ? temp : 0; + + var daysofweek = this.oDow.childNodes, + d_height = Math.floor(squareSize / 4 + 6), + d_paddingTop = Math.floor(squareSize / 4 - 8) > 0 + ? Math.floor(squareSize / 4 - 8) + : 0, + d_fontSize = _width < 150 ? "6px" : (_width <= 220 ? "9px" : "11px"), + d_width = (squareSize * 8 + 32); + + this.oDow.style.width = d_width + "px"; + + this.$sliderHeight += d_height + d_paddingTop; + + for (var z = 0, i = 0; i < daysofweek.length; i++) { + if ((daysofweek[i].className || "").indexOf("dayofweek") > -1) { + daysofweek[i].style.width = squareSize + "px"; + daysofweek[i].style.height = d_height + "px"; + daysofweek[i].style.paddingTop = d_paddingTop + "px"; + daysofweek[i].style.fontSize = d_fontSize; + + if (z > 0) { + daysofweek[i].innerHTML = c.days[z - 1].substr(0, 3); + } + z++; + } + } + + var c_height = Math.floor((squareSize + 12) / 2), + c_paddingTop = squareSize - c_height > 0 + ? squareSize - c_height + : 0; + + rows = this.oSlider.childNodes; + for (z = 0, y = 0, i = 0; i < rows.length; i++) { + if ((rows[i].className || "").indexOf("row") == -1) + continue; + + rows[i].style.width = (d_width - apf.getDiff(rows[i])[0]) + "px"; + if (!apf.isGecko) { + rows[i].style.paddingTop = "1px"; + } + + this.$sliderHeight += (squareSize + 5); + + cells = rows[i].childNodes; + for (var j = 0, disabledRow = 0; j < cells.length; j++) { + if ((cells[j].className || "").indexOf("cell") == -1) + continue; + z++; + cells[j].style.width = squareSize + "px"; + cells[j].style.height = c_height + "px"; + cells[j].style.paddingTop = c_paddingTop + "px"; + + cells[j].style.margin = z%8 == 0 && z !== 1 + ? "1px 0 1px 0" + : "1px 2px 1px 0"; + + this.$setStyleClass(cells[j], "", ["weekend", "disabled", + "active", "prev", "next", "weeknumber"]); + + if ((z - 1) % 8 == 0) { + cells[j].innerHTML = w_weeks + - Math.ceil((c.months[c.currentMonth].number + c.dayNumber) / 7) + + 1 + (z - 1) / 8; + this.$setStyleClass(cells[j], "weeknumber"); + } + else { + y++; + if (y <= c.dayNumber) { + cells[j].innerHTML = prevMonthDays++; + this.$setStyleClass(cells[j], "disabled prev"); + } + else if (y > c.dayNumber && y <= c.numberOfDays + c.dayNumber) { + cells[j].innerHTML = y - c.dayNumber; + + var dayNrWeek = new Date(year, month, + y - c.dayNumber).getDay(); + + if (dayNrWeek == 0 || dayNrWeek == 6) { + this.$setStyleClass(cells[j], "weekend"); + } + + if (month == c.month && year == c.year + && y - c.dayNumber == c.day) { + this.$setStyleClass(cells[j], "active"); + } + } + else if (y > c.numberOfDays + c.dayNumber) { + cells[j].innerHTML = nextMonthDays++; + this.$setStyleClass(cells[j], "disabled next"); + disabledRow++; + } + } + } + + rows[i].style.visibility = disabledRow == 7 + ? "hidden" + : "visible"; + } + }; + + /** + * Selects date and highlights its cell in calendar component + * + * @param {Number} nr day number + * @param {String} type class name of html representation of selected cell + */ + this.selectDay = function(nr, type) { + var c = this.$calVars, + newMonth = type == "prev" + ? c.currentMonth + : (type == "next" + ? c.currentMonth + 2 + : c.currentMonth + 1), + newYear = c.currentYear; + + if (newMonth < 1) { + newMonth = 12; + newYear--; + } + else if (newMonth > 12) { + newMonth = 1; + newYear++; + } + + this.change(new Date(newYear, (newMonth - 1), nr, c.hours, + c.minutes, c.seconds).format(this.outputFormat)); + }; + + /** + * Change displayed year to next one + */ + this.nextYear = function() { + this.redraw(this.$calVars.currentMonth, this.$calVars.currentYear + 1); + }; + + /** + * Change displayed year to previous one + */ + this.prevYear = function() { + this.redraw(this.$calVars.currentMonth, this.$calVars.currentYear - 1); + }; + + /** + * Change displayed month to next one. If actual month is December, function + * change current displayed year to next one + */ + this.nextMonth = function() { + var newMonth, newYear, + c = this.$calVars; + if (c.currentMonth > 10) { + newMonth = 0; + newYear = c.currentYear + 1; + } + else { + newMonth = c.currentMonth + 1; + newYear = c.currentYear; + } + + this.redraw(newMonth, newYear); + }; + + /** + * Change displayed month to previous one. If actual month is January, + * function change current displayed year to previous one + */ + this.prevMonth = function() { + var newMonth, newYear, + c = this.$calVars; + if (c.currentMonth < 1) { + newMonth = 11; + newYear = c.currentYear - 1; + } + else { + newMonth = c.currentMonth - 1; + newYear = c.currentYear; + } + + this.redraw(newMonth, newYear); + }; + + /** + * Select today's date in calendar component + */ + this.nextDay = function() { + var c = this.$calVars; + //this.change( + this.$propHandlers["value"].call(this, new Date(c.year, c.month, c.day + 1, 0, 0, 0).format(this.outputFormat)); + }; + + this.previousDay = function() { + var c = this.$calVars; + //this.change( + this.$propHandlers["value"].call(this, new Date(c.year, c.month, c.day - 1, 0, 0, 0).format(this.outputFormat)); + }; + + /** + * Select today's date in calendar component + */ + this.today = function() { + //this.change + this.$propHandlers["value"].call(this, new Date().format(this.outputFormat)); + }; + + /**** Init ****/ + + this.$draw = function() { + this.$getNewContext("main"); + this.$getNewContext("container"); + + this.$animType = this.$getOption("main", "animtype") || 1; + this.clickOpen = this.$getOption("main", "clickopen") || "button"; + + //Build Main Skin + this.$ext = this.$getExternal(null, null, function(oExt) { + oExt.setAttribute("onmouseover", + 'var o = apf.lookup(' + this.$uniqueId + ');\ + o.$setStyleClass(o.$ext, o.$baseCSSname + "Over", null, true);'); + oExt.setAttribute("onmouseout", + 'var o = apf.lookup('+ this.$uniqueId + ');\ + if (o.isOpen) return;\ + o.$setStyleClass(o.$ext, "", [o.$baseCSSname + "Over"], true);'); + + //Button + var oButton = this.$getLayoutNode("main", "button", oExt); + if (oButton) { + oButton.setAttribute("onmousedown", + 'apf.lookup(' + this.$uniqueId + ').slideToggle(event, true);'); + } + + //Label + var oLabel = this.$getLayoutNode("main", "label", oExt); + if (this.clickOpen == "both") { + oLabel.parentNode.setAttribute("onmousedown", 'apf.lookup(' + + this.$uniqueId + ').slideToggle(event, true);'); + } + }); + this.oLabel = this.$getLayoutNode("main", "label", this.$ext); + + if (this.oLabel.nodeType == 3) + this.oLabel = this.oLabel.parentNode; + + this.oIcon = this.$getLayoutNode("main", "icon", this.$ext); + if (this.$button) + this.$button = this.$getLayoutNode("main", "button", this.$ext); + + if (apf.caldropdown.cache) { + var cal = apf.caldropdown.cache; + this.oSlider = cal["oSlider"]; + this.oNavigation = cal["oNavigation"]; + this.oDow = cal["oDow"]; + + apf.caldropdown.cache.refcount++; + + //Set up the popup + this.$pHtmlDoc = apf.popup.setContent(this.$uniqueId, this.oSlider, + apf.skins.getCssString(this.skinName)); + } + else { + this.oSlider = this.$getExternal("container", null, function(oExt1) { + var i, oSlider = this.$getLayoutNode("container", "contents", oExt1); + + for (i = 0; i < 6; i++) { + this.$getNewContext("row"); + var oRow = oSlider.appendChild(this.$getLayoutNode("row")); + + for (var j = 0; j < 8; j++) { + this.$getNewContext("cell"); + var oCell = this.$getLayoutNode("cell"); + if (j > 0) { + oCell.setAttribute("onmouseout", + "apf.lookup(" + this.$uniqueId + + ").$setStyleClass(this, '', ['hover'], true);"); + oCell.setAttribute("onmouseover", + "if (this.className.indexOf('disabled') > -1 \ + || this.className.indexOf('active') > -1) \ + return;\ + apf.lookup(" + this.$uniqueId + + ").$setStyleClass(this, 'hover', null, true);"); + oCell.setAttribute("onmousedown", + "var o = apf.lookup(" + this.$uniqueId + ");\ + if (o.disabled) return;\ + if (this.className.indexOf('prev') > -1) \ + o.selectDay(this.innerHTML, 'prev');\ + else if (this.className.indexOf('next') > -1) \ + o.selectDay(this.innerHTML, 'next');\ + else \ + o.selectDay(this.innerHTML);\ + o.slideUp();"); + } + oRow.appendChild(oCell); + } + } + + var oNavigation = this.$getLayoutNode("container", "navigation", + oExt1); + + if (oNavigation) { + var buttons = ["prevYear", "prevMonth", "nextYear", "nextMonth", + "today", "status"]; + for (i = 0; i < buttons.length; i++) { + this.$getNewContext("button"); + var btn = oNavigation.appendChild(this.$getLayoutNode("button")); + this.$setStyleClass(btn, buttons[i]); + if (buttons[i] !== "status") { + btn.setAttribute("onmousedown", 'var o = apf.lookup(' + this.$uniqueId + + '); if (o.disabled) return; o.' + + buttons[i] + '();apf.setStyleClass(this, "down");'); + btn.setAttribute("onmouseup", 'apf.lookup(' + this.$uniqueId + + ').$setStyleClass(this, "", ["down"], true);'); + btn.setAttribute("onmouseover", 'apf.lookup(' + this.$uniqueId + + ').$setStyleClass(this, "hover", null, true);'); + btn.setAttribute("onmouseout", 'apf.lookup(' + this.$uniqueId + + ').$setStyleClass(this, "", ["hover"], true);'); + } + } + } + + var oDaysOfWeek = this.$getLayoutNode("container", + "daysofweek", oExt1); + + for (i = 0; i < this.$calVars.days.length + 1; i++) { + this.$getNewContext("day"); + oDaysOfWeek.appendChild(this.$getLayoutNode("day")); + } + }); + + this.oNavigation = this.$getLayoutNode("container", "navigation", this.oSlider); + this.oDow = this.$getLayoutNode("container", "daysofweek", this.oSlider); + } + + + + //Set up the popup + this.$pHtmlDoc = apf.popup.setContent(this.$uniqueId, this.oSlider, + apf.skins.getCssString(this.skinName)); + + document.body.appendChild(this.oSlider); + + //Get Options form skin + //Types: 1=One dimensional List, 2=Two dimensional List + this.listtype = parseInt(this.$getLayoutNode("main", "type")) || 1; + + if (!apf.caldropdown.cache) { + apf.caldropdown.cache = { + "oSlider" : this.oSlider, + "oNavigation" : this.oNavigation, + "oDow" : this.oDow + }; + apf.caldropdown.cache.refcount = 0; + } + }; + + this.$loadAml = function(x) { + var date, c = this.$calVars; + + if (typeof this.value == "undefined") { + switch(this["default"]) { + case "today": + //this.setProperty("value", + this.$propHandlers["value"].call(this, new Date().format(this.outputFormat)); + break; + default : + date = new Date(); + c.day = 0; + c.month = date.getMonth(); + c.year = date.getFullYear(); + +// if (!this.selected && this["initial-message"]) + this.$setLabel(); + break; + } + } + else { + date = apf.date.getDateTime(this.value, this.outputFormat); + c.day = date.getDate(); + c.month = date.getMonth(); + c.year = date.getFullYear(); + + if (c.day && c.month && c.year) { + //this.setProperty("value", + this.$propHandlers["value"].call(this, new Date(c.year, c.month, c.day, c.hours, + c.minutes, c.seconds).format(this.outputFormat)); + } + } + }; + + this.$destroy = function() { + apf.popup.removeContent(this.$uniqueId); + apf.destroyHtmlNode(this.oSlider); + this.oSlider = null; + + if (apf.caldropdown.cache) { + if (apf.caldropdown.cache.refcount == 0) + apf.caldropdown.cache = null; + else + apf.caldropdown.cache.refcount--; + } + }; +}).call(apf.caldropdown.prototype = new apf.StandardBinding()); + +apf.config.$inheritProperties["initial-message"] = 1; + +apf.aml.setElement("caldropdown", apf.caldropdown); + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/elements/calendar.js)SIZE(29646)TIME(Mon, 10 Jan 2011 10:53:12 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + +/** + * Element displaying a calendar, ordered by week. It allows the user to choose + * the month and year for which to display the days. Calendar returns a date + * in chosen date format. Minimal size of calendar is 150px. + * + * Example: + * Calendar component with date set on "Saint Nicholas Day" in iso date format + * + * + * + * + * Example: + * Sets the date based on data loaded into this component. + * + * + * + * + * + * + * + * @constructor + * @define calendar + * @addnode elements + * + * @attribute {String} output-format the format of the returned value. See {@link term.dateformat more about the date format}. + * @attribute {String} default the default date set when the calendar is opened. + * Possible values: + * today calendar is set on today's date + * @attribute {String} value the date returned by calendar; should be in the format specified by the output-format attribute. + * + * @binding value Determines the way the value for the element is retrieved + * from the bound data. + * + * @inherits apf.DataAction + * + * @author Lukasz Lipinski + * @version %I%, %G% + * @since 1.0 + * + */ +apf.calendar = function(struct, tagName){ + this.$init(tagName || "calendar", apf.NODE_VISIBLE, struct); + + /**** Properties and Attributes ****/ + this.$calVars = { + days : ["Sunday", "Monday", "Tuesday", "Wednesday", + "Thursday", "Friday", "Saturday"], + months : [{name : "January", number : 31}, + {name : "February", number : 28}, + {name : "March", number : 31}, + {name : "April", number : 30}, + {name : "May", number : 31}, + {name : "June", number : 30}, + {name : "July", number : 31}, + {name : "August", number : 31}, + {name : "September", number : 30}, + {name : "October", number : 31}, + {name : "November", number : 30}, + {name : "December", number : 31}], + + day : null, + month : null, + year : null, + + hours : 1, + minutes : 0, + seconds : 0, + + currentMonth : null, + currentYear : null, + numberOfDays : null, + dayNumber : null, + + startDay : 0, + + temp : null, + + inited : false, + startDiffs : [], + prevWidth : 0, + + rRows : [], + rCells : [[], [], [], [], [], []], + rDoW : [] + }; +}; + +(function() { + this.implement( + + apf.DataAction + + + ); + + this.$supportedProperties.push("output-format", "default", "day", "month", "year"); + + /** + * @attribute {String} output-format is a style of returned date + * + * Possible values: + * d day of the month as digits, no leading zero for single-digit days + * dd day of the month as digits, leading zero for single-digit days + * ddd day of the week as a three-letter abbreviation + * dddd day of the week as its full name + * m month as digits, no leading zero for single-digit months + * mm month as digits, leading zero for single-digit months + * mmm month as a three-letter abbreviation + * mmmm month as its full name + * yy year as last two digits, leading zero for years less than 2010 + * yyyy year represented by four digits + * h hours, no leading zero for single-digit hours (12-hour clock) + * hh hours, leading zero for single-digit hours (12-hour clock) + * H hours, no leading zero for single-digit hours (24-hour clock) + * HH hours, leading zero for single-digit hours (24-hour clock) + * M minutes, no leading zero for single-digit minutes + * MM minutes, leading zero for single-digit minutes + * s seconds, no leading zero for single-digit seconds + * ss seconds, leading zero for single-digit seconds + */ + this.$propHandlers["output-format"] = function(value) { + if (this.value) { + var c = this.$calVars; + this.setProperty("value", + new Date( + c.year, c.month, c.day, c.hours, c.minutes, c.seconds + ).format(this.outputFormat = value) + ); + } + else + this.outputFormat = value; + }; + + this.$propHandlers["start-day"] = function(value) { + this.$calVars.startDay = parseInt(value); + }; + + this.$propHandlers["value"] = function(value) { + var c = this.$calVars; + + if (!this.outputFormat) { + c.temp = value; + return; + } + + var date = apf.date.getDateTime(value, this.outputFormat); + + + if (!date || isNaN(date)) { + return; + /*throw new Error(apf.formErrorString(this, "Parsing date", + "Invalid date: " + value));*/ + } + + + c.day = date.getDate(); + c.month = date.getMonth(); + c.year = date.getFullYear(); + c.hours = date.getHours(); + c.minutes = date.getMinutes(); + c.seconds = date.getSeconds(); + + this.setProperty("day", c.day); + this.setProperty("month", c.month + 1); + this.setProperty("year", c.year); + + this.value = value; + this.redraw(c.month, c.year); + }; + + /**** Public methods ****/ + + + + /** + * @method Sets calendar date + */ + this.setValue = function(value) { + this.setProperty("value", value, false, true); + }; + + /** + * @method + * + * @return {String} date indicated by calendar + */ + this.getValue = function() { + return this.value; + }; + + + + /**** Keyboard Support ****/ + + + this.addEventListener("keydown", function(e) { + e = e || event; + + var key = e.keyCode, + ctrlKey = e.ctrlKey, + shiftKey = e.shiftKey, + c = this.$calVars; + + switch (key) { + case 13: /* enter */ + this.selectDay(c.day); + break; + + case 33: /* page up */ + this.nextMonth(); + break; + + case 34: /* page down */ + this.prevMonth(); + break; + + case 37: /* left arrow */ + if (ctrlKey) + this.prevMonth(); + else if (shiftKey) + this.prevYear(); + else { + if (c.day - 1 < 1) { + this.prevMonth(); + this.selectDay(c.months[c.currentMonth].number); + } + else { + this.selectDay(c.day - 1); + } + } + break; + + case 38: /* up arrow */ + if (c.day - 7 < 1) { + this.prevMonth(); + this.selectDay(c.months[c.currentMonth].number + c.day - 7); + } + else { + this.selectDay(c.day - 7); + } + break; + + case 39: /* right arrow */ + if (ctrlKey) + this.nextMonth(); + else if (shiftKey) + this.nextYear(); + else + this.selectDay(c.day + 1); + break; + + case 40: /* down arrow */ + this.selectDay(c.day + 7); + break; + + case 84: + if (ctrlKey) + this.today(); + return false; + break; + } + }, true); + + + + this.$blur = function() { + this.$setStyleClass(this.$ext, "", [this.$baseCSSname + "Focus"]); + }; + + this.$focus = function() { + this.$setStyleClass(this.oFocus || this.$ext, this.$baseCSSname + "Focus"); + } + + var isLeapYear = function(year) { + return ((year % 4 == 0) && (year % 100 !== 0)) || (year % 400 == 0) + ? true + : false; + }; + + this.$getMargin = function(oHtml) { + return [ + (parseInt(apf.getStyle(oHtml, "marginLeft")) || 0) + + (parseInt(apf.getStyle(oHtml, "marginLeft")) || 0), + (parseInt(apf.getStyle(oHtml, "marginTop")) || 0) + + (parseInt(apf.getStyle(oHtml, "marginBottom")) || 0) + ]; + }; + + this.$getFontSize = function(oHtml) { + return parseInt(apf.getStyle(oHtml, "fontSize")); + }; + + this.$getPadding = function(oHtml) { + return [parseInt(apf.getStyle(oHtml, "paddingLeft")) + + parseInt(apf.getStyle(oHtml, "paddingRight")), + parseInt(apf.getStyle(oHtml, "paddingTop")) + + parseInt(apf.getStyle(oHtml, "paddingBottom"))]; + }; + + this.$resize = function() { + this.redraw(this.$calVars.currentMonth, this.$calVars.currentYear); + }; + + this.redraw = function(month, year) { + var w_firstYearDay = new Date(year, 0, 1), + w_dayInWeek = w_firstYearDay.getDay(), + w_days = w_dayInWeek, + c = this.$calVars; + + c.currentMonth = month; + c.currentYear = year; + + for (i = 0; i <= month; i++) { + if (isLeapYear(year) && i == 1) { + w_days++; + } + w_days += c.months[i].number; + } + + var w_weeks = Math.ceil(w_days / 7), + date = new Date(year, month); + + c.numberOfDays = c.months[date.getMonth()].number; + if (isLeapYear(year) && date.getMonth() == 1) + c.numberOfDays++; + + c.dayNumber = new Date(year, month, 1).getDay(); + var dayInTheWeek = c.dayNumber - c.startDay; + dayInTheWeek = dayInTheWeek < 0 ? 6 + 1 + dayInTheWeek : dayInTheWeek; + var prevMonth = month == 0 ? 11 : month - 1, + prevMonthDays = c.months[prevMonth].number - dayInTheWeek + 1, + + nextMonthDays = 1, + + ctDiff = apf.getDiff(this.$ext), + _width = parseInt(this.$ext.offsetWidth || this.$ext.style.width + || apf.getStyle(this.$ext, "width")) - ctDiff[0], + /* Navigation buttons */ + navi = this.oNavigation.childNodes; + + //@todo fix this!! + if (!_width) return; + + for (i = 0; i < navi.length; i++) { + if ((navi[i].className || "").indexOf("today") != -1) { + navi[i].innerHTML = "T"; + } + else if ((navi[i].className || "").indexOf("status") != -1) { + if (_width >= 300) { + navi[i].innerHTML = c.months[c.currentMonth].name + + " " + c.currentYear; + navi[i].style.width = "100px"; + navi[i].style.marginLeft = "-50px"; + } + else { + navi[i].innerHTML = (c.currentMonth + 1) + + "/" + c.currentYear; + navi[i].style.width = "40px"; + navi[i].style.marginLeft = "-20px"; + } + } + } + + //Rows + var rows = this.oContent.childNodes, + cWidthf, pl; + + for (var i = 0, z = 0, y = 0; i < rows.length; i++) { + if ((rows[i].className || "").indexOf("row") > -1) { + var rDiff = !c.inited ? apf.getDiff(rows[i]) : c.startDiffs[0]; + var rDiff2 = !c.inited ? this.$getMargin(rows[i]) : c.startDiffs[1]; + var rWidth = _width - rDiff[0] - rDiff2[0]; + + if (!c.inited) { + c.startDiffs[0] = rDiff + c.startDiffs[1] = rDiff2; + } + + //Cells + var cells = rows[i].childNodes; + for (var j = 0, disabledRow = 0; j < cells.length; j++) { + if ((cells[j].className || "").indexOf("cell") > -1) { + var cDiff = !c.inited ? apf.getDiff(cells[j]) : c.startDiffs[2], + cDiff2 = !c.inited ? this.$getMargin(cells[j]) : c.startDiffs[3]; + + if (!c.inited) { + c.startDiffs[2] = cDiff + c.startDiffs[3] = cDiff2; + + } + cWidthf = Math.floor(rWidth / 8) + - cDiff[0] - cDiff2[0]; + var width = cWidthf, + height = cWidthf + + (cDiff[1] > cDiff[0] + ? cDiff[0] - cDiff[1] + : 0) + + (cDiff2[1] > cDiff2[0] + ? cDiff2[0] - cDiff2[1] + : 0), + paddingTop; + + var paddingBottom = + paddingTop = Math.ceil((height + - this.$getFontSize(cells[j])) / 2); + + height -= (paddingTop + paddingBottom - cDiff[1]); + + cells[j].style.width = (width > 0 ? width : 0) + "px"; + + if (height > 0) + cells[j].style.height = height + "px"; + cells[j].style.paddingTop = (paddingTop > 0 ? paddingTop + 1 : 0) + "px"; + cells[j].style.paddingBottom = (paddingBottom > 0 ? paddingBottom - 1 : 0) + "px"; + + // Drawing day numbers + this.$setStyleClass(cells[j], "", ["weekend", + "disabled", "active", "prev", "next", "weeknumber"]); + + z++; + if ((z - 1) % 8 == 0) { + cells[j].innerHTML = w_weeks + - Math.ceil((c.months[c.currentMonth].number + c.dayNumber) / 7) + + 1 + (z - 1) / 8; + this.$setStyleClass(cells[j], "weeknumber"); + } + else { + y++; + + var dayNrWeek = new Date(year, month, + y - dayInTheWeek).getDay(); + + if (dayNrWeek == 0 || dayNrWeek == 6) + this.$setStyleClass(cells[j], "weekend"); + + if (y <= dayInTheWeek) { + this.$getLayoutNode("cell", "container", cells[j]).innerHTML = prevMonthDays++; + this.$setStyleClass(cells[j], "disabled prev"); + } + else if (y > dayInTheWeek + && y <= c.numberOfDays + dayInTheWeek) { + this.$getLayoutNode("cell", "container", cells[j]).innerHTML = y - dayInTheWeek; + + if (month == c.month && year == c.year + && y - dayInTheWeek == c.day) + this.$setStyleClass(cells[j], "active"); + } + else if (y > c.numberOfDays + dayInTheWeek) { + this.$getLayoutNode("cell", "container", cells[j]).innerHTML = nextMonthDays++; + this.$setStyleClass(cells[j], "disabled next"); + + disabledRow++; + } + } + } + } + + pl = Math.floor((rWidth - rDiff[0] - rDiff2[0] + - (cWidthf + cDiff[0] + cDiff2[0])*8)/2); + rows[i].style.paddingLeft = pl + "px"; + + var eDiff = !c.inited ? this.$getPadding(this.$ext) : c.startDiffs[4]; + + if (!c.inited) + c.startDiffs[4] = eDiff + + this.$ext.style.paddingBottom = + (Math.floor(eDiff[1]/2) + pl) + "px"; + + + if (!this.height) { + rows[i].style.display = disabledRow == 7 + ? "none" + : "block"; + } + else { + rows[i].style.visibility = disabledRow == 7 + ? "hidden" + : "visible"; + } + } + } + + /* Days of the week */ + var daysofweek = this.oDow.childNodes, + startDay = this.$calVars.startDay, + dayIndex; + this.oDow.style.paddingLeft = pl + "px"; + + for (var z = 0, i = 0; i < daysofweek.length; i++) { + if ((daysofweek[i].className || "").indexOf("dayofweek") > -1) { + daysofweek[i].style.width = (cWidthf > 0 ? cWidthf : 0) + "px"; + + if (cWidthf < 16) { + daysofweek[i].style.fontSize = "9px"; + } + + if (z > 0) { + dayIndex = (z - 1 + startDay) % 7; + daysofweek[i].innerHTML = + c.days[dayIndex].substr(0, cWidthf < 12 + ? 1 : (cWidthf < 30 ? 2 + : 3)); + apf.setStyleClass(daysofweek[i], c.days[dayIndex], c.days, true); + } + else { + daysofweek[i].innerHTML = "W"; + apf.setStyleClass(daysofweek[i], "weeknumber_column", null, true); + } + z++; + } + } + + + c.inited = true; + }; + + this.getYear = function() { + return this.$calVars.currentYear; + }; + + this.getMonth = function() { + return this.$calVars.currentMonth + 1; + }; + + this.getDay = function() { + return this.$calVars.day; + }; + + this.setYear = function(value) { + this.redraw(this.$calVars.currentMonth, value); + }; + + this.setMonth = function(value) { + this.redraw(parseInt(value) - 1, this.$calVars.currentYear); + }; + + /** + * Selects date and highlights its cell in calendar component + * + * @param {Number} nr day number + * @param {String} type class name of html representation of selected cell + */ + this.selectDay = function(cellNode, type, userAction) { + if (userAction && this.disabled) + return; + + var nr = this.$getLayoutNode("cell", "container", cellNode).innerHTML; + + var c = this.$calVars, + newMonth = type == "prev" + ? c.currentMonth + : (type == "next" + ? c.currentMonth + 2 + : c.currentMonth + 1), + + newYear = c.currentYear; + + if (newMonth < 1) { + newMonth = 12; + newYear--; + } + else if (newMonth > 12) { + newMonth = 1; + newYear++; + } + + this.change(new Date(newYear, (newMonth - 1), nr, c.hours, c.minutes, + c.seconds).format(this.outputFormat)); + }; + + /** + * Change displayed year to next one + */ + this.nextYear = function() { + this.redraw(this.$calVars.currentMonth, this.$calVars.currentYear + 1); + }; + + /** + * Change displayed year to previous one + */ + this.prevYear = function() { + this.redraw(this.$calVars.currentMonth, this.$calVars.currentYear - 1); + }; + + /** + * Change displayed month to next one. If actual month is December, function + * change current displayed year to next one + */ + this.nextMonth = function() { + var c = this.$calVars; + this.redraw( + c.currentMonth > 10 ? 0 : c.currentMonth + 1, + c.currentMonth > 10 ? c.currentYear + 1 : c.currentYear + ); + }; + + /** + * Change displayed month to previous one. If actual month is January, function + * change current displayed year to previous one + */ + this.prevMonth = function() { + var c = this.$calVars; + this.redraw( + c.currentMonth < 1 ? 11 : c.currentMonth - 1, + c.currentMonth < 1 ? c.currentYear - 1 : c.currentYear + ); + }; + + /** + * Select today's date in calendar component + */ + this.today = function() { + this.setProperty("value", new Date().format(this.outputFormat)); + }; + + /**** Init ****/ + + this.$draw = function() { + //Build Main Skin + this.$ext = this.$getExternal("main", null, function(oExt) { + var oContent = this.$getLayoutNode("main", "content", oExt); + + for (var i = 0; i < 6; i++) { + this.$getNewContext("row"); + var oRow = oContent.appendChild(this.$getLayoutNode("row")); + + for (var j = 0; j < 8; j++) { + this.$getNewContext("cell"); + var oCell = this.$getLayoutNode("cell"); + if (j > 0) { + oCell.setAttribute("onmouseover", + "if (this.className.indexOf('disabled') > -1 " + + "|| this.className.indexOf('active') > -1) " + + "return; apf.lookup(" + this.$uniqueId + + ").$setStyleClass(this, 'hover', null, true);"); + oCell.setAttribute("onmouseout", + "var o = apf.lookup(" + this.$uniqueId + + ").$setStyleClass(this, '', ['hover'], true);"); + oCell.setAttribute("onmousedown", + "var o = apf.lookup(" + this.$uniqueId + ");" + + " if (this.className.indexOf('prev') > -1) { " + + "o.selectDay(this, 'prev', true);}" + + " else if (this.className.indexOf('next') > -1) {" + + "o.selectDay(this, 'next', true);}" + + " else {o.selectDay(this, null, true);}"); + } + oRow.appendChild(oCell); + } + } + + var oNavigation = this.$getLayoutNode("main", "navigation", oExt); + + if (oNavigation) { + var buttons = ["prevYear", "prevMonth", "nextYear", "nextMonth", + "today", "status"]; + for (var i = 0; i < buttons.length; i++) { + this.$getNewContext("button"); + var btn = oNavigation.appendChild(this.$getLayoutNode("button")); + this.$setStyleClass(btn, buttons[i]); + if (buttons[i] !== "status") + btn.setAttribute("onmousedown", 'var o = apf.lookup(' + + this.$uniqueId + '); \ + if (!o.disabled) \ + o.' + buttons[i] + '();\ + apf.setStyleClass(this, "down");'); + btn.setAttribute("onmouseup", 'apf.setStyleClass(this, "", ["down"]);'); + } + } + + var oDaysOfWeek = this.$getLayoutNode("main", "daysofweek", oExt); + + for (var i = 0; i < this.$calVars.days.length + 1; i++) { + this.$getNewContext("day"); + oDaysOfWeek.appendChild(this.$getLayoutNode("day")); + } + }); + + this.oNavigation = this.$getLayoutNode("main", "navigation", this.$ext); + this.oDow = this.$getLayoutNode("main", "daysofweek", this.$ext); + this.oContent = this.$getLayoutNode("main", "content", this.$ext); + + + + apf.layout.setRules(this.$ext, "resize", "var o = apf.all[" + this.$uniqueId + "];\ + if (o) o.$resize()", true); + apf.layout.queue(this.$ext); + + }; + + this.$loadAml = function(x) { + if (typeof this.value == "undefined") { + switch(this["default"]) { + case "today": + this.setProperty("value", new Date().format(this.outputFormat)); + break; + default : + this.setProperty("value", new Date().format(this.outputFormat)); + break; + } + } + else { + var c = this.$calVars, + date = apf.date.getDateTime(c.temp || this.value, this.outputFormat); + c.day = date.getDate(); + c.month = date.getMonth(); + c.year = date.getFullYear(); + + if (c.day && c.month && c.year) { + this.setProperty("value", new Date(c.year, c.month, c.day, c.hours, + c.minutes, c.seconds).format(this.outputFormat)); + } + + } + }; + + this.$destroy = function() { + apf.popup.removeContent(this.$uniqueId); + apf.destroyHtmlNode(this.$ext); + this.oCalendar = null; + }; + + +}).call(apf.calendar.prototype = new apf.StandardBinding()); + +apf.aml.setElement("calendar", apf.calendar); + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/elements/calendarlist.js)SIZE(15123)TIME(Mon, 10 Jan 2011 10:53:12 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + +/** + * + * @inherits apf.DataAction + * + * @author Lukasz Lipinski + * @version %I%, %G% + * @since 1.0 + * + */ + +apf.calendarlist = function(struct, tagName){ + this.$init(tagName || "calendarlist", apf.NODE_VISIBLE, struct); + + this.date = new Date(); + this.strDate = ""; + this.days = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"]; + this.months = [{name : "January", number : 31}, + {name : "February", number : 28}, + {name : "March", number : 31}, + {name : "April", number : 30}, + {name : "May", number : 31}, + {name : "June", number : 30}, + {name : "July", number : 31}, + {name : "August", number : 31}, + {name : "September", number : 30}, + {name : "October", number : 31}, + {name : "November", number : 30}, + {name : "December", number : 31}]; + this.range = "day"; + this.mode = "normal"; + this.dateFormat = "mm-dd-yyyy"; + this.interval = null; + this.intervals = { + "day" : 15, //half an hour + "week" : 60, //day + "month" : 60 //week + }; + + this.ranges = { + "day" : 1440, + "week" : 10080/*, + "month" : this.months[this.date.getMonth()].number * 1440*/ + }; + + this.calendarEvents = []; +}; + +(function() { + + if (!apf.isIphone) + this.implement(apf.Rename); + + + + this.$getCaptionElement = function() { + if (!(this.$caret || this.$selected)) + return; + + var x = this.$getLayoutNode("item", "caption", this.$caret || this.$selected); + if (!x) + return; + return x.nodeType == 1 ? x : x.parentNode; + }; + + + + this.addEventListener("afterselect", function(e) { + if (this.hasFeature(apf.__VALIDATION__)) + this.validate(true); + }); + + + /**** Properties and Attributes ****/ + + this.$supportedProperties.push("appearance", "mode", "range", "date", "date-format", "interval"); + + this.$propHandlers["range"] = function(value) { + this.date = this.getStartDate(this.date, this.range = value); + this.$updateHours(); + }; + + this.$propHandlers["day"] = function(value) { + this.strDate = value; + this.date = this.getStartDate(apf.date.getDateTime(value, this.dateFormat), this.range); + this.$updateHours(); + }; + + this.$propHandlers["date-format"] = function(value) { + this.dateFormat = value; + }; + + this.$propHandlers["interval"] = function(value) { + value = parseInt(value); + this.interval = value > 5 ? value : 5; + this.$updateHours(); + }; + /** + * Possible values + * normal (default) - adding/editing notes are NOT possible + * add - selecting time range for note + * edit - editing note + * @param {Object} value + */ + this.$propHandlers["mode"] = function(value) { + this.mode = value; + }; + + this.getStartDate = function(objDate, range) { + switch(range) { + case "day": + return apf.date.getDateTime(this.strDate, this.dateFormat); + case "week": + return new Date(objDate.getFullYear(), objDate.getMonth(), objDate.getDate() - objDate.getDay(), 0, 0, 0); + case "month": + return new Date(objDate.getFullYear(), objDate.getMonth(), 1, 0, 0, 0); + } + }; + + function $xmlUpdate(e) { + + } + + /**** Keyboard support ****/ + + + this.addEventListener("keydown", this.$keyHandler, true); + + + /**** Init ****/ + + this.$draw = function() { + //Build Main Skin + this.$ext = this.$getExternal(); + this.$container = this.$getLayoutNode("main", "container", this.$ext); + this.$oHours = this.$getLayoutNode("main", "hours", this.$ext); + this.$oNoteField = this.$getLayoutNode("main", "note_field", this.$ext); + this.$oNoteFieldCon = this.$getLayoutNode("main", "note_field_con", this.$ext); + + + this.$container.setAttribute("onmousemove", + "var o = apf.lookup(" + this.$uniqueId + + "); if (o.mode == 'add')o.$showNoteField(event);"); + + this.$oNoteField.setAttribute("onmousemove", + "var o = apf.lookup(" + this.$uniqueId + + "); if (o.mode == 'add')o.$showNoteField(event);"); + + this.$oNoteField.setAttribute("onmouseout", + "var o = apf.lookup(" + this.$uniqueId + + "); o.$hideNoteField(event);"); + + this.$oNoteField.setAttribute("onclick", + "var o = apf.lookup(" + this.$uniqueId + + "); if (o.mode == 'add')o.$editNoteField();"); + + if (apf.hasCssUpdateScrollbarBug && !this.mode) + this.$fixScrollBug(); + + var _self = this; + this.$ext.onclick = function(e) { + _self.dispatchEvent("click", { + htmlEvent : e || event + }); + } + }; + + function getScrollPage() { + return [ + document.documentElement.scrollLeft || document.body.scrollLeft, + document.documentElement.scrollTop || document.body.scrollTop + ]; + } + + this.$showNoteField = function(e) { + e = e || event; + + var cy = e.clientY; + var intervalHeight = this.getInterval("pixels"); + var interval = this.getInterval("minutes"); + + this.$oNoteField.style.display = "block"; + this.$oNoteField.style.height = (intervalHeight * 2 - apf.getDiff(this.$oNoteField)[1]) + "px"; + this.$oNoteField.style.marginTop = -1 * intervalHeight + "px"; + + var scrollPage = getScrollPage()[1];//333 px + var absPosE = apf.getAbsolutePosition(this.$ext)[1];//822 + var marginTop = parseInt(this.$oNoteField.style.marginTop); + + this.$oNoteField.style.top = (cy + scrollPage - absPosE + this.$ext.scrollTop + intervalHeight / 2) + "px"; + + //Unit relative to interval and intervalHeight + + //var unit = parseInt((cy + this.$ext.scrollTop + parseInt(this.$oNoteField.style.marginTop)) / intervalHeight); + //unit *= interval; + + //Constant interval = 5 min; + intervalHeight = 5 * intervalHeight / interval; + var unit = parseInt((cy + scrollPage - absPosE + this.$ext.scrollTop + marginTop + intervalHeight / 2) / intervalHeight); + unit *= 5; + + var hours = parseInt(unit / 60); + var minutes = unit % 60; + + if (minutes < 0) + minutes = 0; + + minutes = minutes < 10 ? "0" + minutes : minutes; + + if (hours >= 24) + hours %= 24; + + this.$oNoteFieldCon.innerHTML = hours+":"+minutes; + }; + + this.$hideNoteField = function(e) { + e = e || event; + if (this.mode !== "edit") + this.$oNoteField.style.display = "none"; + }; + + this.$editNoteField = function() { + this.mode = "edit"; + }; + + this.getInterval = function(unit) { + if (unit == "minutes") + return this.interval || this.intervals[this.range]; + else if (unit == "pixels") { + var nodes = this.$oHours.childNodes, + l = nodes.length + + for (var i = 0; i < l; i++) { + if ((nodes[i].className || "").indexOf("hour") > -1) { + return nodes[i].offsetHeight; + } + } + } + }; + + this.$isLeapYear = function(year) { + return ((year % 4 == 0) && (year % 100 !== 0)) || (year % 400 == 0) + ? true + : false; + }; + + this.getTimeRange = function(rangeType) { + switch (rangeType) { + case "day": + case "week": + return this.ranges[rangeType]; + case "month": + return this.months[this.date.getMonth()].number * 1440 + + this.$isLeapYear(this.date.getFullYear()) ? 1 : 0; + } + }; + + this.$addModifier = function(xmlNode, oItem, htmlParentNode, beforeNode) { + apf.insertHtmlNodes([oItem], this.$container); + var htmlNode = apf.xmldb.findHtmlNode(xmlNode, this); + + this.calendarEvents.push({ + xmlNode : xmlNode, + htmlNode : htmlNode + }); + + this.updateCalendarEvent(xmlNode, htmlNode); + + return false; + }; + + this.updateCalendarEvent = function(xmlNode, htmlNode) { + var date1 = apf.date.getDateTime(this.$applyBindRule("date", xmlNode), this.dateFormat), + date2 = new Date(this.date.getFullYear(), this.date.getMonth(), this.date.getDate(), 0, 0, 0), + duration = parseInt(this.$applyBindRule("duration", xmlNode)), + interval = this.getInterval("minutes"), + intervalHeight = this.getInterval("pixels"), + max = parseInt(this.getTimeRange(this.range) / interval) * intervalHeight, + top = (((date1.getTime() - date2.getTime()) / 60000) / interval) * intervalHeight; + + if (top >= 0 && top < max) { + htmlNode.style.display = "block"; + htmlNode.style.top = top + "px"; + htmlNode.style.height = ((duration / interval) * intervalHeight) - apf.getDiff(htmlNode)[1] - 1 + "px"; + } + else { + htmlNode.style.display = "none"; + } + }; + + this.updateCalendarEvents = function() { + var calEvents = this.calendarEvents, + calEvents_len = calEvents.length; + + for (var i = 0; i < calEvents_len; i++) { + this.updateCalendarEvent(calEvents[i].xmlNode, calEvents[i].htmlNode); + } + }; + + this.$loadAml = function(x) { + this.$updateHours(); + }; + + this.$getHours = function() { + var nodes = this.$oHours.childNodes, + l = nodes.length, + hours = []; + + for (var i = 0; i < l; i++) { + if ((nodes[i].className || "").indexOf("hour") > -1) { + hours.push(nodes[i]); + } + } + + return hours; + } + + this.$updateHours = function() { + //Calculating number of labels + var range = this.getTimeRange(this.range), + interval = this.getInterval("minutes"), + start = 0, hours = 0, minutes = 0, + day = this.date.getDate(), + caption, cssClass, isMidnight, oHour, oCaption; + + var nodes = [], + existingNodes = this.$getHours(); + + for (var i = 0, l = parseInt(range / interval); i < l; i++) { + hours = parseInt(start / 60); + minutes = start % 60; + minutes = minutes < 10 ? "0" + minutes : minutes; + cssClass = null; + isMidnight = parseInt(hours) == 24 && parseInt(minutes) == 0; + + if (isMidnight) { + hours = 0; minutes = "00"; start = 0; + cssClass = "midnight"; + } + + caption = (i%2 == 0 + ? hours + ":" + minutes + : "") + (isMidnight + ? " (" + new Date(this.date.getFullYear(), this.date.getMonth(), ++day, 0, 0, 0).format(this.dateFormat) + ")" + : ""); + if (i%2 == 0) + cssClass = cssClass ? cssClass + " odd" : "odd"; + + if (existingNodes.length) { + oHour = existingNodes.shift(); + oHour.style.display = "block"; + + if (cssClass) + this.$setStyleClass(oHour, cssClass, []); + + oCaption = this.$getLayoutNode("hour", "caption", oHour); + + apf.setNodeValue(oCaption, caption); + } + else { + this.$getNewContext("hour"); + oHour = this.$getLayoutNode("hour"); + + if (cssClass) + this.$setStyleClass(oHour, cssClass, []); + + apf.setNodeValue(this.$getLayoutNode("hour", "caption"), caption); + nodes.push(oHour); + } + + start += interval; + } + + if (nodes.length) + apf.insertHtmlNodes(nodes, this.$oHours); + + //Hide unused nodes + var existingNodes_len = existingNodes.length + if (existingNodes_len) { + for (var i = 0; i < existingNodes_len; i++) { + existingNodes[i].style.display = "none"; + } + } + + this.$container.style.height = this.$oHours.offsetHeight + "px"; + this.updateCalendarEvents(); + }; + + this.$destroy = function() { + if (this.$ext) + this.$ext.onclick = null; + apf.destroyHtmlNode(this.oDrag); + this.oDrag = null; + }; +}).call(apf.calendarlist.prototype = new apf.BaseList()); +apf.aml.setElement("calendarlist", apf.calendarlist); +apf.aml.setElement("date", apf.BindingRule); +apf.aml.setElement("duration", apf.BindingRule); + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/elements/chart.js)SIZE(9687)TIME(Mon, 10 Jan 2011 10:53:12 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + + +/** + * Element displays a chart. + * + * @classDescription This class creates a new chart + * @return {Chart} Returns a new chart + * @type {Chart} + * @constructor + * @allowchild {elements}, {anyaml} + * @addnode elements:chart + * + * @author Rik Arends + * @version %I%, %G% + * @since 0.4 + */ + +apf.Chart = function(struct, tagName){ + this.$init(tagName || "chart", apf.NODE_VISIBLE, struct); +}; + +apf.aml.setElement("chart", apf.Chart); + +(function(){ + //var space = { x:1000000, w:-2000000, y:1000000, h:-2000000}; + this.$timer = null; + this.$animTimer = null; + this.$doinit = true; + this.$doresize = false; + this.drawtime = 10; + this.anim = 0; + this.a = 0; + this.b = 0; + this.c = 0; + this.d = 0; + this.$supportedProperties = ["anim","a","b","c","d"]; + + this.$redraw = function(now,resize){ + if(resize)this.$doresize = true; + if(now){ + if(this.$timer)window.clearTimeout(this.$timer); + this.$timer = null + this.$drawChart(); + }else{ + var _self = this; + if(!this.$timer) this.$timer = window.setTimeout(function(){ + _self.$timer = null; + _self.$drawChart(); + },this.drawtime); + } + } + + this.$drawChart = function(){ + if (!this.childNodes) //We're being destroyed + return; + + if (!this.$ext.offsetHeight && (apf.isIE || !this.$ext.offsetWidth)) //We're not visible, so let's not bother + return; + + // check if we need to initialize or resize + if(this.$doinit){ + this.$doinit = false; + this.$doresize = false; + this.$copyPos(); + apf.draw.initRoot(this); + }else if (this.$doresize){ + this.$copyPos(); + apf.draw.resizeRoot(this); + } + for(var n, i = 0;i4 || e.button<0)return; + bt = (_self.canvas)?ffbt[e.button]:iebt[e.button]; + if(!bt)return; + var keys = e.shiftKey?1:0 + e.ctrlKey?2:0 + e.altKey?4:0; + //interact = true; + var pos = apf.getAbsolutePosition(_self.$ext,document.documentElement); + lx = e.clientX-pos[0] + document.documentElement.scrollLeft, + ly = e.clientY-pos[1] + document.documentElement.scrollTop; + ox = lx , oy = ly; + // we need to check if our mousedown was in the axis, ifso send it a mousedown and keep it on our eventstack + for(var t, i = _self.childNodes.length-1;i>=0;i--){ + t = _self.childNodes[i]; + // check child name before pushing up + if( ox >= t.left && ox <= t.left+t.width && + oy >= t.top && oy <= t.top+t.height ) + if(t.$mouseDown){ + t.$mouseDown(ox - t.left,oy - t.top, bt, keys); + stack.push( t ); + } + //logw("init "+ox+" "+oy+" "+t.left+" "+t.width+" "+t.top+" "+t.height); + } + hasMoved = false; + } + + this.$ext.oncontextmenu = function(){ + return false; + } + + this.$ext.onselectstart = function(){ + return false; + } + + var hasMoved; + this.addEventListener("contextmenu", function(e){ + if (hasMoved) + e.cancelBubble = true; + }); + + this.$ext.onmouseup = function(e){ + if(this.$doinit)return; + if (!e) e = event; + bt = 0; + var pos = apf.getAbsolutePosition(_self.$ext,document.documentElement); + var x = e.clientX - pos[0] + document.documentElement.scrollLeft, + y = e.clientY - pos[1] + document.documentElement.scrollTop; + for(var t, i = stack.length-1;i>=0;i--) + (t=stack[i]).$mouseUp(x - t.left, y - t.top); + stack.length = 0; + } + + this.$ext.onmousemove = function(e){ + if(this.$doinit)return; + //if (!interact) return; + if (!e) e = event; + var pos = apf.getAbsolutePosition(_self.$ext,document.documentElement); + var dx = (-lx + (lx=e.clientX-pos[0] + document.documentElement.scrollLeft)), + dy = (-ly + (ly=e.clientY-pos[1] + document.documentElement.scrollTop)); + var keys = e.shiftKey?1:0 + e.ctrlKey?2:0 + e.altKey?4:0; + + if (bt) { + if (bt == 2) + hasMoved = true; + + for(var t, i = stack.length-1;i>=0;i--) + (t = stack[i]).$mouseMove(dx,dy,bt,ox-t.left,oy-t.top,lx-t.left,ly-t.top,keys); + } + else { + for(var t, i = _self.childNodes.length-1;i>=0;i--) + if((t = _self.childNodes[i]).$mouseMove) + t.$mouseMove(dx,dy,bt,ox-t.left, + oy-t.top,lx-t.left,ly-t.top,keys); + } + } + + //@todo should use apf's abstraction for scrollwheel + var wheelEvent = function(e) { + if(this.$doinit)return; + if(!e) e = window.event; + + var d = e.wheelDelta? + (window.opera ?-1:1) * e.wheelDelta / 120 : + (e.detail ? -e.detail / 3 : 0); + var keys = e.shiftKey?1:0 + e.ctrlKey?2:0 + e.altKey?4:0; + if(d){ + var pos = apf.getAbsolutePosition(_self.$ext,document.documentElement); + // lets find if we are over a graph + var x = e.clientX - pos[0] + document.documentElement.scrollLeft, + y = e.clientY - pos[1] + document.documentElement.scrollTop; + for(var t, i = 0;i<_self.childNodes.length;i++){ + t = _self.childNodes[i]; + if( x >= t.left && x <= t.left+t.width && + y >= t.top && y <= t.top+t.height ){ + t.$mouseWheel(x - t.left,y - t.top,d,keys); + } + } + } + if(e.preventDefault) e.preventDefault(); + e.returnValue = false; + } + if (!apf.supportVML && this.$ext.addEventListener){ + this.$ext.addEventListener('DOMMouseScroll', wheelEvent, false); + } + this.$ext.onmousewheel = wheelEvent; + + + apf.layout.setRules(this.$ext, "resize", "var o = apf.all[" + this.$uniqueId + "];\ + if (o) o.$resize()", true); + apf.layout.queue(this.$ext); + + } + + this.$loadAml = function(x){ + apf.draw.initDriver(); + + if (this.anim > 0){ + var _self = this; + this.$animTimer = window.setInterval(function(){ + _self.$redraw(); + }, this.anim); + } + } + + this.$destroy = function() { + + apf.layout.removeRule(this.$ext, "resize"); + + + this.$ext.onmousedown = + this.$ext.oncontextmenu = + this.$ext.onselectstart = + this.$ext.onmouseup = + this.$ext.onmousemove = + this.$ext.onmousewheel = null; + + window.clearTimeout(this.$timer); + window.clearInterval(this.$animTimer); + }; +}).call(apf.Chart.prototype = new apf.Presentation()); + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/elements/checkbox.js)SIZE(7729)TIME(Mon, 10 Jan 2011 10:53:12 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + + +/** + * Element displaying a clickable rectangle having two states which + * can be toggled by user interaction. + * Example: + * + * the glass is full + * + * + * @constructor + * + * @define checkbox + * @addnode elements + * + * @author Ruben Daniels (ruben AT ajax DOT org) + * @version %I%, %G% + * @since 0.4 + * + * @inherits apf.BaseButton + * @inherits apf.XForms + * + * @binding value Determines the way the value for the element is retrieved + * from the bound data. + * Example: + * Sets the value of the checkbox based on data loaded into this component. + * + * + * + * + * Caption + * + * Example: + * A shorter way to write this is: + * + * + * + * + * Caption + * + */ +apf.checkbox = function(struct, tagName){ + this.$init(tagName || "checkbox", apf.NODE_VISIBLE, struct); +}; + +(function() { + this.implement( + + + apf.DataAction + + ); + + //Options + this.$focussable = apf.KEYBOARD; // This object can get the focus + this.checked = false; + this.$values = [1, 0]; + + /**** Properties and Attributes ****/ + + this.$booleanProperties["checked"] = true; + this.$supportedProperties.push("value", "checked", "label", "values"); + + /** + * @attribute {String} value the value of this element. + */ + this.$propHandlers["value"] = function(value){ + value = (typeof value == "string" ? value.trim() : value); + + this.checked = (typeof value != "undefined" && value !== null + && value.toString() == this.$values[0].toString()); + + if (this.checked) + apf.setStyleClass(this.$ext, this.$baseCSSname + "Checked"); + else + apf.setStyleClass(this.$ext, "", [this.$baseCSSname + "Checked"]); + }; + + /** + * @attribute {Boolean} checked whether the element is in the checked state. + */ + this.$propHandlers["checked"] = function(value) { + if (!this.$values) { + if (this.getAttribute("values")) + this.$propHandler["values"].call(this, this.getAttribute("values")); + else + this.$values = [true, false]; + } + this.setProperty("value", this.$values[value ? 0 : 1]); + }; + + /** + * @attribute {String} label the caption of the label explaining what + * the meaning of the checked state of this element is. + */ + this.$propHandlers["label"] = function(value){ + if (!this.$ext) + return; + + var lbl = this.$getLayoutNode("main", "label", this.$ext); + if (!lbl) + return; + + if (lbl.nodeType == 1) + lbl.innerHTML = value; + else + lbl.nodeValue = value; + }; + + /** + * @attribute {String} values a pipe seperated list of two values which + * correspond to the two states of the checkbox. The first for the checked + * state, the second for the unchecked state. Defaults to "true|false". + */ + this.$propHandlers["values"] = function(value){ + this.$values = typeof value == "string" + ? value.split("\|") + : (value || [1, 0]); + + this.$propHandlers["value"].call(this, this.value); + }; + + /**** Public Methods ****/ + + + + /** + * Sets the value of this element. This should be one of the values + * specified in the values attribute. + * @param {String} value the new value of this element + */ + this.setValue = function(value){ + if (!this.$values) return; + this.setProperty("value", value, false, true); + }; + + /** + * Returns the current value + */ + this.getValue = function(){ + return this.xmlRoot ? this.$values[this.checked ? 0 : 1] : this.value; + }; + + /** + * Sets the checked state and related value + */ + this.check = function(){ + this.setProperty("value", this.$values[0], false, true); + }; + + /** + * Sets the unchecked state and related value + */ + this.uncheck = function(){ + this.setProperty("value", this.$values[1], false, true); + }; + + + + /**** Private state handling methods ****/ + + this.addEventListener("$clear", function(){ + this.setProperty("value", this.$values[1]); + }); + + this.$enable = function(){ + if (this.$input) this.$input.disabled = false; + this.$doBgSwitch(1); + }; + + this.$disable = function(){ + if (this.$input) this.$input.disabled = true; + this.$doBgSwitch(4); + }; + + this.$setState = function(state, e, strEvent){ + //if (this.disabled) return; + + this.$doBgSwitch(this.states[state]); + this.$setStyleClass(this.$ext, (state != "Out" ? this.$baseCSSname + state : ""), + [this.$baseCSSname + "Down", this.$baseCSSname + "Over"]); + this.state = state; // Store the current state so we can check on it coming here again. + + if (strEvent) + this.dispatchEvent(strEvent, {htmlEvent: e}); + + /*if (state == "Down") + apf.cancelBubble(e, this); + else + e.cancelBubble = true;*/ + }; + + this.$clickHandler = function(){ + //this.checked = !this.checked; + this.change(this.$values[(!this.checked) ? 0 : 1]); + + + if (this.validate) //@todo rewrite button + this.validate(true); + + + return true; + }; + + /**** Init ****/ + + this.$draw = function(){ + //Build Main Skin + this.$ext = this.$getExternal(); + this.$input = this.$getLayoutNode("main", "input", this.$ext); + this.$notfromext = this.$input && this.$input != this.$ext; + + this.$setupEvents(); + }; + + this.$childProperty = "label"; + + + this.addEventListener("$skinchange", function(){ + if (this.label) + this.$propHandlers["label"].call(this, this.label); + }) + + + +}).call(apf.checkbox.prototype = new apf.BaseButton()); + +apf.aml.setElement("checkbox", apf.checkbox); + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/elements/codeeditor.js)SIZE(18550)TIME(Tue, 15 Feb 2011 08:59:13 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + + +/** + * Element allowing the user to type code. + * + * @constructor + * @define codeeditor + * @addnode elements + * + * @inherits apf.StandardBinding + * + * @author Ruben Daniels (ruben AT ajax DOT org) + * @author Fabian Jakobs (fabian AT ajax DOT org) + * @version %I%, %G% + * @since 0.1 + */ + +if (!apf.hasRequireJS) + apf.aml.setElement("codeeditor", apf.textbox); +else + define(function(require, exports, module) { + +require("pilot/fixoldbrowsers"); +var Editor = require("ace/editor").Editor; +var EditSession = require("ace/edit_session").EditSession; +var VirtualRenderer = require("ace/virtual_renderer").VirtualRenderer; +var UndoManager = require("ace/undomanager").UndoManager; +var Range = require("ace/range").Range; + +apf.codeeditor = function(struct, tagName) { + this.$init(tagName || "codeeditor", apf.NODE_VISIBLE, struct); + + this.documents = []; + this.$cache = {}; + + //this.setProperty("overwrite", false); + this.setProperty("line", 1); + this.setProperty("col", 1) +}; + +(function() { + this.implement( + + apf.DataAction + + ); + + this.$focussable = true; // This object can get the focus + this.$childProperty = "value"; + this.$isTextInput = true; + + this.value = ""; + this.multiline = true; + this.caching = true; + + this.$booleanProperties["activeline"] = true; + this.$booleanProperties["caching"] = true; + this.$booleanProperties["readonly"] = true; + this.$booleanProperties["activeline"] = true; + this.$booleanProperties["showinvisibles"] = true; + this.$booleanProperties["showprintmargin"] = true; + this.$booleanProperties["overwrite"] = true; + this.$booleanProperties["softtabs"] = true; + this.$booleanProperties["gutter"] = true; + + this.$supportedProperties.push("value", "syntax", "activeline", "selectstyle", + "caching", "readonly", "showinvisibles", "showprintmargin", "printmargincolumn", + "overwrite", "tabsize", "softtabs", "debugger", "model-breakpoints", "scrollspeed", + "theme", "gutter"); + + var cacheId = 0; + this.$getCacheKey = function(value) { + if (typeof value == "string") { + var key = this.xmlRoot + ? this.xmlRoot.getAttribute(apf.xmldb.xmlIdTag) + : value; + } + else if (value.nodeType) { + key = value.getAttribute(apf.xmldb.xmlIdTag); + } + + return key; + } + + this.clearCacheItem = function(xmlNode) { + if (!this.caching) + return; + + var key = this.$getCacheKey(xmlNode); + if (key) + delete this.$cache[key]; + } + + this.addEventListener("unloadmodel", function(e) { + this.syncValue(); + }); + + /** + * @attribute {String} value the text of this element + * @todo apf3.0 check use of this.$propHandlers["value"].call + */ + this.$propHandlers["value"] = function(value){ //@todo apf3.0 add support for the range object as a value + var doc, key, + _self = this; + + if (this.caching) + key = this.$getCacheKey(value); + + //Assuming document + if (value instanceof EditSession) + doc = value; + + if (!doc && key) + doc = this.$cache[key]; + + if (!doc) { + if (value.nodeType) { + apf.xmldb.addNodeListener(value.nodeType == 1 + ? value : value.parentNode, this); + } + + doc = new EditSession(typeof value == "string" + ? value + : (value.nodeType > 1 && value.nodeType < 5 //@todo replace this by a proper function + ? value.nodeValue + : value.firstChild && value.firstChild.nodeValue || "")); + + doc.cacheId = key; + doc.setUndoManager(new UndoManager()); + + if (key) + this.$cache[key] = doc; + } + //@todo value can also be an xml node and should be updated in a similar fashion as above + else if (typeof value == "string" && !doc.hasValue) { + //@todo big hack! + doc.setValue(value); + doc.hasValue = true; + } + + //apf.queue.add("ce" + _self.$uniqueId, function() { + _self.$getMode(_self.syntax, function(mode) { + doc.setMode(mode); + }); + doc.setTabSize(parseInt(_self.tabsize)); + doc.setUseSoftTabs(_self.softtabs); + + _self.$removeDocListeners && _self.$removeDocListeners(); + _self.$removeDocListeners = _self.$addDocListeners(doc); + + _self.$editor.setSession(doc); + + _self.$updateMarker(); + _self.$updateBreakpoints(doc); + //}) + }; + + this.$addDocListeners = function(doc) { + var _self = this; + var onCursorChange = function() { + var cursor = doc.getSelection().getCursor(); + _self.setProperty("line", cursor.row+1); + _self.setProperty("col", cursor.column+1); + }; + + doc.getSelection().addEventListener("changeCursor", onCursorChange); + + onCursorChange(); + + return function() { + doc.getSelection().removeEventListener("changeCursor", onCursorChange); + }; + }; + + //@todo fix that this is not called three times + this.$updateMarker = function(removeOnly) { + if (this.$marker) { + this.$editor.renderer.removeGutterDecoration(this.$lastRow[0], this.$lastRow[1]); + this.$editor.getSession().removeMarker(this.$marker); + this.$marker = null; + + if (removeOnly) + return; + } + + if (!this.$debugger) + return; + + var frame = this.$debugger.activeframe; + if (!frame) + return; + + var script = this.xmlRoot; + if (script.getAttribute("scriptid") !== frame.getAttribute("scriptid")) + return; + + var head = this.$debugger.$mdlStack.queryNode("frame[1]"); + var isTop = frame == head; + + var lineOffset = parseInt(script.getAttribute("lineoffset") || "0"); + var row = parseInt(frame.getAttribute("line")) - lineOffset; + var range = new Range(row, 0, row+1, 0); + + this.$marker = this.$editor.getSession().addMarker(range, isTop ? "ace_step" : "ace_stack", "line"); + var type = isTop ? "arrow" : "stack"; + this.$lastRow = [row, type]; + this.$editor.renderer.addGutterDecoration(row, type); + + this.$editor.gotoLine(row + 1, parseInt(frame.getAttribute("column"))); + //this.$editor.moveCursorTo(row, parseInt(frame.getAttribute("column"))); + }; + + this.$updateBreakpoints = function(doc) { + doc = doc || this.$editor.getSession(); + + doc.setBreakpoints([]); + if (!this.$breakpoints) + return; + + if (this.xmlRoot) { + var scriptName = this.xmlRoot.getAttribute("scriptname"); + if (!scriptName) + return; + + var breakpoints = this.$breakpoints.queryNodes("//breakpoint[@script='" + scriptName + "']"); + + var rows = []; + for (var i=0; i + * + * + * + * + * + * Example: + * A shorter way to write this is: + * + * + * + * + * + * + */ +apf.colorpicker = function(struct, tagName){ + this.$init(tagName || "colorpicker", apf.NODE_VISIBLE, struct); +}; + +(function(){ + this.implement( + + apf.StandardBinding + + + ,apf.DataAction + + + ); + //Options + this.$focussable = true; // This object can get the focus + + // PUBLIC METHODS + this.setValue = function(value, type){ + //this.value = value; + if (!type) type = "RGBHEX"; + var a; + switch (type) { + case "HSL": + this.fill(value[0], value[1], value[2]); + break; + case "RGB": + a = RGBtoHLS(value[0], value[1], value[2]); + this.fill(a[0], a[1], a[2]); + break; + case "RGBHEX": + var RGB = arguments[0].match(/(..)(..)(..)/); + a = RGBtoHLS(Math.hexToDec(RGB[0]), + Math.hexToDec(RGB[1]), Math.hexToDec(RGB[2])); + this.fill(a[0], a[1], a[2]); + break; + } + }; + + this.getValue = function(type){ + return HSLRangeToRGB(cH, cS, cL); + }; + + // PRIVATE METHODS + var cL = 120, + cS = 239, + cH = 0, + cHex = "#FF0000", + HSLRange = 240; + + function HSLRangeToRGB(H, S, L){ + return HSLtoRGB(H / (HSLRange - 1), S / HSLRange, + Math.min(L / HSLRange, 1)) + }; + + function RGBtoHLS(R,G,B){ + var RGBMAX = 255, + HLSMAX = HSLRange, + UNDEF = (HLSMAX*2/3), + + /* calculate lightness */ + cMax = Math.max(Math.max(R,G), B), + cMin = Math.min(Math.min(R,G), B); + L = (((cMax + cMin) * HLSMAX) + RGBMAX) / (2 * RGBMAX); + + if (cMax == cMin) { /* r=g=b --> achromatic case */ + S = 0; /* saturation */ + H = UNDEF; /* hue */ + } + /* chromatic case */ + else { + /* saturation */ + if (L <= (HLSMAX/2)) + S = (((cMax - cMin) * HLSMAX) + ((cMax + cMin) / 2)) / (cMax + cMin); + else + S = (((cMax - cMin) * HLSMAX) + ((2 * RGBMAX - cMax - cMin) / 2)) + / (2 * RGBMAX - cMax - cMin); + + /* hue */ + Rdelta = (((cMax - R) * (HLSMAX / 6)) + ((cMax - cMin) / 2)) / (cMax - cMin); + Gdelta = (((cMax - G) * (HLSMAX / 6)) + ((cMax - cMin) / 2)) / (cMax - cMin); + Bdelta = (((cMax - B) * (HLSMAX / 6)) + ((cMax - cMin) / 2)) / (cMax - cMin); + + if (R == cMax) + H = Bdelta - Gdelta; + else if (G == cMax) + H = (HLSMAX / 3) + Rdelta - Bdelta; + else + H = ((2 * HLSMAX) / 3) + Gdelta - Rdelta; + + if (H < 0) + H += HLSMAX; + if (H > HLSMAX) + H -= HLSMAX; + } + + return [H, S, L]; + } + + function hueToColorValue(hue){ + var V; + + if (hue < 0) + hue = hue + 1 + else if (hue > 1) + hue = hue - 1; + + if (6 * hue < 1) + V = M1 + (M2 - M1) * hue * 6 + else if (2 * hue < 1) + V = M2 + else if (3 * hue < 2) + V = M1 + (M2 - M1) * (2 / 3 - hue) * 6 + else + V = M1; + + return Math.max(Math.floor(255 * V), 0); + }; + + function HSLtoRGB(H, S, L){ + var R, G, B; + + if (S == 0) + G = B = R = Math.round (255 * L); + else { + M2 = (L <= 0.5) ? (L * (1 + S)) : (L + S - L * S); + + M1 = 2 * L - M2; + R = hueToColorValue(H + 1 / 3); + G = hueToColorValue(H); + B = hueToColorValue(H - 1 / 3); + } + + return Math.decToHex(R) + "" + Math.decToHex(G) + "" + Math.decToHex(B); + }; + + this.fill = function(H, S, L){ + var Hex = HSLRangeToRGB(H,S,L); + this.value = Hex; + + //RGB + var RGB = Hex.match(/(..)(..)(..)/); + this.tbRed.value = Math.hexToDec(RGB[1]); + this.tbGreen.value = Math.hexToDec(RGB[2]); + this.tbBlue.value = Math.hexToDec(RGB[3]); + + //HSL + this.tbHue.value = Math.round(H); + this.tbSatern.value = Math.round(S); + this.tbLuminance.value = Math.round(L); + + //HexRGB + this.tbHexColor.value = Hex; + + //Shower + this.shower.style.backgroundColor = Hex; + + //Luminance + var HSL120 = HSLRangeToRGB(H, S, 120); + this.bar1.style.backgroundColor = HSL120; + this.bgBar1.style.backgroundColor = HSLRangeToRGB(H, S, 240); + this.bar2.style.backgroundColor = HSLRangeToRGB(H, S, 0); + this.bgBar2.style.backgroundColor = HSL120; + }; + + this.movePointer = function(e){ + e = e || event; + + var ty = this.pHolder.ty; + if ((e.clientY - ty >= 0) && (e.clientY - ty + <= this.pHolder.offsetHeight - this.pointer.offsetHeight + 22)) + this.pointer.style.top = e.clientY - ty; + if (e.clientY - ty < 21) + this.pointer.style.top = 21; + if (e.clientY - ty + > this.pHolder.offsetHeight - this.pointer.offsetHeight + 19) + this.pointer.style.top = this.pHolder.offsetHeight + - this.pointer.offsetHeight + 19; + + // 255 - posY: + cL = (255 - (this.pointer.offsetTop - 22)) / 2.56 * 2.4; + this.fill(cH, cS, cL); + + e.returnValue = false; + e.cancelBubble = true; + }; + + this.setLogic = function(){ + var _self = this; + this.pHolder.style.zIndex = 10; + this.pHolder.onmousedown = function(){ + this.ty = apf.getAbsolutePosition(this)[1] - 20; + + _self.movePointer(); + document.onmousemove = _self.movePointer + document.onmouseup = function(){ this.onmousemove = function(){}; }; + } + + this.container.onmousedown = function(e){ + e = e || event; + + this.active = true; + if (e.srcElement == this) { + if (e.offsetX >= 0 && e.offsetX <= 256 + && e.offsetY >= 0 && e.offsetY <= 256) { + cS = (256 - e.offsetY) / 2.56 * 2.4 + cH = e.offsetX / 2.56 * 2.39 + } + _self.fill(cH, cS, cL); + _self.shower.style.backgroundColor = _self.currentColor; + } + _self.point.style.display = "none"; + + e.cancelBubble = true; + } + + this.container.onmouseup = function(e){ + e = e || event; + this.active = false; + _self.point.style.top = e.offsetY - _self.point.offsetHeight - 2; + _self.point.style.left = e.offsetX - _self.point.offsetWidth - 2; + _self.point.style.display = "block"; + + _self.change(_self.tbHexColor.value); + } + + this.container.onmousemove = function(e){ + e = e || event; + if (this.active) { + if (e.offsetX >= 0 && e.offsetX <= 256 + && e.offsetY >= 0 && e.offsetY <= 256) { + cS = (256 - e.offsetY) / 2.56 * 2.4 + cH = e.offsetX / 2.56 * 2.39 + } + _self.fill(cH, cS, cL); + _self.shower.style.backgroundColor = _self.currentColor; + } + } + + /*this.tbHexColor.host = + this.tbRed.host = + this.tbGreen.host = + this.tbBlue.host = this; + this.tbHexColor.onblur = function(){_self.setValue("RGBHEX", this.value);} + this.tbRed.onblur = function(){_self.setValue("RGB", this.value, _self.tbGreen.value, _self.tbBlue.value);} + this.tbGreen.onblur = function(){_self.setValue("RGB", _self.tbRed.value, this.value, _self.tbBlue.value);} + this.tbBlue.onblur = function(){_self.setValue("RGB", _self.tbRed.value, _self.tbGreen.value, this.value);} + */ + } + + // Databinding + this.$mainBind = "color"; + + this.$draw = function(parentNode, clear){ + //Build Main Skin + this.$ext = this.$getExternal(); + + this.tbRed = this.$getLayoutNode("main", "red", this.$ext); + this.tbGreen = this.$getLayoutNode("main", "green", this.$ext); + this.tbBlue = this.$getLayoutNode("main", "blue", this.$ext); + + this.tbHue = this.$getLayoutNode("main", "hue", this.$ext); + this.tbSatern = this.$getLayoutNode("main", "satern", this.$ext); + this.tbLuminance = this.$getLayoutNode("main", "luminance", this.$ext); + + this.tbHexColor = this.$getLayoutNode("main", "hex", this.$ext); + var _self = this; + this.tbHexColor.onchange = function(){ + _self.setValue(this.value, "RGBHEX"); + }; + + this.shower = this.$getLayoutNode("main", "shower", this.$ext); + + this.bar1 = this.$getLayoutNode("main", "bar1", this.$ext); + this.bgBar1 = this.$getLayoutNode("main", "bgbar1", this.$ext); + this.bar2 = this.$getLayoutNode("main", "bar2", this.$ext); + this.bgBar2 = this.$getLayoutNode("main", "bgbar2", this.$ext); + + this.pHolder = this.$getLayoutNode("main", "pholder", this.$ext); + this.pointer = this.$getLayoutNode("main", "pointer", this.$ext); + this.container = this.$getLayoutNode("main", "container", this.$ext); + this.point = this.$getLayoutNode("main", "point", this.$ext); + + var nodes = this.$ext.getElementsByTagName("input"); + for (var i = 0; i < nodes.length; i++) { + nodes[i].onselectstart = function(e){ + e = e || event; + e.cancelBubble = true; + }; + } + + this.setLogic(); + + this.setValue("ffffff"); + //this.fill(cH, cS, cL); + } + + this.$loadAml = function(x){ + if (x.getAttribute("color")) + this.setValue(x.getAttribute("color")); + } + + this.$destroy = function(){ + this.container.host = + this.tbRed.host = + this.tbGreen.host = + this.tbBlue.host = + this.tbHexColor.host = + this.pHolder.host = null; + } +}).call(apf.colorpicker.prototype = new apf.GuiElement()); + +apf.aml.setElement("colorpicker", apf.colorpicker); + + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/elements/colorpicker2.js)SIZE(11113)TIME(Mon, 10 Jan 2011 10:53:12 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + + +/** + * Element giving the user a visual choice to pick a color just like Photoshop + * does it! + * + * @constructor + * @define colorpicker + * @addnode elements + * + * @author Mike de Boer (mike AT javeline DOT com) + * @version %I%, %G% + * @since 3.0 + * + * @inherits apf.StandardBinding + * @inherits apf.DataAction + * + * @attribute {String} value the color that is selected in the color picker. + * + * @binding value Determines the way the value for the element is retrieved + * from the bound data. + * Example: + * Sets the color based on data loaded into this component. + * + * + * + * + * + * + */ +apf.colorpicker = function(struct, tagName){ + this.$init(tagName || "colorpicker", apf.NODE_VISIBLE, struct); +}; + +(function(){ + this.value = "ff0000"; + this.changeTimer = null; + + var c = apf.color; + + this.$supportedProperties.push("color", "red", "green", "blue", "hue", + "saturation", "brightness", "hex"); + + this.$propHandlers["red"] = + this.$propHandlers["green"] = + this.$propHandlers["blue"] = + this.$propHandlers["hue"] = + this.$propHandlers["saturation"] = + this.$propHandlers["brightness"] = + this.$propHandlers["hex"] = function(val, doChange) { + clearTimeout(this.changeTimer); + if (doChange) { + var _self = this; + this.changeTimer = $setTimeout(function() { + _self.$change(); + }); + } + }; + + this.$propHandlers["value"] = function(val) { + this.$restoreOriginal(); + }; + + this.$restoreOriginal = function() { + this.$change(c.hexToHSB(this.value)); + this.oCustomColor.style.backgroundColor = + (this.value.substr(0, 1) != "#" ? "#" : "") + this.value; + }; + + this.$change = function(hsb) { + if (!hsb) { + hsb = { + h: this.hue, + s: this.saturation, + b: this.brightness + }; + } + hsb = c.fixHSB(hsb); + + var hex = c.HSBToHex(hsb), + rgb = c.HSBToRGB(hsb); + + this.oNewColor.style.backgroundColor = "#" + hex; + + this.setProperty("red", rgb.r); + this.setProperty("green", rgb.g); + this.setProperty("blue", rgb.b); + this.setProperty("saturation", hsb.s); + this.setProperty("brightness", hsb.b); + this.setProperty("hue", hsb.h); + this.setProperty("hex", hex); + + this.oSelector.style.background = "#" + c.HSBToHex({h: hsb.h, s: 100, b: 100}); + this.oHue.style.top = parseInt(150 - 150 * hsb.h / 360, 10) + "px"; + this.oSelectorInd.style.left = parseInt(150 * hsb.s / 100, 10) + "px"; + this.oSelectorInd.style.top = parseInt(150 * (100 - hsb.b) / 100, 10) + "px"; + }; + + this.$draw = function() { + if (!this.id) + this.setProperty("id", "colorpicker" + this.$uniqueId); + + //Build Main Skin + this.$ext = this.$getExternal(); + this.oSelector = this.$getLayoutNode("main", "selector", this.$ext); + this.oSelectorInd = this.$getLayoutNode("main", "selector_indic", this.$ext); + this.oHue = this.$getLayoutNode("main", "hue", this.$ext); + this.oNewColor = this.$getLayoutNode("main", "newcolor", this.$ext); + this.oCustomColor = this.$getLayoutNode("main", "customcolor", this.$ext); + this.oInputs = this.$getLayoutNode("main", "inputs", this.$ext); + + this.$restoreOriginal(); + + //attach behaviours + var _self = this, + doc = (!document.compatMode || document.compatMode == 'CSS1Compat') + ? document.html : document.body; + function stopMoving() { + document.onmousemove = document.onmouseup = null; + _self.$change(); + return false; + } + function selectorDown() { + var el = this, + pos = apf.getAbsolutePosition(el); + + function selectorMove(e) { + e = e || event; + var pageX = e.pageX || e.clientX + (doc ? doc.scrollLeft : 0), + pageY = e.pageY || e.clientY + (doc ? doc.scrollTop : 0); + // only the saturation and brightness change... + _self.brightness = parseInt(100 * (150 - Math.max(0, Math.min(150, + (pageY - pos[1])))) / 150, 10); + _self.saturation = parseInt(100 * (Math.max(0, Math.min(150, + (pageX - pos[0])))) / 150, 10); + _self.$change(); + pos = apf.getAbsolutePosition(el); + return false; + } + document.onmousemove = selectorMove; + document.onmouseup = function(e) { + selectorMove(e); + return stopMoving(e); + }; + } + + function hueDown(e) { + var el = this, + pos = apf.getAbsolutePosition(el); + + function hueMove(e) { + e = e || event; + var pageY = e.pageY || e.clientY + (doc ? doc.scrollTop : 0); + _self.hue = parseInt(360 * (150 - Math.max(0, + Math.min(150, (pageY - pos[1])))) / 150, 10); + _self.$change(); + pos = apf.getAbsolutePosition(el); + } + document.onmousemove = hueMove; + document.onmouseup = function(e) { + hueMove(e); + return stopMoving(e); + }; + } + this.oSelector.onmousedown = selectorDown; + this.oHue.parentNode.onmousedown = hueDown; + this.oCustomColor.onmousedown = function() { + _self.$restoreOriginal(); + }; + + function spinnerChange(e) { + var o = e.currentTarget, + isRGB = false; + if (o.id.indexOf("hue") > -1) + _self.hue = e.value; + else if (o.id.indexOf("saturation") > -1) + _self.saturation = e.value; + else if (o.id.indexOf("brightness") > -1) + _self.brightness = e.value; + else if (o.id.indexOf("red") > -1) + _self.red = e.value, isRGB = true; + else if (o.id.indexOf("green") > -1) + _self.green = e.value, isRGB = true; + else if (o.id.indexOf("blue") > -1) + _self.blue = e.value, isRGB = true; + + if (isRGB) { + var hsb = c.RGBToHSB({r: _self.red, g: _self.green, b: _self.blue}); + _self.hue = hsb.h; + _self.saturation = hsb.s; + _self.brightness = hsb.b; + } + + _self.$change(); + } + + //append APF widgets for additional controls + var skin = apf.getInheritedAttribute(this.parentNode, "skinset"); + new apf.table({ + htmlNode: this.oInputs, + skinset: skin, + left: 206, + top: 52, + width: 150, + columns: "50%,50%", + cellheight: 26, + childNodes: [ + new apf.spinner({ + id: this.id + "_hue", + width: 62, + min: 0, + max: 360, + value: "{" + this.id + ".hue}", + onafterchange: spinnerChange + }), + new apf.spinner({ + id: this.id + "_red", + width: 62, + min: 0, + max: 255, + value: "{" + this.id + ".red}", + onafterchange: spinnerChange + }), + new apf.spinner({ + id: this.id + "_saturation", + width: 62, + min: 0, + max: 100, + value: "{" + this.id + ".saturation}", + onafterchange: spinnerChange + }), + new apf.spinner({ + id: this.id + "_green", + width: 62, + min: 0, + max: 255, + value: "{" + this.id + ".green}", + onafterchange: spinnerChange + }), + new apf.spinner({ + id: this.id + "_brightness", + width: 62, + min: 0, + max: 100, + value: "{" + this.id + ".brightness}", + onafterchange: spinnerChange + }), + new apf.spinner({ + id: this.id + "_blue", + width: 62, + min: 0, + max: 255, + value: "{" + this.id + ".blue}", + onafterchange: spinnerChange + }) + ] + }); + + new apf.label({ + htmlNode: this.oInputs, + skinset: skin, + left: 212, + top: 142, + width: 7, + value: "#", + "for": this.id + "_hex" + }); + + new apf.textbox({ + htmlNode: this.oInputs, + skinset: skin, + left: 222, + top: 140, + width: 75, + value: "{" + this.id + ".hex}", + onafterchange: function(e) { + _self.hex = c.fixHex(e.value); + var hsb = c.hexToHSB(_self.hex); + _self.hue = hsb.h; + _self.saturation = hsb.s; + _self.brightness = hsb.b; + _self.$change(); + } + }); + }; + + this.$destroy = function() { + this.$ext = this.oSelector = this.oSelectorInd = this.oHue = + this.oNewColor = this.oCustomColor = this.oInputs = null; + }; + +}).call(apf.colorpicker.prototype = new apf.StandardBinding()); + + +apf.aml.setElement("colorpicker", apf.colorpicker); + + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/elements/comment.js)SIZE(1324)TIME(Mon, 10 Jan 2011 10:53:12 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + + +/** + * all elements within the comment tag are ignored by the parser. + * + * @author Ruben Daniels (ruben AT ajax DOT org) + * @version %I%, %G% + * @since 0.4 + */ +apf.comment = function(){ + this.$init("comment", apf.NODE_HIDDEN); +}; + +apf.comment.prototype = new apf.AmlComment(); +apf.aml.setElement("comment", apf.comment); + + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/elements/contextmenu.js)SIZE(2557)TIME(Mon, 10 Jan 2011 10:53:12 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + + +/** + * element specifying which menu is shown when a + * contextmenu is requested by a user for a aml node. + * Example: + * This example shows a list that shows the mnuRoot menu when the user + * right clicks on the root {@link term.datanode data node}. Otherwise the mnuItem menu is + * shown. + * + * + * + * + * + * + * @attribute {String} menu the id of the menu element. + * @attribute {String} select the xpath executed on the selected element of the databound element which determines whether this contextmenu is shown. + * + * @author Ruben Daniels (ruben AT ajax DOT org) + * @version %I%, %G% + * @since 0.4 + */ +apf.contextmenu = function(){ + this.$init("contextmenu", apf.NODE_HIDDEN); +}; + +(function(){ + this.$amlNodes = []; + + //1 = force no bind rule, 2 = force bind rule + this.$attrExcludePropBind = apf.extend({ + "match" : 1 + }, this.$attrExcludePropBind); + + this.register = function(amlParent){ + if (!amlParent.contextmenus) + amlParent.contextmenus = []; + amlParent.contextmenus.push(this); + }; + + this.unregister = function(amlParent){ + amlParent.contextmenus.remove(this); + }; + + this.addEventListener("DOMNodeInsertedIntoDocument", function(e){ + this.register(this.parentNode); + }); +}).call(apf.contextmenu.prototype = new apf.AmlElement()); + +apf.aml.setElement("contextmenu", apf.contextmenu); + + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/elements/datagrid.js)SIZE(53948)TIME(Mon, 10 Jan 2011 10:53:12 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + + +/** + * Element providing a sortable, selectable grid containing scrollable + * information. Grid columns can be reordered and resized. + * Example: + * This example shows a datagrid width several columns mixing percentage and + * fixed size columns. + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * @constructor + * @define datagrid + * @addnode elements + * + * @author Ruben Daniels (ruben AT ajax DOT org) + * @version %I%, %G% + * @since 0.4 + * + * @inherits apf.BaseTree + * + * @binding invalidmsg Determines the error message that is shown when a cell is not valid. + * @binding description Determines the text that is displayed under the expanded row. + */ +apf.datagrid = function(struct, tagName){ + this.$init(tagName || "datagrid", apf.NODE_VISIBLE, struct); + + this.$headings = [], + this.$cssRules = []; //@todo Needs to be reset; + this.$lastOpened = {}; + + this.$editors = {}; + + + this.$dynCssClasses = []; + +}; + +(function(){ + var HAS_CHILD = 1 << 1, + IS_CLOSED = 1 << 2, + IS_LAST = 1 << 3, + IS_ROOT = 1 << 4, + treeState = this.$treeState; + + + this.implement( + apf.DataAction + ); + + + /*this.$init(function() { + this.addEventListener("keydown", keyHandler, true); + });*/ + + this.bufferselect = false; + this.$useTable = false; + this.$focussable = true; + this.$isWindowContainer = -1; + + this.$widthdiff = 0; + this.$defaultwidth = 0; + this.$useiframe = 0; + this.$needsDepth = true; + + + this.canrename = false; //@todo remove rename from basetree and move to tree.js + + + /** + * @attribute {Boolean} iframe whether this element is rendered inside an iframe. This is only supported for IE. Default is false for datagrid and true for spreadsheet and propedit. + */ + this.$booleanProperties["iframe"] = true; + + + + /** + * @attribute {String} mode Sets the way this element interacts with the user. + * Possible values: + * check the user can select a single item from this element. The selected item is indicated. + * radio the user can select multiple items from this element. Each selected item is indicated. + */ + this.$mode = 0; + this.$propHandlers["mode"] = function(value){ + if ("check|radio".indexOf(value) > -1) { + if (!this.hasFeature(apf.__MULTICHECK__)) + this.implement(apf.MultiCheck); + + this.addEventListener("afterrename", $afterRenameMode); //what does this do? + + this.multicheck = value == "check"; //radio is single + this.$mode = this.multicheck ? 1 : 2; + } + else { + //@todo undo actionRules setting + this.removeEventListener("afterrename", $afterRenameMode); + //@todo unimplement?? + this.$mode = 0; + } + }; + + //@todo apf3.0 retest this completely + function $afterRenameMode(){ + } + + + + this.$propHandlers["options"] = function(value){ + for (var i = 0, l = this.$headings.length; i < l; i++) { + this.$headings[i].setAttribute("options", value); + } + }; + + /** + * This method imports a stylesheet defined in a multidimensional array + * @param {Array} def Required Multidimensional array specifying + * @param {Object} win Optional Reference to a window + * @method + * @deprecated + */ + function importStylesheet(def, win){ + for (var i = 0; i < def.length; i++) { + if (!def[i][1]) continue; + + if (apf.isIE) + (win || window).document.styleSheets[0].addRule(def[i][0], + def[i][1]); + else + (win || window).document.styleSheets[0].insertRule(def[i][0] + + " {" + def[i][1] + "}", 0); + } + } + + function scrollIntoView(){ + var Q = (this.current || this.$selected), + o = this.$container; + o.scrollTop = (Q.offsetTop) - 21; + } + + /**** Keyboard Support ****/ + + + /*function keyHandler(e){ + var key = e.keyCode, + ctrlKey = e.ctrlKey, + shiftKey = e.shiftKey, + selHtml = this.$selected || this.$caret; + + if (!e.force && (!selHtml || this.renaming)) //@todo how about allowdeselect? + return; + + var selXml = this.caret || this.selected, + oInt = this.$useiframe ? this.oDoc.documentElement : this.$container, + margin, node, hasScroll, hasScrollX, hasScrollY, items, lines; + + switch (key) { + case 13: + if (this.$tempsel) + this.$selectTemp(); + + this.choose(selHtml); + break; + case 32: + if (ctrlKey || !this.isSelected(this.caret)) + this.select(this.caret, true); + return false; + case 109: + case 46: + //DELETE + if (this.disableremove) + return; + + if (this.celledit) { + this.rename(this.caret || this.selected, ""); + return; + } + + if (this.$tempsel) + this.$selectTemp(); + + this.remove(this.mode ? this.caret : null); //this.mode != "check" + break; + case 36: + //HOME + this.$setTempSelected (this.getFirstTraverseNode(), false, shiftKey); + this.$container.scrollTop = 0; + return false; + case 35: + //END + this.$setTempSelected (this.getLastTraverseNode(), false, shiftKey); + this.$container.scrollTop = this.$container.scrollHeight; + return false; + case 107: + //+ + if (this.more) + this.startMore(); + break; + case 37: + //LEFT + if (this.$tempsel) + this.$selectTemp(); + + if (this.cellselect) { + if (this.$lastcell) { + if (this.$lastcell.previousSibling) { + this.selectCell({target:this.$lastcell.previousSibling}, + this.$selected); + } + } + else { + this.selectCell({target:this.$selected.firstChild}, + this.$selected); + } + } + else if (this.$withContainer) + this.slideToggle(this.$caret || this.$selected, 2) + return false; + case 107: + case 39: + //RIGHT + if (this.$tempsel) + this.$selectTemp(); + + if (this.cellselect) { + if (this.$lastcell) { + if (this.$lastcell.nextSibling) { + this.selectCell({target:this.$lastcell.nextSibling}, + this.$selected); + } + } + else { + this.selectCell({target:this.$selected.firstChild}, + this.$selected); + } + } + else if (this.$withContainer) + this.slideToggle(this.$caret || this.$selected, 1) + + return false; + case 38: + //UP + if (!selXml && !this.$tempsel) + return false; + + node = this.$tempsel + ? apf.xmldb.getNode(this.$tempsel) + : selXml; + + margin = apf.getBox(apf.getStyle(selHtml, "margin")); + hasScroll = oInt.scrollHeight > oInt.offsetHeight; + items = Math.floor((oInt.offsetWidth + - (hasScroll ? 15 : 0)) / (selHtml.offsetWidth + + margin[1] + margin[3])); + + node = this.getNextTraverseSelected(node, false, items); + if (node) + this.$setTempSelected (node, ctrlKey, shiftKey); + else + return false; + + selHtml = apf.xmldb.findHtmlNode(node, this); + if (selHtml.offsetTop <= oInt.scrollTop) { + oInt.scrollTop = (Array.prototype.indexOf.call(this.getTraverseNodes(), node) < items + ? 0 + : selHtml.offsetTop - margin[0]) + - parseInt(apf.getStyle(oInt, "paddingTop")); + } + return false; + case 40: + //DOWN + if (!selXml && !this.$tempsel) + return false; + + node = this.$tempsel + ? apf.xmldb.getNode(this.$tempsel) + : selXml; + + margin = apf.getBox(apf.getStyle(selHtml, "margin")); + hasScroll = oInt.scrollHeight > oInt.offsetHeight; + items = Math.floor((oInt.offsetWidth + - (hasScroll ? 15 : 0)) / (selHtml.offsetWidth + + margin[1] + margin[3])); + + node = this.getNextTraverseSelected(node, true, items); + if (node) + this.$setTempSelected (node, ctrlKey, shiftKey); + else + return false; + + selHtml = apf.xmldb.findHtmlNode(node, this); + if (selHtml.offsetTop + selHtml.offsetHeight + > oInt.scrollTop + oInt.offsetHeight) // - (hasScroll ? 10 : 0) + oInt.scrollTop = selHtml.offsetTop + - oInt.offsetHeight + selHtml.offsetHeight + + margin[0]; //+ (hasScroll ? 10 : 0) + + return false; + case 33: + //PGUP + if (!selXml && !this.$tempsel) + return false; + + node = this.$tempsel + ? apf.xmldb.getNode(this.$tempsel) + : selXml; + + margin = apf.getBox(apf.getStyle(selHtml, "margin")); + hasScrollY = oInt.scrollHeight > oInt.offsetHeight; + hasScrollX = oInt.scrollWidth > oInt.offsetWidth; + items = Math.floor((oInt.offsetWidth + - (hasScrollY ? 15 : 0)) / (selHtml.offsetWidth + + margin[1] + margin[3])) || 1; + lines = Math.floor((oInt.offsetHeight + - (hasScrollX ? 15 : 0)) / (selHtml.offsetHeight + + margin[0] + margin[2])); + + node = this.getNextTraverseSelected(node, false, items * lines); + if (!node) + node = this.getFirstTraverseNode(); + if (node) + this.$setTempSelected (node, ctrlKey, shiftKey); + else + return false; + + selHtml = apf.xmldb.findHtmlNode(node, this); + if (selHtml.offsetTop < oInt.scrollTop) { + oInt.scrollTop = (Array.prototype.indexOf.call(this.getTraverseNodes(), node) < items + ? 0 + : selHtml.offsetTop - margin[0]) + - parseInt(apf.getStyle(oInt, "paddingTop")); + } + return false; + case 34: + //PGDN + if (!selXml && !this.$tempsel) + return false; + + node = this.$tempsel + ? apf.xmldb.getNode(this.$tempsel) + : selXml; + + margin = apf.getBox(apf.getStyle(selHtml, "margin")); + hasScrollY = oInt.scrollHeight > oInt.offsetHeight; + hasScrollX = oInt.scrollWidth > oInt.offsetWidth; + items = Math.floor((oInt.offsetWidth - (hasScrollY ? 15 : 0)) + / (selHtml.offsetWidth + margin[1] + margin[3])) || 1; + lines = Math.floor((oInt.offsetHeight - (hasScrollX ? 15 : 0)) + / (selHtml.offsetHeight + margin[0] + margin[2])); + + node = this.getNextTraverseSelected(selXml, true, items * lines); + if (!node) + node = this.getLastTraverseNode(); + if (node) + this.$setTempSelected (node, ctrlKey, shiftKey); + else + return false; + + selHtml = apf.xmldb.findHtmlNode(node, this); + if (selHtml.offsetTop + selHtml.offsetHeight + > oInt.scrollTop + oInt.offsetHeight) // - (hasScrollY ? 10 : 0) + oInt.scrollTop = selHtml.offsetTop + - oInt.offsetHeight + selHtml.offsetHeight + + margin[0]; //+ 10 + (hasScrollY ? 10 : 0) + return false; + default: + if (this.celledit) { + if (!ctrlKey && !e.altKey && (key > 46 && key < 112 || key > 123)) + this.startRename(null, true); + return; + } + else if (key == 65 && ctrlKey) { + this.selectAll(); + return false; + } + //@todo make this work with the sorted column + else if (this.caption || (this.bindingRules || {})["caption"]) { + if (!this.xmlRoot) return; + + //this should move to a onkeypress based function + if (!this.lookup || new Date().getTime() + - this.lookup.date.getTime() > 300) + this.lookup = { + str : "", + date : new Date() + }; + + this.lookup.str += String.fromCharCode(key); + + var nodes = this.getTraverseNodes(); //@todo start at current indicator + for (var v, i = 0; i < nodes.length; i++) { + v = this.$applyBindRule("caption", nodes[i]); + if (v && v.substr(0, this.lookup.str.length) + .toUpperCase() == this.lookup.str) { + + if (!this.isSelected(nodes[i])) { + if (this.mode == "check") + this.setCaret(nodes[i]); + else + this.select(nodes[i]); + } + + if (selHtml) + this.$container.scrollTop = selHtml.offsetTop + - (this.$container.offsetHeight + - selHtml.offsetHeight) / 2; + return; + } + } + return; + } + break; + }; + + this.lookup = null; + //return false; + }*/ + + + + /**** Focus ****/ + // Too slow for IE + + + this.$getCaptionElement = function(){ + if (!this.$selected) + return false; + + var nodes = this.$head.childNodes, + htmlNodes = this.$selected.childNodes, i = 0; + + nodeIter = htmlNodes[i]; + while (nodeIter) { + if (nodeIter.nodeType != 1) { + nodeIter = nodeIter.nextSibling; + continue; + } + + h = apf.all[nodes[i].getAttribute("hid")]; + if (h.tree || h.rename) + return this.$getLayoutNode(h.tree ? "treecell" : "cell", "caption", nodeIter) || nodeIter; + + i++; + nodeIter = nodeIter.nextSibling; + } + + throw new Error("Datagrid without rename column specified"); + }; + + + this.$focus = function(){ + if (!this.$ext || (apf.isIE && this.$useiframe && this.cssfix)) //@todo fix this by fixing focussing for this component + return; + + this.$setStyleClass(this.$ext, this.$baseCSSname + "Focus"); + + if (this.oDoc) + this.$setStyleClass(this.oDoc.documentElement, this.$baseCSSname + "Focus"); + }; + + this.$blur = function(){ + //@todo fix this by fixing focussing for this component + if (!this.$ext || (apf.isIE && this.$useiframe && this.cssfix)) + return; + + this.$setStyleClass(this.$ext, "", [this.$baseCSSname + "Focus"]); + + if (this.oDoc) + this.$setStyleClass(this.oDoc.documentElement, "", [this.$baseCSSname + "Focus"]); + + hideEditor.call(this); + }; + + /**** Databinding ****/ + + this.addEventListener("bindingsload", this.$loaddatabinding = function(e){ + var rules = e.bindings["column"]; + if (!rules || !rules.length) + return; + + this.$headings = rules; + + var fixed = 0, found = false; + for (var h, i = 0, l = rules.length; i < l; i++) { + h = rules[i]; + + + if (!h.$width) + throw new Error("missing width"); //temporary check + + + if (!h.$isPercentage) + fixed += parseFloat(h.$width) || 0; + else + found = true; + } + + if (!found) { //@todo removal??? + this.$isFixedGrid = true; + this.$setStyleClass(this.$ext, "fixed"); + + if (this.$useiframe) + this.$setStyleClass(this.oDoc.documentElement, "fixed"); + } + else { + //@todo remove + } + + if (fixed > 0 && !this.$isFixedGrid) { + var vLeft = fixed; + + //first column has total -1 * fixed margin-left. - 5 + //cssRules[0][1] += ";margin-left:-" + vLeft + "px;"; + //cssRules[1][1] += ";margin-left:-" + vLeft + "px;"; + this.$cssRules.push(["." + this.$baseCSSname + " .row" + this.$uniqueId, + "padding-right:" + vLeft + "px;margin-right:-" + vLeft + "px"]); + + //headings and records have same padding-right + this.$container.style.paddingRight = + this.$head.style.paddingRight = vLeft + "px"; + } + + this.$fixed = fixed; + this.$first = 0; + + this.$withContainer = e.bindings.description ? true : false; + + //Activate CSS Rules + importStylesheet(this.$cssRules, window); + + if (this.$useiframe) + importStylesheet(this.$cssRules, this.oWin); + }); + + this.$initNode = function(xmlNode, state, Lid, depth){ + //Build Row + this.$getNewContext("item"); + var oRow = this.$getLayoutNode("item"); + oRow.setAttribute("id", Lid); + + //@todo if treearch + oRow.setAttribute("class", oRow.getAttribute("class") + " " + + treeState[state] + " item" + this.$uniqueId);//"width:" + (totalWidth+40) + "px"); + this.$setStyleClass(this.$getLayoutNode("item", "container"), treeState[state]) + + oRow.setAttribute("ondblclick", 'var o = apf.lookup(' + this.$uniqueId + ');o.choose(null, true);' + + (this.$withContainer ? 'o.slideToggle(this, null, true);' : '') + + (this.celledit && !this.namevalue ? 'o.startRename(null, null, true);' : '')); + + if (this.hasFeature(apf.__DRAGDROP__)) { + oRow.setAttribute("onmouseout", 'this.hasPassedDown = false;'); + oRow.setAttribute("onmousedown", 'var o = apf.lookup(' + this.$uniqueId + ');\ + var xmlNode = apf.xmldb.findXmlNode(this);\ + var isSelected = o.isSelected(xmlNode);\ + this.hasPassedDown = true;\ + if (!o.hasFeature(apf.__DRAGDROP__) || !isSelected && !event.ctrlKey)\ + o.select(this, event.ctrlKey, event.shiftKey, -1);' + + (this.cellselect || this.namevalue ? 'o.selectCell(event, this, isSelected);' : '')); + + oRow.setAttribute("onmouseup", 'if (!this.hasPassedDown) return;\ + var o = apf.lookup(' + this.$uniqueId + ');\ + var xmlNode = apf.xmldb.findXmlNode(this);\ + var isSelected = o.isSelected(xmlNode);\ + if (o.hasFeature(apf.__DRAGDROP__))\ + o.select(this, event.ctrlKey, event.shiftKey, -1);'); + } //@todo add DRAGDROP ifdefs + else { + oRow.setAttribute("onmousedown", 'var o = apf.lookup(' + this.$uniqueId + ');\ + var wasSelected = o.$selected == this;\ + o.select(this, event.ctrlKey, event.shiftKey, -1);' + + (this.cellselect || this.namevalue ? 'o.selectCell(event, this, wasSelected);' : '')); + } + + //Build the Cells + for (var cellType, cell, h, i = 0; i < this.$headings.length; i++) { + h = this.$headings[i]; + + if (h.tree && h.check) { + cellType = "treecheckcell"; + + this.$getNewContext("treecheckcell"); + cell = this.$getLayoutNode("treecheckcell"); + var oc = this.$getLayoutNode("treecheckcell", "openclose"); + oc.setAttribute("style", "margin-left:" + (((depth||0)) * 15 + 4) + "px;"); + oc.setAttribute("onmousedown", + "var o = apf.lookup(" + this.$uniqueId + ");\ + o.slideToggle(this, null, null, true);\ + event.cancelBubble = true;\ + apf.window.$mousedown(event);"); + + oc.setAttribute("ondblclick", "event.cancelBubble = true"); + } + else if (h.tree) { + cellType = "treecell"; + + this.$getNewContext("treecell"); + cell = this.$getLayoutNode("treecell"); + var oc = this.$getLayoutNode("treecell", "openclose"); + oc.setAttribute("style", "margin-left:" + (((depth||0)) * 15 + 4) + "px;"); + oc.setAttribute("onmousedown", + "var o = apf.lookup(" + this.$uniqueId + ");\ + o.slideToggle(this, null, null, true);\ + event.cancelBubble = true;\ + apf.window.$mousedown(event);"); + + oc.setAttribute("ondblclick", "event.cancelBubble = true"); + + /*cell.setAttribute("style", "background-position: " + + ((((depth||0)+1) * 15) - 10) + "px 50%");*/ + } + else { + + cellType = h.check ? "checkcell" : "cell"; + + + this.$getNewContext(cellType); + cell = this.$getLayoutNode(cellType); + } + + + if (this.$mode && h.check) { + var elCheck = this.$getLayoutNode(cellType, "check"); + if (elCheck) { + elCheck.setAttribute("onmousedown", + "var o = apf.lookup(" + this.$uniqueId + ");\ + o.checkToggle(this, true);\o.$skipSelect = true;"); + + if (apf.isTrue(this.$applyBindRule("checked", xmlNode))) { + this.$checkedList.push(xmlNode); + this.$setStyleClass(oRow, "checked"); + } + else if (this.isChecked(xmlNode)) + this.$setStyleClass(oRow, "checked"); + } + else { + + throw new Error(apf.formatErrorString(0, this, + "Could not find check attribute", + 'Maybe the attribute check is missing from your skin file:\ + \ +
    \ +
    \ + \ + ')); + + return false; + } + } + + + apf.setStyleClass(cell, h.$className); + + if (h.css) + apf.setStyleClass(cell, (apf.lm.compile(h.css))(xmlNode)); //@todo cashing of compiled function? + + if (h.icon) { + var node = this.$getLayoutNode(cellType, "caption", oRow.appendChild(cell)); + node = (node.nodeType == 1 && node || node.parentNode); + node.setAttribute("style", "background-image:url(" + + apf.getAbsolutePath(this.iconPath, + ((h.cicon || h.$compile("icon", {nostring: true}))(xmlNode) || "")) + + ");"); + this.$setStyleClass(node, "iconCell", []); + } + + if (h.value) { + if (!h.cvalue2) { + h.$compile("value", {nostring: true}); + + + if (h.value) + h.cvalue2.hasAml = h.value.indexOf(" -1; + + } + + + if (h.cvalue2.hasAml){ + var q = (this.$amlBindQueue || (this.$amlBindQueue = {})); + + var htmlEl = this.$getLayoutNode(cellType, + "caption", oRow.appendChild(cell)); + htmlEl.setAttribute("id", "placeholder_" + this.$uniqueId + + "_" + ((q.column || (q.column = [])).push(xmlNode) - 1)); + apf.setNodeValue(htmlEl, ' '); + } + else + + { + apf.setNodeValue(this.$getLayoutNode(cellType, + "caption", oRow.appendChild(cell)), + h.cvalue2(xmlNode) || ' '); + } + } + } + + if (this.$bindings && this.$bindings.color) { + var colorRule = this.$getDataNode("color", xmlNode); + this.$setStyleClass(oRow, colorRule ? "highlight" : null, colorRule ? ["highlight"] : null); + } + + + var cssClass = this.$applyBindRule("css", xmlNode); + if (cssClass) { + this.$setStyleClass(oRow, cssClass); + if (cssClass) + this.$dynCssClasses.push(cssClass); + } + + + /*if (this.$withContainer) { + var desc = this.$applyBindRule("description", xmlNode); + this.$getNewContext("container"); + var oDesc = this.$getLayoutNode("container"); + apf.setNodeValue(this.$getLayoutNode("container", "container", + oDesc), desc); + oDesc.setAttribute("class", (oDesc.getAttribute("class") || "") + + " row" + this.$uniqueId); + + if (htmlParentNode) + apf.insertHtmlNode(oDesc, htmlParentNode, beforeNode); + else + this.$nodes.push(oDesc); + }*/ + + return oRow; + }; + + this.$updateNode = function(xmlNode, htmlNode){ + if (!htmlNode) return; + + var nodes = this.$head.childNodes, + htmlNodes = htmlNode.childNodes, + cell, p; + + if (!this.namevalue && this.$curBtn) + p = this.$curBtn.parentNode; + + var nodeIter, h, i = 0; + nodeIter = htmlNodes[0]; + while (nodeIter) { + if (nodeIter.nodeType != 1) { + nodeIter = nodeIter.nextSibling; + continue; + } + + h = apf.all[nodes[i].getAttribute("hid")]; + + //@todo fake optimization + cell = this.$getLayoutNode(h.tree ? "treecell" : "cell", "caption", nodeIter) || nodeIter;//htmlNodes[i].firstChild || + + if (h.css) + apf.setStyleClass(cell, (apf.lm.compile(h.css))(xmlNode)); //@todo cashing of compiled function? + + if (h.tree) { + + //@todo + + + /*var oc = this.$getLayoutNode("treecell", "openclose", cell); + oc.setAttribute("style", "margin-left:" + (((depth||0)) * 15 + 4) + "px;"); + oc.setAttribute("onmousedown", + "var o = apf.lookup(" + this.$uniqueId + ");\ + o.slideToggle(this, null, null, true);");*/ + } + + if (h.value) { + if (!h.cvalue2) { + h.$compile("value", {nostring: true}); + + + if (h.value) + h.cvalue2.hasAml = h.value.indexOf(" -1; + + } + + + if (!h.cvalue2.hasAml) + + cell.innerHTML = h.cvalue2(xmlNode) || ""; + } + + if (h.icon) { + (cell.nodeType == 1 && cell || cell.parentNode).style.backgroundImage = + "url(" + apf.getAbsolutePath(this.iconPath, + ((h.cicon || h.$compile("icon", {nostring: true}))(xmlNode) || "")) + + ")"; + } + + i++; + nodeIter = nodeIter.nextSibling; + } + + //return; //@todo fake optimization + + if (this.$bindings && this.$bindings.color) { + var colorRule = this.$getDataNode("color", xmlNode); + this.$setStyleClass(htmlNode, colorRule ? "highlight" : null, + colorRule ? ["highlight"] : null); + } + + + var cssClass = this.$applyBindRule("css", xmlNode); + if (cssClass || this.$dynCssClasses.length) { + this.$setStyleClass(htmlNode, cssClass, this.$dynCssClasses); + if (cssClass && !this.$dynCssClasses.contains(cssClass)) + this.$dynCssClasses.push(cssClass); + } + + + /*if (this.$withContainer) { + htmlNode.nextSibling.innerHTML + = this.$applyBindRule("description", xmlNode) || ""; + }*/ + }; + + this.$dblclick = function(htmlNode){ + var _self = this, id, cell; + while (!(id = htmlNode.getAttribute(apf.xmldb.htmlIdTag)) || id.indexOf("|") == -1) { + htmlNode = (cell = htmlNode).parentNode; + if (htmlNode.nodeType != 1) + return; + } + + if (this.$lastEditor && this.$lastEditor[3] == htmlNode) + return; + + var h, colId = cell.className.match(/(col\d+)/)[1]; + for (var i = 0; i < this.$headings.length; i++) { + if (this.$headings[i].$className == colId) { + h = this.$headings[i]; + break; + } + } + + if (!h.editor) //No editor specified + return; + + /*if (this.$lastEditor) { + //this.$lastEditor[0].$blur(); + this.$lastEditor[0].setProperty("visible", false); + + var nodes = this.$lastEditor[1].childNodes; + for (var i = 0, l = nodes.length; i < l; i++) { + if (!nodes[i].host) + nodes[i].style.display = ""; + } + }*/ + + var xmlNode = apf.xmldb.getNode(htmlNode); + /* + - editor (name of widget, lm function returning amlNode or lm template ref) + - children being aml nodes + */ + var editParent = h.tree + ? this.$getLayoutNode("cell", "caption", cell) + : cell; + + var oEditor, editor = h.editor; + var ceditor = apf.lm.compile(editor, {xpathmode: 2}); //@todo can this be more efficient? + + var nodes = editParent.childNodes; + for (var i = 0, l = nodes.length; i < l; i++) { + if (!nodes[i].host) { + if (nodes[i].nodeType == 1) + nodes[i].style.display = "none"; + else { + this.$lastTextValue = nodes[i].nodeValue; + nodes[i].nodeValue = ""; //@todo + } + } + } + + if (ceditor.type == 2) { + if (!this.$editors[editor]) { + var constr = apf.namespaces[apf.ns.aml].elements[editor]; + var info = { + htmlNode : editParent, + style : "position:relative;z-index:10000", + value : "[{" + this.id + ".selected}::" + + (v = h.value).substr(1, v.length - 2) //only xpath value's supported for now + + "]", + focussable : false + }; + if (!h.tree) + info.width = "100%-3"; + + //@todo copy all non-known properties of the prop element + + if (constr.prototype.hasFeature(apf.__MULTISELECT__)) { + info.caption = h.eachcaption || "[text()]"; + info.eachvalue = h.eachvalue; // || "[@value]"; + + var model; + if (model = h.getAttribute("model")) { + info.model = model; + info.each = h.each; + } + else { + /*var each = h.each; + if (each.charAt(0) == "[") + each = each.substr(1, each.length - 2); + info.each = "[{" + this.id + ".selected}::" + each + "]";*/ + info.model = "{" + this.id + ".selected}" + info.each = h.each; + } + } + + if (h.skin) + info.skin = h.skin; + + oEditor = this.$editors[editor] = new constr(info); + + var box = apf.getBox(apf.getStyle(oEditor.$ext, "margin")); + if (box[1] || box[3]) { + oEditor.setAttribute("width", "100%+2-" + (box[1] + box[3])); + } + //else if (!box[3]) + //oEditor.$ext.style.marginLeft = "-1px"; + + //oEditor.$focussable = false; + oEditor.addEventListener("blur", function(){ + hideEditor.call(_self); + }); + oEditor.parentNode = this; + oEditor.realtime = false; + oEditor.$focusParent = this; + oEditor.setAttribute("focussable", "true"); + //delete oEditor.parentNode; + + oEditor.addEventListener("keydown", function(e){ + if (e.keyCode == 13) { + hideEditor.call(_self); + _self.$focus(); + } + else if (e.keyCode == 27) { + oEditor.removeAttribute("value"); //@todo this bugs in slider + hideEditor.call(_self); + //_self.getActionTracker().undo(); + } + }); + + //@todo set actiontracker + + //Patch oEditor to forward change + oEditor.$executeAction = function(){ + this.parentNode.$executeAction.apply(this.parentNode, arguments); + } + } + else { + oEditor = this.$editors[editor]; + + if (oEditor.hasFeature(apf.__MULTISELECT__) && !h.model) { + //oEditor.setAttribute("model", "{" + this.id + ".selected}"); + /*var each = h.each; + if (each.charAt(0) == "[") + each = each.substr(1, each.length - 2); + oEditor.setAttribute("each", "[{" + this.id + ".selected}::" + each + "]");*/ + /*apf.queue.empty();*/ + oEditor.setAttribute("value", "[{" + this.id + ".selected}::" + + (v = h.value).substr(1, v.length - 2) + + "]"); + } + + /*oEditor.setAttribute("value", "[{" + this.id + ".selected}::" + + (v = h.value).substr(1, v.length - 2) + + "]");*/ + + oEditor.setProperty("visible", true); + editParent.appendChild(oEditor.$ext); + + oEditor.setAttribute("width", h.tree ? "" : "100%-3"); + } + + /*setTimeout(function(){ + oEditor.focus(); + });*/ + } + else { + //Create dropdown + + var obj = ceditor.call(this, this.xmlRoot); + if (obj.localName == "template") { + //add template contents to dropped area + } + else { + //add xml into dropped area + } + } + + if (oEditor.localName == "textbox") + oEditor.select(); + + oEditor.focus(); + oEditor.$focus(); + + this.$setStyleClass(htmlNode, "editing"); + + this.$lastEditor = [oEditor, editParent, xmlNode, htmlNode, this.getActionTracker().undolength]; + } + + this.addEventListener("mousedown", function(e){ + if (this.$lastEditor + && !apf.isChildOf(this.$lastEditor[1], + e.htmlEvent.srcElement || e.htmlEvent.target, true)) + hideEditor.call(this); + }); + + var hideEditor = function(e){ + if (this.$lastEditor) { + var ed = this.$lastEditor; + this.$lastEditor = null; + + if (ed[0].hasFeature(apf.__MULTISELECT__)) // && !ed[0].model + ed[0].$clearDynamicProperty("value"); + + //this.$lastEditor[0].$blur(); + ed[0].setProperty("visible", false); + + var nodes = ed[1].childNodes; + for (var i = 0, l = nodes.length; i < l; i++) { + if (!nodes[i].host) { + if (nodes[i].nodeType == 1) + nodes[i].style.display = ""; + else if (!ed[0].value) { + nodes[i].nodeValue = this.$lastTextValue; //@todo + } + } + } + + this.$setStyleClass(ed[3], "", ["editing"]); + + this.focus(); + } + }; + this.addEventListener("beforeselect", hideEditor); + + /**** Column management ****/ + + /** + * Returns a column definition object based on the column number. + * @param {Number} hid the heading number; this number is based on the sequence of the column elements. + */ + this.getColumn = function(nr){ + return this.$headings[nr || this.$lastcol || 0]; + }; + + /** + * Resizes a column. + * @param {Number} hid the heading number; this number is based on the sequence of the column elements. + * @param {Number} newsize the new size of the column. + * @todo optimize but bringing down the string concats + */ + this.resizeColumn = function(nr, newsize){ + var h = this.$headings[nr]; + h.resize(newsize); + }; + + /** + * Hides a column. + * @param {Number} hid the heading number; this number is based on the sequence of the column elements. + */ + this.hideColumn = function(nr){ + var h = this.$headings[nr]; + h.hide(); + }; + + /** + * Shows a hidden column. + * @param {Number} hid the heading number; this number is based on the sequence of the column elements. + */ + this.showColumn = function(nr){ + var h = this.$headings[nr]; + h.show(); + }; + + /** + * Sorts a column. + * @param {Number} hid the heading number; this number is based on the sequence of the column elements. + */ + this.sortColumn = function(hid){ + var h = this.$headings[nr]; + h.sort(); + }; + + /** + * Moves a column to another position. + * @param {Number} fromHid the heading number of the column to move; this number is based on the sequence of the column elements. + * @param {Number} toHid the position the column is moved to; + */ + this.moveColumn = function(from, to){ + var h = this.$headings[nr]; + h.move(to); + } + + /**** Init ****/ + + this.$draw = function(){ + this.$drawBase(); + + var _self = this; + this.$ext.onmousedown = function(e){ + _self.dispatchEvent("mousedown", {htmlEvent: e || event}); + } + + //@todo rename 'body' to 'container' + + //Build Main Skin + this.$head = this.$getLayoutNode("main", "head", this.$ext); + this.$pointer = this.$getLayoutNode("main", "pointer", this.$ext); + + if (this.$head.firstChild) + this.$head.removeChild(this.$head.firstChild); + if (this.$container.firstChild) + this.$container.removeChild(this.$container.firstChild); + + var widthdiff = this.$widthdiff = this.$getOption("main", "widthdiff") || 0; + this.$defaultwidth = this.$getOption("main", "defaultwidth") || "100"; + this.$useiframe = apf.isIE && (apf.isTrue(this.$getOption("main", "iframe")) || this.iframe); + + //Initialize Iframe + if (this.$useiframe && !this.oIframe) { + //this.$container.style.overflow = "hidden"; + //var sInt = this.$container.outerHTML + var sClass = this.$container.className; + //this.$container.parentNode.removeChild(this.$container); + this.oIframe = this.$container.appendChild(document.createElement(apf.isIE + ? "" + : "iframe")); + this.oIframe.frameBorder = 0; + this.oWin = this.oIframe.contentWindow; + this.oDoc = this.oWin.document; + this.oDoc.write('\ + \ + \ + \ + \ + '); + //Import CSS + //this.oDoc.body.innerHTML = sInt; + this.$container = this.oDoc.body;//.firstChild; + this.$container.className = sClass;//this.oIframe.parentNode.className; + this.oDoc.documentElement.className = this.$ext.className; + //this.oDoc.body.className = this.$ext.className; + + apf.skins.loadCssInWindow(this.skinName, this.oWin, this.mediaPath, this.iconPath); + + if (apf.isIE) //@todo this can be removed when focussing is fixed for this component + this.$setStyleClass(this.oDoc.documentElement, this.$baseCSSname + "Focus"); + + apf.convertIframe(this.oIframe, true); + + if (apf.getStyle(this.oDoc.documentElement, "overflowY") == "auto") { + //@todo ie only + this.oIframe.onresize = function(){ + _self.$head.style.marginRight = + _self.oDoc.documentElement.scrollHeight > _self.oDoc.documentElement.offsetHeight + ? "16px" : "0"; + } + + this.addEventListener("afterload", this.oIframe.onresize); + this.addEventListener("xmlupdate", this.oIframe.onresize); + } + + this.oDoc.documentElement.onscroll = + function(){ + if (_self.$isFixedGrid) + _self.$head.scrollLeft = _self.oDoc.documentElement.scrollLeft; + }; + } + else { + if (apf.getStyle(this.$container, "overflowY") == "auto") { + this.$resize = function(){ + _self.$head.style.marginRight = + _self.$container.scrollHeight > _self.$container.offsetHeight + ? "16px" : "0"; + } + + + apf.layout.setRules(this.$ext, this.$uniqueId + "_datagrid", + "var o = apf.all[" + this.$uniqueId + "];\ + if (o) o.$resize()"); + apf.layout.queue(this.$ext); + + + this.addEventListener("afterload", this.$resize); + this.addEventListener("xmlupdate", this.$resize); + } + + this.$container.onscroll = + function(){ + if (_self.$isFixedGrid) + _self.$head.scrollLeft = _self.$container.scrollLeft; + }; + } + + this.$container[this.clickedit ? "onmousedown" : "ondblclick"] = function(e){ + if (!e) e = event; + _self.$dblclick(e.srcElement || e.target); + } + }; + + this.$destroy = function(){ + //@todo destroy this.$txt here + + this.$ext.onclick = this.$container.onresize = null; + + + apf.layout.removeRule(this.$container, "dg" + this.$uniqueId); + apf.layout.activateRules(this.$container); + + }; +}).call(apf.datagrid.prototype = new apf.BaseTree()); + +apf.aml.setElement("datagrid", apf.datagrid); +//apf.aml.setElement("column", apf.BindingRule); +apf.aml.setElement("description", apf.BindingRule); +apf.aml.setElement("color", apf.BindingRule); +apf.aml.setElement("contents", apf.BindingRule); + + + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/elements/debugger.js)SIZE(9859)TIME(Mon, 10 Jan 2011 10:53:12 GMT)*/ + +if (apf.hasRequireJS) require.def("apf/elements/debugger", + [], function() { + +apf.dbg = function(struct, tagName){ + this.$init(tagName || "debugger", apf.NODE_HIDDEN, struct); +}; + +(function(){ + + this.$host = null; + this.$debugger = null; + + this.$supportedProperties.push("state-running", "state-attached", + "model-sources", "model-stacks", "model-breakpoints", "activeframe"); + + this.$createModelPropHandler = function(name, xml, callback) { + return function(value) { + if (!value) return; + + this[name] = apf.setReference(value, + apf.nameserver.register("model", value, new apf.model())); + + // set the root node for this model + this[name].id = this[name].name = value; + this[name].load(xml); + + } + }; + + this.$createStatePropHandler = function(name) { + return function(value) { + if (!value) return; + + this[name] = apf.setReference(value, + apf.nameserver.register("state", value, new apf.state())); + + // set the root node for this model + this[name].id = this[name].name = value; + this[name].deactivate(); + + } + }; + + this.$propHandlers["model-sources"] = this.$createModelPropHandler("$mdlSources", ""); + this.$propHandlers["model-stack"] = this.$createModelPropHandler("$mdlStack", ""); + this.$propHandlers["model-breakpoints"] = this.$createModelPropHandler("$mdlBreakpoints", ""); + + this.$propHandlers["state-running"] = this.$createStatePropHandler("$stRunning"); + this.$propHandlers["state-attached"] = this.$createStatePropHandler("$stAttached"); + + this.$propHandlers["activeframe"] = function(value) { + if (this.$debugger) { + this.$ignoreFrameEvent = true; + this.$debugger.setFrame(value); + this.$ignoreFrameEvent = false; + } + this.dispatchEvent("changeframe", {data: value}); + }; + + this.attach = function(host, tab) { + var _self = this; + + host.$attach(this, tab, function(err, dbgImpl) { + _self.$host = host; + _self.$debugger = dbgImpl; + dbgImpl.addEventListener("afterCompile", _self.$onAfterCompile.bind(_self)); + + _self.$stAttached.activate(); + _self.$stRunning.setProperty("active", dbgImpl.isRunning()); + + dbgImpl.addEventListener("changeRunning", _self.$onChangeRunning.bind(_self)); + dbgImpl.addEventListener("break", _self.$onBreak.bind(_self)); + dbgImpl.addEventListener("detach", _self.$onDetach.bind(_self)); + dbgImpl.addEventListener("changeFrame", _self.$onChangeFrame.bind(_self)); + + _self.$loadSources(function() { + dbgImpl.setBreakpoints(_self.$mdlBreakpoints, function() { + _self.$debugger.backtrace(_self.$mdlStack, function() { + var frame = _self.$mdlStack.queryNode("frame[1]"); + if (frame) { + var scriptId = frame.getAttribute("scriptid"); + var scriptName = _self.$mdlSources.queryValue("file[@scriptid='" + scriptId + "']/@scriptname"); + + if (scriptName) { + var line = frame.getAttribute("line"); + var bp = _self.$mdlBreakpoints.queryNode("breakpoint[@script='" + scriptName + "' and @line='" + line + "']"); + } + if (!scriptName || !bp) { + _self.$debugger.continueScript(); + } + } + }); + }); + }); + }); + }; + + this.$onChangeRunning = function() { + var isRunning = this.$debugger && this.$debugger.isRunning(); + if (this.$stRunning.active && !isRunning) + this.$onBreak(); + + this.$stRunning.setProperty("active", isRunning); + + //if (isRunning) + //this.$mdlStack.load(""); + }; + + this.$onBreak = function() { + var _self = this; + if (!this.$debugger || this.$debugger.isRunning()) + return; + + this.$debugger.backtrace(this.$mdlStack, function() { + _self.dispatchEvent("break"); + }); + }; + + this.$onAfterCompile = function(e) { + var id = e.script.getAttribute("id"); + var oldNode = this.$mdlSources.queryNode("//file[@id='" + id + "']"); + if (oldNode) + this.$mdlSources.removeXml(oldNode); + this.$mdlSources.appendXml(e.script); + }; + + this.$onDetach = function() { + if (this.$debugger) { + this.$debugger.destroy(); + this.$debugger = null; + } + + this.$host = null; + + this.$mdlSources.load(""); + this.$mdlStack.load(""); + this.$stAttached.deactivate(); + this.setProperty("activeframe", null); + }; + + this.$onChangeFrame = function() { + if (!this.$ignoreFrameEvent) { + this.setProperty("activeframe", this.$debugger.getActiveFrame()); + } + }; + + this.changeFrame = function(frame) { + this.$debugger.setFrame(frame); + }; + + this.detach = function(callback) { + this.continueScript(); + if (this.$host) + this.$host.$detach(this.$debugger, callback); + else + this.$onDetach(); + }; + + this.$loadSources = function(callback) { + this.$debugger.scripts(this.$mdlSources, callback); + }; + + this.loadScript = function(script, callback) { + this.$debugger.loadScript(script, callback); + }; + + this.loadObjects = function(item, callback) { + this.$debugger.loadObjects(item, callback); + }; + + this.loadFrame = function(frame, callback) { + this.$debugger.loadFrame(frame, callback); + }; + + this.toggleBreakpoint = function(script, row) { + var model = this.$mdlBreakpoints; + if (this.$debugger) + this.$debugger.toggleBreakpoint(script, row, model); + else { + var scriptName = script.getAttribute("scriptname"); + var bp = model.queryNode("breakpoint[@script='" + scriptName + "' and @line='" + row + "']"); + if (bp) + model.removeXml(bp) + else { + var bp = apf.n("") + .attr("script", scriptName) + .attr("line", row) + .attr("text", script.getAttribute("path") + ":" + row) + .attr("lineoffset", 0) + .node(); + model.appendXml(bp); + } + } + }; + + this.continueScript = function(callback) { + this.dispatchEvent("beforecontinue"); + + if (this.$debugger) + this.$debugger.continueScript(callback); + else + callback && callback(); + }; + + this.stepInto = function() { + this.dispatchEvent("beforecontinue"); + + this.$debugger && this.$debugger.stepInto(); + }; + + this.stepNext = function() { + this.dispatchEvent("beforecontinue"); + + this.$debugger && this.$debugger.stepNext(); + }; + + this.stepOut = function() { + this.dispatchEvent("beforecontinue"); + + this.$debugger && this.$debugger.stepOut(); + }; + + this.suspend = function() { + this.$debugger && this.$debugger.suspend(); + }; + + this.evaluate = function(expression, frame, global, disableBreak, callback){ + this.$debugger && this.$debugger.evaluate(expression, frame, global, disableBreak, callback); + }; + + this.changeLive = function(scriptId, newSource, previewOnly, callback) { + this.$debugger && this.$debugger.changeLive(scriptId, newSource, previewOnly, callback); + }; + +}).call(apf.dbg.prototype = new apf.AmlElement()); + +apf.aml.setElement("debugger", apf.dbg); + + +window.adbg = { + exec : function(method, args, callback, options) { + if (method == "loadScript") { + var dbg = args[0]; + var script = args[1]; + dbg.loadScript(script, function(source) { + if (options && options.callback) { + options.callback(apf.escapeXML(source), apf.SUCCESS); + } else { +// callback("" + apf.escapeXML(source) + "", apf.SUCCESS); + //TODO: ugly text() bug workaround + callback("", "]] >") + "]]>", apf.SUCCESS); + } + }); + } + else if (method == "loadObjects") { + var dbg = args[0]; + var item = args[1]; + + dbg.loadObjects(item, function(xml) { + if (options && options.callback) { + options.callback(xml, apf.SUCCESS); + } else { + callback(xml, apf.SUCCESS); + } + }); + } + else if (method == "loadFrame") { + var dbg = args[0]; + var frame = args[1]; + + dbg.loadFrame(frame, function(xml) { + if (options && options.callback) { + options.callback(xml, apf.SUCCESS); + } else { + callback(xml, apf.SUCCESS); + } + }); + } + } + }; +(apf.$asyncObjects || (apf.$asyncObjects = {}))["adbg"] = 1; + +return apf.dbg; +}); + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/elements/debughost.js)SIZE(4767)TIME(Mon, 10 Jan 2011 10:53:12 GMT)*/ + +if (apf.hasRequireJS) require.def("apf/elements/debughost", + ["apf/elements/dbg/chromedebughost", + "apf/elements/dbg/v8debughost", + "apf/elements/dbg/v8websocketdebughost"], + function(ChromeDebugHost, V8DebugHost, V8WebSocketDebugHost) { + +apf.debughost = function(struct, tagName){ + this.$init(tagName || "debughost", apf.NODE_HIDDEN, struct); +}; + +(function(){ + + this.port = 9222; + this.server = "localhost"; + this.type = "chrome"; + this.autoinit = false; + this.$modelTabs = null; + this.$stateConnected = null; + + this.$host = null; + + this.$booleanProperties["autostart"] = true; + + this.$supportedProperties.push("port", "server", "type", "autoinit", + "model-tabs", "state-connected", "strip"); + + this.$propHandlers["model-tabs"] = function(value) { + if (!value) return; + + this.$modelTabs = apf.nameserver.get("model", value) || + apf.setReference(value, apf.nameserver.register("model", value, new apf.model())); + + // set the root node for this model + this.$modelTabs.id = this.$modelTabs.name = value; + this.$modelTabs.load(""); + + }; + + this.$propHandlers["state-connected"] = function(value) { + if (!value) return; + + this.$stateConnected = apf.nameserver.get("state", value) || + apf.setReference(value, apf.nameserver.register("state", value, new apf.state())); + + // set the root node for this model + this.$stateConnected.id = this.$stateConnected.name = value; + this.$stateConnected.deactivate(); + + }; + + this.init = function() { + if (this.$host) { + return; + } + + if (this.type == "chrome" || this.type == "v8" || this.type == "v8-ws") { + if (!apf.debughost.$o3obj && this.type !== "v8-ws") { + apf.debughost.$o3obj = window.o3Obj || o3.create("8A66ECAC-63FD-4AFA-9D42-3034D18C88F4", { + oninstallprompt: function() { alert("can't find o3 plugin"); }, + product: "O3Demo" + }); + } + + if (this.type == "chrome") { + this.$host = new ChromeDebugHost(this.server, this.port, apf.debughost.$o3obj); + } else if (this.type == "v8") { + this.$host = new V8DebugHost(this.server, this.port, apf.debughost.$o3obj); + } else if (this.type == "v8-ws") { + var socket = this.dispatchEvent("socketfind"); + if (!socket) + throw new Error("no socket found!") + this.$host = new V8WebSocketDebugHost(socket); + } else if (this.type == "chrome-ws") { + var socket = this.dispatchEvent("socketfind"); + if (!socket) + throw new Error("no socket found!") + this.$host = new ChromeDebugHost(null, null, null, socket); + } + + var self = this; + this.$host.addEventListener("connect", function() { + self.dispatchEvent("connect"); + self.$stateConnected.activate(); + }); + this.$host.addEventListener("disconnect", function() { + self.dispatchEvent("disconnect"); + self.$stateConnected.deactivate(); + }); + + this.loadTabs(); + } + }; + + this.loadTabs = function() { + if (!this.$host) + this.init(); + + var self = this; + this.$host.loadTabs(this.$modelTabs, function() { + self.$dispatchEvent("tabsloaded"); + }); + } + + this.addEventListener("DOMNodeInsertedIntoDocument", function(e) { + if (this.autoinit) + this.init(); + }); + + this.$attach = function(dbg, tab, callback) { + if (!this.$host) + this.init(); + + if (tab) { + var id = tab.getAttribute("id"); + } else { + var id = null; + } + + var _self = this; + this.$host.attach(id, function(err, dbg) { + dbg.setStrip(_self.strip || ""); + callback(err, dbg); + }); + }; + + this.$detach = function(dbgImpl, callback) { + if (!this.$host) + return; + + this.$host.detach(dbgImpl, callback); + }; + + this.disconnect = function() { + if (!this.$host) + return; + + this.$host.disconnect(); + this.$host = null; + }; + +}).call(apf.debughost.prototype = new apf.AmlElement()); + +apf.aml.setElement("debughost", apf.debughost); +return apf.debughost; + +}); + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/elements/defaults.js)SIZE(1838)TIME(Mon, 10 Jan 2011 10:53:12 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + + +/** + * + * @author Ruben Daniels (ruben AT ajax DOT org) + * @version %I%, %G% + * @since 0.4 + */ +apf.defaults = function(struct, tagName){ + this.$init(tagName || "services", apf.NODE_HIDDEN, struct); +}; + +(function(){ + this.$parsePrio = "002"; + + this.$propHandlers["for"] = function(value){ + if (this.$lastFor) + apf.nameserver.remove("defaults_" + this.$lastFor, this); + + apf.nameserver.add("defaults_" + value, this); + this.$lastFor = value; + } + + //@todo apf3.x how should this work? + this.addEventListener("DOMNodeRemovedFromDocument", function(e){ + apf.nameserver.remove("defaults_" + this.$lastFor, this); + }); + +}).call(apf.defaults.prototype = new apf.AmlElement()); + +apf.aml.setElement("defaults", apf.defaults); + + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/elements/divider.js)SIZE(2918)TIME(Mon, 10 Jan 2011 10:53:12 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + +/** + * Element displaying a divider. For use in toolbars, menu's and such. + * @define divider + * @constructor + */ +apf.divider = function(struct, tagName){ + this.$init(tagName || "divider", apf.NODE_VISIBLE, struct); +}; + +(function() { + this.$focussable = false; + + this.implement(apf.ChildValue); + this.$childProperty = "caption"; + + //@todo apf3.0 fix this + this.addEventListener("AMLReparent", function(beforeNode, pNode, withinParent){ + if (!this.$amlLoaded) + return; + + if (!withinParent && this.skinName != pNode.skinName) { + //@todo for now, assuming dom garbage collection doesn't leak + this.loadAml(); + } + }); + + /** + * @attribute {String} caption the text displayed in the area defined by this + * element. + */ + this.$supportedProperties.push("caption", "value", "for", "textalign"); + this.$propHandlers["caption"] = function(value){ + if (this.$caption) { + this.$setStyleClass(this.$ext, this.$baseCSSname + "Caption"); + this.$caption.innerHTML = value; + } + else { + this.$setStyleClass(this.$ext, "", [this.$baseCSSname + "Caption"]); + } + }; + + this.$canLeechSkin = true; + + /** + * @private + */ + this.$draw = function() { + if (this.parentNode.isPaged && this.parentNode.$buttons) + this.$pHtmlNode = this.parentNode.$buttons; + + if (this.$isLeechingSkin) { + this.$ext = apf.insertHtmlNode( + this.parentNode.$getLayoutNode("divider"), this.$pHtmlNode); + } + else { + this.$ext = this.$getExternal("main"); + this.$caption = this.$getLayoutNode("main", "caption", this.$ext); + } + }; +}).call(apf.divider.prototype = new apf.Presentation); + +apf.aml.setElement("divider", apf.divider); + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/elements/dropdown.js)SIZE(15075)TIME(Mon, 10 Jan 2011 10:53:12 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + + +/** + * Element allowing a user to select a value from a list, which is + * displayed when the user clicks a button. + * Example: + * A simple dropdown with inline items. + * + * + * The Netherlands + * United States of America + * United Kingdom + * ... + * + * + * Example: + * A databound dropdown with items loaded from an xml file. + * + * + * + * Example: + * A databound dropdown using the bindings element + * + * + * + * + * + * + * + * + * + * Example: + * A small form. + * + * + * + * Mike + * amsterdam + * + * + * + * + * Name + * + * + * City + * + * + * + * + * + * + * + * + * Submit + * + * + * + * @event slidedown Fires when the calendar slides open. + * cancelable: Prevents the calendar from sliding open + * @event slideup Fires when the calendar slides up. + * cancelable: Prevents the calendar from sliding up + * + * @constructor + * @define dropdown + * @allowchild item, {smartbinding} + * @addnode elements + * + * @inherits apf.BaseList + * + * @author Ruben Daniels (ruben AT ajax DOT org) + * @version %I%, %G% + * @since 0.4 + */ +apf.dropdown = function(struct, tagName){ + this.$init(tagName || "dropdown", apf.NODE_VISIBLE, struct); +}; + +(function(){ + this.$animType = 1; + this.$animSteps = 5; + this.$animSpeed = 20; + this.$itemSelectEvent = "onmouseup"; + + /**** Properties and Attributes ****/ + + this.dragdrop = false; + this.reselectable = true; + this.$focussable = apf.KEYBOARD; + this.autoselect = false; + this.multiselect = false; + this.disableremove = true; + this.delayedselect = false; + this.maxitems = 5; + + this.$booleanProperties["disableremove"] = true; + this.$supportedProperties.push("maxitems", "disableremove", + "initial-message", "fill"); + + /** + * @attribute {String} initial-message the message displayed by this element + * when it doesn't have a value set. This property is inherited from parent + * nodes. When none is found it is looked for on the appsettings element. + * + * @attribute {Number} maxitems the number of items that are shown at the + * same time in the container. + */ + this.$propHandlers["maxitems"] = function(value){ + this.sliderHeight = value + ? (Math.min(this.maxitems || 100, value) * this.itemHeight) + : 10; + this.containerHeight = value + ? (Math.min(this.maxitems || 100, value) * this.itemHeight) + : 10; + /*if (this.containerHeight > 20) + this.containerHeight = Math.ceil(this.containerHeight * 0.9);*/ + }; + + /**** Public methods ****/ + + /** + * Toggles the visibility of the container with the list elements. It opens + * or closes it using a slide effect. + * @private + */ + this.slideToggle = function(e, userAction){ + if (!e) e = event; + if (userAction && this.disabled) + return; + + if (this.isOpen) + this.slideUp(); + else + this.slideDown(e); + }; + + /** + * Shows the container with the list elements using a slide effect. + * @private + */ + this.slideDown = function(e){ + if (this.dispatchEvent("slidedown") === false) + return false; + + this.isOpen = true; + + this.$propHandlers["maxitems"].call(this, this.xmlRoot && this.each + ? this.getTraverseNodes().length : this.childNodes.length); //@todo apf3.0 count element nodes + + this.oSlider.style.display = "block"; + if (!this.ignoreOverflow) { + this.oSlider.style[apf.supportOverflowComponent + ? "overflowY" + : "overflow"] = "visible"; + this.$container.style.overflowY = "hidden"; + } + + this.oSlider.style.display = ""; + + this.$setStyleClass(this.$ext, this.$baseCSSname + "Down"); + + //var pos = apf.getAbsolutePosition(this.$ext); + this.oSlider.style.height = (this.sliderHeight - 1) + "px"; + this.oSlider.style.width = (this.$ext.offsetWidth - 2 - this.widthdiff) + "px"; + + var _self = this; + + apf.popup.show(this.$uniqueId, { + x : 0, + y : this.$ext.offsetHeight, + animate : true, + container : this.$getLayoutNode("container", "contents", this.oSlider), + ref : this.$ext, + width : this.$ext.offsetWidth - this.widthdiff, + height : this.containerHeight, + callback: function(container){ + if (!_self.ignoreOverflow) { + _self.$container.style.overflowY = "auto"; + } + } + }); + }; + + /** + * Hides the container with the list elements using a slide effect. + * @private + */ + this.slideUp = function(){ + if (this.isOpen == 2) return false; + if (this.dispatchEvent("slideup") === false) return false; + + this.isOpen = false; + if (this.selected) { + var htmlNode = apf.xmldb.findHtmlNode(this.selected, this); + if(htmlNode) this.$setStyleClass(htmlNode, '', ["hover"]); + } + + this.$setStyleClass(this.$ext, '', [this.$baseCSSname + "Down"]); + apf.popup.hide(); + return false; + }; + + /**** Private methods and event handlers ****/ + + //@todo apf3.0 why is this function called 6 times on init. + this.$setLabel = function(value){ + + this.oLabel.innerHTML = value || this["initial-message"] || ""; + + + this.$setStyleClass(this.$ext, value ? "" : this.$baseCSSname + "Initial", + !value ? [] : [this.$baseCSSname + "Initial"]); + }; + + this.addEventListener("afterselect", function(e){ + if (!e) e = event; + + this.slideUp(); + if (!this.isOpen) + this.$setStyleClass(this.$ext, "", [this.$baseCSSname + "Over"]); + + this.$setLabel(e.selection.length + ? this.$applyBindRule("caption", this.selected) + : ""); + }); + + function setMaxCount() { + if (this.isOpen == 2) + this.slideDown(); + } + + this.addEventListener("afterload", setMaxCount); + this.addEventListener("xmlupdate", function(){ + setMaxCount.call(this); + this.$setLabel(this.$applyBindRule("caption", this.selected)); + }); + + // Private functions + this.$blur = function(){ + this.slideUp(); + //this.$ext.dispatchEvent("mouseout") + if (!this.isOpen) + this.$setStyleClass(this.$ext, "", [this.$baseCSSname + "Over"]) + + this.$setStyleClass(this.$ext, "", [this.$baseCSSname + "Focus"]); + }; + + /*this.$focus = function(){ + apf.popup.forceHide(); + this.$setStyleClass(this.oFocus || this.$ext, this.$baseCSSname + "Focus"); + }*/ + + this.$setClearMessage = function(msg){ + this.$setLabel(msg); + }; + + this.$removeClearMessage = function(){ + this.$setLabel(""); + }; + + this.addEventListener("popuphide", this.slideUp); + + /**** Keyboard Support ****/ + + + this.addEventListener("keydown", function(e){ + var key = e.keyCode; + //var ctrlKey = e.ctrlKey; << unused + //var shiftKey = e.shiftKey; + + if (!this.xmlRoot) return; + + var node; + + switch (key) { + case 32: + this.slideToggle(e.htmlEvent); + break; + case 38: + //UP + if (e.altKey) { + this.slideToggle(e.htmlEvent); + return; + } + + if (!this.selected) + return; + + node = this.getNextTraverseSelected(this.caret + || this.selected, false); + + if (node) + this.select(node); + break; + case 40: + //DOWN + if (e.altKey) { + this.slideToggle(e.htmlEvent); + return; + } + + if (!this.selected) { + node = this.getFirstTraverseNode(); + if (!node) + return; + } + else + node = this.getNextTraverseSelected(this.selected, true); + + if (node) + this.select(node); + + break; + default: + if (key == 9 || !this.xmlRoot) return; + + //if(key > 64 && key < + if (!this.lookup || new Date().getTime() - this.lookup.date.getTime() > 1000) + this.lookup = { + str : "", + date : new Date() + }; + + this.lookup.str += String.fromCharCode(key); + + var caption, nodes = this.getTraverseNodes(); + for (var i = 0; i < nodes.length; i++) { + caption = this.$applyBindRule("caption", nodes[i]); + if (caption && caption.indexOf(this.lookup.str) > -1) { + this.select(nodes[i]); + return; + } + } + return; + } + + return false; + }, true); + + + /**** Init ****/ + + this.$draw = function(){ + this.$getNewContext("main"); + this.$getNewContext("container"); + + this.$animType = this.$getOption("main", "animtype") || 1; + this.clickOpen = this.$getOption("main", "clickopen") || "button"; + + //Build Main Skin + this.$ext = this.$getExternal(null, null, function(oExt){ + oExt.setAttribute("onmouseover", 'var o = apf.lookup(' + this.$uniqueId + + ');o.$setStyleClass(o.$ext, o.$baseCSSname + "Over", null, true);'); + oExt.setAttribute("onmouseout", 'var o = apf.lookup(' + this.$uniqueId + + ');if(o.isOpen) return;o.$setStyleClass(o.$ext, "", [o.$baseCSSname + "Over"], true);'); + + //Button + var oButton = this.$getLayoutNode("main", "button", oExt); + if (oButton) { + oButton.setAttribute("onmousedown", 'apf.lookup(' + + this.$uniqueId + ').slideToggle(event, true);'); + } + + //Label + var oLabel = this.$getLayoutNode("main", "label", oExt); + if (this.clickOpen == "both") { + oLabel.parentNode.setAttribute("onmousedown", 'apf.lookup(' + + this.$uniqueId + ').slideToggle(event, true);'); + } + }); + this.oLabel = this.$getLayoutNode("main", "label", this.$ext); + + + if (this.oLabel.nodeType == 3) + this.oLabel = this.oLabel.parentNode; + + + this.oIcon = this.$getLayoutNode("main", "icon", this.$ext); + if (this.$button) + this.$button = this.$getLayoutNode("main", "button", this.$ext); + + this.oSlider = apf.insertHtmlNode(this.$getLayoutNode("container"), + document.body); + this.$container = this.$getLayoutNode("container", "contents", this.oSlider); + this.$container.host = this; + + //Set up the popup + this.$pHtmlDoc = apf.popup.setContent(this.$uniqueId, this.oSlider, + apf.skins.getCssString(this.skinName)); + + //Get Options form skin + //Types: 1=One dimensional List, 2=Two dimensional List + this.listtype = parseInt(this.$getLayoutNode("main", "type")) || 1; + + this.itemHeight = this.$getOption("main", "item-height") || 18.5; + this.widthdiff = this.$getOption("main", "width-diff") || 0; + this.ignoreOverflow = apf.isTrue(this.$getOption("main", "ignore-overflow")) || false; + }; + + this.addEventListener("DOMNodeInsertedIntoDocument", function(){ + if (typeof this["initial-message"] == "undefined") + this.$setInheritedAttribute("initial-message"); + + if (!this.selected && this["initial-message"]) + this.$setLabel(); + }); + + this.$destroy = function(){ + apf.popup.removeContent(this.$uniqueId); + apf.destroyHtmlNode(this.oSlider); + this.oSlider = null; + }; + + +}).call(apf.dropdown.prototype = new apf.BaseList()); + +apf.config.$inheritProperties["initial-message"] = 1; + +apf.aml.setElement("dropdown", apf.dropdown); + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/elements/editor.js)SIZE(18601)TIME(Mon, 10 Jan 2011 10:53:12 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + +/** + * Element displaying a Rich Text Editor, like M$ Office Word in a browser + * window. Even though this Editor does not offer the same amount of features + * as Word, we did try to make it behave that way, simply because it is + * considered to be the market leader among word-processors. + * Example: + * + * + * Default value... + * + * + * + * @constructor + * @addnode elements:editor + * + * @author Mike de Boer + * @version %I%, %G% + * @since 1.0 + * + * @inherits apf.XForms + * @inherits apf.StandardBinding + * + * @binding value Determines the way the value for the element is retrieved + * from the bound data. + * Example: + * Sets the text based on data loaded into this component. + * + * + * + * Some text + * + * + * + * + */ +apf.editor = function(struct, tagName){ + this.$init(tagName || "editor", apf.NODE_VISIBLE, struct); +}; + +(function() { + this.implement( + apf.LiveEdit + + ); + + this.value = ""; + this.$value = ""; + + this.$oWin = + this.$oBookmark = + this.$fTimer = null; + + /**** Properties and Attributes ****/ + + this.$isTextInput = true; + + this.$booleanProperties["scalewithiframe"] = true; + this.$supportedProperties.push("value", "characterset", "scalewithiframe"); + + this.$propHandlers["value"] = function(html){ + if (typeof html != "string")// || html == "" + html = "";//apf.isIE ? "
    " : + + // If the HTML string is the same as the contents of the iframe document, + // don't do anything... + if (this.$value.replace(/\r/g, "") == html) + return; + + this.$value = html; + + + html = html.replace(/]*>/gi, "").replace(/<\/p>/gi, + "

    "); + + html = apf.htmlCleaner.prepare(html); + + + if (this.$pluginsActive == "code") { + this.$plugins["code"].update(this, html); + } + else if (this.$activeDocument) { + this.$activeDocument.body.innerHTML = html; + this.$controlAgentBehavior(this.$activeDocument.body); + } + + if (this.scalewithiframe) + rescale.call(this); + + this.dispatchEvent("sethtml", {editor: this}); + }; + + var rescaleTimer; + function rescale(timeout) { + clearTimeout(rescaleTimer); + var _self = this; + rescaleTimer = $setTimeout(function() { + var h, w, + el = _self.$activeDocument && _self.$activeDocument.body; + if (!el || !(h = el.scrollHeight) || !(w = el.scrollWidth)) + return; + _self.$resize({/*width: w, */height: h}); + }, timeout || 0); + } + + this.addEventListener("execcommand", function(e) { + if (this.scalewithiframe) + rescale.call(this); + }); + + /** + * Important function; tells the right iframe element that it may be + * edited by the user. + * + * @type void + */ + this.makeEditable = function() { + var _self = this; + + if (apf.isIE) { + $setTimeout(function() { + _self.$activeDocument.body.contentEditable = true; + }); + } + else { + try { + this.$activeDocument.designMode = "on"; + if (apf.isGecko) { + // Tell Gecko (Firefox 1.5+) to enable or not live resizing of objects + this.$activeDocument.execCommand("enableObjectResizing", + false, this.imagehandles); + // Disable the standard table editing features of Firefox. + this.$activeDocument.execCommand("enableInlineTableEditing", + false, this.tablehandles); + } + } + catch (e) {} + } + + //this.$propHandlers["value"].call(this, ""); + this.dispatchEvent("complete", {editor: this}); + }; + + + + /** + * processes the current state of the editor's content and outputs the result that + * can be used inside any other content or stored elsewhere. + * + * @return The string of (X)HTML that is inside the editor. + * @type {String} + */ + this.getValue = function(bStrict) { + if (!this.$activeDocument) + return ""; + + return (this.$value = apf.htmlCleaner.parse( + this.$activeDocument.body.innerHTML, bStrict)); + }; + + /** + * replace the (X)HTML that's inside the Editor with something else + * + * @param {String} html + * @type {void} + */ + this.setValue = function(value){ + return this.setProperty("value", value, false, true); + }; + + + + /** + * Invoked by the Databinding layer when a model is reset/ cleared. + * + * @type {void} + */ + this.addEventListener("$clear", function(e) { + if (!e.nomsg) { + this.value = ""; + return this.$propHandlers["value"].call(this, ""); + } + }); + + var clickTimer; + + /** + * Event handler; fired when the user clicked inside the editable area. + * + * @see object.abstractevent + * @param {Event} e + * @type void + * @private + */ + function onClick(e) { + clearTimeout(clickTimer); + if (this.$oBookmark && apf.isGecko) { + var oNewBm = _self.$selection.getBookmark(); + if (typeof oNewBm.start == "undefined" && typeof oNewBm.end == "undefined") { + //this.$selection.moveToBookmark(this.$oBookmark); + //RAAAAAAAAAAH stoopid firefox, work with me here!! + } + } + + var which = e.which, + button = e.button, + _self = this; + clickTimer = $setTimeout(function() { + var rClick = ((which == 3) || (button == 2)); + + if (apf.document.activeElement != this) { + //this.$visualFocus(true); + _self.focus({}); + } + + else + if (!rClick) + _self.$focus({}); + }); + + //apf.stopEvent(e); // @todo why was here a stopEvent?? + } + + /** + * Event handler; fired when the user right clicked inside the editable area + * + * @param {Event} e + * @type {void} + * @private + */ + function onContextmenu(e) { + if (this.state == apf.DISABLED) return; + //if (apf.isIE) + // this.$visualFocus(true); + this.$notifyAllPlugins("context", e); + } + + /**** Focus Handling ****/ + + /** + * Fix for focus handling to mix 'n match nicely with other APF elements + * + * @param {Event} e + * @type {void} + */ + this.$focus = function(e){ + if (!this.$ext || this.$ext.disabled) + return; + clearInterval(this.$fTimer); + + this.setProperty("state", (this.$pluginsActive == "code") + ? apf.DISABLED + : apf.OFF); + + this.$setStyleClass(this.$ext, this.$baseCSSname + "Focus"); + + var _self = this; + + this.$fTimer = setInterval(function delay(){ + try { + if (!_self.$fTimer || document.activeElement != _self.$ext) { + _self.$visualFocus(true); + clearInterval(_self.$fTimer); + } + else { + clearInterval(_self.$fTimer); + return; + } + } + catch(e) {} + }, 1); + }; + + /** + * Probe whether we should apply a focus correction to the editor at any + * given interval + * + * @param {Event} e + * @type {Boolean} + */ + this.$isTextInput = function(e){ + return apf.isChildOf(this.$activeDocument, e.srcElement, true); + }; + + /** + * Fix for focus/ blur handling to mix 'n match nicely with other APF + * elements + * + * @param {Event} e + * @type {void} + */ + this.$blur = function(e){ + if (!this.$ext) + return; + + var pParent = apf.popup.last && apf.lookup(apf.popup.last); + if (pParent && pParent.editor == this) + apf.popup.forceHide(); + + this.$setStyleClass(this.$ext, "", [this.$baseCSSname + "Focus"]); + + var bCode = (this.$pluginsActive == "code"); + if (!this.realtime || bCode) { + this.change(bCode + ? this.$plugins["code"].getValue() + : this.getValue()); + } + + this.setProperty("state", apf.DISABLED); + }; + + /** + * Add various event handlers to a Editor object. + * + * @type {void} + */ + this.$addListeners = function() { + var _self = this; + apf.addListener(this.$activeDocument, "mouseup", onClick.bindWithEvent(this)); + //apf.addListener(this.$activeDocument, 'select', onClick.bindWithEvent(this)); + apf.addListener(this.$activeDocument, "keyup", function(e) { + e = e || window.event; + if (_self.scalewithiframe) + rescale.call(_self); + apf.document.activeElement = _self; + //e.amlNode = _self; + apf.window.$keyup(e); + }); + apf.addListener(this.$activeDocument, "keydown", function(e) { + apf.document.activeElement = _self; + //e.amlNode = _self; + apf.window.$keydown(e); + }); + apf.addListener(this.$activeDocument, "mousedown", function(e){ + e = e || window.event; + _self.$selection.cache(); + + apf.popup.forceHide(); + + apf.window.$mousedown(e); + }); + + var scrollHandler = function(e){ + if (!e) e = event; + apf.window.$mousewheel.call(window, {target: _self.$ext, wheelDelta: e.wheelDelta, detail: e.detail}); + } + if (this.$oWin && this.$oWin.document.addEventListener) + this.$oWin.document.addEventListener("DOMMouseScroll", scrollHandler, false); + window.onmousewheel = document.onmousewheel = scrollHandler; + + apf.addListener(this.$activeDocument, "contextmenu", onContextmenu.bindWithEvent(this)); + + apf.addListener(this.$activeDocument, "focus", apf.window.$focusevent); + apf.addListener(this.$activeDocument, "blur", apf.window.$blurevent); + + this.$activeDocument.host = this; + + apf.addListener(this.$activeDocument.body, "paste", function(e) { + e = e || window.event; + _self.$paste(e); + $setTimeout(function() { + if (_self.scalewithiframe) + rescale.call(_self); + }); + }); + }; + + //this.addEventListener("contextmenu", onContextmenu); + + /**** Button Handling ****/ + + /** + * Draw all the HTML elements at startup time. + * + * @type {void} + */ + this.$draw = function() { + this.$editable(function() { + //this.plugins = new apf.editor.plugins(this.$plugins, this); + var oEditor = this.$getLayoutNode("main", "editor", this.$ext), + _self = this; + + this.iframe = document.createElement("iframe"); + this.iframe.setAttribute("frameborder", "0"); + this.iframe.setAttribute("border", "0"); + this.iframe.setAttribute("scrolling", "no"); + this.iframe.setAttribute("marginwidth", "0"); + this.iframe.setAttribute("marginheight", "0"); + oEditor.appendChild(this.iframe); + this.$oWin = this.iframe.contentWindow; + this.$activeDocument = this.$oWin.document; + + this.$selection = new apf.selection(this.$oWin, + this.$activeDocument, this); + + // get the document style (CSS) from the skin: + // see: apf.presentation.getCssString(), where the following statement + // is derived from. + var sCss = apf.queryValue($xmlns(apf.skins.skins[ + this.skinName.split(":")[0]].xml, "docstyle", apf.ns.aml)[0], + "text()"); + if (!sCss) { + sCss = "\ + html {\ + cursor: text;\ + border: 0;\ + }\ + body {\ + margin: 8px;\ + padding: 0;\ + border: 0;\ + color: #000;\ + font-family: Verdana,Arial,Helvetica,sans-serif;\ + font-size: 10pt;\ + background: #fff;\ + word-wrap: break-word;\ + }\ + .itemAnchor {\ + background:url(images/editor/items.gif) no-repeat left bottom;\ + line-height:6px;\ + overflow:hidden;\ + padding-left:12px;\ + width:12px;\ + }\ + .visualAid table,\ + .visualAid table td {\ + border: 1px dashed #bbb;\ + }\ + .visualAid table td {\ + margin: 8px;\ + }\ + h1 {\ + margin : 15px 0 15px 0;\ + }\ + p {\ + margin: 0;\ + padding: 0;\ + }\ + sub, sup {\ + line-height: 10px;\ + }"; + } + var c = this.getAttribute("characterset") + || this.getAttribute("charset") || apf.characterSet; + this.$activeDocument.open(); + this.$activeDocument.write('\ + \ + \ + \ + \ + \ + \ + \ + '); + this.$activeDocument.close(); + + + if (apf.hasFocusBug) + apf.sanitizeTextbox(this.$activeDocument.body); + + + + // setup layout rules: + //@todo add this to $destroy + /*apf.layout.setRules(this.$ext, this.$uniqueId + "_editor", + "var o = apf.all[" + this.$uniqueId + "];if (o) o.$resize()"); + apf.layout.queue(this.$ext);*/ + + + this.addEventListener("resize", this.$resize); + + this.$addListeners(); + + // do the magic, make the editor editable. + this.makeEditable(); + + $setTimeout(function() { + _self.setProperty("state", apf.DISABLED); + }); + }); + }; + + /** + * Takes care of setting the proper size of the editor after a resize event + * was fired through the APF layout manager + * @see object.layout + * + * @type {void} + */ + this.$resize = function(oSize) { + if (!this.iframe || !this.iframe.parentNode || !this.$ext.offsetHeight) + return; + + if (this.$container) { + var h = (this.$ext.offsetHeight - this.$toolbar.offsetHeight - 2); + if (!h || h < 0) + h = 0; + this.$container.style.height = h + "px"; + } + + if (this.scalewithiframe && oSize) { + if (oSize.width) + this.iframe.style.width = oSize.width + "px"; + if (oSize.height) + this.iframe.style.height = oSize.height + "px"; + } + + //TODO: check if any buttons from the toolbar became invisible/ visible again... + this.$notifyAllPlugins("resize"); + + if (this.$pluginsActive == "code") + this.$plugins["code"].setSize(this); + }; + + /** + * Parse the block of AML that constructed this editor instance for arguments + * like width, height, etc. + * + * @param {XMLRootElement} x + * @type {void} + */ + this.addEventListener("DOMNodeInsertedIntoDocument", function(){ + this.$container = this.$getLayoutNode("main", "container", this.$ext); + + if (apf.isOnlyChild(this.firstChild, [3,4])) + this.$handlePropSet("value", this.firstChild.nodeValue.trim()); + + //if (typeof this.realtime == "undefined") + //this.$propHandlers["realtime"].call(this); + if (this.scalewithiframe) + rescale.call(this, 2000); + }); + + this.$destroy = function() { + this.$selection.$destroy(); + this.$selection = this.$activeDocument.host = this.oToobar = + this.$activeDocument = this.$oWin = this.iframe = null; + }; + +}).call(apf.editor.prototype = new apf.StandardBinding()); + + +apf.aml.setElement("editor", apf.editor); + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/elements/errorbox.js)SIZE(6106)TIME(Tue, 15 Feb 2011 10:52:21 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + + +/** + * Element showing an error message when the attached element + * is in erroneous state and has the invalidmsg="" attribute specified. + * In most cases the errorbox element is implicit and will be created + * automatically. + * Example: + * + * + * Invalid e-mail address entered. + * + * + * Remarks: + * In most cases the errorbox element is not created directly but implicitly + * by a validationgroup. An element that goes into an error state will + * show the errorbox. + * + * + * Phone number + * + * + * Password + * + * Validate + * + * + * + * To check if the element has valid information entered, leaving the textbox + * (focussing another element) will trigger a check. Programmatically a check + * can be done using the following code: + * + * txtPhone.validate(); + * + * //Or use the html5 syntax + * txtPhone.checkValidity(); + * + * + * To check for the entire group of elements use the validation group. For only + * the first non-valid element the errorbox is shown. That element also receives + * focus. + * + * vgForm.validate(); + * + * + * @constructor + * @define errorbox + * + * @allowchild {anyxhtml} + * @addnode elements + * + * @inherits apf.Presentation + * + * @author Ruben Daniels (ruben AT ajax DOT org) + * @version %I%, %G% + * @since 0.4 + */ +apf.errorbox = function(struct, tagName){ + this.$init(tagName || "errorbox", apf.NODE_VISIBLE, struct); +}; + +(function(){ + this.$positioning = "basic"; + this.display = function(host){ + this.host = host; + + var refHtml = + + host.validityState && host.validityState.$errorHtml || + + host.$ext; + + document.body.appendChild(this.$ext); + /*var pos = apf.getAbsolutePosition(refHtml, document.body); + + if (document != refHtml.ownerDocument) { + var pos2 = apf.getAbsolutePosition(refHtml.ownerDocument.parentWindow.frameElement, document.body); + pos[0] += pos2[0]; + pos[1] += pos2[1]; + }*/ + + var x = (parseFloat(host.$getOption && host.$getOption("main", "erroffsetx") || 0)), + y = (parseFloat(host.$getOption && host.$getOption("main", "erroffsety") || 0)); + //this.$ext.style.left = x + "px" + //this.$ext.style.top = y + "px" + + this.show(); + apf.popup.show(this.$uniqueId, { + x : x, + y : y, + animate : false, + ref : refHtml, + allowTogether: true + }); + + this.$setStyleClass(this.$ext, + x + this.$ext.offsetWidth > this.$ext.offsetParent.offsetWidth + ? "rightbox" + : "leftbox", ["leftbox", "rightbox"]); + }; + + /** + * Sets the message of the errorbox. + * @param {String} value + */ + this.setMessage = function(value){ + if (value && value.indexOf(";") > -1) { + value = value.split(";"); + value = "" + value.shift() + "" + value.join(";"); + } + this.$int.innerHTML = value || ""; + }; + + this.$draw = function(){ + //Build Main Skin + this.$ext = this.$getExternal(); + this.$int = this.$getLayoutNode("main", "container", this.$ext); + this.oClose = this.$getLayoutNode("main", "close", this.$ext); + + if (this.oClose) { + var _self = this; + this.oClose.onclick = function(){ + _self.hide(); + + if (apf.document.activeElement) + apf.document.activeElement.focus(true, {mouse:true}); + }; + } + + this.$ext.onmousedown = function(e){ + (e || event).cancelBubble = true; + + + if (apf.hasFocusBug) + apf.window.$focusfix(); + + } + + apf.popup.setContent(this.$uniqueId, this.$ext, "", null, null); + }; + + this.$loadAml = function(x){ + if (!apf.isTrue(this.getAttribute("visible"))) + this.hide(); + }; + + this.$destroy = function(){ + if (this.oClose) + this.oClose.onclick = null; + + this.$ext.onmousedown = null; + + apf.popup.removeContent(this.$uniqueId); + }; +}).call(apf.errorbox.prototype = new apf.Presentation()); + +apf.aml.setElement("errorbox", apf.errorbox); + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/elements/event.js)SIZE(2115)TIME(Mon, 10 Jan 2011 10:53:12 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + + +/** + * Displays a popup element with a message with optionally an icon at the + * position specified by the position attribute. After the timeout has passed + * the popup will dissapear automatically. When the mouse hovers over the popup + * it doesn't dissapear. + * + * @event click Fires when the user clicks on the representation of this event. + */ +apf.event = function(struct, tagName){ + this.$init(tagName || "event", apf.NODE_HIDDEN, struct); +}; + +(function() { + this.$hasInitedWhen = false; + + this.$booleanProperties["repeat"] = true; + this.$supportedProperties.push("when", "message", "icon", "repeat"); + + this.$propHandlers["when"] = function(value) { + if (this.$hasInitedWhen && value && this.parentNode && this.parentNode.popup) { + var _self = this; + $setTimeout(function() { + _self.parentNode.popup(_self.message, _self.icon, _self); + }); + } + this.$hasInitedWhen = true; + + if (this.repeat) + delete this.when; + }; + + this.$loadAml = function(x) {}; +}).call(apf.event.prototype = new apf.AmlElement()); + +apf.aml.setElement("event", apf.event); + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/elements/filler.js)SIZE(1385)TIME(Mon, 10 Jan 2011 10:53:12 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + +apf.filler = function(struct, tagName){ + this.$init(tagName || "filler", apf.NODE_VISIBLE, struct); +}; + +(function() { + this.$focussable = false; + this.flex = 1; + + this.$draw = function() { + this.$ext = this.$pHtmlNode.appendChild(this.$pHtmlNode.ownerDocument.createElement("div")); + }; +}).call(apf.filler.prototype = new apf.GuiElement()); + +apf.aml.setElement("filler", apf.filler); + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/elements/flashplayer.js)SIZE(4071)TIME(Mon, 10 Jan 2011 10:53:12 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + +/** + * Element displaying the contents of a .swf (adobe flash) file. + * + * @constructor + * @define flashplayer + * @allowchild {smartbinding} + * @addnode elements + * + * @inherits apf.BaseSimple + * + * @author Ruben Daniels (ruben AT ajax DOT org) + * @version %I%, %G% + * @since 0.9 + * + * @binding value Determines the way the value for the element is retrieved + * from the bound data. + * Example: + * Sets the flash source text based on data loaded into this component. + * + * + * + * + * + * + * + */ +apf.flashplayer = function(struct, tagName){ + this.$init(tagName || "flashplayer", apf.NODE_VISIBLE, struct); +}; + +(function(){ + this.implement( + + apf.DataAction, + + apf.StandardBinding + ); + + /**** Public methods ****/ + + + + /** + * @ref global#setValue + */ + this.setValue = function(value){ + this.setProperty("value", value, false, true); + }; + + + + /**** Properties and attributes ****/ + + this.$supportedProperties.push("value"); + this.$propHandlers["value"] = function(value){ + this.setSource(value); + }; + + /**** Init ****/ + + this.$draw = function(){ + //Build Main Skin + this.$ext = this.$pHtmlNode.appendChild(document.createElement("div")); + this.$ext.onclick = function(){this.host.dispatchEvent("click");} + + var src = this.getAttribute("src") || ""; + this.$ext.insertAdjacentHTML("beforeend", + '\ + \ + \ + \ + \ + \ + \ + \ + \ + \ + ') + }; + + this.$loadAml = function(x){ + }; +}).call(apf.flashplayer.prototype = new apf.GuiElement()); + +apf.aml.setElement("flashplayer", apf.flashplayer); + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/elements/flowchart.js)SIZE(50799)TIME(Mon, 10 Jan 2011 10:53:12 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + +/** + * Element with which you can add and remove graphical blocks and create + * connections between them. Block could be rotated, flipped, resized, + * renamed, locked and moved. + * + * Example: + * Flowchart component + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * @define flowchart + * @attribute {String} template the data instruction to load the xml for the + * template that defines all the elements which are available on the flowchart. + * + * @attribute {String} snap snap block to grid; Default is false + * Possible values: + * true Block is snap to grid; @see grid-width, @see grid-height + * false Block is not snap to grid; + * {Number} Block is snap to grid; Grid size is equal to this value + * + * @attribute {Number} grid-width horizontal grid size, Default is 48px + * @attribute {Number} grid-height vertical grid size, Default is 48px + * + * Example: + * A template describing a single capacitor element + * + * + * + * + * @binding lock immobilize block element on workarea, default is false. + * Possible values: + * false block element is not immobilized + * true block element is immobilized + * @binding fliph Determines whether to mirror the block over the horizontal axis, default is false + * Possible values: + * true block element is flipped + * false block element is not flipped + * @binding flipv Determines whether to mirror the block over the vertical axis, default is false + * Possible values: + * true block element is flipped + * false block element is not flipped + * @binding rotation the rotation in degrees clockwise, default is 0 + * Possible values: + * 0 0 degrees rotation + * 90 90 degrees rotation + * 180 180 degrees rotation + * 270 270 degrees rotation + * @binding id Determines unique name + * @binding image Determines path to background image file + * @binding width Determines horizontal size + * @binding height Determines vertical size + * @binding type Determines name of block with special abilities, which can be defined in separate file + * @binding zindex Determines z-index number + * @binding left Determines horizontal position + * @binding top Determines vertical position + * + * @binding connection Determines xml representation of connection element + * @binding ref Determines unique name of destination block which will be connected with source block + * @binding blockinput Determines input number of source block + * @binding blockoutput Determines input number of destination block + * @binding blocklabel Specifies a description of the block + * + * @constructor + * + * @inherits apf.DataAction + * @inherits apf.Cache + * @inherits apf.BaseList + * @inherits apf.Rename + * + * @author Lukasz Lipinski + * @version %I%, %G% + */ +apf.flowchart = function(struct, tagName){ + this.$init(tagName || "flowchart", apf.NODE_VISIBLE, struct); +}; + +(function() { + this.implement( + + apf.DataAction, + + + apf.Cache, + + apf.Rename + ); + + this.objCanvas = null; + this.nodes = []; + this.snap = false; + this.gridW = 48; + this.gridH = 48; + + this.$flowVars = { + lastBlockId : 0, + template : null, + //torename = null, + resizeManager : null, + + xmlBlocks : {}, + objBlocks : {}, + xmlConnections: {}, + connToPaint : [] + }; + + this.$init(function() { + var _self = this; + var onkeydown_ = function(e) { + e = (e || event); + + var key = e.keyCode, + ctrlKey = e.ctrlKey, + shiftKey = e.shiftKey, + sel = this.getSelection(), + value = this.snap + ? ((key == 37 || key == 39 + ? this.gridW + : (key == 38 || key == 40 + ? this.gridH + : 0)) * (ctrlKey + ? 2 + : (shiftKey + ? 3 + : 1))) + : (ctrlKey + ? 10 + : (shiftKey + ? 100 + : 1)), + disabled = _self.objCanvas.disableremove; + + if (!sel || disabled) + return; + + switch (key) { + case 37: + //Left Arrow + this.moveTo(sel, - value, 0); + break; + case 38: + //Top Arrow + this.moveTo(sel, 0, - value); + break; + case 39: + //Right Arrow + this.moveTo(sel, value, 0); + break; + case 40: + //Bottom Arrow + this.moveTo(sel, 0, value); + break; + case 46: + //Delete + _self.$flowVars.resizeManager.hide(); + + switch (_self.objCanvas.mode) { + case "normal": + //Removing Blocks + this.removeBlocks(sel); + break; + case "connection-change": + //Removing Connections + var connectionsToDelete = [], + connectors = _self.objCanvas.htmlConnectors; + for (var id in connectors) { + if (connectors[id].selected) { + connectionsToDelete.push(connectors[id].other.xmlNode); + connectors[id].destroy(); + } + } + _self.removeConnectors(connectionsToDelete); + _self.objCanvas.mode = "normal"; + break; + } + break; + } + + return false; + } + + + this.addEventListener("keydown", onkeydown_, true); + + apf.addEventListener("contextmenu", function() {return false;}); + + + function $afterRenameMode() { + _self.objCanvas.disableremove = false; + } + this.addEventListener("afterrename", $afterRenameMode); + this.addEventListener("stoprename", $afterRenameMode); + }); + + this.$propHandlers["snap"] = function(value) { + var isNumber = parseInt(value) > 0 ? true : false + + this.snap = this.objCanvas.snap = value == "true" || isNumber + ? true + : false; + + if (isNumber) { + this.gridW = this.objCanvas.gridW = parseInt(value); + this.gridH = this.objCanvas.gridH = parseInt(value); + } + } + + this.$propHandlers["grid-width"] = function(value) { + this.gridW = this.objCanvas.gridW = parseInt(value); + } + + this.$propHandlers["grid-height"] = function(value) { + this.gridH = this.objCanvas.gridH = parseInt(value); + } + + + this.$getCaptionElement = function() { + var objBlock = this.$flowVars.objBlocks[this.$applyBindRule("id", this.selected)]; + if (!objBlock) + return; + return objBlock.caption; + }; + + + this.$beforeRename = function(e, userAction) { + if (userAction && this.disabled) + return; + + e = e || event; + var target = e.srcElement || e.target; + this.$selectCaption(target); + + this.objCanvas.disableremove = true; + this.$deselectCaption(target); + this.startRename(); + + var rename_input = this.$pHtmlDoc.getElementById("txt_rename"); + this.$setStyleClass(rename_input, target.className); + + var c = rename_input; + if ((target.className || "").indexOf("inside") != -1) { + if (c.offsetHeight !== 0) { + + c.style.marginTop = + "-" + (Math.ceil(c.offsetHeight / 2)) + "px"; + } + } + + return false; + }; + + + this.$selectCaption = function(o) { + if (!o || o.nodeType != 1) return; + this.$setStyleClass(o, "selected"); + }; + + this.$deselectCaption = function(o) { + if (!o || o.nodeType != 1) return; + this.$setStyleClass(o, "", ["selected"]); + }; + + this.$select = function(o) { + if (!o) + return; + + var objBlock = apf.flow.isBlock(o); + + if (objBlock) { + if (this.$flowVars.resizeManager) { + var prop = objBlock.other; + + if (prop.lock == 1) + return; + + this.$flowVars.resizeManager.grab(o, { + scalex : prop.scalex, + scaley : prop.scaley, + scaleratio : prop.scaleratio, + dwidth : prop.dwidth, + dheight : prop.dheight, + ratio : prop.ratio, + snap : this.snap, + gridH : this.gridH, + gridW : this.gridW + }); + } + this.$setStyleClass(o, "selected"); + + if (objBlock.other.capPos !== "inside") + this.$selectCaption(objBlock.caption); + } + }; + + this.$deselect = function(o) { + if (!o) + return; + this.$setStyleClass(o, "", ["selected"]); + + var objBlock = apf.flow.isBlock(o); + if (!objBlock) return; //it might have been deleted recently... + this.$deselectCaption(objBlock.caption); + + this.$flowVars.resizeManager.hide(); + }; + + /* ******************************************************************** + PUBLIC METHODS + *********************************************************************/ + + /** + * Updates the position of a block to vector [x, y] and the XML it is bound + * to. It's possible to back to previous state with Undo/Redo. + * + * @param {Object} xmlNodeArray array with xml representations of blocks elements + * @param {Number} dl horizontal alteration + * @param {Number} dt vertical alteration + */ + this.moveTo = function(xmlNodeArray, dl, dt) { + if(!xmlNodeArray) return; + + if (!xmlNodeArray.length) + xmlNodeArray = [xmlNodeArray]; + + var changes = [], + setNames = ["top", "left"], + node, value, i, l, j; + for (i = 0, l = xmlNodeArray.length; i < l; i++) { + for (j = 0; j < setNames.length; j++) { + node = this.$getDataNode( + setNames[j], xmlNodeArray[i], this.$createModel); + value = (setNames[j] == "left" ? dl : dt) + + (parseInt(this.$applyBindRule(setNames[j], xmlNodeArray[i])) || 0); + + if (this.snap) { + var gridSize = setNames[j] == "top" ? this.gridH : this.gridW; + value = Math.round(value / gridSize) * gridSize; + } + + if (node) { + var atAction = node.nodeType == 1 || node.nodeType == 3 + || node.nodeType == 4 + ? "setTextNode" + : "setAttribute"; + var args = node.nodeType == 1 + ? [node, value] + : (node.nodeType == 3 || node.nodeType == 4 + ? [node.parentNode, value] + : [node.ownerElement || node.selectSingleNode(".."), + node.nodeName, value]); + changes.push({ + action: atAction, + args : args + }); + } + } + } + + this.$executeAction("multicall", changes, "moveTo", xmlNodeArray); + }; + + /** + * Set a new z-index of a block and the XML it is bound to. It's + * possible to back to previous state with Undo/Redo. + * + * @param {XMLElement} xmlNode xml representation of block element + * @param {Number} value new z-index number + */ + this.setZindex = function(xmlNode, value) { + this.$executeSingleValue("setzindex", "zindex", xmlNode, value); + }; + + /** + * Sets mode to canvas object. Modes adds new features. For example, + * if connection-change mode is active, deleting connections + * between blocks is possible. + * + * @private + * @param {String} mode Operations mode + * + * Possible values: + * normal all operations are allowed except operations from different modes + * connection-add it's possible to add new connection between blocks, all operations from "normal" mode its allowed + * connection-change it's possible to change existing connection, all operations from "normal" mode its allowed + */ + this.setMode = function(mode) { + this.objCanvas.setMode(mode); + }; + + /** + * Returns current mode type. Modes adds new features. For example, + * if connection-change mode is active, possible is deleting connections + * between blocks. + * + * @private + * @return {String} Operation mode + * + * Possible values: + * normal all operations are allowed except operations from different modes + * connection-add it's possible to add new connection between blocks, all operations from "normal" mode its allowed + * connection-change it's possible to change existing connection, all operations from "normal" mode its allowed + * + * + */ + this.getMode = function() { + return this.objCanvas.getMode(); + }; + + /** + * Immobilise block element on workarea. This is an action. + * It's possible to back to previous state with Undo/Redo. + * + * @param {XMLElement} xmlNode xml representation of block element + * @param {Boolean} value prohibit block move, default is false. + * Possible values: + * true block is locked + * false block is unlocked + */ + this.setLock = function(xmlNode, value) { + this.$executeSingleValue("setlock", "lock", xmlNode, String(value)); + }; + + /** + * Rotate block element. This is an action. It's possible to back to + * previous state with Undo/Redo + * + * @param {XMLElement} xmlNode xml representation of block element + * @param {Number} newRotation the rotation in degrees clockwise, default is 0 + * Possible values: + * 0 0 degrees rotation + * 90 90 degrees rotation + * 180 180 degrees rotation + * 270 270 degrees rotation + */ + this.rotate = function(xmlNode, newRotation/*, start*/) { + var prevFlipV = this.$applyBindRule("flipv", xmlNode) == "true" + ? true + : false, + prevFlipH = this.$applyBindRule("fliph", xmlNode) == "true" + ? true + : false, + prevRotation = parseInt(this.$applyBindRule("rotation", xmlNode)) || 0, + + names = ["fliph", "flipv", "rotation"], + values; + + if (prevFlipV && prevFlipH) { + values = ["false", "false", (newRotation + 180) % 360]; + } + else { + values = [String(prevFlipH), String(prevFlipV), newRotation]; + + if (Math.abs(newRotation - prevRotation) % 180 !== 0) { + var + w = parseInt(this.$applyBindRule("width", xmlNode)) || 0, + h = parseInt(this.$applyBindRule("height", xmlNode)) || 0; + + names.push("width", "height"); + values.push(h, w); + } + } + + this.$executeMulticallAction("rotate", names, xmlNode, values); + }; + + /** + * Mirrors the block over the vertical axis. This is an action. + * It's possible to back to previous state with Undo/Redo. + * + * @param {XMLElement} xmlNode xml representation of block element + * @param {Number} newFlipV new flip value, default is false + * Possible values: + * true block element is flipped + * false block element is not flipped + */ + this.flipVertical = function(xmlNode, newFlipV) { + var prevFlipH = this.$applyBindRule("fliph", xmlNode) == "true" + ? true + : false, + prevRotate = this.$applyBindRule("rotation", xmlNode) + ? parseInt(this.$applyBindRule("rotation", xmlNode)) + : 0; + + var values = prevFlipH && newFlipV + ? ["false", "false", (prevRotate + 180) % 360] + : [String(prevFlipH), String(newFlipV), prevRotate]; + + this.$executeMulticallAction( + "verticalFlip", ["fliph", "flipv", "rotation"], xmlNode, values); + }; + + /** + * Mirrors the block over the horizontal axis. This is an action. + * It's possible to back to previous state with Undo/Redo. + * + * @param {XMLElement} xmlNode xml representation of block element + * @param {Number} newFlipH new flip value, default is false + * Possible values: + * true block element is flipped + * false block element is not flipped + */ + this.flipHorizontal = function(xmlNode, newFlipH) { + var prevFlipV = this.$applyBindRule("flipv", xmlNode) == "true" + ? true + : false, + + prevRotate = this.$applyBindRule("rotation", xmlNode) + ? parseInt(this.$applyBindRule("rotation", xmlNode)) + : 0; + + var values = prevFlipV && newFlipH + ? ["false", "false", (prevRotate + 180) % 360] + : [String(newFlipH), String(prevFlipV), prevRotate]; + + this.$executeMulticallAction( + "horizontalFlip", ["fliph", "flipv", "rotation"], xmlNode, values); + }; + + /** + * Resize block element in vertical and horizontal plane. This is an + * action. It's possible to back to previous state with Undo/Redo. + * + * @param {XMLElement} xmlNode xml representation of block element + * @param {Number} newWidth block element horizontal size + * @param {Number} newHeight block element vertical size + * @param {Number} newTop vertical position of block element + * @param {Number} newLeft horizontal position of block element + */ + this.resize = function(xmlNode, newWidth, newHeight, newTop, newLeft) { + this.$executeMulticallAction( + "resize", + ["top", "left", "width", "height"], + xmlNode, + [newTop, newLeft, newWidth, newHeight]); + }; + + /** + * Executes multi actions on one element in one call + * + * @param {String} atName the name of action rule defined in actions for this element. + * @param {Object} setNames the names list of the binding rule defined in bindings for this element. + * @type {String} + * @param {XMLElement} xmlNode the xml representation of element to which rules are applied + * @param {Object} values the new values list of the node + * @type {String} + */ + this.$executeMulticallAction = function(atName, setNames, xmlNode, values) { + var i, node, value, atAction, args, + changes = [], + l = setNames.length; + for (i = 0; i < l; i++) { + node = this.$getDataNode( + setNames[i], xmlNode, this.$createModel); + value = values[i]; + + if (node) { + atAction = node.nodeType == 1 || node.nodeType == 3 + || node.nodeType == 4 + ? "setTextNode" + : "setAttribute"; + args = node.nodeType == 1 + ? [node, value] + : (node.nodeType == 3 || node.nodeType == 4 + ? [node.parentNode, value] + : [node.ownerElement || node.selectSingleNode(".."), + node.nodeName, value]); + changes.push({ + action: atAction, + args : args + }); + } + } + this.$executeAction("multicall", changes, atName, xmlNode); + }; + + /** + * Creates new connection between two blocks. This is an action. It's + * possible to back to previous state with Undo/Redo. + * + * @param {XMLElement} sXmlNode xml representation of source block element + * @param {Number} sInput source block input number + * @param {XMLElement} dXmlNode xml representation of destination block element + * @param {Number} dInput destination block output number + */ + this.addConnector = function(sXmlNode, sInput, dXmlNode, dInput) { + var nXmlNode = this.xmlRoot.ownerDocument.createElement("connection"); + + nXmlNode.setAttribute("ref", this.$applyBindRule("id", dXmlNode)); + nXmlNode.setAttribute("output", sInput); + nXmlNode.setAttribute("input", dInput); + + this.$executeAction("appendChild", [sXmlNode, nXmlNode], + "addConnector", sXmlNode); + }; + + /** + * Removes connections between blocks. It's possible to remove more + * connections in one call. This is an action. It's possible to back + * to previous state with Undo/Redo. + * + * @param {Object} xmlNodeArray xml representations of connections elements + */ + this.removeConnectors = function(xmlNodeArray) { + var changes = [], + i = 0, + l = xmlNodeArray.length; + + for (; i < l; i++) { + changes.push({ + action : "removeNode", + args : [xmlNodeArray[i]] + }); + } + + this.$executeAction("multicall", changes, + "removeConnectors", xmlNodeArray[0]); + }; + + /** + * Creates new xml representation of block element + * + * @param {XMLElement} parentXmlNode xml representation of block parent node + * @param {Number} left horizontal position + * @param {Number} top vertical position + * @param {String} type determines name of block with special abilities, which can be defined in separate file + * @param {Number} width horizontal size + * @param {Number} height vertical size + * @param {String} id unique name + */ + this.addBlock = function(type, left, top, caption) { + if (!type) + return; + + var elTemplate = this.$flowVars.template + .selectSingleNode("//element[@type='" + type + "']"); + + if (!elTemplate) + return; + + var nXmlNode = this.xmlRoot.ownerDocument.createElement("block"); + + nXmlNode.setAttribute("id", "b" + (this.$flowVars.lastBlockId + 1)); + nXmlNode.setAttribute("left", left || 20); + nXmlNode.setAttribute("top", top || 20); + nXmlNode.setAttribute("width", elTemplate.getAttribute("dwidth")); + nXmlNode.setAttribute("height", elTemplate.getAttribute("dheight")); + nXmlNode.setAttribute("type", type); + nXmlNode.setAttribute("caption", caption); + + this.$executeAction("appendChild", [this.xmlRoot, nXmlNode], + "addBlock", this.xmlRoot); + }; + + /** + * Removes xml representation of block. It's possible to remove more + * xmlNodes in one call. This is an action. It's possible to back to + * previous state with Undo/Redo. + * + * @param {Object} xmlNodeArray xml representations of blocks elements + */ + this.removeBlocks = function(xmlNodeArray) { + var id, id2, j, k, + changes = [], + ids = [], + i = 0, + l = xmlNodeArray.length; + + for (; i < l; i++) { + id = this.$applyBindRule("id", xmlNodeArray[i]); + ids.push(id); + + changes.push({ + action : "removeNode", + args : [xmlNodeArray[i]] + }); + } + + /* Removing connections from other blocks */ + for (id2 in this.$flowVars.xmlConnections) { + for (j = this.$flowVars.xmlConnections[id2].length - 1; j >= 0 ; j--) { + for (k = 0, l = ids.length; k < l; k++) { + if (this.$flowVars.xmlConnections[id2][j].ref == ids[k]) { + changes.push({ + action : "removeNode", + args : [this.$flowVars.xmlConnections[id2][j].xmlNode] + }); + } + } + } + } + + this.$executeAction("multicall", changes, + "removeBlocksWithConnections", xmlNodeArray); + }; + + this.$draw = function() { + //Build Main Skin + this.$ext = this.$getExternal(); + this.$int = this.$getLayoutNode("main", "container", this.$ext); + + this.objCanvas = new apf.flow.getCanvas(this.$int); + apf.flow.init(); + /*apf.flow.onconnectionrename = function(e) { + _self.$beforeRename(e); + }*/ + }; + + /* Action when block is removed */ + this.$deInitNode = function(xmlNode, htmlNode) { + var id = this.$applyBindRule("id", xmlNode); + + this.$flowVars.objBlocks[id].destroy(); + + delete this.$flowVars.objBlocks[id]; + delete this.$flowVars.xmlBlocks[id]; + htmlNode.parentNode.removeChild(htmlNode); + this.$flowVars.resizeManager.hide(); + } + + //this.$dragdrop = function(el, dragdata, candrop) { + this.addEventListener("dragdrop", function(e){ + var blockPos = apf.getAbsolutePosition(e.indicator), + canvasPos = apf.getAbsolutePosition(this.objCanvas.htmlElement); + + apf.setNodeValue(this.$getDataNode("top", e.data[0], true), parseInt(e.top) - canvasPos[1]); + apf.setNodeValue(this.$getDataNode("left", e.data[0], true), blockPos[0] - canvasPos[0]); + }); + + this.$updateModifier = function(xmlNode, htmlNode) { + apf.console.info("UPDATE") + + var fv = this.$flowVars, + blockId = this.$applyBindRule("id", xmlNode), + objBlock = fv.objBlocks[blockId], + + t = parseInt(this.$applyBindRule("top", xmlNode)) + ? this.$applyBindRule("top", xmlNode) + : parseInt(objBlock.htmlElement.style.top), + l = parseInt(this.$applyBindRule("left", xmlNode)) + ? this.$applyBindRule("left", xmlNode) + : parseInt(objBlock.htmlElement.style.left); + + objBlock.moveTo(t, l); + + var w = parseInt(this.$applyBindRule("width", xmlNode)) + ? this.$applyBindRule("width", xmlNode) + : objBlock.other.dwidth, + h = parseInt(this.$applyBindRule("height", xmlNode)) + ? this.$applyBindRule("height", xmlNode) + : objBlock.other.dheight; + objBlock.resize(w, h); + + /* Rename */ + objBlock.setCaption(this.$applyBindRule("caption", xmlNode)); + + /* Lock */ + var lock = this.$applyBindRule("lock", xmlNode) == "true" + ? true + : false; + + objBlock.setLock(lock); + + objBlock.changeRotation( + this.$applyBindRule("rotation", xmlNode), + this.$applyBindRule("fliph", xmlNode), + this.$applyBindRule("flipv", xmlNode) + ); + + /* Checking for changes in connections */ + var j, l2, found, ref, output, input, label, type, + cNew = this.$getDataNode("connection", xmlNode, null, null, true), + cCurrent = fv.xmlConnections[blockId] || [], + i = 0; + l = cCurrent.length; + + //Checking for removed connections + if (cCurrent.length) { + for (; i < l; i++) { + for (j = 0, found = false, l2 = cNew.length; j < l2; j++) { + if (cCurrent[i].xmlNode == cNew[j]) { + found = true; + break; + } + } + + if (!found) { + if (fv.objBlocks[blockId] && fv.objBlocks[cCurrent[i].ref]) { + var ConToDel = apf.flow.findConnector( + fv.objBlocks[blockId], cCurrent[i].output, + fv.objBlocks[cCurrent[i].ref], cCurrent[i].input); + if (ConToDel) + apf.flow.removeConnector(ConToDel.connector.htmlElement); + fv.xmlConnections[blockId].removeIndex(i); + } + } + } + } + else { + delete fv.xmlConnections[blockId]; + } + + //Checking for new connections + for (i = 0, l = cNew.length; i < l; i++) { + found = false; + if (cCurrent) { + for (j = 0, l2 = cCurrent.length; j < l2; j++) { + if (cCurrent[j].xmlNode == cNew[i]) { + found = true; + break; + } + } + } + + if (!found) { + ref = this.$applyBindRule("ref", cNew[i]), + output = this.$applyBindRule("blockoutput", cNew[i]), + input = this.$applyBindRule("blockinput", cNew[i]), + label = this.$applyBindRule("blocklabel", cNew[i]), + type = this.$applyBindRule("type", cNew[i]); + + if (fv.xmlBlocks[ref]) { + var r = fv.xmlConnections[blockId] || []; + r.push({ + ref : ref, + output : output, + input : input, + label : label, + type : type, + xmlNode : cNew[i] + }); + + new apf.flow.addConnector(this.objCanvas, + fv.objBlocks[blockId], fv.objBlocks[ref], { + output : output, + input : input, + label : label, + type : type, + xmlNode: cNew[i] + } + ); + fv.xmlConnections[blockId] = r; + } + else { + apf.console.info("Destination block doesn't exist."); + } + } + } + + if (fv.resizeManager && xmlNode == this.selected && !lock) + fv.resizeManager.show(); + else + fv.resizeManager.hide(); + + objBlock.updateOutputs(); + objBlock.onMove(); + }; + + this.$add = function(xmlNode, Lid, xmlParentNode, htmlParentNode, beforeNode) { + /* Creating Block */ + this.$flowVars.lastBlockId++; + + apf.console.info("ADD"); + this.$getNewContext("item"); + var block = this.$getLayoutNode("item"), + elSelect = this.$getLayoutNode("item", "select"), + elImage = this.$getLayoutNode("item", "image"), + elimageContainer = this.$getLayoutNode("item", "imagecontainer"), + elCaption = this.$getLayoutNode("item", "caption"); + + if (elCaption) + elCaption.setAttribute("ondblclick", 'apf.lookup(' + this.$uniqueId + + ').$beforeRename(event, true); return false;'); + + this.nodes.push(block); + + /* Set Css style */ + var style = [], style2 = [], _style, k, l, + left = this.$applyBindRule("left", xmlNode) || 0, + top = this.$applyBindRule("top", xmlNode) || 0, + zindex = this.$applyBindRule("zindex", xmlNode) || 1001; + if (this.snap) { + left = Math.round(left / this.gridW) * this.gridW; + top = Math.round(top / this.gridH) * this.gridH; + } + + style.push("z-index:" + zindex); + style.push("left:" + left + "px"); + style.push("top:" + top + "px"); + + if (this.$flowVars.template) { + var elTemplate = this.$flowVars.template.selectSingleNode("//element[@type='" + + this.$applyBindRule("type", xmlNode) + "']"); + if (elTemplate) { + var stylesFromTemplate = elTemplate.getAttribute("css"); + + if (stylesFromTemplate) { + stylesFromTemplate = stylesFromTemplate.split(";"); + + for (k = 0, l = stylesFromTemplate.length; k < l; k++) { + _style = stylesFromTemplate[k].trim(); + if (_style !== "") { + if (_style.substr(0, 5) == "color" && elCaption) + elCaption.setAttribute("style", [_style].join(";")); + else + style.push(_style); + } + } + } + var w = elTemplate.getAttribute("dwidth"), + h = elTemplate.getAttribute("dheight"); + } + } + + if (this.$applyBindRule("id", xmlNode)) { + var _id = this.$applyBindRule("id", xmlNode).substr(1); + if (_id > this.$flowVars.lastBlockId) + this.$flowVars.lastBlockId = _id; + } + + var id = this.$applyBindRule("id", xmlNode) || "b" + this.$flowVars.lastBlockId, + width = this.$applyBindRule("width", xmlNode) || w || 56, + height = this.$applyBindRule("height", xmlNode) || h || 56, + lock = this.$applyBindRule("lock", xmlNode) || "false", + capPos = this.$applyBindRule("cap-pos", xmlNode) || "outside"; + + style.push("width:" + width + "px"); + style.push("height:" + height + "px"); + + /* Set styles to block */ + block.setAttribute("style", style.join(";")); + + style2.push("width:" + width + "px"); + style2.push("height:" + height + "px"); + + /* Set styles to image container */ + elImage.setAttribute("style", style2.join(";")); + elimageContainer.setAttribute("style", style2.join(";")); + /* End - Set Css style */ + + xmlNode.setAttribute("id", id); + xmlNode.setAttribute("width", width); + xmlNode.setAttribute("height", height); + xmlNode.setAttribute("lock", lock); + xmlNode.setAttribute("left", left); + xmlNode.setAttribute("top", top); + xmlNode.setAttribute("zindex", zindex); + xmlNode.setAttribute("cap-pos", capPos); + + if (elCaption) + this.$setStyleClass(elCaption, capPos); + + elSelect.setAttribute(this.itemSelectEvent || + "onmousedown", 'var o = apf.lookup(' + + this.$uniqueId + + '); o.select(this, event.ctrlKey, event.shiftKey)'); + + apf.xmldb.nodeConnect(this.documentId, xmlNode, block, this); + this.$flowVars.xmlBlocks[id] = xmlNode; + + /* Creating Connections */ + var r = [], i, + connections = this.$getDataNode("connection", xmlNode, null, null, true); + + for (i = 0, l = connections.length; i < l; i++) { + r.push({ + ref : this.$applyBindRule("ref", connections[i]), + output : this.$applyBindRule("blockoutput", connections[i]), + input : this.$applyBindRule("blockinput", connections[i]), + label : this.$applyBindRule("blocklabel", connections[i]), + type : this.$applyBindRule("type", connections[i]), + xmlNode : connections[i] + }); + + } + + + var cssClass = this.$applyBindRule("css", xmlNode); + if (cssClass) { + this.$setStyleClass(block, cssClass); + if (cssClass) + this.$dynCssClasses.push(cssClass); + } + + + if (r.length > 0) + this.$flowVars.xmlConnections[id] = r; + }; + + this.$fill = function() { + apf.insertHtmlNodes(this.nodes, this.$int); + apf.console.info("FILL"); + var id, i, l, + fv = this.$flowVars; + for (id in fv.xmlBlocks) { + var xmlBlock = fv.xmlBlocks[id], + htmlElement = apf.xmldb.findHtmlNode(xmlBlock, this), + type = this.$flowVars.xmlBlocks[id].getAttribute("type") || null, + inputList = {}; + + if (type) { + if (fv.template) { + var elTemplate = fv.template.selectSingleNode("element[@type='" + + this.$applyBindRule("type", xmlBlock) + "']"); + + if (elTemplate) { + var inputs = elTemplate.selectNodes("input"); + + if (inputs) { + for (i = 0, l = inputs.length; i < l; i++) { + inputList[parseInt(inputs[i].getAttribute("name"))] = { + x : parseInt(inputs[i].getAttribute("x")), + y : parseInt(inputs[i].getAttribute("y")), + position : inputs[i].getAttribute("position") + }; + } + } + } + } + } + + var lock = this.$applyBindRule("lock", xmlBlock) == "true" + ? true + : false; + var other = {}; + other['lock'] = lock; + other['flipv'] = this.$applyBindRule("flipv", xmlBlock) == "true" + ? true + : false; + other['fliph'] = this.$applyBindRule("fliph", xmlBlock) == "true" + ? true + : false; + other['rotation'] = parseInt(this.$applyBindRule("rotation", xmlBlock)) || 0; + other['inputList'] = inputList; + other['type'] = type; + other['picture'] = type && fv.template && elTemplate + ? elTemplate.getAttribute("picture") + : null; + other['dwidth'] = type && fv.template && elTemplate + ? parseInt(elTemplate.getAttribute("dwidth")) + : 56; + other['dheight'] = type && fv.template && elTemplate + ? parseInt(elTemplate.getAttribute("dheight")) + : 56; + other['scalex'] = type && fv.template && elTemplate + ? (elTemplate.getAttribute("scalex") == "true" + ? true + : false) + : true; + other['scaley'] = type && fv.template && elTemplate + ? (elTemplate.getAttribute("scaley") == "true" + ? true + : false) + : true; + other['scaleratio'] = type && fv.template && elTemplate + ? (elTemplate.getAttribute("scaleratio") == "true" + ? true + : false) + : false; + other['xmlNode'] = xmlBlock; + other['caption'] = this.$applyBindRule("caption", xmlBlock); + other['capPos'] = this.$applyBindRule("cap-pos", xmlBlock); + + var objBlock = apf.flow.isBlock(htmlElement); + var _self = this; + + if (objBlock) { + this.$setStyleClass(htmlElement, "", ["empty"]); + objBlock.other = other; + objBlock.initBlock(); + } + else { + objBlock = apf.flow.addBlock(htmlElement, this.objCanvas, other); + + if (objBlock) { + objBlock.oncreateconnection = function(sXmlNode, sInput, dXmlNode, dInput) { + _self.addConnector(sXmlNode, sInput, dXmlNode, dInput); + }; + objBlock.onremoveconnection = function(xmlNodeArray) { + _self.removeConnectors(xmlNodeArray); + }; + fv.objBlocks[id] = objBlock; + } + } + } + + for (id in fv.xmlBlocks) { + var c = fv.xmlConnections[id] || []; + + for (i = 0, l = c.length; i < l; i++) { + var con = apf.flow.findConnector(fv.objBlocks[id], c[i].output, + fv.objBlocks[c[i].ref], c[i].input); + if (!con) { + if (fv.objBlocks[id] && fv.objBlocks[c[i].ref]) { + //it's called because connection labels must be aligned + fv.objBlocks[id].onMove(); + + new apf.flow.addConnector(this.objCanvas, fv.objBlocks[id], + fv.objBlocks[c[i].ref], { + output : c[i].output, + input : c[i].input, + label : c[i].label, + type : c[i].type, + xmlNode : c[i].xmlNode + }); + } + else { + fv.connToPaint.push({ + id : id, + id2 : c[i].ref, + output : c[i].output, + input : c[i].input, + label : c[i].label, + type : c[i].type, + xmlNode : c[i].xmlNode + }); + } + } + else { + con.connector.other = { + output : c[i].output, + input : c[i].input, + label : c[i].label, + type : c[i].type, + xmlNode : c[i].xmlNode + }; + con.connector.activateInputs(); + con.connector.draw(); + } + } + } + + /* Try to draw rest of connections */ + for (i = fv.connToPaint.length - 1; i >= 0 ; i--) { + if (fv.objBlocks[fv.connToPaint[i].id] && fv.objBlocks[fv.connToPaint[i].id2]) { + new apf.flow.addConnector(this.objCanvas, + fv.objBlocks[fv.connToPaint[i].id], + fv.objBlocks[fv.connToPaint[i].id2], { + output : fv.connToPaint[i].output, + input : fv.connToPaint[i].input, + label : fv.connToPaint[i].label, + type : fv.connToPaint[i].type, + xmlNode : fv.connToPaint[i].xmlNode + }); + fv.connToPaint.removeIndex(i); + } + } + + this.nodes = []; + + if (!this.objCanvas.scrollPointer) + this.objCanvas.addScrollPointer(); + }; + + this.$destroy = function() { + /*this.oPrevious.onclick = null; + + this.removeEventListener("onkeydown", onkeydown_); + this.removeEventListener("mousescroll", onmousescroll_); + + this.x = null;*/ + }; + + this.$loadAml = function(x) { + /* Loading template */ + apf.getData(this.getAttribute("template"), {callback: + function(data, state, extra) { + if (state == apf.SUCCESS) { + _self.loadTemplate(data); + } + else { + apf.console.info("An error has occurred: " + extra.message, 2); + return; + } + } + }); + + var _self = this, + fv = this.$flowVars; + + /* Resize */ + fv.resizeManager = new apf.resize(); + + fv.resizeManager.onresizedone = function(w, h, t , l) { + _self.resize(_self.selected, w, h, t, l); + }; + + fv.resizeManager.onresize = function(htmlElement, t, l, w, h) { + if (!htmlElement) + return; + var objBlock = apf.flow.isBlock(htmlElement); + objBlock.moveTo(t, l); + objBlock.resize(w, h); + objBlock.updateOutputs(); + objBlock.onMove(); + }; + + apf.flow.onaftermove = function(dt, dl) { + _self.moveTo(_self.selected, dl, dt); + }; + + apf.flow.onblockmove = function() { + fv.resizeManager.show(); + }; + }; + + /** + * Loads a template for elements, which defines the type, size and other + * properties of blocks. + * + * @see Template attribute + * + * @param {XMLElement} data xml representation of template + */ + this.loadTemplate = function(xmlRootNode) { + if (typeof xmlRootNode != "object") + xmlRootNode = apf.getXmlDom(xmlRootNode); + if (xmlRootNode.nodeType == 9) + xmlRootNode = xmlRootNode.documentElement; + if (xmlRootNode.nodeType == 3 || xmlRootNode.nodeType == 4) + xmlRootNode = xmlRootNode.parentNode; + if (xmlRootNode.nodeType == 2) + xmlRootNode = xmlRootNode.ownerElement + || xmlRootNode.parentNode + || xmlRootNode.selectSingleNode(".."); + + this.$flowVars.template = xmlRootNode; + this.$checkLoadQueue(); + }; + + this.$canLoadData = function() { + return this.$flowVars.template ? true : false; + }; + +}).call(apf.flowchart.prototype = new apf.BaseList()); + +apf.aml.setElement("flowchart", apf.flowchart); +apf.aml.setElement("resize", apf.BindingRule); +apf.aml.setElement("left", apf.BindingRule); +apf.aml.setElement("top", apf.BindingRule); +apf.aml.setElement("id", apf.BindingRule); +apf.aml.setElement("width", apf.BindingRule); +apf.aml.setElement("height", apf.BindingRule); +apf.aml.setElement("flipv", apf.BindingRule); +apf.aml.setElement("fliph", apf.BindingRule); +apf.aml.setElement("rotation", apf.BindingRule); +apf.aml.setElement("lock", apf.BindingRule); +apf.aml.setElement("type", apf.BindingRule); +apf.aml.setElement("cap-pos", apf.BindingRule); +apf.aml.setElement("zindex", apf.BindingRule); +apf.aml.setElement("connection", apf.BindingRule); +apf.aml.setElement("ref", apf.BindingRule); +apf.aml.setElement("blockoutput", apf.BindingRule); +apf.aml.setElement("blockinput", apf.BindingRule); +apf.aml.setElement("blocklabel", apf.BindingRule); + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/elements/flowchart2.js)SIZE(45889)TIME(Mon, 10 Jan 2011 10:53:12 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/elements/frame.js)SIZE(4989)TIME(Mon, 10 Jan 2011 10:53:12 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + + +/** + * Element displaying a frame with a caption, containing other elements. This + * element is called a fieldset in html. + * Example: + * + * + * Option 1 + * Option 2 + * Option 3 + * Option 4 + * + * + * + * @constructor + * @define fieldset, frame + * @allowchild {elements}, {anyaml} + * @addnode elements:frame + * + * @author Ruben Daniels (ruben AT ajax DOT org) + * @version %I%, %G% + * @since 0.9 + * + * @inherits apf.Presentation + */ +apf.panel = function(struct, tagName){ + this.$init(tagName || "panel", apf.NODE_VISIBLE, struct); +}; + +apf.fieldset = function(struct, tagName){ + this.$init(tagName || "fieldset", apf.NODE_VISIBLE, struct); +}; + +apf.frame = function(struct, tagName){ + this.$init(tagName || "submit", apf.NODE_VISIBLE, struct); +}; + +(function(){ + this.implement(apf.BaseStateButtons); + + this.$focussable = false; + + + this.$editableCaption = ["caption"] + + + /**** Properties and Attributes ****/ + + /** + * @attribute {String} caption the text of the caption. + */ + this.$supportedProperties.push("caption", "url"); + this.$propHandlers["caption"] = function(value){ + if (!this.oCaption) return; + + if (this.oCaption.nodeType == 1) + this.oCaption.innerHTML = value; + else + this.oCaption.nodeValue = value; + }; + + /** + * @attribute {String} icon the location of the image. + */ + this.$propHandlers["icon"] = function(value){ + var oIcon = this.$getLayoutNode("main", "icon", this.$ext); + if (!oIcon) return; + + if (oIcon.nodeType == 1) + oIcon.style.display = value ? "block" : "none"; + apf.skins.setIcon(oIcon, value, this.iconPath); + }; + + this.$propHandlers["url"] = function(value){ + var node = this.oCaption; + if (node.tagName == "A" || node.nodeType != 1) + node = node.parentNode; + + node.innerHTML = "" + + this.caption + ""; + this.oCaption = this.oCaption.firstChild; + }; + + /** + * Sets the text of the title of this element + * @param {String} value the text of the title. + */ + this.setTitle = function(value){ + this.setProperty("title", value); + }; + + /**** Init ****/ + + this.$draw = function(){ + //Build Main Skin + this.$ext = this.$getExternal(null, null, function(oExt){ + this.$initButtons(oExt); + }); + this.oCaption = this.$getLayoutNode("main", "caption", this.$ext); + this.$int = this.$getLayoutNode("main", "container", this.$ext); + this.$buttons = this.$getLayoutNode("main", "buttons", this.$ext); + + /*if (this.oCaption) { + this.oCaption = this.oCaption.nodeType == 1 + ? this.oCaption + : this.oCaption.parentNode; + }*/ + }; + + this.$loadAml = function(x){ + // not implement now. + }; + + +}).call(apf.frame.prototype = new apf.Presentation()); + +apf.panel.prototype = +apf.fieldset.prototype = apf.frame.prototype; + +apf.aml.setElement("panel", apf.panel); +apf.aml.setElement("fieldset", apf.fieldset); +apf.aml.setElement("frame", apf.frame); + + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/elements/gallery.js)SIZE(27418)TIME(Mon, 10 Jan 2011 10:53:12 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + + + +/** + * This component is used to view images. For each of them we can add a + * thumbnail and description. We can choose an displayed image in several ways. + * From the list of thumbnails, using the keyboard, mouse or buttons. + * + * @author Lukasz Lipinski + * @version %I%, %G% + * @since 3.0 + * + * @inherits apf.Presentation + */ + +apf.gallery = function(struct, tagName){ + this.$init(tagName || "gallery", apf.NODE_VISIBLE, struct); + + this.imageheight = "auto"; + + this.defaultthumb = null; + this.defaultimage = null; + + this.scalewidth = false; + this.zoomIcon = false; + + this.previous = null, + this.next = null, + this.current = null, + this.last = null; + + /** + * Possbile values: + * - grid + * - bar + */ + this.thumbnailMode = "grid"; + this.title = "text"; + + this.nodes = []; + + this.tmrRefresh = null; + this.tmrSlide = null; + + this.stepShow = apf.isIE ? 10 : 15; + this.stepShowFast = apf.isIE ? 5 : 8; + this.stepSlide = apf.isIE ? 25 : 30; + this.stepHide = apf.isIE ? 5 : 7; + this.intSSlide = apf.isIE ? 2 : 10; + + this.imageStack = {}; + this.isOnStack = false; + + this.noThumbArrows = false; + + this.mediaType = null; + this.supportedMediaTypes = ["image", "flash"]; +}; + +(function(){ + this.$supportedProperties.push("thumbheight", "imageheight", "scalewidth", + "defaultthumb", "defaultimage", "thumb-mode", "title", "zoom-icon"); + + this.$booleanProperties["scalewidth"] = true; + this.$booleanProperties["zoom-icon"] = true; + + this.$propHandlers["zoom-icon"] = function(value) { + this.zoomIcon = value; + this.$oZoomIcon.style.display = this.zoomIcon ? "block" : "none"; + }; + + this.$propHandlers["imageheight"] = function(value) { + this.imageheight = value == "auto" + ? "auto" + : parseInt(value); + }; + + this.$propHandlers["thumb-mode"] = function(value) { + this.thumbnailMode = value; + this.$setStyleClass(this.$ext, value + "Mode", ["barMode", "gridMode"]); + }; + + this.isSupportedType = function(type) { + for (var i = 0, l = this.supportedMediaTypes.length; i < l; i++) { + if (type == this.supportedMediaTypes[i]) + return true; + } + return false; + }; + + this.$setSiblings = function() { + var temp_n = this.getNextTraverse(this.current), + temp_p = this.getNextTraverse(this.current, true); + + this.next = temp_n ? temp_n : this.getFirstTraverseNode(); + this.previous = temp_p ? temp_p : this.getLastTraverseNode(); + }; + + this.show = function() { + this.$show(); + }; + + this.hide = function() { + + }; + + this.calcThumbBarSize = function() { + var nodes = this.$oThumbs.childNodes; + var nodes_len = nodes.length; + + var thumbBarSize = 0; + for (var i = 0; i < nodes_len; i++) { + if ((nodes[i].className || "").indexOf("thumbnail") > -1) { + thumbBarSize += nodes[i].offsetWidth + apf.getMargin(nodes[i])[0]; + } + } + + this.$oThumbs.style.width = thumbBarSize + "px"; + + //Whether thumbnail arrows should be displayed or not + this.noThumbArrows = this.$oThumbs.offsetWidth < this.$oThumbs.parentNode.offsetWidth + ? true + : false; + + if (this.noThumbArrows) + this.$setStyleClass(this.$oBar, "noArrows", null); + else + this.$setStyleClass(this.$oBar, "", ["noArrows"]); + + this.calculateRange(); + }; + + this.initiateThumbnailEvents = function() { + var thumbs = this.$oThumbs.childNodes; + var thumbs_len = thumbs.length; + var _self = this; + + var tHeight, tWidth = null; + var iHeight, iWidth; + + for (var i = 0; i < thumbs_len; i++) { + if ((thumbs[i].className || "").indexOf("thumbnail") > -1) { + if (tHeight == null || tHeight <= 0) + tHeight = (thumbs[i].offsetHeight || parseInt(thumbs[i].height)) - apf.getVerBorders(thumbs[i]); + + thumbs[i].onmouseover = function(e) { + _self.$hoverThumb(e, this); + }; + + thumbs[i].onmouseout = function(e) { + _self.$hoverThumb(e, this); + }; + + var images = thumbs[i].childNodes; + images_len = images.length; + + for (var j = 0; j < images_len; j++) { + if ((images[j].tagName || "").toLowerCase() == "img") { + iHeight = images[j].offsetHeight || images[j].height; + iWidth = images[j].offsetWidth || images[j].width; + + if (iHeight > 0 && iWidth > 0 && tHeight > 0) { + images[j].style.height = tHeight + "px"; + images[j].parentNode.style.width = images[j].style.width = parseInt(iWidth * tHeight/iHeight) + "px"; + } + else { + images[j].onload = function() { + iHeight = this.offsetHeight || this.height; + iWidth = this.offsetWidth || this.width; + + if (iHeight > 0 && iWidth > 0 && tHeight > 0) { + this.style.height = tHeight + "px"; + if (this.parentNode) + this.parentNode.style.width = this.style.width = parseInt(iWidth * tHeight/iHeight) + "px"; + } + } + } + } + } + } + } + + if (_self.thumbnailMode == "bar") { + _self.calcThumbBarSize(); + } + }; + + this.$hoverThumb = function(e, htmlElement) { + e = e || event; + var target = e.target || e.srcElement; + var from = e.type == "mouseover" ? 0.7 : 1; + var to = e.type == "mouseover" ? 1 : 0.7; + + if (apf.isChildOf(htmlElement, target, false)) { + if ((htmlElement.className || "").indexOf("selected") > -1) + return; + + apf.tween.single(htmlElement, { + steps : 10, + type : "fade", + from : from, + to : to + }); + } + }; + + this.loading = false; + this.$show = function() { + var _self = this; + //this.$oImage.src = "about:blank"; + this.$showLoader(); + + if (this.imageheight !== "auto") + this.$oImageContainer.style.height = this.$oViewport.style.height = this.imageheight + "px"; + + this.initiateThumbnailEvents(); + + if (this.thumbnailMode == "bar") { + this.calcThumbBarSize(); + } + + this.$oImage.onload = function() { + _self.$hideLoader(); + _self.$oImageBase.src = _self.$oImage.src; + _self.last = _self.current; + _self.$setSiblings(); + + //Get image dimension + _self.$oImageBase.style.display = "block"; + var imgWidth = _self.$oImageBase.offsetWidth || _self.$oImageBase.width; + var imgHeight = _self.$oImageBase.offsetHeight || _self.$oImageBase.height; + _self.$oImageBase.style.display = "none"; + + if (_self.imageheight !== "auto") { + //Get viewport dimension + var vpWidth = _self.$oViewport.offsetWidth; + var vpHeight = _self.$oViewport.offsetHeight; + + //New image dimensions + var nHeight = imgHeight, nWidth = imgWidth; + + if (nHeight > vpHeight) { + nWidth = parseInt(imgWidth * (vpHeight / nHeight)); + nHeight = vpHeight; + } + + if (nWidth > vpWidth) { + nHeight = parseInt(nHeight * (vpWidth / nWidth)); + nWidth = vpWidth; + } + + if (nWidth > 0 && nHeight > 0) { + _self.$oImage.style.width = nWidth + "px"; + _self.$oImage.style.height = nHeight + "px"; + _self.$oImage.style.marginTop = parseInt((vpHeight - nHeight) / 2) + "px"; + _self.$oImage.style.marginLeft = parseInt((vpWidth - nWidth) / 2) + "px"; + } + } + else { + if (imgWidth > 0 && imgHeight > 0) { + _self.$oImageContainer.style.width = imgWidth + "px"; + _self.$oImageContainer.style.height = imgHeight + "px"; + } + _self.$oImageContainer.style.margin = "0 auto"; + } + + _self.$oImage.style.display = "block"; + + if (!_self.isOnStack) { + apf.tween.single(_self.$oImage, { + steps : _self.stepShow, + type : "fade", + anim : apf.tween.NORMAL, + from : 0, + to : 1, + onfinish: function(){ + _self.loading = false; + } + }); + } + else { + if (apf.isIE){ + _self.$oImage.style.filter = "alpha(opacity=100)"; + } + else { + _self.$oImage.style.opacity = 1; + } + _self.loading = false; + } + + //Keep loaded image to won't show loader once again + _self.imageStack[_self.$applyBindRule("src", _self.current)] = true; + }; + }; + + this.$showVideo = function() { + this.$oImage.style.display = "none"; + this.$hideLoader(); + this.last = this.current; + this.$setSiblings(); + + if (this.mediaType == "flash") { + //Get viewport dimension + var vpWidth = this.$oViewport.offsetWidth; + var vpHeight = this.$oViewport.offsetHeight; + + this.$oMedia.style.width = vpWidth + "px"; + this.$oMedia.style.height = vpHeight + "px"; + + this.$oMedia.style.display = "block"; + + if (!this.isOnStack) { + var _self = this; + apf.tween.single(this.$oMedia, { + steps : _self.stepShow, + type : "fade", + anim : apf.tween.NORMAL, + from : 0, + to : 1, + onfinish: function(){ + _self.loading = false; + } + }); + } + else { + this.$oMedia.style.filter = "alpha(opacity=100)"; + this.$oMedia.style.opacity = 1; + this.loading = false; + } + } + + this.imageStack[this.$applyBindRule("src", this.current)] = true; + }; + + this.$refresh = function() { + this.isOnStack = this.imageStack[this.$applyBindRule("src", this.current)] + ? true + : false; + + if (!this.isOnStack) + this.$showLoader(); + + var _self = this; + clearTimeout(this.tmrRefresh); + _self.tmrRefresh = window.setTimeout(function() { + clearTimeout(_self.tmrRefresh); + + if (_self.$amlDestroyed) + return; + + if (!_self.isOnStack) { + apf.tween.single(_self.mediaType == "image" ? _self.$oMedia : _self.$oImage, { + steps : _self.stepHide, + type : "fade", + anim : apf.tween.NORMAL, + from : 1, + to : 0, + onfinish: function() { + _self.setImagePath(_self.current); + + if (_self.mediaType !== "image") { + _self.$showVideo(); + } + else { + _self.$oMedia.style.display = "none"; + } + } + }); + } + else { + _self.setImagePath(_self.current); + + if (_self.mediaType !== "image") { + _self.$showVideo(); + } + else { + _self.$oMedia.style.display = "none"; + } + } + + }, apf.isIE ? 80 : 100); + }; + + this.setDescription = function(xmlNode) { + var title = this.$applyBindRule("caption", xmlNode || this.current); + + switch(this.title) { + case "text": + var descr = title || "No description"; + break; + case "number+text": + case "text+number": + var part1 = (apf.language.getWord("sub/slideshow/image") || "Image") + + " " + (this.getPos() + 1) + " " + + (apf.language.getWord("sub/slideshow/of") || "of") + " " + + this.getTraverseNodes().length; + var part2 = title || this.defaulttitle || apf.language.getWord("sub/slideshow/defaulttitle") || "No description"; + + var descr = "" + part1 + "
    " + part2; + break; + default: + var descr = apf.language.getWord("sub/slideshow/image") || "Image" + + " " + (this.getPos() + 1) + " " + + (apf.language.getWord("sub/slideshow/of") || "of") + " " + + this.getTraverseNodes().length; + break; + } + + this.$oDescr.innerHTML = descr; + }; + + this.getPos = function() { + return Array.prototype.indexOf.call(this.getTraverseNodes(), this.current); + }; + + this.setImagePath = function(xmlNode) { + var src = this.$applyBindRule("src", xmlNode); + + if (this.mediaType == "image") { + this.$oImage.src = src + || this.defaultimage + || this.defaultthumb; + } + else if (this.mediaType == "flash") { + this.loadMedia(this.mediaType, src); + } + else { + alert("Not supported media type."); + } + }; + + this.loadMedia = function (mediaType, data) { + this.$oMedia.innerHTML = data; + }; + + this.$next = function() { + this.select(this.current = this.next); + }; + + this.$previous = function() { + this.select(this.current = this.previous); + }; + + this.centerThumbnail = function(xmlNode) { + var htmlNode = apf.xmldb.findHtmlNode(xmlNode, this); + + if (!htmlNode || this.noThumbArrows) + return; + + var oLeft = htmlNode.offsetLeft; + var oWidth = htmlNode.offsetWidth; + var _self = this; + + var ocLeft = this.$oThumbs.offsetLeft; + + if (htmlNode == this.$oThumbs.firstChild && ocLeft == 0) + return; + + var tCenteredPos = -1 * oLeft - parseInt((oWidth + apf.getMargin(htmlNode)[0]) / 2); + var vpCenteredPos = parseInt(this.$oThumbs.parentNode.offsetWidth / 2) + + var newLeft = tCenteredPos + this.maxLeft + vpCenteredPos; + + newLeft = newLeft > this.maxLeft + ? this.maxLeft + : (newLeft < this.minLeft + ? this.minLeft + : newLeft); + + apf.tween.single(this.$oThumbs, { + steps : 15, + type : "left", + anim : apf.tween.EASEOUT, + from : ocLeft, + to : newLeft, + onfinish : function() { + + } + }); + }; + + this.isLoaderActive = false; + + this.$showLoader = function() { + if (this.isLoaderActive) + return; + + this.isLoaderActive = true; + + this.$oLoader.style.display = "block"; + apf.tween.single(this.$oLoader, { + steps : 6, + type : "fade", + from : 0, + to : 1 + }); + }; + + this.$hideLoader = function() { + this.$oLoader.style.display = "none"; + this.isLoaderActive = false; + }; + + this.$removeFilters = function() { + this.$oLoader.style.filter = ""; + } + + this.addEventListener("$clear", function(){ + return false + }); + + this.addEventListener("afterload", function(){ + this.$setStyleClass(this.$ext, this.$baseCSSname + this.thumbnailMode + "Mode"); + this.imageStack = {}; + this.$show(); + }); + + this.addEventListener("beforeselect", function(e){ + if (this.loading) + return false; + + if (e.selected) + this.current = e.selected; + + var mediaType = this.$applyBindRule("mediatype", this.current); + this.mediaType = this.isSupportedType(mediaType) + ? mediaType + : "image"; + }); + + this.addEventListener("afterselect", function(e){ + if (this.thumbnailMode == "bar") + this.centerThumbnail(this.current); + + this.loading = true; + + if (this.$oZoomIcon) { + if (this.mediaType == "image") { + var _self = this; + var url = this.$applyBindRule("url", this.current); + + this.$oZoomIcon.onclick = function() { + _self.dispatchEvent("zoomclick", {url: url, selected: _self.current}); + if (url) + window.location.href = url; + }; + } + } + this.setDescription(); + this.$refresh(); + }); + + this.$resize = function() { + + }; + + this.calculateRange = function() { + this.maxLeft = this.$oThumbs.offsetLeft; + this.minLeft = -1 * (this.$oThumbs.offsetWidth - this.$oThumbs.parentNode.offsetWidth); + } + + this.$draw = function(){ + this.$getNewContext("main"); + this.$getNewContext("container"); + + //Build Main Skin + this.$ext = this.$getExternal(null, null, function(oExt){ + + }); + + this.$oImageContainer = this.$getLayoutNode("main", "image_container", this.$ext); + this.$oViewport = this.$getLayoutNode("main", "viewport", this.$ext); + this.$oZoomIcon = this.$getLayoutNode("main", "zoom_icon", this.$ext); + this.$oImage = this.$getLayoutNode("main", "image", this.$ext); + this.$oImageBase = this.$getLayoutNode("main", "image_base", this.$ext); + this.$oNext = this.$getLayoutNode("main", "next", this.$ext); + this.$oPrevious = this.$getLayoutNode("main", "previous", this.$ext); + + this.$oBar = this.$getLayoutNode("main", "bar", this.$ext); + this.$oDescr = this.$getLayoutNode("main", "descr", this.$ext); + this.$oLoader = this.$getLayoutNode("main", "loader", this.$ext); + this.$oThumbs = this.$getLayoutNode("main", "thumbs", this.$ext); + this.$container = this.$oThumbs; + + this.$oArrowPrev = this.$getLayoutNode("main", "arrow_prev", this.$ext); + this.$oArrowNext = this.$getLayoutNode("main", "arrow_next", this.$ext); + + this.$oMedia = this.$getLayoutNode("main", "media", this.$ext); + + var rules = "var o = apf.all[" + this.$uniqueId + "];\ + if (o) o.$resize()"; + apf.layout.setRules(this.$pHtmlNode, this.$uniqueId + "_scaling", + rules, true); + apf.layout.queue(this.$pHtmlNode); + + //Events + var _self = this; + + this.$oNext.onclick = function() { + _self.$next(); + }; + + this.$oPrevious.onclick = function() { + _self.$previous(); + }; + + this.calculateRange(); + this.slideFinish = true; + + this.$oArrowPrev.onmouseover = function() { + if (_self.noThumbArrows) + return; + + clearTimeout(_self.tmrSlide); + + _self.tmrSlide = window.setTimeout(function() { + var oLeft = _self.$oThumbs.offsetLeft; + + if (oLeft + 1 > _self.minLeft && oLeft < _self.maxLeft) { + _self.$oThumbs.style.left = (oLeft + 1) + "px"; + } + }, _self.intSSlide); + }; + + this.$oArrowPrev.onmouseout = function() { + clearTimeout(_self.tmrSlide); + }; + + this.$oArrowNext.onmouseover = function() { + if (_self.noThumbArrows) + return; + + clearTimeout(_self.tmrSlide); + + _self.tmrSlide = window.setTimeout(function() { + var oLeft = _self.$oThumbs.offsetLeft; + + if (oLeft > _self.minLeft && oLeft - 1 < _self.maxLeft) { + _self.$oThumbs.style.left = (oLeft - 1) + "px"; + } + }, _self.intSSlide); + }; + + this.$oArrowNext.onmouseout = function() { + clearTimeout(_self.tmrSlide); + }; + + this.$oArrowPrev.onclick = function() { + if (!_self.slideFinish || _self.noThumbArrows) + return; + + _self.slideFinish = false; + + var oLeft = _self.$oThumbs.offsetLeft; + var newLeft = oLeft + 200 > _self.maxLeft + ? _self.maxLeft + : oLeft + 200; + + apf.tween.single(_self.$oThumbs, { + steps : _self.stepSlide, + type : "left", + anim : apf.tween.EASEOUT, + from : oLeft, + to : newLeft, + onfinish : function() { + _self.slideFinish = true; + } + }); + }; + + this.$oArrowNext.onclick = function() { + if (!_self.slideFinish || _self.noThumbArrows) + return; + + _self.slideFinish = false; + var oLeft = _self.$oThumbs.offsetLeft; + var newLeft = oLeft - 200 < _self.minLeft + ? _self.minLeft + : oLeft - 200; + + apf.tween.single(_self.$oThumbs, { + steps : _self.stepSlide, + type : "left", + anim : apf.tween.EASEOUT, + from : oLeft, + to : newLeft, + onfinish : function() { + _self.slideFinish = true; + } + }); + }; + + this.arrowsAreVisible = false; + this.arrowsVisible; + this.arrowsInvisible; + this.$oImage.onmouseover = function(e) { + e = e || event; + + if (_self.arrowsAreVisible) + return; + + _self.arrowsVisible = { + stop : false + }; + + if (_self.arrowsInvisible) + _self.arrowsInvisible.stop = true; + + _self.arrowsAreVisible = true; + _self.$oNext.style.display = "block"; + _self.$oPrevious.style.display = "block"; + + apf.tween.multi(_self.$oNext, { + steps : 15, + control : _self.arrowsVisible, + anim : apf.tween.EASEOUT, + tweens : [ + {type: "fade", from: apf.getOpacity(_self.$oNext), to: 1}, + {type: "fade", from: apf.getOpacity(_self.$oPrevious), to: 1, oHtml : _self.$oPrevious} + ], + onfinish : function() { + _self.$oNext.style.display = "block"; + _self.$oPrevious.style.display = "block"; + _self.$oNext.style.filter = ""; + _self.$oPrevious.style.filter = ""; + } + }); + }; + + this.$oImageContainer.onmouseout = function(e) { + e = e || event; + var target = e.target || e.srcElement; + + if (!_self.arrowsAreVisible) + return; + + target = e.toElement + ? e.toElement + : (e.relatedTarget + ? e.relatedTarget + : null); + + if (apf.isChildOf(_self.$oImageContainer, target, true)) + return; + + _self.arrowsInvisible = { + stop : false + }; + + if (_self.arrowsVisible) + _self.arrowsVisible.stop = true; + + _self.arrowsAreVisible = false; + + apf.tween.multi(_self.$oNext, { + steps : 15, + control : _self.arrowsInvisible, + anim : apf.tween.EASEOUT, + tweens : [ + {type: "fade", from: apf.getOpacity(_self.$oNext), to: 0}, + {type: "fade", from: apf.getOpacity(_self.$oPrevious), to: 0, oHtml : _self.$oPrevious} + ], + onfinish : function() { + _self.$oNext.style.display = "none"; + _self.$oPrevious.style.display = "none"; + } + }); + }; + + if (apf.window.vManager.check(this, "gallery", this.initiateThumbnailEvents)) + this.initiateThumbnailEvents(); + }; +}).call(apf.gallery.prototype = new apf.BaseList()); + +apf.aml.setElement("gallery", apf.gallery); + +apf.aml.setElement("url", apf.BindingRule); +apf.aml.setElement("mediatype", apf.BindingRule); + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/elements/graph.js)SIZE(21525)TIME(Mon, 10 Jan 2011 10:53:12 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + + +/** + * Element displays a chart. + * + * @classDescription This class creates a new chart + * @return {Chart} Returns a new chart + * @type {Chart} + * @constructor + * @allowchild {elements}, {anyaml} + * @addnode elements:chart + * + * @author Rik Arends + * @version %I%, %G% + * @since 0.4 + */ + + apf.Graph = function(struct, tagName){ + this.$init(tagName || "graph", apf.NODE_VISIBLE, struct); +}; + +apf.aml.setElement("graph", apf.Graph); + +(function(){ + //this.$attrExcludePropBind = apf.extend({ + //���style : 1 + //}, this.$attrExcludePropBind); + + //this.$supportedProperties = ["type","series","formula"]; + this.$focussable = false; + + this.$drawCode = 0; + this.$_style = null; + this.$docompile = true; + this.$doinit = true; + this.style = ''; + + this.$microtime = 0; + this.$datasource = null; + this.$datamode = null; + this.$datatype = null; + this.dataslice = '1X'; + this.steps = 100; + this.$data = null; + this.$tilex = 0; + this.$tiley = 0; + this.m={x:0,y:0}; + this.nc = 0; + this.$styletag = ''; + this.$propHandlers["left"] = + this.$propHandlers["top"] = + this.$propHandlers["width"] = + this.$propHandlers["height"] =function(value){ + } + + this.$propHandlers["series"] = function(value){ + var v_yval = this.v_yval = []; + var v_xval = this.v_xval = []; + var v_zval = this.v_zval = []; + var v_time = this.v_time = []; + var v_caption = this.v_caption = []; // mouseover title + var v_class = this.v_class = []; // class + var v_state = this.v_state = []; // class + this.v_stateresolve = false; + // x / y value array + var p,v,n,k,l,t = (new Date()).getTime()*0.001, css; + var series, split, delim, caption, cls, formula, length, mip; + if (typeof value == "string") { + series = value; + split = ","; + delim = " "; + css = "#"; + caption = null; + } + else { + series = value.series; + split = value.split; + caption = value.caption; + delim = value.delim; + css = value.css; + formula = value.formula; + length = value.length; + } + + if (!series) + return; + if(formula){ + //alert(formula); + var mdl = this.getModel(); + if(!mdl.v_yval){ + var f = new Function('length','v_yval',apf.draw.baseMacro( + ["for(v = 0;v1){ + var step = this.mipstep; + // lets switch mipdiv type + var v_yvalmip = this.v_yvalmip = [v_yval]; + var newd = [], oldd = v_yval,i, j, v, n, m, c; + + switch(this.mipset){ + case 'avg': + while(oldd.length>10){ + v_yvalmip.push(newd); + for(j=0,n=0,m=oldd.length;n10){ + v_yvalmip.push(newd); + for(j=0,n=0,m=oldd.length;n10){ + v_yvalmip.push(newd); + for(j=0,n=0,m=oldd.length;n (c=oldd[n])) v = c; + } + oldd= newd, newd = []; + } + break; + case 'max': + while(oldd.length>10){ + v_yvalmip.push(newd); + for(j=0,n=0,m=oldd.length;n1){// we have a clsname + this.v_class[v] = n[1]; + } + + if(caption){ + k = n[0].split(caption); + if(k.length>1) + v_caption[v] = k[1]; + k=k[0].split(split); + }else k = n[0].split(split); + var dim = 1; + if ((l = k.length) > 0){ + if (l == 1) + v_yval[v] = parseFloat(k[0]); + else if (l >= 2){ + v_xval[v] = parseFloat(k[0]); + v_yval[v] = parseFloat(k[1]); + if(l>=3) + v_zval[v] = parseFloat(k[2]); + } + } + } + // lets pick a series type + this.$datamode = 'series'; + + } + // set source type + } + + this.$propHandlers["formula"] = function(value){ + this.pformula = apf.draw.parseJSS(value); + // set source type + this.$datamode = 'math'; + this.$docompile = true; + } + + this.$propHandlers["mode"] = function(value){ + this.$regenerate(); + } + + this.$propHandlers["style"] = function(value){ + this.$regenerate(); + } + + this.$propHandlers["a"] = + this.$propHandlers["b"] = + this.$propHandlers["c"] = + this.$propHandlers["d"] = function(value){ + this.$redraw(); + } + + this.$redraw = function(now,resize){ + // call parent to repaint + if(this.$parentChart) + this.$parentChart.$redraw(now,resize); + } + + this.$regenerate = function(){ + this.$_style = null; + this.$docompile = true; + } + + this.$drawGraph = function( v, doresize ){ + if(this.$doinit){ + this.$doinit = false; + this.$parentAxis.$copySubPos(this); + apf.draw.initLayer(this, this.$parentChart); + }else if(doresize){ + // resize layer + this.$parentAxis.$copySubPos(this); + apf.draw.resizeLayer(this, this.$parentChart); + } + + if(this.$docompile){ + // if we dont have a sourcetype, the data is not ready yet + if(!this.$datamode) return this.$redraw(); + + this.$docompile = false; + var err = {}; + var mode = this.mode+this.$parentAxis.mode; + // go and reparse style + if(!this.$_style)this.$_style = + apf.draw.parseStyle( apf.chart_draw['_'+mode], this.style+this.$styletag, err ); + if(this.$_style.graph && this.$_style.graph.$clslist && this.v_class){ + for(var t,c = this.v_class,s = this.v_state,i=0,j=c.length;i=0){ + switchState(l, sb.hoverout, sb.hoverin ); + + if( this.$_style.graph.nogap==1 && t - this.lasttime<0.3 ){ + if(t-this.lasttime==0){ + //logw( this.$microtime - this.lastMicro); + } + var s = this.lastOut, e = l; + if( s > e ) + for(var i = s;i>e;i--) + setState(i, sb.hoverout, t - (t-this.lasttime)*(1-(s-i)/(s-e)), 1); + else + for(var i = s;i=0){ + switchState(o, sb.hoverin, sb.hoverout ); + } + + this.lastOver = o; + this.$redraw(); + } + } + /**** Databinding ****/ + this.$load = function(XMLRoot){ + + //Add listener to XMLRoot Node + apf.xmldb.addNodeListener(XMLRoot, this); + + var v_yval = this.v_yval = []; + var v_xval = this.v_xval = []; + var v_zval = this.v_zval = []; + var v_time = this.v_time = []; + var v_caption = this.v_caption = []; // mouseover title + var v_state = this.v_state = []; // class + + if (this.$hasBindRule("series")) { + var rule = (this.$getBindRule("series", XMLRoot) || {})[4]; + this.setProperty("series", { + series : this.$applyBindRule("series", XMLRoot), + split : rule.getAttribute("split") || ",", + datatype : rule.getAttribute("datatype") || "1X", + caption : rule.getAttribute("caption") || null, + cls : rule.getAttribute("class") || null, + delim : rule.getAttribute("delimeter") || " ", + formula : rule.getAttribute("formula") || null, + length : rule.getAttribute("length") || null, + mip : rule.getAttribute("mip") || null + + }); + } + else if (this.$hasBindRule("formula")) { + this.setProperty("formula", this.$applyBindRule("formula", XMLRoot)); + } + else { + // this.info = []; + // this.ylabel = {};hoeveel + var v_nodes = this.v_nodes = this.getTraverseNodes(XMLRoot); + // x / y value array + var n,p,v,k,length,t = (new Date()).getTime()*0.001; + if (!this.$hasBindRule("y")){ + apf.console.warn("No y binding rule found for graph " + + this.name + " [" + this.localName + "]"); + } + else { + var bz = this.$hasBindRule("z") ? true : false, + bx = this.$hasBindRule("x") ? true : false; + for (v = 0, length = v_nodes.length; v < length; v++) { + n = v_nodes[v]; + //caching + //v_cacheid[apf.xmldb.nodeConnect(this.documentId, n, null, this)] = v; + + this.v_time[v] = t; + v_yval[v] = parseFloat(this.$applyBindRule("y", n)); + if (bx) { + v_xval[v] = parseFloat(this.$applyBindRule("x", n)); + if (bz) + v_zval[v] = parseFloat(this.$applyBindRule("z", n)); + } + var cls = this.$applyBindRule("css", n); + v_caption[v] = this.$applyBindRule("caption", n); + //// TODO write code that looks up class + v_state[v] = 0; + } + this.$sourcetype = 'seriesX'; + } + + if (length != this.length) + this.setProperty("length", length); + + } + }; + +/* +addEventListener("DOMNodeInsertedIntoDocument", function( e ){ + +}); + $loadAml + addEventListener("DOMNodeInserted", function( e ){ + +}); + addEventListener("DOMNodeRemoved", function( e ){ + +}); + addEventListener("DOMNodeRemovedFromDocument", function( e ){ + +}); + addEventListener("DOMNodeInsertedIntoDocument", function( e ){ + + }); +*/ + this.$xmlUpdate = function(action, xmlNode, listenNode, UndoObj){ + + //Clear this component if some ancestor has been detached + if (action == "redo-remove") { + var retreatToListenMode = false, model = this.getModel(true); + if (model) { + var xpath = model.getXpathByAmlNode(this); + if (xpath) { + var xmlNode = model.data.selectSingleNode(xpath); + if (xmlNode != this.xmlRoot) + retreatToListenMode = true; + } + } + + if (retreatToListenMode || this.xmlRoot == xmlNode) { + //Set Component in listening state untill data becomes available again. + return model.$waitForXml(this); + } + } + + if (this.$hasBindRule("series") || this.$hasBindRule("formula")) { + //Action Tracker Support + if (UndoObj && !UndoObj.xmlNode) + UndoObj.xmlNode = this.xmlRoot; + + + //@todo apf3.0 check if this is still needed + /*var lrule, rule; + for (rule in this.bindingRules) { + lrule = rule.toLowerCase(); + if (this.$supportedProperties.contains(lrule)) { + var value = this.$applyBindRule(rule, this.xmlRoot) || ""; + + if (this[lrule] != value) + this.setProperty(lrule, value, true); + } + }*/ + + } + else { + //this should be made more optimal + var v_yval = this.v_yval = []; + var v_xval = this.v_xval = []; + var v_zval = this.v_zval = []; + var v_time = this.v_time = []; + var v_caption = this.v_caption = []; // mouseover title + var v_state = this.v_state = []; // class + + //cacheid = xmlNode.getAttribute(apf.xmldb.xmlIdTag); + // this.info = []; + // this.ylabel = {};hoeveel + var v_nodes = this.v_nodes = this.getTraverseNodes(this.xmlRoot); + // x / y value array + var n,p,v,k,length,t = (new Date()).getTime()*0.001; + if (!this.$hasBindRule("y")){ + apf.console.warn("No y binding rule found for graph " + + this.name + " [" + this.localName + "]"); + } + else { + var bz = this.$hasBindRule("z") ? true : false, + bx = this.$hasBindRule("x") ? true : false; + for (v = 0, length = v_nodes.length; v < length; v++) { + n = v_nodes[v]; + //caching + //v_cacheid[apf.xmldb.nodeConnect(this.documentId, n, null, this)] = v; + + this.v_time[v] = t; + v_yval[v] = parseFloat(this.$applyBindRule("y", n)); + if (bx) { + v_xval[v] = parseFloat(this.$applyBindRule("x", n)); + if (bz) + v_zval[v] = parseFloat(this.$applyBindRule("z", n)); + } + var cls = this.$applyBindRule("css", n); + v_caption[v] = this.$applyBindRule("caption", n); + //// TODO write code that looks up class + v_state[v] = 0; + } + this.$sourcetype = 'seriesX'; + } + //if (this.focussable) + //apf.document.activeElement == this ? this.$focus() : this.$blur(); + + + if (length != this.length) + this.setProperty("length", length); + + this.$redraw(); + } + } + + /**** Selection ****/ + this.addEventListener("DOMNodeInsertedIntoDocument", function(e){ + this.$parentAxis = this.parentNode; + this.$parentChart = this.$parentAxis.parentNode; + var n = this.getElementsByTagNameNS(apf.ns.apf, "style"); + if(n.length>0){ + this.$styletag = n[0].firstChild.nodeValue; + } + }); +}).call(apf.Graph.prototype = new apf.AmlElement()); + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/elements/hbox.js)SIZE(40073)TIME(Mon, 10 Jan 2011 10:53:12 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + +/** + * @define vbox Container that stacks it's children vertically. + * @see element.hbox + * @define hbox Container that stacks it's children horizontally. + * Example: + * + * + * + * + * + * + * + * + * + * + * + * + * Remarks: + * Firefox has some issues. + * 1. Sometimes it's necessary to put a fixed width to have it calculate the right + * height value. + * 2. Using flex="1" on non fixed height/width tree's will give unexpected results. + * @addnode elements + * @constructor + * + * @author Ruben Daniels (ruben AT ajax DOT org) + * @version %I%, %G% + * @since 0.9 + */ +apf.hbox = function(struct, tagName){ + this.$init(tagName || "hbox", apf.NODE_VISIBLE, struct); +}; +apf.vbox = function(struct, tagName){ + this.$init(tagName || "vbox", apf.NODE_VISIBLE, struct); +}; + +(function(){ + /**** Properties and Attributes ****/ + + this.$focussable = false; + this.$useLateDom = true; + this.$box = true; + this.$layout = true; + + var MOZSTACK = "-moz-stack"; + var input = {"INPUT":1, "SELECT":1, "TEXTAREA":1} + + /** + * @attribute {String} padding the space between each element. Defaults to 2. + * @attribute {Boolean} reverse whether the sequence of the elements is in reverse order. + * @attribute {String} edge the space between the container and the elements, space seperated in pixels for each side. Similar to css in the sequence top right bottom left. Defaults to "5 5 5 5". + * Example: + * + * + * + * @attribute {String} pack + * Possible values: + * start + * center + * end + * @attribute {Boolean} align + * Possible values: + * start + * center + * end + * stretch + */ + this.$booleanProperties["splitters"] = true; + this.$supportedProperties.push("padding", "reverse", "edge", "pack", "align", "splitters"); + + this.$propHandlers["padding"] = function(value){ + this.padding = parseInt(value); + + var node, nodes = this.childNodes, elms = []; + for (var i = 0, l = nodes.length; i < l; i++) { + if ((node = nodes[i]).nodeFunc == apf.NODE_VISIBLE && node.$amlLoaded && node.visible !== false) + elms.push(node); + } + + if (!elms.length) + return; + + for (var last, b, el, i = elms.length - 2; i >= 0; i--) { + b = (el = elms[i]).margin && apf.getBox(el.margin) || [0,0,0,0]; + + if ((!last || !last.$splitter) && !el.$splitter) { + b[this.$vbox ? 2 : 1] += this.padding; + + if (!apf.hasFlexibleBox && i != 0 && this.align == "stretch" && this.$vbox) + b[0] += this.padding; + } + + el.$ext.style.margin = b.join("px ") + "px"; + last = el; + } + b = (el = elms[elms.length - 1]).margin && apf.getBox(el.margin) || [0,0,0,0]; + el.$ext.style.margin = b.join("px ") + "px"; + + if (!apf.hasFlexibleBox) + this.$resize(); + } + + this.$propHandlers["reverse"] = function(value){ + if (apf.hasFlexibleBox) + this.$int.style[apf.CSSPREFIX + "BoxDirection"] = value ? "reverse" : "normal"; + else { + //@todo + } + }; + + this.$propHandlers["edge"] = function(value){ + var el = !apf.hasFlexibleBox && this.$vbox ? this.$ext : this.$int; + el.style.padding = (this.$edge = apf.getBox(value)).join("px ") + "px"; + + if (!apf.hasFlexibleBox) + this.$resize(); + }; + + this.$propHandlers["pack"] = function(value){ + if (apf.hasFlexibleBox) + this.$int.style[apf.CSSPREFIX + "BoxPack"] = value || "start"; + else if (this.$amlLoaded) { + if (this.$vbox) { + this.$int.style.verticalAlign = value == "center" ? "middle" : (value == "end" ? "bottom" : "top"); + } + else { + this.$int.style.textAlign = ""; + + var nodes = this.childNodes; + for (var i = 0, l = nodes.length; i < l; i++) { + if ((node = nodes[i]).nodeFunc != apf.NODE_VISIBLE || !node.$amlLoaded) //|| node.visible === false + continue; + + node.$ext.style.textAlign = apf.getStyle(node.$ext, "textAlign") || "left"; + } + + this.$int.style.textAlign = value == "center" ? "center" : (value == "end" ? "right" : "left"); + } + } + }; + + //@todo change overflow when height/width changes depending on $vbox + + this.$propHandlers["align"] = function(value){ + if (apf.hasFlexibleBox) { + this.$int.style[apf.CSSPREFIX + "BoxAlign"] = value || "stretch"; + + if (apf.isGecko) + this.$int.style.overflow = "visible"; + + //@todo this should probably be reinstated + var stretch = !value || value == "stretch"; + var nodes = this.childNodes; + var size = this.$vbox ? "width" : "height"; + + var isInFixed = false, loopNode = this; + while(!isInFixed && loopNode) { + isInFixed = loopNode[size] || loopNode.anchors || (loopNode.$vbox ? loopNode.top && loopNode.bottom : loopNode.left && loopNode.right); + if (!loopNode.flex) + break; + loopNode = loopNode.parentNode || loopNode.$parentNode; + } + + for (var i = 0, l = nodes.length; i < l; i++) { + if (!(node = nodes[i]).$ext || node.$ext.nodeType != 1) + continue; + + //(this[size] || this.anchors || (this.$vbox ? this.top && this.bottom : this.left && this.right) + if (stretch && !node[size]) //(node.$altExt || + node.$ext.style[size] = apf.isGecko && (this.flex || node.flex) + ? (isInFixed ? "1px" : "auto") + : (apf.isWebkit && input[node.$ext.tagName] + ? "100%" + : (apf.isWebkit && node[this.$vbox ? "minwidth" : "minheight"] && this.flex //nasty bug fix + ? "0px" + : "auto"));//(apf.isWebkit && node.flex && size == "height" ? "100%" : "auto"); // && (this.flex && node.flex) + else if (node[size]) + handlers["true"][size].call(node, node[size]); + } + } + else if (this.$amlLoaded) { + var stretch = !value || value == "stretch"; + + if (!this.$vbox) { + var nodes = this.childNodes; + for (var i = 0, l = nodes.length; i < l; i++) { + if ((node = nodes[i]).nodeFunc != apf.NODE_VISIBLE || !node.$amlLoaded) //|| node.visible === false + continue; + + node.$ext.style.verticalAlign = value == "center" ? "middle" : (value == "end" ? "bottom" : "top"); + } + } + else { + var el = !apf.hasFlexibleBox && this.$vbox ? this.$ext : this.$int; + el.style.textAlign = ""; + + var nodes = this.childNodes; + for (var i = 0, l = nodes.length; i < l; i++) { + if ((node = nodes[i]).nodeFunc != apf.NODE_VISIBLE || !node.$amlLoaded) //|| node.visible === false + continue; + + if (node.visible !== false) { + node.$ext.style.display = value == "stretch" ? "block" : apf.INLINE; + node.$br.style.display = value == "stretch" ? "none" : ""; + + if (apf.needZoomForLayout) + node.$ext.style.zoom = 1; + } + node.$ext.style.textAlign = apf.getStyle(node.$ext, "textAlign") || "left"; + } + + el.style.textAlign = value == "center" ? "center" : (value == "end" ? "right" : "left"); + } + } + }; + + function visibleHandler(e){ + + if (this.parentNode.splitters && !this.$splitter) { + if (!e.value) { + if (this.nextSibling && this.nextSibling.$splitter) + this.nextSibling.removeNode(); + else if (this.previousSibling && this.previousSibling.$splitter) + this.previousSibling.removeNode(); + } + else { + var isLast = isLastVisibleChild(this); + if (!isLast) { + if (!this.nextSibling.$splitter) { + this.parentNode.insertBefore( + this.ownerDocument.createElementNS(apf.ns.aml, "splitter"), + this.nextSibling); + } + } + else if (this.previousSibling && !this.previousSibling.$splitter) { + this.parentNode.insertBefore( + this.ownerDocument.createElementNS(apf.ns.aml, "splitter"), + this); + } + } + } + + + //@todo this can be more optimized by calcing if it WAS the last vis child. + //if (isLastVisibleChild(this)) { + this.parentNode.$propHandlers["padding"] + .call(this.parentNode, this.parentNode.padding); + //} + + apf.layout.forceResize(this.parentNode.$int); + + if (apf.hasFlexibleBox) { + if (this.$altExt) + this.$altExt.style.display = e.value + ? (apf.isGecko ? MOZSTACK : apf.CSSPREFIX2 + "-box") + : "none"; + return; + } + + if (e.value) { + this.$ext.style.display = this.parentNode.$vbox + && this.parentNode.align == "stretch" ? "block" : apf.INLINE; + if (apf.needZoomForLayout) + this.$ext.style.zoom = 1; + if (this.$br) + this.$br.style.display = this.parentNode.align == "stretch" ? "none" : ""; + } + else { + if (this.$br) + this.$br.style.display = "none"; + } + + this.parentNode.$resize(); + } + + function resizeHandler(){ + if (!this.flex) { + if (this.$isRszHandling || this.$lastSizeChild && + this.$lastSizeChild[0] == this.$ext.offsetWidth && + this.$lastSizeChild[1] == this.$ext.offsetHeight) + return; + + /*if (this.$skipResizeOnce) + delete this.$skipResizeOnce; + else*/ + this.parentNode.$resize(true); + + this.$lastSizeChild = [this.$ext.offsetWidth, this.$ext.offsetHeight]; + } + } + + var handlers = { + //Handlers for flexible box layout + "true" : { + "optimize" : function(value){ + this.optimize = apf.isTrue(value); + }, + + "width" : function(value){ + //@todo this should check the largest and only allow that one + //if (this.parentNode.$vbox && this.parentNode.align == "stretch") + //return; + + (this.$altExt || this.$ext).style.width = value + ? (parseFloat(value) == value + ? value + "px" + : value) + : ""; + }, + + "height" : function(value){ + //@todo this should check the largest and only allow that one + //if (!this.parentNode.$vbox && this.parentNode.align == "stretch") + //return; + + (this.$altExt || this.$ext).style.height = value + ? (parseFloat(value) == value + ? value + "px" + : value) + : (apf.isGecko && this.flex && this.parentNode.$vbox ? "auto" : ""); + }, + + "margin" : function(value){ + var b = apf.getBox(value); + if (!isLastVisibleChild(this)) + b[this.parentNode.$vbox ? 2 : 1] += this.parentNode.padding; + this.$ext.style.margin = b.join("px ") + "px"; + }, + + "flex" : function(value){ + this.flex = value = parseInt(value); + if (value) { + if (!this.optimize && !this.$altExt) { + this.$altExt = this.$ext.ownerDocument.createElement("div"); + this.parentNode.$int.replaceChild(this.$altExt, this.$ext); + this.$altExt.appendChild(this.$ext); + this.$altExt.style[apf.CSSPREFIX + "BoxSizing"] = "border-box"; + this.$altExt.style.display = apf.CSSPREFIX2 + "-box"; + this.$altExt.style[apf.CSSPREFIX + "BoxOrient"] = "vertical"; + this.$ext.style[apf.CSSPREFIX + "BoxFlex"] = 1; + var size = this.parentNode.$vbox ? "height" : "width"; + //var osize = this.parentNode.$vbox ? "width" : "height"; + + if (apf.isWebkit) { + if (!this.preventforcezero) + this.$altExt.style[size] = "0px"; + } + else if (apf.isGecko) { + this.$altExt.style[size] = "0px"; + + //Possible hack to not do this for $box elements + if (!this.$box) + this.$altExt.style.overflow = "hidden"; //Gecko + if (apf.getStyle(this.$ext, "overflow") == "visible") + this.$ext.style.overflow = "hidden"; //Gecko + this.$ext.style[size] = "1px"; + + this.$altExt.style.minHeight = this.$ext.style.minHeight; + this.$altExt.style.maxHeight = this.$ext.style.maxHeight; + this.$altExt.style.minWidth = this.$ext.style.minWidth; + this.$altExt.style.maxWidth = this.$ext.style.maxWidth; + } + } + + (this.$altExt || this.$ext).style[apf.CSSPREFIX + "BoxFlex"] = parseInt(value) || 1; + } + else if (this.$altExt) { + this.parentNode.$int.replaceChild(this.$ext, this.$altExt); + this.$ext.style[apf.CSSPREFIX + "BoxFlex"] = ""; + if (apf.isGecko) + this.$ext.style.overflow = ""; + delete this.$altExt; + } + } + }, + + //Handlers for older browsers + "false" : { + "width" : function(value){ + //@todo this should check the largest and only allow that one + //if (this.parentNode.$vbox && this.parentNode.align == "stretch") + //return; + + this.$ext.style.width = value + ? (parseFloat(value) == value + ? Math.max(0, value - apf.getWidthDiff(this.$ext)) + "px" + : value) + : ""; + }, + + "height" : function(value){ + //@todo this should check the largest and only allow that one + //if (this.parentNode.localName == "hbox" && this.parentNode.align == "stretch") + //return; + + this.$ext.style.height = value + ? (parseFloat(value) == value + ? Math.max(0, value - apf.getHeightDiff(this.$ext)) + "px" + : value) + : ""; + }, + + "margin" : function(value){ + var b = apf.getBox(value); + if (this.padding) { + if (!isLastVisibleChild(this)) + b[this.parentNode.$vbox ? 2 : 1] += this.padding; + if (this != this.parentNode.firstChild && this.parentNode.align == "stretch" && this.parentNode.$vbox) //@todo + b[0] += this.padding; + } + this.$ext.style.margin = b.join("px ") + "px"; + }, + + "flex" : function(value){ + this.flex = parseInt(value); + if (this.$amlLoaded) + this.parentNode.$resize(true); + } + } + } + + function isLastVisibleChild(amlNode){ + var lastChild = amlNode.parentNode.lastChild; + while(lastChild && (lastChild.nodeFunc != apf.NODE_VISIBLE + || lastChild.visible === false + || lastChild.visible == 2 && apf.isFalse(lastChild.getAttribute("visible")))) { + lastChild = lastChild.previousSibling; + } + + return lastChild && lastChild == amlNode; + } + + //@todo move this to enableTable, disableTable + this.register = function(amlNode, insert){ + if (amlNode.$altExt) //@todo hack, need to re-arch layouting + return; + + amlNode.$propHandlers["left"] = + amlNode.$propHandlers["top"] = + amlNode.$propHandlers["right"] = + amlNode.$propHandlers["bottom"] = apf.K; + + var propHandlers = handlers[apf.hasFlexibleBox]; + for (var prop in propHandlers) { + amlNode.$propHandlers[prop] = propHandlers[prop]; + } + + if (amlNode.nodeFunc == apf.NODE_VISIBLE) { + if (apf.hasFlexibleBox) { + //if (apf.isGecko && apf.getStyle(amlNode.$ext, "display") == "block") + //amlNode.$ext.style.display = MOZSTACK; //@todo visible toggle + + //input elements are not handled correctly by firefox and webkit + if (amlNode.$ext.tagName == "INPUT" || apf.isWebkit && input[amlNode.$ext.tagName]) { + var doc = amlNode.$ext.ownerDocument; + amlNode.$altExt = doc.createElement("div"); + amlNode.parentNode.$int.replaceChild(amlNode.$altExt, amlNode.$ext); + amlNode.$altExt.style[apf.CSSPREFIX + "BoxSizing"] = "border-box"; + amlNode.$altExt.appendChild(amlNode.$ext); + + if (apf.isWebkit) { + var d = apf.getDiff(amlNode.$ext); + //amlNode.$altExt.style.padding = "0 " + d[0] + "px " + d[1] + "px 0"; + amlNode.$altExt.style.height = "100%"; + amlNode.$altExt.style.width = "0"; + amlNode.$altExt.style.lineHeight = 0; + amlNode.$altExt.style.margin = "-1px 0 0 0"; + amlNode.$ext.style.width = "100%"; + amlNode.$ext.style.height = "100%"; + amlNode.$ext.style.top = "1px"; + amlNode.$ext.style.position = "relative"; + } + else { + amlNode.$altExt.style.display = apf.CSSPREFIX2 + "-box"; + amlNode.$altExt.style[apf.CSSPREFIX + "BoxOrient"] = "horizontal"; + amlNode.$altExt.style[apf.CSSPREFIX + "BoxAlign"] = "stretch"; + amlNode.$ext.style[apf.CSSPREFIX + "BoxFlex"] = 1; + } + } + else { + if (apf.getStyle(amlNode.$ext, "display") == "inline") + amlNode.$ext.style.display = "block"; //@todo undo + //This is nice for positioning elements in the context of an hbox/vbox + //if (apf.getStyle(amlNode.$ext, "position") == "absolute") + //amlNode.$ext.style.position = "relative"; //@todo undo + } + + amlNode.$ext.style[apf.CSSPREFIX + "BoxSizing"] = "border-box"; + } + else { + if (this.$vbox) { + amlNode.$br = this.$int.insertBefore(amlNode.$ext.ownerDocument.createElement("br"), amlNode.$ext.nextSibling); + if (amlNode.visible === false) + amlNode.$br.style.display = "none"; + } + else { + if (amlNode.visible !== false) { + amlNode.$ext.style.display = apf.INLINE; + if (apf.needZoomForLayout) + amlNode.$ext.style.zoom = 1; + } + this.$int.style.whiteSpace = ""; + amlNode.$ext.style.whiteSpace = apf.getStyle(amlNode.$ext, "whiteSpace") || "normal"; + this.$int.style.whiteSpace = "nowrap"; + } + + this.$int.style.fontSize = "0"; + if (!amlNode.$box) { + var fontSize = apf.getStyle(amlNode.$ext, "fontSize"); + if (fontSize == "0px") { + amlNode.$ext.style.fontSize = ""; + var pNode = this.$int.parentNode; + while(apf.getStyle(pNode, "fontSize") == "0px") { + pNode = pNode.parentNode; + } + fontSize = apf.getStyle(pNode, "fontSize"); + } + amlNode.$ext.style.fontSize = fontSize;//apf.getStyle(amlNode.$ext, "fontSize") || "normal"; + } + + amlNode.addEventListener("resize", resizeHandler); + } + + amlNode.addEventListener("prop.visible", visibleHandler); + + this.$noResize = true; + + if (amlNode.height) + propHandlers.height.call(amlNode, amlNode.height); + if (amlNode.width) + propHandlers.width.call(amlNode, amlNode.width); + if (amlNode.margin) + propHandlers.margin.call(amlNode, amlNode.margin); + if (amlNode.flex) + propHandlers.flex.call(amlNode, amlNode.flex); + + //Ie somehow sets the visible flags in between registration + var isLast = isLastVisibleChild(amlNode); //apf.isIE ? this.lastChild == amlNode : + if (isLast || insert) { + this.$propHandlers["padding"].call(this, this.padding); + this.$propHandlers["align"].call(this, this.align); + + if (!apf.hasFlexibleBox) + this.$propHandlers["pack"].call(this, this.pack); + + if (insert && amlNode.visible !== false) + visibleHandler.call(amlNode, {value: true}); + } + + else if (this.splitters && !amlNode.$splitter && amlNode.visible !== false) { + this.insertBefore( + this.ownerDocument.createElementNS(apf.ns.aml, "splitter"), + amlNode.nextSibling); + } + + + delete this.$noResize; + + if (!apf.hasFlexibleBox && isLast) + this.$resize(); + } + } + + this.unregister = function(amlNode){ + amlNode.$propHandlers["left"] = + amlNode.$propHandlers["top"] = + amlNode.$propHandlers["right"] = + amlNode.$propHandlers["bottom"] = null; + + var propHandlers = handlers[apf.hasFlexibleBox]; + for (var prop in propHandlers) { + delete amlNode.$propHandlers[prop]; + } + + //Clear css properties and set layout + if (amlNode.nodeFunc == apf.NODE_VISIBLE) { + if (amlNode.flex) + propHandlers.flex.call(amlNode, 0); + + if (apf.hasFlexibleBox) { + amlNode.$ext.style[apf.CSSPREFIX + "BoxSizing"] = ""; + + if (apf.isGecko) { + this.$int.style.overflow = "visible"; + + if (amlNode.$ext.style.display == "block") + amlNode.$ext.style.display = ""; + } + } + else { + amlNode.$ext.style.verticalAlign = ""; + amlNode.$ext.style.textAlign = ""; + amlNode.$ext.style.whiteSpace = ""; + //amlNode.$ext.style[apf.CSSFLOAT] = ""; + + if (amlNode.$br) { + amlNode.$br.parentNode.removeChild(amlNode.$br); + delete amlNode.$br; + //amlNode.$ext.style.fontSize = ""; + } + + amlNode.removeEventListener("resize", resizeHandler); + } + + amlNode.removeEventListener("prop.visible", visibleHandler); + + amlNode.$ext.style.display = amlNode.visible ? "block" : "none"; + + if (amlNode.margin) + amlNode.$ext.style.margin = ""; + + if (amlNode.width) + amlNode.$ext.style.width = ""; + + + if (this.splitters && !amlNode.$splitter + && amlNode.nextSibling && amlNode.nextSibling.$splitter) { + amlNode.nextSibling.$splitter.removeNode(); + } + + } + } + /* + this.addEventListener("DOMNodeInsertedIntoDocument", function(e){ + this.register(this.parentNode); + }); + */ + + /**** DOM Hooks ****/ + + this.addEventListener("DOMNodeRemoved", function(e){ + if (e.$doOnlyAdmin || e.currentTarget == this) + return; + + if (e.relatedNode == this){ + this.unregister(e.currentTarget); + //e.currentTarget.$setLayout(); + } + }); + + this.addEventListener("DOMNodeInserted", function(e){ + if (e.currentTarget == this || e.currentTarget.nodeType != 1) + return; + + if (e.relatedNode == this && !e.$isMoveWithinParent) { + e.currentTarget.$setLayout(this.localName, true); + + if (e.currentTarget.$altExt) { + + return false; + } + } + }); + + function myVisibleHandler(e){ + if (e.value) + this.$int.style.display = apf.CSSPREFIX2 + "-box"; + } + + function myHeightHandler(e){ + clearInterval(this.$heighttimer); + if (e.value || this.align != "stretch") { + delete this.$heighttimer; + } + else if (!this.$heighttimer) { + var _self = this; + this.$heighttimer = $setInterval(function(){ + if (_self.$amlDestroyed) + return; + + var nodes = _self.childNodes; + for (var $int, i = 0, l = nodes.length; i < l; i++) { + if (!($int = (node = nodes[i]).$int || node.$container)) + continue; + + if (Math.min($int.scrollHeight, node["maxheight"] || 10000) > $int.offsetHeight) + return _self.$resize(true); + } + + if (_self.flex) + clearInterval(this.$heighttimer); + }, this.flex ? 1 : 500); + } + } + + this.$draw = function(){ + var doc = this.$pHtmlNode.ownerDocument; + this.$ext = this.$pHtmlNode.appendChild(doc.createElement("div")); + this.$ext.className = this.localName; + + this.$vbox = this.localName == "vbox"; + this.$int = apf.isGecko && !this.parentNode.$box || !apf.hasFlexibleBox && this.$vbox //@todo reparenting for gecko needs some admin work + ? this.$ext.appendChild(doc.createElement("div")) + : this.$ext; + this.$ext.host = this; + + if (apf.isGecko && !this.parentNode.$box) { + this.$int.style.width = "100%"; + this.$int.style.height = "100%"; + } + else if (!apf.hasFlexibleBox && this.$vbox) { + this.$int.style.display = apf.INLINE; + if (apf.needZoomForLayout) + this.$int.style.zoom = 1; + this.$int.style.width = "100%"; + } + + if (apf.hasFlexibleBox) { + this.$display = "-" + apf.CSSPREFIX +"-box"; + + this.$int.style.display = apf.CSSPREFIX2 + "-box"; + this.$int.style[apf.CSSPREFIX + "BoxOrient"] = this.localName == "hbox" ? "horizontal" : "vertical"; + if (apf.isGecko) //!webkit + this.$int.style[apf.CSSPREFIX + "BoxSizing"] = "border-box"; + this.$int.style[apf.CSSPREFIX + "BoxAlign"] = "stretch"; + + this.addEventListener("prop.visible", myVisibleHandler); + } + else { + if (!this.$vbox) { + this.$int.style.whiteSpace = "nowrap"; + this.addEventListener("prop.height", myHeightHandler); + } + + var spacer = (!apf.hasFlexibleBox && this.$vbox ? this.$ext : this.$int) + .appendChild(doc.createElement("strong")); + spacer.style.height = "100%"; + spacer.style.display = apf.INLINE; + if (apf.needZoomForLayout) + spacer.style.zoom = 1; + //spacer.style.marginLeft = "-4px"; + spacer.style.verticalAlign = "middle"; + + this.addEventListener("resize", this.$resize); + } + + if (this.getAttribute("class")) + apf.setStyleClass(this.$ext, this.getAttribute("class")); + + this.$originalMin = [this.minwidth || 0, this.minheight || 0]; + }; + + this.$resize = function(force){ + if (!this.$amlLoaded || this.$noResize) // || apf.isIE7 && force !== true) + return; + + //Protection for stretch re-resizing + if (force !== true && this.$lastSize && + this.$lastSize[0] == this.$int.offsetWidth && + this.$lastSize[1] == this.$int.offsetHeight) + return; + + if (!apf.window.vManager.check(this, this.$uniqueId, this.$resize)) + return; +this.$noResize = true; + this.$lastSize = [this.$int.offsetWidth, this.$int.offsetHeight]; + + //this.$ext.style.border = "1px solid " + (["red", "green", "blue", "orange", "pink", "yellow"])[Math.round(Math.random() * 5)]; + + /*if (this.$table.offsetWidth >= this.$ext.offsetWidth) + this.$ext.style.minWidth = (this.minwidth = Math.max(0, this.$table.offsetWidth + - apf.getWidthDiff(this.$ext))) + "px"; + else { + this.$ext.style.minWidth = ""; + this.minwidth = this.$originalMin[0]; + } + + if (this.$table.offsetHeight >= this.$ext.offsetHeight) + this.$ext.style.minHeight = (this.minheight = Math.max(0, this.$table.offsetHeight + - apf.getHeightDiff(this.$ext))) + "px"; + else { + this.$ext.style.minHeight = ""; + this.minheight = this.$originalMin[1]; + }*/ + + //if (!this.$vbox) alert("here"); + + var total = 0; + var size = this.$vbox ? "width" : "height"; + var minsize = this.$vbox ? "minWidth" : "minHeight"; + var osize = this.$vbox ? "height" : "width"; + var scroll = this.$vbox ? "scrollWidth" : "scrollHeight"; + var offset = this.$vbox ? "offsetWidth" : "offsetHeight"; + var ooffset = this.$vbox ? "offsetHeight" : "offsetWidth"; + var getDiff = this.$vbox ? "getWidthDiff" : "getHeightDiff"; + var ogetDiff = this.$vbox ? "getHeightDiff" : "getWidthDiff"; + var inner = this.$vbox ? "getHtmlInnerWidth" : "getHtmlInnerHeight"; + var oinner = this.$vbox ? "getHtmlInnerHeight" : "getHtmlInnerWidth"; + var borders = this.$vbox ? "getVerBorders" : "getHorBorders"; + + var nodes = this.childNodes, hNodes = [], fW = 0, max = 0; + for (var node, i = 0; i < nodes.length; i++) { + if ((node = nodes[i]).nodeFunc != apf.NODE_VISIBLE || node.visible === false || !node.$amlLoaded) + continue; + + hNodes.push(node); + if (!node[size]) { + //if (!node.$skipResizeOnce) node.$skipResizeOnce = 1; + //else node.$skipResizeOnce++; + //node.$skipResizeOnce = 1 + //node.$ext.style[size] = ""; //@todo this is a sucky way of measuring + var m = node.margin && apf.getBox(node.margin); + if (m && this.$vbox) m.unshift(); + var mdiff = (m ? m[0] + m[2] : 0); + max = Math.max(max, mdiff + Math.min(node.$ext[scroll] + apf[borders](node.$ext), node["max" + size] || 10000)); + } + + if (parseInt(node.flex)) + total += parseFloat(node.flex); + else { + var m = node.margin && apf.getBox(node.margin); + if (m && !this.$vbox) m.shift(); + fW += node.$ext[ooffset] + (m ? m[0] + m[2] : 0); //this.padding + + } + } + if (!max && this[size]) { + max = this[size] + //- (this.$vbox ? this.$edge[0] + this.$edge[2] : this.$edge[1] + this.$edge[3]); + - apf[ogetDiff](this.$ext); + } + + /* + && (this[size] || this.flex) + */ + if (this.align == "stretch") { + //var hasSize = this[size] || this.flex; + var l = hNodes.length; + var pH = max;//this.$int[offset] - apf[getDiff](this.$int);// - (2 * this.padding); + for (var i = 0; i < l; i++) { + node = hNodes[i]; + + if (!node[size] && !this.$vbox || this.$vbox && input[node.$ext.tagName]) { + var m = node.margin && apf.getBox(node.margin); + if (m && this.$vbox) m.unshift(); + var mdiff = (m ? m[0] + m[2] : 0); + + /*shouldClear = !this[size] && !this.flex && node.$ext.offsetHeight == (pH - mdiff); + if (shouldClear) + node.$ext.style[size] = ""; + else + node.$ext.style[size] = Math.max(0, pH - apf[getDiff](node.$ext) - mdiff) + "px"; + node.$setResizeHeight = !shouldClear;*/ + + //!this[size] && !this.flex + if (max && Math.min(node.$ext[scroll], node["max" + size] || 10000) != max) + node.$ext.style[size] = Math.max(0, max - apf[getDiff](node.$ext) - mdiff) + "px"; + else + node.$ext.style[size] = ""; + + /*node.$ext.style[size] = !this[size] && !this.flex && node.$ext.offsetHeight == pH - mdiff + ? "" + : Math.max(0, pH - apf[getDiff](node.$ext) - mdiff) + "px";*/ + } + } + } + + //Flexing + if (total > 0) { + if (this.$vbox) + this.$int.style.height = "100%"; + this.$int.style.overflow = "hidden"; + + var splitterCount = apf.n(this).children("a:splitter").length() * 2; + + var rW = this.$int[ooffset] - apf[ogetDiff](this.$int) - fW + - ((hNodes.length - 1 - splitterCount) * this.padding);// - (2 * this.edge); + var lW = rW, done = 0; + for (var i = 0, l = hNodes.length; i < l; i++) { + if ((node = hNodes[i]).flex) { + var v = (i % 2 == 0 ? Math.floor : Math.ceil)((rW / total) * parseInt(node.flex)); + done += parseInt(node.flex); + var m = node.margin && apf.getBox(node.margin); + if (m && !this.$vbox) m.shift(); + node.$ext.style[osize] = Math.max(0, (done == total ? lW : v) - apf[ogetDiff](node.$ext) - (m ? m[0] + m[2] : 0)) + "px"; + lW -= v; + } + } + } + else { + if (this.$vbox) + this.$int.style.height = ""; + this.$int.style.overflow = ""; + } + + this.$noResize = false; + /*this.$noResize = true; + var _self = this; + setTimeout(function(){ + _self.$noResize = false; + });*/ + } + + this.$loadAml = function(x){ + if (this.padding == undefined) + this.padding = 0; + //this.$propHandlers.padding.call(this, this.padding = 0); + if (this.edge == undefined) + this.$propHandlers.edge.call(this, this.edge = 0); + if (this.pack == undefined) + this.$propHandlers.pack.call(this, this.edge = "start"); + if (this.align == undefined) + this.align = "stretch"; + //this.$propHandlers.align.call(this, this.align = "stretch"); + if (!apf.hasFlexibleBox && !this.$vbox && !this.height && this.align == "stretch") + myHeightHandler.call(this, {}); + }; +}).call(apf.vbox.prototype = new apf.GuiElement()); + +apf.hbox.prototype = apf.vbox.prototype; + +apf.aml.setElement("hbox", apf.hbox); +apf.aml.setElement("vbox", apf.vbox); + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/elements/iconmap.js)SIZE(3244)TIME(Mon, 10 Jan 2011 10:53:12 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + + +/** + * element that provides a means to get icons from a + * single image containing many icons. + * Example: + * + * + * + * Tutorials + * Contact + * + * + * + * File + * + * + * + * @attribute {String} src the location of the image. + * @attribute {String} type the spatial distribution of the icons within the image. + * Possible values: + * horizontal the icons are horizontally tiled. + * vertically the icons are vertically tiled. + * @attribute {String} size the width and height in pixels of an icon. Use this for square icons. + * @attribute {String} width the width of an icon in pixels. + * @attribute {String} height the height of an icon in pixels. + * @attribute {String} offset the distance from the calculated grid point that has to be added. This value consists of two numbers seperated by a comma. Defaults to 0,0. + * @addnode elements + * + * @author Ruben Daniels (ruben AT ajax DOT org) + * @version %I%, %G% + * @since 0.4 + */ +apf.iconmap = function(){ + this.$init("iconmap", apf.NODE_HIDDEN); +}; + +(function(){ + this.$parsePrio = "050"; + + this.addEventListener("DOMNodeInsertedIntoDocument", function(e){ + + if (!this.id) { + throw new Error(apf.formatErrorString(0, this, + "Creating icon map", + "Could not create iconmap. Missing id attribute", this.$aml)); + } + + + apf.skins.addIconMap({ + name : this.id, + src : this.src, + type : this.type, + size : parseInt(this.size), + width : parseInt(this.width), + height : parseInt(this.height), + offset : (this.offset || "0,0").splitSafe(",") + }); + }); +}).call(apf.iconmap.prototype = new apf.AmlElement()); + +apf.aml.setElement("iconmap", apf.iconmap); + + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/elements/img.js)SIZE(7692)TIME(Mon, 10 Jan 2011 10:53:12 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + + +/** + * Element displaying a picture. This element can read databound resources. + * Example: + * This example shows a list with pictures. When one is selected its displayed + * in the img element. + * + * + * + * + * + * + * + * + * + * + * + * path/to/image/[@src] + * + * + * + * + * + * @constructor + * @define img + * @allowchild {smartbinding} + * @addnode elements + * + * @inherits apf.BaseSimple + * + * @author Ruben Daniels (ruben AT ajax DOT org) + * @version %I%, %G% + * @since 0.4 + * + * @event click Fires when a user presses a mouse button while over this element. + * + * @binding value Determines the way the value for the element is retrieved + * from the bound data. + * Example: + * Sets the image source based on data loaded into this component. + * + * + * + * + * + * + */ +apf.img = function(struct, tagName){ + this.$init(tagName || "img", apf.NODE_VISIBLE, struct); +}; + +apf.preview = function(struct, tagName){ + this.$init(tagName || "preview", apf.NODE_VISIBLE, struct); +}; + +(function(){ + + + /** + * Sets the value of this element. This should be one of the values + * specified in the values attribute. + * @param {String} value the new value of this element + */ + this.setValue = function(value){ + this.setProperty("value", value, false, true); + }; + + /** + * Returns the current value of this element. + * @return {String} + */ + this.getValue = function(value){ + return this.value; + }; + + + + this.$supportedProperties.push("value", "src"); + /** + * @attribute {String} value the url location of the image displayed. + */ + this.$propHandlers["src"] = + this.$propHandlers["value"] = function(value){ + if (this.oImage.nodeType == 1) + this.oImage.style.backgroundImage = "url(" + value + ")"; + else + this.oImage.nodeValue = value; + + //@todo resize should become a generic thing + if (this.oImage.nodeType == 2 && !this.$resize.done) { + if (this.oImg) { + + //@todo add this to $destroy + var pNode = apf.hasSingleRszEvent ? this.$pHtmlNode : this.$ext; + apf.layout.setRules(pNode, this.$uniqueId + "_image", + "var o = apf.all[" + this.$uniqueId + "];\ + if (o) o.$resize()"); + apf.layout.queue(pNode); + + this.oImg.onload = function(){ + apf.layout.forceResize(pNode); + } + + } + + this.$resize.done = true; + } + + if (this.oImg) { + this.oImg.style.display = value ? "block" : "none"; + + //RLD: disabled lines below for the preview element. the image is probably not loaded yet. + //if (value) + //this.$resize(); + } + }; + + this.refetch = function(){ + this.$propHandlers["value"].call(this, "") + this.$propHandlers["value"].call(this, this.value || this.src) + } + + this.addEventListener("$clear", function(){ + this.value = ""; + + if (this.oImg) + this.oImg.style.display = "none"; + }); + + /**** Init ****/ + + this.$draw = function(){ + //Build Main Skin + this.$ext = this.$getExternal(); + this.$ext.onclick = function(e){ + this.host.dispatchEvent("click", {htmlEvent: e || event}); + }; + this.oImage = this.$getLayoutNode("main", "image", this.$ext); + if (this.oImage.nodeType == 1) + this.oImg = this.oImage.getElementsByTagName("img")[0]; + if (this.localName == "preview") { + var _self = this; + this.$ext.onclick = function() { + if (!_self.sPreview) return; + _self.$ext.innerHTML = _self.sPreview; + this.onclick = null; + }; + } + + var _self = this; + apf.addListener(this.$ext, "mouseover", function(e) { + if (!_self.disabled) + _self.dispatchEvent("mouseover", {htmlEvent: e}); + }); + + apf.addListener(this.$ext, "mouseout", function(e) { + if (!_self.disabled) + _self.dispatchEvent("mouseout", {htmlEvent: e}); + }); + }; + + this.addEventListener("DOMNodeInsertedIntoDocument", function() { + var node, + val = "", + i = this.childNodes.length; + + for (; i >= 0; --i) { + if ((node = this.childNodes[i]) && node.nodeName + && node.nodeName == "#cdata-section") { + val = node.nodeValue; + node.removeNode(); + } + } + + this.sPreview = val; + }); + + this.$resize = function(){ + var diff = apf.getDiff(this.$ext); + var wratio = 1, hratio = 1; + + this.oImg.style.width = ""; + this.oImg.style.height = ""; + + if (this.oImg.offsetWidth > this.$ext.offsetWidth) + wratio = this.oImg.offsetWidth / (this.$ext.offsetWidth - diff[0]); + if (this.oImg.offsetHeight > this.$ext.offsetHeight) + hratio = this.oImg.offsetHeight / (this.$ext.offsetHeight - diff[1]); + + if (wratio > hratio && wratio > 1) + this.oImg.style.width = "100%"; + else if (hratio > wratio && hratio > 1) + this.oImg.style.height = "100%"; + + this.oImg.style.top = ((this.$ext.offsetHeight - apf.getHeightDiff(this.$ext) + - this.oImg.offsetHeight) / 2) + "px"; + } +}).call(apf.img.prototype = new apf.BaseSimple()); + +apf.preview.prototype = apf.img.prototype; + +apf.aml.setElement("img", apf.img); +apf.aml.setElement("preview", apf.preview); + +apf.aml.setElement("name", apf.BindingRule); +apf.aml.setElement("image", apf.BindingRule); + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/elements/item.js)SIZE(23745)TIME(Mon, 10 Jan 2011 10:53:12 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + +/** + * Item of a menu displaying a clickable area. + * Example: + * + * + * Tutorials + * Contact + * + * + * + * File + * + * + * + * @define item + * @constructor + * + * @event click Fires when a user presses the mouse button while over this element. + * object: + * {XMLElement} xmlContext the xml data node that was selected in the opener at the time of showing the context menu. + * {AMLElement} opener the element that was clicked upon when showing the context menu. + */ +apf.item = function(struct, tagName){ + this.$init(tagName || "item", apf.NODE_VISIBLE, struct); +}; + +(function(){ + this.$focussable = false; + this.$childProperty = "caption"; + this.$canLeechSkin = "item"; + + this.checked = false; + this.selected = false; + + this.implement(apf.ChildValue); + + /**** Properties and Attributes ****/ + + //1 = force no bind rule, 2 = force bind rule + this.$attrExcludePropBind = apf.extend({ + "match" : 1 + }, this.$attrExcludePropBind); + + this.$supportedProperties.push("submenu", "value", "match", "group", "icon", + "checked", "selected", "disabled", "caption", + "type"); + + /** + * @attribute {String} [submenu] the id of the menu that is shown + * when the user hovers over this menu item. + * Example: + * + * + * test + * test2 + * + * + * + * Sub menu + * + * + * + * + * File + * + * + * + */ + this.$propHandlers["submenu"] = function(value){ + apf.setStyleClass(this.$ext, "submenu"); + } + + /** + * @attribute {String} value the value of this element. + */ + + /** + * @attribute {String} [select] the xpath statement which works on the + * xml context of the parent menu element to determine whether this + * item is shown. + * Example: + * This example shows a list + * + * + * Send an E-mail + * Call Number + * + * Remove + * + * View Pictures + * + * + * + * Reboot + * + * + * + * Please right-click on this plane + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + */ + this.$propHandlers["select"] = function(value){ + this.select = value + ? "self::" + value.split("|").join("|self::") + : value; + } + + /** + * @attribute {String} [group] the name of the group this item belongs + * to. + * Example: + * + * + * item 1 + * item 2 + * item 3 + * item 4 + * + * + */ + this.$propHandlers["group"] = function(value){ + if (this.$group && this.$group.$removeRadio) + this.$group.$removeRadio(this); + + if (!value) { + this.$group = null; + return; + } + + var group = typeof value == "string" + ? + + apf.nameserver.get("radiogroup", value) + + : value; + if (!group) { + + group = apf.nameserver.register("radiogroup", value, + new apf.$group()); + group.setAttribute("id", value); + group.dispatchEvent("DOMNodeInsertedIntoDocument"); + group.parentNode = this; + + } + this.$group = group; + + this.$group.$addRadio(this); + }; + + + /** + * @attribute {String} hotkey the key combination a user can press + * to active the function of this element. Use any combination of + * Ctrl, Shift, Alt, F1-F12 and alphanumerical characters. Use a + * space, a minus or plus sign as a seperator. + * Example: + * + * Quit + * + */ + this.$propHandlers["hotkey"] = function(value){ + if (this.$hotkey) + apf.setNodeValue(this.$hotkey, apf.isMac ? apf.hotkeys.toMacNotation(value) : value); + + if (this.$lastHotkey) + apf.hotkeys.remove(this.$lastHotkey); + + if (value) { + this.$lastHotkey = value; + var _self = this; + apf.hotkeys.register(value, function(){ + if (_self.disabled || !_self.visible) + return; + + //hmm not very scalable... + var buttons = apf.document.getElementsByTagNameNS(apf.ns.aml, "button"); + for (var i = 0; i < buttons.length; i++) { + if (buttons[i].submenu == _self.parentNode.name) { + var btn = buttons[i]; + btn.$setState("Over", {}); + + $setTimeout(function(){ + btn.$setState("Out", {}); + }, 200); + + break; + } + } + + _self.$down(); + _self.$up(); + _self.$click(); + }); + } + } + + /** + * @attribute {String} icon the url of the image used as an icon or + * a reference to an iconmap. + */ + this.$propHandlers["icon"] = function(value){ + if (this.$icon) + apf.skins.setIcon(this.$icon, value, this.parentNode.iconPath); + } + + /** + * @attribute {String} caption the text displayed on the item. + */ + this.$propHandlers["caption"] = function(value){ + if (this.$caption) + apf.setNodeValue(this.$caption, value); + } + + /** + * @attribute {String} type the function of this item + * Possible values: + * item + * check + * radio + */ + this.$propHandlers["type"] = function(value){ + apf.setStyleClass(this.$ext, value, ["item", "check", "radio"]); + } + + this.$values = [1, 0]; + this.$propHandlers["values"] = function(value){ + this.$values = value && value.split("|"); + } + + this.$propHandlers["value"] = function(value){ + if (this.type != "check") + return; + + this.setProperty("checked", this.$values.indexOf(value) == 0); + } + + /** + * @attribute {Boolean} checked whether the item is checked. + */ + this.$propHandlers["checked"] = function(value){ + if (this.type != "check") + return; + + if (apf.isTrue(value)) + apf.setStyleClass(this.$ext, "checked"); + else + apf.setStyleClass(this.$ext, "", ["checked"]); + + if (this.$values && this.$values[value ? 0 : 1] != this.value) + return this.setProperty("value", this.$values[value ? 0 : 1]); + } + + this.select = function(){ + this.parentNode.select(this.group, this.value || this.caption); + } + + this.check = function(){ + this.setProperty("checked", true); + } + this.uncheck = function(){ + this.setProperty("checked", false); + } + + this.$check = function(){ + apf.setStyleClass(this.$ext, "selected"); + } + + this.$uncheck = function(){ + apf.setStyleClass(this.$ext, "", ["selected"]); + } + + /** + * @attribute {Boolean} selected whether the item is selected. + */ + this.$propHandlers["selected"] = function(value){ + if (this.type != "radio") + return; + + if (this.$group) + this.$group.setProperty("value", this.value); + + if (apf.isTrue(value)) + this.$check() + else + this.$uncheck(); + } + + /** + * @attribute {Boolean} disabled whether the item is active. + */ + this.$propHandlers["disabled"] = function(value){ + if (apf.isTrue(value)) + apf.setStyleClass(this.$ext, "disabled"); + else + apf.setStyleClass(this.$ext, "", ["disabled"]); + } + + /**** Dom Hooks ****/ + + //@todo apf3.0 + this.addEventListener("AMLReparent", function(beforeNode, pNode, withinParent){ + if (!this.$amlLoaded) + return; + + if (!withinParent && this.skinName != pNode.skinName) { + //@todo for now, assuming dom garbage collection doesn't leak + this.loadAml(); + } + }); + + /**** Events ****/ + + this.$down = function(){ + + }; + + this.$up = function(){ + if (this.type == "radio") + this.parentNode.select(this.group, this.value || this.caption); + + else if (this.type == "check") { + this.setProperty("checked", !this.checked); + //this.$handlePropSet("checked", !this.checked); + } + + if (this.submenu) { + this.$over(null, true); + return; + } + + this.parentNode.$hideTree = true; + + //@todo This statement makes the menu loose focus. + this.parentNode.hide();//true not focus?/ + + this.parentNode.dispatchEvent("itemclick", { + value : this.value || this.caption, + relatedNode : this, + checked : this.checked, + selected : this.selected + }); + + //@todo Anim effect here? + + this.dispatchEvent("click", { + xmlContext : this.parentNode.xmlReference, + opener : this.parentNode.opener + }); + }; + + this.$click = function(){ + + }; + + var timer; + this.$out = function(e){ + if (apf.isChildOf(this.$ext, e.toElement || e.explicitOriginalTarget) + || apf.isChildOf(this.$ext, e.srcElement || e.target)) //@todo test FF + return; + + clearTimeout(timer); + if (!this.submenu || this.$submenu(true)) { + apf.setStyleClass(this.$ext, '', ['hover']); + + var sel = this.parentNode.$selected; + if (sel && sel != this) + apf.setStyleClass(sel.$ext, "", ["hover"]); + + this.parentNode.$selected = null; + } + }; + + this.$over = function(e, force){ + if (this.parentNode.$selected == this) + return; + + if (this.parentNode.$selected) + apf.setStyleClass(this.parentNode.$selected.$ext, "", ["hover"]); + + apf.setStyleClass(this.$ext, "hover"); + this.parentNode.$selected = this; + + if (!force && (apf.isChildOf(this.$ext, e.toElement || e.explicitOriginalTarget) + || apf.isChildOf(this.$ext, e.fromElement || e.target))) //@todo test FF + return; + + var _self = this, ps = this.parentNode.$showingSubMenu; + if (ps && ps.name == this.submenu) + return; + + clearTimeout(timer); + + function submenu(){ + if (ps && ps.visible) { + ps.hide(); + + if (_self.parentNode.$showingSubMenu == ps) + _self.parentNode.$showingSubMenu = null; + } + + if (_self.submenu && _self.parentNode.opener + && _self.parentNode.opener.visible) + _self.$submenu(); + } + + if (force) + submenu(); + else { + timer = $setTimeout(function(){ + submenu(); + timer = null; + }, 210); + } + }; + + this.$submenu = function(hide, force){ + if (!this.submenu) + return true; + + var menu = self[this.submenu]; + if (!menu) { + + throw new Error(apf.formatErrorString(0, this, + "Displaying submenu", + "Could not find submenu '" + this.submenu + "'", this.$aml)); + + + return; + } + + if (!hide) { + //if (this.parentNode.showingSubMenu == this.submenu) + //return; + + this.parentNode.$showingSubMenu = menu; + + var pos = apf.getAbsolutePosition(this.$ext, this.parentNode.$ext.offsetParent); + menu.display(pos[0] + this.$ext.offsetWidth - 3, + pos[1] + 3, true, this, + this.parentNode.xmlReference, this.parentNode.$uniqueId); + menu.setAttribute("zindex", (this.parentNode.zindex || 1) + 1); + } + else { + if (menu.visible && !force) { + return false; + } + + if (this.parentNode.$showingSubMenu == menu) + this.parentNode.$showingSubMenu = null; + + apf.setStyleClass(this.$ext, '', ['hover']); + menu.hide(); + return true; + } + }; + + /**** Init ****/ + + this.$draw = function(isSkinSwitch){ + var p = this.parentNode; + while (p.$canLeechSkin == "item") + p = p.parentNode; + + //@todo apf3.0 rename doesnt work yet. + //@todo apf3.0 implement DOM Mutation events for multiselect widgets + //@todo apf3.0 implement attribute change triggers for icon, image, value, caption to updateNode this.$container + //@todo apf3.x this should be rearchitected + //@todo apf3.x the functions dont need to be overloaded if selectNodes would work properly + if (p.hasFeature(apf.__MULTISELECT__)) { + var _self = this; + + //@todo DOMNodeInserted should reset this + //@todo DOMNodeRemoved should reset this + if (!this.$hasSetSkinListener) { + var f; + this.parentNode.addEventListener("$skinchange", f = function(){ + if (_self.$amlDestroyed) //@todo apf3.x + return; + + if (_self.$ext.parentNode) + this.$deInitNode(_self, _self.$ext); + + var oInt = p == _self.parentNode ? p.$container : _self.parentNode.$container; + var node = oInt.lastChild;//@todo this should be more generic + p.$add(_self, _self.getAttribute(apf.xmldb.xmlIdTag) + "|" + this.$uniqueId, + _self.parentNode, oInt != p.$container && oInt, null); + p.$fill(); + + if (p.$isTreeArch) { + _self.$container = p.$getLayoutNode("item", "container", + _self.$ext = node && node.nextSibling || oInt.firstChild);//@todo this should be more generic + } + else _self.$ext = node && node.nextSibling || oInt.firstChild; + + var ns = _self; + while((ns = ns.nextSibling) && ns.nodeType != 1); + + if (!ns || ns.$canLeechSkin != "item") + p.dispatchEvent("afterload"); + }); + this.addEventListener("DOMNodeRemoved", function(e){ + if (e.currentTarget == this) + this.parentNode.removeEventListener("$skinchange", f); + }); + + this.$hasSetSkinListener = true; + } + + if (!p.$itemInited) { + p.canrename = false; //@todo fix rename + p.$removeClearMessage(); //@todo this should be more generic + p.$itemInited = [p.getTraverseNodes, p.getFirstTraverseNode, p.getTraverseParent]; + + p.getTraverseNodes = function(xmlNode){ + return (xmlNode || p).getElementsByTagNameNS(apf.ns.apf, "item"); + } + p.getFirstTraverseNode = function(xmlNode){ + return (xmlNode || p).getElementsByTagNameNS(apf.ns.apf, "item")[0]; + } + p.getTraverseParent = function(xmlNode){ + return xmlNode && xmlNode.parentNode; + } + p.each = (this.prefix ? this.prefix + ":" : "") + "item"; + + //@todo this is all an ugly hack (copied to baselist.js line 868) + p.$preventDataLoad = true;//@todo apf3.0 add remove for this + + p.$initingModel = true; + p.$setDynamicProperty("icon", "[@icon]"); + p.$setDynamicProperty("image", "[@image]"); + p.$setDynamicProperty("caption", "[label/text()|@caption|text()]"); + p.$setDynamicProperty("eachvalue", "[value/text()|@value|text()]"); + p.$canLoadDataAttr = false; + + if (!p.xmlRoot) + p.xmlRoot = p; + } + + this.$loadAml = function(){ + //hack + if (!this.getAttribute("caption")) + this.setAttribute("caption", this.caption); + + var oInt = p == this.parentNode ? p.$container : this.parentNode.$container; + var node = oInt.lastChild;//@todo this should be more generic + if (!p.documentId) + p.documentId = apf.xmldb.getXmlDocId(this); + p.$add(this, apf.xmldb.nodeConnect(p.documentId, this, null, p), + this.parentNode, oInt != p.$container && oInt, null); + p.$fill(); + + if (p.$isTreeArch) { + this.$container = p.$getLayoutNode("item", "container", + this.$ext = node && node.nextSibling || oInt.firstChild);//@todo this should be more generic + } + else this.$ext = node && node.nextSibling || oInt.firstChild; + + var ns = this; + while((ns = ns.nextSibling) && ns.nodeType != 1); + + if (!ns || ns.$canLeechSkin != "item") { + p.dispatchEvent("afterload"); + if (p.autoselect) + p.$selectDefault(this.parentNode); + } + } + + return; + } + + this.$ext = this.$getExternal(this.$isLeechingSkin + ? "item" //this.type + : "main", null, function($ext){ + var o = 'var o = apf.lookup(' + this.$uniqueId + '); if (o.disabled) return; o'; + $ext.setAttribute("onmouseup", o + '.$up(event)'); + $ext.setAttribute("onmousemove", o + '.$over(event)'); + $ext.setAttribute("onmouseout", o + '.$out(event)'); + $ext.setAttribute("onmousedown", o + '.$down()'); + $ext.setAttribute("onclick", o + '.$click()'); + }); + + /*p.$getNewContext("item"); + var elItem = p.$getLayoutNode("item");*/ + + //@todo if not elItem try using own skin + + //this.$ext = apf.insertHtmlNode(elItem, this.parentNode.$container); + this.$caption = this.$getLayoutNode("item", "caption", this.$ext) + this.$icon = this.$getLayoutNode("item", "icon", this.$ext); + this.$hotkey = this.$getLayoutNode("item", "hotkey", this.$ext); + + if (!isSkinSwitch && this.nextSibling && this.nextSibling.$ext) + this.$ext.parentNode.insertBefore(this.$ext, this.nextSibling.$ext); + }; + + /** + * @private + */ + this.addEventListener("DOMNodeInsertedIntoDocument", function(e){ + //var x = this.$aml; + + //this.skinName = this.parentNode.skinName; + var isSkinSwitch = this.$ext ? true : false; + if (isSkinSwitch) { + if (typeof this.checked !== "undefined") + this.$handlePropSet("checked", this.checked); + else if (typeof this.selected !== "undefined") + this.$handlePropSet("selected", this.selected); + + if (this.disabled) + this.$handlePropSet("disabled", this.disabled); + + if (this.caption) + this.$handlePropSet("caption", this.caption); + } + }); +}).call(apf.item.prototype = new apf.Presentation()); + +//apf.aml.setElement("radio", apf.radio); +//apf.aml.setElement("check", apf.check); +apf.aml.setElement("item", apf.item); + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/elements/junction.js)SIZE(2555)TIME(Mon, 10 Jan 2011 10:53:12 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + + +/** + * A symbolic link to an AML element. The visibility of this element determines + * the true parent of the references AML element. Multiple junctions will compete + * to determine the parent. Make sure only one junction reference is visible + * at the same time. + * + * @author Ruben Daniels (ruben AT ajax DOT org) + * @version %I%, %G% + * @since 0.4 + */ +apf.junction = function(){ + this.$init("junction", apf.NODE_HIDDEN); +}; + +(function(){ + this.$focussable = false; + + this.autoshow = true; + + /** + * @attribute {String} for + * @attribute {String} autoshow + */ + this.$booleanProperties["autoshow"] = true; + + this.$propHandlers["for"] = function(value){ + if (this.$amlLoaded) //@todo remove vManager + init.call(this); + } + + function init(e){ + var _self = this; + if (apf.window.vManager.permanent(this.parentNode, function(){ + //Show + _self.$reparent(); + }, function(){ + //Hide + + })) { + this.$reparent(); + } + } + + this.$reparent = function(){ + var amlNode = self[this["for"]]; + if (!amlNode) + return; //@todo warn? + + if (this.autoshow) + amlNode.show(); + + this.parentNode.insertBefore(amlNode, this); + } + + this.addEventListener("DOMNodeInsertedIntoDocument", init); +}).call(apf.junction.prototype = new apf.AmlElement()); + +apf.aml.setElement("junction", apf.junction); + + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/elements/label.js)SIZE(4978)TIME(Mon, 10 Jan 2011 10:53:12 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + +/** + * Element displaying a text in the user interface, usually specifying + * a description of another element. When the user clicks on the label it + * can set the focus to the connected aml element. + * Example: + * This example uses the for attribute to connect the label to the form element. + * + * Address + * + * + * + * @constructor + * @allowchild {smartbinding} + * @addnode elements + * + * @inherits apf.BaseSimple + * + * @author Ruben Daniels (ruben AT ajax DOT org) + * @version %I%, %G% + * @since 0.4 + * + * @binding value Determines the way the value for the element is retrieved + * from the bound data. + * Example: + * Sets the label text based on data loaded into this component. + * + * + * + * + * + * + * Example: + * A shorter way to write this is: + * + * + * + * + * + * + */ +apf.label = function(struct, tagName){ + this.$init(tagName || "label", apf.NODE_VISIBLE, struct); +}; + +(function(){ + this.implement( + + apf.DataAction, + + apf.ChildValue + ); + + var _self = this; + + this.$focussable = false; + var forElement; + + + + /** + * Sets the value of this element. This should be one of the values + * specified in the values attribute. + * @param {String} value the new value of this element + */ + this.setValue = function(value){ + this.setProperty("value", value, false, true); + }; + + /** + * Returns the current value of this element. + * @return {String} + */ + this.getValue = function(){ + return this.value; + } + + + + /** + * @attribute {String} caption the text displayed in the area defined by this + * element. Using the value attribute provides an alternative to using + * the text using a text node. + * + * @attribute {String} for the id of the element that receives the focus + * when the label is clicked on. + */ + this.$supportedProperties.push("caption", "for", "textalign"); + this.$propHandlers["caption"] = function(value){ + this.$caption.innerHTML = value; + }; + this.$propHandlers["for"] = function(value){ + forElement = typeof value == "string" ? self[value] : value; + }; + this.$propHandlers["textalign"] = function(value){ + this.$caption.style.textAlign = value || ""; + }; + + this.$draw = function(){ + //Build Main Skin + this.$ext = this.$getExternal(); + this.$caption = this.$getLayoutNode("main", "caption", this.$ext); + if (this.$caption.nodeType != 1) + this.$caption = this.$caption.parentNode; + + this.$ext.onmousedown = function(){ + if (forElement && forElement.$focussable && forElement.focussable) + forElement.focus(); + } + + var _self = this; + apf.addListener(this.$ext, "click", function(e) { + if (!_self.disabled) + _self.dispatchEvent("click", {htmlEvent: e}); + }); + + apf.addListener(this.$ext, "mouseover", function(e) { + if (!_self.disabled) + _self.dispatchEvent("mouseover", {htmlEvent: e}); + }); + + apf.addListener(this.$ext, "mouseout", function(e) { + if (!_self.disabled) + _self.dispatchEvent("mouseout", {htmlEvent: e}); + }); + }; + + this.$childProperty = "caption"; + +}).call(apf.label.prototype = new apf.BaseSimple()); + +apf.aml.setElement("label", apf.label); + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/elements/lineselect.js)SIZE(4747)TIME(Mon, 10 Jan 2011 10:53:12 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + + +/** + * Element displaying text with each line being selectable. This is especially + * useful for log messages. + * + * @experimental + * @todo test this + */ +apf.lineselect = function(struct, tagName){ + this.$init(tagName || "lineselect", apf.NODE_VISIBLE, struct); +}; + +(function(){ + this.deselect = function(){ + this.value = this.selected = null; + } + + this.select = function(node){ + if(this.selected) this.selected.className = this.skin.clsItem; + + node.className = this.skin.clsSelected; + this.selected = node; + + this.dispatchEvent("afterselect", node.innerHTML); + } + + this.$focus = function(){ + if(this.selected) this.selected.className = this.skin.clsSelected; + } + + this.$blur = function(){ + if(this.selected) this.selected.className = this.skin.clsBlur; + } + + this.$focussable = true; + + this.getHTML = function(t){ + t = t.replace(//g, ">"); + t = t.replace(/(^|\n)[^\(]*\(/g, "\n("); + return t; + } + + this.loadText = function(text){ + //this.parentNode.style.width = this.parentNode.offsetWidth; + + var ar = this.getHTML(text).split("\n"); + + for(var i=0;i" + ar[i] + "
    "; + } + + this.$ext.innerHTML = "
    APF Packager
    (c) 2001-2003 All Rights Reserved.

    " + ar.join(""); + //this.parentNode.style.width = "100%"; + } + + + this.addEventListener("keydown", function(e){ + var key = e.keyCode; + + if(this.renaming){ + if(key == 27 || key == 13){ + this.stopRename(); + if(key == 13) this.stopRename(); + } + + return; + } + + if(!this.selected) return; + + switch(key){ + case 13: + this.dispatchEvent("choose"); + break; + case 38: + //UP + if(!this.value) return; + var node = this.value; + + if(node.previousSibling) node = node.previousSibling; + if(node && node.nodeType == 1) this.select(document.getElementById(node.getAttribute("id"))); + + //this.selected.scrollIntoView(); + return false; + break; + case 40: + //DOWN + if(!this.value) return; + var node = this.value; + + if(node.nextSibling) node = node.nextSibling; + if(node && node.nodeType == 1) this.select(document.getElementById(node.getAttribute("id"))); + + //this.selected.scrollIntoView(); + return false; + break; + } + }, true); + + + /**** Init ****/ + + this.$draw = function(clear, parentNode){ + this.$ext = this.$getExternal(); + this.$int = this.$ext; + } +}).call(apf.lineselect.prototype = new apf.GuiElement()); + +apf.aml.setElement("lineselect", apf.lineselect); + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/elements/list.js)SIZE(14336)TIME(Mon, 10 Jan 2011 10:53:12 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + +/** + * Element displaying a skinnable list of options which can be selected. + * Selection of multiple items can be allowed. Items can be renamed + * and removed. The list can be used as a collection of checkboxes or + * radiobuttons. This is especially useful for use in forms. + * This element is one of the most often used elements. It can display lists + * of items in a cms style interface or display a list of search results in + * a more website like interface. + * Example: + * A simple list with inline items. + * + * + * The Netherlands + * United States of America + * United Kingdom + * ... + * + * + * Example: + * A databound list with items loaded from an xml file. + * + * + * + * Example: + * A databound list using the bindings element + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * @event click Fires when a user presses a mouse button while over this element. + * + * @constructor + * @define list, select, select1, thumbnail + * @allowchild {smartbinding} + * @addnode elements + * + * @inherits apf.BaseList + * @inherits apf.Rename + * + * @author Ruben Daniels (ruben AT ajax DOT org) + * @version %I%, %G% + * @since 0.4 + */ +apf.list = function(struct, tagName){ + this.$init(tagName || "list", apf.NODE_VISIBLE, struct); +}; + +/** + * Example: + * A small product search application using a list to display results. + * + * + *

    Search for a product

    + * + * Search + *
    + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * function search(){ + * mdlSearch.$loadFrom("http://localhost/search.php?keyword=" + txtSearch.getValue()); + * } + * + *
    + */ +apf.thumbnail = function(struct, tagName){ + this.$init(tagName || "thumbnail", apf.NODE_VISIBLE, struct); +}; + +apf.select = function(struct, tagName){ + this.$init(tagName || "select", apf.NODE_VISIBLE, struct); +}; + +apf.select1 = function(struct, tagName){ + this.$init(tagName || "selectl", apf.NODE_VISIBLE, struct); +}; + +(function(){ + this.morePos = "end"; + + + if (!apf.isIphone) + this.implement(apf.Rename); + + + + this.$getCaptionElement = function(){ + if (!(this.$caret || this.$selected)) + return; + + var x = this.$getLayoutNode("item", "caption", this.$caret || this.$selected); + if (!x) + return; + return x.nodeType == 1 ? x : x.parentNode; + }; + + + + this.$selectSeries = function(e) { + e = e || event; + //e.cancelBubble = true; + var target = e.target || e.srcElement; + + if (e.type == "mouseover") { + var target = (target.className || "").indexOf("item") != -1 + ? target + : target.parentNode; + + this.highlight(target); + } + else { + target = e.toElement + ? e.toElement + : (e.relatedTarget + ? e.relatedTarget + : null); + + if (!apf.isChildOf(this.$ext, target, true)) { + this.highlight(this.$selected); + } + } + }; + + this.highlight = function(target) { + var options = this.$ext.childNodes; + var options_len = options.length; + var deselect = false; + + for (var i = 0; i < options_len; i++) { + if ((options[i].className || "").indexOf("item") != -1) { + if (!deselect) { + this.$setStyleClass(options[i], "selected"); + } + else { + this.$setStyleClass(options[i], "", ["selected"]); + } + + if (options[i] == target) { + deselect = true; + } + } + } + }; + + + + + this.addEventListener("afterselect", function(e){ + if (this.hasFeature(apf.__VALIDATION__)) + this.validate(true); + }); + + + /**** Properties and Attributes ****/ + + this.$supportedProperties.push("appearance", "mode", "more", "thumbsize", "morepos"); + + this.$propHandlers["morepos"] = function(value) { + this.morePos = value; + }; + + this.$propHandlers["thumbsize"] = function(value){ + var className = this.thumbclass; + + if (apf.isIE) { //@todo detection?? + className = className.splitSafe(","); + for (var i = 0; i < className.length; i++) { + apf.setStyleRule(className[i], "width", value + "px"); + apf.setStyleRule(className[i], "height", value + "px"); + } + return; + } + + apf.setStyleRule(className, "width", value + "px"); + apf.setStyleRule(className, "height", value + "px"); + }; + + + /** + * @attribute {String} appearance the type of select this element is. + * This is an xforms property and only available if apf is compiled + * with __WITH_XFORMS set to 1. + * Possible values: + * full depending on the tagName this element is either a list of radio options or of checked options. + * compact this elements functions like a list with multiselect off. + * minimal this element functions as a dropdown element. + */ + this.$propHandlers["appearance"] = function(value){ + + }; + + + /** + * @attribute {String} more Adds a new item to the list and lets the users + * type in the new name. This is especially useful in the interface when + * {@link element.list.attribute.mode} is set to check or radio. For instance in a form. + * Example: + * This example shows a list in form offering the user several options. The + * user can add a new option. A server script could remember the addition + * and present it to all new users of the form. + * + * + * + * + * Suggestion 1 + * Suggestion 2 + * + * + * + * Which newspapers do you read? + * + * + * + * + * + * + * + * + * + * + * New Answer + * + * + * + * + */ + this.$propHandlers["more"] = function(value){ + if (value) { + this.delayedselect = false; + this.addEventListener("xmlupdate", $xmlUpdate); + this.addEventListener("afterload", $xmlUpdate); + //this.addEventListener("afterrename", $afterRenameMore); + //this.addEventListener("beforeselect", $beforeSelect); + + this.$addMoreItem = function(msg){ + if (!this.moreItem) + this.$fill(); + if (this.morePos == "begin") + this.$container.insertBefore(this.moreItem, this.$container.firstChild); + else + this.$container.appendChild(this.moreItem); + }; + this.$updateClearMessage = function(){} + this.$removeClearMessage = function(){}; + } + else { + this.removeEventListener("xmlupdate", $xmlUpdate); + this.removeEventListener("afterload", $xmlUpdate); + //this.removeEventListener("afterrename", $afterRenameMore); + //this.removeEventListener("beforeselect", $beforeSelect); + } + }; + + function $xmlUpdate(e){ + if ((!e.action || "insert|add|synchronize|move".indexOf(e.action) > -1) && this.moreItem) { + if (this.morePos == "begin") + this.$container.insertBefore(this.moreItem, this.$container.firstChild); + else + this.$container.appendChild(this.moreItem); + } + } + + /*function $afterRenameMore(){ + var caption = this.$applyBindRule("caption", this.caret) + var xmlNode = this.findXmlNodeByValue(caption); + + var curNode = this.caret; + if (xmlNode != curNode || !caption) { + if (xmlNode && !this.isSelected(xmlNode)) + this.select(xmlNode); + this.remove(curNode); + } + else + if (!this.isSelected(curNode)) + this.select(curNode); + } + + function $beforeSelect(e){ + //This is a hack + if (e.xmlNode && this.isSelected(e.xmlNode) + && e.xmlNode.getAttribute('custom') == '1') { + this.setCaret(e.xmlNode); + this.selected = e.xmlNode; + $setTimeout(function(){ + _self.startRename() + }); + return false; + } + }*/ + + + /**** Keyboard support ****/ + + + this.addEventListener("keydown", this.$keyHandler, true); + + + /**** Init ****/ + + this.$draw = function(){ + this.appearance = this.getAttribute("appearance") || "compact"; + + //Build Main Skin + this.$ext = this.$getExternal(); + this.$container = this.$getLayoutNode("main", "container", this.$ext); + + if (apf.hasCssUpdateScrollbarBug && !this.mode) + this.$fixScrollBug(); + + var _self = this; + this.$ext.onclick = function(e){ + _self.dispatchEvent("click", { + htmlEvent: e || event + }); + } + + + this.$gridlist = apf.isTrue(this.$getOption("main", "grid")); + + if (this.$gridlist) { + this.$ext.setAttribute("onmouseout", this.$ext.getAttribute("onmouseout") + + ';var o = apf.lookup(' + this.$uniqueId + ');o.$selectSeries(event);'); + } + + + //Get Options form skin + //Types: 1=One dimensional List, 2=Two dimensional List + this.listtype = parseInt(this.$getOption("main", "type")) || 1; + //Types: 1=Check on click, 2=Check independent + this.behaviour = parseInt(this.$getOption("main", "behaviour")) || 1; + + this.thumbsize = this.$getOption("main", "thumbsize"); + this.thumbclass = this.$getOption("main", "thumbclass"); + }; + + this.$loadAml = function(x){ + }; + + this.$destroy = function(){ + if (this.$ext) + this.$ext.onclick = null; + apf.destroyHtmlNode(this.oDrag); + this.oDrag = null; + }; +}).call(apf.list.prototype = new apf.BaseList()); + +apf.thumbnail.prototype = +apf.select.prototype = +apf.select1.prototype = apf.list.prototype; + +apf.aml.setElement("thumbnail", apf.thumbnail); +apf.aml.setElement("select", apf.select); +apf.aml.setElement("select1", apf.select1); +apf.aml.setElement("list", apf.list); + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/elements/loader.js)SIZE(2153)TIME(Mon, 10 Jan 2011 10:53:12 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + + +/** + * @todo description + * + * @author Ruben Daniels (ruben AT ajax DOT org) + * @version %I%, %G% + * @since 0.4 + */ +apf.loader = function(){ + this.$init("loader", apf.NODE_HIDDEN); + + this.show = function(){ + this.$ext.style.display = "block"; + } + + this.hide = function(){ + this.$ext.style.display = "none"; + } + + this.addEventListener("DOMNodeInsertedIntoDocument", function(e){ + var pHtmlNode; + if (!(pHtmlNode = this.parentNode.$int)) + return; + + this.$ext = apf.insertHtmlNode(null, pHtmlNode, null, (this.$aml + ? (this.$aml.serialize ? this.$aml.serialize() : this.$aml.xml) + : this.serialize()).replace(/^<[^>]*>\s*|\s*<[^>]*>$/g, "")); + + if (!apf.loaded) { + var _self = this; + apf.addEventListener("load", function(){ + if (apf.config.autoHideLoading) { + apf.queue.empty(); + _self.hide(); + } + }); + } + }); +}; + +apf.loader.prototype = new apf.AmlElement(); + +apf.aml.setElement("loader", apf.loader); + + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/elements/loadindicator.js)SIZE(5234)TIME(Mon, 10 Jan 2011 10:53:12 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + + +/** + * Element shows a nice animated loader + * where you can use any time of image + * Example: + * This example shows a spinner + * + * + * + * + * spinner.start() + * + * + * @constructor + * @allowchild {smartbinding} + * @addnode elements:loadindicator + * + * + * @author Giannis Panagiotou (bone.jp AT gmail DOT com) + * @version %I%, %G% + * @since 2.0 + * + */ +apf.loadindicator = function(struct, tagName){ + this.$init(tagName || "loadindicator", apf.NODE_VISIBLE, struct); +}; + +(function() { + this.spinnerspeed = 66; + this.$running = false; + this.$loadingFrame = 1; + this.framelength = 12; + this.frameheight = 40; + this.framewidth = 40; + this.$timer; + + this.$supportedProperties.push("framewidth", "frameheight", "src", "framelength", "spinnerspeed"); + + this.$propHandlers["framewidth"] = function(value) { + this.framewidth = value; + this.oSpinner.style.width = value + 'px'; + this.oContainer.style.width = value + 'px'; + }; + + this.$propHandlers["frameheight"] = function(value) { + this.frameheight = value; + this.oContainer.style.height = value + 'px'; + }; + + this.$propHandlers["framelength"] = function(value) { + this.framelength = value; + this.oSpinner.style.height = value * this.frameheight + 'px'; + }; + + this.$propHandlers["spinnerspeed"] = function(value) { + this.spinnerspeed = value; + }; + + /** + * @attribute {String} value the url location of the image displayed. + */ + this.$propHandlers["src"] = function(value){ + this.oSpinner.style.backgroundImage = "url(" + value + ")"; + }; + + /** + * Starts the spinner indicator. + * @param {Number} start the time between each step in milliseconds. + */ + this.start = function(){ + if(this.$running) + return; + + this.$running = true; + var _self = this; + this.oContainer.style.display = 'block'; + this.$timer = setInterval(function(){ + _self.oSpinner.style.top = (_self.$loadingFrame * - _self.frameheight) + 'px'; + + _self.$loadingFrame = (_self.$loadingFrame + 1) % _self.framelength; + }, this.spinnerspeed); + }; + + /** + * Stops the spinner indicator from moving. + * @param {Boolean} restart whether the spinner should start from the first frame. + */ + this.stop = function(restart){ + clearInterval(this.$timer); + + this.oContainer.style.display = 'none'; + + if (restart) { + this.$loadingFrame = 1; + this.oSpinner.style.top = 0; + } + + this.$running = false; + }; + + /**** Init ****/ + + this.$draw = function() { + //Build Main Skin + this.$ext = this.$getExternal(); + this.oContainer = this.$getLayoutNode("main", "container", this.$ext); + this.oSpinner = this.$getLayoutNode("main", "spinner", this.$ext); + + var framewidth, frameheight, framelength, spinnerspeed; + + if(framewidth = this.getAttribute("framewidth")) { + this.framewidth = framewidth; + this.oSpinner.style.width = framewidth + 'px'; + this.oContainer.style.width = framewidth + 'px'; + } + + if(frameheight = this.getAttribute("frameheight")) { + this.frameheight = frameheight; + this.oContainer.style.height = frameheight + 'px'; + } + + if(framelength = this.getAttribute("framelength")) { + this.framelength = framelength; + this.oSpinner.style.height = framelength * this.frameheight + 'px'; + } + + if(spinnerspeed = this.getAttribute("spinnerspeed")) { + this.spinnerspeed = spinnerspeed; + } + }; +}).call(apf.loadindicator.prototype = new apf.Presentation()); + +apf.aml.setElement("loadindicator", apf.loadindicator); + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/elements/map.js)SIZE(21831)TIME(Mon, 10 Jan 2011 10:53:12 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + + +/** + * Element displaying a Google Map with all option customizable through attributes + * + * @constructor + * @define map + * @addnode elements + * + * @author Mike de Boer (mike AT ajax DOT org) + * @version %I%, %G% + * @since 3.0 + * + * @inherits apf.StandardBinding + * @inherits apf.DataAction + * @inherits apf.XForms + * + * @attribute {Number} latitude geographical coordinate + * @attribute {Number} longitude geographical coordinate + * @attribute {mixed} maptypecontrol defines the if a MapType control should + * be visible and what its position and style should be. + * The value may be either 'false' (no control) + * or of the following form: + * 'position:bottom-left,style:dropdown' + * Style options: 'dropdown', 'bar' + * @attribute {mixed} navigationcontrol defines the if a Navigation control should + * be visible and what its position and style should be. + * The value may be either 'false' (no control) + * or of the following form: + * 'position:bottom-left,style:zoompan' + * Style options: 'android', 'small' or 'zoompan' + * @attribute {mixed} scalecontrol defines the if a Navigation control should + * be visible and what its position and style should be. + * The value may be either 'false' (no control) + * or of the following form: + * 'position:bottom-left,style:default' + * Style options: 'default' only. + * @attribute {String} type the type of map that should be rendered. + * Possible values: 'hybrid', 'roadmap', 'satellite', 'terrain' + * @attribute {Number} zoom The zoomlevel of the map. + * Value may vary between 1..100 + * @attribute {String} marker if set, a marker will be placed on the map + * at the current location (or once a location + * is specified) with the value as its title. + * @attribute {Boolean} loaded if the javascript libraries from Google + * have been loaded and the map is drawn, this + * attribute has the value 'true' + * + * @event loaded Fires after the the javascript libraries from Google have been loaded and the map is drawn + */ +apf.map = function(struct, tagName){ + this.$init(tagName || "map", apf.NODE_VISIBLE, struct); +}; + +(function(){ + this.implement( + + apf.StandardBinding + + + ,apf.DataAction + + + ); + //Options + this.$focussable = true; // This object can get the focus + this.$hasMaptypeControl = true; + this.$hasNavigationControl = true; + this.$hasScaleControl = true; + this.$mapTypeControl = {}; + this.$navigationControl = {}; + this.$scaleControl = {}; + this.$markers = {}; + this.$map = null; + + // for property specs, see: http://code.google.com/apis/maps/documentation/javascript/reference.html#MapOptions + this.$booleanProperties["draggable"] = true; + this.$booleanProperties["loaded"] = true; + this.$supportedProperties.push("latitude", "longitude", "bgcolor", "draggable", + "maptypecontrol", "navigationcontrol", "scalecontrol", "type", "zoom", + "marker", "marker-icon", "loaded"); + // default values: + this.latitude = 0; + this.longitude = 0; + this.bgcolor = null; + this.draggable = true; + this.maptypecontrol = true; + this.navigationcontrol = true; + this.scalecontrol = true; + this.type = null; + this.zoom = 9; + this.loaded = false; + + var timer, deltimer, lastpos, + delegates = [], + _slice = Array.prototype.slice, + loaddone = false; + + this.$propHandlers["latitude"] = + this.$propHandlers["longitude"] = function(value, prop) { + if (!value) return; + clearTimeout(timer); + this[prop] = parseFloat(value); + var _self = this; + timer = setTimeout(function() { + _self.setValue(_self.latitude, _self.longitude); + }); + }; + + function delegate2(func, args) { + var _self = this; + $setTimeout(function() { + delegate.call(_self, func, args); + }); + } + + function delegate(func, args) { + clearTimeout(deltimer); + args = _slice.call(args); + var i = delegates.indexOf(func), + l = args.length; + if (i > -1) { + if (l && args[0]) + delegates[i].$__args = args; + return; + } + if (!l || !args[0]) return; + func.$__args = args; + if (i === -1) + delegates.push(func); + } + + function callDelegates() { + clearTimeout(deltimer); + if (!loaddone) { + var _self = this; + deltimer = $setTimeout(function() {callDelegates.call(_self)}, 1000); + return; + } + var d, + i = 0, + l = delegates.length; + if (!l) return; + for (; i < l; ++i) { + if (typeof (d = delegates[i]) != "function") continue; + d.apply(this, d.$__args); + delete d.$__args; + } + delegates = []; + } + + function parseOptions(sOpts) { + sOpts = sOpts || ""; + var t, + aOpts = sOpts.splitSafe(",|;"), + oOpts = {}, + i = 0, + l = aOpts.length; + for (; i < l; ++i) { + if (!aOpts[i]) continue; + t = aOpts[i].splitSafe(":|="); + if (t.length != 2) continue; + oOpts[t[0]] = typeof t[1] == "string" ? t[1].toLowerCase() : t[1]; + } + return oOpts; + } + + this.$propHandlers["maptypecontrol"] = function(value) { + this.maptypecontrol = !apf.isFalse(value); + this.$mapTypeControl = {}; + if (!this.maptypecontrol) return; + if (loaddone) { + var o = parseOptions(value), + oPos = google.maps.ControlPosition, + oStyle = google.maps.MapTypeControlStyle; + o.position = typeof o.position != "undefined" ? o.position.replace(/\-/g, "_").toUpperCase() : "TOP_RIGHT"; + this.$mapTypeControl.position = oPos[o.position] || oPos.TOP_LEFT; + switch (o.style) { + case "dropdown": + this.$mapTypeControl.style = oStyle.DROPDOWN_MENU; + break; + case "bar": + this.$mapTypeControl.style = oStyle.HORIZONTAL_BAR; + break; + default: + case "default": + this.$mapTypeControl.style = oStyle.DEFAULT; + } + } + if (!this.$map) + return delegate2.call(this, arguments.callee, arguments); + this.$map.setOptions({ + mapTypeControl : this.maptypecontrol, + mapTypeControlOptions: this.$mapTypeControl + }); + }; + + this.$propHandlers["navigationcontrol"] = function(value) { + this.navigationcontrol = !apf.isFalse(value); + this.$navigationControl = {}; + if (!this.navigationcontrol) return; + if (loaddone) { + var o = parseOptions(value), + oPos = google.maps.ControlPosition, + oStyle = google.maps.NavigationControlStyle; + o.position = typeof o.position != "undefined" ? o.position.replace(/\-/g, "_").toUpperCase() : "TOP_LEFT"; + this.$navigationControl.position = oPos[o.position] || oPos.TOP_LEFT; + switch (o.style) { + case "android": + this.$navigationControl.style = oStyle.ANDROID; + break; + case "small": + this.$navigationControl.style = oStyle.SMALL; + break; + case "zoompan": + this.$navigationControl.style = oStyle.ZOOM_PAN; + break; + default: + case "default": + this.$navigationControl.style = oStyle.DEFAULT; + } + } + if (!this.$map) + return delegate2.call(this, arguments.callee, arguments); + this.$map.setOptions({ + navigationControl : this.navigationcontrol, + navigationControlOptions: this.$navigationControl + }); + }; + + this.$propHandlers["scalecontrol"] = function(value) { + this.scalecontrol = !apf.isFalse(value); + this.$scaleControl = {}; + if (!this.scalecontrol) return; + if (loaddone) { + var o = parseOptions(value), + oPos = google.maps.ControlPosition, + oStyle = google.maps.ScaleControlStyle; + o.position = typeof o.position != "undefined" ? o.position.replace(/\-/g, "_").toUpperCase() : "BOTTOM_LEFT"; + this.$scaleControl.position = oPos[o.position] || oPos.TOP_LEFT; + switch (o.style) { + default: + case "default": + this.$scaleControl.style = oStyle.DEFAULT; + } + } + if (!this.$map) + return delegate2.call(this, arguments.callee, arguments); + this.$map.setOptions({ + scaleControl : this.scalecontrol, + scaleControlOptions: this.$scaleControl + }); + }; + + this.$propHandlers["type"] = function(value) { + if (!value) return; + if (loaddone) + this.type = google.maps.MapTypeId[value.toUpperCase()]; + if (!this.$map) + return delegate2.call(this, arguments.callee, arguments); + this.$map.setMapTypeId(this.type); + }; + + this.$propHandlers["zoom"] = function(value) { + this.zoom = parseInt(value); + if (!this.$map) + return delegate.call(this, arguments.callee, arguments); + this.$map.setZoom(this.zoom); + }; + + this.$propHandlers["marker"] = function(value) { + this.addMarker(value); + }; + + // PUBLIC METHODS + /** + * Sets the geographical coordinates and centers the map on it. + * If no coordinates are passed as arguments, the current values of the + * latitude and longitude attributes are used. + * + * @param {Number} lat geographical coordinate latitude + * @param {Number} lon geographical coordinate longitude + * @type {void} + */ + this.setValue = function(lat, lon){ + if (!lat || !lon) return; + if (!loaddone) + return delegate.call(this, arguments.callee, arguments); + if (lat) + this.latitude = parseFloat(lat); + if (lon) + this.longitude = parseFloat(lon); + if (!this.$map) + return delegate2.call(this, arguments.callee, arguments); + lastpos = new google.maps.LatLng(this.latitude, this.longitude); + this.$map.setCenter(lastpos); + var _self = this; + $setTimeout(function() { + callDelegates.call(_self); + }); + }; + + /** + * Retrieves the current geographical coordinates as displayed by the map. + * The returned object has the following structure: + * + * { + * latitude: [number between -90 and 90 degrees], + * longitude: [number between -180 and 180 degrees] + * } + * + * + * @type {Object} + */ + this.getValue = function(){ + return { + latitude : this.latitude, + longitude: this.longitude + }; + }; + + var addressParts = { + "street_number": 1, + "route": 1, + "sublocality": 1, + "locality": 1, + "administrative_area_level_1": 1, + "country": 1, + "postal_code": 1 + }; + + function parseAddress(addr) { + var o, k, m, + i = 0, + l = addr.address_components.length, + res = {}; + for (; i < l; ++i) { + o = addr.address_components[i]; + for (k = 0, m = o.types.length; k < m; ++k) { + if (!addressParts[o.types[k]]) continue; + res[o.types[k]] = { + long_name : o.long_name, + short_name : o.short_name + }; + } + } + return res; + } + + /** + * Retrieves the current location by reverse geocoding the geographical + * coordinates as displayed by the map. You may also pass in a different set + * of coordinates. + * Since the reverse geocoding process is asynchronous, a callback is required + * to pass the result, because it cannot be returned directly. + * The object that is passed to the callback has the following structure: + * + * { + * "street_number":{"long_name":"241","short_name":"241"}, + * "route":{"long_name":"Keizersgracht","short_name":"Keizersgracht"}, + * "sublocality":{"long_name":"Amsterdam","short_name":"Amsterdam"}, + * "locality":{"long_name":"Amsterdam","short_name":"Amsterdam"}, + * "administrative_area_level_1":{"long_name":"North Holland","short_name":"North Holland"}, + * "country":{"long_name":"The Netherlands","short_name":"NL"}, + * "postal_code":{"long_name":"1016","short_name":"1016"} + * } + * + * + * @type {void} + */ + this.getLocation = function(cb, coords) { + if (!loaddone) + return delegate.call(this, arguments.callee, arguments); + if (!this.$geo) + this.$geo = new google.maps.Geocoder(); + var pos = lastpos; + if (coords && coords.latitude && coords.longitude) + pos = new google.maps.LatLng(coords.latitude, coords.longitude); + if (!pos) + return cb(null); + this.$geo.geocode({location: pos}, function(res, st) { + if (st != google.maps.GeocoderStatus.OK) + return cb(null); + cb(parseAddress(res[0])); + }); + }; + + function getFunctionPointer(name) { + var i, + p = self, + aName = name.splitSafe("\."), + sFunc = aName.pop(); + while (p[i = aName.shift()]) + p = p[i]; + return p[sFunc] || apf.K; + } + + function markerExists(lat, lon) { + for (var loc in this.$markers) { + if (loc[0] == lat && loc[1] == lon) + return this.$markers[loc]; + } + return false; + } + + /** + * Adds a marker on a specific geographical location, optionally with an + * information window attached to it with arbitrary HTML as its content. + * + * @param {String} [title] title of the marker. Defaults to 'Marker' + * @param {String} [content] content of the information window. If not set, + * no information window is created and attached to + * the marker. + * @param {Object} [coords] geographical coordinates to drop the marker at. + * Format: {latitude: x, longitude: y} + */ + this.addMarker = function(title, content, coords) { + if (!this.$map) { + delegate2.call(this, arguments.callee, arguments); + return false; + } + var pos = lastpos; + if (coords && coords.latitude && coords.longitude) + pos = new google.maps.LatLng(coords.latitude, coords.longitude); + if (!pos) { + delegate2.call(this, arguments.callee, arguments); + return false; + } + + if (this["marker-icon"] && !this.$markerIcon) + this.$markerIcon = new apf.url(this["marker-icon"]).uri; + + var lat = pos.lat(), + lon = pos.lng(), + marker = markerExists(lat, lon); + if (!marker) { + marker = this.$markers[[lat, lon]] = new google.maps.Marker({ + position: pos, + map : this.$map, + title : title || "Marker", + icon : this.$markerIcon || null + }); + } + + if (!content) + return marker; + + if (marker.infowindow) { + marker.infowindow.setContent(content); + } + else { + var _self = this; + marker.infowindow = new google.maps.InfoWindow({ + content: content + }); + google.maps.event.addListener(marker, "click", function() { + marker.infowindow.open(_self.$map, marker); + }); + } + + return marker; + }; + + this.removeMarker = function(coords) { + if (!this.$map) + return delegate2.call(this, arguments.callee, arguments); + var pos = lastpos; + if (coords && coords.latitude && coords.longitude) + pos = new google.maps.LatLng(coords.latitude, coords.longitude); + if (!pos) + return delegate2.call(this, arguments.callee, arguments); + + var lat = pos.lat(), + lon = pos.lng(); + for (var i in this.$markers) { + if (i[0] !== lat || i[1] !== lon) continue; + this.$markers[i].setMap(null); //this removes the marker from the map + if (this.$markers[i].infowindow) { + // remove the infowindow from the DOM + this.$markers[i].infowindow.close(); + delete this.$markers[i].infowindow; + } + delete this.$markers[i]; + break; + } + }; + + this.$draw = function(){ + if (!this.$ext) { + var doc = this.$pHtmlNode.ownerDocument; + this.$ext = this.$pHtmlNode.appendChild(doc.createElement("div")); + this.$ext.className = this.localName; + } + + if (!loaddone) return; + + callDelegates.call(this); + + this.$map = new google.maps.Map(this.$ext, { + zoom : this.zoom, + mapTypeControl : this.maptypecontrol, + mapTypeControlOptions : this.$mapTypeControl, + navigationControl : this.navigationcontrol, + navigationControlOptions: this.$navigationControl, + scaleControl : this.scalecontrol, + scaleControlOptions : this.$scaleControl, + mapTypeId : this.type || google.maps.MapTypeId.HYBRID + }); + + var _self = this; + $setTimeout(function() { + callDelegates.call(_self); + + _self.setProperty("loaded", true); + _self.dispatchEvent("loaded"); + }); + }; + + this.$destroy = function(){ + if (this.$map) { + var div = this.$map.getDiv(); + div.parentNode.removeChild(div); + delete this.$map; + } + }; + + this.addEventListener("DOMNodeInsertedIntoDocument", function() { + // include Map scripts: + // + // + var _self = this; + function loaded() { + loaddone = true; + + if (apf.window.vManager.check(_self, "map", _self.$draw)) + _self.$draw(); + + try{ + delete self.google_maps_initialize; + } + catch(e){} + } + if (typeof google == "undefined" || typeof google.maps == "undefined") { + self.google_maps_initialize = function() { + loaded(); + }; + var head = document.getElementsByTagName("head")[0], + script = document.createElement("script"); + script.type = "text/javascript"; + script.src = "http://maps.google.com/maps/api/js?sensor=true&callback=google_maps_initialize"; + + document.body.appendChild(script); + //apf.include("http://code.google.com/apis/gears/gears_init.js", false, null, null, loaded); + } + else { + loaded(); + } + }); +}).call(apf.map.prototype = new apf.GuiElement()); + +apf.aml.setElement("map", apf.map); + + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/elements/markupedit.js)SIZE(57357)TIME(Mon, 10 Jan 2011 10:53:12 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + + +var HAS_CHILD = 1 << 1, + IS_CLOSED = 1 << 2, + IS_LAST = 1 << 3, + IS_ROOT = 1 << 4; + +/** + * Element for editing markup in the same way firebug provides. + * + * @experimental + * @todo see if it's possible to create a tree baseclass + * @constructor + * @allowchild {smartbinding} + * @addnode elements:markupedit + * + * @inherits apf.XForms + * @inherits apf.MultiSelect + * @inherits apf.Cache + * @inherits apf.DataAction + * @inherits apf.GuiElement + * + * @author Ruben Daniels (ruben AT ajax DOT org) + * @version %I%, %G% + * @since 0.98.3 + * @default_private + * + * @binding css Determines a css class for a node. + * @binding empty Determines the empty message of a node. + */ +apf.markupedit = function(struct, tagName){ + this.$init(tagName || "markupedit", apf.NODE_VISIBLE, struct); +}; + +(function(){ + this.implement( + + + apf.DataAction, + + + apf.Cache, + + apf.Rename + ); + + this.$isTreeArch = true; // Tree Architecture for loading Data + this.$focussable = true; // This object can get the focus + + this.$preventRecursiveUpdate = true; + this.$enableTextNodeHack = true; + + this.startcollapsed = true; + this.$animType = 0; + this.$animSteps = 3; + this.$animSpeed = 20; + this.reselectable = true; //@todo hack! + this.autoselect = false; + this.bufferselect = true; + + this.prerender = false; + this.each = "node()[local-name(.) and not(@nomk = 'true')]"; + + + this.$dynCssClasses = []; + + + /* ******************************************************************** + PUBLIC METHODS + *********************************************************************/ + + this.expandAndSelect = function(xmlNode) { + if (!this.xmlRoot) + return; + + var _self = this; + if ((function _recur(loopNode){ + var pNode = _self.getTraverseParent(loopNode); + if (!pNode || pNode != _self.xmlRoot && _recur(pNode) === false) + return false; + + _self.slideToggle(apf.xmldb.getHtmlNode(pNode, _self), 1, true); + })(xmlNode) !== false) { + this.select(xmlNode); + + var oExt = this.$container; + var selHtml = this.$getLayoutNode("item", "select", apf.xmldb.findHtmlNode(xmlNode, this)); + var top = apf.getAbsolutePosition(selHtml, oExt)[1] + + oExt.scrollTop; + if (top <= oExt.scrollTop) + oExt.scrollTop = top; + else { + top += selHtml.offsetHeight; + if (top > oExt.scrollTop + oExt.offsetHeight) + oExt.scrollTop = top - oExt.offsetHeight; + } + } + } + + /** + * Sets an attribute to an xml node + * + */ + this.setAttributeValue = function(xmlNode, name, value){ + if (!xmlNode) + xmlNode = this.caret || this.selected; + + if (!xmlNode || xmlNode.getAttribute(name) == value) + return; + + this.$executeAction("setAttribute", [xmlNode, name, value], + "setAttributeValue", xmlNode); + }; + + /** + * Remove an attribute + * + */ + this.removeAttribute = function(xmlNode, name, value){ + if (!xmlNode) + xmlNode = this.caret || this.selected; + + if (!xmlNode || xmlNode.getAttribute(name) == value) + return; + + this.$executeAction("removeAttribute", [xmlNode, name], + "removeAttribute", xmlNode); + }; + + /** + * Renames an attribute of an xml node + * + */ + this.renameAttribute = function(xmlNode, name, newName){ + if (!xmlNode) + xmlNode = this.caret || this.selected; + + if (!xmlNode || name == newName) + return; + + this.$executeAction("multicall", [ + {action: "removeAttribute", args: [xmlNode, name]}, + {action: "setAttribute", args: [xmlNode, newName, xmlNode.getAttribute(name)]} + ], "renameAttribute", xmlNode); + }; + + /** + * Sets a text node to an xml node + * + */ + this.setTextNode = function(xmlNode, value){ + if (!xmlNode) + xmlNode = this.caret || this.selected; + + if (!xmlNode || apf.queryValue(xmlNode, "text()") == value) + return; + + this.$executeAction("setTextNode", [xmlNode, value], "setTextNode", xmlNode); + }; + + + /** + * @private + */ + this.slideToggle = function(htmlNode, force, immediate){ + if (this.nocollapse) + return; + + if (!htmlNode) + htmlNode = this.$selected; + + var id = htmlNode.getAttribute(apf.xmldb.htmlIdTag); + while (!id && htmlNode.parentNode) + var id = (htmlNode = htmlNode.parentNode) + .getAttribute(apf.xmldb.htmlIdTag); + + var elClass, container = this.$getLayoutNode("item", "container", htmlNode); + if (apf.getStyle(container, "display") == "block") { + if (force == 1) return; + elClass = this.$getLayoutNode("item", "openclose", htmlNode); + elClass.className = elClass.className.replace(/min/, "plus"); + htmlNode.className = htmlNode.className.replace(/min/, "plus"); + this.slideClose(container, apf.xmldb.getNode(htmlNode), immediate); + } + else { + if (force == 2) return; + elClass = this.$getLayoutNode("item", "openclose", htmlNode); + elClass.className = elClass.className.replace(/plus/, "min"); + htmlNode.className = htmlNode.className.replace(/plus/, "min"); + this.slideOpen(container, apf.xmldb.getNode(htmlNode), immediate); + } + }; + + this.isCollapsed = function(xmlNode){ + return (apf.getStyle(this.$getLayoutNode("item", "container", + apf.xmldb.getHtmlNode(xmlNode, this)), "display") == "none"); + } + + var lastOpened = {}; + /** + * @private + */ + this.slideOpen = function(container, xmlNode, immediate){ + if (!xmlNode) + xmlNode = this.selected; + + var htmlNode = apf.xmldb.findHtmlNode(xmlNode, this); + if (!container) + container = this.$findContainer(htmlNode); + + //We don't slide open elements without children. + if (!container.innerHTML && !this.getTraverseNodes(xmlNode).length) + return; + + if (this.singleopen) { + var pNode = this.getTraverseParent(xmlNode), + p = (pNode || this.xmlRoot).getAttribute(apf.xmldb.xmlIdTag); + if (lastOpened[p] && lastOpened[p][1] != xmlNode + && this.getTraverseParent(lastOpened[p][1]) == pNode) + this.slideToggle(lastOpened[p][0], 2);//lastOpened[p][1]); + lastOpened[p] = [htmlNode, xmlNode]; + } + + container.style.display = "block"; + if (!this.prerender && this.$hasLoadStatus(xmlNode, "potential")) { + this.$extend(xmlNode, container, immediate); + return; + } + + if (immediate) { + container.style.height = "auto"; + return; + } + + var _self = this; + apf.tween.single(container, { + type : 'scrollheight', + from : 0, + to : container.scrollHeight, + anim : this.$animType, + steps : this.$animOpenStep, + interval: this.$animSpeed, + onfinish: function(container){ + if (xmlNode && _self.$hasLoadStatus(xmlNode, "potential")) { + $setTimeout(function(){ + _self.$extend(xmlNode, container); + }); + container.style.height = "auto"; + } + else { + //container.style.overflow = "visible"; + container.style.height = "auto"; + } + } + }); + }; + + /** + * @private + */ + this.slideClose = function(container, xmlNode){ + if (this.nocollapse) + return; + + if (!xmlNode) + xmlNode = this.selected; + + if (this.singleopen) { + var p = (this.getTraverseParent(xmlNode) || this.xmlRoot) + .getAttribute(apf.xmldb.xmlIdTag); + lastOpened[p] = null; + } + + container.style.height = container.offsetHeight; + container.style.overflow = "hidden"; + + apf.tween.single(container, { + type : 'scrollheight', + from : container.scrollHeight, + to : 0, + anim : this.$animType, + steps : this.$animCloseStep, + interval: this.$animSpeed, + onfinish: function(container, data){ + container.style.display = "none"; + } + }); + }; + + //check databinding for how this is normally implemented + //PROCINSTR + this.doUpdate = function(xmlNode, container){ + var rule = this.$getBindRule("insert", xmlNode); + var xmlContext = rule && rule[1] + ? rule[1](xmlNode) + : null; + + if (rule && xmlContext) { + this.$setLoadStatus(xmlNode, "loading"); + + if (rule.getAttribute("get")) { + this.getModel().$insertFrom(rule.getAttribute("get"), { + xmlNode : xmlContext, + insertPoint : xmlContext, + amlNode : this + }); + } + else { + var data = this.$applyBindRule("Insert", xmlNode); + if (data) + this.insert(data, {insertPoint: xmlContext}); + } + } + else + if (!this.prerender) { + this.$setLoadStatus(xmlNode, "loading"); + var result = this.$addNodes(xmlNode, container, true); //checkChildren ??? + xmlUpdateHandler.call(this, "insert", xmlNode, result); + } + }; + + /* *********************** + Skin + ************************/ + + var treeState = {}; + treeState[0] = ""; + treeState[HAS_CHILD] = "min"; + treeState[HAS_CHILD | IS_CLOSED] = "plus"; + treeState[IS_LAST] = "last"; + treeState[IS_LAST | HAS_CHILD] = "minlast"; + treeState[IS_LAST | HAS_CHILD | IS_CLOSED] = "pluslast"; + treeState[IS_ROOT] = "root"; + + this.$fixItem = function(xmlNode, htmlNode, isDeleting, oneLeft, noChildren){ + if (!htmlNode) return; + + if (isDeleting) { + //if isLast fix previousSibling + var prevSib; + if (prevSib = this.getNextTraverse(xmlNode, true)) + this.$fixItem(prevSib, this.$findHtmlNode( + prevSib.getAttribute(apf.xmldb.xmlIdTag) + "|" + + this.$uniqueId), null, true); + + //if no sibling fix parent + if (!this.emptyMessage + && xmlNode.parentNode.selectNodes(this.each).length == 1) //@todo each parent?? + this.$fixItem(xmlNode.parentNode, this.$findHtmlNode( + xmlNode.parentNode.getAttribute(apf.xmldb.xmlIdTag) + + "|" + this.$uniqueId), null, false, true); + } + else { + var hasChildren, container = this.$getLayoutNode("item", "container", htmlNode); + + if (noChildren) + hasChildren = false; + else + if (xmlNode.selectNodes(this.each).length > 0) + hasChildren = true; + else + if (this.$getBindRule("insert", xmlNode)) + hasChildren = true; + else + hasChildren = false; + + var isClosed = hasChildren && container.style.display != "block"; + var isLast = this.getNextTraverse(xmlNode, null, oneLeft ? 2 : 1) + ? false + : true; + + var state = (hasChildren ? HAS_CHILD : 0) | (isClosed ? IS_CLOSED : 0) + | (isLast ? IS_LAST : 0); + this.$setStyleClass(this.$getLayoutNode("item", "openclose", + htmlNode), treeState[state], ["min", "plus", "last", "minlast", + "pluslast"]); + this.$setStyleClass(htmlNode, + treeState[state], ["min", "plus", "last", "minlast", "pluslast"]); + + if (!hasChildren) + container.style.display = "none"; + + if (state & HAS_CHILD) { + //this.$getLayoutNode("item", "openclose", htmlNode).onmousedown = new Function('e', 'if(!e) e = event; if(e.button == 2) return;var o = apf.lookup(' + this.$uniqueId + ');o.slideToggle(this);if(o.onmousedown) o.onmousedown(e, this);apf.cancelBubble(e, o);'); + //this.$getLayoutNode("item", "icon", htmlNode)[this.opencloseaction || "ondblclick"] = new Function('var o = apf.lookup(' + this.$uniqueId + '); o.slideToggle(this);o.choose();'); + //this.$getLayoutNode("item", "select", htmlNode)[this.opencloseaction || "ondblclick"] = new Function('e', 'var o = apf.lookup(' + this.$uniqueId + '); o.slideToggle(this, true);o.choose();(e||event).cancelBubble=true;'); + } + /*else{ + //Experimental + this.$getLayoutNode("item", "openclose", htmlNode).onmousedown = null; + this.$getLayoutNode("item", "icon", htmlNode)[this.opencloseaction || "ondblclick"] = null; + this.$getLayoutNode("item", "select", htmlNode)[this.opencloseaction || "ondblclick"] = null; + }*/ + } + }; + + this.startRenameThis = function(oHtml, Lid, isName, id){ + if (this.renaming) { + this.stopRename(null, true); + var _self = this; + return setTimeout(function(){ + _self.startRenameThis(oHtml, Lid, isName); + }); + } + + this.$getCaptionElement = function(){ + return oHtml; + } + + var attrName = oHtml.getAttribute("aname"); + + var xmlNode = id ? knownElements[id] : apf.xmldb.getNodeById(Lid); + if (!xmlNode) throw new Error(); + + this.$multiLineRename = id && xmlNode.nodeType != 3 ? true : false; + + /*if (!this.isSelected(xmlNode)) { + this.select(xmlNode); + return; + }*/ + + this.$getCaptionXml = function(){ //@todo + return xmlNode.nodeType != 1 ? xmlNode + : (attrName ? xmlNode.getAttributeNode(attrName) : xmlNode.firstChild); + } + + this.getSelectFromRule = function(setname, cnode){ + return [null, xmlNode.nodeType != 1 ? xmlNode + : (attrName ? xmlNode.getAttributeNode(attrName) : xmlNode.firstChild)]; + }; + + this.$validateRename = function(value){ + return !isName || !value || value.match(/^[a-z]/i); + } + + this.rename = function(x, value){ + this.$noanim = true; + if (attrName) { + if (isName) { + if (value == "") + this.removeAttribute(xmlNode, attrName); + else + this.renameAttribute(xmlNode, attrName, value); + } + else + this.setAttributeValue(xmlNode, attrName, value); + } + else + this.setTextNode(xmlNode, value); + this.$noanim = false; + }; + + this.startRename(); + + if (isName) { + this.$txt[apf.hasContentEditable ? "innerHTML" : "value"] = attrName; + this.$txt.className = ""; + } + else + this.$txt.className = "attrvalue"; + + apf.selectTextHtml(this.$txt); + } + + /** + * @todo Make it xmlNode locked + * @todo Use escape(27) key to cancel change (see rename) + */ + function addAttribute(pNode, name, value, htmlNode, Lid){ + this.$getNewContext("attribute"); + var elName = this.$getLayoutNode("attribute", "name"); + var elValue = this.$getLayoutNode("attribute", "value"); + apf.setNodeValue(elName, name); + apf.setNodeValue(elValue, (value.length > 50 ? "..." : value)); + if (value.length > 50) + elValue.setAttribute("title", value); + + elName.setAttribute("aname", name); + elName.setAttribute("onmousedown", "if ((event.which || event.button) == 1)\ + apf.lookup(" + this.$uniqueId + ").startRenameThis(this, '" + Lid + "', true);\ + apf.stopPropagation(event);"); + elName.setAttribute("onmouseup", "\ + apf.stopPropagation(event);\ + return false;"); + elName.setAttribute("onkeydown", "if (event.keyCode==13) {\ + this.blur();\ + return false;\ + };\ + apf.stopPropagation(event);"); + elName.setAttribute("onselectstart", "event.cancelBubble = true;"); + elName.setAttribute("ondblclick", "event.cancelBubble = true;"); + + elValue.setAttribute("aname", name); + elValue.setAttribute("onmousedown", "if ((event.which || event.button) == 1)\ + apf.lookup(" + this.$uniqueId + ").startRenameThis(this, '" + Lid + "');\ + apf.stopPropagation(event);"); + elValue.setAttribute("onmouseup", "\ + apf.stopPropagation(event);\ + return false;"); + elValue.setAttribute("onkeydown", "if (event.keyCode==13) {\ + this.blur();\ + return false;\ + };\ + apf.stopPropagation(event);"); + elValue.setAttribute("onselectstart", "event.cancelBubble = true;"); + elValue.setAttribute("ondblclick", "event.cancelBubble = true;"); + + if (pNode.style) { + this.$setStyleClass(this.$getLayoutNode("attribute"), "generated"); + + htmlNode = apf.insertHtmlNode( + this.$getLayoutNode("attribute"), + pNode, + this.$getLayoutNode("item", "begintag", htmlNode).nextSibling); + + if (!this.$noanim) { + animHighlight(htmlNode); + animHighlight(this.$getLayoutNode("attribute", "name", htmlNode)); + animHighlight(this.$getLayoutNode("attribute", "value", htmlNode)); + } + } + else + pNode.appendChild(this.$getLayoutNode("attribute")); + } + + function addTextnode(pNode, value, Lid){ + this.$getNewContext("textnode"); + var elTextNode = this.$getLayoutNode("textnode", "text"); + var elTag = this.$getLayoutNode("textnode", "tag"); + apf.setNodeValue(elTextNode, (value.length > 50 ? "..." : value)); + if (value.length > 50) + elTextNode.setAttribute("title", value); + + elTextNode.setAttribute("onmousedown", "if ((event.which || event.button) == 1)\ + apf.lookup(" + this.$uniqueId + ").startRenameThis(this, '" + Lid + "');"); + elTextNode.setAttribute("onmouseup", "\ + apf.stopPropagation(event);\ + return false;"); + elTextNode.setAttribute("onkeydown", "if (event.keyCode==13) {\ + this.blur();\ + return false;\ + };\ + apf.stopPropagation(event);"); + elTextNode.setAttribute("onselectstart", "event.cancelBubble = true;"); + elTextNode.setAttribute("ondblclick", "event.cancelBubble = true;"); + + apf.setNodeValue(elTag, ">"); + + if (pNode.style) { + var htmlNode = apf.insertHtmlNode( + this.$getLayoutNode("textnode"), pNode, pNode.lastChild); + + if (!this.$noanim) + animHighlight(this.$getLayoutNode("textnode", "text", htmlNode)); + } + else + pNode.appendChild(this.$getLayoutNode("textnode")); + } + + //This can be optimized by NOT using getLayoutNode all the time + this.$initNode = function(xmlNode, state, Lid){ + //Setup Nodes Interaction + this.$getNewContext("item"); + + var hasChildren = (state & HAS_CHILD || this.emptyMessage + && this.$applyBindRule("empty", xmlNode)); + + //should be restructured and combined events set per element + var elItem = this.$getLayoutNode("item"); + elItem.setAttribute(apf.xmldb.htmlIdTag, Lid); + + //Set open/close skin class & interaction + this.$setStyleClass(this.$getLayoutNode("item", "openclose"), + treeState[state]); + this.$setStyleClass(this.$getLayoutNode("item"), + treeState[state]) + var elOpenClose = this.$getLayoutNode("item", "openclose"); + if (hasChildren) + elOpenClose.setAttribute(this.opencloseaction || "onmousedown", + "var o = apf.lookup(" + this.$uniqueId + ");\ + o.slideToggle(this);\ + apf.cancelBubble(event,o);"); + + //Select interaction + var elSelect = this.$getLayoutNode("item", "select"); + if (hasChildren) { + var strFunc2 = "var o = apf.lookup(" + this.$uniqueId + ");\ + o.slideToggle(this, true);"; + //if(this.opencloseaction != "onmousedown") elSelect.setAttribute(this.opencloseaction || "ondblclick", strFunc2); + } + //if(event.button != 1) return; + //apf.isChildOf(o.$selected, this) && o.selected [REMOVED THIS LINE... dunno what the repurcusions are exactly] + elSelect.setAttribute("onmousedown", "var o = apf.lookup(" + this.$uniqueId + ");\ + apf.cancelBubble(event, o);\ + if (o.hasFocus()) \ + o.select(this);" + + (strFunc2 && this.opencloseaction == "onmousedown" ? strFunc2 : "")); + //if(!elSelect.getAttribute("ondblclick")) elSelect.setAttribute("ondblclick", 'var o = apf.lookup(' + this.$uniqueId + ');o.choose();'); + + //elItem.setAttribute("contextmenu", 'alert(1);var o = apf.lookup(' + this.$uniqueId + ');o.dispatchEvent("contextMenu", o.selected);'); + + var elBegin = this.$getLayoutNode("item", "begintag"); + apf.setNodeValue(elBegin, "<" + xmlNode.tagName); + + //attributes + var elAttributes = this.$getLayoutNode("item", "attributes"); + var len = xmlNode.attributes.length; + if (typeof len == "function") + len = xmlNode.attributes.length(); + for (var i = 0; i < len; i++) { + var attr = xmlNode.attributes.item(i); + if (attr.nodeName.match(/a_id|a_listen|a_doc|a_loaded/)) + continue; + + addAttribute.call(this, elAttributes, attr.nodeName, + attr.nodeValue, null, Lid); + } + + var elBeginTail = this.$getLayoutNode("item", "begintail"); + var elEnd = this.$getLayoutNode("item", "endtag"); + if (!(state&HAS_CHILD)) { + elEnd.setAttribute("style", "display:none"); + + if (this.$enableTextNodeHack && (xmlNode.childNodes.length > 1 + || xmlNode.childNodes.length == 1 + && xmlNode.firstChild.nodeValue.trim())) { + addTextnode.call(this, elAttributes, xmlNode.childNodes[0].nodeValue, Lid); + apf.setNodeValue(elBeginTail, "</" + xmlNode.tagName + ">"); + } + else + apf.setNodeValue(elBeginTail, " />"); + } + else { + apf.setNodeValue(elEnd, "</" + xmlNode.tagName + ">"); + apf.setNodeValue(elBeginTail, ">"); + } + elBeginTail.parentNode.appendChild(elBeginTail); + + elEnd.setAttribute("onmousedown", 'var o = apf.lookup(' + this.$uniqueId + ');apf.cancelBubble(event, o);'); + + + var cssClass = this.$applyBindRule("css", xmlNode); + if (cssClass) { + this.$setStyleClass(this.$getLayoutNode("item", null, + this.$getLayoutNode("item")), cssClass); + if (cssClass) + this.$dynCssClasses.push(cssClass); + } + + + return this.$getLayoutNode("item"); + }; + + this.$deInitNode = function(xmlNode, htmlNode){ + //Lookup container + var containerNode = this.$getLayoutNode("item", "container", htmlNode); + var pContainer = htmlNode.parentNode; + + //Remove htmlNodes from tree + containerNode.parentNode.removeChild(containerNode); + pContainer.removeChild(htmlNode); + + //Fix Images (+, - and lines) + if (xmlNode.parentNode != this.xmlRoot) + this.$fixItem(xmlNode, htmlNode, true); + + if (this.emptyMessage && !pContainer.childNodes.length) + this.setEmpty(pContainer); + + //Fix look (tree thing) + this.$fixItem(xmlNode, htmlNode, true); + + if (xmlNode == this.selected) + this.clearSelection(); + + //this.$fixItem(xmlNode.parentNode, apf.xmldb.findHtmlNode(xmlNode.parentNode, this)); + /*throw new Error(); + if(xmlNode.previousSibling) //should use each here + this.$fixItem(xmlNode.previousSibling, apf.xmldb.findHtmlNode(xmlNode.previousSibling, this));*/ + }; + + function animHighlight(oHtml){ + if (!oHtml.offsetHeight) return; + + apf.setStyleClass(oHtml, "highlight"); + $setTimeout(function(){ + /*apf.tween.css(oHtml, "highlight", { + anim : 0, + steps : 20, + interval: 30}, true);*/ + apf.setStyleClass(oHtml, "", ["highlight"]); + }, 1000); + } + + this.$updateNode = function(xmlNode, htmlNode){ + //Attributes + var len, i, aLookup = {}; + var elAttributes = this.$getLayoutNode("item", "attributes", htmlNode); + var elEnd = this.$getLayoutNode("item", "endtag", htmlNode); + var elBeginTail = this.$getLayoutNode("item", "begintail", htmlNode); + + //if (typeof len == "function") + len = xmlNode.attributes.length; + for (var i = 0; i < len; i++) { + var attr = xmlNode.attributes.item(i); + if (attr.nodeName.match(/a_id|a_listen|a_doc|a_loaded/)) + continue; + aLookup[attr.nodeName] = attr.nodeValue; + } + + var doneFirstChild = false; + var deleteQueue = []; + var nodes = [], cnodes = elAttributes.childNodes; + for (i = 0; i < cnodes.length; i++) + nodes.push(cnodes[i]); + + for (i = 0; i < nodes.length; i++) { + if (nodes[i].nodeType != 1) + continue; + + if (nodes[i].className.indexOf("textnode") > -1) { + if (xmlNode.childNodes.length == 1 + && xmlNode.childNodes[0].nodeType == 3) { + var elText = this.$getLayoutNode("textnode", "text", nodes[i]); + if (xmlNode.firstChild.nodeValue != elText.innerHTML) { + elText.innerHTML = xmlNode.firstChild.nodeValue; + //Animate change here + animHighlight(elText); + } + } + else { + nodes[i].parentNode.removeChild(nodes[i]);//apf.removeChild here?? + apf.setNodeValue(elBeginTail, " />"); + } + + doneFirstChild = true; + } + + if (nodes[i].className.indexOf("attribute") == -1) + continue; + + var elName = this.$getLayoutNode("attribute", "name", nodes[i]); + var elValue = this.$getLayoutNode("attribute", "value", nodes[i]); + + //Remove attribute if it no longer exists + var name = elName.innerHTML; + if (!aLookup[name]) + deleteQueue.push(nodes[i]); + //Change it + else if(aLookup[name] != elValue.innerHTML) { + elValue.innerHTML = aLookup[name]; + + animHighlight(elValue); + //Animate change here... + delete aLookup[name]; + } + else if(aLookup[name]) + delete aLookup[name]; + + elName.setAttribute("aname", name); + elValue.setAttribute("aname", name); + } + + //Add the remaining attributes + for (var attr in aLookup) { + if (deleteQueue.length) { + var html = deleteQueue.pop(); + var elName = this.$getLayoutNode("attribute", "name", html); + var elValue = this.$getLayoutNode("attribute", "value", html); + elName.setAttribute("aname", attr); + elValue.setAttribute("aname", attr); + elName.innerHTML = attr; + elValue.innerHTML = aLookup[attr]; + + animHighlight(elName); + } + else { + addAttribute.call(this, elAttributes, attr, aLookup[attr], htmlNode, + xmlNode.getAttribute(apf.xmldb.xmlIdTag) + "|" + this.$uniqueId); + } + } + + //Remove remaining queue + for (var i = 0; i < deleteQueue.length; i++) { + deleteQueue[i].parentNode.removeChild(deleteQueue[i]);//apf.removeChild here?? + } + + //Add textnode if its not there yet + if (this.$enableTextNodeHack && !doneFirstChild + && xmlNode.childNodes.length == 1 + && xmlNode.childNodes[0].nodeType == 3) { + addTextnode.call(this, elAttributes, xmlNode.childNodes[0].nodeValue); + apf.setNodeValue(elBeginTail, ""); + } + + + var cssClass = this.$applyBindRule("css", xmlNode); + if (cssClass || this.$dynCssClasses.length) { + this.$setStyleClass(htmlNode, cssClass, this.$dynCssClasses); + if (cssClass && !this.$dynCssClasses.contains(cssClass)) + this.$dynCssClasses.push(cssClass); + } + + }; + + this.clearEmpty = function(container){ + container.innerHTML = ""; + }; + + this.setEmpty = function(container){ + this.$getNewContext("empty"); + var oItem = this.$getLayoutNode("empty"); + this.$getLayoutNode("empty", "caption").nodeValue = this.emptyMessage; + apf.insertHtmlNode(oItem, container); + + if (!this.startcollapsed) { + if (container.style) { + //container.style.display = "block"; + //container.style.height = "auto"; + } + //else container.setAttribute("style", "display:block;height:auto;"); + } + }; + + this.$setLoading = function(xmlNode, container){ + this.$getNewContext("loading"); + this.$setLoadStatus(xmlNode, "potential"); + apf.insertHtmlNode(this.$getLayoutNode("loading"), container); + }; + + this.$removeLoading = function(htmlNode){ + if (!htmlNode) return; + this.$getLayoutNode("item", "container", htmlNode).innerHTML = ""; + }; + + //check databinding for how this is normally implemented + this.$extend = function(xmlNode, container, immediate){ + var rule = this.$getBindRule("insert", xmlNode), + xmlContext = rule && rule.match + ? (rule.cmatch || rule.compile("match"))(xmlNode) + : xmlNode; + + if (rule && xmlContext) { + this.$setLoadStatus(xmlNode, "loading"); + + if (rule.get) { + + if (!apf.offline.onLine) { + apf.offline.transactions.actionNotAllowed(); + this.slideClose(container, xmlNode); + return; + } + + + this.getModel().$insertFrom(rule.getAttribute("get"), { + xmlNode : xmlContext, + insertPoint : xmlContext, + amlNode : this + }); + } + else { + if (this.$applyBindRule("insert", xmlNode)) + this.insert(data, {insertPoint: xmlContext}); + } + } + else if (!this.prerender) { + this.$setLoadStatus(xmlNode, "loaded"); + this.$removeLoading(apf.xmldb.findHtmlNode(xmlNode, this)); + + this.$noanim = true; + xmlUpdateHandler.call(this, { + action : "insert", + xmlNode : xmlNode, + result : this.$addNodes(xmlNode, container, true), //checkChildren ??? + anim : !immediate + }); + delete this.$noanim; + } + }; + + function xmlUpdateHandler(e){ + /* + Display the animation if the item added is + * Not in the cache + - Being insterted using xmlUpdate + - there is at least 1 child inserted + */ + + if (e.action == "move-away") + this.$fixItem(e.xmlNode, apf.xmldb.findHtmlNode(e.xmlNode, this), true); + + if (e.action != "insert") return; + + var htmlNode = this.$findHtmlNode(e.xmlNode.getAttribute( + apf.xmldb.xmlIdTag) + "|" + this.$uniqueId); + if (!htmlNode) return; + + if (this.$hasLoadStatus(e.xmlNode, "loaded") && e.result.length > 0) { + var container = this.$getLayoutNode("item", "container", htmlNode); + this.slideOpen(container, e.xmlNode); + } + else + this.$fixItem(e.xmlNode, htmlNode); + + //Can this be removed?? (because it was added in the insert function) + //if (this.$hasLoadStatus(e.xmlNode, "loading")) + //this.$setLoadStatus(e.xmlNode, "loaded"); + } + + this.addEventListener("xmlupdate", xmlUpdateHandler); + this.addEventListener("beforeload", function(e){ + this.$enableTextNodeHack = !e.xmlNode.$regbase; + this.each = e.xmlNode.$regbase + ? "node()[not(@nomk = 'true') and not(local-name() = 'include')]" + : "node()[local-name(.) and not(@nomk = 'true')]"; + }); + + /* *********************** + Keyboard Support + ************************/ + + + this.addEventListener("keydown", function(e){ + var key = e.keyCode; + var ctrlKey = e.ctrlKey; + var shiftKey = e.shiftKey; + var selHtml = this.$caret || this.$selected; + var pos, top, el, node, nodes, sNode, pNode, container; + + if (!selHtml || this.renaming) + return; + + var selXml = this.caret || this.selected; + var oExt = this.$container; + + switch (key) { + case 13: + if (this.$tempsel) + this.$selectTemp(); + + if (this.ctrlselect == "enter") + this.select(this.caret, true); + + this.choose(selHtml); + break; + case 32: + if (ctrlKey) + this.select(this.caret, true); + break; + case 46: + if (this.$tempsel) + this.$selectTemp(); + + //DELETE + //this.remove(); + this.remove(this.mode ? this.caret : null); //this.mode != "check" + break; + case 109: + case 37: + //LEFT + if (this.$tempsel) + this.$selectTemp(); + + if (this.caret.selectSingleNode(this.each)) + this.slideToggle(this.$caret || this.$selected, 2) + break; + case 107: + case 39: + //RIGHT + if (this.$tempsel) + this.$selectTemp(); + + if (this.caret.selectSingleNode(this.each)) + this.slideToggle(this.$caret || this.$selected, 1) + break; + case 187: + //+ + if (shiftKey) + arguments.callee(39); + break; + case 189: + //- + if (!shiftKey) + arguments.callee(37); + break; + case 38: + //UP + if (!selXml && !this.$tempsel) + return; + + var node = this.$tempsel + ? apf.xmldb.getNode(this.$tempsel) + : selXml; + + var sNode = this.getNextTraverse(node, true); + if (sNode) { + var nodes = this.getTraverseNodes(sNode); + + do { + var container = this.$getLayoutNode("item", "container", + this.$findHtmlNode(apf.xmldb.getID(sNode, this))); + if (container && apf.getStyle(container, "display") == "block" + && nodes.length) { + sNode = nodes[nodes.length-1]; + } + else + break; + } + while (sNode && (nodes = this.getTraverseNodes(sNode)).length); + } + else if (this.getTraverseParent(node) == this.xmlRoot) { + this.dispatchEvent("selecttop"); + return; + } + else + sNode = this.getTraverseParent(node); + + if (sNode && sNode.nodeType == 1) + this.$setTempSelected (sNode, ctrlKey, shiftKey); + else + return false; + + selHtml = this.$getLayoutNode("item", "select", apf.xmldb.findHtmlNode(sNode, this)); + top = apf.getAbsolutePosition(selHtml, oExt)[1] + + oExt.scrollTop; + if (top <= oExt.scrollTop) + oExt.scrollTop = top; + + return false; + case 40: + //DOWN + if (!selXml && !this.$tempsel) + return; + + var node = this.$tempsel + ? apf.xmldb.getNode(this.$tempsel) + : selXml; + + var sNode = this.getFirstTraverseNode(node); + if (sNode) { + var container = this.$getLayoutNode("item", "container", + this.$findHtmlNode(apf.xmldb.getID(node, this))); + if (container && apf.getStyle(container, "display") != "block") + sNode = null; + } + + while (!sNode) { + var pNode = this.getTraverseParent(node); + if (!pNode) break; + + var i = 0; + var nodes = this.getTraverseNodes(pNode); + while (nodes[i] && nodes[i] != node) + i++; + sNode = nodes[i+1]; + node = pNode; + } + + if (sNode && sNode.nodeType == 1) + this.$setTempSelected (sNode, ctrlKey, shiftKey); + else + return false; + + selHtml = this.$getLayoutNode("item", "select", apf.xmldb.findHtmlNode(sNode, this)); + top = apf.getAbsolutePosition(selHtml, oExt)[1] + + selHtml.offsetHeight + oExt.scrollTop; + if (top > oExt.scrollTop + oExt.offsetHeight) + oExt.scrollTop = top - oExt.offsetHeight; + + return false; + case 33: //@todo + //PGUP + break; + case 34: //@todo + //PGDN + break; + case 36: //@todo + //HOME + break; + case 35: //@todo + //END + break; + } + }, true); + + + /* *********************** + DATABINDING + ************************/ + + var nodes = []; + + this.$add = function(xmlNode, Lid, xmlParentNode, htmlParentNode, beforeNode, isLast){ + //Why is this function called 3 times when adding one node? (hack/should) + var loadChildren = this.$getBindRule("insert", xmlNode) ? true : false, + traverseNodes = this.getTraverseNodes(xmlNode), + hasTraverseNodes = traverseNodes.length ? true : false, + hasChildren = loadChildren || hasTraverseNodes, + startcollapsed = this.$hasBindRule("collapsed") + ? (this.$getDataNode("collapsed", xmlNode) ? true : false) + : (this.$hasBindRule("expanded") + ? (this.$getDataNode("expanded", xmlNode) ? false : true) + : this.startcollapsed), + state = (hasChildren ? HAS_CHILD : 0) + | (startcollapsed && hasChildren || loadChildren ? IS_CLOSED : 0) + | (isLast ? IS_LAST : 0), + htmlNode = this.$initNode(xmlNode, state, Lid), + container = this.$getLayoutNode("item", "container"), + eachLength; + + if (!startcollapsed && !this.nocollapse) + container.setAttribute("style", "overflow:visible;height:auto;display:block;"); + + //TEMP on for dynamic subloading + if (!hasChildren || loadChildren) + container.setAttribute("style", "display:none;"); + + //Dynamic SubLoading (Insertion) of SubTree + if (!this.prerender) + eachLength = traverseNodes.length; + + //Dynamic SubLoading (Insertion) of SubTree + //@todo weird bug with eachLenght > 2 + if (hasChildren && !this.prerender && eachLength > 0 && startcollapsed + || loadChildren && (!this.$hasLoadStatus(xmlNode) + || this.$hasLoadStatus(xmlNode, "potential"))) { + this.$setLoading(xmlNode, container); + } + else if (!hasTraverseNodes && (msg = this.$applyBindRule("empty", xmlNode))) { + //this.$setEmptyMessage(container, msg); + this.setEmpty(container); + } + + if (!htmlParentNode && (xmlParentNode == this.xmlRoot + || xmlNode == this.xmlRoot)) { + nodes.push(htmlNode); + if (!apf.isChildOf(htmlNode, container, true)) + nodes.push(container); + + this.$setStyleClass(htmlNode, "root"); + this.$setStyleClass(container, "root"); + + //if(xmlNode == xmlParentNode) return container; + } + else { + if (!htmlParentNode) { + htmlParentNode = apf.xmldb.findHtmlNode(xmlNode.parentNode, this); + htmlParentNode = htmlParentNode + ? this.$getLayoutNode("item", "container", htmlParentNode) + : this.$container; + } + + if (htmlParentNode == this.$container) { + this.$setStyleClass(htmlNode, "root"); + this.$setStyleClass(container, "root"); + + if (this.renderRoot) { + var realParent = apf.xmldb.findHtmlNode(this.xmlRoot, this); + htmlParentNode = this.$getLayoutNode("item", "container", realParent); + } + } + + if (!beforeNode && this.getNextTraverse(xmlNode)) + beforeNode = apf.xmldb.findHtmlNode(this.getNextTraverse(xmlNode), this); + if (beforeNode && beforeNode.parentNode != htmlParentNode) + beforeNode = null; + + if (htmlParentNode.style && this.getTraverseNodes(xmlNode.parentNode).length == 1) + this.clearEmpty(htmlParentNode); + + //alert("|" + htmlNode.nodeType + "-" + htmlParentNode.nodeType + "-" + beforeNode + ":" + container.nodeType); + //Insert Node into Tree + if (htmlParentNode.style) { + var q = apf.insertHtmlNode(htmlNode, htmlParentNode, beforeNode); + if (!this.$noanim) + animHighlight(this.$getLayoutNode("item", "select", q)); + + if (!apf.isChildOf(htmlNode, container, true)) + var container = apf.insertHtmlNode(container, htmlParentNode, beforeNode); + } + else { + htmlParentNode.insertBefore(htmlNode, beforeNode); + if (!apf.isChildOf(htmlParentNode, container, true)) + htmlParentNode.insertBefore(container, beforeNode); + } + + //Fix parent if child is added to drawn parentNode + if (htmlParentNode.style) { + if(!startcollapsed && this.openOnAdd + && htmlParentNode != this.$container + && htmlParentNode.style.display != "block") + this.slideOpen(htmlParentNode, xmlParentNode); + + //this.$fixItem(xmlNode, htmlNode); this one shouldn't be called, because it should be set right at init + this.$fixItem(xmlParentNode, apf.xmldb.findHtmlNode(xmlParentNode, this)); + if (this.getNextTraverse(xmlNode, true)) { //should use each here + this.$fixItem(this.getNextTraverse(xmlNode, true), + apf.xmldb.findHtmlNode(this.getNextTraverse(xmlNode, true), this)); + } + } + } + + if (this.prerender || eachLength < 1 || !startcollapsed) { + this.$addNodes(xmlNode, container, false); //checkChildren ??? + } + /*else { + this.$setLoadStatus(xmlNode, "potential"); + }*/ + + return container; + }; + + this.$fill = function(){ + var container; + + //Please please consider moving this to apf.databinding and make it generic.. this is a mess + /*if(this.renderRoot){ + var htmlNode = apf.xmldb.findHtmlNode(this.xmlRoot, this); + if(!htmlNode || htmlNode.parentNode != this.$container){ + var nodes = nodes; + nodes = []; + + var Lid = apf.xmldb.nodeConnect(this.documentId, this.xmlRoot, null, this); + var p = this.$add(this.xmlRoot, Lid, this.xmlRoot, null, null, true); + for(var i=0;i -1 ? '' : 'open', ['open']);") + + var txtNode = this.$getLayoutNode("anynode", "text"); + } + + var value = xmlNode.nodeValue; + if (value.trim().indexOf("\n") > -1) { + //@todo Changing the nodeValue is technically wrong, but we do it to get rename to play nice.. there are better solution. + value = (xmlNode.nodeValue = apf.removeStartWhitespaces(value + .replace(/^ *\n/, "") + .replace(/\n *$/, ""))) + .replace(/"); + } + else + value = value.trim().replace(/ + * + * + * + * test + * test2 + * + * + * + * table_wizard + * table_wizard + * + * item 1 + * item 2 + * item 3 + * item 4 + * + * item check 1 + * item check 2 + * + * table_wizard + * table_wizard + * + * + * + * + * + * @see baseclass.guielement.event.contextmenu + * + * @event display Fires when the contextmenu is shown. + * @event itemclick Fires when a user presses the mouse button while over a child of this element. + * object: + * {String} value the value of the clicked element. + * + * @constructor + * @define menu + * @allowchild item, divider, check, radio + * @addnode elements + * + * @author Ruben Daniels (ruben AT ajax DOT org) + * @version %I%, %G% + * @since 0.4 + * + * @inherits apf.Presentation + */ +apf.menu = function(struct, tagName){ + this.$init(tagName || "menu", apf.NODE_VISIBLE, struct); + + this.animate = apf.enableAnim; +}; + +(function(){ + this.$focussable = apf.KEYBOARD; + this.$positioning = "basic" + //var _self = this; + //var blurring = false; + + /**** Properties and Attributes ****/ + + this.zindex = 10000000; + this.visible = false; + this.matchhide = false; + + this.$booleanProperties["animate"] = true; + this.$booleanProperties["autohide"] = true; + this.$booleanProperties["matchhide"] = true; + + this.$propHandlers["visible"] = function(value, prop, force, nofocus, hideOpener){ + if (value) { + this.$ext.style.display = "block"; + if (this.opener && this.opener.localName.indexOf('item') > -1) + this.opener.parentNode.$showingSubMenu = this; + } + else { + this.$ext.style.display = "none"; + + var lastFocus = apf.menu.lastFocus; + + //@todo test this with a list being the opener of the menu + if (lastFocus != this.opener && this.opener && this.opener.$blur) + this.opener.$blur(); + + if (this.opener && this.opener.parentNode && this.opener.parentNode.localName == "menu") { + if (!this.$hideTree) + this.$hideTree = -1 + this.opener.parentNode.focus(); + } + + + else if (lastFocus) { + //We're being hidden because some other object gets focus + if (apf.window.$settingFocus) { + if (apf.window.$settingFocus != lastFocus && lastFocus.$blur) + lastFocus.$blur(); + this.$blur(); + + if (apf.window.$settingFocus.localName != "menu") //not menu walking + apf.menu.lastFocus = null; + } + //We're being hidden because window looses focus + else if (!apf.window.hasFocus()) { + if (lastFocus.$blur) + lastFocus.$blur(); + this.$blur(); + + apf.document.activeElement = lastFocus; + if (lastFocus.$focusParent) + lastFocus.$focusParent.$lastFocussed = lastFocus; + + apf.menu.lastFocus = null; + } + //We're just being hidden + else if (this.$hideTree) { + if (!this.$hideTree) + this.$hideTree = -1 + + var visTest = (lastFocus.disabled || !lastFocus.visible) + && lastFocus != apf.document.documentElement; + + if (nofocus || visTest) { + if (lastFocus.$blur) + lastFocus.$blur(); + this.$blur(); + apf.document.activeElement = null; + + if (visTest && apf.window.moveNext() === false) + apf.window.$focusRoot(); + } + else { + lastFocus.focus(null, null, true); + } + + apf.menu.lastFocus = null; + } + } + + + clearTimeout(this.$submenuTimer); + + if (this.$showingSubMenu) { + this.$showingSubMenu.hide(); + this.$showingSubMenu = null; + } + + if (this.opener && this.opener.$submenu) { + this.opener.$submenu(true, true); + + //@todo problem with loosing focus when window looses focus + if (this.$hideTree === true && this.opener.parentNode.localName == "menu") { + this.opener.parentNode.$hideTree = true + this.opener.parentNode.hide(); + } + + this.opener = null; + } + this.$hideTree = null; + + if (this.$selected) { + apf.setStyleClass(this.$selected.$ext, "", ["hover"]); + this.$selected = null; + } + } + }; + + /**** Public Methods ****/ + + var lastFocus; + + /** + * Shows the menu, optionally within a certain context. + * @param {Number} x the left position of the menu. + * @param {Number} y the top position of the menu. + * @param {Boolean} noanim whether to animate the showing of this menu. + * @param {AMLElement} opener the element that is the context of this menu. + * @param {XMLElement} xmlNode the {@link term.datanode data node} that provides data context to the menu child nodes. + * @see baseclass.guielement.event.contextmenu + */ + this.display = function(x, y, noanim, opener, xmlNode, openMenuId, btnWidth){ + this.opener = opener; + + //Show / hide Child Nodes Based on XML + if (xmlNode) { + var last, i, node, + nodes = this.childNodes, + c = 0, + l = nodes.length, result; + for (i = 0; i < l; i++) { + node = nodes[i]; + if (node.nodeType != 1) continue; + + result = !xmlNode || !node.match || (node.cmatch || (node.cmatch = apf.lm.compile(node.match, { + xpathmode : 3, + injectself : true + })))(xmlNode) + + if (result) { + if (this.matchhide) + node.show(); + else + node.enable(); + + if (node.localName == "divider" && this.matchhide) { + last = node; + if (c == 0) + node.hide(); + c = 0; + } + else c++; + } + else { + if (this.matchhide) + node.hide(); + else + node.disable(); + + if (!node.nextSibling && c == 0) + last.hide(); + } + } + } + + if (this.oOverlay) { + if (btnWidth) { + this.oOverlay.style.display = "block"; + this.oOverlay.style.width = btnWidth + "px"; + } + else + this.oOverlay.style.display = "none"; + } + + function afterRender(){ + //@todo consider renaming this to onshow and onhide + this.dispatchEvent("display", {opener: opener}); + + if (x === null) { + apf.popup.show(this.$uniqueId, { + x : 0, + y : opener.$ext.offsetHeight, + animate : noanim || !this.animate ? false : "fade", + steps : 10, + ref : opener.$ext, + allowTogether: openMenuId, + autohide : this.autohide !== false, + noleft : this.left !== undefined + }); + } + else { + var bodyPos = apf.getAbsolutePosition(document.body); + apf.popup.show(this.$uniqueId, { + x : x - bodyPos[0], + y : y - bodyPos[1] - (apf.isIE && apf.isIE < 8 ? 1 : 0), + animate : noanim || !this.animate ? false : "fade", + steps : 10, + //ref : this.$ext.offsetParent, + allowTogether: openMenuId, + autohide : this.autohide !== false + //autoCorrect : false + }); + } + + var lastFocus = + apf.menu.lastFocus = opener && opener.$focussable === true + ? opener + : apf.menu.lastFocus || apf.document.activeElement; + + apf.popup.last = null; + + //if (!apf.isGecko) //This disables keyboard support for gecko - very strange behaviour + this.focus(); + + //Make the component that provides context appear to have focus + + if (lastFocus && lastFocus != this && lastFocus.$focus) + lastFocus.$focus(); + + this.xmlReference = xmlNode; + } + + this.visible = false; + + if (this.$rendered !== false) { + this.show(); + afterRender.call(this); + } + else { + this.addEventListener("afterrender", afterRender); + this.show(); + } + }; + + /** + * Returns the current value of this element. + * @return {String} + */ + this.getValue = function(group){ + return this.getSelected(group).value || ""; + }; + + /** + * Retrieves the selected element from a group of radio elements. + * @param {String} group the name of the group. + * @return {radio} the selected radio element. + */ + this.getSelected = function(group){ + var nodes = this.childNodes; + var i, l = nodes.length; + for (i = 0; i < l; i++) { + if (nodes[i].group != group) + continue; + + if (nodes[i].selected) + return nodes[i]; + } + + return false; + } + + /** + * Selects an element within a radio group. + * @param {String} group the name of the group. + * @param {String} value the value of the item to select. + */ + this.select = function(group, value){ + var nodes = this.childNodes; + var i, l = nodes.length; + for (i = 0; i < l; i++) { + if (nodes[i].group != group) + continue; + + if (nodes[i].value == value || !nodes[i].value && nodes[i].caption == value) + nodes[i].$handlePropSet("selected", true); + else if (nodes[i].selected) + nodes[i].$handlePropSet("selected", false); + } + }; + + /**** Events ****/ + + + this.addEventListener("keydown", function(e){ + var node, key = e.keyCode; + //var ctrlKey = e.ctrlKey; + //var shiftKey = e.shiftKey; + + switch (key) { + case 13: + if (!this.$selected) + return; + + node = this.$selected; + node.$down(); + node.$up(); + node.$click(); + break; + case 27: + this.hide(); + break; + case 38: + //UP + node = this.$selected && this.$selected.previousSibling + || this.lastChild; + + if (node && node.localName == "divider") + node = node.previousSibling; + + if (!node) + return; + + if (this.$selected) + apf.setStyleClass(this.$selected.$ext, "", ["hover"]); + + apf.setStyleClass(node.$ext, "hover"); + this.$selected = node; + break; + case 40: + //DOWN + node = this.$selected && this.$selected.nextSibling + || this.firstChild; + + if (node && node.localName == "divider") + node = node.nextSibling; + + if (!node) + return; + + if (this.$selected) + apf.setStyleClass(this.$selected.$ext, "", ["hover"]); + + apf.setStyleClass(node.$ext, "hover"); + this.$selected = node; + break; + case 37: + //LEFT + //if (this.$selected && this.$selected.submenu) + //this.$selected.$submenu(true, true); + + if (!this.opener) + return; + + if (this.opener.localName == "button") { + node = this.opener.previousSibling; + while(node && !node.submenu) + node = node.previousSibling; + + if (node) { + node.dispatchEvent("mouseover"); + + var btnMenu = node.parentNode.menuIsPressed; + if (btnMenu) { + self[btnMenu.submenu].dispatchEvent("keydown", { + keyCode : 40 + }); + } + } + } + else if (this.opener.parentNode.localName == "menu") { + //@todo Ahum bad abstraction boundary + var op = this.opener; + this.hide(); + apf.setStyleClass(op.$ext, "hover"); + op.parentNode.$showingSubMenu = null; + } + + break; + case 39: + //RIGHT + if (this.$selected && this.$selected.submenu) { + this.$selected.$submenu(null, true); + this.$showingSubMenu.dispatchEvent("keydown", { + keyCode : 40 + }); + + return; + } + + if (this.opener) { + var op = this.opener; + while (op && op.parentNode && op.parentNode.localName == "menu") + op = op.parentNode.opener; + + if (op && op.localName == "button") { + node = op.nextSibling; + while(node && !node.submenu) + node = node.nextSibling; + + if (node) { + node.dispatchEvent("mouseover"); + + var btnMenu = node.parentNode.menuIsPressed; + if (btnMenu) { + self[btnMenu.submenu].dispatchEvent("keydown", { + keyCode : 40 + }); + } + + return; + } + } + } + + if (!this.$selected) { + arguments.callee.call(this, { + keyCode : 40 + }); + } + + break; + default: + return; + } + + return false; + }, true); + + + //Hide menu when it looses focus or when the popup hides itself + function forceHide(e){ + if (this.$showingSubMenu || this.autohide === false + || apf.isChildOf(e.fromElement, e.toElement) + || apf.isChildOf(this, e.toElement) || !e.toElement) + return; + + if (this.$hideTree != -1) { + this.$hideTree = true; + this.hide(); + } + + return false; + } + + this.addEventListener("focus", function(){ + apf.popup.last = this.$uniqueId; + }); + + this.addEventListener("blur", forceHide); + this.addEventListener("popuphide", forceHide); + + /**** Init ****/ + + this.$draw = function(){ + this.$pHtmlNode = document.body; + + //Build Main Skin + this.$ext = this.$getExternal(); + this.oOverlay = this.$getLayoutNode("main", "overlay", this.$ext); + + apf.popup.setContent(this.$uniqueId, this.$ext, "", null, null); + }; + + this.$loadAml = function(x){ + this.$int = this.$getLayoutNode("main", "container", this.$ext); + }; + + this.$destroy = function(){ + apf.popup.removeContent(this.$uniqueId); + }; +}).call(apf.menu.prototype = new apf.Presentation()); + +apf.aml.setElement("menu", apf.menu); + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/elements/method.js)SIZE(3973)TIME(Mon, 10 Jan 2011 10:53:12 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + + +/** + * element specifying a method available within the rpc element. + * + * @attribute {String} name the name of the method. This name will + * be available on the rpc object as a + * javascript method. + * Example: + * + * + * + * + * + * + * comm.save(data); + * + * + * @attribute {String} [callback] the name of the method that handles + * the return of the call. + * @attribute {Boolean} [async] whether the call is executed in the + * backround. Default is true. When set + * to false the application hangs while + * this call is executed. + * @attribute {Boolean} [caching] whether the call is cached. Default + * is false. When set to true any call + * with the same data will return immediately + * with the cached result. + * @attribute {Boolean} [ignore-offline] whether the method should not be stored + * for later execution when offline. + * @attribute {Boolean} [method-name] the name sent to the server. + * + * @attribute {String} [type] the type of the returned data + * Possible values: + * xml returns the response as an xml document + * + * @allowchild variable + */ +apf.method = function(struct, tagName){ + this.$init(tagName || "method", apf.NODE_HIDDEN, struct); + + this.async = true; + this.caching = false; + this["ignore-offline"] = false; +}; + +(function(){ + this.$parsePrio = "002"; + + this.$booleanProperties["async"] = true; + this.$booleanProperties["caching"] = true; + this.$booleanProperties["ignore-offline"] = true; + + this.$supportedProperties.push("name", "receive", "async", "caching", + "ignore-offline", "method-name", "type", "url"); + + this.$propHandlers["ignore-offline"] = function(value){ + this.ignoreOffline = value; + }; + + this.$propHandlers["method-name"] = function(value){ + this.methodName = value; + }; + + /**** DOM Handlers ****/ + + this.addEventListener("DOMNodeInserted", function(e){ + if (this.parentNode.$addMethod && e.currentTarget == this) + this.parentNode.$addMethod(this); + }); + + this.addEventListener("DOMNodeRemoved", function(e){ + if (this.parentNode.$removeMethod && e.currentTarget == this) + this.parentNode.$removeMethod(this); + }); + + this.addEventListener("DOMNodeInsertedIntoDocument", function(e){ + if (this.parentNode.$addMethod) + this.parentNode.$addMethod(this); + }); +}).call(apf.method.prototype = new apf.AmlElement()); + +apf.aml.setElement("method", apf.method); + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/elements/modalwindow.js)SIZE(24784)TIME(Mon, 10 Jan 2011 10:53:12 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + + +/** + * @private + */ +apf.WinServer = { + count : 9000, + wins : [], + + setTop : function(win, norecur){ + if (win.zindex || win.modal) + return; + + if (win.$opened) { + if (win.$opened.visible) + return; + else + delete win.$opened; + } + + var topmost; + if (!norecur && this.wins.length) { + var topmost = this.wins[this.wins.length - 1]; + if (topmost == win) + return; + + if (!topmost.modal || !topmost.visible) + topmost = null; + else if (topmost && win.modal) { + win.$opener = topmost; + topmost.$opened = win; + topmost = null; + } + } + + this.count += 2; + + win.setProperty("zindex", this.count); + this.wins.remove(win); + this.wins.push(win); + + if (topmost) + this.setTop(topmost, true); + + return win; + }, + + setNext : function(){ + if (this.wins.length < 2) return; + var nwin, start = this.wins.shift(); + do { + if (this.setTop(nwin || start).visible) + break; + nwin = this.wins.shift(); + } while (start != nwin); + }, + + setPrevious : function(){ + if (this.wins.length < 2) return; + this.wins.unshift(this.wins.pop()); + var nwin, start = this.wins.pop(); + do { + if (this.setTop(nwin || start).visible) + break; + nwin = this.wins.pop(); + } while (start != nwin); + }, + + remove : function(win){ + this.wins.remove(win); + } +} + +/** + * Element displaying a skinnable, draggable window with optionally + * a min, max, edit and close button. This element is also used + * as a portal widget container. Furthermore this element supports + * docking in an alignment layout. + * Example: + * + * + * + * + * + * @constructor + * @define modalwindow + * @allowchild {elements}, {smartbinding}, {anyaml} + * @addnode elements + * + * @author Ruben Daniels (ruben AT ajax DOT org) + * @version %I%, %G% + * @since 0.4 + * + * @inherits apf.Presentation + * @inherits apf.Transaction + * + * @event show Fires when the window is opened. + * @event close Fires when the window is closed. + * @event editstart Fires before the user edits the properties of this window. Used mostly for when this window is part of the {@link element.portal}. + * @event editstop Fires after the user edited the properties of this window. Used mostly for when this window is part of the {@link element.portal}. + * cancelable: Prevents the edit panel from being closed. + * @event statechange Fires after the state of this window changed. + * object: + * {Boolean} minimized whether the window is minimized. + * {Boolean} maximized whether the window is maximized. + * {Boolean} normal whether the window has it's normal size and position. + * {Boolean} edit whether the window is in the edit state. + * {Boolean} closed whether the window is closed. + */ +apf.toolwindow = function(struct, tagName){ + this.$init(tagName || "toolwindow", apf.NODE_VISIBLE, struct); +}; + +apf.modalwindow = function(struct, tagName){ + this.$init(tagName || "modalwindow", apf.NODE_VISIBLE, struct); +}; + +apf.AmlWindow = function(struct, tagName){ + this.$init(tagName || "window", apf.NODE_VISIBLE, struct); +}; + +(function(){ + this.implement( + apf.BaseStateButtons + ); + + this.$isWindowContainer = true; + this.collapsedHeight = 30; + this.canHaveChildren = 2; + this.visible = false; + this.showdragging = false; + this.kbclose = false; + this.$focussable = apf.KEYBOARD; + this.$editableCaption = ["title"]; + + /**** Public Methods ****/ + + + + /** + * Sets the title of the window. Call-chaining is supported. + * @param {String} caption the text of the title. + */ + this.setTitle = function(caption){ + this.setProperty("title", caption, false, true); + return this; + }; + + /** + * Sets the icon of the window. Call-chaining is supported. + * @param {String} icon the location of the image. + */ + this.setIcon = function(icon){ + this.setProperty("icon", icon, false, true); + return this; + }; + + //For modal elements + this.show = function(callback){ + this.execAction = callback; //@todo Proper error handling?? + this.setProperty("visible", true, false, true); + return this; + } + + + + + this.slideIn = function(sFrom, bSticky) { + if (!sFrom) + sFrom = "bottom"; + var _center = this.center; + this.center = false; + this.setProperty("visible", true); + this.center = _center; + + var iFrom = 0, + iTo = 0, + innerW = (apf.isIE + ? this.$ext.offsetParent.offsetWidth + : window.innerWidth), + innerH = (apf.isIE + ? this.$ext.offsetParent.offsetHeight + : window.innerHeight), + cX = Math.max(0, ((innerW - this.$ext.offsetWidth) / 2)), + cY = Math.max(0, ((innerH - this.$ext.offsetHeight) / 3)), + sType = "top", + pad = 10; + + switch(sFrom) { + case "top": + iFrom = -(this.$ext.offsetHeight) - pad; + iTo = bSticky ? 0 : cY; + break; + case "left": + iFrom = -(this.$ext.offsetWidth) - pad; + iTo = bSticky ? 0 : cX; + sType = "left"; + break; + case "bottom": + iFrom = innerH + this.$ext.offsetHeight + pad; + iTo = bSticky ? innerH - this.$ext.offsetHeight : cY; + break; + case "right": + iFrom = innerW + this.$ext.offsetLeft + pad; + iTo = bSticky ? innerW - this.$ext.offsetWidth : cX; + sType = "left"; + break; + } + + apf.tween.single(this.$ext, { + steps : apf.isIphone ? 5 : 30, + interval: 10, + from : iFrom, + to : iTo, + type : sType, + anim : apf.tween.EASEIN + }); + return this; + }; + + this.slideOut = function(sTo) { + if (!sTo) + sTo = "bottom"; + var iFrom = 0, + iTo = 0, + sType = "top", + pad = 10; + + switch(sTo) { + case "top": + iFrom = this.$ext.offsetTop; + iTo = -(this.$ext.offsetHeight) - pad; + break; + case "left": + iFrom = this.$ext.offsetLeft; + iTo = -(this.$ext.offsetWidth) - pad; + sType = "left"; + break; + case "bottom": + iFrom = this.$ext.offsetTop; + iTo = (apf.isIE + ? this.$ext.offsetParent.offsetHeight + : window.innerHeight) + this.$ext.offsetHeight + pad; + break; + case "right": + iFrom = this.$ext.offsetLeft; + iTo = (apf.isIE + ? this.$ext.offsetParent.offsetWidth + : window.innerWidth) + this.$ext.offsetLeft + pad; + sType = "left"; + break; + } + + var _self = this; + apf.tween.single(this.$ext, { + steps : apf.isIphone ? 5 : 30, + interval: 10, + from : iFrom, + to : iTo, + type : sType, + anim : apf.tween.EASEOUT, + onfinish: function() { _self.setProperty("visible", false); } + }); + return this; + }; + + + + this.bringToFront = function(){ + apf.WinServer.setTop(this); + return this; + }; + + /**** Properties and Attributes ****/ + + this.$booleanProperties["modal"] = true; + this.$booleanProperties["center"] = true; + this.$booleanProperties["transaction"] = true; + this.$booleanProperties["hideselects"] = true; + this.$booleanProperties["showdragging"] = true; + this.$booleanProperties["kbclose"] = true; + this.$supportedProperties.push("title", "icon", "modal", "minwidth", + "minheight", "hideselects", "center", "kbclose", + "maxwidth", "maxheight", "showdragging", "transaction"); + + /** + * @attribute {Boolean} modal whether the window prevents access to the + * layout below it. + */ + this.$propHandlers["modal"] = function(value){ + if (value) { + if (this.visible) + apf.plane.show(this.$ext, false, null, null, { + color : "black", + opacity : 0.5, + protect : this.$uniqueId + }); + } + else { + apf.plane.hide(this.$uniqueId); + } + }; + + /** + * @attribute {Boolean} center centers the window relative to it's parent's + * containing rect when shown. + */ + this.$propHandlers["center"] = function(value){ + this.$ext.style.position = "absolute"; //@todo no unset + }; + + /** + * @attribute {String} title the text of the title. + */ + this.$propHandlers["title"] = function(value){ + if (this.oTitle) + this.oTitle.nodeValue = value; + }; + + /** + * @attribute {String} icon the location of the image. + */ + this.$propHandlers["icon"] = function(value){ + if (!this.oIcon) return; + + this.oIcon.style.display = value ? "" : "none"; + apf.skins.setIcon(this.oIcon, value, this.iconPath); + }; + + this.$afterRender = function(){ + if (this.center && !this.left && !this.top && !this.right && !this.bottom && !this.anchors) { + + apf.layout.processQueue(); + + + var size = !this.$ext.offsetParent || this.$ext.offsetParent.tagName == "BODY" + ? [apf.getWindowWidth(), apf.getWindowHeight()] + : [this.$ext.offsetParent.offsetWidth, this.$ext.offsetParent.offsetHeight, 0, 0]; + + if (size.length == 2) { + size.push(document.documentElement.scrollLeft, + document.documentElement.scrollTop); + } + + //@todo it's better to add this to the layout queue + this.$ext.style.left = (Math.max(0, (( + size[0] - parseInt(this.$ext.offsetWidth || 0))/2)) + size[2]) + "px"; + this.$ext.style.top = (Math.max(0, (( + size[1] - parseInt(this.$ext.offsetHeight || 0))/3)) + size[3]) + "px"; + } + + + //@todo make widget a tagname and alias + if (this.$amlLoaded && (this.model + || (!this.dockable || !this.aData) && !this.$isWidget + && this.localName != "toolwindow")) + this.focus(false, {mouse:true}); + + + this.dispatchEvent("show"); + } + + var hEls = [], wasVisible; + this.$propHandlers["visible"] = function(value){ + if (apf.isTrue(value)){ + if (this.dispatchEvent("beforeshow") === false) + return (this.visible = false); + + if (this.modal){ + apf.plane.show(this.$ext, false, null, null, { + color : "black", + opacity : 0.5, + protect : this.$uniqueId + }); + } + + this.state = this.state.split("|").remove("closed").join("|"); + + this.$ext.style.display = ""; //Some form of inheritance detection + + //if (this.modal) + //this.$ext.style.position = "fixed"; + + if (!apf.canHaveHtmlOverSelects && this.hideselects) { + hEls = []; + var nodes = document.getElementsByTagName("select"); + for (var i = 0; i < nodes.length; i++) { + var oStyle = apf.getStyle(nodes[i], "display"); + hEls.push([nodes[i], oStyle]); + nodes[i].style.display = "none"; + } + } + + //if (this.modal) + //this.$ext.style.zIndex = apf.plane.$zindex - 1; + + if (apf.isIE) { + var cls = this.$ext.className; + this.$ext.className = "rnd" + Math.random(); + this.$ext.className = cls; + } + + if (this.$rendered === false) + this.addEventListener("afterrender", this.$afterRender); + else + this.$afterRender(); + } + else { + if (this.modal) + apf.plane.hide(this.$uniqueId); + + this.$ext.style.display = "none"; + + if (!apf.canHaveHtmlOverSelects && this.hideselects) { + for (var i = 0; i < hEls.length; i++) { + hEls[i][0].style.display = hEls[i][1]; + } + } + + if (this.hasFocus()) + apf.window.moveNext(true, this, true);//go backward to detect modals + + this.visible = false; + + this.dispatchEvent("hide"); + } + + + if (apf.layout && this.$int) + apf.layout.forceResize(this.$int); //@todo this should be recursive down + + + wasVisible = value; + }; + + this.$propHandlers["zindex"] = function(value){ + this.$ext.style.zIndex = value + 1; + }; + + /**** Keyboard ****/ + + + + if (!apf.isIphone) { + + this.addEventListener("keydown", function(e){ + var key = e.keyCode; + var ctrlKey = e.ctrlKey; + var shiftKey = e.shiftKey; + + /*if (key > 36 && key < 41) { + if (this.hasFeature && this.hasFeature(apf.__ANCHORING__)) + this.$disableAnchoring(); + }*/ + + var retValue = false; + switch (key) { + /*case 9: + break; + case 13: + break; + case 32: + break;*/ + case 38: + //UP + if (shiftKey && this.resizable) + this.setProperty("height", Math.max(this.minheight || 0, + this.$ext.offsetHeight - (ctrlKey ? 50 : 10))); + else if (this.draggable) + this.setProperty("top", + this.$ext.offsetTop - (ctrlKey ? 50 : 10)); + break; + case 37: + //LEFT + if (shiftKey && this.resizable) + this.setProperty("width", Math.max(this.minwidth || 0, + this.$ext.offsetWidth - (ctrlKey ? 50 : 10))); + else if (this.draggable) + this.setProperty("left", + this.$ext.offsetLeft - (ctrlKey ? 50 : 10)); + break; + case 39: + //RIGHT + if (shiftKey && this.resizable) + this.setProperty("width", Math.min(this.maxwidth || 10000, + this.$ext.offsetWidth + (ctrlKey ? 50 : 10))); + else if (this.draggable) + this.setProperty("left", + this.$ext.offsetLeft + (ctrlKey ? 50 : 10)); + break; + case 40: + //DOWN + if (shiftKey && this.resizable) + this.setProperty("height", Math.min(this.maxheight || 10000, + this.$ext.offsetHeight + (ctrlKey ? 50 : 10))); + else if (this.draggable) + this.setProperty("top", + this.$ext.offsetTop + (ctrlKey ? 50 : 10)); + break; + default: + retValue = null; + return; + } + + if (apf.hasSingleRszEvent) + apf.layout.forceResize(this.$int); + + return retValue; + }, true); + + this.addEventListener("keydown", function(e){ + if (e.keyCode == 27 && this.buttons.indexOf("close") > -1 + && (!this.dockable || !this.aData) && this.kbclose) + this.close(); + }); + + } + + + + + /**** Init ****/ + + this.$draw = function(){ + this.popout = apf.isTrue(this.getAttribute("popout")); + if (this.popout) + this.$pHtmlNode = document.body; + + this.$ext = this.$getExternal(null, null, function(oExt){ + this.$initButtons(oExt); + }); + this.oTitle = this.$getLayoutNode("main", "title", this.$ext); + this.oIcon = this.$getLayoutNode("main", "icon", this.$ext); + this.oDrag = this.$getLayoutNode("main", "drag", this.$ext); + this.$buttons = this.$getLayoutNode("main", "buttons", this.$ext); + + if (this.popout) + this.$ext.style.position = "absolute"; + + if (this.oIcon) + this.oIcon.style.display = "none"; + + + if (!apf.isIphone) { + + + var _self = this; + if (this.oDrag) { + this.oDrag.host = this; + this.oDrag.onmousedown = function(e){ + if (!e) e = event; + + //because of some issue I don't understand oExt.onmousedown is not called + if (!_self.$isWidget && (!_self.aData || !_self.dockable || _self.aData.hidden == 3)) + apf.WinServer.setTop(_self); + + if (_self.$lastState.maximized) + return false; + + + if (_self.aData && _self.dockable) { + if (_self.$lastState.normal) //@todo + _self.startDocking(e); + return false; + } + + }; + } + + this.$ext.onmousedown = function(){ + + var p = apf.document.activeElement; + if (p && p.$focusParent != _self && p.$focusParent.modal) + return false; + + + //Set ZIndex on oExt mousedown + if (!_self.$isWidget && (!_self.aData || !_self.dockable || _self.aData.hidden == 3)) + apf.WinServer.setTop(_self); + + if (!_self.$lastState.normal) + return false; + } + this.$ext.onmousemove = function(){ + if (!_self.$lastState.normal) + return false; + } + + } + + + + /*var v; + if (!((v = this.getAttribute("visible")).indexOf("{") > -1 || v.indexOf("[") > -1)) { + this.$aml.setAttribute("visible", "{" + apf.isTrue(v) + "}"); + }*/ + }; + + this.$loadAml = function(x){ + apf.WinServer.setTop(this); + + this.$int = this.$getLayoutNode("main", "container", this.$ext); + + + if (!apf.isIphone) { + + if (this.oTitle) { + var _self = this; + (this.oTitle.nodeType != 1 + ? this.oTitle.parentNode + : this.oTitle).ondblclick = function(e){ + if (_self.state.indexOf("normal") == -1) + _self.restore(); + else if (_self.buttons.indexOf("max") > -1) + _self.maximize(); + else if (_self.buttons.indexOf("min") > -1) + _self.minimize(); + } + } + + if (typeof this.draggable == "undefined") { + (this.$propHandlers.draggable + || apf.GuiElement.propHandlers.draggable).call(this, true); + this.draggable = true; + } + + if (typeof this.buttons == "undefined") + this.buttons = ""; + //this.setProperty("buttons", "min|max|close"); + + } + + + if (this.modal === undefined) { + this.$propHandlers.modal.call(this, true); + this.modal = true; + } + + //Set default visible hidden + if (!this.visible) { + this.$ext.style.display = "none"; + } + + else if (this.modal) { + var _self = this; + apf.queue.add("focus", function(){ + _self.focus(false, {mouse:true}); + }); + } + + + if (this.minwidth === undefined) + this.minwidth = this.$getOption("Main", "min-width"); + if (this.minheight === undefined) + this.minheight = this.$getOption("Main", "min-height"); + if (this.maxwidth === undefined) + this.maxwidth = this.$getOption("Main", "max-width"); + if (this.maxheight === undefined) + this.maxheight = this.$getOption("Main", "max-height"); + + if (this.center && this.visible) { + this.visible = false; + this.$ext.style.display = "none"; /* @todo temp done for project */ + + var _self = this; + $setTimeout(function(){ + _self.setProperty("visible", true); + }); + } + }; + + + this.addEventListener("$skinchange", function(){ + if (this.title) + this.$propHandlers["title"].call(this, this.title); + + if (this.icon) + this.$propHandlers["icon"].call(this, this.icon); + }); + + + this.$destroy = function(skinChange){ + if (this.oDrag) { + this.oDrag.host = null; + this.oDrag.onmousedown = null; + apf.destroyHtmlNode(this.oDrag); + this.oDrag = null; + } + + this.oTitle = this.oIcon = null; + + if (this.$ext && !skinChange) { + this.$ext.onmousedown = null; + this.$ext.onmousemove = null; + } + }; + + +}).call(apf.modalwindow.prototype = new apf.Presentation()); + +apf.AmlWindow.prototype = apf.toolwindow.prototype = apf.modalwindow.prototype; + +apf.aml.setElement("toolwindow", apf.toolwindow); +apf.aml.setElement("modalwindow", apf.modalwindow); +apf.aml.setElement("window", apf.modalwindow); + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/elements/model.js)SIZE(43289)TIME(Mon, 10 Jan 2011 10:53:12 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + + +/** + * Element functioning as the central access point for xml data. Data can be + * retrieved from any data source using data instructions. Data can be + * submitted using data instructions in a similar way to html form posts. The + * modal can be reset to it's original state. It has support for offline use and + * {@link element.remove synchronization between multiple clients}. + * Example: + * + * + * + * Example: + * A small form where the bound data is submitted to a server using a model. + * + * + * + * + * + * + * Name + * + * Address + * + * Submit + * + * + * + * @event beforeretrieve Fires before a request is made to retrieve data. + * cancelable: Prevents the data from being retrieved. + * @event afterretrieve Fires when the request to retrieve data returns both on success and failure. + * @event receive Fires when data is successfully retrieved + * object: + * {String} data the retrieved data + * @event beforeload Fires before data is loaded into the model. + * cancelable: + * @event afterload Fires after data is loaded into the model. + * @event beforesubmit Fires before data is submitted. + * cancelable: Prevents the submit. + * object: + * {String} instruction The data instruction used to store the data. + * @event submiterror Fires when submitting data has failed. + * @event submitsuccess Fires when submitting data was successfull. + * @event aftersubmit Fires after submitting data. + * @event error Fires when a communication error has occured while making a request for this element. + * cancelable: Prevents the error from being thrown. + * bubbles: + * object: + * {Error} error the error object that is thrown when the event callback doesn't return false. + * {Number} state the state of the call + * Possible values: + * apf.SUCCESS the request was successfull + * apf.TIMEOUT the request has timed out. + * apf.ERROR an error has occurred while making the request. + * apf.OFFLINE the request was made while the application was offline. + * {mixed} userdata data that the caller wanted to be available in the callback of the http request. + * {XMLHttpRequest} http the object that executed the actual http request. + * {String} url the url that was requested. + * {Http} tpModule the teleport module that is making the request. + * {Number} id the id of the request. + * {String} message the error message. + * + * @constructor + * @define model + * @allowchild [cdata], instance, load, submission + * @addnode smartbinding, global + * @attribute {String} src the data instruction on how to load data from the data source into this model. + * @attribute {String} submission the data instruction on how to record the data from the data source from this model. + * @attribute {String} session the data instruction on how to store the session data from this model. + * @attribute {Boolean} autoinit whether to initialize the model immediately. If set to false you are expected to call init() when needed. This is useful when the system has to log in first. + * @attribute {Boolean} enablereset whether to save the original state of the data. This enables the use of the reset() call. + * @attribute {String} remote the id of the {@link element.remote} element to use for data synchronization between multiple clients. + * + * @author Ruben Daniels (ruben AT ajax DOT org) + * @version %I%, %G% + * @since 0.8 + */ +apf.model = function(struct, tagName){ + this.$init(tagName || "model", apf.NODE_HIDDEN, struct); + + this.$amlNodes = {}; + this.$propBinds = {}; + + this.$listeners = {}; + this.$proplisteners = {}; + + if (!apf.globalModel) { + apf.globalModel = this; + + apf.nameserver.register("model", "@default", this); + + } +}; + +(function(){ + this.$parsePrio = "020"; + this.$isModel = true; + + this.canHaveChildren = false; + this.enablereset = false; + + this.$state = 0;//1 = loading + + //1 = force no bind rule, 2 = force bind rule + this.$attrExcludePropBind = apf.extend({ + submission : 1, + src : 1, + session : 1 + }, this.$attrExcludePropBind); + + this.$booleanProperties["autoinit"] = true; + this.$booleanProperties.enablereset = true; + this.$supportedProperties = ["submission", "src", "session", "autoinit", + "enablereset", "remote"]; + + this.$propHandlers["src"] = + this.$propHandlers["get"] = function(value, prop){ + if (this.$amlLoaded) + this.$loadFrom(value); + }; + + + //Connect to a remote databinding + this.$propHandlers["remote"] = function(value, prop){ + if (value) { + if (this.src && this.src.indexOf("rdb://") === 0) { + var _self = this; + apf.queue.add("rdb_load_" + this.$uniqueId, function(){ + _self.unshare(); + _self.share(); + }); + } + } + else + this.unshare(); + }; + + this.share = function(xpath) { + this.rdb = typeof this.remote == "string" + ? + + apf.nameserver.get("remote", this.remote) + + : this.remote; + + + if (!this.rdb || !this.rdb.$sessions) { + throw new Error(apf.formatErrorString(0, null, + "Loading AML into model", + "Could not find reference to remote databinding: '" + + this.remote + "'", this)) + } + + + this.rdb.createSession(this.src, this, xpath); + }; + + this.unshare = function(xpath) { + if (!this.rdb) return; + this.rdb.endSession(this.src); + this.rdb = null; + }; + + + /** + * Registers a aml element to this model in order for the aml element to + * receive data loaded in this model. + * + * @param {AMLElement} amlNode The aml element to be registered. + * @param {String} [xpath] the xpath query which is executed on the + * data of the model to select the node to be + * loaded in the amlNode. + * @return {Model} this model + * @private + */ + this.register = function(amlNode, xpath){ + if (!amlNode || !amlNode.load) //hasFeature(apf.__DATABINDING__)) + return this; + + var isReloading = amlNode.$model == this; + + //Remove previous model + if (amlNode.$model && !isReloading) + amlNode.$model.unregister(amlNode); + + //Register the aml node + var item = this.$amlNodes[amlNode.$uniqueId] = { + amlNode : amlNode, + xpath : xpath + }; + amlNode.$model = this; + + if (typeof amlNode.noloading == "undefined" + && amlNode.$setInheritedAttribute + && !amlNode.$setInheritedAttribute("noloading")) + amlNode.noloading = false; + + //amlNode.$model = this; + if (this.$state == 1) { + if (amlNode.clear && !amlNode.noloading) + amlNode.clear("loading");//@todo apf3.0 + } + else if (this.data) { + this.$loadInAmlNode(item); + //this.$loadInAmlProp(amlNode); + } + else { //@experimental + if (amlNode.hasFeature(apf.__CACHE__)) // amlNode.clear + amlNode.clear("empty"); + } + + var p, node, list = amlNode.$propsUsingMainModel, id = amlNode.$uniqueId; + for (var prop in list) { + this.$unbindXmlProperty(amlNode, prop); + p = this.$bindXmlProperty(amlNode, prop, + list[prop].xpath, list[prop].optimize); + + if (this.data) { + //if (node = p.root || p.listen ? this.data.selectSingleNode(p.root || p.listen) : this.data) { + if (node = p.listen ? this.data.selectSingleNode(p.listen) : this.data) { + amlNode.$execProperty(prop, node); + } + else + this.$waitForXml(amlNode, prop); + } + } + + return this; + }; + + this.$register = function(amlNode, xpath){ + //@todo apf3.0 update this.$propBinds; + + this.$amlNodes[amlNode.$uniqueId].xpath = xpath; + }; + + /** + * Removes a aml element from the group of registered aml elements. + * The aml element will not receive any updates from this model, however + * the data loaded in the aml element is not unloaded. + * + * @param {AMLElement} amlNode The aml element to be unregistered. + * @private + */ + this.unregister = function(amlNode){ + delete this.$amlNodes[amlNode.$uniqueId]; + + var list = amlNode.$propsUsingMainModel; + for (var prop in list) + this.$unbindXmlProperty(amlNode, prop); + + amlNode.dispatchEvent("unloadmodel"); + }; + + /** + * @private + */ + this.getXpathByAmlNode = function(amlNode){ + var n = this.$amlNodes[amlNode.$uniqueId]; + if (!n) + return false; + + return n.xpath; + }; + + /** + * @private + */ + this.$loadInAmlNode = function(item){ + var xmlNode; + var xpath = item.xpath; + var amlNode = item.amlNode; + + if (this.data && xpath) { + xmlNode = this.data.selectSingleNode(xpath); + } + else + xmlNode = this.data || null; + + if (xmlNode) { + delete this.$listeners[amlNode.$uniqueId]; + amlNode.load(xmlNode); + } + else + this.$waitForXml(amlNode); + }; + + this.$loadInAmlProp = function(id, xmlNode){ + var prop, node, p = this.$propBinds[id], amlNode = apf.all[id]; + if (!amlNode){ + delete this.$propBinds[id]; + return; + } + + if (amlNode.$noInitModel) { + delete amlNode.$noInitModel; + return; + } + + + for (prop in p) { + if (xmlNode && (node = p[prop].listen + ? xmlNode.selectSingleNode(p[prop].listen) + : xmlNode)) { + apf.xmldb.addNodeListener(xmlNode, amlNode, + "p|" + id + "|" + prop + "|" + this.$uniqueId); + + delete this.$proplisteners[id + prop]; + amlNode.$execProperty(prop, node); + } + else + this.$waitForXml(amlNode, prop); + } + }; + + /* + We don't want to connect to the root, that would create a rush + of unnecessary update messages, so we'll find the element that's + closest to the node that is going to feed us the value + + mdlBlah::bli/persons + mdlBlah::bli/persons/person + + $attrBindings + //split / join, pop, indexOf + + + */ + this.$bindXmlProperty = function(amlNode, prop, xpath, optimize, listenRoot) { + var q ,p, id = amlNode.$uniqueId; + if (!this.$propBinds[id]) + this.$propBinds[id] = {}; + + /* + Store + 0 - Original xpath + 1 - Store point of change listener + 2 - Xpath to determine data node passed into load + */ + p = this.$propBinds[id][prop] = { + bind: xpath + }; + + //@todo apf3.0 + //Optimize root point, doesnt work right now because it doesnt change the original rule + if (optimize && false) { + //Find xpath for bind on this model of the amlNode + if ((q = this.$amlNodes[id]) && q.xpath) + xpath = (p.root = q.xpath) + "/" + xpath; + + var l = xpath.split("/"), z = l.pop(); + if (z.indexOf("@") == 0 + || z.indexOf("text()") > -1 + || z.indexOf("node()") > -1) { + p.listen = l.join("/"); + } + else p.listen = xpath; + } + else { + if ((q = this.$amlNodes[id]) && q.xpath) + p.listen = q.xpath; + } + + if (listenRoot) + p.listen = "."; + + if (this.data) { + var xmlNode = + + amlNode.$noInitModel ? amlNode.xmlRoot : + + (p.listen ? this.data.selectSingleNode(p.listen) : this.data); + + if (xmlNode) { + apf.xmldb.addNodeListener(xmlNode, amlNode, + "p|" + amlNode.$uniqueId + "|" + prop + "|" + this.$uniqueId); + + return p; + } + } + + this.$waitForXml(amlNode, prop); + + return p; + }; + + this.$unbindXmlProperty = function(amlNode, prop){ + var id = amlNode.$uniqueId; + + //@todo apf3.0 + var p = this.$propBinds[id] && this.$propBinds[id][prop]; + if (!p) return; + + if (this.data) { + var xmlNode = p.listen ? this.data.selectSingleNode(p.listen) : this.data; + if (xmlNode) { + apf.xmldb.removeNodeListener(xmlNode, amlNode, + "p|" + id + "|" + prop + "|" + this.$uniqueId); + } + } + + delete this.$proplisteners[id + prop]; + delete this.$propBinds[id][prop]; + return p; + }; + + /** + * Gets a copy of current state of the xml of this model. + * + * @return {XMLNode} context of this model + */ + this.getXml = function(){ + return this.data + ? apf.xmldb.cleanNode(this.data.cloneNode(true)) + : false; + }; + + /** + * Sets a value of an XMLNode based on an xpath statement executed on the data of this model. + * + * @param {String} xpath the xpath used to select a XMLNode. + * @param {String} value the value to set. + * @return {XMLNode} the changed XMLNode + */ + this.setQueryValue = function(xpath, value){ + if (!this.data) + return false; + + var node = apf.createNodeFromXpath(this.data, xpath); + if (!node) + return null; + + apf.setNodeValue(node, value, true); + //apf.xmldb.setTextNode(node, value); + return node; + }; + + /** + * Sets a value of a set of xml nodes based on an xpath statement executed on the data of this model. + * + * @param {String} xpath the xpath used to select a the nodeset. + * @param {String} value the value to set. + * @return {XMLNodeSet} the changed XMLNodeSet + */ + this.setQueryValues = function(xpath, value){ + if (!this.data) + return []; + + var nodes = this.data.selectNodes(xpath); + for (var i = 0, l = nodes.length; i < l; i++) + apf.setNodeValue(node, value, true); + + return nodes; + }; + + /** + * Gets the value of an XMLNode based on a xpath statement executed on the data of this model. + * + * @param {String} xpath the xpath used to select a XMLNode. + * @return {String} value of the XMLNode + */ + this.queryValue = function(xpath){ + if (!this.data) + return false; + + return apf.queryValue(this.data, xpath); + }; + + /** + * Gets the value of an XMLNode based on a xpath statement executed on the data of this model. + * + * @param {String} xpath the xpath used to select a XMLNode. + * @return {String} value of the XMLNode + */ + this.queryValues = function(xpath){ + if (!this.data) + return []; + + return apf.queryValue(this.data, xpath); + }; + + /** + * Executes an xpath statement on the data of this model + * + * @param {String} xpath the xpath used to select the XMLNode(s). + * @return {variant} XMLNode or NodeList with the result of the selection + */ + this.queryNode = function(xpath){ + if (!this.data) + return null; + + return this.data.selectSingleNode(xpath) + }; + + /** + * Executes an xpath statement on the data of this model + * + * @param {String} xpath the xpath used to select the XMLNode(s). + * @return {variant} XMLNode or NodeList with the result of the selection + */ + this.queryNodes = function(xpath){ + if (!this.data) + return []; + + return this.data.selectNodes(xpath); + }; + + /** + * Appends a copy of the xmlNode or model to this model as a child + * of it's root node + */ + this.appendXml = function(xmlNode, xpath){ + var insertNode = xpath + ? apf.createNodeFromXpath(this.data, xpath) + : this.data; + if (!insertNode) + return null; + + if (typeof xmlNode == "string") + xmlNode = apf.getXml(xmlNode); + else { + xmlNode = !xmlNode.nodeType //Check if a model was passed + ? xmlNode.getXml() + : apf.xmldb.getCleanCopy(xmlNode); + } + + if (!xmlNode) return; + + apf.xmldb.appendChild(insertNode, xmlNode); + + this.dispatchEvent("update", {xmlNode: xmlNode}); + return xmlNode; + }; + + /** + * Removes xmlNode from this model + */ + this.removeXml = function(xmlNode){ + if (typeof xmlNode == "string") + var xmlNodes = this.data.selectNodes(xmlNode); + else if (!xmlNode.length) + xmlNodes = [xmlNode]; + + if (xmlNodes.length) + apf.xmldb.removeNodeList(xmlNodes); + }; + + /** + * Clears the loaded data from this model. + */ + this.clear = function(){ + this.load(null); + doc = null; //Fix for safari refcount issue; + }; + + /** + * Resets data in this model to the last saved point. + * + */ + this.reset = function(){ + var doc = this.data.ownerDocument; + //doc.removeChild(this.data); + //var node = doc.appendChild(apf.isWebkit ? doc.importNode(this.$copy, true) : this.$copy); + this.data.parentNode.replaceChild(this.$copy, this.data); + this.load(this.$copy); + }; + + /** + * Sets a new saved point based on the current state of the data in this + * model. The reset() method returns the model to this point. + */ + this.savePoint = function(){ + this.$copy = apf.xmldb.getCleanCopy(this.data); + }; + + /** + * @private + */ + this.reloadAmlNode = function(uniqueId){ + if (!this.data) + return; + + var item = this.$amlNodes[uniqueId]; + var xmlNode = item.xpath + ? this.data.selectSingleNode(item.xpath) + : this.data; + item.amlNode.load(xmlNode); + }; + + /** + * @private + */ + //@todo refactor this to use .blah instead of getAttribute + //@todo move this to propHandlers + this.addEventListener("DOMNodeInsertedIntoDocument", function(e){ + var x = this.$aml; + if (this.parentNode && this.parentNode.hasFeature(apf.__DATABINDING__)) { + if (!this.name) + this.setProperty("id", "model" + this.parentNode.$uniqueId); + //this.parentNode.$aml.setAttribute("model", this.name); //@todo don't think this is necesary anymore... + this.register(this.parentNode); + } + + //Load literal model + if (!this.src) { + var strXml, xmlNode = x; + if (xmlNode && xmlNode.childNodes.length) { + if (apf.getNode(xmlNode, [0])) { + if ((strXml = xmlNode.xml || xmlNode.serialize()).match(/^[\s\S]*?>([\s\S]*)<[\s\S]*?$/)) { + strXml = RegExp.$1; //@todo apf3.0 test this with json + if (!apf.supportNamespaces) + strXml = strXml.replace(/xmlns=\"[^"]*\"/g, ""); + } + + return this.load(apf.getXmlDom(strXml).documentElement); + } + // we also support JSON data loading in a model CDATA section + else if (apf.isJson(xmlNode.childNodes[0].nodeValue)) { + return this.load(apf.getXmlDom(xmlNode.childNodes[0].nodeValue).documentElement); + } + } + + //Default data for XForms models without an instance but with a submission node + if (this.submission) + this.load(""); + } + + //Load data into model if allowed + if (!apf.isFalse(this.autoinit)) + this.init(); + + //@todo actions apf3.0 + + return this; + }); + + /** + * Loads the initial data into this model. + * @see element.model.attribute.init + */ + //callback here is private + this.init = function(callback){ + if (this.session) { + this.$loadFrom(this.session, {isSession: true}); + } + else { + + if (typeof apf.offline != "undefined" && apf.offline.models.enabled) { + //Check if there's stored data + if (apf.offline.models.loadModel(this)) { + return; + } + + //Hmm we're offline, lets wait until we're online again + //@todo this will ruin getting data from offline resources + if (this.src && !apf.offline.onLine) { + apf.offline.models.addToInitQueue(this); + return; + } + } + + + if (this.src) + this.$loadFrom(this.src, {callback: callback}); + } + }; + + /* *********** LOADING ****************/ + + /** + * Loads data into this model using a data instruction. + * @param {String} instruction the data instrution how to retrieve the data. + * @param {Object} options + * Properties: + * {XMLElement} xmlNode the {@link term.datanode data node} that provides context to the data instruction. + * {Function} callback the code executed when the data request returns. + * {mixed} [] Custom properties available in the data instruction. + */ + this.$loadFrom = function(instruction, options){ + + if (instruction.indexOf("rdb://") === 0) { + this.src = instruction; //@todo + return this.$propHandlers["remote"].call(this, this.remote); + } + + var data = instruction.split(":"); + + if (!options) + options = {}; + + if (!options.isSession) { + this.src = instruction; + this.$srcOptions = [instruction, options]; + } + + //Loading data in non-literal model + this.dispatchEvent("beforeretrieve"); + + //Set all components on loading... + var uniqueId, item; + for (uniqueId in this.$amlNodes) { + if (!(item = this.$amlNodes[uniqueId]) || !item.amlNode) + continue; + + //@todo apf3.0 + if (!item.amlNode.noloading) + item.amlNode.clear("loading"); + } + + this.$state = 1; + if (!this.$callCount) + this.$callCount = 1; + else + this.$callCount++; + + var _self = this, + callCount = this.$callCount, + callback = options.callback; + options.callback = function(data, state, extra){ + if (callCount != _self.$callCount) + return; //another call has invalidated this one + + _self.dispatchEvent("afterretrieve"); + + + if (state == apf.OFFLINE) { + apf.offline.models.addToInitQueue(this); + return false; + } + + + if (state != apf.SUCCESS) { + var oError; + + oError = new Error(apf.formatErrorString(1032, + _self, "Loading xml data", "Could not load data\n" + + "Instruction: " + instruction + "\n" + + "Url: " + extra.url + "\n" + + "Info: " + extra.message + "\n\n" + data)); + + if (callback && callback.apply(this, arguments) === true) + return true; + + if (extra.tpModule && extra.tpModule.retryTimeout(extra, state, _self, oError) === true) + return true; + + _self.$state = 0; + + throw oError; + } + + if (options && options.isSession && !data) { + if (this.src) + return _self.$loadFrom(this.src); + } + else { + if (options && options.cancel) + return; + + _self.load(data); + _self.dispatchEvent("receive", { + data: data + }); + + if (callback) + callback.apply(this, arguments); + } + }; + + return apf.getData(instruction, options); + }; + + /** + * Loads the data from the datasource specified for init. + */ + this.reload = function(){ + if (!this.data) + return; + + if (this.$srcOptions) + this.$loadFrom.apply(this, this.$srcOptions); + else if (this.src) + this.$loadFrom(this.src); + }; + + /** + * Loads data in this model + * + * @param {mixed} [xmlNode] the data to load in this model. A string specifies the data instruction how to retrieve the data, which can be an xml string. null will clear the data from this model. + * @param {Object} options + * Properties: + * {XMLElement} xmlNode the {@link term.datanode data node} that provides context to the data instruction. + * {Function} callback the code executed when the data request returns. + * {mixed} [] Custom properties available in the data instruction. + * {Boolean} [nocopy] Whether the data loaded will not overwrite the reset point. + */ + this.load = function(xmlNode, options){ + if (typeof xmlNode == "string") { + if (xmlNode.charAt(0) == "<") { //xml + if (xmlNode.substr(0, 5).toUpperCase() == "")+1); + if (!apf.supportNamespaces) + xmlNode = xmlNode.replace(/xmlns\=\"[^"]*\"/g, ""); + xmlNode = apf.getXmlDom(xmlNode, null, true).documentElement; //@todo apf3.0 whitespace issue + } + + else if (apf.isJson(xmlNode)) { + xmlNode = apf.json2Xml(xmlNode).documentElement + } + + else + return this.$loadFrom(xmlNode, options); + } + + if (this.ownerDocument && this.ownerDocument.$domParser.$shouldWait) { + //if (!this.$queueLoading) { + var _self = this; + this.data = xmlNode; //@todo expirement //this.$copy = + apf.xmldb.getXmlDocId(xmlNode, this); //@todo experiment + + this.$queueLoading = true; + apf.queue.add("modelload" + this.$uniqueId, function(){ + if (_self.ownerDocument && _self.ownerDocument.$domParser.$shouldWait) + apf.queue.add("modelload" + _self.$uniqueId, arguments.callee); + else { + _self.load(xmlNode, options); + _self.$queueLoading = false; + } + }); + //} + return; + } + else if (this.$queueLoading) + apf.queue.remove("modelload" + this.$uniqueId); + + this.$state = 0; + + if (this.dispatchEvent("beforeload", {xmlNode: xmlNode}) === false) + return false; + + var doc = xmlNode ? xmlNode.ownerDocument : null; //Fix for safari refcount issue; + + //if (apf.isIE && this.$aml && this.getAttribute("ns")) + //doc.setProperty("SelectionNamespaces", this.getAttribute("ns")); + + if (xmlNode) { + if (!apf.supportNamespaces) { + /* && (xmlNode.prefix || xmlNode.scopeName)) { + doc.setProperty("SelectionNamespaces", "xmlns:" + + (xmlNode.prefix || xmlNode.scopeName) + "='" + + xmlNode.namespaceURI + "'");*/ + var xmlns = [], attr = xmlNode.attributes; + for (var i = 0, l = attr.length; i < l; i++) { + if (attr[i].nodeName.substr(0, 5) == "xmlns") { + xmlns.push(attr[i].xml); + } + } + if (xmlns.length) + doc.setProperty("SelectionNamespaces", xmlns.join(" ")); + } + + apf.xmldb.addNodeListener(xmlNode, this); //@todo this one can be added for this.$listeners and when there are none removed + apf.xmldb.nodeConnect( + apf.xmldb.getXmlDocId(xmlNode, this), xmlNode, null, this); + + if ((!options || !options.nocopy) && this.enablereset) + this.$copy = apf.xmldb.getCleanCopy(xmlNode); + } + + this.data = xmlNode; + + this.dispatchEvent("afterload", {xmlNode: xmlNode}); + this.dispatchEvent("update", {xmlNode: xmlNode}); + + for (var id in this.$amlNodes) + this.$loadInAmlNode(this.$amlNodes[id]); + + for (id in this.$propBinds) + this.$loadInAmlProp(id, xmlNode); + + return this; + }; + + //Listening nodes should be removed in unregister + this.$waitForXml = function(amlNode, prop){ + if (prop) + this.$proplisteners[amlNode.$uniqueId + prop] = { + id : amlNode.$uniqueId, + amlNode : amlNode, + prop : prop + }; + else + this.$listeners[amlNode.$uniqueId] = amlNode; + + //When data is not available at model load but element had already data + //loaded, it is cleared here. + if (amlNode.xmlRoot) + amlNode.clear(); + }; + + this.$xmlUpdate = function(action, xmlNode, listenNode, UndoObj){ + //@todo optimize by only doing this for add, sync etc actions + + if (action == "replacenode" && xmlNode == this.data.ownerDocument.documentElement) { + var _self = this; + setTimeout(function(){ + _self.load(xmlNode); + }); + return; + } + + + if (this.rdb && !this.$at && UndoObj) + this.$at = UndoObj.at; + + + + + var p, b; + for (var id in this.$listeners) { + if (xmlNode = this.data.selectSingleNode(this.$amlNodes[id].xpath || ".")) { + this.$listeners[id].load(xmlNode); + delete this.$listeners[id]; + } + } + + for (id in this.$proplisteners) { + p = this.$proplisteners[id]; + b = this.$propBinds[p.id][p.prop]; + if (xmlNode = b.listen ? this.data.selectSingleNode(b.listen) : this.data) { + delete this.$proplisteners[id]; + + apf.xmldb.addNodeListener(xmlNode, p.amlNode, + "p|" + p.id + "|" + p.prop + "|" + this.$uniqueId); + + p.amlNode.$execProperty(p.prop, b.root + ? this.data.selectSingleNode(b.root) + : this.data); + } + } + + this.dispatchEvent("update", {xmlNode: xmlNode, action: action, undoObj: UndoObj}); + }; + + /**** INSERT ****/ + + /** + * Inserts data into the data of this model using a data instruction. + * @param {String} instruction the data instrution how to retrieve the data. + * @param {Object} options + * Properties: + * {XMLElement} insertPoint the parent element for the inserted data. + * {Boolean} clearContents wether the contents of the insertPoint should be cleared before inserting the new children. + * {Boolean} copyAttributes wether the attributes of the merged element are copied. + * {Function} callback the code executed when the data request returns. + * {mixed} <> Custom properties available in the data instruction. + */ + this.$insertFrom = function(instruction, options){ + if (!instruction) return false; + + this.dispatchEvent("beforeretrieve"); + + + var amlNode = options.amlNode; + + + var callback = options.callback, _self = this; + options.callback = function(data, state, extra){ + _self.dispatchEvent("afterretrieve"); + + if (!extra) + extra = {}; + + if (state != apf.SUCCESS) { + var oError; + + + oError = new Error(apf.formatErrorString(0, + _self, "Inserting xml data", "Could not insert data\n" + + "Instruction:" + instruction + "\n" + + "Url: " + extra.url + "\n" + + "Info: " + extra.message + "\n\n" + data)); + + + if (extra.tpModule.retryTimeout(extra, state, + options.amlNode || _self, oError) === true) + return true; + + if (callback + && callback.call(this, extra.data, state, extra) === false) + return; + + throw oError; + } + + //Checking for xpath + if (typeof options.insertPoint == "string") + options.insertPoint = _self.data.selectSingleNode(options.insertPoint); + + if (typeof options.clearContents == "undefined" && extra.userdata) + options.clearContents = apf.isTrue(extra.userdata[1]); //@todo is this still used? + + //Call insert function + (options.amlNode || _self).insert(data, options); + + if (callback) + callback.call(this, extra.data, state, extra); + }; + + apf.getData(instruction, options); + }; + + /** + * Inserts data in this model as a child of the currently loaded data. + * + * @param {XMLElement} XMLRoot the {@link term.datanode data node} to insert into this model. + * @param {Object} options + * Properties: + * {XMLElement} insertPoint the parent element for the inserted data. + * {Boolean} clearContents wether the contents of the insertPoint should be cleared before inserting the new children. + * {Boolean} copyAttributes wether the attributes of the merged element are copied. + * {Function} callback the code executed when the data request returns. + * {mixed} <> Custom properties available in the data instruction. + */ + this.insert = function(xmlNode, options){ + if (typeof xmlNode == "string") { + if (xmlNode.charAt(0) == "<") { + if (xmlNode.substr(0, 5).toUpperCase() == "")+1); + if (!apf.supportNamespaces) + xmlNode = xmlNode.replace(/xmlns\=\"[^"]*\"/g, ""); + xmlNode = apf.getXmlDom(xmlNode).documentElement; + } + + else if (apf.isJson(xmlNode)) { + xmlNode = apf.json2Xml(xmlNode).documentElement + } + + else + return this.$insertFrom(xmlNode, options); + } + + if (!options.insertPoint) + options.insertPoint = this.data; + + + if (!options.insertPoint) { + throw new Error(apf.formatErrorString(0, amlNode || _self, + "Inserting data", "Could not determine insertion point for " + + "instruction: " + instruction)); + } + + + //if(this.dispatchEvent("beforeinsert", parentXMLNode) === false) return false; + + //Integrate XMLTree with parentNode + if (typeof options.copyAttributes == "undefined") + options.copyAttributes = true; + + var newNode = apf.mergeXml(xmlNode, options.insertPoint, options); + + //Call __XMLUpdate on all this.$listeners + apf.xmldb.applyChanges("insert", options.insertPoint);//parentXMLNode); + + //this.dispatchEvent("afterinsert"); + + return xmlNode; + }; + + /* *********** SUBMISSION ****************/ + + /** + * Serialize the full XML DOM to a format specified by 'type' + * + * @param {String} type how to serialize the data + */ + this.convertXml = function(type) { + if (!type) + return this.data.xml; + + return apf.convertXml(this.data, type); + }; + + /** + * Submit the data of the model to a data source. + * @param {String} instruction the instruction for sending the data, or the url to send the data to. + * @param {String} type how to serialize the data. + * Possible values: + * xml, application/xml + * form, application/x-www-form-urlencoded + * json, application/json + * @param {XMLElement} xmlNode the data node to send to the server. + */ + //@todo rewrite this for apf3.0 + this.submit = function(instruction, type, xmlNode, options){ + if (!instruction) + instruction = this.submission; + + if (!xmlNode) + xmlNode = this.data; + + + if (!xmlNode) { + throw new Error(apf.formatErrorString(0, this, + "Submitting model", + "Could not submit data, because no data was passed and the " + + "model does not have data loaded.")); + } + + + if (!type) + type = "form"; + + if (this.dispatchEvent("beforesubmit", { + instruction: instruction + }) === false) + return false; + + var model = this; + function cbFunc(data, state, extra){ + if ((state == apf.TIMEOUT + || (model.retryOnError && state == apf.ERROR)) + && extra.retries < apf.maxHttpRetries) { + return extra.tpModule.retry(extra.id); + } + else { + if (state != apf.SUCCESS) { + model.dispatchEvent("submiterror", extra); + } + else { + model.dispatchEvent("submitsuccess", apf.extend({ + data: data + }, extra)); + } + } + } + + var data; + if (type.indexOf("xml") > -1) + data = apf.getXmlString(xmlNode); + else if (type.indexOf("form") > -1) + data = apf.convertXml(apf.xmldb.getCleanCopy(xmlNode), "cgiobjects"); + else if (type.indexOf("json") > -1) + data = apf.convertXml(xmlNode, "json"); + + apf.saveData(instruction, apf.extend({ + xmlNode : xmlNode, + data : data, + callback : cbFunc + }, options)); + + this.dispatchEvent("aftersubmit"); + }; + + this.$destroy = function(){ + if (this.session && this.data) + apf.saveData(this.session, {xmlNode: this.getXml()}); + }; +}).call(apf.model.prototype = new apf.AmlElement()); + +apf.aml.setElement("model", apf.model); + + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/elements/notifier.js)SIZE(15296)TIME(Mon, 10 Jan 2011 10:53:12 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + +/** + * Notification element, which shows popups when events occur. Similar + * to growl on the OSX platform. + * Example: + * + * + * + * + * + * + * + * + * Example: + * Notifier with 4 notifications which appears and stays over the 3 seconds + * begins to the top right corner and goes to the left. First notification will + * be displayed when value in textbox will be bigger than 4. In next two cases + * notification will be shown when notifier's position or arrange attribute will + * be changed. In the last case notification will be shown when date 2008-12-24 + * will be selected on calendar. + * + * + * + * + * + * + * + * + * + * @define notifier + * @attribute {String} position Vertical and horizontal element's start + * position, it can be changed in any time, + * default is 'top-right' + * Possible values: + * top-right element is placed in top-right corner of browser window + * top-left element is placed in top-left corner of browser window + * bottom-right element is placed in bottom-right corner of browser window + * bottom-left element is placed in bottom-left corner of browser window + * center-center element is placed in the middle of browser window + * right-top element is placed in top-right corner of browser window + * left-top element is placed in top-left corner of browser window + * right-bottom element is placed in bottom-right corner of browser window + * left-bottom element is placed in bottom-left corner of browser window + * center-center element is placed in the middle of browser window + * @attribute {String} margin It's a free space around popup element, + * default is '10 10 10 10' pixels + * @attribute {String} columnsize Specify element width and col width where + * element will be displayed, default is 300 pixels + * @attribute {String} arrange popup elements can be displayed in rows + * or columns, default is 'vertical' + * Possible values: + * vertical element will be displayed in rows + * horizontal element will be displayed in columns + * @attribute {String} timeout After the timeout has passed the popup + * will dissapear automatically. When the + * mouse hovers over the popup it doesn't + * dissapear, default is 2 seconds + * $attribute {String} onclick It's an action executed after user click + * on notifier cloud + * + * @constructor + * + * @inherits apf.Presentation + * + * @author + * @version %I%, %G% + * + * @allowchild event + */ +apf.notifier = function(struct, tagName){ + this.$init(tagName || "notifier", apf.NODE_VISIBLE, struct); +}; + +(function() { + this.timeout = 2000; + this.position = "top-right"; + this.columnsize = 300; + this.arrange = "vertical"; + this.margin = "10 10 10 10"; + + this.lastPos = null; + this.showing = 0; + this.sign = 1; + + this.$supportedProperties.push("margin", "position", "timeout", + "columnsize", "arrange"); + + this.$propHandlers["position"] = function(value) { + this.lastPos = null; + }; + + this.$propHandlers["margin"] = function(value) { + this.margin = value; + }; + + this.$propHandlers["timeout"] = function(value) { + this.timeout = parseInt(value) * 1000; + }; + + function getPageScroll() { + return [ + document.documentElement.scrollTop || document.body.scrollTop, + document.documentElement.scrollLeft || document.body.scrollLeft + ]; + } + + function getStartPosition(x, wh, ww, nh, nw, margin) { + var scrolled = getPageScroll(); + + return [ + (x[0] == "top" + ? margin[0] + : (x[0] == "bottom" + ? wh - nh - margin[2] + : wh / 2 - nh / 2)) + scrolled[0], + (x[1] == "left" + ? margin[3] + : (x[1] == "right" + ? ww - nw - margin[1] + : ww / 2 - nw / 2)) + scrolled[1] + ]; + } + + /** + * Function creates new notifie popup element + * + * @param {String} message Message content displaing in popup element, + * default is [No message] + * @param {String} icon Path to icon file relative to "icon-path" which + * is set in skin declaration + * @param {Object} ev object representation of event + * + */ + this.popup = function(message, icon, ev) { + if (!this.$ext) + return; + + this.$ext.style.width = this.columnsize + "px"; + + var _self = this, + oNoti = this.$pHtmlNode.appendChild(this.$ext.cloneNode(true)), + ww = apf.isIE + ? document.documentElement.offsetWidth + : window.innerWidth, + wh = apf.isIE + ? document.documentElement.offsetHeight + : window.innerHeight, + + removed = false, + + oIcon = this.$getLayoutNode("notification", "icon", oNoti), + oBody = this.$getLayoutNode("notification", "body", oNoti); + + this.showing++; + + if (oIcon && icon) { + if (oIcon.nodeType == 1) { + oIcon.style.backgroundImage = "url(" + + this.iconPath + icon + ")"; + } + else { + oIcon.nodeValue = this.iconPath + icon; + } + + this.$setStyleClass(oNoti, this.$baseCSSname + "ShowIcon"); + } + + oBody.insertAdjacentHTML("beforeend", message || "[No message]"); + oNoti.style.display = "block"; + + var margin = apf.getBox(this.margin || "0"), + nh = oNoti.offsetHeight, + nw = oNoti.offsetWidth, + /* It's possible to set for example: position: top-right or right-top */ + x = this.position.split("-"), + _reset = false; + + if (x[1] == "top" || x[1] == "bottom" || x[0] == "left" || x[0] == "right") + x = [x[1], x[0]]; + /* center-X and X-center are disabled */ + if ((x[0] == "center" && x[1] !== "center") || (x[0] !== "center" && x[1] == "center")) + x = ["top", "right"]; + + /* start positions */ + if (!this.lastPos) { + this.lastPos = getStartPosition(x, wh, ww, nh, nw, margin); + this.sign = 1; + _reset = true; + } + + if ((!_reset && x[0] == "bottom" && this.sign == 1) || + (x[0] == "top" && this.sign == -1)) { + if (this.arrange == "vertical") { + this.lastPos[0] += x[1] == "center" + ? 0 + : this.sign * (x[0] == "top" + ? margin[0] + nh + : (x[0] == "bottom" + ? - margin[2] - nh + : 0)); + } + else { + this.lastPos[1] += x[0] == "center" + ? 0 + : this.sign * (x[1] == "left" + ? margin[3] + nw + : (x[1] == "right" + ? - margin[1] - nw + : 0)); + } + } + + /* reset to next line, first for vertical, second horizontal */ + var scrolled = getPageScroll(); + + if (this.lastPos[0] > wh + scrolled[0] - nh || this.lastPos[0] < scrolled[0]) { + this.lastPos[1] += (x[1] == "left" + ? nw + margin[3] + : (x[1] == "right" + ? - nw - margin[3] + : 0)); + this.sign *= -1; + this.lastPos[0] += this.sign*(x[0] == "top" + ? margin[0] + nh + : (x[0] == "bottom" + ? - margin[2] - nh + : 0)); + } + else if (this.lastPos[1] > ww + scrolled[1] - nw || this.lastPos[1] < scrolled[1]) { + this.lastPos[0] += (x[0] == "top" + ? nh + margin[0] + : (x[0] == "bottom" + ? - nh - margin[0] + : 0)); + this.sign *= -1; + this.lastPos[1] += x[0] == "center" + ? 0 + : this.sign * (x[1] == "left" + ? margin[3] + nw + : (x[1] == "right" + ? - margin[1] - nw + : 0)); + } + + /* Start from begining if entire screen is filled */ + if (this.lastPos) { + if ((this.lastPos[0] > wh + scrolled[0] - nh || this.lastPos[0] < scrolled[1]) + && this.arrange == "horizontal") { + this.lastPos = getStartPosition(x, wh, ww, nh, nw, margin); + this.sign = 1; + } + if ((this.lastPos[1] > ww + scrolled[1] - nw || this.lastPos[1] < scrolled[1]) + && this.arrange == "vertical") { + this.lastPos = getStartPosition(x, wh, ww, nh, nw, margin); + this.sign = 1; + } + } + + oNoti.style.left = this.lastPos[1] + "px"; + oNoti.style.top = this.lastPos[0] + "px"; + + if ((x[0] == "top" && this.sign == 1) || (x[0] == "bottom" && this.sign == -1)) { + if (this.arrange == "vertical") { + this.lastPos[0] += x[1] == "center" + ? 0 + : this.sign * (x[0] == "top" + ? margin[0] + nh + : (x[0] == "bottom" + ? - margin[2] - nh + : 0)); + } + else { + this.lastPos[1] += x[0] == "center" + ? 0 + : this.sign * (x[1] == "left" + ? margin[3] + nw + : (x[1] == "right" + ? - margin[1] - nw + : 0)); + } + }; + + var isMouseOver = false; + + apf.tween.css(oNoti, "fade", { + anim : apf.tween.NORMAL, + steps : 10, + interval : 10, + onfinish : function(container) { + oNoti.style.filter = ""; + $setTimeout(hideWindow, _self.timeout) + } + }); + + function hideWindow() { + if (isMouseOver) + return; + + apf.tween.css(oNoti, "notifier_hidden", { + anim : apf.tween.NORMAL, + steps : 10, + interval: 20, + onfinish: function(container) { + apf.setStyleClass(oNoti, "", ["notifier_hover"]); + if (isMouseOver) + return; + + if (oNoti.parentNode) { + if (oNoti.parentNode.removeChild(oNoti) && !removed) { + _self.showing--; + removed = true; + } + } + + if (_self.showing == 0) + this.lastPos = null; + } + }); + } + + /* Events */ + oNoti.onmouseover = function(e) { + e = (e || event); + var tEl = e.explicitOriginalTarget || e.toElement; + if (isMouseOver) + return; + if (tEl == oNoti || apf.isChildOf(oNoti, tEl)) { + apf.tween.css(oNoti, "notifier_hover", { + anim : apf.tween.NORMAL, + steps : 10, + interval: 20, + onfinish: function(container) { + apf.setStyleClass(oNoti, "", ["notifier_shown"]); + } + }); + + isMouseOver = true; + } + }; + + oNoti.onmouseout = function(e) { + e = (e || event); + var tEl = e.explicitOriginalTarget || e.toElement; + + if (!isMouseOver) + return; + + if (apf.isChildOf(tEl, oNoti) || + (!apf.isChildOf(oNoti, tEl) && oNoti !== tEl )) { + isMouseOver = false; + hideWindow(); + } + }; + + if (ev) { + oNoti.onclick = function() { + ev.dispatchEvent("click"); + }; + } + }; + + /**** Init ****/ + + this.$draw = function() { + //Build Main Skin + this.$pHtmlNode = document.body; + + this.$ext = this.$getExternal("notification"); + this.$ext.style.display = "none"; + this.$ext.style.position = "absolute"; + apf.window.zManager.set("notifier", this.$ext); + }; +}).call(apf.notifier.prototype = new apf.Presentation()); + +apf.aml.setElement("notifier", apf.notifier); +apf.aml.setElement("event", apf.event); + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/elements/page.js)SIZE(18410)TIME(Mon, 10 Jan 2011 10:53:12 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + + +/** + * A page in a pageable element. (i.e. a page in {@link element.tab}) + * + * @constructor + * @define page + * @allowchild {elements}, {anyaml} + * @addnode elements + * + * @author Ruben Daniels (ruben AT ajax DOT org) + * @version %I%, %G% + * @since 0.8 + */ +apf.page = function(struct, tagName){ + this.$init(tagName || "page", apf.NODE_VISIBLE, struct); +}; + +(function(){ + this.canHaveChildren = true; + + //this.$canEdit = false; + + this.$focussable = false; + this.closebtn = false; + + + this.$getEditableCaption = function(){ + if (!this.parentNode.$hasButtons) + return false; + + return [this.parentNode.$getLayoutNode("button", "caption", this.$button), "caption"]; + } + + + + /** + * Sets the caption of the button of this element. + * @param {String} caption the text displayed on the button of this element. + */ + this.setCaption = function(caption){ + this.setProperty("caption", caption, false, true); + }; + + /** + * Sets the icon of the button of this element. + * @param {String} icon the icon displayed on the button of this element. + */ + this.setIcon = function(icon) { + this.setProperty("icon", icon, false, true); + }; + + + /**** Delayed Render Support ****/ + + + //Hack + this.addEventListener("beforerender", function(){ + this.parentNode.dispatchEvent("beforerender", { + page : this + }); + }); + + this.addEventListener("afterrender", function(){ + this.parentNode.dispatchEvent("afterrender", { + page : this + }); + }); + + + /**** Properties ****/ + + this.$booleanProperties["visible"] = true; + this.$booleanProperties["fake"] = true; + this.$booleanProperties["closebtn"] = true; + this.$supportedProperties.push("fake", "caption", "icon", "tooltip", + "type", "buttons", "closebtn", "trans-in", "trans-out"); + + /** + * @attribute {Boolean} closebtn whether this page's button shows a close button inside it. + */ + this.$propHandlers["closebtn"] = function(value){ + this.closebtn = value; + }; + + /** + * @attribute {String} caption the text displayed on the button of this element. + */ + this.$propHandlers["tooltip"] = function(value){ + if (!this.parentNode) + return; + + var node = this.parentNode + .$getLayoutNode("button", "caption", this.$button); + + (node.nodeType == 1 ? node : node.parentNode).setAttribute("title", value || ""); + } + + /** + * @attribute {String} caption the text displayed on the button of this element. + */ + this.$propHandlers["caption"] = function(value){ + if (!this.parentNode) + return; + + var node = this.parentNode + .$getLayoutNode("button", "caption", this.$button); + + if (node.nodeType == 1) + node.innerHTML = value; + else + node.nodeValue = value; + }; + + this.$propHandlers["icon"] = function(value) { + if (!this.parentNode) + return; + + var node = this.parentNode + .$getLayoutNode("button", "icon", this.$button); + + if (node && node.nodeType == 1) + apf.skins.setIcon(node, value, this.parentNode.iconPath); + }; + + this.$propHandlers["visible"] = function(value){ + if (!this.parentNode) + return; + + if (value) { + if (this.$fake) { + this.parentNode.set(this.$fake); + this.visible = false; + return; + } + + this.$ext.style.display = ""; + if (this.parentNode.$hasButtons) + this.$button.style.display = "block"; + + if (!this.parentNode.$activepage) + this.parentNode.set(this); + } + else { + if (this.$active) { + this.$deactivate(); + + // Try to find a next page, if any. + var nextPage = this.parentNode.activepagenr + 1; + var pages = this.parentNode.getPages() + var len = pages.length + while (nextPage < len && !pages[nextPage].visible) + nextPage++; + + if (nextPage == len) { + // Try to find a previous page, if any. + nextPage = this.parentNode.activepagenr - 1; + while (nextPage >= 0 && len && !pages[nextPage].visible) + nextPage--; + } + + if (nextPage >= 0) + this.parentNode.set(nextPage); + else { + this.parentNode.activepage = + this.parentNode.activepagenr = + this.parentNode.$activepage = null; + } + } + + this.$ext.style.display = "none"; + if (this.parentNode.$hasButtons) + this.$button.style.display = "none"; + } + }; + + /** + * @attribute {Boolean} fake whether this page actually contains elements or + * only provides a button in the pageable parent element. + */ + this.$propHandlers["fake"] = function(value){ + if (this.$ext) { + apf.destroyHtmlNode(this.$ext); + this.$int = this.$ext = null; + } + }; + + this.$propHandlers["type"] = function(value) { + this.setProperty("fake", true); + + if (this.relPage && this.$active) + this.relPage.$deactivate(); + + this.relPage = this.parentNode.getPage(value); + if (this.$active) + this.$activate(); + }; + + /**** DOM Hooks ****/ + + this.addEventListener("DOMNodeRemoved", function(e){ + if (e && e.currentTarget != this) + return; + + if (this.$button) { + if (this.$position & 1) + this.parentNode.$setStyleClass(this.$button, "", ["firstbtn", "firstcurbtn"]); + if (this.$position & 2) + this.parentNode.$setStyleClass(this.$button, "", ["lastbtn"]); + } + + if (!e.$doOnlyAdmin) { + if (this.$button) + this.$button.parentNode.removeChild(this.$button); + + if (this.parentNode && this.parentNode.$activepage == this) { + if (this.$button) + this.parentNode.$setStyleClass(this.$button, "", ["curbtn"]); + this.parentNode.$setStyleClass(this.$ext, "", ["curpage"]); + } + } + }); + + //beforeNode, pNode, withinParent + this.addEventListener("DOMNodeInserted", function(e){ + if (e && e.currentTarget != this || !this.$amlLoaded + || !e.$oldParent) + return; + + if (!e.$isMoveWithinParent + && this.skinName != this.parentNode.skinName) { + this.$destroy(); //clean up button + } + else if (this.$button && e.$oldParent.$hasButtons) + this.parentNode.$buttons.insertBefore(this.$button, + e.$beforeNode && e.$beforeNode.$button || null); + }, true); + + /**** Private state functions ****/ + + this.$position = 0; + this.$first = function(remove){ + if (remove) { + this.$isFirst = false; + this.$position -= 1; + this.parentNode.$setStyleClass(this.$button, "", + ["firstbtn", "firstcurbtn"]); + } + else { + this.$isFirst = true; + this.$position = this.$position | 1; + this.parentNode.$setStyleClass(this.$button, "firstbtn" + + (this.parentNode.$activepage == this ? " firstcurbtn" : "")); + } + }; + + this.$last = function(remove){ + if (remove) { + this.$isLast = false; + this.$position -= 2; + this.parentNode.$setStyleClass(this.$button, "", ["lastbtn"]); + } + else { + this.$isLast = true; + this.$position = this.$position | 2; + this.parentNode.$setStyleClass(this.$button, "lastbtn"); + } + }; + + this.$deactivate = function(fakeOther){ + //if (this.disabled) + //return false; + + this.$active = false + + if (this.parentNode.$hasButtons) { + if (this.$position > 0) + this.parentNode.$setStyleClass(this.$button, "", ["firstcurbtn"]); + this.parentNode.$setStyleClass(this.$button, "", ["curbtn"]); + } + + if ((!this.fake || this.relPage) && !fakeOther) { + this.parentNode.$setStyleClass(this.fake + ? this.relPage.$ext + : this.$ext, "", ["curpage"]); + + if (this.fake) { + if (!this.relPage.visible) + this.relPage.$ext.style.display = "none"; + + this.relPage.dispatchEvent("prop.visible", {value:false}); + } + + this.dispatchEvent("prop.visible", {value:false}); + } + }; + + this.$activate = function(){ + //if (this.disabled) + //return false; + + this.$active = true; + + if (!this.$drawn) { + var f; + this.addEventListener("DOMNodeInsertedIntoDocument", f = function(e){ + this.removeEventListener("DOMNodeInsertedIntoDocument", f); + if (!this.$active) + return; + + this.$activate(); + }); + return; + } + + if (this.parentNode.$hasButtons) { + if (this.$isFirst) + this.parentNode.$setStyleClass(this.$button, "firstcurbtn"); + this.parentNode.$setStyleClass(this.$button, "curbtn"); + } + + if (!this.fake || this.relPage) { + if (this.fake) { + if (this.relPage) { + this.relPage.$ext.style.display = ""; + this.parentNode.$setStyleClass(this.relPage.$ext, "curpage"); + this.relPage.$fake = this; + + + if (this.relPage.$render) + this.relPage.$render(); + + + this.relPage.dispatchEvent("prop.visible", {value:true}); + } + } + else { + this.parentNode.$setStyleClass(this.$ext, "curpage"); + } + + + if (apf.layout && this.relPage) + apf.layout.forceResize(this.fake ? this.relPage.$int : this.$int); + + } + + + if (this.$render) + this.$render(); + + + if (!this.fake) { + if (apf.isIE) { + var cls = this.$ext.className; + this.$ext.className = "rnd" + Math.random(); + this.$ext.className = cls; + } + + this.dispatchEvent("prop.visible", {value:true}); + } + }; + + this.addEventListener("$skinchange", function(){ + if (this.caption) + this.$propHandlers["caption"].call(this, this.caption); + + if (this.icon) + this.$propHandlers["icon"].call(this, this.icon); + }); + + this.$enable = function(){ + if (this.$button) + this.$setStyleClass(this.$button, null, ["btnDisabled"]);//@todo this.$baseCSSname + + }; + + this.$disable = function(){ + if (this.$button) + this.$setStyleClass(this.$button, "btnDisabled");//@todo this.$baseCSSname + + }; + + /**** Init ****/ + + this.$canLeechSkin = true; + + this.$draw = function(isSkinSwitch){ + this.skinName = this.parentNode.skinName; + + var sType = this.getAttribute("type") + if (sType) { + this.fake = true; + this.relPage = this.parentNode.getPage(sType) || null; + } + + if (this.parentNode.$hasButtons) { + //this.parentNode.$removeEditable(); //@todo multilingual support is broken when using dom + + this.parentNode.$getNewContext("button"); + var elBtn = this.parentNode.$getLayoutNode("button"); + elBtn.setAttribute(this.parentNode.$getOption("main", "select") || "onmousedown", + 'var page = apf.lookup(' + this.$uniqueId + ');\ + if (page.disabled) return;\ + if (event.button == 2 && page.parentNode.contextmenu) {\ + page.parentNode.contextPage = page;\ + return;\ + }\ + page.parentNode.set(page);\ + page.canHaveChildren = 2;\ + page.$setStyleClass(this, "down", null, true);'); + elBtn.setAttribute("onmouseup", 'apf.lookup(' + + this.parentNode.$uniqueId + ').$setStyleClass(this, "", ["down"], true);'); + elBtn.setAttribute("onmouseover", 'var o = apf.lookup(' + + this.parentNode.$uniqueId + ');if(apf.lookup(' + this.$uniqueId + + ') != o.$activepage) o.$setStyleClass(this, "over", null, true);'); + elBtn.setAttribute("onmouseout", 'var o = apf.lookup(' + + this.parentNode.$uniqueId + ');\ + o.$setStyleClass(this, "", ["over"], true);\ + var page = apf.lookup(' + this.$uniqueId + ');\ + page.canHaveChildren = true;'); + + var cssClass = this.getAttribute("class"); + if (cssClass) + apf.setStyleClass(elBtn, cssClass); + + + var closebtn = this.getAttribute("closebtn"); + if ((apf.isTrue(closebtn) || ((this.parentNode.buttons || "").indexOf("close") > -1 && !apf.isFalse(closebtn)))) { + var btncontainer = this.parentNode.$getLayoutNode("button", "container"); + + this.parentNode.$getNewContext("btnclose"); + var elBtnClose = this.parentNode.$getLayoutNode("btnclose"); + + if (elBtnClose) { + apf.setStyleClass(elBtn, "btnclose"); + + elBtnClose.setAttribute("onmousedown", + "apf.cancelBubble(event, apf.lookup(" + this.$uniqueId + "));"); + elBtnClose.setAttribute("onclick", + 'var page = apf.lookup(' + this.$uniqueId + ');\ + page.parentNode.remove(page, event);'); + + btncontainer.appendChild(elBtnClose); + } + + else { + apf.console.warn("Missing close button in tab skin"); + } + + } + + + this.$button = apf.insertHtmlNode(elBtn, this.parentNode.$buttons); + + + if (this.parentNode.$scale) { + var w = apf.getHtmlInnerWidth(this.parentNode.$buttons); + var l = this.parentNode.getPages().length; + this.$button.style.width = Math.round(Math.min(w/l, this.parentNode.$maxBtnWidth)) + "px"; + } + + + if (!isSkinSwitch && this.nextSibling && this.nextSibling.$button) + this.$button.parentNode.insertBefore(this.$button, this.nextSibling.$button); + + this.$button.host = this; + } + + if (this.fake) + return; + + if (this.$ext) + this.$ext.parentNode.removeChild(this.$ext); //@todo mem leaks? + + this.$ext = this.parentNode.$getExternal("page", + this.parentNode.oPages, null, this); + this.$ext.host = this; + + this.$int = this.parentNode + .$getLayoutNode("page", "container", this.$ext); + //if (this.$int) + //this.$int.setAttribute("id", this.$int.getAttribute("id")); + + //@todo this doesnt support hidden nodes. + if (this.$isLast) + this.$last(); + if (this.$isFirst) + this.$first(); + }; + + this.$destroy = function(){ + if (this.$button) { + if (!this.parentNode.$amlDestroyed) + this.$button.parentNode.removeChild(this.$button); + + this.$button.host = null; + this.$button = null; + } + }; + + +}).call(apf.page.prototype = new apf.Presentation()); + +apf.aml.setElement("page", apf.page); + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/elements/pager.js)SIZE(9037)TIME(Mon, 10 Jan 2011 10:53:12 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + + +/** + * This elements displays buttons which can be used to navigate between some + * parts of data, for example between parts of article + * + * @define pager + * @attribute {String} range determines how much page buttons is displayed + * @attribute {String} previous determines the caption of "go to previous page" button + * @attribute {String} next determines the caption of "go to next page" button + * + * @inherits apf.Presentation + * @inherits apf.StandardBinding + * @inherits apf.DataAction + * + * @author + * @version %I%, %G% + * + * @define bindings + * @allowchild current, total + * + * @binding current Determines which page is currently selected + * @binding total Determines the number of pages. + * + */ +apf.pager = function(struct, tagName){ + this.$init(tagName || "pager", apf.NODE_VISIBLE, struct); +}; + +(function() { + this.previous = "Previous"; + this.next = "Next"; + this.range = 5; + this.curpage = 1; + this.totalpages = 0; + this.autohide = false; + this.oEmpty = null; + + //1 = force no bind rule, 2 = force bind rule + this.$attrExcludePropBind = apf.extend({ + pageload : 1 + }, this.$attrExcludePropBind); + + this.$supportedProperties.push("range", "curpage", "totalpages", + "previous", "next", "autohide", "pageload"); + + this.$booleanProperties["autohide"] = true; + + this.$propHandlers["curpage"] = function(value){ + this.gotoPage(value); + }; + + /** + * Selects page depends on its number or jump length + * + * @param {Number} pageNr number of choosen page + * @param {Number} pageDelta length of jump which should be done between + * current page and new selected page + */ + this.gotoPage = function(pageNr, pageDelta, userAction) { + if (userAction && this.disabled) + return; + + var lastCurpage = this.curpage; + this.curpage = pageNr || this.curpage + pageDelta; + if (lastCurpage != this.curpage) + this.setProperty("curpage", this.curpage); + + //Sanity checks + if (this.curpage < 1) + this.curpage = 1; + else if (this.totalpages && this.curpage > this.totalpages) + this.curpage = this.totalpages; + + if (this.dispatchEvent("beforepagechange", {page:this.curpage}) === false) + return false; + + var model = this.getModel(true), + _self = this; + if (model) { + model.$loadFrom(this.pageload, { + xmlNode : this.xmlRoot, + page : this.curpage, + callback : function(){ + _self.dispatchEvent("afterpagechange", {page:_self.curpage}); + } + }); + } + else { + //@todo is this the best way to detect a model? + $setTimeout(function(){ + var model = _self.getModel(true); + if (model) { + model.$loadFrom(_self.pageload, { + xmlNode : _self.xmlRoot, + page : _self.curpage, + callback : function(){ + _self.dispatchEvent("afterpagechange", {page:_self.curpage}); + } + }); + } + + _self.removeEventListener("afterload", arguments.callee); + }); + } + }; + + this.addEventListener("$clear", function(e){ + return false; + }); + + this.$setClearMessage = function(msg, type){ + if (!this.$empty) { + this.$empty = this.$container.ownerDocument.createElement("span"); + this.$setStyleClass(this.$empty, "loader"); + } + + if (type == "loading") { + this.$setStyleClass(this.$ext, this.$baseCSSname + "Loading"); + this.$container.appendChild(this.$empty); + } + } + + this.$removeClearMessage = function(){ + if (this.$empty && this.$empty.parentNode) { + this.$empty.parentNode.removeChild(this.$empty); + this.$setStyleClass(this.$ext, "", [this.$baseCSSname + "Loading"]); + } + } + + this.$draw = function() { + this.$ext = this.$getExternal("main"); + this.$container = this.$getLayoutNode("main", "container", this.$ext); + }; + + this.$load = function(xmlRoot) { + this.setProperty("curpage", parseInt(this.$applyBindRule("current", xmlRoot))); + this.setProperty("totalpages", parseInt(this.$applyBindRule("total", xmlRoot))); + + var curpage = this.curpage, + totalpages = this.totalpages, + nodes = [], + btn; + + this.$container.innerHTML = ""; + + if (!totalpages) + return; + + if (curpage != 1 || !this.autohide) { + this.$getNewContext("button"); + btn = this.$getLayoutNode("button"); + this.$getLayoutNode("button", "caption").nodeValue = this.previous; + this.$setStyleClass(btn, "previous"); + + if (curpage != 1) { + btn.setAttribute("onclick", "apf.lookup(" + this.$uniqueId + + ").gotoPage(null, -1, true)"); + btn.setAttribute("onmousedown", 'apf.setStyleClass(this, "down");'); + btn.setAttribute("onmouseup", 'apf.setStyleClass(this,"", ["down"]);'); + } + else { + this.$setStyleClass(btn, "disabled"); + } + + nodes.push(btn); + } + + var rlow = Math.floor(this.range / 2), + // rhigh = Math.ceil(this.range / 2); + start = Math.max(1, curpage - rlow), + end = Math.min(totalpages + 1, start + this.range), + i; + if (end - start < this.range && start != 1) + start = Math.max(end - this.range, 1); + + for (i = start; i < end; i++) { + this.$getNewContext("button"); + btn = this.$getLayoutNode("button"); + this.$getLayoutNode("button", "caption").nodeValue = i; + btn.setAttribute("onclick", "apf.lookup(" + this.$uniqueId + + ").gotoPage(" + i + ", null, true)"); + btn.setAttribute("onmousedown", 'apf.setStyleClass(this, "down");'); + btn.setAttribute("onmouseup", 'apf.setStyleClass(this,"", ["down"]);'); + nodes.push(btn); + + if (i == curpage) + this.$setStyleClass(btn, "current"); + } + + if (curpage != totalpages || !this.autohide) { + this.$getNewContext("button"); + btn = this.$getLayoutNode("button"); + this.$getLayoutNode("button", "caption").nodeValue = this.next; + this.$setStyleClass(btn, "next"); + + if (curpage != totalpages) { + btn.setAttribute("onclick", "apf.lookup(" + this.$uniqueId + + ").gotoPage(null, 1, true)"); + btn.setAttribute("onmousedown", 'apf.setStyleClass(this, "down");'); + btn.setAttribute("onmouseup", 'apf.setStyleClass(this,"", ["down"]);'); + } + else { + this.$setStyleClass(btn, "disabled"); + } + + nodes.push(btn); + } + + apf.insertHtmlNodes(nodes, this.$container); + + if (this.$empty) + this.$container.appendChild(this.$empty); + } + + +}).call(apf.pager.prototype = new apf.StandardBinding()); + + +apf.aml.setElement("pager", apf.pager); +apf.aml.setElement("total", apf.BindingRule); +apf.aml.setElement("current", apf.BindingRule); + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/elements/palette.js)SIZE(5945)TIME(Mon, 10 Jan 2011 10:53:12 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + +/** + * Element displaying a set of choices to the user which allows + * him/her to pick a specific color. This element also gives the + * user a choice to add a custom color. + * + * @constructor + * @define palette + * @allowchild {smartbinding} + * @addnode elements + * + * @inherits apf.XForms + * @inherits apf.StandardBinding + * + * @author Ruben Daniels (ruben AT ajax DOT org) + * @version %I%, %G% + * @since 0.4 + * + * @binding value Determines the way the value for the element is retrieved + * from the bound data. + * Example: + * Sets the color based on data loaded into this component. + * + * + * + * + * + * + * Example: + * A shorter way to write this is: + * + * + * + * + * + * + */ +apf.palette = function(struct, tagName){ + this.$init(tagName || "palette", apf.NODE_VISIBLE, struct); +}; + +(function(){ + this.implement( + + apf.DataAction + + + ); + /**** Properties and Attributes ****/ + + this.$focussable = true; // This object can get the focus + this.value = "ff0000"; + this.direction = "down"; + + this.$supportedProperties.push("value"); + /** + * The selected color of the palette + */ + this.$propHandlers["value"] = function(value){ + this.oViewer.style.backgroundColor = value; + }; + + /**** Public methods ****/ + + + + /** + * Sets the value of this element. This should be one of the values + * specified in the values attribute. + * @param {String} value the new value of this element + */ + this.setValue = function(value){ + this.setProperty("value", value, false, true); + }; + + /** + * Returns the current value of this element. + * @return {String} + */ + this.getValue = function(){ + return this.value ? this.value : ""; + }; + + + + /**** Private state handling methods ****/ + + this.$addColor = function(clr, oContainer){ + if (!oContainer) + oContainer = this.oCustom; + + var oItem = this.$getLayoutNode("item"); + + if (oContainer == this.oCustom) { + oItem.setAttribute("onmousedown", "apf.lookup(" + + this.$uniqueId + ").$doCustom(this, null, true)"); + oItem.setAttribute("ondblclick", "apf.lookup(" + + this.$uniqueId + ").$doCustom(this, true, true)"); + } + else + oItem.setAttribute("onmousedown", "apf.lookup(" + this.$uniqueId + + ").change(this.style.backgroundColor.replace(/^#/, ''))"); + + oItem = apf.insertHtmlNode(oItem, oContainer); + this.$getLayoutNode("item", "background", oItem).style.backgroundColor = "#"+clr; + }; + + this.$setCustom = function(oItem, clr){ + oItem.style.backgroundColor = clr; + this.change(clr); + }; + + /** + * @event createcustom Fires when a custom color is choosen. This event allows the developer to display a color picker to fill the palette's color. + * object: + * {HTMLElement} htmlNode the rectangle in the palette to be filled. + */ + this.$doCustom = function(oItem, force_create, userAction){ + if (userAction && this.disabled) + return; + + if (force_create || oItem.style.backgroundColor == "#ffffff") { + this.dispatchEvent("createcustom", { + htmlNode: oItem + }); + } + else + this.change(oItem.style.backgroundColor.replace(/^#/, "")); + }; + + this.defaultValue = "ff0000"; + + this.colors = ["fc0025", "ffd800", "7dff00", "32ffe0", "0026ff", + "cd00ff", "ffffff", "e5e5e5", "d9d9d9", "de003a", + "ffc600", "009022", "00bee1", "003e83", "dc0098", + "737373", "666666", "000000"]; + + this.$draw = function(){ + //Build Main Skin + this.$ext = this.$getExternal(); + this.oViewer = this.$getLayoutNode("main", "viewer", this.$ext); + this.oStandard = this.$getLayoutNode("main", "standard", this.$ext); + this.oCustom = this.$getLayoutNode("main", "custom", this.$ext); + + var i; + for (i = 0; i < this.colors.length; i++) + this.$addColor(this.colors[i], this.oStandard); + for (i = 0; i < 9; i++) + this.$addColor("ffffff"); + + //this.oViewer.setAttribute("ondblclick", "apf.lookup(" + this.$uniqueId + ").openColorPicker()"); + }; + +}).call(apf.palette.prototype = new apf.StandardBinding()); + + +apf.aml.setElement("palette", apf.palette); + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/elements/param.js)SIZE(1681)TIME(Mon, 10 Jan 2011 10:53:12 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + + +/** + * element specifying an argument of a method in an rpc element. + * @attribute {String} name the argument name. + * @attribute {String} [value] the value of the argument. + * @attribute {String} [default] the default value of the argument. If + * no value is specified when this function + * is called, the default value is used. + */ +apf.param = function(struct, tagName){ + this.$init(tagName || "param", apf.NODE_HIDDEN, struct); +}; + +apf.param.prototype = new apf.AmlElement(); +apf.param.prototype.$parsePrio = "002"; +apf.aml.setElement("variable", apf.param); //backwards compatibility +apf.aml.setElement("param", apf.param); + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/elements/persist.js)SIZE(17598)TIME(Mon, 10 Jan 2011 10:53:12 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + + +/** + * Element implementing the persist messaging protocol. + * + * @event authfailure Fires when the authentication process failed or halted. + * bubbles: yes + * cancelable: Prevents an authentication failure to be thrown + * object: + * {String} username the username used for the login attempt + * {String} server the server address (URI) of the PERSIST server + * {String} message a more detailed explanation of the error + * @event connectionerror Fires when the connection with the PERSIST server dropped. + * bubbles: yes + * cancelable: Prevents an connection error to be thrown + * object: + * {String} username the username used for the last-active session + * {String} server the server address (URI) of the PERSIST server + * {String} message a more detailed explanation of the error + * @event connected Fires when a login attempt has succeeded, and a session has been setup. + * bubbles: yes + * object: + * {String} username the username used for the last-active session + * @event receivechat Fires when the user received a chat message from a contact. + * bubbles: yes + * object: + * {String} from the username of the contact that sent the message + * {String} message the body of the chat message + * @event datachange Fires when a data-change message is received from one of the contacts. + * bubbles: yes + * object: + * {String} data the data-instruction of the changed data that + * the RDB implementation can grok + * + * @define persist + * @addnode teleport + * + * @author Ruben Daniels + * @version %I%, %G% + * @since 3.0 + * @constructor + * + * @inherits apf.Class + * @inherits apf.BaseComm + * @inherits apf.http + * @namespace apf + * + * @default_private + */ + +apf.persist = function(struct, tagName){ + this.$init(tagName || "persist", apf.NODE_HIDDEN, struct); + + var _self = this; + apf.addEventListener("exit", function(e){ + if (_self.listening) + _self.disconnect(); + }); +}; + +(function() { + this.retryinterval = 1000; + + this.PATHS = { + login : "/login", + logout : "/logout", + pipe : "/pipe" + }; + + this.$supportedProperties.push("host", "retry", "retryinterval"); + + /*this.$propHandlers["host"] = function(value) { + + };*/ + + this.$handleError = function(data, state, extra, callback){ + var oError, amlNode = this; + + if (extra.http.readyState && (extra.status == 401 + || extra.status == 403 + || extra.status == 500)) { + oError = new Error(apf.formatErrorString(10000, amlNode, + "Persist protocol", + extra.statusText + " in " + amlNode.name + + "[" + amlNode.tagName + "] \nUrl: " + extra.url + + "\nInfo: " + extra.message)); + } + else { + oError = new Error(apf.formatErrorString(10001, amlNode, + "Polling in persist protocol", + "Connection dissapeared " + amlNode.name + + "[" + amlNode.tagName + "] \nUrl: " + extra.url + + "\nInfo: " + extra.message)); + } + + if (typeof callback == "function") { + return callback.call(amlNode, data, state, extra, oError); + } + else { + var result = amlNode.retryTimeout(extra, state, amlNode, oError) + //Retrying + if (result === true) + return true; + + //Canceled Error + else if (result === 2) { + this.dispatchEvent("disconnect"); + this.$stopListen(); + return true; + } + } + + this.dispatchEvent("disconnect"); + this.$stopListen(); + + throw oError; + + /*bIsAuth + ? "authfailure" + : bIsConn ? "connectionerror" : "registererror", extra);*/ + } + + this.addEventListener("error", function(e){ + return this.dispatchEvent("connectionerror", e); + }); + + this.normalizeEntity = function(id){ + return id; + } + + this.isConnected = function(){ + return this.listening == true; + } + + this.$startListen = function(){ + this.listening = true; + + this.$poll(); + } + + this.$stopListen = function(){ + this.listening = false; + this.setProperty("sessionId", ""); + + this.cancel(this.$lastpoll); + } + + this.$poll = function(){ + if (!this.sessionId || !this.listening) + return; + + var _self = this; + this.$lastpoll = this.get(this.host + this.PATHS.pipe + "?sid=" + this.sessionId, { + nocache : true, + ignoreOffline : true, + method : "GET", + callback : function(message, state, extra){ + if (state != apf.SUCCESS) { + var knownError = extra.http.readyState + && (extra.status == 401 + || extra.status == 403 + || extra.status == 500); + + if (state == apf.TIMEOUT || !knownError + && extra.retries < (_self.maxretries || apf.maxHttpRetries)) { + setTimeout(function(){ + extra.tpModule.retry(extra.id); + }, _self.retryinterval); + return true; + } + + _self.$stopListen(); + + return _self.$handleError(message, state, extra); + } + else { + _self.$poll(); //continue polling + + if (message) { + var data = apf.unserialize(message); + var body = []; + for (var i = 0, l = data.length; i < l; i++) { + if (!data[i]) { + apf.console.warn("empty message received!"); + continue; + } + + var d = data[i]; + if (d.type == "update") { + _self.dispatchEvent("update", { + message : d.message, //@todo remote.js is not generic enough + uri : d.uri, + annotator : d.uId + }); + } + else if (d.type == "join") { + _self.dispatchEvent("join", { + uri : d.uri, + basetime : d.basetime, //@todo what is this? + document : d.document, + annotator : d.uId + }); + } + else if (d.type == "leave") { + //@todo what to do here? + _self.dispatchEvent("leave", { + uri : d.uri + }); + } + } + } + } + } + }); + }; + + //add a listener to a document + this.join = function(uri, callback){ + if (!this.sessionId) { + apf.console.warn("Could not start RDB session, missing session id."); + return false; + } + + var _self = this; + this.get(this.host + new apf.url(uri).path + "?sid=" + this.sessionId, { + nocache : true, + ignoreOffline : true, + method : "LOCK", + callback : function(data, state, extra){ + if (state != apf.SUCCESS) + return _self.$handleError(data, state, extra, callback); + else { + if (callback) + callback(uri); + } + } + }); + } + + //remove a listener to a document + this.leave = function(uri){ + var _self = this; + this.get(this.host + new apf.url(uri).path + "?sid=" + this.sessionId, { + nocache : true, + ignoreOffline : true, + method : "UNLOCK", + callback : function(data, state, extra){ + if (state != apf.SUCCESS) + return _self.$handleError(data, state, extra); //, callback + } + }); + } + + //send change + this.sendUpdate = function(uri, message){ + var _self = this; + this.contentType = "application/json"; + this.get(this.host + new apf.url(uri).path + "?sid=" + this.sessionId, { + nocache : true, + ignoreOffline : true, + method : "PUT", + data : message, + callback : function(data, state, extra){ + if (state != apf.SUCCESS) + return _self.$handleError(data, state, extra); + } + }); + } + + /** + * Connect to the PERSIST server with a username and password combination + * provided. + * + * @param {String} username Name of the user on the PERSIST server Virtual + * Host + * @param {String} password Password of the user + * @param {Function} [callback] Function that will be called after the Async + * login request + * @type {void} + */ + this.login = + this.connect = function(username, password, redirect, token, callback) { + var _self = this; + if (this.listening) { + this.$stopListen(); + _self.dispatchEvent("disconnect"); + /*return this.disconnect(function(){ + _self.connect(username, password, redirect, token, callback); + })*/ + } + + this.contentType = "application/x-www-form-urlencoded"; + this.get(this.host + this.PATHS.login + "?sid=" + this.sessionId, { + nocache : true, + ignoreOffline : true, + method : "POST", + data : "username=" + encodeURIComponent(username) + + "&password=" + encodeURIComponent(password) + + (redirect + ? "&redirect=" + encodeURIComponent(redirect) + : "") + + (token + ? "&token=" + encodeURIComponent(token) + : ""), + callback : function(data, state, extra){ + if (state != apf.SUCCESS) + return _self.$handleError(data, state, extra, callback); + else { + data = apf.unserialize(data); + _self.setProperty("sessionId", data.sId || data.pId); + + if (!_self.sessionId) { + extra.message = "Did not get a session id from the server"; + return _self.$handleError(data, apf.ERROR, extra, callback); + } + else { + var m = data.availableMethods; + for (var i = 0; i < m.length; i++) { + this[m[i]] = _self.$addMethod(m[i]); + } + + _self.$startListen(); + + _self.dispatchEvent("connect", data); + + if (callback) + callback(data, state, extra); + } + } + } + }); + }; + + /** + * Disconnect from the PERSIST server. It suspends the connection with the + * 'pause' attribute when using BOSH. Poll-based connection only need to + * stop polling. + * + * @param {Function} callback Data instruction callback that will be called + * after the Async request + * @type {void} + */ + this.logout = + this.disconnect = function(callback) { + if (!this.listening) + return; + + var _self = this; + this.contentType = "application/x-www-form-urlencoded"; + this.get(this.host + this.PATHS.logout + "?sid=" + this.sessionId, { + nocache : true, + ignoreOffline : true, + method : "POST", + callback : function(data, state, extra){ + if (state != apf.SUCCESS) { + //@todo startlisten here? + + return _self.$handleError(data, state, extra, callback); + } + else { + _self.dispatchEvent("disconnect"); + + if (callback) + callback(data, state, extra); + } + } + }); + + this.$stopListen(); + }; + + /** + * If the client uses a BOSH session to connect to the PERSIST server, the + * connection can be paused to any number of seconds until the maximum set + * by the server. + * + * @see http://persist.org/extensions/xep-0124.html#inactive + * @param {Number} secs Number of seconds to pause the connection. + * Defaults to the max set by the server. (usually 120) + * @type {void} + */ + this.pause = function(secs) { + + }; + + this.$addMethod = function(def){ + var funcName = def.name.replace(/\/(\w)/g, function(m, a) { return a.toUpperCase()}) + this[funcName] = function(){ + var args = def.args, out = []; + for (var i = 0; i < args.length; i++) { + out.push(args[i] + "=" + encodeURIComponent(arguments[i])); + } + var callback = arguments[arguments.length - 1]; + + var _self = this; + this.contentType = "application/x-www-form-urlencoded"; + this.get(this.host + "/" + def.name + "?sid=" + this.sessionId, { + nocache : true, + ignoreOffline : true, + method : "POST", + data : out.join("&"), + callback : function(data, state, extra){ + if (state != apf.SUCCESS) + return _self.$handleError(data, state, extra, callback); + else { + if (typeof callback == "function") + callback(data, state, extra); + } + } + }); + } + } + this.$addMethod({name: "sendpassword", args: ["username"]}); + + /** + * Instruction handler for XMPP protocols. It supports the following directives: + * - xmpp:name.login(username, password) + * - xmpp:name.logout() + * - xmpp:name.notify(message, to_address, thread, type) + */ + this.exec = function(method, args, callback){ + switch(method){ + case "login": + this.connect(args[0], args[1], args[2], args[3], callback); + break; + case "logout": + this.disconnect(callback); + break; + default: + if (typeof this[method] == "function") { + args.push(callback); + this[method].apply(this, args); + } + else { + + throw new Error(apf.formatErrorString(0, null, "Saving/Loading data", + "Invalid XMPP method '" + method + "'")); + + } + break; + } + }; +}).call(apf.persist.prototype = new apf.Teleport()); + +apf.aml.setElement("persist", apf.persist); + + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/elements/portal.js)SIZE(25076)TIME(Mon, 10 Jan 2011 10:53:12 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + +/** + * Element displaying a rectangle consisting of one or more columns + * which contain zero or more windows. Each window is loaded with specific + * content described in aml. Each of these so-called 'docklets' + * can have specific data loaded from a datasource and can + * be instantiated more than once. + * Example: + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * Remarks: + * A docklet xml is a piece of aml that should be in the following form: + * + * + * + * + * + * + * ... + * + * + * + * + * ... + * + * + * + * + * @constructor + * @allowchild {smartbinding} + * @addnode elements:portal + * + * @inherits apf.MultiSelect + * @inherits apf.DataAction + * @inherits apf.Cache + * + * @author Ruben Daniels (ruben AT ajax DOT org) + * @version %I%, %G% + * @since 0.9 + * + * @binding src Determines the data instruction that loads the docklet from it's datasource. + * @binding collapsed Determines whether the docklet is collapsed after init. + * @binding icon Determines the icon of the docklet. + * @binding column Determines the column in which the docklet is created. + * @binding caption Determines the caption of the docklet. + */ +apf.portal = function(struct, tagName){ + this.$init(tagName || "portal", apf.NODE_VISIBLE, struct); + + this.$columns = []; +}; + +(function(){ + this.implement( + //apf.Cache, + + apf.DataAction + + ); + + this.$focussable = false; + this.buttons = "edit|min|close"; + + this.$deInitNode = function(xmlNode, htmlNode){ + cacheDocklet.call(this, apf.findHost(htmlNode)); + }; + + this.$updateNode = function(xmlNode, htmlNode){ + var docklet = apf.findHost(htmlNode); + docklet.setProperty("buttons", this.$applyBindRule("buttons", xmlNode) || ""); + docklet.draggable = this.$applyBindRule("draggable", xmlNode); + }; + + this.$moveNode = function(xmlNode, htmlNode){ + if (!htmlNode) + return; + }; + this.select = function(){} + + this.findColumnNr = function(x, y){ + var el = document.elementFromPoint(x, y); + + //search for element + while (!el.isColumn && el.parentNode) { + el = el.parentNode; + } + + return el.isColumn && this.$columns.indexOf(el) || 0; + } + + this.$moveDocklet = function(docklet) { + var colNr = this.$columns.indexOf(docklet.$ext.parentNode) || 0; + + var dataNode = docklet.dataNode; + + //@todo hacky, should be via executeAction + dataNode.setAttribute("column", colNr); + + //more hack stuff + //determine docklet xml position by the html position + var nr = apf.xmldb.getChildNumber(docklet.oExt), + nodes = dataNode.selectNodes("../node()[@column='" + colNr + "']"); + if (nodes[nr] != dataNode) { + var jmlNode = apf.findHost(docklet.oExt.nextSibling); + dataNode.parentNode.insertBefore(dataNode, jmlNode && jmlNode.dataNode || null); + } + + this.dispatchEvent("widgetmove"); + } + + this.columns = "33.33%,33.33%,33.33%"; + this.$columnWidths = this.columns.splitSafe(","); + + /** + * @attribute {String} columns a comma seperated list of column sizes. + * A column size can be specified in a number (size in pixels) or using + * a number and a % sign to indicate a percentage. + * Defaults to "33%, 33%, 33%". + * Example: + * + * + * ... + * + * + * + * @todo make this dynamic + */ + this.$propHandlers["columns"] = function(value){ + if (!value) return; + this.$columnWidths = value.splitSafe(","); + + var col, nodes, pHtmlNode, amlNode, node; + while (this.$columns.length > this.$columnWidths.length) { + col = this.$columns.pop(); + col.host = null; + + nodes = col.childNodes + pHtmlNode = this.$columns[0]; + for (var i = nodes.length - 1; i >= 0; i--) { + if ((node = nodes[i]).nodeType != 1) + continue; + + pHtmlNode.appendChild(node); + amlNode = apf.findHost(node); + amlNode.$pHtmlNode = pHtmlNode; + amlNode.dataNode.setAttribute("column", 0); //@todo wrong!! apf3.0 + } + + apf.destroyHtmlNode(col); + } + + for (var last, col, size, i = 0; i < this.$columnWidths.length; i++) { + size = this.$columnWidths[i]; + if (!this.$columns[i]) { + this.$getNewContext("column"); + col = apf.insertHtmlNode(this.$getLayoutNode("column"), this.$int, this.$int.lastChild); + this.$columns.push(col); + + col.isColumn = true; + col.host = this; + } + else { + col = this.$columns[i]; + this.oInt.insertBefore(col, this.oInt.lastChild); + } + + this.$setStyleClass(col, (last = (i == this.$columnWidths.length - 1)) + ? "collast" : "", ["collast"]); + + size.match(/(%|px|pt)/); + var unit = RegExp.$1 || "px"; + col.style.width = (parseInt(size) - (apf.isIE && apf.isIE < 8 && last ? 1 : 0)) + unit; + } + } + + /**** Keyboard support ****/ + + //Handler for a plane list + + this.addEventListener("keydown", function(e){ + var key = e.keyCode; + var ctrlKey = e.ctrlKey; + var shiftKey = e.shiftKey; + var altKey = e.altKey; + + if (!this.selected) + return; + + switch (key) { + default: + break; + } + }, true); + + + /**** Databinding and Caching ****/ + + function cacheDocklet(docklet){ + var srcUrl = docklet.$srcUrl; + if (!srcUrl) + throw new Error("Something went terribly wrong"); //@todo + + if (docklet.$dockletClass.$unload) + docklet.$dockletClass.$unload(); + + //Cache settings panel + var amlNodes = [], amlNode, widget = docklet_cache[srcUrl]; + if (docklet.oSettings) { + var nodes = docklet.oSettings.childNodes; + while(nodes.length) { + amlNode = apf.findHost(widget.fragSettings.appendChild(nodes[0])); + if (amlNode) + amlNodes.push(amlNode.removeNode(null, true)); + } + } + + //Cache oInt + var nodes = docklet.$int.childNodes; + while(nodes.length) { + amlNode = apf.findHost(widget.fragInt.appendChild(nodes[0])); + if (amlNode) + amlNodes.push(amlNode.removeNode(null, true)); + } + var nodes = docklet.childNodes; + for (var i = nodes.length - 1; i >= 0; i--) + amlNodes.push(nodes[i].removeNode(null, true)); + + widget.amlNodes = amlNodes; + docklet.oSettings = docklet.$int = null; + docklet.childNodes = [];//@todo hack!! apf3.0 - the childNodes array isnt cleaned correctly. The parsing sees this as that all the children are already rendered + dockwin_cache.push(docklet); + + //Remove from document + docklet.parentNode = null; + docklet.$ext.parentNode.removeChild(docklet.$ext); + + return srcUrl; + } + + this.$getCurrentFragment = function(){ + var fr, col, fragment = []; + for (var i = 0, l = this.$columns.length; i < l; i++) { + col = this.$columns[i]; + for (var j = col.childNodes.length -1; j >= 0; j--) { + if (col.childNodes[j].nodeType != 1) + continue; + + fragment.push(cacheDocklet.call(this, apf.findHost(col.childNodes[j]))); + } + } + + return fragment; + }; + + this.$setCurrentFragment = function(fragment){ + var dataNode, pHtmlNode, srcUrl, docklet, nodes = this.getTraverseNodes(); + for (var i = 0, l = nodes.length; i < l; i++) { + dataNode = nodes[i]; + pHtmlNode = this.$columns[this.$applyBindRule("column", dataNode) || 0]; + srcUrl = this.$applyBindRule("src", dataNode) || "file:" + + this.$applyBindRule("url", dataNode); + docklet = getDockwin.call(this, dataNode, pHtmlNode); + createWidget.call(this, srcUrl, null, docklet, dataNode); //assuming cache + } + }; + + var oEmpty; + this.$setClearMessage = function(msg){ + if (!oEmpty) { + if (!this.$hasLayoutNode("empty")) + return; + + this.$getNewContext("empty"); + + var xmlEmpty = this.$getLayoutNode("empty"); + if (!xmlEmpty) return; + + oEmpty = apf.insertHtmlNode(xmlEmpty, this.$int); + } + else { + if(!this.oInt.lastChild) + this.oInt.appendChild(oEmpty); + else + this.oInt.insertBefore(oEmpty, this.oInt.lastChild); + } + + var empty = this.$getLayoutNode("empty", "caption", oEmpty); + if (empty) + apf.setNodeValue(empty, msg || ""); + if (oEmpty) + oEmpty.setAttribute("id", "empty" + this.$uniqueId); + }; + + this.$removeClearMessage = function(){ + var oEmpty = document.getElementById("empty" + this.$uniqueId); + if (oEmpty) + oEmpty.parentNode.removeChild(oEmpty); + //else this.$int.innerHTML = ""; //clear if no empty message is supported + }; + + var docklet_cache = {}; + function createWidget(srcUrl, strXml, docklet, dataNode){ + var widget; + //Caching + while(widget = docklet_cache[srcUrl]) { + if (!widget.fragInt.childNodes.length) + srcUrl += "_"; + else break; + } + + if (!widget && !strXml) { + srcUrl = "error"; + widget = docklet_cache["error"]; + } + + docklet.$getLayoutNode("main", "container", docklet.$ext) + .innerHTML = ""; + docklet.$srcUrl = srcUrl; + + if (widget) { + var xmlNode = widget.xmlNode; + + if (xmlNode.getAttribute("width")) + docklet.setProperty("width", xmlNode.getAttribute("width")); + else + docklet.$ext.style.width = "auto"; + + docklet.oSettings = docklet.$getLayoutNode("main", "settings_content", docklet.$ext); + docklet.$int = docklet.$getLayoutNode("main", "container", docklet.$ext); + + docklet.$int.appendChild(widget.fragInt); + if (docklet.oSettings) + docklet.oSettings.appendChild(widget.fragSettings); + + var amlNodes = widget.amlNodes || [];//@todo temp workaround apf3.0 + for (var i = 0, l = amlNodes.length; i < l; i++) + if (amlNodes[i].hasFeature) + docklet.appendChild(amlNodes[i], null, true); + + docklet.$dockletClass = widget.dockletClass; + + if (widget.dockletClass.load) + widget.dockletClass.load(dataNode, docklet); + } + else { + var uId = apf.all.length; + var col = []; + strXml = strXml.replace(/\b(id|actiontracker|group)="([^"]*)"|\b(id|actiontracker|group)='([^']*)''/g, + function(m, n1, id1, n2, id2){ + var id = id1 || id2; + col.push(id); + if (id1) return n1 + '="' + id + "_" + uId + '"'; + if (id2) return n2 + "='" + id + "_" + uId + "'"; + }); + //@todo make the id's regexp safe + if (col.length) { + strXml = strXml.replace(new RegExp("\\b(" + col.join("|") + ")\\b", "g"), + function(m, id){ + return id + "_" + uId; + }); + } + + var dockletWidget = this.ownerDocument.$domParser.parseFromString(strXml, "text/xml", { + //nodelay : true, + docFrag : docklet, + doc : docklet.ownerDocument, + htmlNode : docklet.$int + }).firstChild; + + var name = dockletWidget.name; + + if (dockletWidget.width) + docklet.setProperty("width", dockletWidget.width); + else + docklet.$ext.style.width = "auto"; + + //Create dockletClass + if (!self[name]) + throw new Error("could not find docklet class '" + name + "'"); //@todo proper error apf3.0 + + //instantiate class + var dockletClass = new self[name]().$init(); + this.docklets.push(dockletClass); + dockletClass.create(dataNode, docklet, this); + if (dockletClass.load) + dockletClass.load(dataNode, docklet); + + docklet.$dockletClass = dockletClass; + + docklet_cache[srcUrl] = { + srcUrl : srcUrl, + xmlNode : dockletWidget.$aml, + fragInt : document.createDocumentFragment(), + fragSettings : document.createDocumentFragment(), + dockletClass : dockletClass + }; + } + + docklet.$refParent = this.$int; + } + + var dockwin_cache = []; + function getDockwin(dataNode, pHtmlNode){ + var docklet; + + var columns = this.$applyBindRule("columns", this.xmlRoot); + if (columns != this.columns) { + this.setProperty("columns", columns); + pHtmlNode = this.$columns[this.$applyBindRule("column", dataNode) || 0]; + } + + if (docklet = dockwin_cache.pop() && (typeof dockwin_cache.pop() == 'function' && dockwin_cache.pop().dataNode == dataNode)) { + docklet.parentNode = this; + pHtmlNode.appendChild(docklet.$ext); + docklet.$pHtmlNode = pHtmlNode; + //docklet.setProperty("skin", this.$applyBindRule("skin", dataNode) || "docklet"); //@todo (apf3.0) or something like that + + var skin = this.$applyBindRule("dockskin", dataNode) || "docklet"; + if (docklet.skin != skin) + docklet.$forceSkinChange(docklet.skin = skin); + docklet.show(); + } + //Creating + else { + //, null, true + docklet = new apf.modalwindow({ + htmlNode : pHtmlNode, + skinset : apf.getInheritedAttribute(this.parentNode, "skinset"), + skin : this.$applyBindRule("dockskin", dataNode) || "docklet", + draggable : true, + visible : true + }); + docklet.implement(apf.modalwindow.widget); + docklet.parentNode = this; + + docklet.$create(); + + docklet.addEventListener("beforestatechange", function(e){ + if (e.to.maximized) + docklet.$ext.parentNode.style.zIndex = 100000; + + return this.$dockletClass.dispatchEvent("beforestatechange", e); + }); + docklet.addEventListener("afterstatechange", function(e){ + if (e.from.maximized) + docklet.$ext.parentNode.style.zIndex = 1; + + this.$dockletClass.dispatchEvent("afterstatechange", e); + + if (e.to.closed) + this.remove(this.dataNode); + }); + } + + docklet.dataNode = dataNode; + apf.xmldb.nodeConnect(apf.xmldb.getXmlDocId(dataNode), dataNode, docklet.$ext, this) + + if (this.$hasBindRule("buttons")) + docklet.setProperty("buttons", this.$applyBindRule("buttons", dataNode) || this.buttons); + docklet.setProperty("state", this.$applyBindRule("state", dataNode) || "normal"); + docklet.setProperty("title", this.$applyBindRule("caption", dataNode)); + docklet.setProperty("icon", this.$applyBindRule("icon", dataNode)); + + docklet.show(); + + return docklet; + } + + //@todo hack to prevent oInt.innerHTML to be cleared + this.$clear = function(){} + + this.docklets = []; + var xml_cache = {}; + this.$add = function(dataNode, Lid, xmlParentNode, htmlParentNode, beforeNode){ + //Build window + var pHtmlNode = this.$columns[this.$applyBindRule("column", dataNode) || 0]; + var srcUrl = this.$applyBindRule("src", dataNode) || "file:" + + this.$applyBindRule("url", dataNode); + + //@todo this should be much nicer + if (!pHtmlNode) { + var cols = this.$applyBindRule("columns", this.xmlRoot) + if (cols && cols != this.columns) + this.setProperty("columns", cols); + pHtmlNode = this.$columns[this.$applyBindRule("column", dataNode) || 0]; + + if (!pHtmlNode) //@todo + throw new Error(apf.formatErrorString(0, this, "Building docklet", + "Cannot find column to hook docklet on. Seems like a timing error")); + } + + var docklet = getDockwin.call(this, dataNode, pHtmlNode); + + if (xml_cache[srcUrl]) { + var strXml = xml_cache[srcUrl]; + createWidget.call(this, srcUrl, strXml, docklet, dataNode); + } + else { + //@todo add loading to window + docklet.$getLayoutNode("main", "container", docklet.$ext) + .innerHTML = "
    Loading...
    "; + + //@todo this should be getData (apf3.0) + var model = new apf.model(), _self = this; + model.$loadFrom(srcUrl, {callback: function(data, state, extra){ + //if (this.isLoaded) + //return true; + + //@todo retry + if (!data || state != apf.SUCCESS) { + createWidget.call(_self, "error", "\ + \ + \ + Error loading this widget\ + \ + ", docklet, dataNode); + + return true; + } + + //hmmm this is not as optimized as I'd like (going through the xml parser twice) + var strXml = data;//xmlNode.xml || xmlNode.serialize(); + + + if (apf.isSafariOld) { + strXml = strXml.replace(/name/, "name='" + + xmlNode.getAttribute("name") + "'"); + xml_cache[srcUrl] = strXml; + } + else + + { + xml_cache[srcUrl] = strXml;//xmlNode.cloneNode(true); + } + + createWidget.call(_self, srcUrl, strXml, docklet, dataNode); + this.isLoaded = true; + }}); + } + }; + + this.$fill = function(){ + + }; + + this.addEventListener("beforeload", function(e){ + if (!this.$columns.length) { + var cols = this.$applyBindRule("columns", e.xmlNode) + if (cols && cols != this.columns) + this.setProperty("columns", cols); + } + }); + + this.addEventListener("xmlupdate", function(e){ + //if (e.action.match(/add|insert|move/)) + //apf.AmlParser.parseLastPass(); + + var cols = this.$applyBindRule("columns", this.xmlRoot) + if (cols && cols != this.columns) + this.setProperty("columns", cols); + }); + + this.$selectDefault = function(xmlNode){ + if (this.select(this.getFirstTraverseNode(xmlNode), null, null, null, true)) + return true; + else { + var nodes = this.getTraverseNodes(xmlNode); + for(var i = 0; i < nodes.length; i++) { + if (this.$selectDefault(nodes[i])) + return true; + } + } + }; + + /**** Init ****/ + + this.$draw = function(){ + //Build Main Skin + this.$ext = this.$getExternal(); + this.$int = this.$getLayoutNode("main", "container", this.$ext); + }; + + this.$loadAml = function(x){ + if (document.elementFromPointAdd) + document.elementFromPointAdd(this.$ext); + }; + +}).call(apf.portal.prototype = new apf.MultiSelect()); + + +apf.aml.setElement("portal", apf.portal); +apf.aml.setElement("src", apf.BindingRule); +apf.aml.setElement("column", apf.BindingRule); +//apf.aml.setElement("state", apf.BindingRule); +apf.aml.setElement("draggable", apf.BindingRule); +apf.aml.setElement("dockskin", apf.BindingRule); +apf.aml.setElement("buttons", apf.BindingRule); +apf.aml.setElement("caption", apf.BindingRule); +apf.aml.setElement("traverse", apf.BindingRule); + +/** + * @constructor + */ +apf.portal.Docklet = function(){} +apf.portal.Docklet.prototype = new apf.Class(); +apf.portal.Docklet.prototype.create = function(xmlSettings, oWidget, oPortal){ + this.xmlSettings = xmlSettings + this.oWidget = oWidget; + + if (this.$create) + this.$create(xmlSettings, oWidget, oPortal); +}; + + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/elements/progressbar.js)SIZE(8709)TIME(Mon, 10 Jan 2011 10:53:12 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + + +/** + * Element graphically representing a percentage value which increases + * automatically with time. This element is most often used to show the progress + * of a process. The progress can be either indicative or exact. + * Example: + * This example shows a progress bar that is only visible when an application is + * synchronizing it's offline changes. When in this process it shows the exact + * progress of the sync process. + * + * + * + * + * @constructor + * @allowchild {smartbinding} + * @addnode elements:progressbar + * + * @inherits apf.StandardBinding + * @inherits apf.DataAction + * + * @author Ruben Daniels (ruben AT ajax DOT org) + * @version %I%, %G% + * @since 0.9 + * + * @binding value Determines the way the value for the element is retrieved + * from the bound data. + * Example: + * Sets the progress position based on data loaded into this component. + * + * + * + * + * + * + * Example: + * A shorter way to write this is: + * + * + * + * + * + * + */ +apf.progress = function(struct, tagName){ + this.$init(tagName || "progress", apf.NODE_VISIBLE, struct); +}; +apf.progressbar = function(struct, tagName){ + this.$init(tagName || "progressbar", apf.NODE_VISIBLE, struct); +}; + +(function(){ + + this.implement(apf.DataAction); + + + this.$focussable = false; // This object can get the focus + + /**** Properties and Attributes ****/ + + this.value = 0; + this.min = 0; + this.max = 100; + + this.$running = false; + this.$timer; + + /** + * @attribute {Boolean} autostart whether the progressbar starts automatically. + * @attribute {Boolean} autohide whether the progressbar hides when the progress is at 100%. Setting this to true will hide the progressbar at start when autostart is not set to true. + */ + this.$booleanProperties["autostart"] = true; + this.$booleanProperties["autohide"] = true; + + this.$supportedProperties.push("value", "min", "max", "autostart", "autohide"); + + /** + * @attribute {String} value the position of the progressbar stated between + * the min and max value. + */ + this.$propHandlers["value"] = function(value){ + this.value = parseInt(value) || this.min; + + if (this.value >= this.max) + apf.setStyleClass(this.$ext, this.$baseCSSname + "Complete", [this.$baseCSSname + "Running", this.$baseCSSname + "Half"]); + else + apf.setStyleClass(this.$ext, this.$baseCSSname + "Running", [this.$baseCSSname + "Complete"]); + + if (this.value >= this.max / 2) + apf.setStyleClass(this.$ext, this.$baseCSSname + "Half", []); + + this.oSlider.style.width = (this.value * 100 / (this.max - this.min)) + "%" + + /*Math.max(0, + Math.round((this.$ext.offsetWidth - 5) + * (this.value / (this.max - this.min)))) + "px";*/ + + this.oCaption.nodeValue = + Math.round((this.value / (this.max - this.min)) * 100) + "%"; + }; + + /** + * @attribute {Number} min the minimum value the progressbar may have. This is + * the value that the progressbar has when it's at its start position. + */ + this.$propHandlers["min"] = function(value){ + this.min = parseFloat(value); + } + + /** + * @attribute {Number} max the maximum value the progressbar may have. This is + * the value that the progressbar has when it's at its end position. + */ + this.$propHandlers["max"] = function(value){ + this.max = parseFloat(value); + } + + /**** Public Methods ****/ + + + + /** + * Sets the value of this element. This should be one of the values + * specified in the values attribute. + * @param {String} value the new value of this element + */ + this.setValue = function(value){ + this.setProperty("value", value, false, true); + }; + + /** + * Returns the current value of this element. + * @return {String} + */ + this.getValue = function(){ + return this.value; + }; + + + + /** + * Resets the progress indicator. + * @param {Boolean} restart whether a this.$timer should start with a new indicative progress indicator. + */ + this.clear = function(){ + this.$clear(); + } + + this.$clear = function(restart, restart_time){ + clearInterval(this.$timer); + this.setValue(this.min); + //this.oSlider.style.display = "none"; + apf.setStyleClass(this.$ext, "", [this.$baseCSSname + "Running", this.$baseCSSname + "Complete"]); + + if (restart) { + var _self = this; + this.$timer = setInterval(function(){ + _self.start(restart_time); + }); + } + + if (this.autohide) + this.hide(); + + this.$running = false; + }; + + /** + * Starts the progress indicator. + * @param {Number} start the time between each step in milliseconds. + */ + this.start = function(time){ + if (this.autohide) + this.show(); + + clearInterval(this.$timer); + + //if (this.value == this.max) + //this.setValue(this.min + (this.max - this.min) * 0.5); + + //this.oSlider.style.display = "block"; + var _self = this; + this.$timer = setInterval(function(){ + if (_self.$amlDestroyed) + clearInterval(_self.$timer); + else + _self.$step(); + }, time || 1000); + this.$setStyleClass(this.$ext, this.$baseCSSname + "Running"); + }; + + /** + * Pauses the progress indicator. + */ + this.pause = function(){ + clearInterval(this.$timer); + }; + + /** + * Stops the progress indicator from moving. + * @param {Boolean} restart whether a this.$timer should start with a new indicative progress indicator. + */ + this.stop = function(restart, time, restart_time){ + clearInterval(this.$timer); + this.setValue(this.max); + + var _self = this; + this.$timer = setInterval(function(){ + _self.$clear(restart, (restart_time || 0)); + }, time || 500); + }; + + /**** Private methods ****/ + + this.$step = function(){ + if (this.value == this.max) + return; + + this.setValue(this.value + 1); + }; + + /**** Init ****/ + + this.$draw = function(clear, parentNode, Node, transform){ + //Build Main Skin + this.$ext = this.$getExternal(); + this.oSlider = this.$getLayoutNode("main", "progress", this.$ext); + this.oCaption = this.$getLayoutNode("main", "caption", this.$ext); + }; + + this.$loadAml = function(x){ + if (this.autostart) + this.start(); + + if (this.autohide) + this.hide(); + }; + +}).call(apf.progressbar.prototype = new apf.StandardBinding()); + + +apf.progress.prototype = apf.progressbar.prototype; + +apf.aml.setElement("progress", apf.progress); +apf.aml.setElement("progressbar", apf.progressbar); + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/elements/propedit.js)SIZE(46649)TIME(Mon, 10 Jan 2011 10:53:12 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + + +//@todo There is a lot of dead code in here (also in the skin) remove it + +/** + * Element providing a two column grid with properties and values. The values + * are editable using apf elements. + * + * @constructor + * @define propedit + * @addnode elements + * + * @author Ruben Daniels (ruben AT ajax DOT org) + * @version %I%, %G% + * @since 0.4 + * + * @inherits apf.DataBinding + */ +apf.propedit = function(struct, tagName){ + this.$init(tagName || "propedit", apf.NODE_VISIBLE, struct); + + //this.$headings = [], + //this.$cssRules = []; //@todo Needs to be reset; + this.$nodes = []; + //this.$lastOpened = {}; + + this.$editors = {}; + + + this.$dynCssClasses = []; + +}; + +(function(){ + this.$init(function(){ + this.addEventListener("keydown", keyHandler, true); + }); + + + this.implement(apf.Cache); + + + this.implement(apf.DataAction); + + + this.$focussable = true; // This object can get the focus + this.$isTreeArch = true; // This element has a tree architecture + this.$isWindowContainer = -1; + + this.startClosed = true; + this.$animType = apf.tween.NORMAL; + this.$animSteps = 3; + this.$animSpeed = 20; + + this.$useiframe = 0; + + //1 = force no bind rule, 2 = force bind rule + this.$attrExcludePropBind = apf.extend({ + properties : 3 //only when it has an xpath + }, this.$attrExcludePropBind); + + /** + * @attribute {Boolean} iframe whether this element is rendered inside an iframe. This is only supported for IE. Default is false for datagrid and true for spreadsheet and propedit. + */ + this.$booleanProperties["iframe"] = true; + + /** + * @attribute {String} properties the {@link terms.datainstruction data instruction} + * to fetch a template definition of the layout for this component. A template + * consists of descriptions of columns (or rows for propedit) for which + * several settings are determined such as validation rules, edit component + * and selection rules. + * Example: + * This example contains a template that describes the fields in a property + * editor for xml data representing a news article. + * + * + * + * + * + * + * Yes + * No + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + */ + this.$propHandlers["properties"] = function(value){ + if (!value) + return this.clear(); + + var _self = this; + var propLoadObj = { //Should probably exist only once if expanded with xmlUpdate + load : function(data){ + _self.$loadingProps = false; + + if (typeof data == "string") + data = apf.getXml(data); + + _self.$properties = data; + + _self.$allowLoad = true; + if (_self.$loadqueue) + _self.$checkLoadQueue(); + else if (_self.xmlRoot) + _self.load(_self.xmlRoot); + delete _self.$allowLoad; + }, + + clear : function(){ + _self.$loadingProps = false; + }, + + xmlRoot : this.xmlRoot + }; + + var xml; + this.$loadingProps = true; + if (typeof value == "string") { + if (value.substr(0, 1) == "<") + propLoadObj.load(value); + else + apf.setModel(value, propLoadObj); + } + else if (value.$isModel){ + //Value is model aml element + value.register(propLoadObj); + } + else { + if (this.$properties == value && !this.caching) + return; + + //Assuming value is xml node + + setTimeout(function(){ + propLoadObj.load(value); + }); + + } + + if (this.$properties) + this.$prevProperties = this.$properties; + + delete this.$properties; + }; + this.addEventListener("prop.properties", function(e){ + if (!e.changed && this.$loadqueue) + this.$propHandlers["properties"].call(this, e.value); + }); + + this.$columns = ["50%", "50%"]; + this.$propHandlers["columns"] = function(value){ + this.$columns = value && value.splitSafe(",") || ["50%", "50%"]; + + if (this.$headings) { + this.$headings[0].setProperty("width", this.$columns[0]); + this.$headings[1].setProperty("width", this.$columns[1]); + } + } + + this.$propHandlers["filter"] = function(value){ + if (!value) { + this.$allowLoad = 2; + var xmlRoot = this.xmlRoot; + this.clear(); + if (this.$searchProperties) { + this.$properties = this.$searchProperties; + delete this.$searchProperties; + } + this.load(xmlRoot); + delete this.$allowLoad; + return; + } + + var p = (this.$searchProperties || (this.$searchProperties = this.$getProperties())).cloneNode(true); + var nodes = p.selectNodes("//node()[not(local-name()='group' or contains(translate(@caption, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz'), '" + value + "'))]"); + for (var i = nodes.length - 1; i >= 0; i--) + nodes[i].parentNode.removeChild(nodes[i]); + + nodes = p.selectNodes("//node()[local-name()='group' and not(node())]"); + + var parent; + for (var i = nodes.length - 1; i >= 0; i--) { + parent = nodes[i].parentNode; + parent.removeChild(nodes[i]); + if (!parent.childNodes.length && parent.parentNode) + parent.parentNode.removeChild(parent); + } + + //var t = this.$properties; + var xmlRoot = this.xmlRoot; + this.clear(); + this.$properties = p; + this.$allowLoad = 2; + this.load(xmlRoot, {force: true}); + delete this.$allowLoad; + //this.$properties = this.$searchProperties; + //this.$properties = t; + }; + + function scrollIntoView(){ + var Q = (this.current || this.$selected), + o = this.$body; + o.scrollTop = (Q.offsetTop) - 21; + } + + /**** Keyboard Support ****/ + + this.$findHtmlNode = function(id) { + return this.$pHtmlDoc.getElementById(id); + } + + + function keyHandler(e){ + var key = e.keyCode, + ctrlKey = e.ctrlKey, + shiftKey = e.shiftKey; + + var selXml = this.$lastEditor && this.$lastEditor[2], + oInt = this.$useiframe ? this.oDoc.documentElement : this.$body, + margin, node, hasScroll, hasScrollX, hasScrollY, items, lines; + + switch (key) { + case 36: + //HOME + return false; + case 35: + //END + return false; + case 107: + //+ + break; + case 37: + //LEFT + this.$slideToggle(this.$selected.firstChild); + return false; + case 107: + case 39: + //RIGHT + this.$slideToggle(this.$selected.firstChild); + + return false; + case 38: + //UP + var node = selXml; + var sNode = selXml.previousSibling; + while(sNode && sNode.nodeType != 1) sNode = sNode.previousSibling; + + if (sNode) { + var last = sNode, nodes; + while ((nodes = last.selectNodes("prop")).length) + last = nodes[nodes.length - 1]; + sNode = last; + } + else { + sNode = node.parentNode + if (sNode[apf.TAGNAME] != "prop") { + sNode = sNode.previousSibling; + while(sNode && sNode.nodeType != 1) sNode = sNode.previousSibling; + + if (sNode && sNode[apf.TAGNAME] != "prop") { + sNode = (nodes = sNode.selectNodes("prop"))[nodes.length - 1]; + while(sNode && sNode.nodeType != 1) sNode = sNode.previousSibling; + } + } + } + + if (!sNode) + return; + + var selHtml = apf.xmldb.findHtmlNode(sNode, this); + while (!selHtml.offsetWidth) + selHtml = apf.xmldb.findHtmlNode(sNode = sNode.parentNode, this); + + var top = apf.getAbsolutePosition(selHtml, this.$body)[1] + - (selHtml.offsetHeight/2); + if (top <= this.$ext.scrollTop) + this.$ext.scrollTop = top; + + this.select(selHtml); + + return false; + case 40: + //DOWN + var node, sNode = (node = selXml).selectSingleNode("prop") || node.nextSibling; + do { + while(sNode && (sNode.nodeType != 1 || sNode[apf.TAGNAME] != "prop")) + sNode = sNode.nextSibling; + + if (!sNode) { + sNode = node.parentNode.nextSibling; + if (sNode && sNode[apf.TAGNAME] != "prop") + sNode = sNode.selectSingleNode("prop"); + } + }while(sNode && sNode.nodeType != 1); + + if (!sNode) + return; + + var selHtml = apf.xmldb.findHtmlNode(sNode, this); + while (!selHtml.offsetWidth) + selHtml = apf.xmldb.findHtmlNode(sNode = sNode.parentNode, this); + + if (sNode == node) { + sNode = node.nextSibling + while(sNode && (sNode.nodeType != 1 || sNode[apf.TAGNAME] != "prop")) + sNode = sNode.nextSibling; + var selHtml = apf.xmldb.findHtmlNode(sNode, this); + } + + var top = apf.getAbsolutePosition(selHtml, this.$body)[1] + + (selHtml.offsetHeight/2); + if (top > this.$ext.scrollTop + this.$ext.offsetHeight) + this.$ext.scrollTop = top - this.$ext.offsetHeight; + + this.select(selHtml); + + return false; + }; + } + + + + /**** Focus ****/ + // Too slow for IE + + this.$focus = function(){ + if (!this.$ext || (apf.isIE && this.$useiframe && this.cssfix)) //@todo fix this by fixing focussing for this component + return; + + this.$setStyleClass(this.oFocus || this.$ext, this.$baseCSSname + "Focus"); + + if (this.oDoc) + this.$setStyleClass(this.oDoc.documentElement, this.$baseCSSname + "Focus"); + + if (this.$lastEditor) + this.$lastEditor[0].$focus(); + }; + + this.$blur = function(){ + + if (this.renaming) + this.stopRename(null, true); + + + //@todo fix this by fixing focussing for this component + if (!this.$ext || (apf.isIE && this.$useiframe && this.cssfix)) + return; + + this.$setStyleClass(this.oFocus || this.$ext, "", [this.$baseCSSname + "Focus"]); + + if (this.oDoc) + this.$setStyleClass(this.oDoc.documentElement, "", [this.$baseCSSname + "Focus"]); + + if (this.$lastEditor) + this.$lastEditor[0].$blur(); + }; + + /**** Sliding functions ****/ + + this.$slideToggle = function(htmlNode){ + container = htmlNode.parentNode.lastChild; + + if (apf.getStyle(container, "display") == "block") { + htmlNode.className = htmlNode.className.replace(/min/, "plus"); + this.$slideClose(container); + } + else { + htmlNode.className = htmlNode.className.replace(/plus/, "min"); + this.$slideOpen(container); + } + }; + + this.$slideOpen = function(container){ + container.style.display = ""; + + apf.tween.single(container, { + type : 'scrollheight', + from : 3, + diff : -2, + to : container.scrollHeight, + anim : this.$animType, + steps : this.$animSteps, + interval: this.$animSpeed, + onfinish: function(container){ + container.style.overflow = "visible"; + container.style.height = "auto"; + } + }); + }; + + this.$slideClose = function(container){ + container.style.height = container.offsetHeight; + container.style.overflow = "hidden"; + + apf.tween.single(container, { + type : 'scrollheight', + from : container.scrollHeight, + diff : -2, + to : 0, + anim : this.$animType, + steps : this.$animSteps, + interval: this.$animSpeed, + onfinish: function(container, data){ + container.style.display = "none"; + } + }); + }; + + this.$findContainer = function(htmlNode) { + var node = htmlNode.nextSibling; + if (!node) + return htmlNode; + return node.nodeType == 1 ? node : node.nextSibling; + }; + + /**** Databinding ****/ + + this.addEventListener("bindingsload", this.$loaddatabinding = function(e){ + var rules = e.bindings["properties"]; + if (!rules || !rules.length) + return; + + for (var i = 0, l = rules.length; i < l; i++) { + + } + }); + + this.$unloaddatabinding = function(){ + }; + + /** + * Returns a column definition object based on the column number. + * @param {Number} hid the heading number; this number is based on the sequence of the column elements. + */ + this.getColumn = function(nr){ + return this.$headings[nr || this.$lastcol || 0]; + }; + + /**** Column management ****/ + + /** + * Resizes a column. + * @param {Number} hid the heading number; this number is based on the sequence of the column elements. + * @param {Number} newsize the new size of the column. + * @todo optimize but bringing down the string concats + */ + this.resizeColumn = function(nr, newsize){ + var h = this.$headings[nr]; + h.resize(newsize); + }; + + /** + * Hides a column. + * @param {Number} hid the heading number; this number is based on the sequence of the column elements. + */ + this.hideColumn = function(nr){ + var h = this.$headings[nr]; + h.hide(); + }; + + /** + * Shows a hidden column. + * @param {Number} hid the heading number; this number is based on the sequence of the column elements. + */ + this.showColumn = function(nr){ + var h = this.$headings[nr]; + h.show(); + }; + + /**** Databinding ****/ + + /* + Property: + - caption + - editor (name of widget, lm function returning amlNode or lm template ref) + - children being aml nodes + - value (lm, only when widget is created by grid) + + validation attr: (only when widget is created by grid) + - required + - datatype + - required + - pattern + - min + - max + - maxlength + - minlength + - notnull + - checkequal + - validtest + + Group: + - name + - properties + + Move from dg to widgets: + - autocomplete with template + - dropdown with bound multicheck + + Furthermore it supports: + - properties binding rule to switch properties + - special node introspection mode + - .listAttributes() + - returns array of objects + - name + - editor + - validation rules + - .setAttribute(name, value) + - .getAttribute(name) + - .addEventListener("prop." + name); + - .removeEventListener("prop." + name); + */ + + this.$getProperties = function(xmlNode){ + if (this.properties) { + return this.$properties || false; + } + else if (this.$bindings.properties) { + var props = this.$bindings.properties; + for (var i = 0; i < props.length; i++) { + if (!props[i].match) //compile via lm + return xx; //async request entry + } + } + + return false; + } + + /** + * Forces propedit to wait for the load of a new property definition + */ + this.$canLoadData = function(){ + if (this.$allowLoad || !this.$attrBindings["properties"]) + return this.$getProperties() ? true : false; + + /* + @todo this is turned off because it competes with the signal from + prop.properties. When the properties property contains an async + statement, the prop isnt set until the call returns. This means this + component doesnt know it has the wrong property set until later. + Thats causing problems. Uncommenting this causes a problem when a + dynamic property is not refreshed whenever the model is set. + */ + else if (false) { + var _self = this; + apf.queue.add("load" + this.$uniqueId, function(){ + _self.$allowLoad = true; + _self.$checkLoadQueue(); + delete _self.$allowLoad; + }); + return false; + } + } + + this.$generateCacheId = function(xmlNode){ + var node = this.$getProperties(); + return node ? node.getAttribute(apf.xmldb.xmlIdTag) + || apf.xmldb.nodeConnect(apf.xmldb.getXmlDocId(node), node) + : 0; + } + + this.$getCurrentFragment = function(){ + var fragment = this.$container.ownerDocument.createDocumentFragment(); + fragment.properties = this.$searchProperties || this.$prevProperties || this.$properties; + + while (this.$container.childNodes.length) { + fragment.appendChild(this.$container.childNodes[0]); + } + + this.clearSelection(); + + return fragment; + }; + + this.$setCurrentFragment = function(fragment){ + this.$container.appendChild(fragment); + + if (!apf.window.hasFocus(this)) + this.blur(); + + apf.xmldb.addNodeListener(this.xmlRoot, this); //set node listener if not set yet + + //@todo most unoptimized way possible: + if (this.filter && this.$allowLoad != 2) { + delete this.$searchProperties; + this.$prevProperties = fragment.properties; + this.$propHandlers["filter"].call(this, this.filter); + } + else { + this.$xmlUpdate(null, this.xmlRoot); + this.$properties = fragment.properties; + this.select(apf.xmldb.findHtmlNode(this.$properties.selectSingleNode(".//prop"), this)); + } + }; + + this.$load = function(xmlNode){ + var p = this.$getProperties(); + if (!p) return false; + + var output = []; + var docId = this.documentId = apf.xmldb.getXmlDocId(p); + + //Add listener to XMLRoot Node + apf.xmldb.addNodeListener(xmlNode, this); //@todo apf3 potential cleanup problem + //apf.xmldb.addNodeListener(this.xmlRoot, this); + + var _self = this, doc = p.ownerDocument; + (function walk(nodes, parent, depth){ + for (var u, s, cell, sLid, pnode, html, node, i = 0, l = nodes.length; i < l; i++) { + node = nodes[i]; + _self.$getNewContext("row") + html = _self.$getLayoutNode("row"); + + if (node[apf.TAGNAME] == "group") { + _self.$getNewContext("cell"); + apf.setStyleClass(html, "heading"); + + cell = html.appendChild(_self.$getLayoutNode("cell")); + apf.setNodeValue(_self.$getLayoutNode("cell", "caption", cell), + (node.getAttribute("caption") || "").trim() || ""); //@todo for IE but seems not a good idea + + //group| + pnode = html.appendChild(doc.createElement("blockquote")); + walk(node.selectNodes("prop"), pnode, depth); + html.insertBefore(u = doc.createElement("u"), html.firstChild).appendChild(doc.createTextNode(" ")); + u.setAttribute("class", "min"); + } + else { + apf.xmldb.nodeConnect(docId, node, html, _self); + + //Build the Cells + _self.$getNewContext("cell"); + h = _self.$headings[0]; + + cell = html.appendChild(_self.$setStyleClass(_self.$getLayoutNode("cell"), h.$className)); + apf.setNodeValue(_self.$getLayoutNode("cell", "caption", cell), + (node.getAttribute("caption") || "").trim() || ""); //@todo for IE but seems not a good idea + + if (depth) + cell.firstChild.setAttribute("style", "padding-left:" + (depth * 15) + "px"); + + _self.$getNewContext("cell"); + h = _self.$headings[1]; + + cell = html.appendChild(_self.$setStyleClass(_self.$getLayoutNode("cell"), h.$className)); + apf.setNodeValue(_self.$getLayoutNode("cell", "caption", cell), + ((apf.lm.compile(node.getAttribute("value"), {nostring: true}))(_self.xmlRoot) || "") || ""); //@todo for IE but seems not a good idea + + if ((s = node.selectNodes("prop")).length) { + pnode = html.appendChild(doc.createElement("blockquote")); + pnode.setAttribute("style", "display:none;overflow:hidden;height:0;"); + walk(s, _self.$getLayoutNode("heading", "container", pnode), depth + 1); + + //Add opener + html.insertBefore(u = doc.createElement("u"), html.firstChild).appendChild(doc.createTextNode(" ")); + u.setAttribute("class", "plus"); + } + } + + if (!parent) + output.push(html); + else + parent.appendChild(html); + } + })(p.selectNodes("group|prop"), null, 0); + + apf.insertHtmlNodes(output, this.$body); + + this.setProperty("root", this.xmlRoot); //or xmlNode ?? + + //@todo select the first one + var prop = p.selectSingleNode(".//prop"); + if (prop) { + this.select(this.$findHtmlNode( + prop.getAttribute(apf.xmldb.xmlIdTag) + + "|" + this.$uniqueId)); + } + + //@todo most unoptimized way possible: + if (this.filter && this.$allowLoad != 2) { + delete this.$searchProperties; + this.$propHandlers["filter"].call(this, this.filter); + } + } + + this.$xmlUpdate = function(action, xmlNode, listenNode, UndoObj){ + if (xmlNode != this.xmlRoot) + return; + + if (UndoObj && this.$lastEditor[0] == UndoObj.amlNode) { + this.$lastEditor[1].firstChild.innerHTML = + ((apf.lm.compile(this.$lastEditor[2].getAttribute("value"), { + nostring: true + }))(this.xmlRoot) || "") || ""; + } + else { + var p = this.$getProperties(); + if (!p) + return; + + var node, htmlNode, nodes = p.selectNodes(".//group/prop"); + for (var i = 0, l = nodes.length; i < l; i++) { + node = nodes[i]; + htmlNode = this.$findHtmlNode( + node.getAttribute(apf.xmldb.xmlIdTag) + "|" + this.$uniqueId); + + htmlNode.childNodes[htmlNode.firstChild.tagName == "U" ? 2 : 1] + .firstChild.innerHTML = + ((apf.lm.compile(node.getAttribute("value"), { + nostring: true + }))(this.xmlRoot) || "") || ""; + } + } + } + + this.$hideEditor = function(remove){ + if (this.$lastEditor) { + //this.$lastEditor[0].$blur(); + this.$lastEditor[0].setProperty("visible", false); + + if (remove) { + var pNode = this.$lastEditor[0].$ext.parentNode; + pNode.removeChild(this.$lastEditor[0].$ext); + pNode.removeAttribute("id"); + delete pNode.onresize; + } + + var nodes = this.$lastEditor[1].childNodes; + for (var i = 0, l = nodes.length; i < l; i++) { + if (!nodes[i].host) + nodes[i].style.display = ""; + } + + delete this.$lastEditor; + } + } + + this.clearSelection = function(){ + this.$setStyleClass(this.$selected, "", ["selected"]); + delete this.$selected; + this.$hideEditor(); + } + + this.select = function(htmlNode){ + if (this.disabled) //@todo apf3.0 userAction + return; + + + if (this.$selected == htmlNode) { + /*var oEditor = this.$lastEditor[0]; + $setTimeout(function(){ + oEditor.focus(); + });*/ + + return; + } + + if (this.$selected) + this.$setStyleClass(this.$selected, "", ["selected"]); + + this.$setStyleClass(htmlNode, "selected"); + this.$selected = htmlNode; + + this.$hideEditor(); + + var prop = apf.xmldb.getNode(htmlNode); + var _self = this; + + this.setProperty("selected", prop); + + /* + - editor (name of widget, lm function returning amlNode or lm template ref) + - children being aml nodes + */ + var editParent = this.$selected.childNodes[this.$selected.firstChild.tagName == "U" ? 2 : 1]; + var oEditor, editor = prop.getAttribute("editor"); + var ceditor = apf.lm.compile(editor, {xpathmode: 2}); + if (ceditor.type == 2) { + + if (!editor) { + if (prop.childNodes.length) //It's a group + return; + else + throw new Error("Missing editor attribute on property element: " + prop.xml); //@todo apf3.0 make into proper error + } + + + if (!this.$editors[editor]) { + var constr = apf.namespaces[apf.ns.aml].elements[editor]; + var isTextbox = "textarea|textbox|secret".indexOf(editor) > -1; + var info = { + htmlNode : editParent, + width : "100%+2", + height : 19, + style : "position:relative;", //z-index:10000 + value : "[{" + this.id + ".root}::" + + (v = prop.getAttribute("value")).substr(1, v.length - 2) + + "]", + focussable : false, + realtime : !isTextbox + }; + if (isTextbox) { + info.focusselect = true; + info.onkeydown = function(e){ + if (e.keyCode == 13) + this.change(this.getValue()); + } + } + else if (editor == "checkbox") + info.values = "true|false"; + + //@todo copy all non-known properties of the prop element + + if (constr.prototype.hasFeature(apf.__MULTISELECT__)) { + info.caption = "[text()]"; + info.eachvalue = "[@value]"; + info.each = "item"; + info.model = "{apf.xmldb.getElementById('" + + prop.getAttribute(apf.xmldb.xmlIdTag) + "')}"; + } + + oEditor = this.$editors[editor] = new constr(info); + + var box = apf.getBox(apf.getStyle(oEditor.$ext, "margin")); + if (box[1] || box[3]) { + oEditor.setAttribute("width", "100%+2-" + (box[1] + box[3])); + } + else if (!box[3]) + oEditor.$ext.style.marginLeft = "-1px"; + + //oEditor.$focussable = false; + /*oEditor.addEventListener("focus", function(){ + _self.focus(); + this.$focus(); + });*/ + oEditor.parentNode = this; + oEditor.$focusParent = this; + oEditor.setAttribute("focussable", "true"); + //delete oEditor.parentNode; + + //@todo set actiontracker + oEditor.$parentId = editParent.getAttribute("id"); + oEditor.$parentRsz = editParent.onresize; + + //Patch oEditor to forward change + oEditor.$executeAction = function(atAction, args, action, xmlNode, noevent, contextNode, multiple){ + if (atAction == "setAttribute" && !args[2]) + atAction = "removeAttribute"; + + this.parentNode.$executeAction.call(this.parentNode, + atAction, args, action, xmlNode, noevent, contextNode, multiple); + } + } + else { + oEditor = this.$editors[editor]; + + if (oEditor.hasFeature(apf.__MULTISELECT__)) { + oEditor.setAttribute("model", "{apf.xmldb.getElementById('" + + prop.getAttribute(apf.xmldb.xmlIdTag) + "')}"); + } + + oEditor.setAttribute("value", "[{" + this.id + ".root}::" + + (v = prop.getAttribute("value")).substr(1, v.length - 2) + + "]"); + + oEditor.setProperty("visible", true); + if (oEditor.$ext.parentNode + && oEditor.$ext.parentNode.nodeType == 1 + && !apf.hasSingleResizeEvent) { + if (!oEditor.$parentRsz) + oEditor.$parentRsz = oEditor.$ext.parentNode.onresize; + oEditor.$ext.parentNode.removeAttribute("id"); + delete oEditor.$ext.parentNode.onresize; + } + + editParent.appendChild(oEditor.$ext); + editParent.setAttribute("id", editParent.$parentId); + if (oEditor.$parentRsz && !apf.hasSingleResizeEvent) { + editParent.onresize = oEditor.$parentRsz; + editParent.onresize(); + } + } + + /*setTimeout(function(){ + oEditor.focus(); + });*/ + } + else { + //Create dropdown + + var obj = ceditor.call(this, this.xmlRoot); + if (obj.localName == "template") { + //add template contents to dropped area + } + else { + //add xml into dropped area + } + } + + var nodes = editParent.childNodes; + for (var i = 0, l = nodes.length; i < l; i++) { + if (!nodes[i].host) + nodes[i].style.display = "none"; + } + + this.$lastEditor = [oEditor, editParent, prop]; + } + + this.addEventListener("$clear", function(e){ + this.$hideEditor(true); + }); + + /*this.addEventListener("blur", function(){ + if (this.$lastEditor) + this.$lastEditor[0].$blur(); + }); + + this.addEventListener("focus", function(){ + if (this.$lastEditor) + this.$lastEditor[0].$focus(); + });*/ + + /**** Init ****/ + + this.$draw = function(){ + //Build Main Skin + this.$ext = this.$getExternal(); + this.$body = this.$getLayoutNode("main", "body", this.$ext); + this.$head = this.$getLayoutNode("main", "head", this.$ext); + this.$pointer = this.$getLayoutNode("main", "pointer", this.$ext); + this.$container = this.$body; + + if (this.$head.firstChild) + this.$head.removeChild(this.$head.firstChild); + if (this.$body.firstChild) + this.$body.removeChild(this.$body.firstChild); + + var widthdiff = this.$widthdiff = this.$getOption("main", "widthdiff") || 0; + this.$defaultwidth = this.$getOption("main", "defaultwidth") || "100"; + this.$useiframe = apf.isIE && (apf.isTrue(this.$getOption("main", "iframe")) || this.iframe); + + var _self = this; + + //Initialize Iframe + if (this.$useiframe && !this.oIframe) { + //this.$body.style.overflow = "hidden"; + //var sInt = this.$body.outerHTML + var sClass = this.$body.className; + //this.$body.parentNode.removeChild(this.$body); + this.oIframe = this.$body.appendChild(document.createElement(apf.isIE + ? "" + : "iframe")); + this.oIframe.frameBorder = 0; + this.oWin = this.oIframe.contentWindow; + this.oDoc = this.oWin.document; + this.oDoc.write('\ + \ + \ + \ + \ + '); + //Import CSS + //this.oDoc.body.innerHTML = sInt; + this.$body = this.oDoc.body;//.firstChild; + this.$body.className = sClass;//this.oIframe.parentNode.className; + this.oDoc.documentElement.className = this.$ext.className; + //this.oDoc.body.className = this.$ext.className; + + apf.skins.loadCssInWindow(this.skinName, this.oWin, this.mediaPath, this.iconPath); + + if (apf.isIE) //@todo this can be removed when focussing is fixed for this component + this.$setStyleClass(this.oDoc.documentElement, this.$baseCSSname + "Focus"); + + apf.convertIframe(this.oIframe, true); + + if (apf.getStyle(this.oDoc.documentElement, "overflowY") == "auto") { + //@todo ie only + this.oIframe.onresize = function(){ + _self.$head.style.marginRight = + _self.oDoc.documentElement.scrollHeight > _self.oDoc.documentElement.offsetHeight + ? "16px" : "0"; + } + + this.addEventListener("afterload", this.oIframe.onresize); + this.addEventListener("xmlupdate", this.oIframe.onresize); + } + + this.oDoc.documentElement.onmousedown = function(e){ + if (!e) e = _self.oWin.event; + if ((e.srcElement || e.target).tagName == "HTML") + apf.popup.forceHide(); + } + + this.oDoc.documentElement.onscroll = + function(){ + if (_self.$isFixedGrid) + _self.$head.scrollLeft = _self.oDoc.documentElement.scrollLeft; + }; + } + else { + if (apf.getStyle(this.$body, "overflowY") == "auto") { + this.$resize = function(){ + _self.$head.style.marginRight = + _self.$body.scrollHeight > _self.$body.offsetHeight + ? "16px" : "0"; + } + + + this.addEventListener("resize", this.$resize); + + + this.addEventListener("afterload", this.$resize); + this.addEventListener("xmlupdate", this.$resize); + } + + this.$body.onmousedown = function(e){ + if (!e) e = event; + if ((e.srcElement || e.target) == this) + apf.popup.forceHide(); + } + + this.$body.onscroll = + function(){ + if (_self.$isFixedGrid) + _self.$head.scrollLeft = _self.$body.scrollLeft; + }; + } + + var _self = this; + this.$body.onmousedown = function(e){ + if (!e) e = event; + var target = e.srcElement || e.target; + + if (target == this) return; + + if (target.tagName == "U") { + _self.$slideToggle(target); + return; + } + + while (target.host || (target.getAttribute(apf.xmldb.htmlIdTag) || "").indexOf("|") == -1) { + target = target.parentNode; + if (target == this) return; + } + + _self.select(target); + } + }; + + this.$loadAml = function(x){ + //Create two columns + this.$headings = [ + new apf.BindingColumnRule().$draw(this, "Property", this.$columns[0], "first"), + new apf.BindingColumnRule().$draw(this, "Value", this.$columns[1]) + ]; + }; + + this.$destroy = function(){ + apf.popup.removeContent(this.$uniqueId); + + for (var prop in this.$editors) { + this.$editors[prop].destroy(); + } + + this.$ext.onclick = null; + }; + +}).call(apf.propedit.prototype = new apf.DataBinding()); + + +apf.aml.setElement("propedit", apf.propedit); +apf.aml.setElement("column", apf.BindingColumnRule); +apf.aml.setElement("description", apf.BindingRule); +apf.aml.setElement("color", apf.BindingRule); + + + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/elements/radiobutton.js)SIZE(16958)TIME(Mon, 10 Jan 2011 10:53:12 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + +/** + * Element displaying a two state button which is one of a grouped set. + * Only one of these buttons in the set can be checked at the same time. + * Example: + * + * + * Option 1 + * Option 2 + * Option 3 + * Option 4 + * + * + * Example: + * This example shows radio buttons with an explicit group set: + * + * Options + * Option 1 + * Option 2 + * + * Choices + * + * Choice 1 + * Choice 2 + * + * + * + * @constructor + * @define radiobutton + * @allowchild {smartbinding} + * @addnode elements + * + * @inherits apf.Presentation + * + * @author Ruben Daniels (ruben AT ajax DOT org) + * @version %I%, %G% + * @since 0.4 + * + * @binding value Determines the way the value for the element is retrieved + * from the bound data. + * Example: + * Sets the selection based on data loaded into this component. + * + * Choice 1 + * Choice 2 + * + * + * + * + * + * Example: + * A shorter way to write this is: + * + * Choice 1 + * Choice 2 + * + * + * @event click Fires when the user presses a mousebutton while over this element and then let's the mousebutton go. + * @see baseclass.amlnode.event.afterchange + */ +apf.radiobutton = function(struct, tagName){ + this.$init(tagName || "radiobutton", apf.NODE_VISIBLE, struct); + + /*this.$constructor = apf.radiobutton; + var fEl = apf.aml.setElement("radiobutton", function(){ + this.$init(tagName || "radiobutton", apf.NODE_VISIBLE, struct); + }); + fEl.prototype = apf.radiobutton.prototype; + apf.radiobutton = fEl;*/ +}; + +(function(){ + this.implement(apf.ChildValue); + this.$childProperty = "label"; + + this.$focussable = apf.KEYBOARD; // This object can get the focus + + //1 = force no bind rule, 2 = force bind rule + /*this.$attrExcludePropBind = apf.extend({ + checked: 1 + }, this.$attrExcludePropBind);*/ + + /**** Properties and Attributes ****/ + + this.$booleanProperties["checked"] = true; + this.$supportedProperties.push("value", "background", "group", + "label", "checked", "tooltip", "icon"); + + /** + * @attribute {String} group the name of the group to which this radio + * button belongs. Only one item in the group can be checked at the same + * time. When no group is specified the parent container functions as the + * group; only one radiobutton within that parent can be checked. + */ + this.$propHandlers["group"] = function(value){ + if (!this.$amlLoaded) + return; + + if (this.$group && this.$group.$removeRadio) + this.$group.$removeRadio(this); + + if (!value) { + this.$group = null; + return; + } + + var group = typeof value == "string" + ? + + apf.nameserver.get("radiogroup", value) + + : value; + if (!group) { + + group = apf.nameserver.register("radiogroup", value, + new apf.$group()); + group.setAttribute("id", value); + group.dispatchEvent("DOMNodeInsertedIntoDocument"); + group.parentNode = this; + + } + this.$group = group; + + if (this.oInput) + this.oInput.setAttribute("name", value); + + this.$group.$addRadio(this); + }; + + /** + * @attribute {String} tooltip the tooltip of this radio button. + */ + this.$propHandlers["tooltip"] = function(value){ + this.$ext.setAttribute("title", value); + }; + + /** + * @attribute {String} icon the icon for this radiobutton + */ + this.$propHandlers["icon"] = function(value){ + + if (!this.oIcon) + return apf.console.warn("No icon defined in the Button skin", "button"); + + + if (value) + this.$setStyleClass(this.$ext, this.$baseCSSname + "Icon"); + else + this.$setStyleClass(this.$ext, "", [this.$baseCSSname + "Icon"]); + + apf.skins.setIcon(this.oIcon, value, this.iconPath); + }; + + /** + * @attribute {String} label the label for this radiobutton + */ + this.$propHandlers["label"] = function(value){ + if (value) + this.$setStyleClass(this.$ext, "", [this.$baseCSSname + "Empty"]); + else + this.$setStyleClass(this.$ext, this.$baseCSSname + "Empty"); + + if (this.oLabel) + this.oLabel.innerHTML = value; + }; + + /** + * @attribute {String} checked whether this radiobutton is the checked one in the group it belongs to. + */ + this.$propHandlers["checked"] = function(value){ + if (!this.$group) + return; + + if (value) + this.$group.setProperty("value", this.value); + //else if (this.$group.value == this.value) + //this.$group.setProperty("value", ""); + }; + + this.$propHandlers["selected"] = function(value){ + this.setProperty("checked", value); + } + + this.addEventListener("prop.model", function(e){ + if (this.$group) + this.$group.setProperty("model", e.value); + }); + + /** + * @attribute {string} background sets a multistate background. The arguments + * are seperated by pipes '|' and are in the order of: + * 'imagefilename|mapdirection|nrofstates|imagesize' + * The mapdirection argument may have the value of 'vertical' or 'horizontal'. + * The nrofstates argument specifies the number of states the iconfile contains: + * 1 - normal + * 2 - normal, hover + * 3 - normal, hover, down + * 4 - normal, hover, down, disabled + * The imagesize argument specifies how high or wide each icon is inside the + * map, depending of the mapdirection argument. + * + * Example: + * A 3 state picture where each state is 16px high, vertically spaced + * + * background="threestates.gif|vertical|3|16" + * + * @see baseclass.basebutton + */ + this.$propHandlers["background"] = function(value){ + var oNode = this.$getLayoutNode("main", "background", this.$ext); + if (value) { + var b = value.split("|"); + this.$background = b.concat(["vertical", 2, 16].slice(b.length - 1)); + + oNode.style.backgroundImage = "url(" + this.mediaPath + b[0] + ")"; + oNode.style.backgroundRepeat = "no-repeat"; + } + else { + oNode.style.backgroundImage = ""; + oNode.style.backgroundRepeat = ""; + this.$background = null; + } + } + + /**** Public methods ****/ + + + + /** + * Sets the value of this element. This should be one of the values + * specified in the values attribute. + * @param {String} value the new value of this element + */ + this.setValue = function(value){ + this.setProperty("value", value, false, true); + }; + + /** + * Returns the current value of this element. + * @return {String} + */ + this.getValue = function(){ + return this.value; + }; + + this.select = + this.check = function(){ + this.setProperty("checked", true, false, true); + } + + this.uncheck = function(){ + this.setProperty("checked", false, false, true); + } + + this.getGroup = function(){ + return this.$group; + } + + + + /** + * Sets the checked state and related value + */ + this.$check = function(visually){ + this.$setStyleClass(this.$ext, this.$baseCSSname + "Checked"); + this.checked = true; + if (this.oInput) + this.oInput.checked = true; + this.doBgSwitch(2); + }; + + this.$uncheck = function(){ + this.$setStyleClass(this.$ext, "", [this.$baseCSSname + "Checked"]); + this.checked = false; + if (this.oInput) + this.oInput.checked = false; + this.doBgSwitch(1); + }; + + /**** Private methods ****/ + + this.$enable = function(){ + if (this.oInput) + this.oInput.disabled = false; + + var _self = this; + this.$ext.onclick = function(e){ + if (!e) e = event; + if ((e.srcElement || e.target) == this) + return; + + _self.dispatchEvent("click", { + htmlEvent: e + }); + _self.$group.change(_self.value); + } + + this.$ext.onmousedown = function(e){ + if (!e) e = event; + if ((e.srcElement || e.target) == this) + return; + + apf.setStyleClass(this, _self.$baseCSSname + "Down"); + } + + this.$ext.onmouseover = function(e){ + if (!e) e = event; + if ((e.srcElement || e.target) == this) + return; + + apf.setStyleClass(this, _self.$baseCSSname + "Over"); + } + + this.$ext.onmouseout = + this.$ext.onmouseup = function(){ + apf.setStyleClass(this, "", [_self.$baseCSSname + "Down", _self.$baseCSSname + "Over"]); + } + }; + + this.$disable = function(){ + if (this.oInput) + this.oInput.disabled = true; + + this.$ext.onclick = + this.$ext.onmousedown = + this.$ext.onmouseover = + this.$ext.onmouseout = + this.$ext.onmouseup = null; + }; + + /** + * @private + */ + this.doBgSwitch = function(nr){ + if (this.bgswitch && (this.bgoptions[1] >= nr || nr == 4)) { + if (nr == 4) + nr = this.bgoptions[1] + 1; + + var strBG = this.bgoptions[0] == "vertical" + ? "0 -" + (parseInt(this.bgoptions[2]) * (nr - 1)) + "px" + : "-" + (parseInt(this.bgoptions[2]) * (nr - 1)) + "px 0"; + + this.$getLayoutNode("main", "background", this.$ext) + .style.backgroundPosition = strBG; + } + }; + + this.$focus = function(){ + if (!this.$ext) + return; + if (this.oInput && this.oInput.disabled) + return false; + + this.$setStyleClass(this.$ext, this.$baseCSSname + "Focus"); + }; + + this.$blur = function(){ + if (!this.$ext) + return; + this.$setStyleClass(this.$ext, "", [this.$baseCSSname + "Focus"]); + }; + + /**** Keyboard support ****/ + + + this.addEventListener("keydown", function(e){ + var key = e.keyCode; + + if (key == 13 || key == 32) { + //this.check(); + //this.$group.current = this; + this.$group.change(this.value); + return false; + } + //Up + else if (key == 38) { + var node = this; + while (node && node.previousSibling) { + node = node.previousSibling; + if (node.localName == "radiobutton" && !node.disabled + && node.$group == this.$group) { + node.check(); + node.focus(); + return; + } + } + } + //Down + else if (key == 40) { + var node = this; + while (node && node.nextSibling) { + node = node.nextSibling; + if (node.localName == "radiobutton" && !node.disabled + && node.$group == this.$group) { + node.check(); + node.focus(); + return; + } + } + } + }, true); + + + /**** Init ****/ + + this.$draw = function(){ + //Build Main Skin + this.$ext = this.$getExternal(); + this.oInput = this.$getLayoutNode("main", "input", this.$ext); + this.oLabel = this.$getLayoutNode("main", "label", this.$ext); + this.oIcon = this.$getLayoutNode("main", "icon", this.$ext); + + if (this.oLabel && this.oLabel.nodeType != 1) + this.oLabel = this.oLabel.parentNode; + + //Set events + this.$enable(); + }; + + this.$childProperty = "label"; + this.$loadAml = function(x){ + if (this.group) + this.$propHandlers["group"].call(this, this.group); + + else if (this.parentNode.localName == "group") + this.$propHandlers["group"].call(this, this.parentNode); + + if (!this.$group) { + this.$propHandlers["group"].call(this, + "radiogroup" + this.parentNode.$uniqueId); + } + }; + + this.$destroy = function(){ + if (this.$group) + this.$group.$removeRadio(this); + }; + + +}).call(apf.radiobutton.prototype = new apf.Presentation()); + +apf.aml.setElement("radiobutton", apf.radiobutton); + +apf.$group = function(struct, tagName){ + this.$init(tagName || "radiogroup", apf.NODE_VISIBLE, struct); + + this.implement( + apf.StandardBinding, + + apf.DataAction + + + ); + + var radiobuttons = []; + + this.$supportedProperties.push("value", "selectedItem"); + this.$propHandlers["value"] = function(value){ + for (var i = 0; i < radiobuttons.length; i++) { + if (radiobuttons[i].value == value) { + return this.setProperty("selectedItem", radiobuttons[i]); + } + } + return this.setProperty("selectedItem", null); + }; + + var lastSelected; + this.$propHandlers["selectedItem"] = function(rb){ + if (lastSelected) + lastSelected.$uncheck(); + if (!rb) + return; + + rb.$check(); + lastSelected = rb; + + for (var i = 0; i < radiobuttons.length; i++) + radiobuttons[i].setProperty("selectedItem", rb); + }; + + this.$addRadio = function(rb){ + var id = radiobuttons.push(rb) - 1; + + if (!rb.value) + rb.setProperty("value", id); + + if (this.value && rb.value == this.value) + this.setProperty("selectedItem", rb); + else if (rb.checked) + this.setProperty("value", rb.value); + }; + + this.$removeRadio = function(rb){ + radiobuttons.remove(rb); + + if (rb.value === rb.id) + rb.setProperty("value", ""); + + if (rb.selectedItem == rb) + this.setProperty("value", null); + } + + /** + * Sets the current value of this element. + */ + this.setValue = function(value){ + this.setProperty("value", value); + }; + + /** + * Returns the current value of this element. + * @return {String} + */ + this.getValue = function(){ + return this.value; + }; + + this.$draw = function(){ + this.$ext = this.$int = this.$pHtmlNode; + } +}; +apf.$group.prototype = new apf.GuiElement(); + +apf.aml.setElement("group", apf.$group); + + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/elements/remote.js)SIZE(21584)TIME(Mon, 10 Jan 2011 10:53:12 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + + +/** + * Element allowing data synchronization between multiple clients using the same + * application or application part. This element is designed as thecore of + * collaborative application logic for Ajax.org Platform. The children of this + * element specify how the uniqueness of {@link term.datanode data nodes} is determined. By pointing + * models to this element, all changes to their data will be streamed through + * this element to all listening client over a choosen protocol. + * Example: + * This example shows a small application which is editable by all clients that + * have started it. Any change to the data is synchronized to all participants. + * + * + * + * + * + * + * + * + * mike + * ruben + * + * + * + * + * + * + * + * + * + * + * + * Remove + * Rename + * + * + * Login + * + * + * Remarks: + * Although locking is solved in smartbindings it is directly connected + * to remote databindings. When multiple people are working within the same + * application it's important to have a system that prevents corruption of data + * and data loss by either user overwriting records edited during the same period. + * Ajax.org Platform has built in support for optimistic and pessimistic locking + * in smartbindings. For more information please see {@link term.locking}. + * + * Advanced: + * There is a very small theoretical risk that a user initiates and finishes an + * action during the latency period of the rdb communication. Usually this + * latency is no more than 100 to 300ms which is near impossible for such action + * to be performed. Therefor this is deemed acceptable. + * + * Working in a multi user environment usually implies that data has a high + * probability of changing. This might become a problem when syncing offline + * changes after several hours. This should be a consideration for the + * application architect. + * + * Another concern for offline use is the offline messaging feature of certain + * collaborative protocols (i.e. xmpp). In many cases offline rdb messages should + * not be stored after the user has been offline for longer then a certain time. + * For instance 10 minutes. An accumulation of change messages would create a + * serious scaling problem and is not preferred. apf.offline has built in support + * for this type of timeout. By setting the rdb-timeout attribute it is aware + * of when the server has timed out. When this timeout is reached the application + * will reload all its data from the server and discard all offline rdb + * messages before reconnecting to the server. + * + * @attribute {String} transport the name of the teleport element that provides a + * bidirectional connection to (a pool of) other clients. + * + * @see element.auth + * + * @define remote + * @allowchild unique, {any} + * @addnode elements + * + * @define unique Element defining what is unique about a set of data elements. + * This enables remote databindings to point to xml data in the same way on all + * clients. This way changes that happen to these elements are described + * non-ambiguously. The tagName can be replaced by the tagName of the + * {@link term.datanode data node} for which the uniqueness is specified. + * Example: + * This example shows a complex data set and a remote databinding that + * specifies the uniqueness of all nodes concerned. + * + * + * + * + * + * 3564 + * 8104 + * + * + * Mike + * Rik + * + * + * + * + * + * + * + * @attribute {String} transport ID of a Teleport element that is able to serve + * as a transport for RDB message like {@link element.xmpp xmpp} + */ +/** + * @author Mike de Boer (mike AT ajax DOT org) + * @version %I%, %G% + * @since 3.0 + * + * @default_private + * @constructor + * + * @todo Think about wrapping multiple messages in a single call + * @todo Make RDB support different encoding protocols (think REX) + */ +apf.remote = function(struct, tagName){ + this.$init(tagName || "remote", apf.NODE_HIDDEN, struct); + +// this.lookup = {}; +// this.select = []; + this.$sessions = {}; + this.rdbQueue = {}; + this.queueTimer = null; + this.pendingTerminations = {}; +}; + +apf.remote.SESSION_INITED = 0x0001; //Session has not started yet. +apf.remote.SESSION_STARTED = 0x0002; //Session is started +apf.remote.SESSION_TERMINATED = 0x0004; //Session is terminated + +(function(){ + + this.discardBefore = null; + + + this.logprefix = ""; + if (!apf.isO3) { + this.log = function(msg){ + apf.console.log(msg); + } + } + + //1 = force no bind rule, 2 = force bind rule + this.$attrExcludePropBind = apf.extend({ + match : 1 + }, this.$attrExcludePropBind); + + this.$supportedProperties.push("transport"); + + /* @todo move this to the rdb-xmpp transport layer + function checkProtocol(uri) { + if (uri.indexOf("rdb__") === 0) + return "rdb:" + uri.substr(3).replace(/_/g, "/"); + return uri; + } */ + + this.$propHandlers["transport"] = function(value) { + this.transport = typeof value == "object" ? value : self[this["transport"]]; + + + if (!this.transport) { + throw new Error(apf.formatErrorString(0, this, "RDB: Missing transport", + "A transport element with ID '" + value + "' could not be found.")); + } + + + var _self = this; + this.transport.addEventListener("connect", function() { + var uri, oSession; + for (uri in _self.$sessions) { + oSession = _self.$sessions[uri]; + if (oSession.state == apf.remote.SESSION_STARTED) + continue; + + this.join(uri, function(uri, iTime) { + //_self.$startSession(uri, iTime); + }); + } + }); + + this.transport.addEventListener("disconnect", function() { + var uri, oSession; + for (uri in _self.$sessions) { + oSession = _self.$sessions[uri]; + oSession.state = apf.remote.SESSION_TERMINATED; + } + }); + + this.transport.addEventListener("update", function(e){ + _self.$update(e); + }); + + this.transport.addEventListener("join", function(e) { + if (!e.uri) + return; + + var uri = e.uri, + oSession = _self.$sessions[e.uri]; + //if document isn't passed this must be a join request from a peer + if (!e.document) { + + this.log && this.log(_self.logprefix + "Did not receive a document with the join message. \ + Assuming a join request from a peer. If this \ + message originated from the server something \ + has gone wrong."); + + + return _self.dispatchEvent("joinrequest", e); + } + + //Create sesssion if it doesn't exist + if (!oSession) + oSession = _self.createSession(uri, null, null, e.document, e.basetime); + else { + oSession.model.load(e.document); + _self.$startSession(uri, e.basetime); + } + }); + + this.transport.addEventListener("leave", function(e) { + _self.endSession(e.uri); + }); + }; + + this.$update = function(e){ + + var sData = e.message.args ? [e.message] : e.message, + oData = typeof sData == "string" + ? apf.unserialize(sData) + : sData, + oSession = this.$sessions[e.uri], + i = 0, + l = oData.length; + + for (; i < l; i++) + this.$receiveChange(oData[i], oSession, e.annotator); + }; + + this.clear = function(){ + this.$sessions = {}; + } + + /** + * Create a new RDB session based on a URI. + * @param uri + * @param model + * @param xpath + */ + this.createSession = function(uri, model, xpath, doc, iTime){ + this.log && this.log(this.logprefix + "Creating session for " + uri); + + if (!model) + model = this.dispatchEvent("modelfind", {uri: uri}); + if (model) { + delete model.src; + + //@todo if this model is in a session stop that session + } + else + model = new apf.model(); //apf.nameserver.register("model", id, ); + + model.setProperty("remote", this); + model.rdb = this; + model.src = uri; + + var oSession = this.$addSession(uri, model, xpath); + + //We received the document and load it + if (doc) { + model.load(doc); + this.$startSession(uri, iTime); + } + //We did not receive a document and will issue a join request to the server + else { + //If the transport is already connected, let + if (this.transport && this.transport.isConnected()) { + var _self = this; + this.transport.join(uri, function(uri, iTime) { + _self.$startSession(uri, iTime); + }); + } + } + + return oSession; + }; + + /** + * Terminate an RDB session based on a URI. + * @param uri + */ + this.endSession = function(uri) { + if (!this.$sessions || !this.$sessions[uri]) + return; + + var oSession = this.$sessions[uri]; + if (this.transport && this.transport.isConnected() + && oSession.state != apf.remote.SESSION_TERMINATED) + this.transport.leave(uri); + + oSession.state = apf.remote.SESSION_TERMINATED; + + delete this.$sessions[uri]; + }; + + this.$addSession = function(uri, model, xpath){ + delete this.$sessions[uri]; + + return this.$sessions[uri] = { + uri : uri, + model : model, + xpath : xpath, + state : apf.remote.SESSION_INITED + } + }; + + this.$startSession = function(uri, basetime){ + var oSession = this.$sessions[uri]; + + if (!oSession) { + + this.log && this.log(this.logprefix + "Could not find RDB session to start " + uri); + + return false; + } + + oSession.state = apf.remote.SESSION_STARTED; + if (basetime && !oSession.basetime) + oSession.basetime = basetime; + + + this.log && this.log(this.logprefix + "session started: " + uri + ", " + oSession.basetime); + + }; + + this.$queueMessage = function(args, model, qHost){ + if (!qHost.rdbQueue) + qHost.rdbQueue = {}; + + var uri = model.src, + oSession = this.$sessions[uri]; + + + if (!oSession) { + this.log && this.log(this.logprefix + apf.formatErrorString(0, this, "RDB: sending message", + "No RDB session found. Please make sure a session is created for this model: " + + model.serialize())); + return false; + } + + + if (!qHost.rdbQueue[uri]) { + qHost.rdbQueue[uri] = []; + qHost.rdbModel = model; + } + + for (var node, i = 0, l = args.length; i < l; ++i) { + if ((node = args[i]) && node.nodeType) { + //@todo some changes should not be sent to the server + if (args[0] == "setAttribute" && args[2] == "level" + && args[1] == args[1].ownerDocument.documentElement) + return false; //@todo refactor and make configurable + + args[i] = this.xmlToXpath(args[i], model.data); + } + else if (node && node.dataType == apf.ARRAY) { + for (var j = 0; j < node.length; j++) { + if (node[j] && node[j].nodeType) + node[j] = this.xmlToXpath(node[j], model.data); + } + } + } + + qHost.rdbQueue[uri].push({ + uri : uri, + args : args, + currdelta : (new Date()).getUTCTime() - oSession.basetime + }); + }; + + this.$processQueue = function(qHost){ + if (qHost === this) + clearTimeout(this.queueTimer); + if (apf.xmldb.disableRDB) + return; + + var list; + for (var uri in qHost.rdbQueue) { + if (!(list = qHost.rdbQueue[uri]).length) + continue; + + + this.log && this.log(this.logprefix + "Sending " + list.length + " RDB messages to " + uri); + + + if (this.transport) + this.transport.sendUpdate(uri, apf.serialize(list)); + + this.dispatchEvent("rdbsend", { + uri : uri, + message : list + }); + } + + qHost.rdbQueue = {}; + }; + + this.$receiveChange = function(oMessage, oSession, sAnnotator){ + //if (apf.xmldb.disableRDB) { + this.log && this.log(this.logprefix + "Receiving change. disableRDB=" + apf.xmldb.disableRDB); + //return; + //} + + + // @todo apf3.0 implement proper offline support in RDB + if (apf.offline && apf.offline.inProcess == 2) { + //We're coming online, let's queue until after sync + queue.push(oMessage); + + this.log && this.log(this.logprefix + "Not executing incoming change because we're offline. Action is queued."); + return; + } + + if (!oSession && oMessage.uri) + oSession = this.$sessions[oMessage.uri]; + + + if (!oSession) { + + apf.console.error("Could not find session while receiving data for a session with id '" + + oMessage.uri + "'"); + + return; + } + + //if (oMessage.timestamp < this.discardBefore) //@todo discardBefore + //return; + + var model = oSession.model; + if (!model) { + + apf.console.error("Remote Databinding Received: Could not find model while" + + " receiving data for it with identifier '" + oMessage.model + "'"); + + return; + } + if (!model.$at) + model.$at = apf.window.$at; //@todo find better solution to the case of a missing ActionTracker... + + var oError, xmlNode, disableRDB = apf.xmldb.disableRDB; + apf.xmldb.disableRDB = 2; //Feedback prevention + + // Correct timestamp with the session basetime + var time = oSession.basetime + parseInt(oMessage.currdelta); + + + this.log && this.log(this.logprefix + "timestamp comparison (base: " + oSession.basetime + ") : " + + (new Date((new Date()).getUTCTime()).toGMTString()) + + ", " + (new Date(time).toGMTString())); + + + // Undo all items until state is equal to when message was executed on original client. + var aUndos = [], //model.$at.getDone(time), + i = 0, + l = aUndos.length; + if (l) { + for (; i < l; ++i) + aUndos[i].$dontapply = true; + model.$at.undo(l); + } + + //Fetch node based on their xpath + var q = oMessage.args.slice(), + xpath = q[1]; + xmlNode = q[1] = this.xpathToXml(xpath, model.data); + if (xmlNode) { + var action = q.shift(); + + if (action == "addChildNode") + q[3] = this.xpathToXml(q[3], model.data); + else if (action == "appendChild") { + q[1] = typeof q[1] == "string" ? apf.getXml(q[1]) : q[1]; + q[2] = q[2] ? this.xpathToXml(q[2], model.data) : null; + } + else if (action == "moveNode") { + q[1] = this.xpathToXml(q[1], model.data); + q[2] = q[2] ? this.xpathToXml(q[2], model.data) : null; + } + else if (action == "replaceNode") { + q[0] = typeof q[1] == "string" ? apf.getXml(q[1]) : q[1]; + q[1] = xmlNode; + } + else if (action == "removeNodeList") { + var arr = q[0]; + for (var i = 0; i < arr.length; i++) { + arr[i] = this.xpathToXml(arr[i], model.data); + } + } + else if (action == "setValueByXpath") {} + + // pass the action to the actiontracker to execute it + model.$at.execute({ + action : action, + args : q, + annotator: sAnnotator, + message : oMessage, + rdb : true + }); + + this.dispatchEvent("change", { + uri : oMessage.uri, + model : model, + xmlNode : xmlNode, + message : oMessage + }); + } + + else { + oError = new Error(apf.formatErrorString(0, this, + "Remote Databinding Received", "Could not get XML node from \ + model with Xpath '" + xpath + "' for URI '" + oMessage.uri + "' " + apf.serialize(oMessage))); + } + + + if (l) { + model.$at.redo(l); + for (i = 0; i < l; ++i) + delete aUndos[i].$dontapply; + } + + apf.xmldb.disableRDB = disableRDB; + + if (oError) { + apf.console.error(this.logprefix + oError.message) + } + }; + + this.xmlToXpath = apf.xmlToXpath; + this.xpathToXml = apf.xpathToXml; + + this.addEventListener("DOMNodeInsertedIntoDocument", function(e){ + + this.log && this.log(this.logprefix + (this.id + ? "Creating remote [" + this.id + "]" + : "Creating implicitly assigned remote")); + + + + if (apf.offline && apf.offline.enabled) { + var queue = []; + apf.offline.addEventListener("afteronline", function(){ + for (var i = 0, l = queue.length; i < l; i++) + _self.$receiveChange(queue[i]); + + queue.length = 0; + }); + } + + }); + + this.addEventListener("DOMNodeRemovedFromDocument", function(e){ + for (var i = 0, l = this.$sessions.length; i < l; ++i) + this.endSession(this.$sessions[i].uri); + }); +}).call(apf.remote.prototype = new apf.AmlElement()); + +apf.aml.setElement("remote", apf.remote); + + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/elements/rpc.js)SIZE(21108)TIME(Mon, 10 Jan 2011 10:53:12 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + +/** + * Baseclass for rpc in teleport. Modules are available for + * SOAP, XML-RPC, CGI, JSON-RPC and several proprietary protocols. + * + * Example: + * Ajax.org Markup Language + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * //This function is called when the search returns + * function processSearch(data, state, extra){ + * alert(data) + * } + * + * //Execute a search for the product car + * comm.searchProduct('car', 10); + * + * + * + * Example: + * This example shows an rpc element using the xmlrpc protocol. It contains + * two methods which can be called. The return of the first method is handled + * by a javascript function called processSearch. + * + * + * + * + * + * // + * + * + * Example: + * Ajax.org Markup Language + * + * + * + * + * + * + * + * + * + * + * //This function is called when the search returns + * function processSearch(data, state, extra){ + * alert(data) + * } + * + * //Execute a search for the product car + * comm.searchProduct('car', 10); + * + * + * + * Example: + * Ajax.org Markup Language + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * //This function is called when the search returns + * function processSearch(data, state, extra){ + * alert(data) + * } + * + * //Execute a search for the product car + * comm.searchProduct('car', 10); + * + * + * + * Example: + * Ajax.org Markup Language + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * //This function is called when the search returns + * function processSearch(data, state, extra){ + * alert(data) + * } + * + * //Execute a search for the product car + * comm.searchProduct('car', 10); + * + * + * + * Example: + * Ajax.org Markup Language + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * //This function is called when the search returns + * function processSearch(data, state, extra){ + * alert(data) + * } + * + * //Execute a search for the product car + * comm.searchProduct('car', 10); + * + * + * + * @attribute {String} protocol the name of the plugin that is used + * to provide the messages. + * @attribute {Boolean} [multicall] whether the call is stacked until + * purge() is called. + * @attribute {String} [route-server] the location of the proxy script that + * allows for cross domain communication. + * @attribute {String} [http-method] the http method used to send the data. + * This attribute is only used by the cgi protocol. + * Possible values: + * post Used to store large chunks of data (on a resource). + * get Used to retrieve data from a resource. + * delete Used to delete a resource. + * head Returns only the headers. + * put Used to store data at a resource. + * @attribute {String} [method-name] the variable name used to sent the + * name of the method called to the + * server. This attribute is only used + * by the cgi protocol. + * @attribute {String} [soap-xmlns] the url that uniquely identifies the + * xml namespace for the message. This + * attribute is only used by the soap + * protocol. + * @attribute {String} [soap-prefix] the prefix that is paired with the + * message xml namespace. This attribute + * is only used by the soap protocol. + * @define rpc + * @allowchild method + * + * @constructor + * + * @author Ruben Daniels (ruben AT ajax DOT org) + * @author Mike de Boer (mike AT javeline DOT com) + * @version %I%, %G% + * @since 0.4 + * @default_private + */ +apf.rpc = function(struct, tagName){ + this.$init(tagName || "rpc", apf.NODE_HIDDEN, struct); + + if (!this.supportMulticall) + this.multicall = false; + this.stack = {}; + this.urls = {}; + this.$methods = {}; +}; + +(function(){ + this.useHTTP = true; + this.namedArguments = false; + + this["route-server"] = apf.host + "/cgi-bin/rpcproxy.cgi"; + this.autoroute = false; + + this.$auth = false; + + this.$booleanProperties["multicall"] = true; + + this.$supportedProperties.push("protocol", "type", "multicall", "http-method"); + + this.$propHandlers["route-server"] = function(value){ + this.autoroute = value ? true : false; + }; + + //@todo change this to use prototype + this.$propHandlers["protocol"] = function(value){ + if (!value) + return; + + if (!apf[value]) { + + throw new Error(apf.formatErrorString(1025, null, "Teleport baseclass", + "Could not find Ajax.org Teleport RPC Component '" + value + "'", this)); + + return; + } + var _self = this; + // use a timeout, so that these protocols may override default methods + // inherited from http.js and the like. + $setTimeout(function() {_self.implement(apf[value]);}) + }; + + this.$propHandlers["type"] = function(value) { + this.$useXml = (typeof value == "string" && value.toUpperCase() == "XML"); + }; + + /** + * Sets the callback for a method on this object. + * Example: + * + * comm.setCallback("login", function(data, state, extra) { + * alert(data); + * }); + * + * comm.login(user, pass); + * + * @param {String} name the name of the method defined on this object. + * @param {Function} func the function that is called when the rpc method returns. + */ + this.setCallback = function(name, func){ + + if (!this.$methods[name]) + throw new Error(apf.formatErrorString(0, this, "Teleport RPC", + "Trying to set callback: method not found.")); + + + this.$methods[name].callback = func; + }; + + /** + * Sets the target url for a method on this object. + * Example: + * + * comm.setCallback("login", "scripts/login.php"); + * + * comm.login(user, pass); + * + * @param {String} name the name of the method defined on this object. + * @param {String} url the target url of method defined on this object. + */ + this.setUrl = function(name, url) { + + if (!this.$methods[name]) + throw new Error(apf.formatErrorString(0, this, "Teleport RPC", + "Trying to set callback: method not found.")); + + + this.$methods[name].setProperty("url", url); + }; + + this.$convertArgs = function(name, args){ + if (!this.namedArguments) + return Array.prototype.slice.call(args); + + var nodes = this.$methods[name].names; + if (!nodes || !nodes.length) + return {}; + + var value, j, i, l, result = {}; + for (j = 0, i = 0, l = nodes.length; i < l; i++) { + name = nodes[i].name; + value = nodes[i].value; + + if (value) { + value = apf.parseExpression(value); + } + else { + value = args[j++]; + + if (apf.isNot(value) && nodes[i]["default"]) + value = apf.parseExpression(nodes[i]["default"]); + } + + //Encode string optionally + value = apf.isTrue(nodes[i].encoded) + ? encodeURIComponent(value) + : value; + + result[name] = value; + } + + return result; + }; + + function getCallback(node) { + var p, f; + if (typeof node.callback == "string") { + // support objects and namespaced functions + p = node.callback.split("."), + f = self[p.shift()]; + while (f && p.length) + f = f[p.shift()]; + } + else { + f = node.callback; + } + return f || apf.K; + } + + this.call = function(name, args, options){ + var callback, + node = this.$methods[name]; + + if (typeof args[args.length - 1] == "function") { + args = Array.prototype.slice.call(args); //@todo optimize? + callback = args.pop(); + } + else { + callback = getCallback(node); + } + + args = this.$convertArgs(name, args); + + // Set up multicall + if (this.multicall) { + if (!this.stack[this.url]) + this.stack[this.url] = this.getMulticallObject + ? this.getMulticallObject() + : []; + + this.getSingleCall(name, args, this.stack[this.url]) + return true; + } + + // Get Data + var _self = this, + data = options && options.message + ? options.message + : this.createMessage(node["method-name"] || name, args); //function of module + + function pCallback(data, state, extra){ + extra.data = data; + + if (state != apf.SUCCESS) + callback.call(_self, null, state, extra); + else if (_self.isValid && !_self.isValid(extra)) + callback.call(_self, null, apf.ERROR, extra); + else + callback.call(_self, _self.unserialize(extra.data), state, extra); + } + + // Send the request + var auth, + url = apf.getAbsolutePath(this.baseurl || apf.config.baseurl, this.url), + o = apf.extend({ + callback : pCallback, + async : node.async, + userdata : node.userdata, + nocache : (this.nocache === false) ? false : true, + data : data, + useXML : this.$useXml || node.type == "xml", + caching : node.caching, + ignoreOffline : node["ignore-offline"] + }, options); + + + //@todo this shouldn't be in here + if (node.auth && this.$auth) { + if (auth = this.$auth.$credentials) { + o.username = auth.username; + o.password = auth.password; + } + else { + return this.$auth.authRequired(function() { + auth = _self.$auth.$credentials + o.username = auth.username; + o.password = auth.password; + _self.$get(url, o); + }); + } + } + + + return this.$get(url, o); + }; + + /** + * Purge multicalled requests + */ + this.purge = function(callback, userdata, async, extradata){ + + if (!this.stack[this.url] || !this.stack[this.url].length) { + throw new Error(apf.formatErrorString(0, null, "Executing a multicall", + "No RPC calls where executed before calling purge().")); + } + + + // Get Data + var data = this.createMessage("multicall", [this.stack[this.url]]), //function of module + url = apf.getAbsolutePath(this.baseurl || apf.config.baseurl, this.url); + if (extradata) { + for (var vars = [], i = 0; i < extradata.length; i++) { + vars.push(encodeURIComponent(extradata[i][0]) + "=" + + encodeURIComponent(extradata[i][1] || "")) + } + url = url + (url.match(/\?/) ? "&" : "?") + vars.join("&"); + } + + var info = this.$get(url, { + callback : callback, + async : async, + userdata : userdata, + nocache : true, + data : data, + useXML : this.$useXml + }); + + this.stack[this.url] = this.getMulticallObject + ? this.getMulticallObject() + : []; + + //return info[1]; + }; + + this.revert = function(modConst){ + this.stack[modConst.url] = this.getMulticallObject + ? this.getMulticallObject() + : []; + }; + + this.getStackLength = function(){ + return this.stack[this.url] ? this.stack[this.url].length : 0; + }; + + /** + * Loads aml definition + */ + this.$addMethod = function(amlNode){ + if (amlNode.localName != "method"){ + + throw new Error(apf.formatErrorString(0, this, + "Parsing RPC Teleport node", + "Found element which is not a method", this)); + + return false; + } + + var name = amlNode.name, + cb = amlNode.receive || this.receive, + i = 0, + l = amlNode.childNodes.length, + node; + + this[name] = function(){ + return this.call(name, arguments); + }; + + if (cb) + amlNode.callback = cb; + + this.$methods[name] = amlNode; + + if (!amlNode.names) + amlNode.names = []; + for (; i < l; i++) { + node = amlNode.childNodes[i]; + if (node.localName == "param" || node.localName == "variable") //@todo deprecate variable + amlNode.names.push(node); + } + + return true; + }; + + this.$removeMethod = function(amlNode) { + var name = amlNode.name; + delete this[name]; + delete this.$methods[name]; + }; + + this.$setAuth = function(amlNode) { + this.$auth = amlNode; + }; + + /* + this.addEventListener("DOMNodeInserted", function(e){ + var node = e.currentTarget; + if (node.parentNode != this) + return; + + this.register(node); + }); + + this.addEventListener("DOMNodeRemoved", function(e){ + var node = e.currentTarget; + // we support two levels deep: + if (!(node.parentNode == this || node.parentNode.parentNode == this)) + return; + + this.unregister(node); + });*/ + + + this.exec = function(method, args, callback, options){ + if (!options) options = {}; + + //force multicall if needed; + if (options.multicall) + this.forceMulticall = true; + + //Set information later neeed + + if (!this[method]) + throw new Error(apf.formatErrorString(0, null, "Saving/Loading data", + "Could not find RPC function by name '" + method + "' in data " + + "instruction '" + options.instruction + "'")); + + + var props = this.$methods[method]; + + if (options.userdata) + props.userdata = options.userdata; + + if (!this.multicall) + props.callback = callback; //&& this[method].async + + //Call method + var retvalue = this.call(method, args, options); + + if (this.multicall) + return this.purge(callback, "&@^%!@"); //Warning!! @todo Make multicall work with offline + else if (options.multicall) { + this.forceMulticall = false; + return this; + } + + + if (typeof apf.offline != "undefined" && !apf.offline.onLine) + return; + + + //Call callback for sync calls + if (!this.multicall && !props.async && callback) + callback(retvalue, apf.SUCCESS, {tpModule: this}); + }; + + + /* + * Post a form with ajax + * + * @param form form + * @param function callback Called when http result is received + * / + this.submitForm = function(form, callback, callName) { + this.addMethod('postform', callback); + this.urls['postform'] = form.action; + var args = []; + for (var i = 0; i < form.elements.length; i++) { + var name = form.elements[i].name.split("["); + for(var j = 0; j < name.length; j++) { + //Hmm problem with sequence of names... have to get that from the variable sequence... + } + args[] = form.elements[i].value; + } + + this['postform'].apply(this, args); + }; + */ +}).call(apf.rpc.prototype = new apf.Teleport()); + +apf.config.$inheritProperties["baseurl"] = 1; + +apf.aml.setElement("rpc", apf.rpc); + + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/elements/script.js)SIZE(3679)TIME(Mon, 10 Jan 2011 10:53:12 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + + +/** + * element that loads javascript into the application + * either from it's first child or from a file. + * Example: + * + * + * + * Example: + * + * // + * + * @attribute {String} src the location of the script file. + * @addnode global, anyaml + * + * @author Ruben Daniels (ruben AT ajax DOT org) + * @version %I%, %G% + * @since 0.4 + */ +apf.script = function(){ + this.$init("script", apf.NODE_HIDDEN); +}; + +(function(){ + this.$propHandlers["src"] = function(value){ + if (!this.type) + this.type = this.getAttribute("type"); + + if (!this.type || this.type == "text/javascript") { + if (apf.isOpera) { + $setTimeout(function(){ + apf.window.loadCodeFile(apf.hostPath + + value); + }, 1000); + } + else { + apf.window.loadCodeFile(apf.getAbsolutePath(apf.hostPath, + value)); + } + } + else { + var _self = this; + apf.ajax(value, {callback: function(data, state, extra){ + if (state != apf.SUCCESS) { + return apf.console.warn("Could not load script " + value); + } + + _self.$execute(data); + }}); + } + } + + this.$execute = function(code, e){ + if (!this.type || this.type == "text/javascript") { + apf.jsexec(code); + } + else if (this.type.indexOf("livemarkup") > -1 + || this.type.indexOf("lm") > -1) { //@todo this is wrong, it should start in code mode + var func = apf.lm.compile(code, {event: true, parsecode: true, funcglobal: true, nostring: true}); + func(e || {}); + } + } + + this.addEventListener("DOMNodeInserted", function(e){ + if (e.currentTarget.nodeType == 3 || e.currentTarget.nodeType == 4) { + this.$execute(e.currentTarget.nodeValue, apf.isIE && window.event); + } + }); + + //@todo this should use text node insertion + this.addEventListener("DOMNodeInsertedIntoDocument", function(e){ + var nodes = this.childNodes, s = []; + for (var i = 0, l = nodes.length; i < l; i++) { + s[s.length] = nodes[i].nodeValue; + } + + var code = s.join("\n"); + + this.$execute(code); + }); +}).call(apf.script.prototype = new apf.AmlElement()); + +apf.aml.setElement("script", apf.script); + + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/elements/scrollbar.js)SIZE(25545)TIME(Tue, 15 Feb 2011 08:59:13 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + + +//@todo: fix the stuff with all the uppercase variable and function names...wazzup? + +/** + * This library needs to be refactored. + * @constructor + * @private + */ +apf.scrollbar = function(struct, tagName){ + this.$init(tagName || "scrollbar", apf.NODE_VISIBLE, struct); +}; + +(function(){ + this.realtime = true; + this.visible = false; + this.$visible = true; + this.overflow = "scroll"; + + this.$scrollSizeValue = 0; + this.$stepValue = 0.03; + this.$bigStepValue = 0.1; + this.$curValue = 0; + this.$timer = null; + this.$scrollSizeWait; + this.$slideMaxSize; + + this.addEventListener("focus", function(){ + if (this.$host.focus && this.$host.$isWindowContainer !== true) + this.$host.focus(); + }); + + this.$propHandlers["overflow"] = function(value){ + if (value == "auto") { + this.$ext.style.display = "none"; + this.$resize(); + } + else if (value == "scroll") + this.setProperty("visible", true); + } + + this.$propHandlers["for"] = function(value){ + if (value) { + var amlNode = typeof value == "string" ? self[value] : value; + if (!amlNode || !amlNode.$amlLoaded) { + var _self = this; + apf.queue.add("scrollbar" + this.$uniqueId, function(){ + if (!amlNode) { + amlNode = typeof value == "string" ? self[value] : value; + + if (!amlNode) { + throw new Error(apf.formatErrorString(0, _self, + "Attaching scrollbar to element", + "Could not find element to attach scrollbar to: " + value)); + } + } + _self.$attach(amlNode); + }); + } + else + this.$attach(amlNode); + } + } + + this.addEventListener("prop.visible", function(e){ + if (!this.$updating) { + this.$visible = e.value; + } + }); + + this.$detach = function(){ + + } + + //@deprecated + this.attach = function(oHtml, o, scroll_func){ + this.$attach(o); + this.addEventListener("scroll", scroll_func); + } + + this.$getHtmlHost = function(){ + var h = this.$host && (this.$host.$int || this.$host.$container); + return (h && (h.tagName == "BODY" || h.tagName == "HTML") ? (apf.isSafari || apf.isChrome ? document.body : h.parentNode) : h); + } + + this.$getViewPort = function(oHtml){ + return oHtml.tagName == "HTML" || oHtml.tagName == "BODY" ? apf[this.$windowSize]() : oHtml[this.$offsetSize]; + } + this.$getScrollHeight = function(oHtml){ + //add margin + bottom padding + return (apf.isIE && oHtml.lastChild + ? oHtml.lastChild[this.$offsetPos] + + oHtml.lastChild[this.$offsetSize] + + apf.getBox(apf.getStyle(oHtml, "padding"))[2] + + (parseInt(apf.getStyle(oHtml, "marginBottom")) || 0) + : oHtml[this.$scrollSize]); + } + + //oHtml, o, scroll_func + this.$attach = function(amlNode){ + if (!amlNode) + return apf.console.warn("Scrollbar could not connect to amlNode"); + + if (amlNode.host) + amlNode = amlNode.host; + + if (!amlNode.nodeFunc && amlNode.style) { + this.$host = { + empty : true, + $int : amlNode + }; + } + else { + this.$host = amlNode; + } + + //oHtml.parentNode.appendChild(this.$ext); + //if (this.overflow == "scroll") { + // this.$ext.style.display = "block"; + // this.enable(); + //} + + //this.$ext.style.zIndex = 100000; + //this.$ext.style.left = "166px";//(o.offsetLeft + o.offsetWidth) + "px"; + //this.$ext.style.top = "24px";//o.offsetTop + "px"; + //this.$ext.style.height = "160px";//o.offsetHeight + "px"; + + this.$recalc(); + + //this.$viewheight / this.$scrollSizeheight + //if (o.length) { + // this.$caret.style.height = Math.max(5, ((o.limit / o.length) + // * this.$slideMaxSize)) + "px"; + // if (this.$caret.offsetHeight - 4 == this.$slideMaxSize) + // this.$ext.style.display = "none"; + //} + + var scrollFunc = function(e){ + if (e.returnValue === false) + return; + + scrolling = apf.isIE; + var oHtml = _self.$getHtmlHost(); + + var div = (_self.$getScrollHeight(oHtml) - _self.$getViewPort(oHtml)); + if (div) { + if (oHtml[_self.$scrollPos] == 0 && e.delta > 0) { + if (_self.$lastScrollState === 0) + return; + setTimeout(function(){_self.$lastScrollState = 0;}, 300); + } + else if (oHtml[_self.$scrollPos] == _self.$getScrollHeight(oHtml) - oHtml[_self.$offsetSize] && e.delta < 0) { + if (_self.$lastScrollState === 1) + return; + setTimeout(function(){_self.$lastScrollState = 1;}, 300); + } + delete _self.$lastScrollState; + _self.$curValue = (oHtml[_self.$scrollPos] + -1 * e.delta * Math.min(45, apf[_self.$getInner](oHtml)/10)) / div; + _self.setScroll(); + e.preventDefault(); + } + }; + + var _self = this, scrolling; + if (!this.$host.empty) { + amlNode.addEventListener("resize", function(){ //@todo cleanup? + _self.$update(); + }); + if (amlNode.hasFeature(apf.__DATABINDING__)) { + amlNode.addEventListener("afterload", function(){ + _self.$update(); + }); + amlNode.addEventListener("xmlupdate", function(){ + _self.$update(); + }); + } + + amlNode.addEventListener("prop.value", function(){ + _self.$update(); + }); + + if (amlNode.$isTreeArch) { + amlNode.addEventListener("collapse", function(){ + _self.$update(); + }); + amlNode.addEventListener("expand", function(){ + _self.$update(); + }); + } + + if (!this.horizontal) + amlNode.addEventListener("mousescroll", scrollFunc); + } + else { + if (!this.horizontal) { + apf.addEventListener("mousescroll", function(e){ + if (amlNode == e.target || (amlNode == document.documentElement && e.target == document.body)) + scrollFunc(e); + }) + } + } + + var oHtml = _self.$getHtmlHost(); + oHtml.onscroll = function(){ + if (_self.animating || !_self.$visible) + return; + + if (!scrolling) { + var oHtml = _self.$getHtmlHost(); + var m = _self.$getScrollHeight(oHtml) - _self.$getViewPort(oHtml); + var p = oHtml[_self.$scrollPos] / m; + if (Math.abs(_self.$curValue - p) > 1/m) { + _self.$curValue = p; + _self.setScroll(); + } + return false; + } + scrolling = false; + } + + if ("HTML|BODY".indexOf(oHtml.tagName) > -1) { + var lastHeight = oHtml.scrollHeight; + setInterval(function(){ + if (lastHeight != oHtml.scrollHeight) { + lastHeight = oHtml.scrollHeight; + _self.$recalc(); + _self.$update(); + //_self.setScroll(null, true);*/ + } + }, 100); + } + + this.$update(); + + return this; + }; + + this.$resize = function(){ + var oHtml = this.$getHtmlHost(); + if (!oHtml || !oHtml.offsetHeight) + return; + + this.$recalc(); + this.$update(); + this.setScroll(null, true); + } + + this.$recalc = function(){ + var oHtml = this.$getHtmlHost(); + if (!oHtml) return; + + this.$viewheight = this.$getViewPort(oHtml); + this.$scrollSizeheight = this.$viewheight; + this.$scrollSizeWait = 0;//(this.$host.len * COLS)/2; + this.$stepValue = (this.$viewheight / this.$scrollSizeheight) / 20; + this.$bigStepValue = this.$stepValue * 3; + this.$slideMaxSize = this.$caret.parentNode[this.$offsetSize] + - (this.$btnDown ? this.$btnDown[this.$offsetSize] : 0) + - (this.$btnUp ? this.$btnUp[this.$offsetSize] : 0); + } + + //@todo this function is called way too many times + this.$update = function(){ + if (this.animating || !this.$visible) + return; + + var oHtml = this.$getHtmlHost(); + if (!oHtml || !oHtml.offsetHeight) //@todo generalize this to resize for non-ie + return; + + this.$updating = true; + + //Disable scrollbar + var vp = this.$getViewPort(oHtml); + var sz = this.$getScrollHeight(oHtml);//this.$getScrollHeight(oHtml); + + if (vp >= sz) { + if (this.overflow == "scroll") { + this.$caret.style.display = "none"; + this.disable(); + } + else if (this.visible) { + this.hide(); + + //this.$ext.style.display = "none"; + } + //if (this.id == "sbtest") console.log(vp + ":" + sz); + //oHtml.style.overflowY = "visible"; + } + //Enable scrollbar + else { + if (this.overflow == "scroll") { + this.$caret.style.display = "block"; + this.enable(); + } + else if (!this.visible) { + this.show(); + //this.$ext.style.display = "block"; + //this.$caret.style.display = "block"; + } + + if (!this.$slideMaxSize) + this.$recalc(); + if (!this.$slideMaxSize) + return; + + //oHtml.style.overflowY = "scroll"; + + //Set scroll size + this.$caret.style[this.$size] = (Math.max(5, (vp / sz + * this.$slideMaxSize)) - apf[this.$getDiff](this.$caret)) + "px"; + //if (this.$caret.offsetHeight - 4 == this.$slideMaxSize) + //this.$ext.style.display = "none"; + + this.$curValue = oHtml[this.$scrollPos] / (sz - vp); + + var bUpHeight = this.$btnUp ? this.$btnUp[this.$offsetSize] : 0; + this.$caret.style[this.$pos] = (bUpHeight + (apf[this.$getInner](this.$caret.parentNode) + - (bUpHeight * 2) - this.$caret[this.$offsetSize]) * this.$curValue) + "px"; + } + + this.$updating = false; + } + + this.setScroll = function (timed, noEvent){ + if (this.$curValue > 1) + this.$curValue = 1; + if (this.$curValue < 0) + this.$curValue = 0; + + if (this.$curValue == NaN) { + + apf.console.warn("Scrollbar is hidden while scrolling."); + + return; + } + + var bUpHeight = this.$btnUp ? this.$btnUp[this.$offsetSize] : 0; + this.$caret.style[this.$pos] = (bUpHeight + (apf[this.$getInner](this.$caret.parentNode) + - (bUpHeight * 2) - this.$caret[this.$offsetSize]) * this.$curValue) + "px"; + + if (this.animating || !this.$visible) + return; + + if (!noEvent) { + var oHtml, from, viewport, to; + if (this.$host) { + oHtml = this.$getHtmlHost(); + from = oHtml[this.$scrollPos]; + viewport = this.$getViewPort(oHtml); + to = (this.$getScrollHeight(oHtml) - viewport) * this.$curValue; + } + + if (this.$host) + oHtml[this.$scrollPos] = to; + + (this.$host && this.$host.dispatchEvent + ? this.$host + : this).dispatchEvent("scroll", { + timed : timed, + viewportSize : viewport, + scrollPos : to, + scrollSize : this.$getScrollHeight(oHtml), + from : from, + pos : this.pos + }); + } + + this.pos = this.$curValue; + } + + this.scrollUp = function (v){ + if (v > this.$caret[this.$offsetPos]) + return this.$ext.onmouseup(); + this.$curValue -= this.$bigStepValue; + this.setScroll(); + + if (this.$slideFast) { + this.$slideFast.style[this.$size] = Math.max(1, this.$caret[this.$offsetPos] + - this.$btnUp[this.$offsetSize]) + "px"; + this.$slideFast.style[this.$pos] = this.$btnUp[this.$offsetSize] + "px"; + } + } + + this.scrollDown = function (v){ + if (v < this.$caret[this.$offsetPos] + this.$caret[this.$offsetSize]) + return this.$ext.onmouseup(); + this.$curValue += this.$bigStepValue; + this.setScroll(); + + if (this.$slideFast) { + this.$slideFast.style[this.$pos] = (this.$caret[this.$offsetPos] + this.$caret[this.$offsetSize]) + "px"; + this.$slideFast.style[this.$size] = Math.max(1, apf[this.$getInner](this.$caret.parentNode) - this.$slideFast[this.$offsetPos] + - this.$btnUp[this.$offsetSize]) + "px"; + } + } + + this.getPosition = function(){ + return this.pos; + }; + + this.setPosition = function(pos, noEvent){ + this.$curValue = pos; + this.setScroll(null, noEvent); + }; + + this.updatePos = function(){ + if (this.animating || !this.$visible) + return; + + var o = this.$host; + var indHeight = Math.round(Math.max(10, (((o.limit - 1) / o.length) * this.$slideMaxSize))); + this.$caret.style[this.$pos] = (this.$curValue * (this.$slideMaxSize - indHeight) + this.$btnUp[this.$offsetSize]) + "px"; + } + + this.$onscroll = function(timed, perc){ + this.$host[this.$scrollPos] = (this.$host[this.$scrollSize] - this.$host[this.$offsetSize] + 4) * this.$curValue; + } + + this.$draw = function(){ + //Build Skin + this.$getNewContext("main"); + this.$ext = this.$getExternal(); + //this.$ext.style.display = "none"; + + this.$caret = this.$getLayoutNode("main", "indicator", this.$ext); + this.$slideFast = this.$getLayoutNode("main", "slidefast", this.$ext); + this.$btnUp = this.$getLayoutNode("main", "btnup", this.$ext) + this.$btnDown = this.$getLayoutNode("main", "btndown", this.$ext); + + this.horizontal = apf.isTrue(this.$getOption("main", "horizontal")); + + this.$windowSize = this.horizontal ? "getWindowWidth" : "getWindowHeight"; + this.$offsetSize = this.horizontal ? "offsetWidth" : "offsetHeight"; + this.$size = this.horizontal ? "width" : "height"; + this.$offsetPos = this.horizontal ? "offsetLeft" : "offsetTop"; + this.$pos = this.horizontal ? "left" : "top"; + this.$scrollSize = this.horizontal ? "scrollWidth" : "scrollHeight"; + this.$scrollPos = this.horizontal ? "scrollLeft" : "scrollTop"; + this.$getDiff = this.horizontal ? "getWidthDiff" : "getHeightDiff"; + this.$getInner = this.horizontal ? "getHtmlInnerWidth" : "getHtmlInnerHeight"; + this.$eventDir = this.horizontal + ? (apf.isIE || apf.isWebkit ? "offsetX" : "layerX") + : (apf.isIE || apf.isWebkit ? "offsetY" : "layerY"); + this.$clientDir = this.horizontal ? "clientX" : "clientY"; + this.$posIndex = this.horizontal ? 0 : 1; + + this.$startPos = false; + + this.$caret.ondragstart = function(){ + return false + }; + + var _self = this; + if (this.$btnUp) { + this.$btnUp.onmousedown = function(e){ + if (_self.disabled) + return; + + if (!e) + e = event; + this.className = "btnup btnupdown"; + clearTimeout(_self.$timer); + + _self.$curValue -= _self.$stepValue; + + _self.setScroll(); + apf.stopPropagation(e); + + //apf.window.$mousedown(e); + + _self.$timer = $setTimeout(function(){ + _self.$timer = setInterval(function(){ + _self.$curValue -= _self.$stepValue; + _self.setScroll(); + }, 20); + }, 300); + }; + + this.$btnUp.onmouseout = this.$btnUp.onmouseup = function(){ + if (_self.disabled) + return; + + this.className = "btnup"; + clearInterval(_self.$timer); + }; + } + + if (this.$btnDown) { + this.$btnDown.onmousedown = function(e){ + if (_self.disabled) + return; + + if (!e) + e = event; + this.className = "btndown btndowndown"; + clearTimeout(_self.$timer); + + _self.$curValue += _self.$stepValue; + _self.setScroll(); + apf.stopPropagation(e); + + //apf.window.$mousedown(e); + + _self.$timer = $setTimeout(function(){ + _self.$timer = setInterval(function(){ + _self.$curValue += _self.$stepValue; + _self.setScroll(); + }, 20); + }, 300); + }; + + this.$btnDown.onmouseout = this.$btnDown.onmouseup = function(){ + if (_self.disabled) + return; + + this.className = "btndown"; + clearInterval(_self.$timer); + }; + } + + this.$caret.onmousedown = function(e){ + if (_self.disabled) + return; + + if (!e) + e = event; + + var tgt = e.target || e.srcElement; + var pos = tgt != this + ? [tgt.offsetLeft, tgt.offsetTop] //Could be improved + : [0, 0]; + + var relDelta = e[_self.$eventDir] + pos[_self.$posIndex]; + _self.$startPos = relDelta + + (_self.$btnUp ? _self.$btnUp[_self.$offsetSize] : 0); + + if (this.setCapture) + this.setCapture(); + + _self.$setStyleClass(_self.$ext, _self.$baseCSSname + "Down"); + + document.onmousemove = function(e){ + if (!e) + e = event; + //if(e.button != 1) return _self.onmouseup(); + if (_self.$startPos === false) + return false; + + var bUpHeight = _self.$btnUp ? _self.$btnUp[_self.$offsetSize] : 0; + var next = bUpHeight + (e[_self.$clientDir] - _self.$startPos + + (apf.isWebkit ? document.body : document.documentElement)[_self.$scrollPos] + - apf.getAbsolutePosition(_self.$caret.parentNode)[_self.horizontal ? 0 : 1]); // - 2 + var min = bUpHeight; + if (next < min) + next = min; + var max = (apf[_self.$getInner](_self.$caret.parentNode) + - bUpHeight - _self.$caret[_self.$offsetSize]); + if (next > max) + next = max; + //_self.$caret.style.top = next + "px" + + _self.$curValue = (next - min) / (max - min); + _self.setScroll(true); + }; + + document.onmouseup = function(){ + _self.$startPos = false; + if (!_self.realtime) + _self.setScroll(); + + if (this.releaseCapture) + this.releaseCapture(); + + _self.$setStyleClass(_self.$ext, "", [_self.$baseCSSname + "Down"]); + + document.onmouseup = + document.onmousemove = null; + }; + + apf.stopPropagation(e); + //apf.window.$mousedown(e); + + return false; + }; + + this.$ext.onmousedown = function(e){ + if (_self.disabled) + return; + if (!e) + e = event; + clearInterval(_self.$timer); + var offset; + if (e[_self.$eventDir] > _self.$caret[_self.$offsetPos] + _self.$caret[_self.$offsetSize]) { + _self.$curValue += _self.$bigStepValue; + _self.setScroll(true); + + if (_self.$slideFast) { + _self.$slideFast.style.display = "block"; + _self.$slideFast.style[_self.$pos] = (_self.$caret[_self.$offsetPos] + + _self.$caret[_self.$offsetSize]) + "px"; + _self.$slideFast.style[_self.$size] = (apf[_self.$getInner](_self.$caret.parentNode) - _self.$slideFast[_self.$offsetPos] + - _self.$btnUp[_self.$offsetSize]) + "px"; + } + + offset = e[_self.$eventDir]; + _self.$timer = $setTimeout(function(){ + _self.$timer = setInterval(function(){ + _self.scrollDown(offset); + }, 20); + }, 300); + } + else if (e[_self.$eventDir] < _self.$caret[_self.$offsetPos]) { + _self.$curValue -= _self.$bigStepValue; + _self.setScroll(true); + + if (_self.$slideFast) { + _self.$slideFast.style.display = "block"; + _self.$slideFast.style[_self.$pos] = _self.$btnUp[_self.$offsetSize] + "px"; + _self.$slideFast.style[_self.$size] = (_self.$caret[_self.$offsetPos] - _self.$btnUp[_self.$offsetSize]) + "px"; + } + + offset = e[_self.$eventDir]; + _self.$timer = $setTimeout(function(){ + _self.$timer = setInterval(function(){ + _self.scrollUp(offset); + }, 20); + }, 300); + } + }; + + this.$ext.onmouseup = function(){ + if (_self.disabled) + return; + + clearInterval(_self.$timer); + if (!_self.realtime) + _self.setScroll(); + if (_self.$slideFast) + _self.$slideFast.style.display = "none"; + }; + } + + this.$loadAml = function(){ + if (this.overflow == "scroll") + this.disable(); + else { + this.$caret.style.display = "block"; + this.enable(); + } + + this.addEventListener("resize", this.$resize); + this.$update(); + } +}).call(apf.scrollbar.prototype = new apf.Presentation()); +apf.aml.setElement("scrollbar", apf.scrollbar); + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/elements/services.js)SIZE(1488)TIME(Mon, 10 Jan 2011 10:53:12 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + + +/** + * + * @author Ruben Daniels (ruben AT ajax DOT org) + * @version %I%, %G% + * @since 0.4 + */ +apf.services = function(struct, tagName){ + this.$init(tagName || "services", apf.NODE_VISIBLE, struct); + + this.addEventListener("DOMNodeInsertedIntoDocument", function(aml){ + var pNode = this.parentNode; + if (pNode.register) + pNode.register(this); + }); +}; + +apf.services.prototype = new apf.AmlElement(); +apf.aml.setElement("services", apf.services); + + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/elements/skin.js)SIZE(9963)TIME(Mon, 10 Jan 2011 10:53:12 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + +/** + * element specifying the skin of an application. + * Example: + * + * + * + * @attribute {String} name the name of the skinset. + * @attribute {String} src the location of the skin definition. + * @attribute {String} media-path the basepath for the images of the skin. + * @attribute {String} icon-path the basepath for the icons used in the elements using this skinset. + * @allowchild style, presentation + * @addnode global, anyaml + * + * @author Ruben Daniels (ruben AT ajax DOT org) + * @version %I%, %G% + * @since 0.4 + */ +apf.skin = function(struct, tagName){ + this.$init(tagName || "skin", apf.NODE_HIDDEN, struct); +}; +apf.aml.setElement("skin", apf.skin); + +(function(){ + this.$parsePrio = "002"; + this.$includesRemaining = 0; + + this.$propHandlers["src"] = function(value){ + if (value.trim().charAt(0) == "<") { + apf.skins.Init(apf.getXml(value), this, this.$path); + return; + } + + this.$path = apf.getAbsolutePath(apf.hostPath, value) + getSkin.call(this, this.$path); + } + + this.$propHandlers["name"] = function(value){ + if (!this.attributes.getNamedItem("src")) { + this.$path = apf.getAbsolutePath(apf.hostPath, value) + "/index.xml"; + getSkin.call(this, this.$path); + } + } + + /** + * @private + */ + function checkForAmlNamespace(xmlNode){ + if (!xmlNode.ownerDocument.documentElement) + return false; + + var nodes = xmlNode.ownerDocument.documentElement.attributes; + for (var found = false, i=0; i' + xmlString + ''); + apf.mergeXml(newPart, xmlNode, {beforeNode: includeNode}); + includeNode.parentNode.removeChild(includeNode); + + var includeNodes = $xmlns(newPart, "include", apf.ns.aml); + _self.$includesRemaining += includeNodes.length; + if (includeNodes.length) { + var path = apf.getDirname(extra.url); + for (var i = 0; i < includeNodes.length; i++) { + loadSkinInclude.call(_self, includeNodes[i], xmlNode, path); + } + } + else if (--_self.$includesRemaining == 0) { + + apf.console.info("Loading of " + xmlNode[apf.TAGNAME].toLowerCase() + " skin done from file: " + extra.url); + + + finish.call(_self, xmlNode); + } + } + }); + } + + + function loadSkinFile(path){ + + apf.console.info("Loading include file: " + path); + + + var _self = this; + + apf.getData( + + path, { + + type : "skin", + + callback: function(xmlString, state, extra){ + if (state != apf.SUCCESS) { + var oError = new Error(apf.formatErrorString(1007, + _self, "Loading skin file", "Could not load skin file '" + + (path || _self.src) + + "'\nReason: " + extra.message)); + + if (extra.tpModule.retryTimeout(extra, state, null, oError) === true) + return true; + + + if (this.autoload) { + apf.console.warn("Could not autload skin."); + return finish.call(_self); + } + + + throw oError; + } + + //if (!apf.supportNamespaces) + xmlString = xmlString.replace(/\<\!DOCTYPE[^>]*>/, "") + .replace(/^[\r\n\s]*/, "") //.replace(/ /g, " ") + .replace(/xmlns\=\"[^"]*\"/g, ""); + + if (!xmlString) { + throw new Error(apf.formatErrorString(0, _self, + "Loading skin", + "Empty skin file. Maybe the file does not exist?", _self)); + } + + var xmlNode = apf.getXml(xmlString);//apf.getAmlDocFromString(xmlString); + + + checkForAmlNamespace(xmlNode); + + + if (!xmlNode) { + throw new Error(apf.formatErrorString(0, _self, + "Loading skin", + "Could not parse skin. Maybe the file does not exist?", _self)); + } + + xmlNode.setAttribute("filename", extra.url); + + + var includeNodes = $xmlns(xmlNode, "include", apf.ns.aml); + _self.$includesRemaining += includeNodes.length; + if (includeNodes.length) { + var path = apf.getDirname(extra.url); + for (var i = 0; i < includeNodes.length; i++) { + loadSkinInclude.call(_self, includeNodes[i], xmlNode, path); + } + return; + } + else + + { + + apf.console.info("Loading of " + xmlNode[apf.TAGNAME].toLowerCase() + " skin done from file: " + extra.url); + + + finish.call(_self, xmlNode); + } + }, + async : true, + ignoreOffline : true + }); + } + + //@todo use mutation events to update + this.addEventListener("DOMNodeInsertedIntoDocument", function(e){ + if (this.src || this.name) + return; + + apf.skins.Init(this.$aml || this); + + //@todo implied skin + /*if (this.parentNode && this.parentNode.parentNode) { + var name = "skin" + Math.round(Math.random() * 100000); + q.parentNode.setAttribute("skin", name); + apf.skins.skins[name] = {name: name, templates: {}}; + apf.skins.skins[name].templates[q.parentNode[apf.TAGNAME]] = q; + }*/ + }); +}).call(apf.skin.prototype = new apf.AmlElement()); + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/elements/slider.js)SIZE(32197)TIME(Mon, 10 Jan 2011 10:53:12 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + +/** + * Element allowing the user to select a value from a range of + * values between a minimum and a maximum value. + * Example: + * This example shows a slider that influences the position of a video. The + * value attribute of the slider is set using property binding. The square + * brackets imply a {@link term.propertybinding bidirectional binding}. + * + * + * Unsupported video codec. + * + * + * play + * pause + * + * + * + * Example: + * This example shows two slider which lets the user indicate a value in a form. + * + * + * + * + * How would you grade the opening hours of the helpdesk + * + * How soon will you make your buying decision + * + * + * + * @constructor + * @define slider, range + * @allowchild {smartbinding} + * @addnode elements + * + * @author Ruben Daniels (ruben AT ajax DOT org) + * @version %I%, %G% + * @since 0.9 + * + * @inherits apf.StandardBinding + * @inherits apf.XForms + * + * @binding value Determines the way the value for the element is retrieved + * from the bound data. + * Example: + * Sets the slider position based on data loaded into this component. + * + * + * + * + * + * + * Example: + * A shorter way to write this is: + * + * + * + * + * + * + */ +apf.range = function(struct, tagName){ + this.$init(tagName || "range", apf.NODE_VISIBLE, struct); +}; + +apf.slider = function(struct, tagName){ + this.$init(tagName || "slider", apf.NODE_VISIBLE, struct); +}; + +(function(){ + this.implement( + + apf.DataAction + + + ); + + this.$focussable = true; // This object can get the focus + + this.$dragging = false; + this.$onlySetXml = false; + + /**** Properties and Attributes ****/ + this.disabled = false; // Object is enabled + this.realtime = true; + this.balloon = true; + this.value = 0; + this.mask = "%"; + this.min = 0; + this.max = 1000001; + this.isOpened = false; + this.$hasTSlider = false; + + this.$supportedProperties.push("step", "mask", "min", "max", "slide", + "value", "markers"); + + this.$booleanProperties["realtime"] = true; + this.$booleanProperties["markers"] = true; + this.$booleanProperties["balloon"] = true; + + /** + * @attribute {Boolean} realtime whether the slider updates it's value realtime, + * or just when the user stops dragging. + * @attribute {Boolean} balloon whether to show the balloon with extra + * information on the position of the slider. + * Default is true when the skin supports it. + * @attribute {Number} step specifying the step size of a discreet slider. + * Example: + * + * How much money do you make annualy. + * + * + */ + this.$propHandlers["step"] = function(value){ + this.step = parseInt(value) || 0; + + if (!this.$hasLayoutNode("marker")) + return; + + if (!this.slider) + this.slideDiscreet = true; + }; + + + /** + * @attribute {Boolean} markers whether to display a marker at each discrete step. + */ + this.$propHandlers["markers"] = function(value){ + //Remove Markers + var i, + markers = this.oMarkers.childNodes; + for (i = markers.length - 1; i >= 0; i--) { + if (markers[i].tagName == "U" && markers[i].nodeType == 1) //small hack + apf.destroyHtmlNode(markers[i]); + } + + if (!this.step && this.$aml) + this.step = parseInt(this.getAttribute("step")) || 0; + + //Add markers + if (value && this.step) { + var pos, o, + nodes = [], + max = (this.max == 1000001) ? 1 : this.max, + + count = (max - this.min) / this.step, + prop = this.$dir == "horizontal" ? "left" : "top", + size = this.$dir == "horizontal" + ? this.oSlider.offsetWidth - this.oKnob.offsetWidth + - apf.getWidthDiff(this.oSlider) + : this.oSlider.offsetHeight - this.oKnob.offsetHeight; + + for (i = 0; i < count + 1; i++) { + this.$getNewContext("marker"); + o = this.$getLayoutNode("marker"); + pos = Math.max(0, (i * (1 / (count)))); + o.setAttribute("style", prop + ":" + Math.round(pos * size) + "px"); + nodes.push(o); + } + apf.insertHtmlNodes(nodes, this.oMarkers); + } + }; + + this.$resize = function(){ + this.$propHandlers.value.call(this, this.value); + + var count = (this.max - this.min) / this.step; + if (!count) return; + + var pos, i, + prop = this.$dir == "horizontal" ? "left" : "top", + size = this.$dir == "horizontal" + ? this.oSlider.offsetWidth - this.oKnob.offsetWidth + - apf.getWidthDiff(this.oSlider) + : this.oSlider.offsetHeight - this.oKnob.offsetHeight, + nodes = this.oMarkers.getElementsByTagName("U");//small hack + for (i = nodes.length - 1; i >= 0; i--) { + pos = Math.max(0, i * (1 / count)); + nodes[i].style[prop] = Math.round(pos * size) + "px"; + } + }; + + /** + * @attribute {String} mask a pipe '|' seperated list of strings that are + * used as the caption of the slider when their connected value is picked. + * Or set mask to # to display the numerical value of the position or use % + * to display the position as percentage of the total. + * Example: + * + * How big is your cat? + * + * + */ + this.$propHandlers["mask"] = function(value){ + if (!value) + this.mask = "%"; + + if (!this.mask.match(/^(%|#.?#*)$/)) { + this.mask = value.split(/\||;/); + } + }; + + /** + * @attribute {String} progress a value between 0 and 1 which is visualized + * inside the slider. This can be used to show a progress indicator for + * the download of movies or other media. + * Example: + * + * + * Unsupported video codec. + * + * + * + * + */ + this.$propHandlers["progress"] = function(value){ + if (!this.oProgress) { + this.oProgress = + apf.insertHtmlNode(this.$getLayoutNode("progress"), + this.$getLayoutNode("main", "progress", this.$ext)); + } + + this.oProgress.style.width = ((value || 0) * 100) + "%"; + }; + + /** + * @attribute {Number} min the minimal value the slider can have. This is + * the value that the slider has when the grabber is at it's begin position. + */ + this.$propHandlers["min"] = function(value){ + this.min = parseInt(value) || 0; + if (this.markers) + this.$propHandlers["markers"].call(this, this.markers); + if (this.value < this.min || this.value != this.$value) { //@todo apf3.0 + this.value = -1; //@todo apf3.0 + this.setProperty("value", this.$value); + } + }; + + /** + * @attribute {Number} max the maximal value the slider can have. This is + * the value that the slider has when the grabber is at it's end position. + */ + this.$propHandlers["max"] = function(value){ + this.max = parseInt(value) || 1; + if (this.markers) + this.$propHandlers["markers"].call(this, this.markers); + if (this.value > this.min || this.value != this.$value) { //@todo apf3.0 + this.value = -1; //@todo apf3.0 + this.setProperty("value", this.$value); + } + }; + + /** + * @attribute {String} slide the way the grabber can be handled + * Possible values: + * normal the slider moves over a continuous space. + * discrete the slider's value is discrete but the grabber moves over a continuous space and only snaps when the user lets go of the grabber. + * snap the slider snaps to the discrete values it can have while dragging. + * Remarks: + * Discrete space is set by the step attribute. + */ + this.$propHandlers["slide"] = function(value){ + this.slideDiscreet = value == "discrete"; + this.slideSnap = value == "snap"; + }; + + /** + * @attribute {String} value the value of slider which is represented in + * the position of the grabber using the following + * formula: (value - min) / (max - min) + */ + this.$propHandlers["value"] = function(value, prop, force, animate){ + if (!this.$dir || this.$onlySetXml) + return; //@todo fix this + + if (this.$dragging && !force && !this.realtime) + return; + + this.$value = value; + var value = Math.max(this.min, Math.min(this.max, value)) || 0; + + var max, min, offset, + _self = this, + multiplier = this.max == this.min + ? 0 + : (value - this.min) / (this.max - this.min); + + if (this.$dir == "horizontal") { + max = (this.oSlider.offsetWidth + - apf.getWidthDiff(this.oSlider)) + - this.oKnob.offsetWidth; + min = parseInt(apf.getBox( + apf.getStyle(this.oSlider, "padding"))[3]); + + offset = Math.round(((max - min) * multiplier) + min); + + if (animate) { + apf.tween.single(this.oKnob, { + type : 'left', + steps : 5, + interval: 10, + from : this.oKnob.offsetLeft, + to : offset, + anim : apf.tween.NORMAL, + oneach : function(oNode) { + if (_self.oFill) + _self.oFill.style.width = (oNode.offsetLeft + 3) + "px"; + } + }); + } + else { + this.oKnob.style.left = offset + "px"; + if (this.oFill) + this.oFill.style.width = (offset + 3) + "px"; + } + } + else { + max = (this.oSlider.offsetHeight + - apf.getHeightDiff(this.oSlider)) + - this.oKnob.offsetHeight; + min = parseInt(apf.getBox( + apf.getStyle(this.oSlider, "padding"))[0]); + + offset = (((max - min) * (1 - multiplier)) + min); + + if (animate) { + apf.tween.single(this.oKnob, { + type : 'top', + steps : 5, + interval: 10, + from : this.oKnob.offsetTop, + to : offset, + anim : apf.tween.NORMAL, + oneach : function(oNode) { + if (_self.oFill) + _self.oFill.style.height = (oNode.offsetTop + 3) + "px"; + } + }); + } + else { + this.oKnob.style.top = offset + "px"; + if (this.oFill) + this.oFill.style.height = (offset + 3) + "px"; + } + } + + if (this.oLabel) { + var mask = typeof this.mask == "string" ? this.mask.split(".") : this.mask; + + switch(mask[0]) { + case "%": + var inputValue = Math.round(multiplier * 100) + "%"; + break; + case "#": + var tempValue = (this.step + ? (Math.round(value / this.step) * this.step) + : value); + var inputValue = new Number(tempValue).toFixed(mask[1] ? mask[1].length : 0); + break; + default: + var inputValue = this.mask[Math.round(value - this.min) / (this.step || 1)]; + break; + } + + this.setProperty("valuemask", inputValue); + + if (this.oLabel.nodeValue !== null) { + this.oLabel.nodeValue = inputValue; + } + else { + this.oLabel.value = inputValue; + } + /* + //Percentage + if (this.mask == "%") { + this.oLabel.nodeValue = Math.round(multiplier * 100) + "%"; + } + //Number + else if (this.mask == "#") { + //status = value; + this.oLabel.nodeValue = this.step + ? (Math.round(value / this.step) * this.step) + : value; + } + //Lookup + else { + this.oLabel.nodeValue = this.mask[Math.round(value - this.min) + / (this.step || 1)]; //optional floor ?? + }*/ + } + }; + + this.$setLabelValue = function(value) { + var mask = this.mask.split("."); + + switch(mask[0]) { + case "%": + value = parseInt(value) * (this.max - this.min) / 100; + break; + case "#": + value = new Number(value).toFixed(mask[1] ? mask[1].length : 0); + break; + } + + this.$propHandlers["value"].call(this, value); + }; + + /**** Public methods ****/ + + + + /** + * Sets the value of this element. This should be one of the values + * specified in the values attribute. + * @param {String} value the new value of this element + */ + this.setValue = function(value) { + this.setProperty("value", value, false, true); + }; + + /** + * Returns the current value of this element. + * @return {String} + */ + this.getValue = function() { + return this.step + ? Math.round(parseInt(this.value) / this.step) * this.step + : this.value; + }; + + + + /**** Keyboard support ****/ + + + this.addEventListener("keydown", function(e) { + var key = e.keyCode; + var ctrlKey = e.ctrlKey; + + switch (key) { + case 37: + //LEFT + if (this.$dir != "horizontal") + return; + this.change(this.value - (ctrlKey ? 0.01 : 0.1)); + break; + case 38: + //UP + if (this.$dir != "vertical") + return; + this.change(this.value + (ctrlKey ? 0.01 : 0.1)); + break; + case 39: + //RIGHT + if (this.$dir != "horizontal") + return; + this.change(this.value + (ctrlKey ? 0.01 : 0.1)); + break; + case 40: + //DOWN + if (this.$dir != "vertical") + return; + this.change(this.value - (ctrlKey ? 0.01 : 0.1)); + break; + case 13: + //ENTER + if (this.$hasTSlider) + this.$setLabelValue(this.oLabel.value); + break; + default: + return; + } + + return false; + }, true); + + + this.slideToggle = function(e, userAction) { + if (!e) e = event; + if (userAction && this.disabled) + return; + + if (this.isOpened) + this.slideUp(); + else + this.slideDown(e); + }; + + this.slideDown = function(e) { + if (this.dispatchEvent("slidedown") === false) + return false; + + this.isOpened = true; + + this.oSliderContainer.style.display = "block"; + this.oSliderContainer.style[apf.supportOverflowComponent + ? "overflowY" + : "overflow"] = "hidden"; + + this.oSliderContainer.style.display = "block"; + var scWidth = this.oSliderContainer.offsetWidth; + var scHeight = this.oSliderContainer.offsetHeight; + var kWidth = this.oKnob.offsetWidth; + var diff = apf.getDiff(this.oSliderContainer); + var right = scWidth - this.$ext.offsetWidth; + this.$setLabelValue(this.oLabel.value); + this.oSliderContainer.style.display = "none"; + + //Place grabber in the same position as button + if(this.$hasTSlider) { + right -= scWidth - parseInt(this.oKnob.style.left) - kWidth; + } + + this.oSliderContainer.style.display = ""; + this.$setStyleClass(this.$ext, this.$baseCSSname + "Down"); + + var _self = this; + apf.popup.show(this.$uniqueId, { + x : -1 * right, + y : this.$ext.offsetHeight, + animate : true, + ref : this.$ext, + width : scWidth + 1, + height : scHeight - diff[1], + callback: function(container) { + container.style[apf.supportOverflowComponent + ? "overflowY" + : "overflow"] = "hidden"; + } + }); + }; + + this.slideUp = function() { + if (!this.isOpened) return false; + if (this.dispatchEvent("slideup") === false) return false; + + this.isOpened = false; + if (this.selected) { + var htmlNode = apf.xmldb.findHtmlNode(this.selected, this); + if (htmlNode) this.$setStyleClass(htmlNode, '', ["hover"]); + } + + this.$setStyleClass(this.$ext, "", [this.$baseCSSname + "Down"]); + apf.popup.hide(); + return false; + }; + + this.addEventListener("afterselect", function(e) { + if (!e) e = event; + + this.slideUp(); + if (!this.isOpened) + this.$setStyleClass(this.$ext, "", [this.$baseCSSname + "Over"]); + }); + + this.$blur = function() { + this.slideUp(); + + if (!this.isOpened) + this.$setStyleClass(this.$ext, "", [this.$baseCSSname + "Over"]) + + this.$setStyleClass(this.$ext, "", [this.$baseCSSname + "Focus", this.$baseCSSname + "Down"]); + + if (this.$hasTSlider) + this.$setLabelValue(this.oLabel.value); + }; + + this.$focus = function(){ + this.$setStyleClass(this.oFocus || this.$ext, this.$baseCSSname + "Focus"); + } + + /**** Init ****/ + + this.$draw = function(){ + this.$getNewContext("main"); + + //Build Main Skin + this.$ext = this.$getExternal(null, null, function(oExt) { + var oButton = this.$getLayoutNode("main", "button", oExt); + if (oButton) { + oButton.setAttribute("onmousedown", + 'apf.lookup(' + this.$uniqueId + ').slideToggle(event, true);'); + } + }); + + this.$hasTSlider = this.$hasLayoutNode("container"); + if (this.$hasTSlider) { + this.$getNewContext("container"); + this.oSliderContainer = this.$getExternal("container"); + + //Allows to select text in IE + this.$ext.onselectstart = function(e){ + if (!e) e = event; + e.cancelBubble = true; + } + } + + this.oLabel = this.$getLayoutNode("main", "status", this.$ext); + this.oFill = this.$getLayoutNode("main", "fill", this.$ext); + this.oBalloon = this.$getLayoutNode("main", "balloon", this.$ext); + + if (this.$hasTSlider) { + this.oMarkers = this.$getLayoutNode("container", "markers", this.oSliderContainer); + this.oKnob = this.$getLayoutNode("container", "grabber", this.oSliderContainer); + this.oSlider = this.$getLayoutNode("container", "slider", this.oSliderContainer); + + //Set up the popup + this.$pHtmlDoc = apf.popup.setContent(this.$uniqueId, this.oSliderContainer, apf.skins.getCssString(this.skinName)); + document.body.appendChild(this.oSliderContainer); + } + else { + this.oMarkers = this.$getLayoutNode("main", "markers", this.$ext); + this.oKnob = this.$getLayoutNode("main", "slider", this.$ext); + this.oSlider = this.$ext; + } + + this.$dir = this.$getOption("main", "direction") || "horizontal"; + + this.oKnob.style.left = (parseInt(apf.getBox( + apf.getStyle(this.oSlider, "padding"))[3])) + "px"; + + var _self = this; + function prepareKnob(e) { + this.x = (e.clientX || e.x); + this.y = (e.clientY || e.y); + this.stX = this.offsetLeft; + this.siX = this.offsetWidth + this.stY = this.offsetTop; + this.siY = this.offsetheight + this.startValue = _self.value; + + if (_self.$dir == "horizontal") { + this.max = _self.oSlider.offsetWidth + - apf.getWidthDiff(_self.oSlider) + - this.offsetWidth; + this.min = parseInt(apf.getBox( + apf.getStyle(_self.oSlider, "padding"))[3]); + } + else { + this.max = _self.oSlider.offsetHeight + - apf.getHeightDiff(_self.oSlider) + - this.offsetHeight; + this.min = parseInt(apf.getBox( + apf.getStyle(_self.oSlider, "padding"))[0]); + } + } + + function getKnobValue(o, e, slideDiscreet){ + var to = (_self.$dir == "horizontal") + ? (e.clientX || e.x) - o.x + o.stX + : (e.clientY || e.y) - o.y + o.stY; + to = (to > o.max ? o.max : (to < o.min ? o.min : to)); + var value = (((to - o.min) * 100 / (o.max - o.min) / 100) + * (_self.max - _self.min)) + _self.min; + + value = slideDiscreet + ? (Math.round(value / _self.step) * _self.step) + : value; + value = (_self.$dir == "horizontal") ? value : 1 - value; + + return value; + } + + this.oKnob.onmousedown = function(e){ + if (_self.disabled) + return false; + + //@todo use start action here + + e = e || window.event; + document.dragNode = this; + + prepareKnob.call(this, e); + + _self.$setStyleClass(this, "btndown", ["btnover"]); + + apf.dragMode = true; + + _self.$dragging = true; + + if (_self.balloon && _self.oBalloon) { + _self.oBalloon.style.display = "block"; + _self.oBalloon.style.left = (_self.oKnob.offsetLeft + - (_self.oBalloon.offsetWidth + - _self.oKnob.offsetWidth)/2) + "px"; + } + + var startValue = this.value; + document.onmousemove = function(e){ + e = e || window.event; + var o = this.dragNode; + if (!o) { + apf.dragMode = document.onmousemove = document.onmouseup = null; + return; //? + } + + var knobValue = getKnobValue(o, e, _self.slideSnap); + if (_self.realtime) { + //_self.value = -1; //reset value //@todo apf3.0 please fix this to be not needed. just set a flag to not do change detect + if (_self.slideDiscreet) { + this.$onlySetXml = true;//blrgh.. + var rValue = _self.change(Math.round((knobValue / _self.step) + * _self.step)); + this.$onlySetXml = false; + + if (rValue !== false) { + _self.$propHandlers["value"].call(_self, knobValue, + "value", true); + } + } + else { + _self.change(knobValue); + } + } + else { + _self.$propHandlers["value"].call(_self, knobValue, "value", true); + } + + if (_self.balloon && _self.oBalloon) { + _self.oBalloon.style.left = (_self.oKnob.offsetLeft + - (_self.oBalloon.offsetWidth + - _self.oKnob.offsetWidth)/2) + "px"; + } + + /*clearTimeout(timer); + if (new Date().getTime() - lastTime > 20) { + _self.$propHandlers["value"].call(_self, knobValue, true); + lastTime = new Date().getTime(); + } + else { + timer = $setTimeout(function(){ + _self.$propHandlers["value"].call(_self, knobValue, true); + lastTime = new Date().getTime(); + }, 20); + }*/ + } + + document.onmouseup = function(e){ + var o = this.dragNode; + this.dragNode = null; + + o.onmouseout(); + + _self.$dragging = false; + + var knobValue = getKnobValue(o, e || window.event, + _self.slideDiscreet || _self.slideSnap); + + _self.value = startValue; + var rValue = startValue != knobValue + ? _self.change(knobValue, true) : false; + + if (rValue !== false && _self.slideDiscreet) + _self.$propHandlers["value"].call(_self, knobValue, "value", true); + + apf.dragMode = false; + document.onmousemove = + document.onmouseup = null; + + if (_self.balloon && _self.oBalloon) { + _self.oBalloon.style.left = (_self.oKnob.offsetLeft + - (_self.oBalloon.offsetWidth + - _self.oKnob.offsetWidth)/2) + "px"; + + $setTimeout(function(){ + if (apf.isIE) { + _self.oBalloon.style.display = "none"; + } + else { + apf.tween.single(_self.oBalloon, { + type : "fade", + from : 1, + to : 0, + steps : 5, + onfinish : function(){ + _self.oBalloon.style.display = "none"; + _self.oBalloon.style.opacity = 1; + } + }) + } + }, _self.slideDiscreet ? 200 : 0); + } + }; + //event.cancelBubble = true; + return false; + }; + + this.oKnob.onmouseup = this.oKnob.onmouseover = function(){ + if (document.dragNode != this) + _self.$setStyleClass(this, "btnover", ["btndown"]); + }; + + this.oKnob.onmouseout = function(){ + if (document.dragNode != this) + _self.$setStyleClass(this, "", ["btndown", "btnover"]); + }; + + this.oSlider.onmousedown = function(e) { + if (_self.disabled) return false; + e = e || window.event; + + var o = _self.oKnob; + if ((e.srcElement || e.target) != o) { + var p = apf.getAbsolutePosition(o); + prepareKnob.call(o, { + x : p[0] + o.offsetWidth / 2, + y : p[1] + o.offsetHeight / 2 + }); + var value = getKnobValue(o, e, _self.slideDiscreet || _self.slideSnap); + _self.$propHandlers["value"].call(_self, getKnobValue(o, e, _self.slideDiscreet), "value", true, true); + _self.setValue(value); + } + }; + + + if (apf.isIphone) + apf.iphone.linkEvents(this.oKnob); + + }; + + this.$loadAml = function(){ + if (this.max == 1000001) + this.setProperty("max", 1); + //this.$propHandlers["value"].call(this, this.value); + + //@todo this goes wrong with skin switching. smartbindings is called again. + + + apf.layout.setRules(this.oSlider, "knob", + "apf.all[" + this.$uniqueId + "].$resize()", true); + apf.layout.queue(this.$ext); + + }; + + this.$destroy = function(){ + this.oKnob.onmousedown = + this.oKnob.onmouseup = + this.oKnob.onmouseover = + this.oKnob.onmouseout = null; + + + apf.layout.removeRule(this.$ext, "knob"); + + }; + + + + +}).call(apf.slider.prototype = new apf.StandardBinding()); + + +apf.range.prototype = apf.slider.prototype; + +apf.aml.setElement("range", apf.range); +apf.aml.setElement("slider", apf.slider); + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/elements/slideshow.js)SIZE(47089)TIME(Mon, 10 Jan 2011 10:53:12 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + +/** + * This element is used for viewing images. It's possible to add thumbnail and + * description to each of them. You can select a displayed image in several ways. + * With a mouse buttons, the mousewheel or keyboard arrows. The thumbnails allow + * the user to quickly select the image from the displayed list. + * + * Remarks: + * The language variables possible to use of this component: + * + * + * + * Loading... + * Default title + * Picture + * of + * + * + * + * + * + * Example: + * Slideshow component with 3 pictures. Each image has its own thumbnail + * and description. A new image is shown every 5 seconds. + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * @attribute {String} title the description of the picture on the slide. + * Default is "number". + * Possible values: + * number the description contains only slide number on a list. + * text the description contains only text added by creator. + * number+text the description contains slide number on a list and text + * added by creator. + * + * @attribute {Number} delay the delay between slides when the play + * button is pressed. Default is 5 seconds. + * @attribute {Number} thumbheight the vertical size of thumbnail bar. + * Default is 50px. + * @attribute {String} defaultthumb the thumbnail shown when a slide doesn't + * have one. + * @attribute {String} defaultimage the image shown when a slide doesn't have + * an image. + * @attribute {String} defaulttitle the text shown when a slide doesn't have + * a description. + * @attribute {String} loadmsg this text displayd while the picture is + * loading. + * @attribute {Boolean} scalewidth whether the width of the thumbnail is + * scaled relative to its height. + * + * @inherits apf.Cache + * @inherits apf.MultiselectBinding + * + * @author Lukasz Lipinski + * @version %I%, %G% + * + * @define slideshow + * @addnode elements + * + * @define bindings + * @allowchild src, title, thumb + * + * @binding src Determines the url to image file. + * @binding title Determines the image description text. + * @binding thumb Determines the url to thumbnail file. + */ +apf.slideshow = function(struct, tagName){ + this.$init(tagName || "slideshow", apf.NODE_VISIBLE, struct); + + this.title = "number"; + this.thumbheight = 50; + this.loadmsg = null; + this.defaultthumb = null; + this.defaultimage = null; + this.defaulttitle = null; + this.delay = 5; + this.scalewidth = false; + + /** + * Contains current selected node, next node to select in two directions + * and last selected node + */ + this.previous = null, + this.next = null, + this.current = null, + this.last = null; + + /** + * Determinates that component is currently loading any image or not + */ + this.inuse = false; + + /** + * Determinates that slideshow is playing or not + */ + this.play = false; + + /** + * Determinates that thumbnail bar is displayed or not + */ + this.thumbnails = true; + + /** + * Keep the last selected image which is saved there when + * slideshow component is busy + */ + this.lastChoose = null; + + /** + * Height of title container + */ + this.$vSpace = 210; + this.$hSpace = 150; + + /* TIMERS */ + this.tmrShowLast = null; //timer for showLast function + this.tmrIsResized = null; + this.tmrPlay = null; + this.tmrKeyDown = null; + this.tmrOnScroll = null; + this.tmrZoom = null; + this.tmrHoverDelay = null; + + /* Used in zooming to keep size of scaled image */ + this.$imageWidth; + this.$imageHeight; + + this.viewPortWidth = 0; + this.viewPortHeight = 0; + + this.$zooming = false; + + this.$oEmpty; + + this.$IEResizeCounter = 0; + + this.lastOverflow = null; + + /* this.$hide and this.$show function are not overwritten */ + this.$positioning = "basic"; +}; + +(function() { + this.implement( + + apf.DataAction + + //,apf.Cache + ); + + this.$supportedProperties.push("model", "thumbheight", "title", "loadmsg", + "defaultthumb", "defaulttitle", + "defaultimage", "scalewidth"); + + this.$booleanProperties["scalewidth"] = true; + + this.$propHandlers["thumbheight"] = function(value) { + if (parseInt(value)) + this.thumbheight = parseInt(value); + }; + + this.$propHandlers["delay"] = function(value) { + if (parseInt(value)) + this.delay = parseInt(value); + }; + + /** + * Prepare previous and next xml representation of slide element dependent + * of actual slide + */ + this.$setSiblings = function() { + var temp_n = this.getNextTraverse(this.current), + temp_p = this.getNextTraverse(this.current, true); + + this.next = temp_n ? temp_n : this.getFirstTraverseNode(); + this.previous = temp_p ? temp_p : this.getLastTraverseNode(); + }; + + /** + * When slideshow is downloading some image, and user is trying to change it, + * new image is saved and will be displayed later by this method + */ + this.$showLast = function() { + var _self = this; + + clearInterval(this.tmrShowLast); + this.tmrShowLast = setInterval(function() { + if (!_self.inuse) { + if (_self.lastChoose) { + _self.current = _self.lastChoose; + _self.lastChoose = null; + + _self.$refresh(); + } + clearInterval(_self.tmrShowLast); + } + }, 100); + }; + + this.$paint = function() { + var _self = this; + + this.current = this.getFirstTraverseNode(); + + //Prepare area + this.oImage.src = "about:blank"; + this.oThumbnails.style.height = + this.otBody.style.height = + this.otPrevious.style.height = + this.otNext.style.height = this.thumbheight + "px"; + + + this.oLoading.innerHTML = this.loadmsg + || apf.language.getWord("sub.slideshow.loadmsg") + || "Loading..."; + + //This function will be called when selected image will be downloaded + this.oImage.onload = function() { + _self.last = _self.current; + _self.oBody.style.display = "block"; + _self.oImageBase.style.display = "block"; + _self.oImageBase.src = _self.oImage.src; + + //Get Image size + this.style.display = "block"; + + var imgWidth = _self.oImageBase.offsetWidth || _self.oImageBase.width; + var imgHeight = _self.oImageBase.offsetHeight || _self.oImageBase.height; + + this.style.display = "none"; + _self.oImageBase.style.display = "none"; + + //Get browser window dimension + var windowWidth = apf.isIE + ? document.documentElement.offsetWidth + : window.innerWidth; + windowHeight = apf.isIE + ? document.documentElement.offsetHeight + : window.innerHeight; + + //Get height of the bottom panel + var bottomPanel = _self.$getPanelSize(); + + //Get body margins + var oBodyDiff = apf.getDiff(_self.oBody); + + //is the image resized ? + var checkWH = [false, false]; + + //calculate viewport size + var viewPortHeight = windowHeight - bottomPanel / 2 - _self.$vSpace, + viewPortWidth = windowWidth - _self.$hSpace; + + var _imgHeight = imgHeight, + _imgWidth = imgWidth; + + //if image height is bigger than body, scale it + if (_imgHeight > viewPortHeight) { + _imgWidth = parseInt(_imgWidth * (viewPortHeight / _imgHeight)); + _imgHeight = viewPortHeight; + } + + if (_imgWidth > viewPortWidth) { + _imgHeight = parseInt(_imgHeight * (viewPortWidth / _imgWidth)); + _imgWidth = viewPortWidth; + } + + _self.viewPortHeight = _imgHeight; + _self.viewPortWidth = _imgWidth; + + //resize image body horizontaly + apf.tween.single(_self.oBody, { + steps : apf.isGecko + ? 20 + : (Math.abs(imgWidth - _self.oBody.offsetWidth) > 40 + ? 10 + : 3), + anim : apf.tween.EASEIN, + type : "mwidth", + from : _self.oBody.offsetWidth - oBodyDiff[0], + to : _imgWidth, + onfinish : function() { + checkWH[0] = true; + } + }); + + //Resize image body verticaly + apf.tween.single(_self.oBody, { + steps : apf.isGecko + ? 20 + : (Math.abs(imgHeight - _self.oBody.offsetHeight) > 40 + ? 10 + : 3), + anim : apf.tween.EASEIN, + type : "mheight", + margin : -1 * (bottomPanel / 2 - 10), + from : _self.oBody.offsetHeight - oBodyDiff[1], + to : _imgHeight, + onfinish : function() { + checkWH[1] = true; + } + }); + + _self.oImage.style.display = "block"; + _self.oImage.style.width = _imgWidth + "px"; + _self.oImage.style.height = _imgHeight + "px"; + _self.oImage.style.display = "none"; + + //do some things when image body is resized + clearInterval(_self.tmrIsResized); + _self.tmrIsResized = setInterval(function() { + if (checkWH[0] && checkWH[1]) { + clearInterval(_self.tmrIsResized); + + if (_self.current) + _self.$setSiblings(); + + //_self.oTitle.style.visibility = "visible"; + _self.oConsole.style.visibility = "visible"; + + _self.oImage.style.display = "block"; + _self.oTitle.style.display = "block"; + + _self.$checkThumbSize(); + + if (_self.thumbnails) { + _self.oThumbnails.style.display = "block"; + } + + apf.tween.single(_self.oImage, { + steps : 2, + type : "fade", + from : 0, + to : 1 + }); + + apf.tween.single(_self.oTitle, { + steps : 10, + type : "fade", + from : 0, + to : 1 + }); + + _self.inuse = false; + _self.addSelection(); + + if (_self.play) { + _self.$play(); + } + } + }, 30); + }; + + //If something went wrong + this.oImage.onerror = function() { + _self.inuse = false; + }; + + this.oImage.onabort = function() { + _self.inuse = false; + }; + + //_self.oImage.src = (_self.$applyBindRule("src", _self.current) + // || _self.defaultimage || "about:blank"); + + this.oContent.innerHTML = _self.title == "text" + ? this.$applyBindRule("title", this.current) + : (this.title == "number+text" + ? "" + + (apf.language.getWord("sub.slideshow.image") || "Image") + + " " + + (this.getPos() + 1) + + " " + (apf.language.getWord("sub.slideshow.of") || "of") + " " + + this.getTraverseNodes().length + + "
    " + + (this.$applyBindRule("title", this.current) + || (this.defaulttitle + ? this.defaulttitle + : (apf.language.getWord("sub.slideshow.defaulttitle") || "No description"))) + : "Image " + (this.getPos() + 1) + + " of " + this.getTraverseNodes().length); + }; + + /** + * Return image position on imagelist + * + * @return {Number} image position + */ + this.getPos = function() { + return Array.prototype.indexOf.call(this.getTraverseNodes(), this.current); + }; + + /** + * Adds selection to thumbnail of actual selected image and removes selection + * from previous. When the "move" param is set, selected thumbnail + * is always in displayed area. + * + * @param {Number} thumbnail bar scrolling direction + * Possible values: + * 1 when thumbnails are scrolling in right + * -1 when thumbnails are scrolling in left + */ + this.addSelection = function(move) { + var htmlElement = apf.xmldb.findHtmlNode(this.current, this), + ww = apf.isIE + ? document.documentElement.offsetWidth + : window.innerWidth, + + diffp = apf.getDiff(this.otPrevious), + diffn = apf.getDiff(this.otNext), + bp = parseInt(apf.getStyle(this.otPrevious, "width")), + bn = parseInt(apf.getStyle(this.otNext, "width")), + ew = parseInt(apf.getStyle(htmlElement, "width")); + + /* checking visiblity */ + if (htmlElement.offsetLeft + ew + 5 > + ww - bp - bn - diffp[0] - diffn[0]) { + if (move) { + if (move > 0) + this.$tPrevious(); + else if (move < 0) + this.$tNext(); + this.addSelection(move); + } + } + if (this.$selected) + this.$selected.className = "ssPicBox"; + if (htmlElement) + htmlElement.className = "ssPicBox ssselected"; + + this.$selected = htmlElement; + }; + + /** + * When xml representation of new image is set, function initiate redrawing + */ + this.$refresh = function() { + var _self = this; + + //When slideshow is downloading some image, and user is trying to change it, + //new image is saved and will be displayed later + if (this.inuse) { + this.lastChoose = this.current; + this.$showLast(); + + return; + } + + if (this.play) + clearInterval(this.tmrPlay); + + this.$setSiblings(); + this.inuse = true; + + apf.tween.single(this.oImage, { + steps : 5, + type : "fade", + from : 1, + to : 0 + }); + + //Hack for Chrome + this.oTitle.style.display = "block"; + + apf.tween.single(this.oTitle, { + steps : 3, + type : "fade", + from : 1, + to : 0, + onfinish : function() { + _self.oImage.style.left = "0px"; + _self.oImage.style.top = "0px"; + var _src = (_self.$applyBindRule("src", _self.current) + || _self.defaultimage || _self.defaultthumb); + var _src_temp = _self.oImage.src; + + _self.oImage.src = _src; + _self.oImage.style.display = "none"; + + // Safari and Chrome fix for reloading current image + if (_self.oImage.src == _src_temp && apf.isWebkit) { + _self.inuse = false; + apf.tween.single(_self.oImage, { + steps : 5, + type : "fade", + from : 0, + to : 1 + }); + apf.tween.single(_self.oTitle, { + steps : 3, + type : "fade", + from : 0, + to : 1 + }); + //_self.oTitle.style.visibility = "visible"; + } + + _self.oContent.innerHTML = _self.title == "text" + ? _self.$applyBindRule("title", _self.current) + : (_self.title == "number+text" + ? "" + (apf.language.getWord("sub.slideshow.image") || "Image") + + " " + + (_self.getPos() + 1) + + " " + (apf.language.getWord("sub.slideshow.of") || "of") + " " + + _self.getTraverseNodes().length + + "
    " + + (_self.$applyBindRule("title", _self.current) + || (_self.defaulttitle + ? _self.defaulttitle + : apf.language.getWord("sub.slideshow.defaulttitle") || "No description" )) + : "Image " + (_self.getPos() + 1) + " of " + + _self.getTraverseNodes().length); + } + }); + }; + + /** + * Selects image by its xml representation + * + * @param {XMLElement} badge xml representation of image + */ + this.select = function(badge) { + this.current = badge; + this.$show(); + }; + + /** + * Hides browser scrollbars + */ + this.$hideScrollbars = function() { + this.lastOverflow = document.documentElement.style.overflow == "hidden" + ? "auto" + : document.documentElement.style.overflow; + + document.documentElement.style.overflow = "hidden"; + }; + + /** + * Shows browser scrollbars + */ + this.$showScrollbars = function() { + document.documentElement.style.overflow = this.lastOverflow; + }; + + + this.$show = function() { + var _self = this; + + this.$hideScrollbars(); + + this.oBeam.style.display = "none"; + this.oBody.style.display = "none"; + this.$int.style.display = "block"; + this.$ext.style.display = "block"; + + apf.tween.single(_self.oCurtain, { + steps : 10, + type : "fade", + from : 0, + to : 0.7, + onfinish : function() { + _self.oBeam.style.display = "block"; + apf.tween.single(_self.oBeam, { + steps : 10, + type : "fade", + from : 0, + to : 1, + onfinish : function() { + _self.oBody.style.display = "block"; + _self.oBody.style.width = "100px"; + _self.oBody.style.height = "100px"; + _self.oBody.style.marginLeft = "-50px"; + _self.oBody.style.marginTop = "-50px"; + apf.tween.single(_self.oBody, { + steps : 5, + type : "fade", + from : 0, + to : 1, + onfinish : function() { + if (apf.isIE) { + _self.oBody.style.filter = ""; + _self.oBeam.style.filter = ""; + } + _self.$refresh(); + } + }); + } + }); + } + }); + }; + + /**** Init ****/ + + /** + * Display next image from imagelist + */ + this.$Next = function() { + this.current = this.next; + this.addSelection(-1); + this.$refresh(); + }; + + /** + * Display previous image from imagelist + */ + this.$Previous = function() { + this.current = this.previous; + this.addSelection(1); + this.$refresh(); + }; + + /** + * Move first thumbnail from the left to end of imagebar elementlist. + * It's possible to scroll imagebar to infinity. + */ + this.$tNext = function() { + this.otBody.appendChild(this.otBody.childNodes[0]); + }; + + /** + * Move last thumbnail to begining of imagebar elementlist. + * It's possible to scroll imagebar to infinity. + */ + this.$tPrevious = function() { + this.otBody.insertBefore( + this.otBody.childNodes[this.otBody.childNodes.length - 1], + this.otBody.firstChild); + }; + + /** + * Starts the slide show + */ + this.$play = function() { + var _self = this; + clearInterval(this.tmrPlay); + this.tmrPlay = setInterval(function() { + _self.play = true; + if (_self.inuse) + return; + + _self.$Next(); + }, _self.delay * 1000); + }; + + /** + * Stops the slide show + */ + this.$stop = function() { + clearInterval(this.tmrPlay); + this.tmrPlay = null; + this.play = false; + }; + + /** + * Adds selection to thumbnail and shows the image. + * + * @param {HTMLElement} oThumb html representation of thumbnail element + */ + this.$clickThumb = function(oThumb) { + this.current = apf.xmldb.getNode(oThumb); + this.addSelection(); + this.$refresh(); + }; + + this.$getPanelSize = function() { + var title_height = this.oTitle.offsetHeight + || parseInt(apf.getStyle(this.oTitle, "height")) + + apf.getDiff(this.oTitle)[1]; + + return Math.max( + this.oBeam.offsetHeight, + title_height + + (this.thumbnails + ? this.thumbheight + : 0) + + this.oConsole.offsetHeight + ); + }; + + this.$resize = function() { + //because resize event is called 2 times in IE + if (apf.isIE) { + this.$IEResizeCounter++; + + if (this.$IEResizeCounter == 2) { + this.$IEResizeCounter = 0; + return; + } + } + + var _self = this; + + _self.oImage.style.display = "none"; + + var windowWidth = apf.isIE + ? document.documentElement.offsetWidth + : window.innerWidth, + windowHeight = apf.isIE + ? document.documentElement.offsetHeight + : window.innerHeight; + + var imgWidth = _self.oImageBase.offsetWidth || _self.oImageBase.width; + var imgHeight = _self.oImageBase.offsetHeight || _self.oImageBase.height; + var oBodyDiff = apf.getDiff(this.oBody); + var bottomPanel = this.$getPanelSize(); + + //calculate viewport size + var viewPortHeight = windowHeight - bottomPanel / 2 - _self.$vSpace; + var viewPortWidth = windowWidth - _self.$hSpace; + + var _imgHeight = imgHeight; + var _imgWidth = imgWidth; + + //if image height is bigger than body, scale it + if (_imgHeight > viewPortHeight) { + _imgWidth = parseInt(_imgWidth * (viewPortHeight / _imgHeight)); + _imgHeight = viewPortHeight; + } + + if (_imgWidth > viewPortWidth) { + _imgHeight = parseInt(_imgHeight * (viewPortWidth / _imgWidth)); + _imgWidth = viewPortWidth; + } + + this.viewPortHeight = _imgHeight; + this.viewPortWidth = _imgWidth; + + var checkWH = [false, false]; + + //resize image body horizontaly + apf.tween.single(_self.oBody, { + steps : 5, + anim : apf.tween.EASEIN, + type : "mwidth", + from : _self.oBody.offsetWidth - oBodyDiff[0], + to : _imgWidth, + onfinish : function() { + checkWH[0] = true; + } + }); + + //Resize image body verticaly + apf.tween.single(_self.oBody, { + steps : 5, + anim : apf.tween.EASEIN, + type : "mheight", + margin : -1 * (bottomPanel / 2 - 10), + from : _self.oBody.offsetHeight - oBodyDiff[1], + to : _imgHeight, + onfinish : function() { + checkWH[1] = true; + } + }); + + clearInterval(_self.tmrIsResized); + _self.tmrIsResized = setInterval(function() { + if (checkWH[0] && checkWH[1]) { + clearInterval(_self.tmrIsResized); + + _self.oImage.style.display = "block"; + _self.oImage.style.width = _imgWidth + "px"; + _self.oImage.style.height = _imgHeight + "px"; + + apf.tween.single(_self.oImage, { + steps : 2, + type : "fade", + from : 0, + to : 1 + }); + } + }, 30); + }; + + /** + * Creates html representation of slideshow elements based on skin file + * and adds events to each one. + */ + this.$draw = function() { + //Build Main Skin + this.$pHtmlNode = document.body; + + this.$ext = this.$getExternal(); + this.$int = this.$getLayoutNode("main", "container", this.$ext); + this.oCurtain = this.$getLayoutNode("main", "curtain", this.$ext); + this.oBody = this.$getLayoutNode("main", "body", this.$ext); + this.oContent = this.$getLayoutNode("main", "content", this.$ext); + this.oImage = this.$getLayoutNode("main", "image", this.$ext); + this.oImageBase = this.$getLayoutNode("main", "image_base", this.$ext); + this.oClose = this.$getLayoutNode("main", "close", this.$ext); + this.oBeam = this.$getLayoutNode("main", "beam", this.$ext); + this.oTitle = this.$getLayoutNode("main", "title", this.$ext); + this.oThumbnails = this.$getLayoutNode("main", "thumbnails", this.$ext); + this.otBody = this.$getLayoutNode("main", "tbody", this.$ext); + this.otPrevious = this.$getLayoutNode("main", "tprevious", this.$ext); + this.otNext = this.$getLayoutNode("main", "tnext", this.$ext); + this.oLoading = this.$getLayoutNode("main", "loading", this.$ext); + this.oEmpty = this.$getLayoutNode("main", "empty", this.$ext); + this.oConsole = this.$getLayoutNode("main", "console", this.$ext); + this.oPrevious = this.$getLayoutNode("main", "previous", this.$ext); + this.oPlay = this.$getLayoutNode("main", "play", this.$ext); + this.oNext = this.$getLayoutNode("main", "next", this.$ext); + + var _self = this; + + + //@todo add this to $destroy + var rules = "var o = apf.all[" + this.$uniqueId + "];\ + if (o) o.$resize()"; + apf.layout.setRules(this.$pHtmlNode, this.$uniqueId + "_scaling", + rules, true); + apf.layout.queue(this.$pHtmlNode); + + + this.oPrevious.onclick = + this.oNext.onclick = function(e) { + if (_self.disabled) + return; + + if ((this.className || "").indexOf("ssprevious") != -1) + _self.$Previous(); + else if ((this.className || "").indexOf("ssnext") != -1) + _self.$Next(); + }; + + var tmrThumbButton = null; + this.otPrevious.onmousedown = function(e) { + if (_self.disabled) + return; + + tmrThumbButton = setInterval(function() { + _self.$tPrevious(); + }, 50); + }; + + this.otNext.onmousedown = function(e) { + if (_self.disabled) + return; + + tmrThumbButton = setInterval(function() { + _self.$tNext(); + }, 50); + }; + + this.otNext.onmouseover = function(e) { + _self.$setStyleClass(_self.otNext, "ssnhover", null, true); + }; + + this.otPrevious.onmouseover = function(e) { + _self.$setStyleClass(_self.otPrevious, "ssphover", null, true); + } + + this.otNext.onmouseout = function(e) { + _self.$setStyleClass(_self.otNext, "", ["ssnhover"], true); + }; + + this.otPrevious.onmouseout = function(e) { + _self.$setStyleClass(_self.otPrevious, "", ["ssphover"], true); + }; + + this.oPlay.onclick = function(e) { + if (_self.disabled) + return; + + if (_self.tmrPlay) { + _self.$stop(); + _self.$setStyleClass(_self.oPlay, "", ["ssstop"]); + _self.$setStyleClass(_self.oPlay, "ssplay"); + _self.oNext.style.visibility = "visible"; + _self.oPrevious.style.visibility = "visible"; + _self.oThumbnails.style.display = "block"; + } + else { + _self.$play(); + _self.$setStyleClass(_self.oPlay, "", ["ssplay"]); + _self.$setStyleClass(_self.oPlay, "ssstop"); + _self.oNext.style.visibility = "hidden"; + _self.oPrevious.style.visibility = "hidden"; + _self.oThumbnails.style.display = "none"; + } + }; + + this.oClose.onclick = function() { + //_self.visible = true; + _self.$hide(); + _self.$showScrollbars(); + }; + + document.onmouseup = function(e) { + clearInterval(tmrThumbButton); + return false; + }; + + this.oImage.onmouseover = function(e) { + if (_self.disabled) + return; + + _self.inuse = true; + + var e = e || event; + var target = e.target || e.srcElement; + + var imgWidth = _self.oImageBase.offsetWidth || _self.oImageBase.width; + var imgHeight = _self.oImageBase.offsetHeight || _self.oImageBase.height; + + var windowWidth = apf.isIE + ? document.documentElement.offsetWidth + : window.innerWidth; + var windowHeight = apf.isIE + ? document.documentElement.offsetHeight + : window.innerHeight; + + var diff = apf.getDiff(_self.oBody); + var posX = _self.oBody.offsetLeft + diff[0] / 2; + var posY = _self.oBody.offsetTop + diff[1] / 2; + + var dx = 0, dy = 0; + var w, h, ml, mt; + var sx = e.clientX - posX; + var sy = e.clientY - posY; + + w = _self.$imageWidth = parseInt(_self.oImage.style.width); + h = _self.$imageHeight = parseInt(_self.oImage.style.height); + + //wg tej wartosci ustale miejsce na duzym obrazku i bede wiedział wg jakiego punktu mam rozszerzać go + var percent_posX = sx / _self.$imageWidth; + var percent_posY = sy / _self.$imageHeight; + + this.onmousemove = function(e) { + var e = e || event; + + sx = e.clientX - posX; + sy = e.clientY - posY; + + percent_posX = sx / _self.$imageWidth; + percent_posY = sy / _self.$imageHeight; + + if((_self.$imageWidth == parseInt(_self.oImage.style.width) + && _self.$imageHeight == parseInt(_self.oImage.style.height)) || _self.$zooming) { + return; + } + + ml = -1 * (percent_posX * w - sx); + mt = -1 * (percent_posY * h - sy); + + if (ml <= 0 && ml >= -1 * (w - _self.viewPortWidth)) { + _self.oImage.style.left = ml + "px"; + } + + if (mt <= 0 && mt >= -1 * (h - _self.viewPortHeight)) { + _self.oImage.style.top = mt + "px"; + } + }; + + //little delay to run zooming + clearInterval(_self.tmrHoverDelay); + _self.tmrHoverDelay = setInterval(function() { + clearInterval(_self.tmrHoverDelay); + clearInterval(_self.tmrZoom); + + _self.tmrZoom = setInterval(function() { + _self.$zooming = true; + w = parseInt(_self.oImage.style.width); + h = parseInt(_self.oImage.style.height); + var l = parseInt(_self.oImage.style.left); + var t = parseInt(_self.oImage.style.top); + var ratio = apf.isIE ? 0.03 : 0.01; + + if (w < imgWidth) { + _self.oImage.style.width = (w + w * ratio) + "px"; + _self.oImage.style.left = -1 * (percent_posX * w - sx) + "px"; + } + if (h < imgHeight) { + _self.oImage.style.height = (h + h * ratio) + "px"; + _self.oImage.style.top = -1 * (percent_posY * h - sy) + "px"; + } + + if (w >= imgWidth && h >= imgHeight) { + clearInterval(_self.tmrZoom); + _self.$zooming = false; + } + }, apf.isIE ? 5 : 10); + + }, 1000); + }; + + this.oImage.onmouseout = function(e) { + if (_self.disabled) + return; + + _self.inuse = false; + clearInterval(_self.tmrZoom); + clearInterval(_self.tmrHoverDelay); + + _self.oImage.style.width = _self.$imageWidth + "px"; + _self.oImage.style.height = _self.$imageHeight + "px"; + + _self.oImage.style.top = "0px"; + _self.oImage.style.left = "0px"; + + document.onmousemove = null; + }; + + var SafariChromeFix = false; + apf.addEventListener("mousescroll", function(e) { + if (!_self.xmlRoot || _self.$ext.style.display == "none" + || _self.disabled) + return; + + e = e || event; + if (apf.isWebkit) { + SafariChromeFix = SafariChromeFix ? false : true; + if (!SafariChromeFix) + return; + } + + var delta = e.delta; + + var curNode = _self.current; + var nextNode = _self.getNextTraverse(curNode); + var prevNode = _self.getNextTraverse(curNode, true); + + _self.next = nextNode ? nextNode : _self.getFirstTraverseNode(); + _self.previous = prevNode ? prevNode : _self.getLastTraverseNode(); + + _self.current = delta < 0 ? _self.next : _self.previous; + + _self.addSelection(delta); + + if (_self.current !== curNode) { + clearInterval(_self.tmrOnScroll); + _self.tmrOnScroll = setInterval(function() { + _self.$refresh(); + clearInterval(_self.tmrOnScroll); + }, 400); + }; + return false; + }); + + apf.addEventListener("onkeydown", function(e) { + e = (e || event); + + if (_self.disabled) + return; + + //39 - Right Arrow + //37 - Left Arrow + + var key = e.keyCode; + var curNode = _self.current; + var nextNode = _self.getNextTraverse(curNode); + var prevNode = _self.getNextTraverse(curNode, true); + + _self.next = nextNode + ? nextNode + : _self.getFirstTraverseNode(); + _self.previous = prevNode + ? prevNode + : _self.getLastTraverseNode(); + _self.current = key == 39 + ? _self.next + : (key == 37 + ? _self.previous + : _self.current); + + _self.addSelection(key == 39 ? -1 : (key == 37 ? 1 : 0)); + + if (_self.current !== curNode) { + clearInterval(_self.tmrKeyDown); + _self.tmrKeyDown = setInterval(function() { + _self.$refresh(); + clearInterval(_self.tmrKeyDown); + }, 550); + }; + return false; + }); + }; + + /** + * Closes slideshow component + */ + this.$hide = function () { + var _self = this; + + _self.$ext.style.display = "block"; + + apf.tween.single(_self.oBody, { + steps : 10, + type : "fade", + from : 1, + to : 0, + onfinish : function() { + _self.oBody.style.display = "none"; + } + }); + + apf.tween.single(_self.oBeam, { + steps : 10, + type : "fade", + from : 1, + to : 0, + onfinish : function() { + _self.oBeam.style.display = "none"; + + apf.tween.single(_self.oCurtain, { + steps : 10, + type : "fade", + from : 0.7, + to : 0, + onfinish : function() { + _self.$int.style.display = "none"; + _self.$ext.style.display = "none"; + } + }); + } + }); + }; + + this.$checkThumbSize = function() { + var nodes = this.getTraverseNodes(); + var nodes_len = nodes.length; + + var picBoxes = this.otBody.childNodes; + var picBoxed_len = picBoxes.length; + + var widthSum = 0; + + var boxDiff, srcThumb, htmlPicBox, h, w, bh, testImg, counter = 0; + + for (var i = 0; i < picBoxed_len; i++) { + if ((picBoxes[i].className || "").indexOf("ssPicBox") > -1) { + htmlPicBox = picBoxes[i]; + + srcThumb = this.$applyBindRule("thumb", nodes[counter]); + boxDiff = apf.getDiff(htmlPicBox); + bh = this.thumbheight - 10 - boxDiff[1]; + + if (this.scalewidth) { + testImg = new Image(); + document.body.appendChild(testImg); + + testImg.src = srcThumb ? srcThumb : this.defaultthumb; + + h = bh; + if (testImg.height < bh) { + w = testImg.width; + } + else { + testImg.setAttribute("height", bh); + w = testImg.width; + } + document.body.removeChild(testImg); + } + else { + h = w = bh; + } + + widthSum += w + boxDiff[0] + + (parseInt(apf.getStyle(htmlPicBox, "marginLeft"))) + + (parseInt(apf.getStyle(htmlPicBox, "marginRight"))); + + htmlPicBox.style.width = w + "px"; + counter++; + } + } + + var thumbDiff = apf.getDiff(this.otBody); + + this.otPrevious.style.visibility = this.otNext.style.visibility = + widthSum < this.oThumbnails.offsetWidth - thumbDiff[0] + ? "hidden" + : "visible"; + }; + + this.$load = function(xmlRoot) { + apf.xmldb.addNodeListener(xmlRoot, this); + + var nodes = this.getTraverseNodes(); + var nodes_len = nodes.length; + + var boxDiff, srcThumb, htmlPicBox, h, w, bh, testImg = null; + for (var i = 0; i < nodes_len; i++) { + //Create box for thumbnail + htmlPicBox = this.otBody.appendChild(document.createElement("div")); + //Get source path to thumbnail image + srcThumb = this.$applyBindRule("thumb", nodes[i]); + + htmlPicBox.style.backgroundImage = 'url(' + (srcThumb ? srcThumb : this.defaultthumb) + ')'; + + htmlPicBox.className = "ssPicBox"; + boxDiff = apf.getDiff(htmlPicBox); + + bh = this.thumbheight - 10 - boxDiff[1]; + + if (this.scalewidth) { + testImg = new Image(); + document.body.appendChild(testImg); + + testImg.src = srcThumb ? srcThumb : this.defaultthumb; + + h = bh; + if (testImg.height < bh) { + w = testImg.width; + } + else { + testImg.setAttribute("height", bh); + w = testImg.width; + } + document.body.removeChild(testImg); + } + else { + h = w = bh; + } + + htmlPicBox.style.height = h + "px"; + htmlPicBox.style.width = w + "px"; + htmlPicBox.style.marginTop = htmlPicBox.style.marginBottom = "5px"; + + apf.xmldb.nodeConnect(this.documentId, nodes[i], htmlPicBox, this); + + var _self = this; + htmlPicBox.onclick = function(e) { + _self.$clickThumb(this); + } + } + + + if (nodes_len != this.length) + this.setProperty("length", nodes_len); + + + this.$paint(); + } + + this.addEventListener("$clear", function(){return false}); + + this.$destroy = function() { + this.otNext.onmouseover = + this.otPrevious.onmouseover = + this.otNext.onmouseout = + this.otPrevious.onmouseout = + this.$ext.onresize = + this.oImage.onmousedown = + this.otNext.onmousedown = + this.otPrevious.onmousedown = + this.oNext.onclick = + this.oPrevious.onclick = null; + + //this.removeEventListener("onkeydown", onkeydown_); + //this.removeEventListener("mousescroll", onmousescroll_); + + this.x = null; + }; + + this.$setClearMessage = function(msg, className) { + var ww = apf.isIE + ? document.documentElement.offsetWidth + : window.innerWidth; + var bp = parseInt(apf.getStyle(this.otPrevious, "width")); + var bn = parseInt(apf.getStyle(this.otNext, "width")); + var ew = parseInt(apf.getStyle(this.oEmpty, "width")); + + this.$oEmpty = this.oCurtain.appendChild(this.oEmpty.cloneNode(true)); + + apf.setNodeValue(this.$oEmpty, msg || ""); + + this.$oEmpty.setAttribute("id", "empty" + this.$uniqueId); + this.$oEmpty.style.display = "block"; + this.$oEmpty.style.left = ((ww - ew) / 2 - bp - bn) + "px"; + apf.setStyleClass(this.$oEmpty, className, ["ssloading", "ssempty", "offline"]); + }; + + this.$removeClearMessage = function() { + if (!this.$oEmpty) + this.$oEmpty = document.getElementById("empty" + this.$uniqueId); + if (this.$oEmpty && this.$oEmpty.parentNode) + this.$oEmpty.parentNode.removeChild(this.$oEmpty); + }; + + this.$setCurrentFragment = function(fragment) { + this.otBody.appendChild(fragment); + + this.dataset = fragment.dataset; + + if (!apf.window.hasFocus(this)) + this.blur(); + }; + + this.$getCurrentFragment = function() { + var fragment = document.createDocumentFragment(); + + while (this.otBody.childNodes.length) { + fragment.appendChild(this.otBody.childNodes[0]); + } + fragment.dataset = this.dataset; + + return fragment; + }; + + +}).call(apf.slideshow.prototype = new apf.MultiselectBinding()); + + +apf.aml.setElement("slideshow", apf.slideshow); + +apf.aml.setElement("src", apf.BindingRule); +apf.aml.setElement("title", apf.BindingRule); +apf.aml.setElement("thumb", apf.BindingRule); + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/elements/smartbinding.js)SIZE(33619)TIME(Mon, 10 Jan 2011 10:53:12 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + + +/** + * @term smartbinding Smartbinding is a type of bidirectional databinding where + * rules specify how data is rendered in a component and how changes to + * the bound data are sent back to the server (or other data source). + * Smartbindings are specifically designed to solve the problem of databinding + * for Ajax applications that connect to remote (non-local) datasources. + * A smartbinding element can contain three elements; {@link element.bindings bindings}, + * {@link element.actions actions} and {@link element.model model}. + * + * See also more information about {@link term.binding binding rules} and + * {@link term.action action rules}. + * + * Model: + * The model is the place where your xml data resides. Data is loaded into the + * model using a {@link term.datainstruction data instruction} as the following + * example shows: + * + * + * + * An element can connect directly to a model in order to bind to data. + * + * + * + * + * + * The model can also be part of a smartbinding that is used by the element. + * A smartbinding can be used by multiple elements referenced by id: + * + * + * + * + * + * + * + * + * + * + * + * + * Bindings: + * The bindings element is a container for binding rules. Binding rules determine + * how an element renders the data that it's bound to. Some binding rules specify + * how data can be interacted with (i.e. {@link baseclass.multiselect.binding.select the select rule}). + * Check the {@link term.binding term binding rules} for more information. + * + * Actions: + * The actions element is a container for action rules. Action rules influence + * and trigger several parts of the user interaction. + *
      + *
    1. It determines whether a user action can be executed on the bound and/or + * selected {@link term.datanode data node}.
    2. + *
    3. It dispatches events, before and after the data is changed.
    4. + *
    5. It creates a {@link http://en.wikipedia.org/wiki/Command_pattern command object} + * that is pushed on the undo stack of the {@link element.actiontracker actiontracker} + * connected to the element that triggered the action.
    6. + *
    7. The command object contains all the information to send the change back + * to the server
    8. + *
    + * So in short, an action rule is always triggered by the user, creates an + * undo item and sends the change back to the server. + * Check the {@link term.action term action rules} for more information. + * + * See: + * {@link baseclass.databinding.attribute.smartbinding} + */ + +/** + * @term binding Binding rules determine how an element displays the data that + * its bound to (ex.: {@link element.tree.binding.caption the caption rule}), + * and determines how it can be interacted with + * (ex.: {@link baseclass.multiselect.binding.select the select rule}). + * Binding rules are part of the {@link term.smartbinding smartbinding concept}. + * + * Basic: + * Let's take a simple example, that of a {@link element.textbox textbox}. A + * textbox has a {@link element.textbox.attribute.value value attribute}. This + * attribute can be set like this: + * + * + * + * In many cases it's handy to bind the value of the textbox to data. Imagine + * you are editing a contact's name in a textbox. In this case you would want to + * bind the value of the textbox to the xml data. The binding rule is configured + * to determine this value based on the bound xml. Let's look at an example: + * + * + * + * Test + * + * + * + * + * + * The textbox binds to the data of the model. The bind rule sets how the value + * is retrieved from the bound data. In this case the value of the name node is + * retrieved. When the user changes the value of the textbox, the name + * node is updated with that value. Subsequently when the xml + * changes the value of the textbox is updated. + * + * Each attribute on an element can be bound to data by using the attribute + * name as the name of the binding rule. In the next example, the visible + * attribute of a textbox is based on the availability of a {@link term.datanode data node}: + * + * + * + * + * + * + * + * Each element has a primary bind rule that can be accessed in a short format. + * This is usually the value bind rule. The short format works as follows: + * + * + * + * Test + * + * + * + * + * + * Advanced: + * For multi node components databinding adds another conceptual step. The basics + * stay the same, though a way is introduced to do 'foreach' on the data to + * determine which nodes are rendered. This is done using the + * {@link element.multiselectbinding.binding.each each binding rule} and + * the selected nodes are called {@link term.eachnode each nodes}. + * + * When the set of each nodes is determined, each is rendered based on other + * binding rules that determine whatever is deemed necesary by the component. + * This can be the caption, icon, tooltip, whether an item is seletable and so on. + * In the next example a list is bound to some data representing a contact list. + * Each contact's name is displayed as the caption of the item. + * + * + * + * + * Ruben + * Javeline + * + * + * Łukasz + * Javeline + * + * + * + * + * + * + * + * + * + * + * + * + * Fallbacks: + * By stacking multiple binding rules it's possible to define different ways to + * determine the value for an attribute. Let's say we have a tree that displays + * files and folders. A file and a folder can have custom icons. If these are + * not specified, they each default to an icon representing their type. This would + * be encoded like this: + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * Processors: + * There are several ways to convert the data retrieved from the xml data into + * the needed string or boolean. The following example uses {@link term.livemarkup live markup} + * to determine the icon by the extension of the filename: + * + * + * + * + * Ruben + * Baseclasses + * + * + * Łukasz + * application.png + * + * + * + * + * + * + * + * + * + * + * + * Instead of live markup you can use xslt as well. Furthermore you can apply some + * javascript to the result by calling a method. The following examples shows + * a caption where a javascript method inserts smileys. + * + * + * + * + * + * + * + * + * + * function insertSmileys(value) { + * //do something with value + * return value; + * } + * + * + * + * + * + * + * + * + * Extending: + * Two special binding rules are the {@link baseclass.databinding.binding.load load} + * and the {@link element.tree.binding.insert insert} bindings. These bindings + * are used to load and insert new data into the data bound to the element that + * uses them. With these rules an application can start out with only a bit of + * data and when the user needs it extends the data. A simple example is that of + * a tree element that loads subnodes whenever a user expands a node. This can + * be achieved in the following way: + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * For more information about how data can be loaded into aml elements please + * check {@link term.datainstruction data instructions}. + */ + +/** + * @term action Action rules determine whether a user can execute an action and + * takes care of executing the change both locally and on a remote server. Each + * triggered action creates an item on the undo stack. + * Action rules are part of the {@link term.smartbinding smartbinding concept}. + * + * Syntax: + * Actions are added to {@link element.actions}. The select attribute specifies + * whether an action can be executed. The set attribute specifies how the change + * to the data is send to the server. The following example shows a remove + * action on a datagrid. A jsp script is called to process the change. This is + * specified using a {@link term.datainstruction data instruction}. + * + * + * + * + * + * + * + * + * Defaults: + * The default behaviour for all components is to enable all actions when no + * actions element has been assigned. This can be change by setting + * {@link element.appsettings.attribute.auto-disable-actions}. When a actions + * element is assigned, all actions are disabled unless they are specified. + * When the select attribute on an action is not set the action will always be + * allowed. + * + * Flow: + * Action rules influence and trigger several parts of the user interaction. + *
      + *
    1. It determines whether a user action can be executed on the bound and/or + * selected {@link term.datanode data node}.
    2. + *
    3. It dispatches events, before and after the data is changed.
    4. + *
    5. It creates a {@link http://en.wikipedia.org/wiki/Command_pattern command object} + * that is pushed on the undo stack of the {@link element.actiontracker actiontracker} + * connected to the element that triggered the action.
    6. + *
    7. The command object ({@link core.undodata UndoData}) contains all the + * information to send the change back to the server.
    8. + *
    + * + * Fallbacks: + * By stacking multiple action rules it's possible to define different ways to + * deal with user actions. Let's say we have a tree that displays + * files and folders. Renaming a file and a folder might have different handlers. + * This would be encoded like this: + * + * + * + * + * + * + * + * + * + * + * + * Undo + * + * + * Undo: + * When an action is execute it creates an entry on the undostack of an + * actiontracker. Undo can be triggered by calling the undo method. + * + * myTree.getActionTracker().undo(); + * //or + * ActionTracker.undo(); + * + * Executing will revert the change to the data. This will also be communicated + * to the server. In some cases the call to the server is not symmetric; the set + * call cannot be used to revert. For these situations set the undo attribute. + * + * + * + * + * + * + * + * + * + * Undo + * Remove + * + * In the example above the server is required to support reverting remove. + * Another possibility is to add the item again as shown in this example: + * + * + * + * + * + * Javascript: + * Each action has a method associated with it that exists on the element that + * the action rule is assigned to. The method has the same name as the action + * and can be called from javascript. For instance, the {@link baseclass.multiselect.binding.remove remove action}: + * + * myTree.remove(); + * myTree.remove(dataNode); + * + * + * Add: + * Adding {@link term.datanode data nodes} to an element is a bit more advanced because the origin of + * the new data can be encoded in {@link baseclass.multiselect.binding.add the add action rule}. + * There are three ways to provide the data to add a node. + * + * The first is by calling the add method using javascript. + * + * + * + * + * + * myList.add(''); + * + * + * + * The second by specifying the template as a child of the add action rule: + * + * + * + * + * + * The third way gets the added node from the server. + * + * + * + * + * + * + * + * + * + * + * + * + * + * LCD Panel + * + * + * + * Add product + * + * + * Purging: + * Sometimes it's necesary to not send the changes directly to the server. For + * instance when the application offers a save button. To achieve this + * set the {@link element.actiontracker.attribute.realtime realtime attribute} + * of the actiontracker to false. The save button can call the + * {@link element.actiontracker.method.purge purge method} to have the + * actiontracker send the calls. + * + * + * + * Save + * + * N.B. At a certain amount of changes this way will become inefficient and + * you'll want to send the state of your data to the server directly. You can + * do that like this: + * + * + * + * + * + * + * + * + * + * LCD Panel + * + * + * + * + * + * + * Save + * + * + * See also {@link element.model.method.submit}. + * + * Transactions: + * A transaction is a + * set of changes to data which are treated as one change. When one of the + * changes in the set fails, all the changes will be cancelled. In the case of + * a gui this is happens when a user decides to cancel after + * making several changes. A good example are the well known Properties + * windows with an ok, cancel and apply button. + * + * When a user edits data, for instance user information, all the changes are + * seen as one edit and put on the undo stack as a single action. Thus clicking + * undo will undo the entire transaction, not just the last change done by that + * user in the edit window. Transaction support both optimistic and pessimistic + * locking. For more information on transactions see {@link baseclass.transaction}. + */ + +/** + * @term datanode A data node is the term used for any xml node (attribute, + * element, textnode or otherwise) that is used in a databound context. So when + * xml is loaded into a {@link element.model model} we refer to those xml nodes + * as data nodes. + */ + +/** + * Element containing information on how databound elements process data. + * The {@link term.smartbinding smartbinding} element specifies how data is transformed and rendered + * in databound elements. It also specifies how changes on the bound data are + * send to their original data source ({@link element.actions actions}) and + * which {@link term.datanode data nodes} can be dragged and dropped ({@link element.dragdrop dragdrop}). + * Example: + * A simple example of a smartbinding transforming data into representation + * + * + * + * + * + * + * + * + * + * + * + * + * LCD Panel + * + * + * + * + * Example: + * This is an elaborate example showing how to create a filesystem tree with + * files and folders in a tree. The smartbinding element describes how the + * files and folders are transformed to tree elements and how actions within + * the tree are sent to the data source. In this case {@link teleport.webdav WebDAV} + * is used. The drag and drop rules specify which elements can be dragged and + * where they can be dropped. + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * function filesort(value, args, xmlNode) { + * return (xmlNode.tagName == "folder" ? 0 : 1) + value; + * } + * + * function getIcon(xmlNode){ + * xmlNode.getAttribute('name').match(/\.([^\.]*)$/); + * + * var ext = RegExp.$1; + * return (SupportedIcons[ext.toUpperCase()] + * ? SupportedIcons[ext.toUpperCase()] + ".png" + * : "unknown.png"); + * } + * + * + * Remarks: + * Each element has it's own set of binding rules it uses to render the data + * elements. The same goes for it's actions. To give an example, a slider has + * one action called 'change'. This action is called when then value of the + * slider changes. A tree element has several actions - among others: 'add', + * 'remove', 'move', 'copy' and 'rename'. + * + * Smartbindings enable many other features in a Ajax.org Platform + * application. Actions done by the user can be undone by calling + * {@link element.actiontracker.method.undo} of the element. The + * Remote Databinding element can send changes on data to other clients. + * + * This element is created especially for reuse. Multiple elements can reference + * a single smartbinding element by setting the value of the 'smartbinding' + * attribute to the ID of this smartbinding element. If an element is only used + * for a single other element it can be set as it's child. In fact, each of the + * children of the smartbinding element can exist outside the smartbinding + * element and referenced indepently. + * Example: + * This example shows a smartbinding element which references to its children as + * stand alone elements. + * + * + * ... + * + * + * ... + * + * + * ... + * + * + * + * + * + * + * + * + * Example: + * The shortest method to add binding rules to an element is as follows: + * + * + * + * @see baseclass.databinding + * @see baseclass.databinding.attribute.smartbinding + * @see term.smartbinding + * @see term.binding + * @see term.action + * + * @define smartbinding + * @allowchild bindings, actions, ref, action, dragdrop, model + * @addnode smartbinding, global + * + * @constructor + * @apfclass + * + * @author Ruben Daniels (ruben AT ajax DOT org) + * @version %I%, %G% + * @since 0.8 + * + * @default_private + */ +apf.smartbinding = function(struct, tagName){ + this.$init(tagName || "smartbinding", apf.NODE_HIDDEN, struct); + + this.$bindNodes = {}; +}; + +(function(){ + this.$supportedProperties = ["bindings", "actions", "model"]; + this.$handlePropSet = function(prop, value, force){ + switch(prop) { + //@todo apf3 change this to use apf.setModel(); + case "model": + + if (typeof value == "string") + value = apf.nameserver.get("model", value); + this.model = apf.nameserver.register("model", this.name, value); + //this.modelBaseXpath = xpath; + + var amlNode; + for (var uniqueId in this.$bindNodes) { + amlNode = this.$bindNodes[uniqueId]; + this.model.unregister(amlNode); + this.model.register(amlNode, this.$modelXpath[amlNode.getHost + ? amlNode.getHost().$uniqueId + //this is a hack.. by making Models with links to other + //models possible, this should not be needed + : amlNode.$uniqueId] || this.modelBaseXpath); + //this.$bindNodes[uniqueId].load(this.model); + } + + break; + case "bindings": + if (this.$bindings) + this.remove(this.$bindings); + + this.$bindings = typeof value == "object" + ? value + : + + apf.nameserver.lookup("bindings", value); + + + this.add(this.$bindings); + + break; + case "actions": + if (this.$actions) + this.remove(this.$actions); + + this.$actions = typeof value == "object" + ? value + : + + apf.nameserver.lookup("actions", value); + + + this.add(this.$actions); + + break; + } + + this[prop] = value; + + + /*if (!apf.nameserver.get(name, attr[i].nodeValue)) + throw new Error(apf.formatErrorString(1036, this, + "Connecting " + name, + "Could not find " + name + " by name '" + + attr[i].nodeValue + "'"));*/ + + }; + + this.add = function(node){ + for (var uId in this.$bindNodes) + node.register(this.$bindNodes[uId]); + + this["$" + node.localName] = node; + }; + + this.remove = function(node){ + for (var uId in this.$bindNodes) + node.unregister(this.$bindNodes[uId]); + }; + + this.register = function(amlNode){ + this.$bindNodes[amlNode.$uniqueId] = amlNode; + + if (this.$bindings) + this.$bindings.register(amlNode); + if (this.$actions) + this.$actions.register(amlNode); + if (this.$model) + this.$model.register(amlNode); + }; + + this.unregister = function(amlNode){ + //unregister element + this.$bindNodes[amlNode.$uniqueId] = null; + delete this.$bindNodes[amlNode.$uniqueId]; + + if (this.$bindings) + this.$bindings.unregister(amlNode); + if (this.$actions) + this.$actions.unregister(amlNode); + if (this.$model) + this.$model.unregister(amlNode); + }; + + /** + * Loads xml data in the model of this smartbinding element. + * + * @param {mixed} xmlNode the {@link term.datanode data node} loaded into + * the model of this smartbinding element. This can be an XMLElement, a + * string or null. + * @private + */ + this.load = function(xmlNode){ + //@todo fix this + new apf.model().register(this).load(xmlNode); + }; + + this.clear = function(state){ + //for all elements do clear(state); + }; + + /** + * @private + * + * @attribute {String} bindings the id of the bindings element that contains + * the {@link term.binding binding rules} for all elements connected to + * this smartbinding element + * Example: + * + * + * + * @see element.bindings + * @see term.binding + * @see term.smartbinding + * + * @attribute {String} actions the id of the actions element that provides + * the {@link term.action action rules} for all elements connected to + * this smartbinding element + * Example: + * + * + * + * @see element.actions + * @see term.action + * @see term.smartbinding + * + * @attribute {String} dragdrop the id of the dragdrop element that provides + * the drag and drop rules for all elements connected to this smartbinding + * element + * Example: + * + * + * + * @see element.dragdrop + * @see term.smartbinding + * + * @attribute {String} model the id of the model element that provides + * the data for all elements connected to this smartbinding element. + * Example: + * + * + * + * @see element.model + * @see term.smartbinding + * + * @define bindings element containing all the binding rules for the data + * bound elements referencing this element. + * Example: + * + * + * + * + * + * + * + * + * + * @see element.smartbinding + * @allowchild {bindings} + * @addnode smartbinding, global + * @addnode smartbinding, global + */ + this.addEventListener("DOMNodeInsertedIntoDocument", function(e){ + if (this.parentNode.hasFeature(apf.__DATABINDING__)) + this.register(this.parentNode); + + + apf.console.info(this.name + ? "Creating SmartBinding [" + this.name + "]" + : "Creating implicitly assigned SmartBinding"); + + }); +}).call(apf.smartbinding.prototype = new apf.AmlElement()); + +apf.aml.setElement("smartbinding", apf.smartbinding); + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/elements/source.js)SIZE(1566)TIME(Mon, 10 Jan 2011 10:53:12 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + + +/** + * Element + * + * @constructor + * + * @define source + * @addnode audio, video + * + * @author Mike de Boer (mike AT javeline DOT com) + * @version %I%, %G% + * @since 3.0 + */ +apf.source = function(struct, tagName){ + this.$init(tagName || "source", apf.NODE_HIDDEN, struct); +}; + +(function(){ + this.$supportedProperties.push("src", "type"); + + this.addEventListener("DOMNodeInsertedIntoDocument", function(e){ + if (this.parentNode.$addSource) + this.parentNode.$addSource(this); + }); +}).call(apf.source.prototype = new apf.AmlElement()); + +apf.aml.setElement("source", apf.source); + + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/elements/spinner.js)SIZE(17516)TIME(Mon, 10 Jan 2011 10:53:12 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + +/** + * This element is used to choosing number by plus/minus buttons. + * When plus button is clicked longer, number growing up faster. The same + * situation is for minus button. It's possible to increment and decrement + * value by moving mouse cursor up or down with clicked input. Max and + * min attributes define range with allowed values. + * + * Example: + * Spinner element with start value equal 6 and allowed values from range + * (-100, 200) + * + * + * + * + * Example: + * Sets the value based on data loaded into this component. + * + * + * + * + * + * + + * Example: + * Is showing usage of model in spinner connected with textbox + * + * + * + * + * + * + * + * + * + * @attribute {Number} max maximal allowed value, default is 64000 + * @attribute {Number} min minimal allowed value, default is -64000 + * @attribute {Number} value actual value displayed in component + * + * @classDescription This class creates a new spinner + * @return {Spinner} Returns a new spinner + * + * @author + * @version %I%, %G% + * + * @inherits apf.StandardBinding + * @inherits apf.DataAction + * @inherits apf.XForms + * + * @binding value Determines the way the value for the element is retrieved + * from the bound data. + */ +apf.spinner = function(struct, tagName){ + this.$init(tagName || "spinner", apf.NODE_VISIBLE, struct); + + this.max = 64000; + this.min = -64000; + this.focused = false; + this.value = 0; + + this.realtime = false; +}; + +(function() { + this.implement( + + apf.DataAction + + + ); + + this.$supportedProperties.push("width", "value", "max", "min", "caption", "realtime"); + + this.$booleanProperties["realtime"] = true; + + this.$propHandlers["value"] = function(value) { + value = parseInt(value) || 0; + + this.value = this.oInput.value = (value > this.max + ? this.max + : (value < this.min + ? this.min + : value)); + }; + + this.$propHandlers["min"] = function(value) { + if (!(value = parseInt(value))) return; + this.min = value; + if (value > this.value) + this.change(value); + }; + + this.$propHandlers["max"] = function(value) { + if (!(value = parseInt(value))) return; + this.max = value; + + if (value < this.value) + this.change(value); + }; + + /* ******************************************************************** + PUBLIC METHODS + *********************************************************************/ + + + + /** + * Sets the value of this element. This should be one of the values + * specified in the values attribute. + * @param {String} value the new value of this element + */ + this.setValue = function(value) { + this.setProperty("value", value, false, true); + }; + + /** + * Returns the current value of this element. + * @return {String} + */ + this.getValue = function() { + return this.value; + }; + + this.increment = function() { + this.change(parseInt(this.oInput.value) + 1); + }; + + this.decrement = function() { + this.change(parseInt(this.oInput.value) - 1); + }; + + + + this.$enable = function() { + this.oInput.disabled = false; + this.$setStyleClass(this.oInput, "", ["inputDisabled"]); + }; + + this.$disable = function() { + this.oInput.disabled = true; + this.$setStyleClass(this.oInput, "inputDisabled"); + }; + + this.$focus = function(e) { + if (!this.$ext || this.focused) //this.disabled || + return; + + + if (apf.hasFocusBug) + apf.sanitizeTextbox(this.oInput); + + + this.focused = true; + this.$setStyleClass(this.oInput, "focus"); + this.$setStyleClass(this.$buttonPlus, "plusFocus"); + this.$setStyleClass(this.$buttonMinus, "minusFocus"); + + if (this.oLeft) + this.$setStyleClass(this.oLeft, "leftFocus"); + }; + + this.$blur = function(e) { + if (!this.$ext && !this.focused) + return; + + this.$setStyleClass(this.oInput, "", ["focus"]); + this.$setStyleClass(this.$buttonPlus, "", ["plusFocus"]); + this.$setStyleClass(this.$buttonMinus, "", ["minusFocus"]); + + if (this.oLeft) + this.$setStyleClass(this.oLeft, "" ["leftFocus"]); + + this.setValue(this.oInput.value); + + this.focused = false; + }; + + /* *********************** + Keyboard Support + ************************/ + + this.addEventListener("keydown", function(e) { + var key = e.keyCode, + + keyAccess = (key < 8 || (key > 9 && key < 37 && key !== 12) + || (key > 40 && key < 46) || (key > 46 && key < 48) + || (key > 57 && key < 96) || (key > 105 && key < 109 && key !== 107) + || (key > 109 && key !== 189)); + + if (keyAccess) + return false; + + switch(key) { + case 38://Arrow up + this.increment(); + break; + case 40://Arrow down + this.decrement(); + break; + } + }, true); + + this.addEventListener("keyup", function(e) { + //this.setValue(this.oInput.value); + }, true); + + + this.increment = function() { + this.change(parseInt(this.oInput.value) + 1); + }; + + this.decrement = function() { + this.change(parseInt(this.oInput.value) - 1); + }; + + /** + * @event click Fires when the user presses a mousebutton while over this element and then let's the mousebutton go. + * @event mouseup Fires when the user lets go of a mousebutton while over this element. + * @event mousedown Fires when the user presses a mousebutton while over this element. + */ + this.$draw = function() { + var _self = this; + + //Build Main Skin + this.$ext = this.$getExternal(null, null, function(oExt) { + oExt.setAttribute("onmousedown", + 'if (!this.host.disabled) \ + this.host.dispatchEvent("mousedown", {htmlEvent : event});'); + oExt.setAttribute("onmouseup", + 'if (!this.host.disabled) \ + this.host.dispatchEvent("mouseup", {htmlEvent : event});'); + oExt.setAttribute("onclick", + 'if (!this.host.disabled) \ + this.host.dispatchEvent("click", {htmlEvent : event});'); + }); + + this.$int = this.$getLayoutNode("main", "container", this.$ext); + this.oInput = this.$getLayoutNode("main", "input", this.$ext); + this.$buttonPlus = this.$getLayoutNode("main", "buttonplus", this.$ext); + this.$buttonMinus = this.$getLayoutNode("main", "buttonminus", this.$ext); + this.oLeft = this.$getLayoutNode("main", "left", this.$ext); + + + apf.sanitizeTextbox(this.oInput); + + + var timer, + doc = (!document.compatMode || document.compatMode == 'CSS1Compat') + ? document.html : document.body, + z = 0; + + /* Setting start value */ + this.oInput.value = this.value; + + this.oInput.onmousedown = function(e) { + if (_self.disabled) + return; + + e = e || window.event; + + clearTimeout(timer); + + var newval, + value = parseInt(this.value) || 0, + step = 0, + cy = e.clientY, + ot = _self.$int.offsetTop, ol = _self.$int.offsetLeft, + ow = _self.$int.offsetWidth, oh = _self.$int.offsetHeight, + func = function() { + clearTimeout(timer); + timer = $setTimeout(func, 10); + if (!step) + return; + + newval = value + step; + if (newval <= _self.max && newval >= _self.min) { + value += step; + value = Math.round(value); + _self.oInput.value = value; + + if (_self.realtime) + _self.change(value); + } + else { + _self.oInput.value = step < 0 + ? _self.min + : _self.max; + } + }; + func(); + + function calcStep(e) { + e = e || window.event; + var x = e.pageX || e.clientX + (doc ? doc.scrollLeft : 0), + y = e.pageY || e.clientY + (doc ? doc.scrollTop : 0), + nrOfPixels = cy - y; + + if ((y > ot && x > ol) && (y < ot + oh && x < ol + ow)) { + step = 0; + return; + } + + step = Math.pow(Math.min(200, Math.abs(nrOfPixels)) / 10, 2) / 10; + if (nrOfPixels < 0) + step = -1 * step; + } + + document.onmousemove = calcStep; + + document.onmouseup = function(e) { + clearTimeout(timer); + + var value = parseInt(_self.oInput.value); + + if (value != _self.value) + _self.change(value); + document.onmousemove = document.onmouseup = null; + }; + }; + + /* Fix for mousedown for IE */ + var buttonDown = false; + this.$buttonPlus.onmousedown = function(e) { + if (_self.disabled) + return; + + e = e || window.event; + buttonDown = true; + + var value = (parseInt(_self.oInput.value) || 0) + 1, + func = function() { + clearTimeout(timer); + timer = $setTimeout(func, 50); + z++; + value += Math.pow(Math.min(200, z) / 10, 2) / 10; + value = Math.round(value); + + _self.oInput.value = value <= _self.max + ? value + : _self.max; + + if (_self.realtime) + _self.change(value <= _self.max ? value : _self.max); + }; + + apf.setStyleClass(this, "plusDown", ["plusHover"]); + + func(); + }; + + this.$buttonMinus.onmousedown = function(e) { + if (_self.disabled) + return; + + e = e || window.event; + buttonDown = true; + + var value = (parseInt(_self.oInput.value) || 0) - 1, + func = function() { + clearTimeout(timer); + timer = $setTimeout(func, 50); + z++; + value -= Math.pow(Math.min(200, z) / 10, 2) / 10; + value = Math.round(value); + + _self.oInput.value = value >= _self.min + ? value + : _self.min; + + if (_self.realtime) + _self.change(value >= _self.min ? value : _self.min); + }; + + apf.setStyleClass(this, "minusDown", ["minusHover"]); + + func(); + }; + + this.$buttonMinus.onmouseout = function(e) { + if (_self.disabled) + return; + + clearTimeout(timer); + z = 0; + + var value = parseInt(_self.oInput.value); + + if (value != _self.value) + _self.change(value); + + apf.setStyleClass(this, "", ["minusHover"]); + + if (!_self.focused) + _self.$blur(e); + }; + + this.$buttonPlus.onmouseout = function(e) { + if (_self.disabled) + return; + + clearTimeout(timer); + z = 0; + + var value = parseInt(_self.oInput.value); + + if (value != _self.value) + _self.change(value); + + apf.setStyleClass(this, "", ["plusHover"]); + + if (!_self.focused) + _self.$blur(e); + }; + + this.$buttonMinus.onmouseover = function(e) { + if (_self.disabled) + return; + + apf.setStyleClass(this, "minusHover"); + }; + + this.$buttonPlus.onmouseover = function(e) { + if (_self.disabled) + return; + + apf.setStyleClass(this, "plusHover"); + }; + + this.$buttonPlus.onmouseup = function(e) { + if (_self.disabled) + return; + + e = e || event; + //e.cancelBubble = true; + apf.cancelBubble(e, this); + + apf.setStyleClass(this, "plusHover", ["plusDown"]); + + clearTimeout(timer); + z = 0; + + var value = parseInt(_self.oInput.value); + + if (!buttonDown) { + value++; + _self.oInput.value = value; + } + else { + buttonDown = false; + } + + if (value != _self.value) + _self.change(value); + }; + + this.$buttonMinus.onmouseup = function(e) { + if (_self.disabled) + return; + + e = e || event; + //e.cancelBubble = true; + apf.cancelBubble(e, this); + + apf.setStyleClass(this, "minusHover", ["minusDown"]); + + clearTimeout(timer); + z = 0; + + var value = parseInt(_self.oInput.value); + + if (!buttonDown) { + value--; + _self.oInput.value = value; + } + else { + buttonDown = false; + } + + + if (value != _self.value) + _self.change(value); + }; + + this.oInput.onselectstart = function(e) { + e = e || event; + e.cancelBubble = true; + }; + + this.oInput.host = this; + }; + + this.$destroy = function() { + this.oInput.onkeypress = + this.oInput.onmousedown = + this.oInput.onkeydown = + this.oInput.onkeyup = + this.oInput.onselectstart = + this.$buttonPlus.onmouseover = + this.$buttonPlus.onmouseout = + this.$buttonPlus.onmousedown = + this.$buttonPlus.onmouseup = + this.$buttonMinus.onmouseover = + this.$buttonMinus.onmouseout = + this.$buttonMinus.onmousedown = + this.$buttonMinus.onmouseup = null; + }; + + + + +}).call(apf.spinner.prototype = new apf.StandardBinding()); + + +apf.aml.setElement("spinner", apf.spinner); + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/elements/splitbutton.js)SIZE(4470)TIME(Mon, 10 Jan 2011 10:53:12 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + + +/** + * Element displaying a skinnable rectangle which can contain other + * aml elements. This element is used by other elements such as the + * toolbar and statusbar element to specify sections within those elements + * which in turn can contain other aml elements. + * Remarks: + * This component is used in the accordion element to create its sections. In + * the statusbar the panel element is an alias of bar. + * + * @constructor + * + * @define bar, panel, menubar + * @attribute {String} icon the url pointing to the icon image. + * @attribute {Boolean} collapsed collapse panel on load, default is false + * Possible values: + * true panel is collapsed + * false panel is not collapsed + * @attribute {String} title describes content in panel + * @allowchild button + * @allowchild {elements}, {anyaml} + * @addnode elements + * + * @author Ruben Daniels (ruben AT ajax DOT org) + * @version %I%, %G% + * @since 0.4 + */ +apf.splitbutton = function(struct, tagName){ + this.$init(tagName || "splitbutton", apf.NODE_VISIBLE, struct); +}; + +(function(){ + this.$focussable = false; + + this.$propHandlers["caption"] = function(value) { + this.$button1.setProperty("caption", value); + } + + this.$propHandlers["icon"] = function(value) { + this.$button1.setProperty("icon", value); + } + + this.$propHandlers["disabled"] = function(value) { + this.$button1.setProperty("disabled", value); + this.$button2.setProperty("disabled", value); + } + + this.$propHandlers["submenu"] = function(value) { + this.$button2.setProperty("submenu", value); + var _self = this; + self[value].addEventListener("display", function() { + this.$ext.style.marginLeft = "-" + _self.$button1.$ext.offsetWidth + "px"; + }); + } + + this.$draw = function(){ + var _self = this; + this.$ext = this.$pHtmlNode.appendChild(document.createElement("div")); + var skin = this.getAttribute("skin") || this.localName; + + this.$button1 = new apf.button({ + htmlNode: this.$ext, + parentNode: this, + skin: skin, + "class": "main", + onmouseover: function() { + apf.setStyleClass(this.$ext, "primary"); + _self.$button2.$setState("Over", {}); + }, + onmouseout: function() { + apf.setStyleClass(this.$ext, "", ["primary"]); + _self.$button2.$setState("Out", {}); + }, + onclick: function(e) { + _self.dispatchEvent("click"); + } + }); + + this.$button2 = new apf.button({ + htmlNode: this.$ext, + parentNode: this, + skin: skin, + "class": "arrow", + onmouseover: function() { + apf.setStyleClass(this.$ext, "primary"); + _self.$button1.$setState("Over", {}); + }, + onmouseout: function() { + if(!_self.$button2.value) { + apf.setStyleClass(this.$ext, "", ["primary"]); + _self.$button1.$setState("Out", {}); + } + else { + apf.setStyleClass(this.$ext, "primary"); + _self.$button1.$setState("Over", {}); + } + } + }); + }; + + this.$loadAml = function(x){ + + }; + +}).call(apf.splitbutton.prototype = new apf.GuiElement()); + +apf.aml.setElement("splitbutton", apf.splitbutton); + + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/elements/splitter.js)SIZE(14357)TIME(Mon, 10 Jan 2011 10:53:12 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + + +/** + * @constructor + * @private + */ +apf.splitter = function(struct, tagName){ + this.$init(tagName || "splitter", apf.NODE_VISIBLE, struct); +}; + +(function() { + this.$focussable = false; // This object can get the focus + this.$splitter = true; + + this.$booleanProperties["realtime"] = true; + + this.$propHandlers["realtime"] = function(value){ + this.$setStyleClass(this.$ext, value && (this.$baseCSSname + "Realtime") || "", + [this.$baseCSSname + "Realtime"]); + } + + this.$propHandlers["type"] = function(value){ + this.$setStyleClass(this.$ext, value, + [value == "horizontal" ? "vertical" : "horizontal"]); + + if (value == "vertical") + this.$setStyleClass(this.$ext, "w-resize", ["n-resize"]); + else + this.$setStyleClass(this.$ext, "n-resize", ["w-resize"]); + + //Optimize this to not recalc for certain cases + if (value == "horizontal") { + this.$info = { + pos : "top", + opos : "left", + size : "width", + osize : "height", + offsetPos : "offsetTop", + offsetSize : "offsetHeight", + oOffsetPos : "offsetLeft", + oOffsetSize : "offsetWidth", + clientPos : "clientY", + d1 : 1, + d2 : 0, + x1 : 0, + x2 : 2 + }; + } + else { + this.$info = { + pos : "left", + opos : "top", + size : "height", + osize : "width", + offsetPos : "offsetLeft", + offsetSize : "offsetWidth", + oOffsetPos : "offsetTop", + oOffsetSize : "offsetHeight", + clientPos : "clientX", + d1 : 0, + d2 : 1, + x1 : 3, + x2 : 1 + } + } + } + + this.addEventListener("DOMNodeInserted", function(e){ + if (e.currentTarget != this) + return; + + /*if (e.$oldParent) { + e.$oldParent.removeEventListener("DOMNodeInserted", this.$siblingChange); + e.$oldParent.removeEventListener("DOMNodeRemoved", this.$siblingChange); + }*/ + + this.init(); + }); + + /*this.$siblingChange = function(e){ + //if (e.currentTarget + + //this.init(); + }*/ + + this.update = function(newPos, finalPass){ + with (this.$info) { + //var pos = Math.ceil(apf.getAbsolutePosition(this.$ext, this.parentNode.$int)[d1] - posPrev[d1]); + var max = this.$previous.$ext[offsetSize] + this.$next.$ext[offsetSize]; + var method = finalPass ? "setAttribute" : "setProperty"; + if (apf.hasFlexibleBox) + newPos -= apf.getAbsolutePosition(this.$previous.$ext, this.parentNode.$int)[d1]; + + //Both flex + if ((this.$previous.flex || this.$previous.flex === 0) && (this.$next.flex || this.$next.flex === 0)) { + if (!finalPass && !this.realtime) + newPos -= this.$ext[offsetSize]; + + //var totalFlex = this.$previous.flex + this.$next.flex - (finalPass && !this.realtime ? this.parentNode.padding : 0); + this.$previous[method]("flex", newPos); + this.$next[method]("flex", this.$totalFlex - newPos); + } + //Fixed + else { + if (!this.$next.flex) + this.$next[method](osize, max - newPos); + if (!this.$previous.flex) + this.$previous[method](osize, newPos); + } + } + + if (apf.hasSingleResizeEvent) + apf.layout.forceResize(this.$ext.parentNode); + }; + + this.$setSiblings = function(){ + this.$previous = this.previousSibling; + while(this.$previous && (this.$previous.nodeType != 1 + || this.$previous.visible === false + || this.$previous.nodeFunc != apf.NODE_VISIBLE)) + this.$previous = this.$previous.previousSibling; + this.$next = this.nextSibling; + while(this.$next && (this.$next.nodeType != 1 + || this.$next.visible === false + || this.$next.nodeFunc != apf.NODE_VISIBLE)) + this.$next = this.$next.nextSibling; + } + + this.init = function(size, refNode, oItem){ + //this.parentNode.addEventListener("DOMNodeInserted", this.$siblingChange); + //this.parentNode.addEventListener("DOMNodeRemoved", this.$siblingChange); + + this.$setSiblings(); + + this.$thickness = null; + if (this.parentNode.$box) { + this.setProperty("type", this.parentNode.localName == "vbox" + ? "horizontal" + : "vertical"); + this.$thickness = parseInt(this.parentNode.padding); + } + + with (this.$info) { + var diff = apf.getDiff(this.$ext); + if (!this.parentNode.$box) { + var iSize = Math.max( + this.$previous.$ext[offsetSize], this.$next.$ext[offsetSize]); + this.$ext.style[size] = (iSize - diff[d1]) + "px"; + } + + var iThick = this[osize] = this.$thickness + || (this.$next[oOffsetPos] - this.$previous[oOffsetPos] + - this.$previous[oOffsetSize]); + + this.$ext.style[osize] = (iThick - diff[d2]) + "px"; + } + + return this; + }; + + this.$draw = function(){ + //Build Main Skin + this.$ext = this.$getExternal(); + + var _self = this; + this.$ext.onmousedown = function(e){ + if (!e) + e = event; + + _self.$setSiblings(); + + var changedPosition, pHtml = _self.parentNode.$int; + if ("absolute|fixed|relative".indexOf(apf.getStyle(pHtml, "position")) == -1) { + pHtml.style.position = "relative"; + changedPosition = true; + } + + _self.$totalFlex = 0; + with (_self.$info) { + var posPrev = apf.getAbsolutePosition(_self.$previous.$ext, _self.parentNode.$int); + var min = posPrev[d1] || 0; + var posNext = apf.getAbsolutePosition(_self.$next.$ext, _self.parentNode.$int); + var max = posNext[d1] + _self.$next.$ext[offsetSize] - this[offsetSize]; + + //Set flex to pixel sizes + if ((_self.$previous.flex || _self.$previous.flex === 0) + && (_self.$next.flex || _self.$next.flex === 0)) { + var set = [], nodes = _self.parentNode.childNodes, padding = 0; + for (var node, i = 0, l = nodes.length; i < l; i++) { + if ((node = nodes[i]).visible === false + || node.nodeFunc != apf.NODE_VISIBLE || node.$splitter) + continue; + + if (node.flex) + set.push(node, node.$ext[offsetSize] + + (apf.hasFlexibleBox && !_self.realtime && node == _self.$previous + ? 2 * _self.parentNode.padding : 0)); + } + for (var i = 0, l = set.length; i < l; i+=2) { + set[i].setAttribute("flex", set[i+1]); + } + } + + _self.$totalFlex += _self.$next.flex + _self.$previous.flex; + + var startPos, startOffset; + if (apf.hasFlexibleBox) { + var coords = apf.getAbsolutePosition(this); + startPos = e[clientPos] - coords[d1]; + + if (!_self.realtime) { + if (apf.hasFlexibleBox) { + if (_self.$previous.flex && !_self.$next.flex) { + var mBox = apf.getBox(_self.$next.margin); + mBox[x1] = _self.parentNode.padding; + _self.$next.$ext.style.margin = mBox.join("px ") + "px"; + } + else { + var mBox = apf.getBox(_self.$previous.margin); + mBox[x2] = _self.parentNode.padding; + _self.$previous.$ext.style.margin = mBox.join("px ") + "px"; + } + } + + var diff = apf.getDiff(this); + this.style.left = coords[0] + "px"; + this.style.top = coords[1] + "px"; //(apf.getHtmlTop(this) - Math.ceil(this.offsetHeight/2)) + this.style.width = (this.offsetWidth - diff[0]) + "px"; + this.style.height = (this.offsetHeight - diff[1]) + "px"; + this.style.position = "absolute"; + } + } + else { + var coords = apf.getAbsolutePosition(this.offsetParent); + startOffset = apf.getAbsolutePosition(_self.$previous.$ext)[d1]; + startPos = e[clientPos] - coords[d1]; + + if (!_self.realtime) { + this.style.left = "0px"; + this.style.top = "0px"; + this.style.position = "relative"; + } + min = -1000; //@todo + } + } + + e.returnValue = false; + e.cancelBubble = true; + + + apf.plane.show(this); + + + _self.$setStyleClass(this, _self.$baseCSSname + "Moving"); + + _self.$setStyleClass(document.body, + _self.type == "vertical" ? "w-resize" : "n-resize", + [_self.type == "vertical" ? "n-resize" : "w-resize"]); + + //@todo convert to proper way + document.onmouseup = function(e){ + if(!e) e = event; + + with (_self.$info) { + var newPos; + if (e[clientPos] >= 0) { + var coords = apf.getAbsolutePosition(_self.$ext.offsetParent); + newPos = (Math.min(max, Math.max(min, (e[clientPos] - coords[d1]) - + (apf.hasFlexibleBox ? startPos : startOffset)))); + } + } + + _self.$setStyleClass(_self.$ext, "", [_self.$baseCSSname + "Moving"]); + _self.$setStyleClass(document.body, "", ["n-resize", "w-resize"]); + + if (changedPosition) + pHtml.style.position = ""; + + if (apf.hasFlexibleBox && !_self.realtime) + (_self.$previous.flex && !_self.$next.flex + ? _self.$next : _self.$previous).$ext.style.margin + = apf.getBox(_self.$previous.margin).join("px ") + "px"; + + if (newPos) + _self.update(newPos, true); + + + apf.plane.hide(); + + + if (!_self.realtime) { + _self.$ext.style.left = ""; + _self.$ext.style.top = ""; + _self.$ext.style[_self.$info.size] = ""; + _self.$ext.style.position = ""; + } + + document.onmouseup = + document.onmousemove = null; + }; + + //@todo convert to proper way + document.onmousemove = function(e){ + if(!e) e = event; + + with (_self.$info) { + var newPos; + if (e[clientPos] >= 0) { + var coords = apf.getAbsolutePosition(_self.$ext.offsetParent); + newPos = (Math.min(max, Math.max(min, (e[clientPos] - coords[d1]) - + (apf.hasFlexibleBox || !_self.realtime ? startPos : startOffset)))); + + if (_self.realtime) + _self.update(newPos); + else { + _self.$ext.style[pos] = newPos + "px"; + } + } + } + + e.returnValue = false; + e.cancelBubble = true; + }; + } + + apf.queue.add("splitter" + this.$uniqueId, function(){ + _self.init(); + }); + }; + + this.$loadAml = function(x){ + if (this.realtime !== false) // && (!apf.isIE || apf.isIE > 8)) + this.$propHandlers.realtime.call(this, this.realtime = true); + }; +}).call(apf.splitter.prototype = new apf.Presentation()); + +apf.aml.setElement("splitter", apf.splitter); + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/elements/state-group.js)SIZE(3131)TIME(Mon, 10 Jan 2011 10:53:12 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + +/** + * Element that groups state elements together and + * provides a way to set a default state. + * Example: + * + * + * + * + * + * + * + * + * @addnode elements + * @see element.state + * + * @author Ruben Daniels (ruben AT ajax DOT org) + * @version %I%, %G% + * @since 0.4 + */ +apf.stateGroup = function(){ + this.$init("state-group", apf.NODE_HIDDEN); +}; +apf.aml.setElement("state-group", apf.stateGroup); + +(function(){ + this.$handlePropSet = function(prop, value, force){ + if (prop == "id") + return; + + var node, nodes = this.childNodes; + for (var i = 0, l = nodes.length; i < l; i++){ + node = nodes[i]; + + if (node.nodeType != 1 || node.localName != "state") + continue; + + if (!node[prop] || node.$inheritProperties[prop] == 2) { + node.$inheritProperties[prop] = 2; + node.setProperty(prop, value); + } + } + }; + + //@todo this should use text node insertion + this.addEventListener("DOMNodeInsertedIntoDocument", function(e){ + if (!this.id) + this.id = "stategroup" + this.$uniqueId; + + //apf.StateServer.addGroup(this.id, null, this.parentNode); //@todo rearch this + + var nodes = this.childNodes; + for (var i = 0, l = nodes.length; i < l; i++){ + nodes[i].setProperty("group", this.id); + } + }); +}).call(apf.stateGroup.prototype = new apf.AmlElement()); + + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/elements/state.js)SIZE(11225)TIME(Mon, 10 Jan 2011 10:53:12 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + +/** + * @private + */ +apf.StateServer = { + states: {}, + groups: {}, + locs : {}, + + removeGroup: function(name, elState){ + this.groups[name].remove(elState); + if (!this.groups[name].length) { + if (self[name]) { + self[name].destroy(); + self[name] = null; + } + + delete this.groups[name]; + } + }, + + addGroup: function(name, elState, pNode){ + if (!this.groups[name]) { + this.groups[name] = []; + + var pState = new apf.state({ + id : name + }); + pState.parentNode = pNode; + //pState.implement(apf.AmlNode); + //pState.name = name; + pState.toggle = function(){ + for (var next = 0, i = 0; i < apf.StateServer.groups[name].length; i++) { + if (apf.StateServer.groups[name][i].active) { + next = i + 1; + break; + } + } + + apf.StateServer.groups[name][ + (next == apf.StateServer.groups[name].length) ? 0 : next + ].activate(); + } + + this.groups[name].pState = self[name] = pState; + } + + if (elState) + this.groups[name].push(elState); + + return this.groups[name].pState; + }, + + removeState: function(elState){ + delete this.states[elState.name]; + }, + + addState: function(elState){ + this.states[elState.name] = elState; + } +} + +/** + * Element that specifies a certain state of (a part of) the application. With + * state we mean a collection of properties on objects that have a certain + * value at one time. This element allows you to specify which properties on + * which elements should be set when a state is activated. This element can + * belong to a state-group containing multiple elements with a default state. + * Example: + * This example shows a log in window and four state elements in a state-group. + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * Username + * + * + * Password + * + * + * + * Log in + * + * Log out + * + * Example: + * This example shows a label using property binding to get it's caption + * based on the current state. + * + * + * + * + * + * Become admin + * + * + * @event change Fires when the active property of this element changes. + * + * @constructor + * @define state + * @addnode global + * + * @author Ruben Daniels (ruben AT ajax DOT org) + * @version %I%, %G% + * @since 0.9 + */ +apf.state = function(struct, tagName){ + this.$init(tagName || "state", apf.NODE_HIDDEN, struct); + + this.$signalElements = []; + this.$groupAdded = {}; + this.$locationAdded = ''; +}; + +(function(){ + /**** Properties and Attributes ****/ + + this.$supportedProperties.push("active"); + this.$booleanProperties["active"] = true; + + /** + * @attribute {Boolean} active whether this state is the active state + */ + this.$propHandlers["active"] = function(value){ + //Activate State + if (apf.isTrue(value)) { + if (this.group) { + var nodes = apf.StateServer.groups[this.group]; + if (!nodes) { + apf.StateServer.addGroup(this.group, this); + nodes = apf.StateServer.groups[this.group]; + } + + for (var i = 0; i < nodes.length; i++) { + if (nodes[i] != this && nodes[i].active !== false) + nodes[i].deactivate(); + } + } + + var q = this.$signalElements; + for (var i = 0; i < q.length; i++) { + if (!self[q[i][0]] || !self[q[i][0]].setProperty) { + + /*throw new Error(apf.formatErrorString(1013, this, + "Setting State", + "Could not find object to give state: '" + + q[i][0] + "' on property '" + q[i][1] + "'"));*/ + apf.console.warn("Could not find object to give state: " + + q[i][0] + "' on property '" + q[i][1] + "'"); + + + continue; + } + + self[q[i][0]].setProperty(q[i][1], this[q[i].join(".")]); + } + + if (this.group) { + var attr = this.attributes; + for (var i = 0; i < attr.length; i++) { + if (attr[i].nodeName.match(/^on|^(?:group|id)$|^.*\..*$/)) + continue; + self[this.group].setProperty(attr[i].nodeName, + attr[i].nodeValue); + } + apf.StateServer.groups[this.group].pState.dispatchEvent("change"); + } + + this.dispatchEvent("activate"); + + + apf.console.info("Setting state '" + this.name + "' to ACTIVE"); + + } + + //Deactivate State + else { + this.setProperty("active", false); + this.dispatchEvent("deactivate"); + + + apf.console.info("Setting state '" + this.name + "' to INACTIVE"); + + } + }; + + + /**** Public methods ****/ + + + + /** + * Sets the value of this element. This should be one of the values + * specified in the values attribute. + * @param {String} value the new value of this element + */ + this.setValue = function(value){ + this.active = 9999; + this.setProperty("active", value, false, true); + }; + + /** + * Actives this state, setting all the properties on the elements that + * were specified. + */ + this.activate = function(){ + this.active = 9999; + this.setProperty("active", true, false, true); + }; + + /** + * Deactivates the state of this element. This is mostly a way to let all + * elements that have property bound to this state know it is no longer + * active. + */ + this.deactivate = function(){ + this.setProperty("active", false, false, true); + }; + + + + /**** Init ****/ + + this.$propHandlers["group"] = function(value){ + if (value) { + apf.StateServer.addGroup(value, this); + this.$groupAdded = {'value' : value, elState : this}; + } + else { + apf.StateServer.removeGroup(this.$groupAdded.value, this.$groupAdded.elState); + this.$groupAdded = {}; + } + } + + this.$propHandlers["location"] = function(value){ + if (value) { + apf.StateServer.locs[value] = this; + this.$locationAdded = value; + } + else { + delete apf.StateServer.locs[this.$locationAdded]; + this.$locationAdded = ''; + } + } + + this.addEventListener("DOMNodeInsertedIntoDocument", function(e){ + apf.StateServer.addState(this); + + //Properties initialization + var attr = this.attributes; + for (var s, i = 0; i < attr.length; i++) { + s = attr[i].nodeName.split("."); + if (s.length == 2) + this.$signalElements.push(s); + } + }); + + this.addEventListener("DOMNodeRemovedFromDocument", function(){ + this.$signalElements = null; + apf.StateServer.removeState(this); + if (this.group) + apf.StateServer.removeGroup(this.group, this); + }); +}).call(apf.state.prototype = new apf.AmlElement()); + +apf.aml.setElement("state", apf.state); + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/elements/statusbar.js)SIZE(3824)TIME(Mon, 10 Jan 2011 10:53:12 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + + +/** + * Element displaying a bar consisting of bars containing other text, icons + * and more aml. This element is usually placed in the bottom of the screen to + * display context sensitive and other information about the state of the + * application. + * Example: + * + * + * Ajax.org + * Some status information + * + * + * + * + * + * + * @constructor + * @define statusbar + * @allowchild bar + * @allowchild progressbar + * @addnode elements + * + * @author Ruben Daniels (ruben AT ajax DOT org) + * @version %I%, %G% + * @since 0.9 + */ +apf.statusbar = function(struct, tagName){ + this.$init(tagName || "statusbar", apf.NODE_VISIBLE, struct); +}; + +(function(){ + this.$focussable = false; + + /**** DOM Hooks ****/ + var insertChild; + + this.addEventListener("AMLRemoveChild", function(amlNode, doOnlyAdmin){ + if (doOnlyAdmin) + return; + + }); + + this.addEventListener("AMLInsert",insertChild = function (amlNode, beforeNode, withinParent){ + if (amlNode.tagName != "bar") + return; + + amlNode.$propHandlers["caption"] = function(value){ + apf.setNodeValue( + this.$getLayoutNode("bar", "caption", this.$ext), value); + } + amlNode.$propHandlers["icon"] = function(value){ + var oIcon = this.$getLayoutNode("bar", "icon", this.$ext); + if (!oIcon) return; + + if (value) + this.$setStyleClass(this.$ext, this.$baseCSSname + "Icon"); + else + this.$setStyleClass(this.$ext, "", [this.$baseCSSname + "Icon"]); + + if (oIcon.tagName == "img") + oIcon.setAttribute("src", value ? this.iconPath + value : ""); + else { + oIcon.style.backgroundImage = value + ? "url(" + this.iconPath + value + ")" + : ""; + } + } + }); + + + /**** Init ****/ + + this.$draw = function(){ + //Build Main Skin + this.$ext = this.$getExternal(); + this.$int = this.$getLayoutNode("main", "container", this.$ext); + }; + + this.$loadAml = function(x){ + var nodes = this.childNodes; + for (var i = nodes.length - 1; i >= 0; i--) { + if (nodes[i].localName == "section") { + nodes[i].addEventListener("DOMNodeInsertedIntoDocument", function(){ + this.$setStyleClass(this.$ext, this.$baseCSSname + "Last"); + }); + break; + } + } + }; +}).call(apf.statusbar.prototype = new apf.Presentation()); + +apf.aml.setElement("statusbar", apf.statusbar); + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/elements/style.js)SIZE(1888)TIME(Mon, 10 Jan 2011 10:53:12 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + + +/** + * @todo description + * + * @author Ruben Daniels (ruben AT ajax DOT org) + * @version %I%, %G% + * @since 0.4 + */ +apf.style = function(struct, tagName){ + this.$init(tagName || "style", apf.NODE_HIDDEN, struct); +}; + +(function(){ + this.$focussable = false; + + this.$propHandlers["src"] = function(value){ + apf.getData(value, { + callback : function(data, state){ + if (state == apf.SUCCESS) { + apf.importCssString(data); + } + } + }); + } + + this.addEventListener("DOMNodeInsertedIntoDocument", function(e){ + if (this.type != "text/chartcss" && this.firstChild) + apf.importCssString(this.firstChild.nodeValue); + }); +}).call(apf.style.prototype = new apf.AmlElement()); + +apf.aml.setElement("style", apf.style); +apf.xhtml.setElement("style", apf.style); + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/elements/submitform.js)SIZE(30092)TIME(Mon, 10 Jan 2011 10:53:12 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + + +/** + * Element allowing special form functionality to a set of AML + * elements. This element is an alias for xforms offering + * xform compatible strategies with relation to submitting the form's data. + * This element also offers form paging, including validation across pages. + * Buttons placed inside this element can contain an action + * attribute specifying whether they behave as next, previous or finish(submit) + * buttons. This element is not necesary for simple forms, like the + * normal html webforms (see {@link baseclass.validation}). This element should + * be used when multi page forms are required, or a wizard style form. + * + * @constructor + * @define submitform, xforms + * @allowchild page, {elements}, {anyaml} + * @addnode elements + * + * @inherits apf.StandardBinding + * @inherits apf.BaseTab + * @inherits apf.ValidationGroup + * + * @author Ruben Daniels (ruben AT ajax DOT org) + * @version %I%, %G% + * @since 0.9 + * + * @default_private + * + * @todo please refactor. This element should be cleared of most its 'features' its all bollocks. + */ + +apf.xforms = function(struct, tagName){ + this.$init(tagName || "xforms", apf.NODE_VISIBLE, struct); +}; + +apf.submitform = function(struct, tagName){ + this.$init(tagName || "submitform", apf.NODE_VISIBLE, struct); +}; + +(function(){ + this.implement( + + apf.DataAction, + + apf.BaseTab, + apf.ValidationGroup + ); + + this.$focussable = false; + + this.elements = {}; + var buttons = { + "next" : [], + "previous" : [], + "submit" : [], + "follow" : [] + }; + + this.$focussable = false; + //this.allowMultipleErrors = true; + + this.inputs = []; + this.errorEl = {}; + this.cq = {}; + this.reqs = []; + this.conditionDeps = {}; + this.depends = {}; + + this.loadValueDeps = {}; + this.loadValues = {}; + + this.listsHeldBack = {}; + this.nextHeldBack = {}; + + this.activepagenr = 0; + this.zCount = 1000000; + + this.clear = function(){}; + + /* ******************************************************************** + PUBLIC METHODS + *********************************************************************/ + + this.showLoader = function(checked, nr){ + if (checked) { + var page = nr ? this.getPage(nr) : this.getNextPage(); + if (!page || page.$rendered) return; + } + + if (this.loadState) { + this.loadState.style.display = "block"; + + var message = this.getPage().getAttribute("loadmessage"); + if (message) + (apf.queryNode(this.loadState, "div[@class='msg']") + || this.loadState).innerHTML = message; + } + }; + + this.hideLoader = function(){ + if (this.loadState) + this.loadState.style.display = "none"; + }; + + var nextpagenr; + this.getNextPage = function(){ + var nextpage, pageNr = this.activepagenr; + do { + nextpage = this.getPage(++pageNr); + } + while(nextpage && !this.testCondition(nextpage.condition)); + + nextpagenr = pageNr; + return nextpage; + }; + + this.next = function(no_error){ + if (!this.testing && !this.isValid(null, null, this.getPage())) { + this.hideLoader(); + return;//checkRequired + } + + //var nextpage = nextpagenr ? this.getPage(nextpagenr) : this.getNextPage(); + //if(this.dispatchEvent("beforeswitch", nextpagenr) === false)return false; + + //this.getPage().hide(); + this.set(this.activepagenr + 1);//nextpagenr + //this.activepagenr = nextpagenr; + + //if(!no_error && !nextpage) throw new Error(apf.formatErrorString(1006, this, "Form", "End of pages reached.")); + + //nextpage.show(); + //if(nextpage.$rendered) this.hideLoader(); + //else nextpage.addEventListener("afterrender", function(){this.parentNode.hideLoader()}); + + for (var prop in buttons) { + if (!prop.match(/next|previous|submit/)) continue; + this.updateButtons(prop); + } + + nextpagenr = null; + + /*var amlNode = this; + $setTimeout(function(){ + amlNode.dispatchEvent("afterswitch", amlNode.activepagenr, nextpage); + }, 1);*/ + }; + + this.previous = function(){ + //var active = this.activepagenr; + //do{var prevpage = this.getPage(--active);} + //while(prevpage && !this.testCondition(prevpage.condition)); + + //if(this.dispatchEvent("beforeswitch", active) === false) return false; + + this.set(this.activepagenr - 1); + //this.getPage().hide(); + //this.activepagenr = active; + + //if(!prevpage) throw new Error(apf.formatErrorString(1006, this, "Form", "End of pages reached.")); + + //prevpage.show(); + + //if(prevpage.$rendered) this.hideLoader(); + //else prevpage.addEventListener("afterrender", function(){this.parentNode.hideLoader()}); + + for (var prop in buttons) { + if (!prop.match(/next|previous|submit/)) continue; + this.updateButtons(prop); + } + + //this.dispatchEvent("afterswitch", this.activepagenr); + }; + + this.$enable = function(){ + forbuttons('enable'); + }; + + this.$disable = function(){ + forbuttons('disable'); + }; + + function forbuttons(feat){ + var arr = ["next", "previous", "submit", "follow"]; + for (var k = 0; k < arr.length; k++) { + for (var i = 0; i < buttons[arr[k]].length; i++) { + buttons[arr[k]][i][feat](); + } + } + } + + this.processValueChange = function(oFormEl){ + if (this.conditionDeps[oFormEl.name]) { + var c = this.conditionDeps[oFormEl.name]; + for (var i = 0; i < c.length; i++) { + if (this.testCondition(c[i].condition)) + c[i].setActive(); + else + c[i].setInactive(); + } + } + + for (var prop in buttons) { + if (!prop.match(/next|previous|submit/)) continue; + this.updateButtons(prop); + } + + this.setLoadValues(oFormEl.name); + }; + + /* *********************** + Actions + ************************/ + + /* ******************************************************************** + PRIVATE METHODS + *********************************************************************/ + this.addInput = function(objEl){ + var name = objEl.name; + objEl.$validgroup = this; + + if (this.elements[name] && !this.elements[name].length) { + this.elements[name] = [this.elements[name]]; + this.elements[name].getValue = new Function( + "for (var i = 0; i < this.length; i++)\ + if (this[i].$int.checked)\ + return this[i].getValue();"); + } + + if (this.elements[name]) + this.elements[name].push(objEl); + else + this.elements[name] = objEl; + + if (this.cq[name]) { + for (var i = 0; i < this.cq[name].length; i++) { + this.cq[name][i][1].call(this.cq[name][i][0], objEl); + objEl.labelEl = this.cq[name][i][0]; + } + } + + if (objEl.getAttribute("dependson")) { + var o = self[objEl.getAttribute("dependson")]; + if (!this.depends[o.name]) + this.depends[o.name] = []; + this.depends[o.name].push(objEl); + objEl.setInactive(); + } + + if (objEl.nodeFunc == apf.NODE_VISIBLE) + objEl.setZIndex(--this.zCount); + + if (this.listsHeldBack[name]) { + var ld = this.listsHeldBack[name]; + this.loadLists(ld[0], ld[1], ld[2]); + this.listsHeldBack[name] = null; + } + + if (this.nQuest && objEl.getAttribute("checknext") == "true") { + if (this.lastEl) { + this.lastEl.nextEl = objEl; + objEl.prevEl = this.lastEl; + } + this.lastEl = objEl; + + if (objEl.prevEl && objEl.getAttribute("show") != "true" + && !this.nextHeldBack[name] && !objHasValue(objEl)) + objEl.setInactive(true); + else if (this.condActiveCheck[objEl.name]) + this.condActiveCheck[objEl.name].setActive(); + + //terrible code, but what the heck + if (this.condActiveCheck[objEl.name]) { + objEl.container = this.condActiveCheck[objEl.name]; + + function activateHandler(){ + if (this.form.hasActiveElement(this.container)) + this.container.setActive(); + else + this.container.setInactive(); + } + + objEl.addEventListener("activate", activateHandler); + objEl.addEventListener("deactivate", activateHandler); + } + } + }; + + this.hasActiveElement = function(objEl){ + var nodes = objEl.$aml.getElementsByTagName("*"); + for (var i = 0; i < nodes.length; i++) { + if (!nodes[i].getAttribute("id")) continue; + var comp = this.elements[nodes[i].getAttribute("id")]; + if (comp && comp.form == this && comp.isActive) + return true; + } + + return false; + }; + + this.condActiveCheck = {}; + + this.getButtons = function(action){ + return buttons[action]; + }; + + this.registerButton = function(action, oBtn){ + buttons[action].push(oBtn); + + if (oBtn.condition) + this.parseCondition(oBtn, oBtn.condition); + this.updateButtons(action, oBtn); + + if (action == "follow") return; + + var amlNode = this; + oBtn.onclick = function(){ + amlNode.showLoader(true); + $setTimeout(function(){ amlNode[action](); }, 10); + }; + + /* + new Function( + "apf.lookup(" + this.$uniqueId + ").showLoader(true);setTimeout("apf.lookup(" + this.$uniqueId + ")." + action + "()", 10)" + ); + + action == "previous" ? + "apf.lookup(" + this.$uniqueId + ")." + action + "()" : + "apf.lookup(" + this.$uniqueId + ").showLoader();setTimeout("apf.lookup(" + this.$uniqueId + ")." + action + "()", 10)" + ); + */ + }; + + //refactor to give buttons classes, so they can decide what to do when inactive + this.updateButtons = function(action, singleBtn){ + return false;// + + if (!buttons[action]) return false; + + var result = true; + if (action == "previous" && this.activepagenr == 0) + result = false; + else if (!this.testing && action == "next" && !this.isValid()) + result = false; + else if (action == "next") { + var cp = this.activepagenr; + do { + var nextpage = this.getPage(++cp); + } + while(nextpage && !this.testCondition(nextpage.condition)); + + if (!nextpage) + result = false; + } + + if (this.testing) + return true; + + var buttons = singleBtn ? [singleBtn] : buttons[action]; + for (var i = 0; i < buttons.length; i++) { + if (result && (!buttons[i].condition || this.testCondition(buttons[i].condition))) + buttons[i].setActive(); + else + buttons[i].setInactive(); + } + + return true; + }; + + this.setLoadValues = function(item, clearElements, noload){ + var lvDep = this.loadValueDeps[item]; + if (!lvDep) return; + //alert(item); + for (var i = 0; i < lvDep.length; i++) { + try{ + if (!eval(lvDep[i][1])) + throw new Error(); + } + catch (e) { + if (clearElements) { + var oEl = self[lvDep[i][0].getAttribute("element")]; + if (oEl) + this.clearNextQuestionDepencies(oEl, true);//might be less optimized... + + if (lvDep[i][0].tagName == "LoadValue") + this.dispatchEvent("clearloadvalue", lvDep[i][0]); + + /*else if(lvDep[i][0].getAttribute("lid")){ + var lid = lvDep[i][0].getAttribute("lid"); + var nodes = this.xmlRoot.selectSingleNode("node()[@lid='" + lid + "']"); + + for(var i=0;i= 0; i--) { + var xmlNode = nodes[i]; + var unique = strUnique ? xmlNode.selectSingleNode(strUnique) : false; + + var node = unique + ? this.xmlRoot.selectSingleNode("node()[" + strUnique + + " = '" + unique.nodeValue + "']") + : null; + if (node) { + //Move all this into the xmldb + apf.xmldb.copyConnections(node, xmlNode); + apf.xmldb.notifyListeners(xmlNode); + + //node.setAttribute("lid", data.getAttribute("lid")); + + //hack!! - should be recursive + var valueNode = xmlNode.selectSingleNode("value"); + if (valueNode) { + apf.xmldb.copyConnections(node + .selectSingleNode("value"), valueNode); + apf.xmldb.notifyListeners(valueNode); + } + } + + this.xmlRoot.insertBefore(xmlNode, node); //consider using replaceChild here + if (node) + this.xmlRoot.removeChild(node); + apf.xmldb.applyChanges("attribute", xmlNode); + } + } + + this.dispatchEvent("afterloadvalue"); + }; + + this.loadLists = function(data, state, extra){ + if (state != apf.SUCCESS){ + if (extra.retries < apf.maxHttpRetries) + return extra.tpModule.retry(extra.id); + else + throw new Error(apf.formatErrorString(1011, this, "Load List", "Could not load data with LoadList query :\n\n" + extra.message)); + } + + if (!self[extra.userdata[0].getAttribute("element")]) + return this.listsHeldBack[extra.userdata[0].getAttribute("element")] = + [data, state, extra]; + + //set style + var jNode = self[extra.userdata[0].getAttribute("element")]; + if (jNode && jNode.nodeFunc == apf.NODE_VISIBLE) { + jNode.$setStyleClass(jNode.$ext, "loaded", ["loading"]); + $setTimeout("var jNode = apf.lookup(" + jNode.$uniqueId + ");\ + jNode.$setStyleClass(jNode.$ext, '', ['loading', 'loaded']);", 500); + } + + if (extra.userdata[0].getAttribute("clearonload") == "true") { + jNode.clearSelection(); + //this.setLoadValues(jNode.name, true); + this.clearNextQuestionDepencies(jNode, true); + } + + //load xml in element + jNode.load(data); + //if(!jNode.value){ + //this.clearNextQuestionDepencies(jNode, true); + //} + + this.dispatchEvent("afterloadlist"); + }; + + /*this.isValid = function(checkReq, setError, page){ + if(!page) page = this.getPage() || this; + var found = checkValidChildren(page, checkReq, setError); + + //Global Rules + // + + return !found; + } + + this.validate = function(){ + if(!this.isValid()){ + + } + }*/ + + //HACK! + this.reset = function(){ + //Clear all error states + var name, el; + for (name in this.elements) { + el = this.elements[name]; + + //Hack!!! maybe each + if (el.length) { + throw new Error(apf.formatErrorString(this, "clearing form", + "Found controls without a name or with a name that isn't unique. Please give all elements of your submitform an id: '" + name + "'")); + } + + el.clearError(); + if (this.errorEl[name]) + this.errorEl[name].hide(); + + el.clear(); + } + }; + + /* *********************** + Databinding + ************************/ + + this.$xmlUpdate = function(action, xmlNode, listenNode, UndoObj){ + //this.setConnections(this.xmlRoot, "select"); + this.dispatchEvent("xmlupdate"); + }; + + this.smartBinding = {}; + + this.$load = function(XMLRoot, id){ + apf.xmldb.addNodeListener(XMLRoot, this); + //this.setConnections(apf.xmldb.getElement(XMLRoot, 0), "select"); + //this.setConnections(XMLRoot, "select"); + }; + + function objHasValue(objEl){ + var oCheck = objEl; + if (!oCheck) + return false; + return oCheck.$applyBindRule(oCheck.$mainBind, + oCheck.xmlRoot, null, true); + } + + //Reset form + function onafterload(){ + //Clear all error states + for (var name in this.elements) { + if (apf.isWebkit && (!this.elements[name] + || !this.elements[name].$amlLoaders)) + continue; + + //Hack!!! maybe each + if (this.elements[name].length) { + throw new Error(apf.formatErrorString(1012, this, "clearing form", + "Found controls without a name or with a name that isn't unique(" + + name + "). Please give all elements of your submitform an id: '" + + name + "'")); + } + + this.elements[name].clearError(); + if (this.errorEl[name]) + this.errorEl[name].hide(); + } + + if (this.nQuest) { + //Show all controls and labels which are in the nquest stack + for (name in this.elements) { + + var objEl = this.elements[name]; + + if (objEl.getAttribute("checknext") == "true") { + if (objHasValue(objEl)) {//oCheck.value || + objEl.setActive(); + if (this.condActiveCheck[name]) + this.condActiveCheck[name].setActive(); + } + else { + objEl.setInactive(true); + } + } + else { + //que ??? + if (objEl.tagName == "Radiogroup" && objEl.current) + objEl.current.uncheck(); + } + } + } + + if (this.nQuest && this.xmlRoot.childNodes.length > 0) { + var element = this.nQuest.getAttribute("final"), + amlNode = self[element].$aml;//apf.queryNode(".//node()[@id='" + element + "']", this.$aml); + + if (amlNode && !apf.getBoundValue(amlNode, this.xmlRoot)) { + var fNextQNode = apf.xmldb + .selectSingleNode(".//node()[@checknext='true']", this.$aml); + if (!fNextQNode) return; + self[fNextQNode.getAttribute("id")].dispatchEvent("afterchange"); + } + } + } + this.addEventListener("afterload", onafterload); + this.addEventListener("afterinsert", onafterload); + + this.addEventListener("beforeload", function(){ + if (!this.smartBinding || !this.smartBinding.actions) return; + var nodes = this.smartBinding.actions.LoadList; + if (nodes) { + for (var objEl, i = 0; i < nodes.length; i++) { + if (!nodes[i].getAttribute("element") + || !(objEl = this.elements[nodes[i].getAttribute("element")])) + continue; + objEl.clear(); + } + } + + var nodes = this.smartBinding.actions.NextQuestion; + if (nodes) { + for (var objEl, i = 0; i < nodes.length; i++) { + if (!nodes[i].getAttribute("final") + || !(objEl = this.elements[nodes[i].getAttribute("element")])) + continue; + objEl.clear(); + } + } + }); + + /* ********* + INIT + **********/ + this.implement(apf.GuiElement); /** @inherits apf.GuiElement */ + + this.addOther = function(tagName, oAml){ + if (tagName == "loadstate") { + var htmlNode = apf.getFirstElement(oAml); + this.loadState = apf.insertHtmlNode(htmlNode, this.$int); + this.loadState.style.display = "none"; + } + }; + + this.$draw = function(){ + //Build Main Skin + this.oPages = this.$ext = this.$getExternal(); + this.$int = this.$getLayoutNode("main", "container", this.$ext); + this.$ext.host = this; + }; + + /** + * Submit this form + * Example: + * + * + * + */ + this.submit = function(submissionId){ + if(!this.isValid()) return; + if(!this.$model) return; //error? + + var type = this.method == "urlencoded-post" + ? "native" + : (this.type || "xml"); + var instruction = submissionId || this.action + ? ((this.method.match(/post/) ? "url.post:" : "") + this.action) + : ""; + + this.$model.submit(instruction, type, this.useComponents, this.ref); + }; + + this.setModel = function(model, xpath){ + this.$model = model; + }; + + this.$loadAml = function(x){ + this.testing = x.getAttribute("testing") == "true"; + + this.action = this.getAttribute("action"); + this.ref = this.getAttribute("ref"); + this.type = this.getAttribute("submittype") || "native"; + this.method = (this.getAttribute("method") || "get").toLowerCase(); + this.useComponents = this.getAttribute("usecomponents") || true; + + apf.setModel(x.getAttribute("model"), this); + + this.$loadChildren(function(xmlPage) { + this.validation = xmlPage.getAttribute("validation") || "true"; + this.invalidmsg = xmlPage.getAttribute("invalidmsg"); + }); + }; + +}).call(apf.submitform.prototype = new apf.StandardBinding()); + + +apf.xforms.prototype = apf.submitform.prototype; + +apf.aml.setElement("xforms", apf.xforms); +apf.aml.setElement("submitform", apf.submitform); + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/elements/tab.js)SIZE(2990)TIME(Mon, 10 Jan 2011 10:53:12 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + + +/** + * Element displaying a page and several buttons allowing a + * user to switch between the pages. Each page can contain + * arbitrary aml. Each page can render it's content during + * startup of the application or when the page is activated. + * Example: + * + * + * + * Example + * Example + * + * + * Test checkbox + * Test checkbox + * Test checkbox + * + * + * This ok? + * This better? + * + * + * + * + * @constructor + * @define tab, pages, switch + * @allowchild page + * @addnode elements + * + * @author Ruben Daniels (ruben AT ajax DOT org) + * @version %I%, %G% + * @since 0.1 + * + * @inherits apf.BaseTab + */ + +apf["switch"] = function(struct, tagName){ + this.$hasButtons = false; + this.$init(tagName || "switch", apf.NODE_VISIBLE, struct); +}; + +apf.pages = function(struct, tagName){ + this.$hasButtons = false; + this.$init(tagName || "pages", apf.NODE_VISIBLE, struct); + + this.$focussable = false; +}; + +apf.tab = function(struct, tagName){ + this.$hasButtons = true; + this.$init(tagName || "tab", apf.NODE_VISIBLE, struct); +}; + +(function(){ + this.$focussable = apf.KEYBOARD; // This object can get the focus from the keyboard + + /**** Init ****/ + + this.$draw = function(bSkinChange){ + //Build Main Skin + this.$ext = this.$getExternal(); + this.$loadChildren(); + }; +}).call(apf.tab.prototype = new apf.BaseTab()); + +apf["switch"].prototype = +apf.pages.prototype = apf.tab.prototype; + +apf.aml.setElement("switch", apf["switch"]); +apf.aml.setElement("pages", apf.pages); +apf.aml.setElement("tab", apf.tab); + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/elements/table.js)SIZE(17574)TIME(Mon, 10 Jan 2011 10:53:12 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + +/** + * Any child element of this element is placed in a table. The size of the + * columns and rows of the table can be set by attributes. Child elements can + * span multiple columns. Using '*' as a size indicator will use the remaining + * size for that column or row, when the table's size is set. + * Example: + * This example shows a window with a table and two buttons that change the + * orientation of the table runtime. The textarea and it's label have a span set + * to '*'. This means they will span the entire width of all columns, no matter + * how many columns there are. + * + * + * + * Name + * + * Address + * + * Country + * + * + * Message + * + * + * + * + * + * + * + * + * Remarks: + * This is one of three positioning methods. + * See {@link baseclass.alignment} + * See {@link baseclass.anchoring} + * + * @define table + * @allowchild {elements}, {anyaml} + * @addnode elements + * @constructor + * + * @author Ruben Daniels (ruben AT ajax DOT org) + * @version %I%, %G% + * @since 1.0 + */ +apf.table = function(struct, tagName){ + this.$init(tagName || "table", apf.NODE_VISIBLE, struct); +}; + +(function(){ + /**** Properties and Attributes ****/ + + this.$focussable = false; + this.$useLateDom = true; + this.$layout = true; + + this.columns = null;//"150,200"; + this.padding = 2; + this.$edge = [5, 5, 5, 5]; + this.cellheight = 19; + + /** + * @attribute {String} columns a comma seperated list of column sizes. A column size can be specified in a number (size in pixels) or using a number and a % sign to indicate a percentage. A '*' indicates the column spans the rest space. There can be only one '*' in the column string. + * Example: + * + * + * + * @attribute {String} padding the space between each element. Defaults to 2. + * @attribute {String} edge the space between the container and the elements, space seperated in pixels for each side. Similar to css in the sequence top right bottom left. Defaults to "5 5 5 5". + * Example: + * + * + * + */ + this.$supportedProperties.push("columns", "padding", "edge", + "cellheight", "span"); + + this.$propHandlers["columns"] = function(value){ + if (!value.match(/^((?:\d+\%?|\*)\s*(?:,\s*|\s*$))+$/)) { + + apf.console.warn("Invalid column string found for table: " + value); + + return; + } + + var col, colsize = this.$columns = value.splitSafe(","); + + var total = 0, cols = this.$table.getElementsByTagName("col"); + if (cols.length) { + for (var sz, i = 0, l = Math.min(cols.length, colsize.length); i < l; i++) { + cols[i].style.width = (sz = colsize[i]).indexOf("%") > -1 ? sz : sz + "px"; + total += parseInt(sz); + } + } + + var start = cols.length - colsize.length; + if (start > 0) { + for (var i = cols.length - start; i < cols.length; i++) { + cols[i].parentNode.removeChild(cols[i]); + } + } + else if (start < 0) { + for (var i = colsize.length + start; i < colsize.length; i++) { + col = this.$table.appendChild(document.createElement("col")); + col.style.width = (sz = colsize[i]).indexOf("%") > -1 ? sz : sz + "px"; + col.setAttribute("valign", "top"); + total += parseInt(sz); + } + } + + this.$table.style.width = String(value).indexOf("%") > -1 + ? "auto" + : (total + ((colsize.length - 1) * this.padding) + + this.$edge[0] + this.$edge[2]) + "px"; + + var cells = this.$tbody.firstChild.getElementsByTagName("td"); + for (var i = cells.length - 1; i >= 0; i--) + cells[i].parentNode.removeChild(cells[i]); + + for (var c, i = 0; i < colsize.length; i++) { + c = this.$tbody.firstChild.appendChild(document.createElement("td")); + if (colsize[i].indexOf("%") > -1) + c.appendChild(document.createElement("div")).style.width = "50px"; + } + + if (start && this.$amlLoaded) + visibleHandler({sync: true, parentNode: this}); + + this.$resize(); + } + + this.$propHandlers["padding"] = function(value){ + if (!this.$columns) return; + var cells = this.$table.getElementsByTagName("td"); + var lastCol, lastRow, cell, lRow = this.$tbody.lastChild; + for (var i = this.$columns.length, l = cells.length; i < l; i++) { + lastCol = (cell = cells[i]).parentNode.lastChild == cell; + lastRow = cell.parentNode == lRow; + cell.style.padding = "0px " + (lastCol ? 0 : value) + "px " + (lastRow ? 0 : value) + "px 0px"; + } + this.$resize(); + } + + this.$propHandlers["edge"] = function(value){ + this.$table.style.padding = (this.$edge = apf.getBox(value)).join("px ") + "px"; + this.$resize(); + } + + function visibleHandler(e){ + var table = e.parentNode || this.parentNode; + if (e.sync || e.value && !this.$altExt || !e.value && this.$altExt) { + var nodes = table.childNodes; + + var cells = apf.getArrayFromNodelist(table.$tbody.getElementsByTagName("td")); + var rows = table.$tbody.getElementsByTagName("tr"); + var empty = [], row = 1, cs, rs, collen = table.$columns.length; + var z = table.$columns.length, lastCol; + for (var node, td, last, l = nodes.length, i = 0; i < l; i++) { + if ((node = nodes[i]).visible === false) + continue; + + td = node.$altExt = last = cells[z++]; + if (!td) break; + //td = node.$altExt = last = document.createElement("td"); + + if (!rows[row]) + table.$tbody.appendChild(document.createElement("tr")); + + rows[row].appendChild(td); + td.appendChild(node.$ext); + td.setAttribute("colspan", cs = Math.min(collen - (empty[0] || 0), parseInt(node.colspan || node.span || 1))); + td.setAttribute("rowspan", rs = parseInt(node.rowspan || 1)); + + //@todo this is wrong it should be cs * rs + if (!empty[0]) + empty[0] = 0; + empty[0] += cs; + + if (rs > 1) { + for (var k = 1; k < rs; k++) { + if (!empty[k]) + empty[k] = 0; + empty[k] += cs; + } + } + + if (empty[0] >= collen) { + lastCol = true; + empty.shift(); + row++; + } + else lastCol = false; + + td.style.padding = "0px " + (lastCol ? 0 : table.padding) + + "px " + (i == l - 1 ? 0 : table.padding) + "px 0px"; + } + + //Fix padding of last row + var lastCells = rows[rows.length - 1].getElementsByTagName("td"); + for (i = 0, il = lastCells.length; i < il; i++) { + lastCells[i].style.padding = "0 " + + (i == il - 1 ? 0 : table.padding) + "px 0 0" + } + + for (;z < cells.length; z++) + cells[z].parentNode.removeChild(cells[z]); + + if (e.sync) return; + + if (e.value) + table.$addTd(nodes[l - 1]); //what if it's not visible + else { + //last.parentNode.removeChild(last); + this.$altExt = null; + } + } + } + + this.$addTd = function(amlNode){ + var cells = this.$table.getElementsByTagName("td"); + var total = 0, collen = this.$columns.length; + for (var cell, i = 0; i < cells.length; i++) { + total += Math.min(collen, + (parseInt((cell = cells[i]).getAttribute("colspan") || 1) + * parseInt(cell.getAttribute("rowspan") || 1))); + } + + if (total % collen == 0) { //New Row + var row = this.$tbody.appendChild(document.createElement("tr")); + } + else + row = cells[cells.length - 1].parentNode; + + //Add a new cell in the last row + var cel = row.appendChild(document.createElement("td")); + cel.style.position = "relative"; + + if (amlNode.colspan || amlNode.span) + cel.setAttribute("colspan", amlNode.colspan || amlNode.span); + if (amlNode.rowspan) + cel.setAttribute("rowspan", amlNode.rowspan); + + cel.appendChild(amlNode.$ext); + + amlNode.$altExt = cel; + } + + var propHandlers = { + "width" : function(value){ + this.$ext.style.width = "";/*value + ? Math.max(0, value - apf.getWidthDiff(this.$ext)) + "px" + : "";*/ + }, + + "height" : function(value){ + this.$ext.style.height = value + ? Math.max(0, value - apf.getHeightDiff(this.$ext)) + "px" + : ""; + this.parentNode.$resize(); + }, + + "margin" : function(value){ + this.$ext.style.margin = apf.getBox(value).join("px ") + "px"; + this.parentNode.$resize(); + }, + + "colspan" : function(value){ + if (!value) + this.$altExt.removeAttribute("colspan"); + else + this.$altExt.setAttribute("colspan", value); + + visibleHandler.call(this, {sync: true}); + this.parentNode.$resize(); + }, + + "rowspan" : function(value){ + if (!value) + this.$altExt.removeAttribute("rowspan"); + else + this.$altExt.setAttribute("rowspan", value); + + visibleHandler.call(this, {sync: true}); + this.parentNode.$resize(); + }, + + "valign" : function(value){ + this.$altExt.valign = value; + }, + + "align" : function(value){ + if ("left|right".indexOf(value) == -1) + return; + + this.$altExt.align = value; + } + } + propHandlers.span = propHandlers.colspan; + + //@todo move this to enableTable, disableTable + this.register = function(amlNode){ + if (amlNode.$altExt) //@todo hack, need to rearch layouting + return; + + amlNode.$propHandlers["left"] = + amlNode.$propHandlers["top"] = + amlNode.$propHandlers["right"] = + amlNode.$propHandlers["bottom"] = apf.K; + + for (var prop in propHandlers) { + amlNode.$propHandlers[prop] = propHandlers[prop]; + } + + amlNode.addEventListener("prop.visible", visibleHandler); + + this.$addTd(amlNode); + + this.$noResize = true; + + if (amlNode.margin) + propHandlers.margin.call(amlNode, amlNode.margin); + + //Why was this commented out? + if (amlNode.$ext.tagName == "INPUT") { + //amlNode.$ext.style.width = "100%"; + } + else + amlNode.$ext.style.width = "auto"; + + if (this.lastChild == amlNode) + this.$propHandlers["padding"].call(this, this.padding); + + delete this.$noResize; + } + + this.unregister = function(amlNode){ + amlNode.$propHandlers["left"] = + amlNode.$propHandlers["top"] = + amlNode.$propHandlers["right"] = + amlNode.$propHandlers["bottom"] = null; + + for (var prop in propHandlers) { + delete amlNode.$propHandlers[prop]; + } + + amlNode.removeEventListener("prop.visible", visibleHandler); + visibleHandler.call(amlNode, {value: false}); //maybe parent is already reset here? + + if (amlNode.margin) + amlNode.$ext.style.margin = ""; + + if (amlNode.width) + amlNode.$ext.style.width = ""; + } + /* + this.addEventListener("DOMNodeInsertedIntoDocument", function(e){ + this.register(this.parentNode); + }); + */ + + /**** DOM Hooks ****/ + + this.addEventListener("DOMNodeRemoved", function(e){ + if (e.$doOnlyAdmin || e.currentTarget == this) + return; + + if (e.relatedNode == this){ + this.unregister(e.currentTarget); + //e.currentTarget.$setLayout(); + } + }); + + this.addEventListener("DOMNodeInserted", function(e){ + if (e.currentTarget == this || e.currentTarget.nodeType != 1) + return; + + if (e.relatedNode == this) { + if (e.$isMoveWithinParent) { + visibleHandler.call(e.currentTarget, {sync: true}); + } + else { + e.currentTarget.$setLayout("table"); + if (e.currentTarget.nextSibling) + visibleHandler.call(e.currentTarget, {sync: true}); + } + } + }); + + this.$draw = function(){ + this.$ext = apf.insertHtmlNode(null, this.$pHtmlNode, null, + "
    "); + this.$table = this.$ext.firstChild; + this.$tbody = this.$table.firstChild; + this.$ext.className = "table " + (this.getAttribute("class") || ""); + //this.$ext.style.overflow = "hidden"; + this.$int = this.$ext; + this.$ext.host = this; + + if (this.getAttribute("class")) + apf.setStyleClass(this.$ext, this.getAttribute("class")); + + this.addEventListener("resize", this.$resize); + this.$originalMin = [this.minwidth || 0, this.minheight || 0]; + }; + + //@todo implement percentage by using fixed and add functionality here + this.$resize = function(){ + if (!this.$amlLoaded || this.$noResize) + return; + + if (this.$table.offsetWidth >= this.$ext.offsetWidth) + this.$ext.style.minWidth = (this.minwidth = Math.max(0, this.$table.offsetWidth + - apf.getWidthDiff(this.$ext))) + "px"; + else { + this.$ext.style.minWidth = ""; + this.minwidth = this.$originalMin[0]; + } + + if (this.$table.offsetHeight >= this.$ext.offsetHeight) + this.$ext.style.minHeight = (this.minheight = Math.max(0, this.$table.offsetHeight + - apf.getHeightDiff(this.$ext))) + "px"; + else { + this.$ext.style.minHeight = ""; + this.minheight = this.$originalMin[1]; + } + } + + this.$loadAml = function(x){ + this.$amlLoaded = false; //@todo hack + + if (!this.$columns) + this.$propHandlers.columns.call(this, this.columns = "150, 200"); + this.$amlLoaded = true; //@todo hack + }; +}).call(apf.table.prototype = new apf.GuiElement()); + +apf.aml.setElement("table", apf.table); + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/elements/teleport.js)SIZE(1019)TIME(Mon, 10 Jan 2011 10:53:12 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + + +apf.aml.setElement("teleport", apf.AmlElement); + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/elements/template.js)SIZE(2498)TIME(Mon, 10 Jan 2011 10:53:12 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + + +/** + * Defines a template for aml elements. + * + * @constructor + * @allowchild {elements}, {anyaml} + * + * @define template + * @addnode elements + * + * @author Ruben Daniels (ruben AT ajax DOT org) + * @version %I%, %G% + * @since 0.4 + */ + +apf.template = function(struct, tagName){ + this.$init(tagName || "template", apf.NODE_HIDDEN, struct); +}; + +(function(){ + this.$focussable = false; + + this.$frags = {}; + this.getNewInstance = function(htmlNode, id, xmlNode, preventLastPass){ + if (this.$frags[id]) { + var frag = this.$frags[id]; + } + else { + var model, xpath, attr, frag = this.$frags[id] = this.cloneNode(true); + frag.$int = htmlNode; + if (xmlNode) { //@todo apf3.0 is this generic enough? + model = apf.xmldb.findModel(xmlNode); + xpath = apf.xmlToXpath(xmlNode, model.data, true) || "."; + frag.attributes.push(attr = new apf.AmlAttr(frag, "model", "")); + attr.inheritedValue = [model.name, xpath]; //@todo apf3.0 to be tested + } + frag.ownerDocument.$domParser.$continueParsing(frag, {delay:true}); + } + + return frag; + } + + this.destroyInstance = function(id){ + var frag = this.$frags[id]; + delete this.$frags[id]; + + frag.destroy(true); + } +}).call(apf.template.prototype = new apf.AmlElement()); + +apf.aml.setElement("template", apf.template); + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/elements/text.js)SIZE(12317)TIME(Mon, 10 Jan 2011 10:53:12 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + + +/** + * Element displaying a rectangle containing arbitrary (X)HTML. + * This element can be databound and use databounding rules to + * convert data into (X)HTML using for instance XSLT or JSLT. + * + * @constructor + * @define text + * @addnode elements + * + * @inherits apf.Cache + * + * @author Ruben Daniels (ruben AT ajax DOT org) + * @version %I%, %G% + * @since 0.1 + * @todo Please refactor this object + */ +apf.text = function(struct, tagName){ + this.$init(tagName || "text", apf.NODE_VISIBLE, struct); + + this.$nodes = []; +}; + +(function(){ + this.implement( + + apf.Cache, + + apf.ChildValue + ); + + this.$focussable = true; // This object can't get the focus + this.focussable = false; + this.textselect = true; + this.$hasStateMessages = true; + + this.$textTimer = this.$lastMsg = this.$lastClass = this.$changedHeight = null; + + /**** Properties and Attributes ****/ + + /** + * @attribute {Boolean} scrolldown whether this elements viewport is always + * scrolled down. This is especially useful + * when this element is used to displayed + * streaming content such as a chat conversation. + * @attribute {Boolean} secure whether the content loaded in this element + * should be filtered in order for it to not + * be able to execute javascript. This is + * especially useful when the content does + * not come from a trusted source, like a + * web service or xmpp feed. + */ + this.$booleanProperties["scrolldown"] = true; + this.$booleanProperties["secure"] = true; + this.$booleanProperties["textselect"] = true; + this.$supportedProperties.push("behavior", "scrolldown", "secure", "value"); + + this.$isTextInput = function(){ + return this.textselect; + } + + this.$propHandlers["scrolldown"] = function(value){ + var _self = this; + + if (value) { + //this.addEventListener("resize", this.$resize); + this.$scrolldown = true; + this.$scrollArea.onscroll = function(){ + _self.$scrolldown = this.scrollTop >= this.scrollHeight + - this.offsetHeight + apf.getVerBorders(this); + } + this.addEventListener("scroll", this.$scroll); + this.addEventListener("afterload", this.$scroll); + clearInterval(this.$textTimer); + this.$textTimer = setInterval(function(){ + if (_self.$scrollArea && _self.$scrolldown && _self.scrolldown) + _self.$scrollArea.scrollTop = _self.$scrollArea.scrollHeight; + }, 1000); + } + else { + //this.removeEventListener("resize", this.$resize); + + this.removeEventListener("scroll", this.$scroll); + this.removeEventListener("afterload", this.$scroll); + clearInterval(this.$textTimer); + if (this.$scrollArea) + this.$scrollArea.onscoll = null; + } + } + + this.$scroll = function(e){ + var html = this.$scrollArea; + + if (e.name == "afterload") { + this.$scrolldown = true; + html.scrollTop = html.scrollHeight; + return; + } + + this.$scrolldown = html.scrollTop >= html.scrollHeight + - html.offsetHeight + apf.getVerBorders(html); + }; + + /*this.$resize = function(){ + if (this.scrolldown && this.$scrolldown) + this.$scrollArea.scrollTop = this.$scrollArea.scrollHeight; + }*/ + + /** + * @attribute {String} value the contents of this element. This can be text or html or xhtml. + */ + this.$propHandlers["value"] = function(value, prop, force, forceAdd){ + if (this.each) + return; + + if (typeof value != "string") { + if (value.nodeType) + value = value.nodeType > 1 && value.nodeType < 5 + ? value.nodeValue + : value.firstChild && value.firstChild.nodeValue || ""; + else + value = value ? value.toString() : ""; + } + + if (this.secure) { + value = value.replace(//g, "") + .replace(//g, "") + .replace(new RegExp("ondblclick|onclick|onmouseover|onmouseout" + + "|onmousedown|onmousemove|onkeypress|onkeydown|onkeyup|onchange" + + "|onpropertychange", "g"), "ona"); + } + + value = value.replace(/\<\?xml version="1\.0" encoding="UTF-16"\?\>/, ""); + + if (forceAdd) { + apf.insertHtmlNodes(null, this.$container, null, value); + if (!this.value) this.value = ""; + this.value += value; + } + else + this.$container.innerHTML = value;//.replace(//ig, "") + + //Iframe bug fix for IE (leaves screen white); + if (apf.cannotSizeIframe && this.oIframe) + this.oIframe.style.width = this.oIframe.offsetWidth + "px"; + + if (this.scrolldown && this.$scrolldown) + this.$scrollArea.scrollTop = this.$scrollArea.scrollHeight; + }; + + this.$eachHandler = function(value) { + this.$attrExcludePropBind = apf.extend({}, this.$attrExcludePropBind); + this.$attrExcludePropBind.value = value ? 2 : 0; + } + this.addEventListener("prop.each", this.$eachHandler); + + this.addEventListener("$clear", function(){ + this.$container.innerHTML = ""; + this.value = ""; + this.dispatchEvent("prop.value", {value: ""}); + }); + + // @todo replace this stub with something that does something + this.$moveNode = function() {}; + + /**** Public methods ****/ + + + + this.addValue = function(value){ + this.$propHandlers["value"].call(this, value, null, null, true); + this.dispatchEvent("prop.value", {value: this.value}); + } + + /** + * Sets the value of this element. This should be one of the values + * specified in the values attribute. + * @param {String} value the new value of this element + */ + this.setValue = function(value){ + this.setProperty("value", value, false, true); + }; + + /** + * Returns the current value of this element. + * @return {String} + */ + this.getValue = function(){ + return this.$container.innerHTML; + }; + + + + /**** Keyboard Support ****/ + + + this.addEventListener("keydown", function(e){ + var key = e.keyCode; + + switch (key) { + case 33: + //PGUP + this.$container.scrollTop -= this.$container.offsetHeight; + break; + case 34: + //PGDN + this.$container.scrollTop += this.$container.offsetHeight; + break; + case 35: + //END + this.$container.scrollTop = this.$container.scrollHeight; + break; + case 36: + //HOME + this.$container.scrollTop = 0; + break; + case 38: + this.$container.scrollTop -= 10; + break; + case 40: + this.$container.scrollTop += 10; + break; + default: + return; + } + + return false; + }, true); + + + /**** Private methods ****/ + + this.$canLoadData = function(){ + return this.$attrBindings.value ? true : false; + } + + this.$add = function(xmlNode, Lid, xmlParentNode, htmlParentNode, beforeNode){ + var f = this.$attrBindings.value.cvalue; + var html = f(xmlNode); + html = "
    " + html + "
    "; + if (htmlParentNode) { + if (beforeNode) + beforeNode.insertAdjacentHTML("beforebegin", html); + else + this.$container.insertAdjacentHTML("beforeend", html); + //apf.insertHtmlNode(oItem, htmlParentNode, beforeNode); + + //Iframe bug fix for IE (leaves screen white); + if (apf.cannotSizeIframe && this.oIframe) + this.oIframe.style.width = this.oIframe.offsetWidth + "px"; + + if (this.scrolldown && this.$scrolldown) + this.$scrollArea.scrollTop = this.$scrollArea.scrollHeight; + } + else + this.$nodes.push(html); + } + + this.$fill = function(){ + //apf.insertHtmlNode(null, this.$container, null, this.$nodes.join("")); + this.$container.insertAdjacentHTML("beforeend", this.$nodes.join("")); + this.$nodes = []; + } + + this.$deInitNode = + this.$updateNode = + this.$moveNode = apf.K; + + /**** Init ****/ + + this.$draw = function(){ + this.$ext = this.$getExternal(); + this.$container = this.$getLayoutNode("main", "container", this.$ext); + + if (apf.hasCssUpdateScrollbarBug && !apf.getStyle(this.$container, "padding")) + this.$fixScrollBug(); + + this.$scrollArea = this.oFocus ? this.oFocus.parentNode : this.$container; + + if (this.$container.tagName.toLowerCase() == "iframe") { + if (apf.isIE) { + this.oIframe = this.$container; + var iStyle = this.skin.selectSingleNode("iframe_style"); + this.oIframe.contentWindow.document.write( + "\ + \ + \ + \ + \ + "); + this.$container = this.oIframe.contentWindow.document.body; + } + else { + var node = document.createElement("div"); + this.$ext.parentNode.replaceChild(node, this.$ext); + node.className = this.$ext.className; + this.$ext = this.$container = node; + } + } + + if (this.getAttribute("each")) + this.$eachHandler(); + }; + + this.addEventListener("DOMNodeRemovedFromDocument", function() { + clearInterval(this.$textTimer); + apf.destroyHtmlNode(this.oDrag); + + if (this.$scrollArea) + this.$scrollArea.onscoll = this.$scrollArea = null; + + this.oDrag = this.oIframe = this.oFocus = this.$container = this.$ext = null; + }); +}).call(apf.text.prototype = new apf.MultiselectBinding()); + +apf.aml.setElement("text", apf.text); + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/elements/textbox.js)SIZE(27385)TIME(Tue, 15 Feb 2011 08:59:13 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + + +//@todo DOCUMENT the modules too + +/** + * Element displaying a rectangular area wich allows a + * user to type information. The information typed can be + * restricted by using this.$masking. The information can also + * be hidden from view when used in password mode. By adding an + * {@link element.autocomplete autocomplete element} as a child the + * value for the textbox can be looked up as you type. By setting the + * {@link element.textbox.attribute.mask mask atribute}, complex data input + * validation is done while the users types. + * + * @constructor + * @define input, secret, textarea, textbox + * @allowchild autocomplete, {smartbinding} + * @addnode elements + * + * @inherits apf.StandardBinding + * @inherits apf.XForms + * + * @author Ruben Daniels (ruben AT ajax DOT org) + * @version %I%, %G% + * @since 0.1 + * + * @binding value Determines the way the value for the element is retrieved + * from the bound data. + * Example: + * Sets the value based on data loaded into this component. + * + * + * + * + * + * + * Example: + * A shorter way to write this is: + * + * + * + * + * + * + * + * @event click Fires when the user presses a mousebutton while over this element and then let's the mousebutton go. + * @event mouseup Fires when the user lets go of a mousebutton while over this element. + * @event mousedown Fires when the user presses a mousebutton while over this element. + * @event keyup Fires when the user lets go of a keyboard button while this element is focussed. + * object: + * {Number} keyCode which key was pressed. This is an ascii number. + * @event clear Fires when the content of this element is cleared. + */ +apf.input = function(struct, tagName){ + this.$init(tagName || "input", apf.NODE_VISIBLE, struct); +}; + +apf.secret = function(struct, tagName){ + this.$init(tagName || "secret", apf.NODE_VISIBLE, struct); +}; + +apf.password = function(struct, tagName){ + this.$init(tagName || "password", apf.NODE_VISIBLE, struct); +}; + +apf.textarea = function(struct, tagName){ + this.$init(tagName || "textarea", apf.NODE_VISIBLE, struct); + + this.multiline = true; +}; + +// HTML5 email element +apf.email = function(struct, tagName){ + this.$init(tagName || "email", apf.NODE_VISIBLE, struct); +}; + +apf.textbox = function(struct, tagName){ + this.$init(tagName || "textbox", apf.NODE_VISIBLE, struct); +}; + +(function(){ + this.implement( + + apf.DataAction + + + ); + + this.$focussable = true; // This object can get the focus + this.$masking = false; + this.$autoComplete = false; + + this.$childProperty = "value"; + + //this.realtime = false; + this.value = ""; + this.$isTextInput = true; + this.multiline = false; + + /** + * @attribute {Boolean} realtime whether the value of the bound data is + * updated as the user types it, or only when this element looses focus or + * the user presses enter. + */ + this.$booleanProperties["focusselect"] = true; + this.$booleanProperties["realtime"] = true; + this.$supportedProperties.push("value", "mask", "initial-message", + "focusselect", "realtime", "type"); + + /** + * @attribute {String} value the text of this element + * @todo apf3.0 check use of this.$propHandlers["value"].call + */ + this.$propHandlers["value"] = function(value, prop, force, initial){ + if (!this.$input || !initial && this.getValue() == value) + return; + + // Set Value + if (!initial && !value && !this.hasFocus()) //@todo apf3.x research the use of clear + return this.$clear(); + else if (this.isHTMLBox) { + if (this.$input.innerHTML != value) + this.$input.innerHTML = value; + } + else if (this.$input.value != value) + this.$input.value = value; + + if (!initial) + apf.setStyleClass(this.$ext, "", [this.$baseCSSname + "Initial"]); + + if (this.$button) + this.$button.style.display = value && !initial ? "block" : "none"; + }; + + //See validation + //var oldPropHandler = this.$propHandlers["maxlength"]; + this.addEventListener("prop.maxlength", function(e){ + //Special validation support using nativate max-length browser support + if (this.$input.tagName.toLowerCase().match(/input|textarea/)) + this.$input.maxLength = parseInt(e.value) || null; + }); + + this.addEventListener("prop.editable", function(e){ + if (apf.isIE) + this.$input.unselectable = e.value ? "On" : "Off"; + else { + if (e.value) + apf.addListener(this.$input, "mousedown", apf.preventDefault); + else + apf.removeListener(this.$input, "mousedown", apf.preventDefault); + } + }); + + /** + * @attribute {String} mask a complex input pattern that the user should + * adhere to. This is a string which is a combination of special and normal + * characters. Then comma seperated it has two options. The first option + * specifies whether the non input characters (the chars not typed by the + * user) are in the value of this element. The second option specifies the + * character that is displayed when the user hasn't yet filled in a + * character. + * Characters: + * 0 Any digit + * 1 The number 1 or 2. + * 9 Any digit or a space. + * # User can enter a digit, space, plus or minus sign. + * L Any alpha character, case insensitive. + * ? Any alpha character, case insensitive or space. + * A Any alphanumeric character. + * a Any alphanumeric character or space. + * X Hexadecimal character, case insensitive. + * x Hexadecimal character, case insensitive or space. + * & Any whitespace. + * C Any character. + * ! Causes the input mask to fill from left to right instead of from right to left. + * ' The start or end of a literal part. + * " The start or end of a literal part. + * > Converts all characters that follow to uppercase. + * < Converts all characters that follow to lowercase. + * \ Cancel the special meaning of a character. + * Example: + * An american style phone number. + * + * + * + * Example: + * A dutch postal code + * + * + * + * Example: + * A date + * + * + * + * Example: + * A serial number + * + * + * + * Example: + * A MAC address + * + * + * + */ + this.$propHandlers["mask"] = function(value){ + if (this.mask.toLowerCase() == "password")// || !apf.hasMsRangeObject) + return; + + if (!value) { + throw new Error("Not Implemented"); + } + + if (!this.$masking) { + this.$masking = true; + this.implement(apf.textbox.masking); + this.focusselect = false; + //this.realtime = false; + } + + this.setMask(this.mask); + }; + + //this.$propHandlers["ref"] = function(value) { + // this.$input.setAttribute("name", value.split("/").pop().split("::").pop() + // .replace(/[\@\.\(\)]*/g, "")); + //}; + + /** + * @attribute {String} initial-message the message displayed by this element + * when it doesn't have a value set. This property is inherited from parent + * nodes. When none is found it is looked for on the appsettings element. + */ + this.$propHandlers["initial-message"] = function(value){ + if (value) { + + if (apf.hasFocusBug) + this.$input.onblur(); + + + //this.$propHandlers["value"].call(this, value, null, true); + } + + if (!this.value) + this.$clear(true); + + if (this.type == "password" && this.$inputInitFix) { + this.$inputInitFix.innerHTML = value; + apf.setStyleClass(this.$inputInitFix, "initFxEnabled"); + } + }; + + /** + * @attribute {Boolean} focusselect whether the text in this element is + * selected when this element receives focus. + */ + this.$propHandlers["focusselect"] = function(value){ + var _self = this; + this.$input.onmousedown = function(){ + _self.focusselect = false; + }; + + this.$input.onmouseup = + this.$input.onmouseout = function(){ + _self.focusselect = value; + }; + }; + + /** + * @attribute {String} type the type or function this element represents. + * This can be any arbitrary name. Although there are some special values. + * Possible values: + * username this element is used to type in the name part of login credentials. + * password this element is used to type in the password part of login credentials. + */ + this.$propHandlers["type"] = function(value){ + if (value && "password|username".indexOf(value) > -1 + && typeof this.focusselect == "undefined") { + this.focusselect = true; + this.$propHandlers["focusselect"].call(this, true); + } + }; + + this.$isTextInput = function(e){ + return true; + }; + + /**** Public Methods ****/ + + + + /** + * Sets the value of this element. This should be one of the values + * specified in the values attribute. + * @param {String} value the new value of this element + */ + this.setValue = function(value){ + return this.setProperty("value", value, false, true); + }; + + this.clear = function(){ + this.setProperty("value", ""); + } + + //@todo cleanup and put initial-message behaviour in one location + this.$clear = function(noEvent){ + if (this["initial-message"]) { + apf.setStyleClass(this.$ext, this.$baseCSSname + "Initial"); + this.$propHandlers["value"].call(this, this["initial-message"], null, null, true); + } + else { + this.$propHandlers["value"].call(this, "", null, null, true); + } + + if (!noEvent) + this.dispatchEvent("clear");//@todo this should work via value change + } + + /** + * Returns the current value of this element. + * @return {String} + */ + this.getValue = function(){ + var v = this.isHTMLBox ? this.$input.innerHTML : this.$input.value; + return v == this["initial-message"] ? "" : v.replace(/\r/g, ""); + }; + + + + /** + * Selects the text in this element. + */ + this.select = function(){ + try { + this.$input.select(); + } + catch(e){} + }; + + /** + * Deselects the text in this element. + */ + this.deselect = function(){this.$input.deselect();}; + + /**** Private Methods *****/ + + this.$enable = function(){this.$input.disabled = false;}; + this.$disable = function(){this.$input.disabled = true;}; + + this.$insertData = function(str){ + return this.setValue(str); + }; + + /** + * @private + */ + this.insert = function(text){ + if (apf.hasMsRangeObject) { + try { + this.$input.focus(); + } + catch(e) {} + var range = document.selection.createRange(); + if (this.oninsert) + text = this.oninsert(text); + range.pasteHTML(text); + range.collapse(true); + range.select(); + } + else { + this.$input.value += text; + } + }; + + this.addEventListener("$clear", function(){ + this.value = "";//@todo what about property binding? + + if (this["initial-message"] && apf.document.activeElement != this) { + this.$propHandlers["value"].call(this, this["initial-message"], null, null, true); + apf.setStyleClass(this.$ext, this.$baseCSSname + "Initial"); + } + else { + this.$propHandlers["value"].call(this, ""); + } + + if (!this.$input.tagName.toLowerCase().match(/input|textarea/i)) { + if (apf.hasMsRangeObject) { + try { + var range = document.selection.createRange(); + range.moveStart("sentence", -1); + //range.text = ""; + range.select(); + } + catch(e) {} + } + } + + this.dispatchEvent("clear"); //@todo apf3.0 + }); + + this.$keyHandler = function(key, ctrlKey, shiftKey, altKey, e){ + if (this.$button && key == 27) { + //this.$clear(); + if (this.value) { + this.change(""); + e.stopPropagation(); + } + //this.focus({mouse:true}); + } + + /*if (this.dispatchEvent("keydown", { + keyCode : key, + ctrlKey : ctrlKey, + shiftKey : shiftKey, + altKey : altKey, + htmlEvent : e}) === false) + return false; + + // @todo: revisit this IF statement - dead code? + if (false && apf.isIE && (key == 86 && ctrlKey || key == 45 && shiftKey)) { + var text = window.clipboardData.getData("Text"); + if ((text = this.dispatchEvent("keydown", { + text : this.onpaste(text)}) === false)) + return false; + if (!text) + text = window.clipboardData.getData("Text"); + + this.$input.focus(); + var range = document.selection.createRange(); + range.text = ""; + range.collapse(); + range.pasteHTML(text.replace(/\n/g, "
    ").replace(/\t/g, "   ")); + + return false; + }*/ + }; + + this.$registerElement = function(oNode) { + if (!oNode) return; + if (oNode.localName == "autocomplete") + this.$autoComplete = oNode; + }; + + var fTimer; + this.$focus = function(e){ + if (!this.$ext || this.$ext.disabled) + return; + + this.$setStyleClass(this.$ext, this.$baseCSSname + "Focus"); + + if (this["initial-message"] && this.$input.value == this["initial-message"]) { + this.$propHandlers["value"].call(this, "", null, null, true); + apf.setStyleClass(this.$ext, "", [this.$baseCSSname + "Initial"]); + } + + var _self = this; + function delay(){ + try { + if (!fTimer || document.activeElement != _self.$input) { + _self.$input.focus(); + } + else { + clearInterval(fTimer); + return; + } + } + catch(e) {} + + if (_self.$masking) + _self.setPosition(); + + if (_self.focusselect) + _self.select(); + }; + + if ((!e || e.mouse) && apf.isIE) { + clearInterval(fTimer); + fTimer = setInterval(delay, 1); + } + else + delay(); + }; + + this.$blur = function(e){ + if (!this.$ext) + return; + + if (!this.realtime) + this.change(this.getValue()); + + if (e) + e.cancelBubble = true; + + this.$setStyleClass(this.$ext, "", [this.$baseCSSname + "Focus", "capsLock"]); + + if (this["initial-message"] && this.$input.value == "") { + this.$propHandlers["value"].call(this, this["initial-message"], null, null, true); + apf.setStyleClass(this.$ext, this.$baseCSSname + "Initial"); + } + + /*if (apf.hasMsRangeObject) { + var r = this.$input.createTextRange(); + r.collapse(); + r.select(); + }*/ + + try { + if (apf.isIE || !e || e.srcElement != apf.window) + this.$input.blur(); + } + catch(e) {} + + // check if we clicked on the oContainer. ifso dont hide it + if (this.oContainer) { + $setTimeout("var o = apf.lookup(" + this.$uniqueId + ");\ + o.oContainer.style.display = 'none'", 100); + } + + clearInterval(fTimer); + }; + + /**** Init ****/ + + this.$draw = function(){ + var _self = this, + typedBefore = false; + + + if (this.localName == "codeeditor") { + this.skin = "textarea"; + this.$loadSkin(); + } + + + //Build Main Skin + this.$ext = this.$getExternal(null, null, function(oExt){ + var mask = this.getAttribute("mask"); + + if ((typeof mask == "string" && mask.toLowerCase() == "password") + || "secret|password".indexOf(this.localName) > -1) { + this.type = "password"; + this.$getLayoutNode("main", "input").setAttribute("type", "password"); + } + + + else if (this.localName == "email") { + this.datatype = (this.prefix ? this.prefix + ":" : "") + "email"; + this.$propHandlers["datatype"].call(this, this.datatype, "datatype"); + } + else if (this.localName == "url") { + this.datatype = (this.prefix ? this.prefix + ":" : "") + "url"; + this.$propHandlers["datatype"].call(this, this.datatype, "datatype"); + } + + + oExt.setAttribute("onmousedown", "if (!this.host.disabled) \ + this.host.dispatchEvent('mousedown', {htmlEvent : event});"); + oExt.setAttribute("onmouseup", "if (!this.host.disabled) \ + this.host.dispatchEvent('mouseup', {htmlEvent : event});"); + oExt.setAttribute("onclick", "if (!this.host.disabled) \ + this.host.dispatchEvent('click', {htmlEvent : event});"); + }); + this.$input = this.$getLayoutNode("main", "input", this.$ext); + this.$button = this.$getLayoutNode("main", "button", this.$ext); + this.$inputInitFix = this.$getLayoutNode("main", "initialfix", this.$ext); + + if (this.type == "password") + this.$propHandlers["type"].call(this, "password"); + + if (!apf.hasContentEditable && "input|textarea".indexOf(this.$input.tagName.toLowerCase()) == -1) { + var node = this.$input; + this.$input = node.parentNode.insertBefore(document.createElement("textarea"), node); + node.parentNode.removeChild(node); + this.$input.className = node.className; + if (this.$ext == node) + this.$ext = this.$input; + } + + if (this.$button) { + this.$button.onmousedown = function(){ + _self.$clear(); //@todo why are both needed for doc filter + _self.change(""); //@todo only this one should be needed + _self.focus({mouse:true}); + } + } + + //@todo for skin switching this should be removed + if (this.$input.tagName.toLowerCase() == "textarea") { + this.addEventListener("focus", function(e){ + //if (this.multiline != "optional") + //e.returnValue = false + }); + } + + this.$input.onselectstart = function(e){ + if (!e) e = event; + e.cancelBubble = true; + } + this.$input.host = this; + + this.$input.onkeydown = function(e){ + e = e || window.event; + + if (this.host.disabled) { + e.returnValue = false; + return false; + } + + //Change + if (!_self.realtime) { + var value = _self.getValue(); + if (e.keyCode == 13 && value != _self.value) + _self.change(value); + } + else if (apf.isWebkit && _self.xmlRoot && _self.getValue() != _self.value) //safari issue (only old??) + $setTimeout("var o = apf.lookup(" + _self.$uniqueId + ");\ + o.change(o.getValue())"); + + if (_self.multiline == "optional" && e.keyCode == 13 && !e.shiftKey + || e.ctrlKey && (e.keyCode == 66 || e.keyCode == 73 + || e.keyCode == 85)) { + e.returnValue = false; + return false; + } + + if (typedBefore && this.getAttribute("type") == "password" && this.value != "") { + var hasClass = (_self.$ext.className.indexOf("capsLock") > -1), + capsKey = (e.keyCode === 20); + if (capsKey) // caps off + apf.setStyleClass(_self.$ext, hasClass ? null : "capsLock", hasClass ? ["capsLock"] : null); + } + + //Autocomplete + if (_self.$autoComplete || _self.oContainer) { + var keyCode = e.keyCode; + $setTimeout(function(){ + if (_self.$autoComplete) + _self.$autoComplete.fillAutocomplete(keyCode); + else + _self.fillAutocomplete(keyCode); + }); + } + + //Non this.$masking + if (!_self.mask) { + return _self.$keyHandler(e.keyCode, e.ctrlKey, + e.shiftKey, e.altKey, e); + } + }; + + this.$input.onkeyup = function(e){ + if (!e) + e = event; + + if (this.host.disabled) + return false; + + var keyCode = e.keyCode; + + if (_self.$button) + _self.$button.style.display = this.value ? "block" : "none"; + + if (_self.realtime) { + $setTimeout(function(){ + var v; + if (!_self.mask && (v = _self.getValue()) != _self.value) + _self.change(v); + _self.dispatchEvent("keyup", {keyCode : keyCode});//@todo + }); + } + else { + _self.dispatchEvent("keyup", {keyCode : keyCode});//@todo + } + + + if (_self.isValid && _self.isValid() && e.keyCode != 13 && e.keyCode != 17) + _self.clearError(); + + }; + + + if (apf.hasFocusBug) + apf.sanitizeTextbox(this.$input); + + + if (apf.hasAutocompleteXulBug) + this.$input.setAttribute("autocomplete", "off"); + + if ("INPUT|TEXTAREA".indexOf(this.$input.tagName) == -1) { + this.isHTMLBox = true; + + this.$input.unselectable = "Off"; + this.$input.contentEditable = true; + this.$input.style.width = "1px"; + + this.$input.select = function(){ + var r = document.selection.createRange(); + r.moveToElementText(this); + r.select(); + } + }; + + this.$input.deselect = function(){ + if (!document.selection) return; + + var r = document.selection.createRange(); + r.collapse(); + r.select(); + }; + + var f; + apf.addListener(this.$input, "keypress", f = function(e) { + if (_self.$input.getAttribute("type") != "password") + return apf.removeListener(_self.$input, "keypress", f); + e = e || window.event; + // get key pressed + var which = -1; + if (e.which) + which = e.which; + else if (e.keyCode) + which = e.keyCode; + + // get shift status + var shift_status = false; + if (e.shiftKey) + shift_status = e.shiftKey; + else if (e.modifiers) + shift_status = !!(e.modifiers & 4); + + if (((which >= 65 && which <= 90) && !shift_status) || + ((which >= 97 && which <= 122) && shift_status)) { + // uppercase, no shift key + apf.setStyleClass(_self.$ext, "capsLock"); + } + else { + apf.setStyleClass(_self.$ext, null, ["capsLock"]); + } + typedBefore = true; + }); + }; + + this.$loadAml = function() { + if (typeof this["initial-message"] == "undefined") + this.$setInheritedAttribute("initial-message"); + + if (typeof this.realtime == "undefined") + this.$setInheritedAttribute("realtime"); + } + + this.addEventListener("DOMNodeRemovedFromDocument", function(){ + if (this.$button) + this.$button.onmousedown = null; + + if (this.$input) { + this.$input.onkeypress = + this.$input.onmouseup = + this.$input.onmouseout = + this.$input.onmousedown = + this.$input.onkeydown = + this.$input.onkeyup = + this.$input.onselectstart = null; + } + }); + +}).call(apf.textbox.prototype = new apf.StandardBinding()); + + +apf.config.$inheritProperties["initial-message"] = 1; +apf.config.$inheritProperties["realtime"] = 1; + +apf.input.prototype = +apf.secret.prototype = +apf.password.prototype = +apf.textarea.prototype = +apf.email.prototype = apf.textbox.prototype; + +apf.aml.setElement("input", apf.input); +apf.aml.setElement("secret", apf.secret); +apf.aml.setElement("password", apf.password); +apf.aml.setElement("textarea", apf.textarea); +apf.aml.setElement("textbox", apf.textbox); + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/elements/toc.js)SIZE(8342)TIME(Mon, 10 Jan 2011 10:53:12 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + + +/** + * Element acting as the navigational instrument for any + * element based on BaseTab. This element displays buttons + * which can be used to navigate the different pages of for instance + * a submitform or pages element. This element is page validation + * aware and can display current page progress when connected to + * a submitform. + * + * @constructor + * @define toc + * @addnode elements + * + * @inherits apf.Presentation + * @todo test if this element still works with the refactored basetab + * + * @author Ruben Daniels (ruben AT ajax DOT org) + * @version %I%, %G% + * @since 0.8 + */ +apf.toc = function(struct, tagName){ + this.$init(tagName || "toc", apf.NODE_VISIBLE, struct); +}; + +(function(){ + /**** Properties and Attributes ****/ + + this.$supportedProperties.push("represent"); + + /** + * @attribute {String} represent the id of the element to display + * navigation for. + */ + this.$propHandlers["represent"] = function(value){ + var _self = this; + $setTimeout(function(){ + var amlNode = _self.$represent = self[value]; + + amlNode.addEventListener("afterswitch", function(e){ + _self.$setActivePage(e.nextId); + }); + + if (amlNode.$drawn) { + _self.$createReflection(); + } + else { + amlNode.$amlLoaders.push(function(){ + toc.$createReflection(); + }); + } + }); + } + + /**** Public methods ****/ + + /** + * Navigates to a page of the represented element. + * @param {Number} nr the child number of the page to activate. + */ + this.gotoPage = function(nr, userAction){ + if (userAction && this.disabled) return false; + + if (this.$represent.isValid && !this.$represent.testing) { + var i, test, + pages = this.$represent.getPages(), + activepagenr = this.$represent.activepagenr; + for (i = activepagenr; i < nr; i++) { + pages[i].$ext.style.position = "absolute"; //hack + pages[i].$ext.style.top = "-10000px"; //hack + pages[i].$ext.style.display = "block"; //hack + test = !this.$represent.isValid || this.$represent + .isValid(i < activepagenr, i < activepagenr, pages[i]); + //false, activepagenr == i, pages[i], true); + pages[i].$ext.style.display = ""; //hack + pages[i].$ext.style.position = ""; //hack + pages[i].$ext.style.top = ""; //hack + pages[i].$ext.style.left = ""; //hack + pages[i].$ext.style.width = "1px"; + pages[i].$ext.style.width = ""; + + if (!test) + return this.$represent.set(i); + } + } + + if (this.$represent.showLoader) + this.$represent.showLoader(true, nr); + + var _self = this; + $setTimeout(function(){ + _self.$represent.set(nr); + }, 1); + //setTimeout("apf.lookup(" + this.$represent.$uniqueId + ").set(" + nr + ");", 1); + }; + + /**** Private Methods ****/ + + this.$setActivePage = function(active){ + //if (this.disabled) return false; + + //Find previous known index and make sure it has known indexes after + if (!this.pagelookup[active]) { + var page, last, is_between; + for (page in this.pagelookup) { + if (page < active) + last = page; + if (page > active) + is_between = true; + } + if (!last || !is_between) return; //exit if there are no known indices + active = last; + } + + for (var isPast = true, i = 0; i < this.pages.length; i++) { + this.$setStyleClass(this.pages[i], "", ["future", "past", "hover", "present"]); + + if (this.pagelookup[active] == this.pages[i]) { + this.$setStyleClass(this.pages[i], "present", []); + isPast = false; + } + else if (isPast) { + this.$setStyleClass(this.pages[i], "past", []); + } + else { + this.$setStyleClass(this.pages[i], "future", []); + } + + if (i == this.pages.length - 1) + this.$setStyleClass(this.pages[i], "last"); + } + }; + + this.$createReflection = function(){ + var i, oCaption, oPage, + l = {}, + pages = this.$represent.getPages(), + l2 = pages.length, + p = []; + + for (i = 0; i < l2; i++) { + this.$getNewContext("page"); + oCaption = this.$getLayoutNode("page", "caption"); + oPage = this.$getLayoutNode("page"); + this.$setStyleClass(oPage, "page" + i); + + oPage.setAttribute("onmouseover", 'apf.lookup(' + this.$uniqueId + + ').$setStyleClass(this, "hover", null, true);'); + oPage.setAttribute("onmouseout", 'apf.lookup(' + this.$uniqueId + + ').$setStyleClass(this, "", ["hover"], true);'); + + if(!pages[i].getAttribute("caption")){ + + apf.console.warn("Page element without caption found."); + + //continue; + } + else { + apf.setNodeValue(oCaption, + pages[i].getAttribute("caption") || ""); + } + + oPage.setAttribute("onmousedown", "setTimeout(function(){\ + apf.lookup(" + this.$uniqueId + ").gotoPage(" + i + ", true);\ + });"); + p.push(apf.insertHtmlNode(oPage, this.$int)); + l[i] = p[p.length - 1]; + } + + //xmldb.htmlImport(p, this.$int); + this.pages = p; + this.pagelookup = l; + + this.$setActivePage(0); + + + if (apf.isGecko) { + var tocNode = this; + $setTimeout(function(){ + tocNode.$ext.style.height = tocNode.$ext.offsetHeight + 1 + "px"; + tocNode.$ext.style.height = tocNode.$ext.offsetHeight - 1 + "px"; + }, 10); + } + + }; + + /**** Init ****/ + + this.$draw = function(){ + //Build Main Skin + this.$ext = this.$getExternal(); + this.oCaption = this.$getLayoutNode("main", "caption", this.$ext); + this.$int = this.$getLayoutNode("main", "container", this.$ext); + }; + + + this.addEventListener("DOMNodeInsertedIntoDocument", function() { + if (!this.represent) { + throw new Error(apf.formatErrorString(1013, this, + "Find representation", + "Could not find representation for the Toc: '" + + this.name + "'", this)); + } + }); + +}).call(apf.toc.prototype = new apf.Presentation()); + +apf.aml.setElement("toc", apf.toc); + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/elements/toolbar.js)SIZE(2787)TIME(Mon, 10 Jan 2011 10:53:12 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + +/** + * Element displaying a bar containing buttons and other aml elements. + * This element is usually positioned in the top of an application allowing + * the user to choose from grouped buttons. + * Example: + * + * + * About us + * Help + * + * + * Tutorials + * Live Helps + * + * Visit Ajax.org + * Exit + * + * + * + * + * File + * Edit + * + * + * + * + * + * @constructor + * @define toolbar + * @addnode elements + * @allowchild bar, menubar + * + * @author Ruben Daniels (ruben AT ajax DOT org) + * @version %I%, %G% + * @since 0.4 + * + * @inherits apf.Presentation + */ + +apf.toolbar = function(struct, tagName){ + this.$init(tagName || "toolbar", apf.NODE_VISIBLE, struct); +}; + +(function(){ + this.$focussable = false; + + /**** DOM Hooks ****/ + + + /**** Init ****/ + + this.$draw = function(){ + //Build Main Skin + this.$ext = this.$getExternal(); + this.$int = this.$getLayoutNode("main", "container", this.$ext); + }; +}).call(apf.toolbar.prototype = new apf.Presentation()); + +apf.aml.setElement("toolbar", apf.toolbar); + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/elements/tree.js)SIZE(17713)TIME(Mon, 10 Jan 2011 10:53:12 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + + +/** + * Element displaying data in a list where each item in the list can contain + * such a list. This element gives the user the ability to walk through this + * tree of data by clicking open elements to show more elements. The tree + * can grow by fetching more data when the user requests it. + * Example: + * A tree with inline items. + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * Example: + * + * + * + * + * + * + * + * + * Example: + * Inline tree description that draws the same as the above example: + * + * + * + * + * @constructor + * @define tree + * @allowchild {smartbinding} + * @addnode elements + * + * @author Ruben Daniels (ruben AT ajax DOT org) + * @version %I%, %G% + * @since 0.4 + * + * @binding insert Determines how new data is loaded when the user expands + * an item. For instance by clicking on the + button. This way only the root nodes + * need to be loaded at the start of the application. All other children are + * received on demand when the user requests it by navigating throught the tree. + * Example: + * This example shows an insert rule that only works on folder elements. It will + * read the directory contents using webdav and insert it under the selected + * tree node. + * + * + * + * + * + * + * + * + * + * @attribute {String} get the {@link term.datainstruction data instruction} that is used to load the new data. + * @binding caption Determines the caption of a tree node. + * @binding icon Determines the icon of a tree node. + * @binding css Determines a css class for a tree node. + * Example: + * In this example a node is bold when the folder contains unread messages: + * + * + * + * + * + * + * + * + * + * @binding tooltip Determines the tooltip of a tree node. + * @binding empty Determines the empty message of a node. + * Example: + * This example shows a gouped contact list, that displays a message under + * empty groups. + * + * + * + * + * + * + * + * + * + */ +apf.tree = function(struct, tagName){ + this.$init(tagName || "tree", apf.NODE_VISIBLE, struct); +}; + +(function(){ + var HAS_CHILD = 1 << 1, + IS_CLOSED = 1 << 2, + IS_LAST = 1 << 3, + IS_ROOT = 1 << 4, + treeState = this.$treeState; + + /**** Properties and Attributes ****/ + + + + /** + * @attribute {String} mode Sets the way this element interacts with the user. + * Possible values: + * check the user can select a single item from this element. The selected item is indicated. + * radio the user can select multiple items from this element. Each selected item is indicated. + */ + this.$mode = 0; + this.$propHandlers["mode"] = function(value){ + if ("check|radio".indexOf(value) > -1) { + if (!this.hasFeature(apf.__MULTICHECK__)) + this.implement(apf.MultiCheck); + + this.addEventListener("afterrename", $afterRenameMode); //what does this do? + + this.multicheck = value == "check"; //radio is single + this.$mode = this.multicheck ? 1 : 2; + } + else { + //@todo undo actionRules setting + this.removeEventListener("afterrename", $afterRenameMode); + //@todo unimplement?? + this.$mode = 0; + } + }; + + //@todo apf3.0 retest this completely + function $afterRenameMode(){ + + } + + + this.$initNode = function(xmlNode, state, Lid){ + //Setup Nodes Interaction + this.$getNewContext("item"); + + var hasChildren = state & HAS_CHILD || this.emptyMessage + && this.$applyBindRule("empty", xmlNode), + //should be restructured and combined events set per element + oItem = this.$getLayoutNode("item"); + //@todo this should use dispatchEvent, and be moved to oExt + oItem.setAttribute("onmouseover", + "var o = apf.lookup(" + this.$uniqueId + ");\ + o.$setStyleClass(this, 'hover', null, true);"); + oItem.setAttribute("onmouseout", + "var o = apf.lookup(" + this.$uniqueId + ");\ + o.$setStyleClass(this, '', ['hover'], true);"); + /*oItem.setAttribute("onmousedown", + "var o = apf.lookup(" + this.$uniqueId + ");\ + if (o.onmousedown) o.onmousedown(event, this);");*/ + + //Set open/close skin class & interaction + this.$setStyleClass(this.$getLayoutNode("item", "class"), treeState[state]).setAttribute(apf.xmldb.htmlIdTag, Lid); + this.$setStyleClass(this.$getLayoutNode("item", "container"), treeState[state]) + //this.$setStyleClass(oItem, xmlNode.tagName) + var elOpenClose = this.$getLayoutNode("item", "openclose"); + if (elOpenClose) { //hasChildren && + elOpenClose.setAttribute("children", hasChildren); + elOpenClose.setAttribute("onmousedown", + "if (this.getAttribute('children') == false) return;\ + var o = apf.lookup(" + this.$uniqueId + ");\ + o.slideToggle(this, null, null, true);\ + apf.cancelBubble(event, o);"); + + elOpenClose.setAttribute("ondblclick", "event.cancelBubble = true"); + } + + + if (this.$mode) { + var elCheck = this.$getLayoutNode("item", "check"); + if (elCheck) { + elCheck.setAttribute("onmousedown", + "var o = apf.lookup(" + this.$uniqueId + ");\ + o.checkToggle(this, true);\o.$skipSelect = true;"); + + if (apf.isTrue(this.$applyBindRule("checked", xmlNode))) { + this.$checkedList.push(xmlNode); + this.$setStyleClass(oItem, "checked"); + } + else if (this.isChecked(xmlNode)) + this.$setStyleClass(oItem, "checked"); + } + else { + + throw new Error(apf.formatErrorString(0, this, + "Could not find check attribute", + 'Maybe the attribute check is missing from your skin file:\ + \ +
    \ +
    \ + \ + ')); + + return false; + } + } + + + var ocAction = this.opencloseaction || "ondblclick"; + + //Icon interaction + var elIcon = this.$getLayoutNode("item", "icon"); + if (elIcon && elIcon != elOpenClose) { + if (ocAction != "ondblclick") { + elIcon.setAttribute(ocAction, + "var o = apf.lookup(" + this.$uniqueId + ");" + + (ocAction == "onmousedown" ? "o.select(this, event.ctrlKey, event.shiftKey, event.button);" : "") + + (true ? "o.slideToggle(this, null, null, true);" : "")); + } + if (ocAction != "onmousedown") { + elIcon.setAttribute("onmousedown", + "apf.lookup(" + this.$uniqueId + ").select(this, event.ctrlKey, event.shiftKey, event.button);"); + } + + elIcon.setAttribute("ondblclick", + "var o = apf.lookup(" + this.$uniqueId + ");\ + o.choose();" + + + "o.stopRename();" + + + (true && !ocAction == "ondblclick" ? "o.slideToggle(this, null, null, true);" : "") + + "apf.cancelBubble(event,o);"); + } + + //Select interaction + var elSelect = this.$getLayoutNode("item", "select"), + strMouseDown; + + + if (this.hasFeature(apf.__RENAME__) || this.hasFeature(apf.__DRAGDROP__)) { + strMouseDown = + 'var o = apf.lookup(' + this.$uniqueId + ');\ + var xmlNode = apf.xmldb.findXmlNode(this);\ + var isSelected = o.isSelected(xmlNode);\ + this.hasPassedDown = true;\ + if (!o.renaming && o.hasFocus() && isSelected == 1) \ + this.dorename = true;\ + if (!o.hasFeature(apf.__DRAGDROP__) || !isSelected && !event.ctrlKey)\ + o.select(this, event.ctrlKey, event.shiftKey, event.button);\ + apf.cancelBubble(event, o);'; + + elSelect.setAttribute("onmouseout", 'this.hasPassedDown = false;' + (elSelect.getAttribute("onmouseout") || "")); + elSelect.setAttribute("onmouseup", 'if (!this.hasPassedDown) return;\ + var o = apf.lookup(' + this.$uniqueId + ');' + + + 'if (this.dorename && !o.mode)\ + o.startDelayedRename(event, null, true);' + + + 'this.dorename = false;\ + var xmlNode = apf.xmldb.findXmlNode(this);\ + var isSelected = o.isSelected(xmlNode);\ + if (o.hasFeature(apf.__DRAGDROP__))\ + o.select(this, event.ctrlKey, event.shiftKey, event.button);'); + } + else + + { + strMouseDown = "o.select(this, event.ctrlKey, event.shiftKey, event.button);\ + apf.cancelBubble(event, o);"; + } + + if (ocAction != "ondblclick") { + elSelect.setAttribute(ocAction, + "var o = apf.lookup(" + this.$uniqueId + ");" + + (ocAction == "onmousedown" ? strMouseDown : "") + + (true ? "o.slideToggle(this, null, null, true);" : "")); + } + if (ocAction != "onmousedown") { + elSelect.setAttribute("onmousedown", + "var o = apf.lookup(" + this.$uniqueId + ");" + strMouseDown); + } + + elSelect.setAttribute("ondblclick", + "var o = apf.lookup(" + this.$uniqueId + ");\ + o.choose();" + + + "o.stopRename();this.dorename=false;" + + + (ocAction == "ondblclick" ? "o.slideToggle(this, null, null, true);" : "") + + "apf.cancelBubble(event, o);"); + + //Setup Nodes Identity (Look) + if (elIcon) { + var iconURL = this.$applyBindRule("icon", xmlNode); + if (iconURL) { + if (elIcon.tagName.match(/^img$/i)) + elIcon.setAttribute("src", apf.getAbsolutePath(this.iconPath, iconURL)); + else + elIcon.setAttribute("style", "background-image:url(" + + apf.getAbsolutePath(this.iconPath, iconURL) + ")"); + } + } + + var elCaption = this.$getLayoutNode("item", "caption"); + if (elCaption) { + + if (elCaption.nodeType == 1 + && this.$cbindings.caption && this.$cbindings.caption.hasAml){ + var q = (this.$amlBindQueue || (this.$amlBindQueue = {})); + + elCaption.setAttribute("id", "placeholder_" + this.$uniqueId + + "_" + ((q.caption || (q.caption = [])).push(xmlNode) - 1)); + apf.setNodeValue(elCaption, ""); + } + else + + { + apf.setNodeValue(elCaption, + this.$applyBindRule("caption", xmlNode)); + } + } + + var strTooltip = this.$applyBindRule("tooltip", xmlNode) + if (strTooltip) + oItem.setAttribute("title", strTooltip); + + + var cssClass = this.$applyBindRule("css", xmlNode); + if (cssClass) { + this.$setStyleClass(this.$getLayoutNode("item", null, oItem), cssClass); + this.$setStyleClass(this.$getLayoutNode("item", "container", oItem), cssClass); + this.$dynCssClasses.push(cssClass); + } + + + return oItem; + }; + + this.$updateNode = function(xmlNode, htmlNode){ + var elIcon = this.$getLayoutNode("item", "icon", htmlNode), + iconURL = this.$applyBindRule("icon", xmlNode); + if (elIcon && iconURL) { + if (elIcon.tagName && elIcon.tagName.match(/^img$/i)) + elIcon.src = apf.getAbsolutePath(this.iconPath, iconURL); + else + elIcon.style.backgroundImage = "url(" + + apf.getAbsolutePath(this.iconPath, iconURL) + ")"; + } + + + //@todo + + + var elCaption = this.$getLayoutNode("item", "caption", htmlNode); + if (elCaption) { + //if (elCaption.nodeType != 1) + //elCaption = elCaption.parentNode; + + if (elCaption.nodeType == 1) { + + if (!this.$cbindings.caption || !this.$cbindings.caption.hasAml) + + elCaption.innerHTML = this.$applyBindRule("caption", xmlNode); + } + else + elCaption.nodeValue = this.$applyBindRule("caption", xmlNode); + } + + var strTooltip = this.$applyBindRule("tooltip", xmlNode); + if (strTooltip) + htmlNode.setAttribute("title", strTooltip); + + + var cssClass = this.$applyBindRule("css", xmlNode); + if (cssClass || this.$dynCssClasses.length) { + this.$setStyleClass(htmlNode, cssClass, this.$dynCssClasses); //@todo overhead! + if (cssClass && !this.$dynCssClasses.contains(cssClass)) + this.$dynCssClasses.push(cssClass); + } + + }; + + /**** Init ****/ + + this.$draw = function(){ + this.$drawBase(); + }; +}).call(apf.tree.prototype = new apf.BaseTree()); + +apf.aml.setElement("tree", apf.tree); + +apf.aml.setElement("checked", apf.BindingRule); + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/elements/upload.js)SIZE(28994)TIME(Mon, 10 Jan 2011 10:53:12 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + +/** + * Element allowing the user to upload a file to a server. This element does + * not have a visual representation. By adding buttons, a progressbar and other + * elements you can fully customize your upload component. Use + * {@link term.propertybinding property binding} to update those elements with + * the state of the upload element. + * + * @attribute {Number} state the current state of the element. + * Possible values: + * apf.upload.STOPPED Inital state of the queue and also the state ones it's finished all it's uploads. + * apf.upload.STARTED Upload process is running + * @attribute {Number} chunksize the size of each chunk of data that is uploaded via the html5 upload control. + * @attribute {Number} maxfilesize the maximum file size of a single file. + * @attribute {Boolean} multiple whether the user can select multiple files from the browse dialog. + * @attribute {String} filedataname the name of the POST variable in the upload request which is send to the server. + * @attribute {String} target the url that the POST request is sent to. + * @attribute {String} filter the file filter used in the browse dialog using wildcards. Default is *.*. + * @attribute {Boolean} multipart whether to use a multipart POST request. This is only required if you send additional variabled as part of the request. Currently sending additional variables is not supported. + * @attribute {String} button the reference to the button that will trigger the display of the browse dialog. + * @attribute {String} model the model that gets the data loaded representing the queue of files. If the model doesn't exist yet it will be created automatically. + * @attribute {Number} [total] the total number of files in the queue. + * @attribute {String} [size] the total size of the files in the queue. + * @attribute {String} [loaded] the total number of bytes uploaded. + * @attribute {String} [uploaded] the total number of uploaded files. + * @attribute {String} [failed] the total number of failed uploads + * @attribute {String} [queued] the total number of queued files + * @attribute {String} [percent] the percentage of total upload + * @attribute {String} [bitrate] the bitrate of current upload + * + * @event error Fires after an error occurred during upload. + * object: + * {Number} code the type of error. + * Possible values: + * apf.upload.ERROR_CODES.GENERIC_ERROR Generic error for example if an exception is thrown inside Silverlight. + * apf.upload.ERROR_CODES.HTTP_ERROR HTTP transport error. For example if the server produces a HTTP status other than 200. + * apf.upload.ERROR_CODES.IO_ERROR Generic I/O error. For exampe if it wasn't possible to open the file stream on local machine. + * apf.upload.ERROR_CODES.SECURITY_ERROR Generic I/O error. For exampe if it wasn't possible to open the file stream on local machine. + * apf.upload.ERROR_CODES.INIT_ERROR Initialization error. Will be triggered if no runtime was initialized. + * apf.upload.ERROR_CODES.FILE_SIZE_ERROR File size error. If the user selects a file that is to large it will be blocked and an error of this type will be triggered. + * apf.upload.ERROR_CODES.FILE_EXTENSION_ERROR File extension error. If the user selects a file that isn't valid according to the filters setting. + * {String} message the description of the error. + * {Object} file the file that was being uploaded when the error occurred. + * Properties: + * {Date} addDate the date this file was added to the queue. + * {Date} creationDate the date this file was created on the filesystem. + * {String} extension the last letters after the last dot in the filename. + * {Number} id the sequence id in the queue of this file. + * {Date} modificationDate the date this file was last modified on the filesystem. + * {String} name the filename. + * {Number} size the size of this file in bytes. + * {String} status the upload status of this file. + * Possible values: + * apf.upload.STOPPED Inital state of the queue and also the state ones it's finished all it's uploads. + * apf.upload.STARTED Upload process is running + * apf.upload.QUEUED File is queued for upload + * apf.upload.UPLOADING File is being uploaded + * apf.upload.FAILED File has failed to be uploaded + * apf.upload.DONE File has been uploaded successfully + * {Number} loaded the number of bytes uploaded. + * + * @event uploaded Fires after the entire queue is uploaded. + * object: + * {Array} files the full queue of files. + * object: + * {Date} addDate the date this file was added to the queue. + * {Date} creationDate the date this file was created on the filesystem. + * {String} extension the last letters after the last dot in the filename. + * {Number} id the sequence id in the queue of this file. + * {Date} modificationDate the date this file was last modified on the filesystem. + * {String} name the filename. + * {Number} size the size of this file in bytes. + * {String} status the upload status of this file. + * Possible values: + * apf.upload.STOPPED Inital state of the queue and also the state ones it's finished all it's uploads. + * apf.upload.STARTED Upload process is running + * apf.upload.QUEUED File is queued for upload + * apf.upload.UPLOADING File is being uploaded + * apf.upload.FAILED File has failed to be uploaded + * apf.upload.DONE File has been uploaded successfully + * {Number} loaded the number of bytes uploaded. + * + * @event queue Fires after the user selected files, put in the queue and are ready for upload. + * object: + * {Array} files the files that were added to the queue. + * object: + * {Date} addDate the date this file was added to the queue. + * {Date} creationDate the date this file was created on the filesystem. + * {String} extension the last letters after the last dot in the filename. + * {Number} id the sequence id in the queue of this file. + * {Date} modificationDate the date this file was last modified on the filesystem. + * {String} name the filename. + * {Number} size the size of this file in bytes. + * {String} status the upload status of this file. + * Possible values: + * apf.upload.STOPPED Inital state of the queue and also the state ones it's finished all it's uploads. + * apf.upload.STARTED Upload process is running + * apf.upload.QUEUED File is queued for upload + * apf.upload.UPLOADING File is being uploaded + * apf.upload.FAILED File has failed to be uploaded + * apf.upload.DONE File has been uploaded successfully + * {Number} loaded the number of bytes uploaded. + * + * @constructor + * @alias upload + * @addnode elements + * + * @inherits apf.StandardBinding + * + * @author Mike de Boer (mike AT ajax DOT org) + * @version %I%, %G% + * @since 3.0 + * + * @binding value Determines the way the value for the element is retrieved + * from the bound data. + * Example: + * Sets the value based on data loaded into this component. + * + * + * + * + * @todo get server side information to update the progressbar. + */ +apf.upload = function(struct, tagName){ + this.$init(tagName || "upload", apf.NODE_HIDDEN, struct); + + var o, + i = 0, + a = ["html5", "flash", "html4"]; + for (; i < 4 && !this.$method; ++i) { + o = apf.upload[a[i]]; + if (typeof o != "undefined" && o.isSupported()) + this.$method = new o(this); + } + + if (!this.$method) { + throw new Error(apf.formatErrorString(0, this, "upload", + "No upload method found that us supported by your browser!")); + } +}; + +apf.upload.STOPPED = 0x0001; // Inital state of the queue and also the state ones it's finished all it's uploads. +apf.upload.STARTED = 0x0002; // Upload process is running +apf.upload.QUEUED = 0x0004; // File is queued for upload +apf.upload.UPLOADING = 0x0008; // File is being uploaded +apf.upload.FAILED = 0x0010; // File has failed to be uploaded +apf.upload.DONE = 0x0020; // File has been uploaded successfully +// Error constants used by the Error event: +apf.upload.ERROR_CODES = { + // Generic error for example if an exception is thrown inside Silverlight. + GENERIC_ERROR : -100, + // HTTP transport error. For example if the server produces a HTTP status other than 200. + HTTP_ERROR : -200, + // Generic I/O error. For exampe if it wasn't possible to open the file stream on local machine. + IO_ERROR : -300, + // Generic I/O error. For exampe if it wasn't possible to open the file stream on local machine. + SECURITY_ERROR : -400, + // Initialization error. Will be triggered if no runtime was initialized. + INIT_ERROR : -500, + // File size error. If the user selects a file that is to large it will be + // blocked and an error of this type will be triggered. + FILE_SIZE_ERROR : -600, + // File extension error. If the user selects a file that isn't valid according + // to the filters setting. + FILE_EXTENSION_ERROR : -700 +}; + +(function(constants){ + this.implement( + + apf.DataAction + + + ,apf.Validation + + ); + + this.state = constants.STOPPED; + this.size = this.loaded = this.uploaded = this.failed = this.queued + = this.percent = this.bitrate = this.chunksize = this.total = 0; + this.maxfilesize = 1073741824; //"1gb" + this.multiple = true; + this.multipart = true; + this.filedataname = "Filedata"; + + this.$method = null; + this.$filter = []; + + this.$booleanProperties["multiple"] = true; + this.$booleanProperties["multipart"] = true; + + this.$supportedProperties.push("state", "total", "chunksize", "maxfilesize", + "multiple", "filedataname", "target", "filter", "multipart", "size", + "loaded", "percent", "bitrate", "uploaded", "failed", "queued", "button", + "model"); + + var startTime, fileIndex; + this.$propHandlers["state"] = function(value) { + // Get start time to calculate bps + if (this.state & constants.STARTED) + startTime = (+new Date()); + }; + + this.$propHandlers["chunksize"] = function(value) { + this.chunksize = parseSize(value); + }; + + this.$propHandlers["maxfilesize"] = function(value) { + this.chunksize = parseSize(value); + }; + + this.$propHandlers["filter"] = function(value) { + this.$filter = value.splitSafe(","); + }; + + this.$propHandlers["button"] = function(value) { + this.$button = self[value]; + + + if (!this.$button) { + throw new Error(apf.formatErrorString(0, this, "upload init", + "No valid identifier for a Button element passed to the 'button' attribute.")); + } + + + if (!this.$method.refresh) + return; + + var _self = this; + if (!this.$button.$amlLoaded) { + this.$button.addEventListener("DOMNodeInsertedIntoDocument", function(){ + if (_self.button == value) + _self.$propHandlers["button"].call(_self, value); + }); + return; + } + + if (!apf.window.vManager.check(this.$button, this.$uniqueId, function(){ + _self.$method.refresh(); + })) + return; + + this.$method.refresh(); + }; + + this.$propHandlers["model"] = function(value) { + this.$files = new constants.files(this, value); + }; + + this.$propHandlers["target"] = function(value) { + var oUrl = new apf.url(value); + this.target = oUrl.uri; + + + if (oUrl.protocol == "file") + apf.console.warn("Upload: files uploaded to URL '" + this.src + "'\n" + + "will be loaded through the 'file://' protocol.\nThis may be " + + "unsupported by your browser.", "upload"); + else if (!oUrl.isSameLocation()) + apf.console.warn("Upload: the upload target with URL '" + this.src + "'\n" + + "does not have the same origin as your web application.\nThis may " + + "be unsupported by your browser.", "upload"); + + }; + + this.$mimeTypes = { + "doc" : "application/msword", + "dot" : "application/msword", + "pdf" : "application/pdf", + "pgp" : "application/pgp-signature", + "ps" : "application/postscript", + "ai" : "application/postscript", + "eps" : "application/postscript", + "rtf" : "text/rtf", + "xls" : "application/vnd.ms-excel", + "xlb" : "application/vnd.ms-excel", + "ppt" : "application/vnd.ms-powerpoint", + "pps" : "application/vnd.ms-powerpoint", + "pot" : "application/vnd.ms-powerpoint", + "zip" : "application/zip", + "swf" : "application/x-shockwave-flash", + "swfl" : "application/x-shockwave-flash", + "docx" : "application/vnd.openxmlformats", + "pptx" : "application/vnd.openxmlformats", + "xlsx" : "application/vnd.openxmlformats", + "mpga" : "audio/mpeg", + "mpega": "audio/mpeg", + "mp2" : "audio/mpeg", + "mp3" : "audio/mpeg", + "wav" : "audio/x-wav", + "bmp" : "image/bmp", + "gif" : "image/gif", + "jpeg" : "image/jpeg", + "jpg" : "image/jpeg", + "jpe" : "image/jpeg", + "png" : "image/png", + "svg" : "image/svg+xml", + "svgz" : "image/svg+xml", + "tiff" : "image/tiff", + "tif" : "image/tiff", + "htm" : "text/html", + "html" : "text/html", + "xhtml": "text/html", + "mpeg" : "video/mpeg", + "mpg" : "video/mpeg", + "mpe" : "video/mpeg", + "qt" : "video/quicktime", + "mov" : "video/quicktime", + "flv" : "video/x-flv", + "rv" : "video/vnd.rn-realvideo", + "asc" : "text/plain", + "txt" : "text/plain", + "text" : "text/plain", + "diff" : "text/plain", + "log" : "text/plain", + "exe" : "application/octet-stream" + }; + + function parseSize(size) { + var mul; + if (typeof size == "string") { + size = /^([0-9]+)([mgk]+)$/.exec(size.toLowerCase().replace(/[^0-9mkg]/g, "")); + mul = size[2]; + size = +size[1]; + if (mul == "g") + size *= 1073741824; + if (mul == "m") + size *= 1048576; + if (mul == "k") + size *= 1024; + } + + return size; + } + + function calc() { + if (!this.$files) return; + // Reset stats + var file, + size = 0, loaded = 0, uploaded = 0, failed = 0, queued = 0, + percent = 0, bitrate = 0, + files = this.$files.toArray(), + i = 0, + l = files.length; + + + // Check status, size, loaded etc on all files + for (; i < l; ++i) { + file = files[i]; + + if (typeof file.size != "undefined") { + size += file.size; + loaded += file.loaded; + }/* else { + size = undef; + }*/ + + if (file.status & constants.DONE) + uploaded++; + else if (file.status & constants.FAILED) + failed++; + else + queued++; + } + + // If we couldn't calculate a total file size then use the number of files to calc percent + if (size === 0) { + percent = files.length > 0 ? Math.ceil(uploaded / files.length * 100) : 0; + } + else { + bitrate = Math.ceil(loaded / ((+new Date() - startTime || 1) / 1000.0)); + percent = Math.ceil(loaded / size * 100); + } + + this.setProperty("size", size); + this.setProperty("loaded", loaded); + this.setProperty("uploaded", uploaded); + this.setProperty("failed", failed); + this.setProperty("queued", queued); + this.setProperty("percent", percent); + this.setProperty("bitrate", bitrate); + } + + function nextQueue(file) { + var files = this.$files.toArray(); + if (this.state & constants.STARTED && fileIndex < files.length) { + file = files[fileIndex++]; + if (file.status & constants.QUEUED) + this.$method.upload(file); + else + nextQueue.call(this); + } + else if (!(this.state & constants.STOPPED)) { + this.dispatchEvent("uploaded", { + files : files + }); + this.stop(); + } + } + + /**** Public methods ****/ + + this.$buildUrl = function(url, items) { + var query = ""; + for (var i in items) + query += (query ? "&" : "") + encodeURIComponent(i) + "=" + encodeURIComponent(items[i]); + if (query) + url += (url.indexOf("?") > 0 ? "&" : "?") + query; + return url; + }; + + this.$queue = function(selected_files) { + var i, l, file, extensionsMap, + count = 0; + + // Convert extensions to map + if (l = this.$filter.length) { + extensionsMap = {}; + for (i = 0; i < l; ++i) + extensionsMap[this.$filter[i].toLowerCase()] = true; + } + + if ((l = selected_files.length) > 1 && !this.multiple) + selected_files = [selected_files[0]], l = 1; + + for (i = 0; i < l; ++i) { + file = selected_files[i]; + file.loaded = 0; + file.percent = 0; + file.status = constants.QUEUED; + + // Invalid file extension + if (extensionsMap && !extensionsMap[file.name.toLowerCase().split(".").slice(-1)]) { + this.dispatchEvent("error", { + code : constants.ERROR.FILE_EXTENSION_ERROR, + message : "File extension error.", + file : file + }); + continue; + } + + // Invalid file size + if (typeof file.size != "undefined" && file.size > this.maxfilesize) { + this.dispatchEvent("error", { + code : constants.ERROR_CODES.FILE_SIZE_ERROR, + message : "File size error.", + file : file + }); + continue; + } + + // Add valid file to list + if (!this.multiple) + this.$files.reset(); + this.$files.create(file); + count++; + } + + this.dispatchEvent("queue", {files: selected_files}); + + // Only refresh if any files where added + if (count) { + calc.call(this); + if (this.$method.refresh) + this.$method.refresh(); + } + }; + + this.$triggerCalc = function() { + calc.call(this); + }; + + this.$progress = function(file) { + if (file.status & constants.QUEUED) + file.status = constants.UPLOADING; + + file.percent = file.size > 0 ? Math.ceil(file.loaded / file.size * 100) : 100; + this.$files.update(file); + calc.call(this); + }; + + this.$fileDone = function(file, e) { + file.status = constants.DONE; + file.response = e.response; + this.$progress(file); + nextQueue.call(this, file); + }; + + this.$fileRemove = function(file) { + if (this.$method.removeFile) + this.$method.removeFile(file); + this.$files.remove(file); + calc.call(this); + }; + + this.$draw = function(){ + if (!this.$method.draw) + return; + + + // we need to check if a parent AML element is not hidden, because + // otherwise the button may still have no height set... + var parentVis = true, + p = this.parentNode; + + while ((parentVis = typeof p.visible == "undefined" ? true : p.visible) && p != apf.document.documentElement) + p = p.parentNode; + if (!parentVis) { + var f, _self = this; + p.addEventListener("prop.visible", f = function(e) { + if (!e.value) return; //visible = false --> no drawing yet... + if (_self.$method.refresh) + _self.$method.refresh(); + p.removeEventListener("prop.visible", f); + }); + } + + p = this.parentNode; + while (p.$layout) { + p = p.parentNode; + } + + this.$pHtmlNode = p; + this.$ext = p.$int.appendChild(p.$int.ownerDocument.createElement("div")); + this.$method.draw(); + }; + + var states = { + "buttonEnter": "Over", + "buttonLeave": "Out", + "buttonDown" : "Down" + }; + + this.$setButtonState = function(state) { + if (!this.$button) return null; // @todo this will go when this class inherits from button + + if (state == "buttonDisabled") + return this.$button.setProperty("disabled", true); + else if (this.$button.disabled) + this.$button.setProperty("disabled", false); + + return this.$button.$setState(states[state]); + }; + + this.start = function() { + if (!(this.state & constants.STARTED)) { + fileIndex = 0; + this.setProperty("state", constants.STARTED); + nextQueue.call(this); + } + }; + + this.stop = function() { + if (!(this.state & constants.STOPPED)) { + this.setProperty("state", constants.STOPPED); + this.setProperty("bitrate", 0); + } + }; + + this.formatSize = function(bytes, precision) { + var test, + res = null; + ["B","kB","mB","gB"].forEach(function(size, index){ + test = bytes / Math.pow(1024, index); + if (Math.floor(test) <= 1024 && !res) + res = test.toFixed(precision || 0) + size; + }); + return res; + }; + + this.addEventListener("error", function(e) { + // Set failed status if an error occured on a file + if (e.file) { + e.file.status = constants.FAILED; + calc.call(this); + + // Upload next file but detach it from the error event + // since other custom listeners might want to stop the queue + var _self = this + $setTimeout(function() { + nextQueue.call(_self); + }); + } + }); + + this.addEventListener("DOMNodeInsertedIntoDocument", function() { + if (!this.$files) + this.$files = new constants.files(this, "apfupload".appendRandomNumber(5)); + + + if (!this.$button) { + throw new Error(apf.formatErrorString(0, this, "upload init", + "Required: 'button' attribute not set, no button element available.")); + } + if (!this.target) { + throw new Error(apf.formatErrorString(0, this, "upload init", + "Required: 'target' attribute not set, thus no valid uri to send files to.")); + } + + + if (!this.$method) + return; + + var _self = this; + $setTimeout(function() { + calc.call(_self); + if (_self.$method.refresh) { + _self.$method.refresh(); + $setTimeout(function() { + _self.$method.refresh(); + }, 1000); + } + }); + }); +}).call(apf.upload.prototype = new apf.GuiElement(), apf.upload); + +apf.upload.files = function(oUpload, model) { + if (typeof model == "string") { + + var sModel = model; + if (!(model = apf.nameserver.get(sModel))) { + model = apf.setReference(sModel, + apf.nameserver.register("model", sModel, new apf.model())); + if (model === 0) + model = self[sModel]; + else + model.id = model.name = sModel; + } + + } + + if (!model) { + throw new Error(apf.formatErrorString(0, oUpload, "upload", + "For the upload control to work, you MUST specify a valid value for the 'model' attribute!")); + } + + + var oFiles = {}, + aFiles = [], + _self = this, + userProps = {"addDate":1, "creationDate":1, "extension":1, "id":1, + "modificationDate":1, "name":1, "size":1, "status":1, + "validationError":1, "loaded":1 + }; + + model.load(""); + model.addEventListener("update", function(e) { + // e.xmlNode, e.action, e.undoObj + if (e.action == "add" || e.action == "synchronize") + return; + var id = e.xmlNode.getAttribute("id"); + if (e.action == "remove") { + _self.remove({id: id}, true); + oUpload.$triggerCalc(); + } + }); + + this.create = function(file) { + if (!file || !file.id || oFiles[file.id]) return null; + + oFiles[file.id] = file; + aFiles.pushUnique(file); + if (model) { + file.xml = model.data.ownerDocument.createElement("file"); + apf.xmldb.appendChild(model.data, file.xml); + } + + return this.update(file); + }; + + this.createMany = function(arr) { + if (!arr || !arr.length) return; + + for (var i = 0, l = arr.length; i < l; i++) { + if (arr[i]) + this.create(arr[i]); + } + }; + + this.read = function(filename) { }; + + this.update = function(file) { + if (!file || !file.id) return null; + + var i; + if (!file.xml) { + var t = file; + file = oFiles[file.id]; + for (i in userProps) + file[i] = t[i]; + } + if (!model || !file.xml) return null; + + for (i in userProps) { + if (typeof file[i] == "undefined") continue; + if (i.indexOf("Date") != -1 && typeof file[i] == "number") + file[i] = new Date(file[i]); + + file.xml.setAttribute(i, file[i]); + } + + apf.xmldb.applyChanges("synchronize", file.xml); + + return file; + }; + + this.remove = function(file, noXml) { + if (!file || !file.id || !oFiles[file.id]) return; + + file = oFiles[file.id]; + if (model && file.xml && !noXml) + apf.xmldb.removeNode(file.xml); + aFiles.remove(file); + delete oFiles[file.id]; + }; + + this.removeMany = function(arr, noXml) { + if (!arr || !arr.length) return; + + for (var i = 0, l = arr.length; i < l; i++) { + if (arr[i]) + this.remove(arr[i], noXml); + } + }; + + this.get = function() { + var l; + if (l = arguments.length) { + if (l === 1) + return oFiles[arguments[0]]; + var res = {}, + i = 0; + for (; i < l; ++i) { + if (oFiles[i]) + res[oFiles[i].id] = oFiles[i]; + } + return res; + } + return oFiles; + }; + + this.toArray = function() { + return aFiles; + }; + + this.getValue = function() { + var i, + a = []; + for (i in oFiles) + a.push(oFiles[i].name); + return a.join("|"); + }; + + this.reset = function() { + model.load(""); + oFiles = {}; + aFiles = []; + }; +}; + +apf.aml.setElement("upload", apf.upload); + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/elements/vectorflow.js)SIZE(65716)TIME(Mon, 10 Jan 2011 10:53:12 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + + + +/*FILEHEAD(/Volumes/bone/Development/ajax.org/javeline/cloud9infra/support/packager/lib/../support/apf/elements/video.js)SIZE(20319)TIME(Mon, 10 Jan 2011 10:53:12 GMT)*/ + +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ + + + +/** + * Element that is able to play a video file or remote stream + * Example: + * Plays a video at 340x180 + * + * + * Video Codec not supported. + * + * + * Example: + * + * + * + * + * + * + * + * Video Codec not supported. + * + * + * + * @return {Video} Returns a new video + * @type {Video} + * @inherits apf.Presentation + * @inherits apf.Media + * @constructor + * @allowchild text, source, nomedia + * @addnode elements:video + * @link http://www.whatwg.org/specs/web-apps/current-work/#video + * + * @author Mike de Boer + * @version %I%, %G% + * @since 1.0 + */ + +apf.video = function(struct, tagName){ + this.$init(tagName || "video", apf.NODE_VISIBLE, struct); +}; + +(function(){ + this.implement( + + apf.DataAction + + ); + + this.$booleanProperties["fullscreen"] = true; + + var oldStyle = null; //will hold old style of the media elements' parentNode on fullscreen + //var placeHolder = null; + this.$propHandlers["fullscreen"] = function(value) { + if (!this.player) return; + // only go fullscreen when the feature is supported by the active player + if (typeof this.player.setFullscreen == "function") + this.player.setFullscreen(value); + else if (this.parentNode && this.parentNode.tagName != "application" + && this.parentNode.setWidth) { + // we're going into fullscreen mode... + var i, node, oParent = this.parentNode.$ext; + if (value) { + oldStyle = { + width : this.parentNode.getWidth(), + height : this.parentNode.getHeight(), + top : this.parentNode.getTop(), + left : this.parentNode.getLeft(), + position : apf.getStyle(oParent, 'position'), + zIndex : apf.getStyle(oParent, 'zIndex'), + resizable: this.parentNode.resizable, + nodes : [] + } + + if (oParent != document.body) { + while (oParent.parentNode != document.body) { + var node = oParent.parentNode; + i = oldStyle.nodes.push({ + pos: apf.getSyle(node, 'position') || "", + top: apf.getSyle(node, 'top') || node.offsetTop + "px", + left: apf.getSyle(node, 'left') || node.offsetLeft + "px", + node: node + }) - 1; + node.style.position = "absolute"; + node.style.top = "0"; + node.style.left = "0"; + /*window.console.log('still reparenting!'); + window.console.dir(oParent.parentNode); + placeHolder = document.createElement('div'); + placeHolder.setAttribute('id', 'apf.__apf_video_placeholder__'); + placeHolder.style.display = "none"; + oParent.parentNode.insertBefore(placeHolder, oParent); + + document.body.appendChild(oParent);*/ + } + } + + this.parentNode.$ext.style.position = "absolute"; + this.parentNode.$ext.style.zIndex = "1000000"; + this.parentNode.setWidth('100%'); + this.parentNode.setHeight('100%'); + this.parentNode.setTop('0'); + this.parentNode.setLeft('0'); + + if (this.parentNode.resizable) + this.parentNode.setAttribute("resizable", false); + } + // we're going back to normal mode... + else if (oldStyle) { + var coll; + if (oldStyle.nodes.length) { + for (i = oldStyle.nodes.length - 1; i >= 0; i--) { + coll = oldStyle.nodes[i]; + node = coll.node; + node.style.position = coll.pos; + node.style.top = coll.top; + node.style.left = coll.left; + } + } + /*if (placeHolder) { + window.console.log('still reparenting!'); + placeHolder.parentNode.insertBefore(oParent, placeHolder); + placeHolder.parentNode.removeChild(placeHolder); + placeHolder = null; + }*/ + + this.parentNode.$ext.style.zIndex = oldStyle.zIndex; + this.parentNode.$ext.style.position = oldStyle.position; + this.parentNode.setWidth(oldStyle.width); + this.parentNode.setHeight(oldStyle.height); + this.parentNode.setTop(oldStyle.top); + this.parentNode.setLeft(oldStyle.left); + + if (oldStyle.resizable) + this.parentNode.setAttribute("resizable", true); + + oldStyle = null; + delete oldStyle; + } + + if (this.player.onAfterFullscreen) + this.player.onAfterFullscreen(value); + + var _self = this; + + window.setTimeout(function() { + apf.layout.forceResize(_self.parentNode.$ext); + }, 100); + + } + }; + + /**** Event listeners ****/ + + + this.addEventListener("keydown", function(e){ + switch (e.keyCode) { + case 13 && (e.ctrlKey || e.altKey): //(CTRL | ALT) + RETURN + case 70: //f + this.setPropery("fullscreen", true); + return false; + break; + case 80: + this.setProperty("paused", !this.paused); + return false; + break; + case 27: //ESC + this.setProperty("fullscreen", false); + return false; + break; + default: + break; + }; + }, true); + + + this.$mainBind = "src"; + + /** + * Load a video by setting the URL pointer to a different video file + * + * @param {String} sVideo + * @type {Object} + */ + this.loadMedia = function() { + if (this.player) { + this.setProperty('currentSrc', this.src); + this.setProperty('networkState', apf.Media.NETWORK_LOADING); + this.player.load(this.src); + } + + return this; + }; + + /** + * Seek the video to a specific position. + * + * @param {Number} iTo The number of seconds to seek the playhead to. + * @type {Object} + */ + this.seek = function(iTo) { + if (this.player && iTo >= 0 && iTo <= this.duration) + this.player.seek(iTo); + }; + + /** + * Set the volume of the video to a specific range (0 - 100) + * + * @param {Number} iVolume + * @type {Object} + */ + this.setVolume = function(iVolume) { + if (this.player) { + this.player.setVolume(iVolume); + } + }; + + var typesQT = "mov", + typesFLV = "flv", + typesWMP = "asf|asx|avi|wmv", + typesNtv = "ogg", + typesVLC = "3gp|3gpp|3g2|3gpp2|divx|mp4|mpg4|mpg|mpeg|mpe|ogg|vob"; + + /** + * Guess the mime-type of a video file, based on its filename/ extension. + * + * @param {String} path + * @type {String} + */ + this.$guessType = function(path) { + // make a best-guess, based on the extension of the src attribute (file name) + var ext = path.substr(path.lastIndexOf('.') + 1), + type = ""; + if (typesQT.indexOf(ext) != -1) + type = "video/quicktime"; + else if (typesFLV.indexOf(ext) != -1) + type = "video/flv"; + else if (typesWMP.indexOf(ext) != -1) + type = "video/wmv"; + else if (typesNtv.indexOf(ext) != -1 && apf.hasVideo) + type = "video/ogg"; + else if (typesVLC.indexOf(ext) != -1) + type = "video/vlc"; + // mpeg video is better to be played by native players + if (ext == "mpg" || ext == "mpeg" || ext == "mpe") + type = apf.isMac ? "video/quicktime" : "video/wmv"; + // default to VLC on *NIX machines + if (!apf.isWin && !apf.isMac && type == "video/wmv") + type = "video/vlc"; + + return type; + }; + + /** + * Find the correct video player type that will be able to playback the video + * file with a specific mime-type provided. + * + * @param {String} mimeType + * @type {String} + */ + this.$getPlayerType = function(mimeType) { + if (!mimeType) return null; + + var playerType = null; + + var aMimeTypes = mimeType.splitSafe(','); + if (aMimeTypes.length == 1) + aMimeTypes = aMimeTypes[0].splitSafe(';'); + for (var i = 0; i < aMimeTypes.length; i++) { + mimeType = aMimeTypes[i]; + + if (mimeType.indexOf('ogg') > -1) + playerType = "TypeNative"; + else if (mimeType.indexOf('flv') > -1) + playerType = "TypeFlv"; + else if (mimeType.indexOf('quicktime') > -1) + playerType = "TypeQT"; + else if (mimeType.indexOf('wmv') > -1) + playerType = "TypeWmp"; + else if (mimeType.indexOf('silverlight') > -1) + playerType = "TypeSilverlight"; + else if (mimeType.indexOf('vlc') > -1) + playerType = "TypeVlc"; + + if (playerType == "TypeWmp") { + if (!apf.isIE && typeof apf.video.TypeVlc != "undefined" + && apf.video.TypeVlc.isSupported()) + playerType = "TypeVlc"; + else if (apf.isMac) + playerType = "TypeQT"; + } + + if (playerType && apf.video[playerType] && + apf.video[playerType].isSupported()) { + this.$lastMimeType = i; + return playerType; + } + } + + this.$lastMimeType = -1; + return null;//playerType; + }; + + /** + * Checks if a specified playerType is supported by APF or not... + * + * @type {Boolean} + */ + this.$isSupported = function(sType) { + sType = sType || this.playerType; + return (apf.video[sType] && apf.video[sType].isSupported()); + }; + + /** + * Initialize and instantiate the video player provided by getPlayerType() + * + * @type {Object} + */ + this.$initPlayer = function() { + this.player = new apf.video[this.playerType](this, this.$ext, { + src : this.src.splitSafe(",")[this.$lastMimeType] || this.src, + width : this.width, + height : this.height, + autoLoad : true, + autoPlay : this.autoplay, + showControls: this.controls, + volume : this.volume, + mimeType : this.type + }); + return this; + }; + + /** + * The 'init' event hook is called when the player control has been initialized; + * usually that means that the active control (flash, QT or WMP) has been loaded + * and is ready to load a file. + * + * @ignore + * @type {void} + */ + this.$initHook = function() { + this.loadMedia(); + }; + + /** + * The 'cuePoint' event hook is called when the player has set a cue point in + * the video file. + * + * @ignore + * @type {void} + */ + this.$cuePointHook = function() {}; //ignored + + /** + * The 'playheadUpdate' event hook is called when the position of the playhead + * that is currently active (or 'playing') is updated. + * This feature is currently handled by {@link element.video.method.$changeHook} + * + * @ignore + * @type {void} + */ + this.$playheadUpdateHook = function() {}; //ignored + + /** + * The 'error' event hook is called when an error occurs within the internals + * of the player control. + * + * @param {Object} e Event data, specific to this hook, containing player data. + * @type {void} + */ + this.$errorHook = function(e) { + apf.console.error(e.error); + }; + + /** + * The 'progress' event hook is called when the progress of the loading sequence + * of an video file is updated. The control signals us on how many bytes are + * loaded and how many still remain. + * + * @param {Object} e Event data, specific to this hook, containing player data. + * @type {void} + */ + this.$progressHook = function(e) { + this.setProperty('bufferedBytes', {start: 0, end: e.bytesLoaded}); + this.setProperty('totalBytes', e.totalBytes); + var iDiff = Math.abs(e.bytesLoaded - e.totalBytes); + if (iDiff <= 20) + this.setProperty('readyState', apf.Media.HAVE_ENOUGH_DATA); + }; + + /** + * The 'stateChange' event hook is called when the internal state of a control + * changes. The state of internal properties of an video control may be + * propagated through this function. + * + * @param {Object} e Event data, specific to this hook, containing player data. + * @type {void} + */ + this.$stateChangeHook = function(e) { + //loading, playing, seeking, paused, stopped, connectionError + if (e.state == "loading") { + this.setProperty('networkState', this.networkState = apf.Media.NETWORK_LOADING); + } + else if (e.state == "connectionError") { + this.$propHandlers["readyState"].call(this, this.networkState = apf.Media.HAVE_NOTHING); + } + else if (e.state == "playing" || e.state == "paused") { + if (e.state == "playing") + this.$readyHook({type: 'ready'}); + this.paused = Boolean(e.state == "paused"); + this.setProperty('paused', this.paused); + } + else if (e.state == "seeking") { + this.seeking = true; + this.setProperty('seeking', true); + } + }; + + /** + * The 'change' event hook is called when a) the volume level changes or + * b) when the playhead position changes. + * + * @param {Object} e Event data, specific to this hook, containing player data. + * @type {void} + */ + this.$changeHook = function(e) { + if (typeof e.volume != "undefined") { + this.volume = e.volume; + if (!this.muted) + this.setProperty("volume", this.volume); + } + else { + this.duration = this.player.getTotalTime(); + this.position = e.playheadTime / this.duration; + if (isNaN(this.position)) return; + this.setProperty('position', this.position); + this.currentTime = e.playheadTime; + this.setProperty('currentTime', this.currentTime); + } + }; + + /** + * The 'complete' event hook is called when a control has finished playing + * an video file completely, i.e. the progress is at 100%. + * + * @param {Object} e Event data, specific to this hook, containing player data. + * @type {void} + */ + this.$completeHook = function(e) { + this.paused = true; + this.setProperty('paused', true); + }; + + /** + * When a video player signals that is has initialized properly and is ready + * to play, this function sets all the flags and behaviors properly. + * + * @type {Object} + */ + this.$readyHook = function(e) { + this.setProperty('networkState', apf.Media.NETWORK_LOADED); + this.setProperty('readyState', apf.Media.HAVE_FUTURE_DATA); + this.setProperty('duration', this.player.getTotalTime()); + this.seeking = false; + this.seekable = true; + this.setProperty('seeking', false); + return this; + }; + + /** + * The 'metadata' event hook is called when a control receives metadata of an + * video file. + * + * @ignore + * @type {void} + */ + this.$metadataHook = function(e) { + this.oVideo.setProperty('readyState', apf.Media.HAVE_METADATA); + }; + + /** + * Unsubscribe from all the events that we have subscribed to with + * startListening() + * + * @type {Object} + */ + this.stopListening = function() { + if (!this.player) return this; + + return this; + }; + + /** + * Build Main Skin + * + * @type {void} + */ + this.$draw = function(){ + this.$ext = this.$getExternal(); + }; + + /** + * Parse the block of AML that constructs the HTML5 compatible