xonomy.js 81 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873
  1. var Xonomy={
  2. lang: "", //"en"|"de"|fr"| ...
  3. mode: "nerd", //"nerd"|"laic"
  4. };
  5. Xonomy.setMode=function(mode) {
  6. if(mode=="nerd" || mode=="laic") Xonomy.mode=mode;
  7. if(mode=="nerd") $(".xonomy").removeClass("laic").addClass("nerd");
  8. if(mode=="laic") $(".xonomy").removeClass("nerd").addClass("laic");
  9. }
  10. Xonomy.jsEscape=function(str) {
  11. return String(str)
  12. .replace(/\"/g, '\\\"')
  13. .replace(/\'/g, '\\\'')
  14. };
  15. Xonomy.xmlEscape=function(str, jsEscape) {
  16. if(jsEscape) str=Xonomy.jsEscape(str);
  17. return String(str)
  18. .replace(/&/g, '&')
  19. .replace(/"/g, '"')
  20. .replace(/'/g, ''')
  21. .replace(/</g, '&lt;')
  22. .replace(/>/g, '&gt;');
  23. };
  24. Xonomy.xmlUnscape=function(value){
  25. return String(value)
  26. .replace(/&quot;/g, '"')
  27. .replace(/&apos;/g, "'")
  28. .replace(/&lt;/g, '<')
  29. .replace(/&gt;/g, '>')
  30. .replace(/&amp;/g, '&');
  31. };
  32. Xonomy.isNamespaceDeclaration=function(attributeName) {
  33. //Tells you whether an attribute name is a namespace declaration.
  34. var ret=false;
  35. if(attributeName=="xmlns") ret=true;
  36. if(attributeName.length>=6 && attributeName.substring(0, 6)=="xmlns:") ret=true;
  37. return ret;
  38. };
  39. Xonomy.namespaces={}; //eg. "xmlns:mbm": "http://lexonista.com"
  40. Xonomy.xml2js=function(xml, jsParent) {
  41. if(typeof(xml)=="string") xml=$.parseXML(xml);
  42. if(xml.documentElement) xml=xml.documentElement;
  43. var js=new Xonomy.surrogate(jsParent);
  44. js.type="element";
  45. js.name=xml.nodeName;
  46. js.htmlID="";
  47. js.attributes=[];
  48. for(var i=0; i<xml.attributes.length; i++) {
  49. var attr=xml.attributes[i];
  50. if(!Xonomy.isNamespaceDeclaration(attr.nodeName)) {
  51. if(attr.name!="xml:space") {
  52. js["attributes"].push({type: "attribute", name: attr.nodeName, value: attr.value, htmlID: "", parent: function(){return js}, });
  53. }
  54. } else {
  55. Xonomy.namespaces[attr.nodeName]=attr.value;
  56. }
  57. }
  58. js.children=[];
  59. for(var i=0; i<xml.childNodes.length; i++) {
  60. var child=xml.childNodes[i];
  61. if(child.nodeType==1) { //element node
  62. js["children"].push(Xonomy.xml2js(child, js));
  63. }
  64. if(child.nodeType==3) { //text node
  65. js["children"].push({type: "text", value: child.nodeValue, htmlID: "", parent: function(){return js}, });
  66. }
  67. }
  68. js=Xonomy.enrichElement(js);
  69. return js;
  70. };
  71. Xonomy.js2xml=function(js) {
  72. if(js.type=="text") {
  73. return Xonomy.xmlEscape(js.value);
  74. } else if(js.type=="attribute") {
  75. return js.name+"='"+Xonomy.xmlEscape(js.value)+"'";
  76. } else if(js.type=="element") {
  77. var xml="<"+js.name;
  78. for(var i=0; i<js.attributes.length; i++) {
  79. var att=js.attributes[i];
  80. xml+=" "+att.name+"='"+Xonomy.xmlEscape(att.value)+"'";
  81. }
  82. if(js.children.length>0) {
  83. var hasText=false;
  84. for(var i=0; i<js.children.length; i++) {
  85. var child=js.children[i];
  86. if(child.type=="text") hasText=true;
  87. }
  88. if(hasText) xml+=" xml:space='preserve'";
  89. xml+=">";
  90. for(var i=0; i<js.children.length; i++) {
  91. var child=js.children[i];
  92. if(child.type=="text") xml+=Xonomy.xmlEscape(child.value); //text node
  93. else if(child.type=="element") xml+=Xonomy.js2xml(child); //element node
  94. }
  95. xml+="</"+js.name+">";
  96. } else {
  97. xml+="/>";
  98. }
  99. return xml;
  100. }
  101. };
  102. Xonomy.enrichElement=function(jsElement) {
  103. jsElement.hasAttribute=function(name) {
  104. var ret=false;
  105. for(var i=0; i<this.attributes.length; i++) {
  106. if(this.attributes[i].name==name) ret=true;
  107. }
  108. return ret;
  109. };
  110. jsElement.getAttribute=function(name) {
  111. var ret=null;
  112. for(var i=0; i<this.attributes.length; i++) {
  113. if(this.attributes[i].name==name) ret=this.attributes[i];
  114. }
  115. return ret;
  116. };
  117. jsElement.getAttributeValue=function(name, ifNull) {
  118. var ret=ifNull;
  119. for(var i=0; i<this.attributes.length; i++) {
  120. if(this.attributes[i].name==name) ret=this.attributes[i].value;
  121. }
  122. return ret;
  123. };
  124. jsElement.hasChildElement=function(name) {
  125. var ret=false;
  126. for(var i=0; i<this.children.length; i++) {
  127. if(this.children[i].name==name) ret=true;
  128. }
  129. return ret;
  130. };
  131. jsElement.getChildElements=function(name) {
  132. var ret=[];
  133. for(var i=0; i<this.children.length; i++) {
  134. if(this.children[i].type=="element") {
  135. if(this.children[i].name==name) ret.push(this.children[i]);
  136. }
  137. }
  138. return ret;
  139. };
  140. jsElement.getDescendantElements=function(name) {
  141. var ret=[];
  142. for(var i=0; i<this.children.length; i++) {
  143. if(this.children[i].type=="element") {
  144. if(this.children[i].name==name) ret.push(this.children[i]);
  145. var temp=this.children[i].getDescendantElements(name);
  146. for(var t=0; t<temp.length; t++) ret.push(temp[t]);
  147. }
  148. }
  149. return ret;
  150. };
  151. jsElement.getText=function(){
  152. var txt="";
  153. for(var i=0; i<this.children.length; i++) {
  154. if(this.children[i].type=="text") txt+=this.children[i].value;
  155. else if(this.children[i].type=="element") txt+=this.children[i].getText();
  156. }
  157. return txt;
  158. };
  159. jsElement.hasElements=function(){
  160. for(var i=0; i<this.children.length; i++) {
  161. if(this.children[i].type=="element") return true;
  162. }
  163. return false;
  164. };
  165. jsElement.getPrecedingSibling=function(){
  166. var parent=this.parent();
  167. if(parent){
  168. var lastSibling=null;
  169. for(var i=0; i<parent.children.length; i++) {
  170. if(parent.children[i].type=="element" && parent.children[i].htmlID!=this.htmlID) {
  171. lastSibling=parent.children[i];
  172. } else if(parent.children[i].htmlID==this.htmlID){
  173. return lastSibling;
  174. }
  175. }
  176. }
  177. return null;
  178. };
  179. jsElement.getFollowingSibling=function(){
  180. var parent=this.parent();
  181. if(parent){
  182. var seenSelf=false;
  183. for(var i=0; i<parent.children.length; i++) {
  184. if(parent.children[i].htmlID==this.htmlID){
  185. seenSelf=true;
  186. } else if(parent.children[i].type=="element" && seenSelf) {
  187. return parent.children[i];
  188. }
  189. }
  190. }
  191. return null;
  192. };
  193. jsElement.setAttribute=function(name, value){
  194. if(this.hasAttribute(name)){
  195. this.getAttribute(name).value=value;
  196. } else {
  197. this.attributes.push({
  198. type: "attribute",
  199. name: name,
  200. value: value,
  201. htmlID: null,
  202. parent: function(){return this;}
  203. });
  204. }
  205. };
  206. jsElement.addText=function(txt){
  207. this.children.push({
  208. type: "text",
  209. value: txt,
  210. htmlID: null,
  211. parent: function(){return this;}
  212. });
  213. };
  214. return jsElement;
  215. };
  216. Xonomy.verifyDocSpec=function() { //make sure the docSpec object has everything it needs
  217. if(!Xonomy.docSpec || typeof(Xonomy.docSpec)!="object") Xonomy.docSpec={};
  218. if(!Xonomy.docSpec.elements || typeof(Xonomy.docSpec.elements)!="object") Xonomy.docSpec.elements={};
  219. if(!Xonomy.docSpec.onchange || typeof(Xonomy.docSpec.onchange)!="function") Xonomy.docSpec.onchange=function(){};
  220. if(!Xonomy.docSpec.validate || typeof(Xonomy.docSpec.validate)!="function") Xonomy.docSpec.validate=function(){};
  221. };
  222. Xonomy.asFunction=function(specProperty, defaultValue){
  223. if(typeof(specProperty)=="function")
  224. return specProperty;
  225. else if (typeof(specProperty)==typeof(defaultValue))
  226. return function() { return specProperty; }
  227. else
  228. return function() { return defaultValue };
  229. }
  230. Xonomy.verifyDocSpecElement=function(name) { //make sure the DocSpec object has such an element, that the element has everything it needs
  231. if(!Xonomy.docSpec.elements[name] || typeof(Xonomy.docSpec.elements[name])!="object") {
  232. if(Xonomy.docSpec.unknownElement) {
  233. Xonomy.docSpec.elements[name]=(typeof(Xonomy.docSpec.unknownElement)==="function")
  234. ? Xonomy.docSpec.unknownElement(name)
  235. : Xonomy.docSpec.unknownElement;
  236. }
  237. else Xonomy.docSpec.elements[name]={};
  238. }
  239. var spec=Xonomy.docSpec.elements[name];
  240. if(!spec.attributes || typeof(spec.attributes)!="object") spec.attributes={};
  241. if(!spec.menu || typeof(spec.menu)!="object") spec.menu=[];
  242. if(!spec.inlineMenu || typeof(spec.inlineMenu)!="object") spec.inlineMenu=[];
  243. if(!spec.canDropTo || typeof(spec.canDropTo)!="object") spec.canDropTo=[];
  244. //if(!spec.mustBeAfter || typeof(spec.mustBeAfter)!="object") spec.mustBeAfter=[];
  245. //if(!spec.mustBeBefore || typeof(spec.mustBeBefore)!="object") spec.mustBeBefore=[];
  246. spec.mustBeAfter=Xonomy.asFunction(spec.mustBeAfter, []);
  247. spec.mustBeBefore=Xonomy.asFunction(spec.mustBeBefore, []);
  248. spec.oneliner=Xonomy.asFunction(spec.oneliner, false);
  249. spec.hasText=Xonomy.asFunction(spec.hasText, false);
  250. spec.collapsible=Xonomy.asFunction(spec.collapsible, true);
  251. spec.collapsed=Xonomy.asFunction(spec.collapsed, false);
  252. spec.localDropOnly=Xonomy.asFunction(spec.localDropOnly, false);
  253. spec.isReadOnly=Xonomy.asFunction(spec.isReadOnly, false);
  254. spec.isInvisible=Xonomy.asFunction(spec.isInvisible, false);
  255. spec.backgroundColour=Xonomy.asFunction(spec.backgroundColour, "");
  256. if(spec.displayName) spec.displayName=Xonomy.asFunction(spec.displayName, "");
  257. if(spec.title) spec.title=Xonomy.asFunction(spec.title, "");
  258. for(var i=0; i<spec.menu.length; i++) Xonomy.verifyDocSpecMenuItem(spec.menu[i]);
  259. for(var i=0; i<spec.inlineMenu.length; i++) Xonomy.verifyDocSpecMenuItem(spec.inlineMenu[i]);
  260. for(var attributeName in spec.attributes) Xonomy.verifyDocSpecAttribute(name, attributeName);
  261. };
  262. Xonomy.verifyDocSpecAttribute=function(elementName, attributeName) { //make sure the DocSpec object has such an attribute, that the attribute has everything it needs
  263. var elSpec=Xonomy.docSpec.elements[elementName];
  264. if(!elSpec.attributes[attributeName] || typeof(elSpec.attributes[attributeName])!="object") {
  265. if(Xonomy.docSpec.unknownAttribute) {
  266. elSpec.attributes[attributeName]=(typeof(Xonomy.docSpec.unknownAttribute)==="function")
  267. ? Xonomy.docSpec.unknownAttribute(elementName, attributeName)
  268. : Xonomy.docSpec.unknownAttribute;
  269. }
  270. else elSpec.attributes[attributeName]={};
  271. }
  272. var spec=elSpec.attributes[attributeName];
  273. if(!spec.asker || typeof(spec.asker)!="function") spec.asker=function(){return ""};
  274. if(!spec.menu || typeof(spec.menu)!="object") spec.menu=[];
  275. spec.isReadOnly=Xonomy.asFunction(spec.isReadOnly, false);
  276. spec.isInvisible=Xonomy.asFunction(spec.isInvisible, false);
  277. spec.shy=Xonomy.asFunction(spec.shy, false);
  278. if(spec.displayName) spec.displayName=Xonomy.asFunction(spec.displayName, "");
  279. if(spec.title) spec.title=Xonomy.asFunction(spec.title, "");
  280. for(var i=0; i<spec.menu.length; i++) Xonomy.verifyDocSpecMenuItem(spec.menu[i]);
  281. };
  282. Xonomy.verifyDocSpecMenuItem=function(menuItem) { //make sure the menu item has all it needs
  283. menuItem.caption=Xonomy.asFunction(menuItem.caption, "?");
  284. if(!menuItem.action || typeof(menuItem.action)!="function") menuItem.action=function(){};
  285. if(!menuItem.hideIf) menuItem.hideIf=function(){return false;};
  286. if(typeof(menuItem.expanded)!="function") menuItem.expanded=Xonomy.asFunction(menuItem.expanded, false);
  287. };
  288. Xonomy.nextID=function() {
  289. return "xonomy"+(++Xonomy.lastIDNum);
  290. };
  291. Xonomy.lastIDNum=0;
  292. Xonomy.docSpec=null;
  293. Xonomy.refresh=function() {
  294. $(".xonomy .textnode[data-value='']").each(function(){ //delete empty text nodes if the parent element is not allowed to have text
  295. var $this=$(this);
  296. var $parent=$this.closest(".element");
  297. var parentName=$parent.data("name");
  298. var elSpec=Xonomy.docSpec.elements[parentName];
  299. if(elSpec && !elSpec.hasText(Xonomy.harvestElement($parent.toArray()[0]))) {
  300. $this.remove();
  301. }
  302. });
  303. $(".xonomy .children ").each(function(){ //determine whether each element does or doesn't have children:
  304. if(this.childNodes.length==0 && !$(this.parentNode).hasClass("hasText")) $(this.parentNode).addClass("noChildren");
  305. else {
  306. $(this.parentNode).removeClass("noChildren");
  307. Xonomy.updateCollapsoid(this.parentNode.id);
  308. }
  309. });
  310. $(".xonomy .element.hasText > .children > .element").each(function () { //determine whether each child element of hasText element should have empty text nodes on either side
  311. if($(this).prev().length == 0 || !$(this).prev().hasClass("textnode")) {
  312. $(this).before(Xonomy.renderText({ type: "text", value: "" }));
  313. }
  314. if($(this).next().length == 0 || !$(this).next().hasClass("textnode")) {
  315. $(this).after(Xonomy.renderText({ type: "text", value: "" }));
  316. }
  317. });
  318. var merged=false; while(!merged) { //merge adjacent text nodes
  319. merged=true; var textnodes=$(".xonomy .textnode").toArray();
  320. for(var i=0; i<textnodes.length; i++) {
  321. var $this=$(textnodes[i]);
  322. if($this.next().hasClass("textnode")) {
  323. var js1=Xonomy.harvestText($this.toArray()[0]);
  324. var js2=Xonomy.harvestText($this.next().toArray()[0]);
  325. js1.value+=js2.value;
  326. $this.next().remove();
  327. $this.replaceWith(Xonomy.renderText(js1));
  328. merged=false;
  329. break;
  330. }
  331. }
  332. }
  333. $(".xonomy .attribute ").each(function(){ //reorder attributes if necessary
  334. var atName=this.getAttribute("data-name");
  335. var elName=this.parentNode.parentNode.parentNode.getAttribute("data-name");
  336. var elSpec=Xonomy.docSpec.elements[elName];
  337. var mustBeAfter=[]; for(var sibName in elSpec.attributes) {
  338. if(sibName==atName) break; else mustBeAfter.push(sibName);
  339. }
  340. var mustBeBefore=[]; var seen=false; for(var sibName in elSpec.attributes) {
  341. if(sibName==atName) seen=true; else if(seen) mustBeBefore.push(sibName);
  342. }
  343. if(mustBeBefore.length>0) { //is it after an attribute it cannot be after? then move it up until it's not!
  344. var $this=$(this);
  345. var ok; do {
  346. ok=true;
  347. for(var ii=0; ii<mustBeBefore.length; ii++) {
  348. if( $this.prevAll("*[data-name='"+mustBeBefore[ii]+"']").toArray().length>0 ) {
  349. $this.prev().before($this);
  350. ok=false;
  351. }
  352. }
  353. } while(!ok)
  354. }
  355. if(mustBeAfter.length>0) { //is it before an attribute it cannot be before? then move it down until it's not!
  356. var $this=$(this);
  357. var ok; do {
  358. ok=true;
  359. for(var ii=0; ii<mustBeAfter.length; ii++) {
  360. if( $this.nextAll("*[data-name='"+mustBeAfter[ii]+"']").toArray().length>0 ) {
  361. $this.next().after($this);
  362. ok=false;
  363. }
  364. }
  365. } while(!ok)
  366. }
  367. });
  368. $(".xonomy .attributes").each(function(){ //determine whether each attribute list has any shy attributes:
  369. if($(this).children(".shy").toArray().length==0) {
  370. $(this.parentNode).children(".rollouter").hide().removeClass("rolledout");
  371. $(this).removeClass("rolledout").css("display", "");
  372. } else {
  373. $(this.parentNode).children(".rollouter").show();
  374. }
  375. });
  376. $(".xonomy .element").each(function(){ //refresh display names, display values and captions:
  377. var elSpec=Xonomy.docSpec.elements[this.getAttribute("data-name")];
  378. if(elSpec.displayName) $(this).children(".tag").children(".name").html(Xonomy.textByLang(elSpec.displayName(Xonomy.harvestElement(this))));
  379. if(elSpec.caption) {
  380. var jsEl=Xonomy.harvestElement(this);
  381. $(this).children(".inlinecaption").html(Xonomy.textByLang(elSpec.caption(jsEl)));
  382. }
  383. if(elSpec.displayValue) {
  384. var jsEl=Xonomy.harvestElement(this);
  385. if(!jsEl.hasElements()) $(this).children(".children").html( Xonomy.textByLang(Xonomy.renderDisplayText(jsEl.getText(), elSpec.displayValue(jsEl))) );
  386. }
  387. $(this).children(".tag.opening").children(".attributes").children(".attribute").each(function(){
  388. var atSpec=elSpec.attributes[this.getAttribute("data-name")];
  389. if(atSpec.displayName) $(this).children(".name").html(Xonomy.textByLang(atSpec.displayName(Xonomy.harvestAttribute(this))));
  390. if(atSpec.displayValue) $(this).children(".value").html(Xonomy.textByLang(atSpec.displayValue(Xonomy.harvestAttribute(this))));
  391. if(atSpec.caption) $(this).children(".inlinecaption").html("&nbsp;"+Xonomy.textByLang(atSpec.caption(Xonomy.harvestAttribute(this)))+"&nbsp;");
  392. });
  393. });
  394. };
  395. Xonomy.harvestCache={};
  396. Xonomy.harvest=function() { //harvests the contents of an editor
  397. //Returns xml-as-string.
  398. var rootElement=$(".xonomy .element").first().toArray()[0];
  399. var js=Xonomy.harvestElement(rootElement);
  400. for(var key in Xonomy.namespaces) {
  401. if(!js.hasAttribute(key)) js.attributes.push({
  402. type: "attribute",
  403. name: key,
  404. value: Xonomy.namespaces[key],
  405. parent: js
  406. });
  407. }
  408. return Xonomy.js2xml(js);
  409. }
  410. Xonomy.harvestElement=function(htmlElement, jsParent) {
  411. var htmlID=htmlElement.id;
  412. if(!Xonomy.harvestCache[htmlID]) {
  413. var js=new Xonomy.surrogate(jsParent);
  414. js.type="element";
  415. js.name=htmlElement.getAttribute("data-name");
  416. js.htmlID=htmlElement.id;
  417. js.attributes=[];
  418. var htmlAttributes=$(htmlElement).find(".tag.opening > .attributes").toArray()[0];
  419. for(var i=0; i<htmlAttributes.childNodes.length; i++) {
  420. var htmlAttribute=htmlAttributes.childNodes[i];
  421. if($(htmlAttribute).hasClass("attribute")) js["attributes"].push(Xonomy.harvestAttribute(htmlAttribute, js));
  422. }
  423. js.children=[];
  424. var htmlChildren=$(htmlElement).children(".children").toArray()[0];
  425. for(var i=0; i<htmlChildren.childNodes.length; i++) {
  426. var htmlChild=htmlChildren.childNodes[i];
  427. if($(htmlChild).hasClass("element")) js["children"].push(Xonomy.harvestElement(htmlChild, js));
  428. else if($(htmlChild).hasClass("textnode")) js["children"].push(Xonomy.harvestText(htmlChild, js));
  429. }
  430. js=Xonomy.enrichElement(js);
  431. Xonomy.harvestCache[htmlID]=js;
  432. }
  433. return Xonomy.harvestCache[htmlID];
  434. };
  435. Xonomy.harvestAttribute=function(htmlAttribute, jsParent) {
  436. var htmlID=htmlAttribute.id;
  437. if(!Xonomy.harvestCache[htmlID]) {
  438. var js = new Xonomy.surrogate(jsParent);
  439. js.type = "attribute";
  440. js.name = htmlAttribute.getAttribute("data-name");
  441. js.htmlID = htmlAttribute.id;
  442. js.value = htmlAttribute.getAttribute("data-value");
  443. Xonomy.harvestCache[htmlID]=js;
  444. }
  445. return Xonomy.harvestCache[htmlID];
  446. }
  447. Xonomy.surrogate=function(jsParent) {
  448. this.internalParent=jsParent;
  449. }
  450. Xonomy.surrogate.prototype.parent=function() {
  451. if (!this.internalParent) {
  452. this.internalParent=Xonomy.harvestParentOf(this);
  453. }
  454. return this.internalParent;
  455. }
  456. Xonomy.harvestText = function (htmlText, jsParent) {
  457. var js = new Xonomy.surrogate(jsParent);
  458. js.type = "text";
  459. js.htmlID = htmlText.id;
  460. js.value = htmlText.getAttribute("data-value");
  461. return js;
  462. }
  463. Xonomy.harvestParentOf=function(js) {
  464. var jsParent=null;
  465. var $parent=$("#"+js.htmlID).parent().closest(".element");
  466. if($parent.toArray().length==1) {
  467. jsParent=Xonomy.harvestElement($parent.toArray()[0]);
  468. for(var i=0; i<jsParent.attributes.length; i++) if(jsParent.attributes[i].htmlID==js.htmlID) jsParent.attributes[i]=js;
  469. for(var i=0; i<jsParent.children.length; i++) if(jsParent.children[i].htmlID==js.htmlID) jsParent.children[i]=js;
  470. }
  471. return jsParent;
  472. };
  473. Xonomy.render=function(data, editor, docSpec) { //renders the contents of an editor
  474. //The data can be a Xonomy-compliant XML document, a Xonomy-compliant xml-as-string,
  475. //or a Xonomy-compliant JavaScript object.
  476. //The editor can be an HTML element, or the string ID of one.
  477. Xonomy.docSpec=docSpec;
  478. Xonomy.verifyDocSpec();
  479. //Clear namespace cache:
  480. Xonomy.namespaces={};
  481. //Convert doc to a JavaScript object, if it isn't a JavaScript object already:
  482. if(typeof(data)=="string") data=$.parseXML(data);
  483. if(data.documentElement) data=Xonomy.xml2js(data);
  484. //Make sure editor refers to an HTML element, if it doesn't already:
  485. if(typeof(editor)=="string") editor=document.getElementById(editor);
  486. if(!$(editor).hasClass("xonomy")) $(editor).addClass("xonomy"); //make sure it has class "xonomy"
  487. $(editor).addClass(Xonomy.mode);
  488. $(editor).hide();
  489. editor.innerHTML=Xonomy.renderElement(data, editor);
  490. $(editor).show();
  491. if(docSpec.allowLayby){
  492. var laybyHtml="<div class='layby closed empty' onclick='if($(this).hasClass(\"closed\")) Xonomy.openLayby()' ondragover='Xonomy.dragOver(event)' ondragleave='Xonomy.dragOut(event)' ondrop='Xonomy.drop(event)''>";
  493. laybyHtml+="<span class='button closer' onclick='Xonomy.closeLayby();'>&nbsp;</span>";
  494. laybyHtml+="<span class='button purger' onclick='Xonomy.emptyLayby()'>&nbsp;</span>";
  495. laybyHtml+="<div class='content'></div>";
  496. laybyHtml+="<div class='message'>"+Xonomy.textByLang(docSpec.laybyMessage)+"</div>";
  497. laybyHtml+="</div>";
  498. $(laybyHtml).appendTo($(editor));
  499. }
  500. if(docSpec.allowModeSwitching){
  501. $("<div class='modeSwitcher'><span class='nerd'></span><span class='laic'></span></div>").appendTo($(editor)).on("click", function(e){
  502. if(Xonomy.mode=="nerd") { Xonomy.setMode("laic"); } else { Xonomy.setMode("nerd"); }
  503. if(docSpec.onModeSwitch) docSpec.onModeSwitch(Xonomy.mode);
  504. });
  505. }
  506. //Make sure the "click off" handler is attached:
  507. $(document.body).off("click", Xonomy.clickoff);
  508. $(document.body).on("click", Xonomy.clickoff);
  509. //Make sure the "drag end" handler is attached:
  510. $(document.body).off("dragend", Xonomy.dragend);
  511. $(document.body).on("dragend", Xonomy.dragend);
  512. Xonomy.refresh();
  513. Xonomy.validate();
  514. };
  515. Xonomy.renderElement=function(element) {
  516. var htmlID=Xonomy.nextID();
  517. Xonomy.verifyDocSpecElement(element.name);
  518. var spec=Xonomy.docSpec.elements[element.name];
  519. var classNames="element";
  520. if(spec.canDropTo && spec.canDropTo.length>0) classNames+=" draggable";
  521. var hasText = spec.hasText(element);
  522. if(hasText) classNames+=" hasText";
  523. if(spec.inlineMenu && spec.inlineMenu.length>0) classNames+=" hasInlineMenu";
  524. if(spec.oneliner(element)) classNames+=" oneliner";
  525. if(!spec.collapsible(element)) {
  526. classNames+=" uncollapsible";
  527. } else {
  528. if(spec.collapsed(element) && element.children.length>0) classNames+=" collapsed";
  529. }
  530. if(spec.isInvisible && spec.isInvisible(element)) { classNames+=" invisible"; }
  531. if(spec.isReadOnly && spec.isReadOnly(element)) { readonly=true; classNames+=" readonly"; }
  532. if(spec.menu.length>0) classNames+=" hasMenu"; //not always accurate: whether an element has a menu is actually determined at runtime
  533. var displayName=element.name;
  534. if(spec.displayName) displayName=Xonomy.textByLang(spec.displayName(element));
  535. var title="";
  536. if(spec.title) title=Xonomy.textByLang(spec.title(element));
  537. var html="";
  538. html+='<div data-name="'+element.name+'" id="'+htmlID+'" class="'+classNames+'">';
  539. html+='<span class="connector">';
  540. html+='<span class="plusminus" onclick="Xonomy.plusminus(\''+htmlID+'\')"></span>';
  541. html+='<span class="draghandle" draggable="true" ondragstart="Xonomy.drag(event)"></span>';
  542. html+='</span>';
  543. html+='<span class="tag opening focusable" style="background-color: '+spec.backgroundColour(element)+';">';
  544. html+='<span class="punc">&lt;</span>';
  545. html+='<span class="warner"><span class="inside" onclick="Xonomy.click(\''+htmlID+'\', \'warner\')"></span></span>';
  546. html+='<span class="name" title="'+title+'" onclick="Xonomy.click(\''+htmlID+'\', \'openingTagName\')">'+displayName+'</span>';
  547. html+='<span class="attributes">';
  548. for(var i=0; i<element.attributes.length; i++) {
  549. Xonomy.verifyDocSpecAttribute(element.name, element.attributes[i].name);
  550. html+=Xonomy.renderAttribute(element.attributes[i], element.name);
  551. }
  552. html+='</span>';
  553. html+='<span class="rollouter focusable" onclick="Xonomy.click(\''+htmlID+'\', \'rollouter\')"></span>';
  554. html+='<span class="punc slash">/</span>';
  555. html+='<span class="punc">&gt;</span>';
  556. html+='</span>';
  557. if(spec.caption && !spec.oneliner(element)) html+="<span class='inlinecaption'>"+Xonomy.textByLang(spec.caption(element))+"</span>";
  558. html+='<span class="childrenCollapsed focusable" onclick="Xonomy.plusminus(\''+htmlID+'\', true)">&middot;&middot;&middot;</span>';
  559. html+='<div class="children">';
  560. if(spec.displayValue && !element.hasElements()) {
  561. html+=Xonomy.renderDisplayText(element.getText(), spec.displayValue(element));
  562. } else {
  563. var prevChildType="";
  564. if(hasText && (element.children.length==0 || element.children[0].type=="element")) {
  565. html+=Xonomy.renderText({type: "text", value: ""}); //if inline layout, insert empty text node between two elements
  566. }
  567. for(var i=0; i<element.children.length; i++) {
  568. var child=element.children[i];
  569. if(hasText && prevChildType=="element" && child.type=="element") {
  570. html+=Xonomy.renderText({type: "text", value: ""}); //if inline layout, insert empty text node between two elements
  571. }
  572. if(child.type=="text") html+=Xonomy.renderText(child); //text node
  573. else if(child.type=="element") html+=Xonomy.renderElement(child); //element node
  574. prevChildType=child.type;
  575. }
  576. if(hasText && element.children.length>1 && element.children[element.children.length-1].type=="element") {
  577. html+=Xonomy.renderText({type: "text", value: ""}); //if inline layout, insert empty text node between two elements
  578. }
  579. }
  580. html+='</div>';
  581. html+='<span class="tag closing focusable" style="background-color: '+spec.backgroundColour(element)+';">';
  582. html+='<span class="punc">&lt;</span>';
  583. html+='<span class="punc">/</span>';
  584. html+='<span class="name" onclick="Xonomy.click(\''+htmlID+'\', \'closingTagName\')">'+displayName+'</span>';
  585. html+='<span class="punc">&gt;</span>';
  586. html+='</span>';
  587. if(spec.caption && spec.oneliner(element)) html+="<span class='inlinecaption'>"+Xonomy.textByLang(spec.caption(element))+"</span>";
  588. html+='</div>';
  589. element.htmlID = htmlID;
  590. return html;
  591. };
  592. Xonomy.renderAttribute=function(attribute, optionalParentName) {
  593. var htmlID=Xonomy.nextID();
  594. classNames="attribute";
  595. var readonly=false;
  596. var displayName=attribute.name;
  597. var displayValue=Xonomy.xmlEscape(attribute.value);
  598. var caption="";
  599. var title="";
  600. if(optionalParentName) {
  601. var spec=Xonomy.docSpec.elements[optionalParentName].attributes[attribute.name];
  602. if(spec) {
  603. if(spec.displayName) displayName=Xonomy.textByLang(spec.displayName(attribute));
  604. if(spec.displayValue) displayValue=Xonomy.textByLang(spec.displayValue(attribute));
  605. if(spec.title) title=Xonomy.textByLang(spec.title(attribute));
  606. if(spec.caption) caption=Xonomy.textByLang(spec.caption(attribute));
  607. if(spec.isReadOnly && spec.isReadOnly(attribute)) { readonly=true; classNames+=" readonly"; }
  608. if(spec.isInvisible && spec.isInvisible(attribute)) { classNames+=" invisible"; }
  609. if(spec.shy && spec.shy(attribute)) { classNames+=" shy"; }
  610. }
  611. }
  612. var html="";
  613. html+='<span data-name="'+attribute.name+'" data-value="'+Xonomy.xmlEscape(attribute.value)+'" id="'+htmlID+'" class="'+classNames+'">';
  614. html+='<span class="punc"> </span>';
  615. var onclick=''; if(!readonly) onclick=' onclick="Xonomy.click(\''+htmlID+'\', \'attributeName\')"';
  616. html+='<span class="warner"><span class="inside" onclick="Xonomy.click(\''+htmlID+'\', \'warner\')"></span></span>';
  617. html+='<span class="name attributeName focusable" title="'+title+'"'+onclick+'>'+displayName+'</span>';
  618. html+='<span class="punc">=</span>';
  619. var onclick=''; if(!readonly) onclick=' onclick="Xonomy.click(\''+htmlID+'\', \'attributeValue\')"';
  620. html+='<span class="valueContainer attributeValue focusable"'+onclick+'>';
  621. html+='<span class="punc">"</span>';
  622. html+='<span class="value">'+displayValue+'</span>';
  623. html+='<span class="punc">"</span>';
  624. html+='</span>';
  625. if(caption) html+="<span class='inlinecaption'>"+caption+"</span>";
  626. html+='</span>';
  627. attribute.htmlID = htmlID;
  628. return html;
  629. };
  630. Xonomy.renderText=function(text) {
  631. var htmlID=Xonomy.nextID();
  632. var classNames="textnode focusable";
  633. if($.trim(text.value)=="") classNames+=" whitespace";
  634. if(text.value=="") classNames+=" empty";
  635. var html="";
  636. html+='<div id="'+htmlID+'" data-value="'+Xonomy.xmlEscape(text.value)+'" class="'+classNames+'">';
  637. html+='<span class="connector"></span>';
  638. var txt=Xonomy.chewText(text.value);
  639. html+='<span class="value" onclick="Xonomy.click(\''+htmlID+'\', \'text\')"><span class="insertionPoint"><span class="inside"></span></span><span class="dots"></span>'+txt+'</span>';
  640. html+='</div>';
  641. text.htmlID = htmlID;
  642. return html;
  643. }
  644. Xonomy.renderDisplayText=function(text, displayText) {
  645. var htmlID=Xonomy.nextID();
  646. var classNames="textnode";
  647. if($.trim(displayText)=="") classNames+=" whitespace";
  648. if(displayText=="") classNames+=" empty";
  649. var html="";
  650. html+='<div id="'+htmlID+'" data-value="'+Xonomy.xmlEscape(text)+'" class="'+classNames+'">';
  651. html+='<span class="connector"></span>';
  652. html+='<span class="value" onclick="Xonomy.click(\''+htmlID+'\', \'text\')"><span class="insertionPoint"><span class="inside"></span></span><span class="dots"></span>'+Xonomy.textByLang(displayText)+'</span>';
  653. html+='</div>';
  654. text.htmlID = htmlID;
  655. return html;
  656. }
  657. Xonomy.chewText=function(txt) {
  658. var ret="";
  659. ret+="<span class='word'>"; //start word
  660. for(var i=0; i<txt.length; i++) {
  661. if(txt[i]==" ") ret+="</span>"; //end word
  662. var t=Xonomy.xmlEscape(txt[i])
  663. if(i==0 && t==" ") t="<span class='space'>&middot;</span>"; //leading space
  664. if(i==txt.length-1 && t==" ") t="<span class='space'>&middot;</span>"; //trailing space
  665. var id=Xonomy.nextID();
  666. ret+="<span id='"+id+"' class='char focusable' onclick='if((event.ctrlKey||event.metaKey) && $(this).closest(\".element\").hasClass(\"hasInlineMenu\")) Xonomy.charClick(this)'>"+t+"<span class='selector'><span class='inside' onclick='Xonomy.charClick(this.parentNode.parentNode)'></span></span></span>";
  667. if(txt[i]==" ") ret+="<span class='word'>"; //start word
  668. }
  669. ret+="</span>"; //end word
  670. return ret;
  671. };
  672. Xonomy.charClick=function(c) {
  673. Xonomy.clickoff();
  674. var isReadOnly=( $(c).closest(".readonly").toArray().length>0 );
  675. if(!isReadOnly) {
  676. Xonomy.notclick=true;
  677. if(
  678. $(".xonomy .char.on").toArray().length==1 && //if there is precisely one previously selected character
  679. $(".xonomy .char.on").closest(".element").is($(c).closest(".element")) //and if it has the same parent element as this character
  680. ) {
  681. var $element=$(".xonomy .char.on").closest(".element");
  682. var chars=$element.find(".char").toArray();
  683. var iFrom=$.inArray($(".xonomy .char.on").toArray()[0], chars);
  684. var iTill=$.inArray(c, chars);
  685. if(iFrom>iTill) {var temp=iFrom; iFrom=iTill; iTill=temp;}
  686. for(var i=0; i<chars.length; i++) { //highlight all chars between start and end
  687. if(i>=iFrom && i<=iTill) $(chars[i]).addClass("on");
  688. }
  689. //Save for later the info Xonomy needs to know what to wrap:
  690. Xonomy.textFromID=$(chars[iFrom]).closest(".textnode").attr("id");
  691. Xonomy.textTillID=$(chars[iTill]).closest(".textnode").attr("id");
  692. Xonomy.textFromIndex=$.inArray(chars[iFrom], $("#"+Xonomy.textFromID).find(".char").toArray());
  693. Xonomy.textTillIndex=$.inArray(chars[iTill], $("#"+Xonomy.textTillID).find(".char").toArray());
  694. //Show inline menu etc:
  695. var htmlID=$element.attr("id");
  696. var content=Xonomy.inlineMenu(htmlID); //compose bubble content
  697. if(content!="" && content!="<div class='menu'></div>") {
  698. document.body.appendChild(Xonomy.makeBubble(content)); //create bubble
  699. Xonomy.showBubble($("#"+htmlID+" .char.on").last()); //anchor bubble to highlighted chars
  700. }
  701. Xonomy.clearChars=true;
  702. } else {
  703. $(".xonomy .char.on").removeClass("on");
  704. $(c).addClass("on");
  705. Xonomy.setFocus(c.id, "char");
  706. }
  707. }
  708. };
  709. Xonomy.wrap=function(htmlID, param) {
  710. Xonomy.clickoff();
  711. Xonomy.destroyBubble();
  712. var xml=param.template;
  713. var ph=param.placeholder;
  714. var jsElement=Xonomy.harvestElement(document.getElementById(htmlID));
  715. if(Xonomy.textFromID==Xonomy.textTillID) { //abc --> a<XYZ>b</XYZ>c
  716. var jsOld=Xonomy.harvestText(document.getElementById(Xonomy.textFromID));
  717. var txtOpen=jsOld.value.substring(0, Xonomy.textFromIndex);
  718. var txtMiddle=jsOld.value.substring(Xonomy.textFromIndex, Xonomy.textTillIndex+1);
  719. var txtClose=jsOld.value.substring(Xonomy.textTillIndex+1);
  720. xml=xml.replace(ph, Xonomy.xmlEscape(txtMiddle));
  721. var html="";
  722. html+=Xonomy.renderText({type: "text", value: txtOpen});
  723. var js=Xonomy.xml2js(xml, jsElement); html+=Xonomy.renderElement(js); var newID=js.htmlID;
  724. html+=Xonomy.renderText({type: "text", value: txtClose});
  725. $("#"+Xonomy.textFromID).replaceWith(html);
  726. window.setTimeout(function(){ Xonomy.setFocus(newID, "openingTagName"); }, 100);
  727. } else { //ab<...>cd --> a<XYZ>b<...>c</XYZ>d
  728. var jsOldOpen=Xonomy.harvestText(document.getElementById(Xonomy.textFromID));
  729. var jsOldClose=Xonomy.harvestText(document.getElementById(Xonomy.textTillID));
  730. var txtOpen=jsOldOpen.value.substring(0, Xonomy.textFromIndex);
  731. var txtMiddleOpen=jsOldOpen.value.substring(Xonomy.textFromIndex);
  732. var txtMiddleClose=jsOldClose.value.substring(0, Xonomy.textTillIndex+1);
  733. var txtClose=jsOldClose.value.substring(Xonomy.textTillIndex+1);
  734. xml=xml.replace(ph, Xonomy.xmlEscape(txtMiddleOpen)+ph);
  735. $("#"+Xonomy.textFromID).nextUntil("#"+Xonomy.textTillID).each(function(){
  736. if($(this).hasClass("element")) xml=xml.replace(ph, Xonomy.js2xml(Xonomy.harvestElement(this))+ph);
  737. else if($(this).hasClass("textnode")) xml=xml.replace(ph, Xonomy.js2xml(Xonomy.harvestText(this))+ph);
  738. });
  739. xml=xml.replace(ph, Xonomy.xmlEscape(txtMiddleClose));
  740. $("#"+Xonomy.textFromID).nextUntil("#"+Xonomy.textTillID).remove();
  741. $("#"+Xonomy.textTillID).remove();
  742. var html="";
  743. html+=Xonomy.renderText({type: "text", value: txtOpen});
  744. var js=Xonomy.xml2js(xml, jsElement); html+=Xonomy.renderElement(js); var newID=js.htmlID;
  745. html+=Xonomy.renderText({type: "text", value: txtClose});
  746. $("#"+Xonomy.textFromID).replaceWith(html);
  747. window.setTimeout(function(){ Xonomy.setFocus(newID, "openingTagName"); }, 100);
  748. }
  749. Xonomy.changed();
  750. };
  751. Xonomy.unwrap=function(htmlID, param) {
  752. var parentID=$("#"+htmlID)[0].parentNode.parentNode.id;
  753. Xonomy.clickoff();
  754. $("#"+htmlID).replaceWith($("#"+htmlID+" > .children > *"));
  755. Xonomy.changed();
  756. window.setTimeout(function(){ Xonomy.setFocus(parentID, "openingTagName"); }, 100);
  757. };
  758. Xonomy.plusminus=function(htmlID, forceExpand) {
  759. var $element=$("#"+htmlID);
  760. var $children=$element.children(".children");
  761. if($element.hasClass("collapsed")) {
  762. $children.hide();
  763. $element.removeClass("collapsed");
  764. if($element.hasClass("oneliner")) $children.fadeIn("fast"); else $children.slideDown("fast");
  765. } else if(!forceExpand) {
  766. Xonomy.updateCollapsoid(htmlID);
  767. if($element.hasClass("oneliner")) $children.fadeOut("fast", function(){ $element.addClass("collapsed"); });
  768. else $children.slideUp("fast", function(){ $element.addClass("collapsed"); });
  769. }
  770. window.setTimeout(function(){
  771. if($("#"+Xonomy.currentHtmlId+" .opening:visible").length>0) {
  772. Xonomy.setFocus(Xonomy.currentHtmlId, "openingTagName");
  773. } else {
  774. Xonomy.setFocus(Xonomy.currentHtmlId, "childrenCollapsed");
  775. }
  776. }, 300);
  777. };
  778. Xonomy.updateCollapsoid=function(htmlID) {
  779. var $element=$("#"+htmlID);
  780. var whisper="";
  781. var elementName=$element.data("name");
  782. var spec=Xonomy.docSpec.elements[elementName];
  783. if(spec.collapsoid) {
  784. whisper=spec.collapsoid(Xonomy.harvestElement($element.toArray()[0]));
  785. } else {
  786. var abbreviated=false;
  787. $element.find(".textnode").each(function(){
  788. var txt=Xonomy.harvestText(this).value;
  789. for(var i=0; i<txt.length; i++) {
  790. if(whisper.length<35) whisper+=txt[i]; else abbreviated=true;
  791. }
  792. whisper+=" ";
  793. });
  794. whisper=whisper.replace(/ +/g, " ").replace(/ +$/g, "");
  795. if(abbreviated && !$element.hasClass("oneliner") && whisper!="...") whisper+="...";
  796. }
  797. if(whisper=="" || !whisper) whisper="...";
  798. $element.children(".childrenCollapsed").html(whisper);
  799. };
  800. Xonomy.lastClickWhat="";
  801. Xonomy.click=function(htmlID, what) {
  802. if(!Xonomy.notclick) {
  803. Xonomy.clickoff();
  804. Xonomy.lastClickWhat=what;
  805. Xonomy.currentHtmlId=htmlID;
  806. Xonomy.currentFocus=what;
  807. $(".xonomy .char.on").removeClass("on");
  808. var isReadOnly=( $("#"+htmlID).hasClass("readonly") || $("#"+htmlID).closest(".readonly").toArray().length>0 );
  809. if(!isReadOnly && (what=="openingTagName" || what=="closingTagName") ) {
  810. $("#"+htmlID).addClass("current"); //make the element current
  811. var content=Xonomy.elementMenu(htmlID); //compose bubble content
  812. if(content!="" && content!="<div class='menu'></div>") {
  813. document.body.appendChild(Xonomy.makeBubble(content)); //create bubble
  814. if(what=="openingTagName") Xonomy.showBubble($("#"+htmlID+" > .tag.opening > .name")); //anchor bubble to opening tag
  815. if(what=="closingTagName") Xonomy.showBubble($("#"+htmlID+" > .tag.closing > .name")); //anchor bubble to closing tag
  816. }
  817. var surrogateElem = Xonomy.harvestElement(document.getElementById(htmlID));
  818. $("#"+htmlID).trigger("xonomy-click-element", [surrogateElem]);
  819. }
  820. if(!isReadOnly && what=="attributeName") {
  821. $("#"+htmlID).addClass("current"); //make the attribute current
  822. var content=Xonomy.attributeMenu(htmlID); //compose bubble content
  823. if(content!="" && content!="<div class='menu'></div>") {
  824. document.body.appendChild(Xonomy.makeBubble(content)); //create bubble
  825. Xonomy.showBubble($("#"+htmlID+" > .name")); //anchor bubble to attribute name
  826. }
  827. var surrogateAttr = Xonomy.harvestAttribute(document.getElementById(htmlID));
  828. $("#"+htmlID).trigger("xonomy-click-attribute", [surrogateAttr]);
  829. }
  830. if(!isReadOnly && what=="attributeValue") {
  831. $("#"+htmlID+" > .valueContainer").addClass("current"); //make attribute value current
  832. var name=$("#"+htmlID).attr("data-name"); //obtain attribute's name
  833. var value=$("#"+htmlID).attr("data-value"); //obtain current value
  834. var elName=$("#"+htmlID).closest(".element").attr("data-name");
  835. Xonomy.verifyDocSpecAttribute(elName, name);
  836. var spec=Xonomy.docSpec.elements[elName].attributes[name];
  837. var content=spec.asker(value, spec.askerParameter, Xonomy.harvestAttribute(document.getElementById(htmlID))); //compose bubble content
  838. if(content!="" && content!="<div class='menu'></div>") {
  839. document.body.appendChild(Xonomy.makeBubble(content)); //create bubble
  840. Xonomy.showBubble($("#"+htmlID+" > .valueContainer > .value")); //anchor bubble to value
  841. Xonomy.answer=function(val) {
  842. var obj=document.getElementById(htmlID);
  843. var html=Xonomy.renderAttribute({type: "attribute", name: name, value: val}, elName);
  844. $(obj).replaceWith(html);
  845. Xonomy.changed();
  846. window.setTimeout(function(){Xonomy.clickoff(); Xonomy.setFocus($(html).prop("id"), what)}, 100);
  847. };
  848. }
  849. }
  850. if(!isReadOnly && what=="text") {
  851. $("#"+htmlID).addClass("current");
  852. var value=$("#"+htmlID).attr("data-value"); //obtain current value
  853. var elName=$("#"+htmlID).closest(".element").attr("data-name");
  854. var spec=Xonomy.docSpec.elements[elName];
  855. if (typeof(spec.asker) != "function") {
  856. var content=Xonomy.askLongString(value, null, Xonomy.harvestElement($("#"+htmlID).closest(".element").toArray()[0])); //compose bubble content
  857. } else {
  858. var content=spec.asker(value, spec.askerParameter, Xonomy.harvestElement($("#"+htmlID).closest(".element").toArray()[0])); //use specified asker
  859. }
  860. if(content!="" && content!="<div class='menu'></div>") {
  861. document.body.appendChild(Xonomy.makeBubble(content)); //create bubble
  862. Xonomy.showBubble($("#"+htmlID+" > .value")); //anchor bubble to value
  863. Xonomy.answer=function(val) {
  864. var obj=document.getElementById(htmlID);
  865. var jsText = {type: "text", value: val};
  866. var html=Xonomy.renderText(jsText);
  867. $(obj).replaceWith(html);
  868. Xonomy.changed(Xonomy.harvestText(document.getElementById(jsText.htmlID)));
  869. window.setTimeout(function(){Xonomy.clickoff(); Xonomy.setFocus($(html).prop("id"), what)}, 100);
  870. };
  871. }
  872. }
  873. if(what=="warner") {
  874. //$("#"+htmlID).addClass("current");
  875. var content=""; //compose bubble content
  876. for(var iWarning=0; iWarning<Xonomy.warnings.length; iWarning++) {
  877. var warning=Xonomy.warnings[iWarning];
  878. if(warning.htmlID==htmlID) {
  879. content+="<div class='warning'>"+Xonomy.formatCaption(Xonomy.textByLang(warning.text))+"</div>";
  880. }
  881. }
  882. document.body.appendChild(Xonomy.makeBubble(content)); //create bubble
  883. Xonomy.showBubble($("#"+htmlID+" .warner .inside").first()); //anchor bubble to warner
  884. }
  885. if(what=="rollouter" && $("#"+htmlID+" > .tag.opening > .attributes").children(".shy").toArray().length>0) {
  886. if( $("#"+htmlID).children(".tag.opening").children(".rollouter").hasClass("rolledout") ) {
  887. $("#"+htmlID).children(".tag.opening").children(".rollouter").removeClass("rolledout");
  888. $("#"+htmlID).children(".tag.opening").children(".attributes").slideUp("fast", function(){
  889. $(this).removeClass("rolledout").css("display", "");
  890. })
  891. } else {
  892. $("#"+htmlID).children(".tag.opening").children(".rollouter").addClass("rolledout");
  893. $("#"+htmlID).children(".tag.opening").children(".attributes").addClass("rolledout").hide().slideDown("fast");
  894. }
  895. window.setTimeout(function(){Xonomy.setFocus(htmlID, "rollouter")}, 100);
  896. }
  897. Xonomy.notclick=true;
  898. }
  899. };
  900. Xonomy.notclick=false; //should the latest click-off event be ignored?
  901. Xonomy.clearChars=false; //if true, un-highlight any highlighted characters at the next click-off event
  902. Xonomy.clickoff=function() { //event handler for the document-wide click-off event.
  903. if(!Xonomy.notclick) {
  904. Xonomy.currentHtmlId=null;
  905. Xonomy.currentFocus=null;
  906. Xonomy.destroyBubble();
  907. $(".xonomy .current").removeClass("current");
  908. $(".xonomy .focused").removeClass("focused");
  909. if(Xonomy.clearChars) {
  910. $(".xonomy .char.on").removeClass("on");
  911. Xonomy.clearChars=false;
  912. }
  913. }
  914. Xonomy.notclick=false;
  915. };
  916. Xonomy.destroyBubble=function() {
  917. if(document.getElementById("xonomyBubble")) {
  918. var bubble=document.getElementById("xonomyBubble");
  919. $(bubble).find(":focus").blur();
  920. bubble.parentNode.removeChild(bubble);
  921. if(Xonomy.keyboardEventCatcher) Xonomy.keyboardEventCatcher.focus();
  922. }
  923. };
  924. Xonomy.makeBubble=function(content) {
  925. Xonomy.destroyBubble();
  926. var bubble=document.createElement("div");
  927. bubble.id="xonomyBubble";
  928. bubble.className=Xonomy.mode;
  929. bubble.innerHTML="<div class='inside' onclick='Xonomy.notclick=true;'>"
  930. +"<div id='xonomyBubbleContent'>"+content+"</div>"
  931. +"</div>";
  932. return bubble;
  933. };
  934. Xonomy.showBubble=function($anchor) {
  935. var $bubble=$("#xonomyBubble");
  936. var offset=$anchor.offset();
  937. var screenWidth = $("body").width();
  938. var screenHeight = $(document).height();
  939. var bubbleHeight = $bubble.outerHeight();
  940. var width = $anchor.width(); if (width > 40) width = 40;
  941. var height = $anchor.height(); if (height > 25) height = 25;
  942. if (Xonomy.mode == "laic") { width = width - 25; height = height + 10; }
  943. function verticalPlacement() {
  944. var top = "";
  945. var bottom = "";
  946. if (offset.top + height + bubbleHeight <= screenHeight) {
  947. // enough space - open down
  948. top = (offset.top + height) + "px";
  949. } else if (screenHeight - offset.top + 5 + bubbleHeight > 0) {
  950. // 5px above for some padding. Anchor using bottom so animation opens upwards.
  951. bottom = (screenHeight - offset.top + 5) + "px";
  952. } else {
  953. // neither downwards nor upwards is enough space => center the bubble
  954. top = (screenHeight - bubbleHeight)/2 + "px";
  955. }
  956. return { top: top, bottom: bottom };
  957. }
  958. var placement = verticalPlacement();
  959. if(offset.left<screenWidth/2) {
  960. placement.left = (offset.left + width - 15) + "px";
  961. } else {
  962. $bubble.addClass("rightAnchored");
  963. placement.right = (screenWidth - offset.left) + "px";
  964. }
  965. $bubble.css(placement);
  966. $bubble.slideDown("fast", function() {
  967. if(Xonomy.keyNav) $bubble.find(".focusme").first().focus(); //if the context menu contains anything with the class name 'focusme', focus it.
  968. else $bubble.find("input.focusme, select.focusme, textarea.focusme").first().focus();
  969. });
  970. $bubble.on("keyup", function(event){
  971. if(event.which==27) Xonomy.destroyBubble();
  972. });
  973. if(Xonomy.keyNav) {
  974. $bubble.find("div.focusme").on("keyup", function(event){
  975. if(event.which==40) { //down key
  976. var $item=$(event.delegateTarget);
  977. var $items=$bubble.find(".focusme:visible");
  978. var $next=$items.eq( $items.index($item[0])+1 );
  979. $next.focus();
  980. }
  981. if(event.which==38) { //up key
  982. var $item=$(event.delegateTarget);
  983. var $items=$bubble.find("div.focusme:visible");
  984. var $next=$items.eq( $items.index($item[0])-1 );
  985. $next.focus();
  986. }
  987. if(event.which==13) { //enter key
  988. $(event.delegateTarget).click();
  989. Xonomy.notclick=false;
  990. }
  991. });
  992. }
  993. };
  994. Xonomy.askString=function(defaultString, askerParameter, jsMe) {
  995. var width=($("body").width()*.5)-75
  996. var html="";
  997. html+="<form onsubmit='Xonomy.answer(this.val.value); return false'>";
  998. html+="<input name='val' class='textbox focusme' style='width: "+width+"px;' value='"+Xonomy.xmlEscape(defaultString)+"' onkeyup='Xonomy.notKeyUp=true'/>";
  999. html+=" <input type='submit' value='OK'>";
  1000. html+="</form>";
  1001. return html;
  1002. };
  1003. Xonomy.askLongString=function(defaultString, askerParameter, jsMe) {
  1004. var width=($("body").width()*.5)-75
  1005. var html="";
  1006. html+="<form onsubmit='Xonomy.answer(this.val.value); return false'>";
  1007. html+="<textarea name='val' class='textbox focusme' spellcheck='false' style='width: "+width+"px; height: 150px;'>"+Xonomy.xmlEscape(defaultString)+"</textarea>";
  1008. html+="<div class='submitline'><input type='submit' value='OK'></div>";
  1009. html+="</form>";
  1010. return html;
  1011. };
  1012. Xonomy.askPicklist=function(defaultString, picklist, jsMe) {
  1013. var html="";
  1014. html+=Xonomy.pickerMenu(picklist, defaultString);
  1015. return html;
  1016. };
  1017. Xonomy.askOpenPicklist=function(defaultString, picklist) {
  1018. var isInPicklist=false;
  1019. var html="";
  1020. html+=Xonomy.pickerMenu(picklist, defaultString);
  1021. html+="<form class='undermenu' onsubmit='Xonomy.answer(this.val.value); return false'>";
  1022. html+="<input name='val' class='textbox focusme' value='"+(!isInPicklist ? Xonomy.xmlEscape(defaultString) : "")+"' onkeyup='Xonomy.notKeyUp=true'/>";
  1023. html+=" <input type='submit' value='OK'>";
  1024. html+="</form>";
  1025. return html;
  1026. };
  1027. Xonomy.askRemote=function(defaultString, param, jsMe) {
  1028. var html="";
  1029. if(param.searchUrl || param.createUrl) {
  1030. html+="<form class='overmenu' onsubmit='return Xonomy.remoteSearch(\""+Xonomy.xmlEscape(param.searchUrl, true)+"\", \""+Xonomy.xmlEscape(param.urlPlaceholder, true)+"\", \""+Xonomy.xmlEscape(Xonomy.jsEscape(defaultString))+"\")'>";
  1031. html+="<input name='val' class='textbox focusme' value=''/>";
  1032. if(param.searchUrl) html+=" <button class='buttonSearch' onclick='return Xonomy.remoteSearch(\""+Xonomy.xmlEscape(param.searchUrl, true)+"\", \""+Xonomy.xmlEscape(param.urlPlaceholder, true)+"\", \""+Xonomy.xmlEscape(Xonomy.jsEscape(defaultString))+"\")'>&nbsp;</button>";
  1033. if(param.createUrl) html+=" <button class='buttonCreate' onclick='return Xonomy.remoteCreate(\""+Xonomy.xmlEscape(param.createUrl, true)+"\", \""+Xonomy.xmlEscape( (param.searchUrl?param.searchUrl:param.url) , true)+"\", \""+Xonomy.xmlEscape(param.urlPlaceholder, true)+"\", \""+Xonomy.xmlEscape(Xonomy.jsEscape(defaultString))+"\")'>&nbsp;</button>";
  1034. html+="</form>";
  1035. }
  1036. html+=Xonomy.wyc(param.url, function(picklist){
  1037. var items=[];
  1038. if(param.add) for(var i=0; i<param.add.length; i++) items.push(param.add[i]);
  1039. for(var i=0; i<picklist.length; i++) items.push(picklist[i]);
  1040. return Xonomy.pickerMenu(items, defaultString);
  1041. });
  1042. Xonomy.lastAskerParam=param;
  1043. return html;
  1044. };
  1045. Xonomy.lastAskerParam=null;
  1046. Xonomy.remoteSearch=function(searchUrl, urlPlaceholder, defaultString){
  1047. var text=$("#xonomyBubble input.textbox").val();
  1048. searchUrl=searchUrl.replace(urlPlaceholder, encodeURIComponent(text));
  1049. $("#xonomyBubble .menu").replaceWith( Xonomy.wyc(searchUrl, function(picklist){
  1050. var items=[];
  1051. if(text=="" && Xonomy.lastAskerParam.add) for(var i=0; i<Xonomy.lastAskerParam.add.length; i++) items.push(Xonomy.lastAskerParam.add[i]);
  1052. for(var i=0; i<picklist.length; i++) items.push(picklist[i]);
  1053. return Xonomy.pickerMenu(items, defaultString);
  1054. }));
  1055. return false;
  1056. };
  1057. Xonomy.remoteCreate=function(createUrl, searchUrl, urlPlaceholder, defaultString){
  1058. var text=$.trim($("#xonomyBubble input.textbox").val());
  1059. if(text!="") {
  1060. createUrl=createUrl.replace(urlPlaceholder, encodeURIComponent(text));
  1061. searchUrl=searchUrl.replace(urlPlaceholder, encodeURIComponent(text));
  1062. $.ajax({url: createUrl, dataType: "text", method: "POST"}).done(function(data){
  1063. if(Xonomy.wycCache[searchUrl]) delete Xonomy.wycCache[searchUrl];
  1064. $("#xonomyBubble .menu").replaceWith( Xonomy.wyc(searchUrl, function(picklist){ return Xonomy.pickerMenu(picklist, defaultString); }) );
  1065. });
  1066. }
  1067. return false;
  1068. };
  1069. Xonomy.pickerMenu=function(picklist, defaultString){
  1070. var html="";
  1071. html+="<div class='menu'>";
  1072. for(var i=0; i<picklist.length; i++) {
  1073. var item=picklist[i];
  1074. if(typeof(item)=="string") item={value: item, caption: ""};
  1075. html+="<div class='menuItem focusme techno"+(item.value==defaultString?" current":"")+"' tabindex='1' onclick='Xonomy.answer(\""+Xonomy.xmlEscape(item.value)+"\")'>";
  1076. var alone=true;
  1077. html+="<span class='punc'>\"</span>";
  1078. if(item.displayValue) {
  1079. html+=Xonomy.textByLang(item.displayValue);
  1080. alone=false;
  1081. } else {
  1082. html+=Xonomy.xmlEscape(item.value);
  1083. if(item.value) alone=false;
  1084. }
  1085. html+="<span class='punc'>\"</span>";
  1086. if(item.caption!="") html+=" <span class='explainer "+(alone?"alone":"")+"'>"+Xonomy.xmlEscape(Xonomy.textByLang(item.caption))+"</span>";
  1087. html+="</div>";
  1088. }
  1089. html+="</div>";
  1090. return html;
  1091. };
  1092. Xonomy.wycLastID=0;
  1093. Xonomy.wycCache={};
  1094. Xonomy.wyc=function(url, callback){ //a "when-you-can" function for delayed rendering: gets json from url, passes it to callback, and delayed-returns html-as-string from callback
  1095. Xonomy.wycLastID++;
  1096. var wycID="xonomy_wyc_"+Xonomy.wycLastID;
  1097. if(Xonomy.wycCache[url]) return callback(Xonomy.wycCache[url]);
  1098. $.ajax({url: url, dataType: "json", method: "POST"}).done(function(data){
  1099. $("#"+wycID).replaceWith(callback(data));
  1100. Xonomy.wycCache[url]=data;
  1101. });
  1102. return "<span class='wyc' id='"+wycID+"'></span>";
  1103. };
  1104. Xonomy.toggleSubmenu=function(menuItem){
  1105. var $menuItem=$(menuItem);
  1106. if($menuItem.hasClass("expanded")){ $menuItem.find(".submenu").first().slideUp("fast", function(){$menuItem.removeClass("expanded");}); }
  1107. else { $menuItem.find(".submenu").first().slideDown("fast", function(){$menuItem.addClass("expanded");}); };
  1108. }
  1109. Xonomy.internalMenu=function(htmlID, items, harvest, getter, indices) {
  1110. indices = indices || [];
  1111. var fragments = items.map(function (item, i) {
  1112. Xonomy.verifyDocSpecMenuItem(item);
  1113. var jsMe=harvest(document.getElementById(htmlID));
  1114. var includeIt=!item.hideIf(jsMe);
  1115. var html="";
  1116. if(includeIt) {
  1117. indices.push(i);
  1118. var icon=""; if(item.icon) icon="<span class='icon'><img src='"+item.icon+"'/></span> ";
  1119. var key=""; if(item.keyTrigger && item.keyCaption) key="<span class='keyCaption'>"+Xonomy.textByLang(item.keyCaption)+"</span>";
  1120. if (item.menu) {
  1121. var internalHtml=Xonomy.internalMenu(htmlID, item.menu, harvest, getter, indices);
  1122. if(internalHtml!="<div class='submenu'></div>") {
  1123. html+="<div class='menuItem"+(item.expanded(jsMe)?" expanded":"")+"'>";
  1124. html+="<div class='menuLabel focusme' tabindex='0' onkeydown='if(Xonomy.keyNav && [37, 39].indexOf(event.which)>-1) Xonomy.toggleSubmenu(this.parentNode)' onclick='Xonomy.toggleSubmenu(this.parentNode)'>"+icon+Xonomy.formatCaption(Xonomy.textByLang(item.caption(jsMe)))+"</div>";
  1125. html+=internalHtml;
  1126. html+="</div>";
  1127. }
  1128. } else {
  1129. html+="<div class='menuItem focusme' tabindex='0' onclick='Xonomy.callMenuFunction("+getter(indices)+", \""+htmlID+"\")'>";
  1130. html+=key+icon+Xonomy.formatCaption(Xonomy.textByLang(item.caption(jsMe)));
  1131. html+="</div>";
  1132. }
  1133. indices.pop();
  1134. }
  1135. return html;
  1136. });
  1137. var cls = !indices.length ? 'menu' : 'submenu';
  1138. return fragments.length
  1139. ? "<div class='"+cls+"'>"+fragments.join("")+"</div>"
  1140. : "";
  1141. };
  1142. Xonomy.attributeMenu=function(htmlID) {
  1143. var name=$("#"+htmlID).attr("data-name"); //obtain attribute's name
  1144. var elName=$("#"+htmlID).closest(".element").attr("data-name"); //obtain element's name
  1145. Xonomy.verifyDocSpecAttribute(elName, name);
  1146. var spec=Xonomy.docSpec.elements[elName].attributes[name];
  1147. function getter(indices) {
  1148. return 'Xonomy.docSpec.elements["'+elName+'"].attributes["'+name+'"].menu['+indices.join('].menu[')+']';
  1149. }
  1150. return Xonomy.internalMenu(htmlID, spec.menu, Xonomy.harvestAttribute, getter);
  1151. };
  1152. Xonomy.elementMenu=function(htmlID) {
  1153. var elName=$("#"+htmlID).attr("data-name"); //obtain element's name
  1154. var spec=Xonomy.docSpec.elements[elName];
  1155. function getter(indices) {
  1156. return 'Xonomy.docSpec.elements["'+elName+'"].menu['+indices.join('].menu[')+']';
  1157. }
  1158. return Xonomy.internalMenu(htmlID, spec.menu, Xonomy.harvestElement, getter);
  1159. };
  1160. Xonomy.inlineMenu=function(htmlID) {
  1161. var elName=$("#"+htmlID).attr("data-name"); //obtain element's name
  1162. var spec=Xonomy.docSpec.elements[elName];
  1163. function getter(indices) {
  1164. return 'Xonomy.docSpec.elements["'+elName+'"].inlineMenu['+indices.join('].menu[')+']';
  1165. }
  1166. return Xonomy.internalMenu(htmlID, spec.inlineMenu, Xonomy.harvestElement, getter);
  1167. };
  1168. Xonomy.callMenuFunction=function(menuItem, htmlID) {
  1169. menuItem.action(htmlID, menuItem.actionParameter);
  1170. };
  1171. Xonomy.formatCaption=function(caption) {
  1172. caption=caption.replace(/\<(\/?)([^\>\/]+)(\/?)\>/g, "<span class='techno'><span class='punc'>&lt;$1</span><span class='elName'>$2</span><span class='punc'>$3&gt;</span></span>");
  1173. caption=caption.replace(/\@"([^\"]+)"/g, "<span class='techno'><span class='punc'>\"</span><span class='atValue'>$1</span><span class='punc'>\"</span></span>");
  1174. caption=caption.replace(/\@([^ =]+)=""/g, "<span class='techno'><span class='atName'>$1</span><span class='punc'>=\"</span><span class='punc'>\"</span></span>");
  1175. caption=caption.replace(/\@([^ =]+)="([^\"]+)"/g, "<span class='techno'><span class='atName'>$1</span><span class='punc'>=\"</span><span class='atValue'>$2</span><span class='punc'>\"</span></span>");
  1176. caption=caption.replace(/\@([^ =]+)/g, "<span class='techno'><span class='atName'>$1</span></span>");
  1177. return caption;
  1178. };
  1179. Xonomy.deleteAttribute=function(htmlID, parameter) {
  1180. Xonomy.clickoff();
  1181. var obj=document.getElementById(htmlID);
  1182. var parentID=obj.parentNode.parentNode.parentNode.id;
  1183. obj.parentNode.removeChild(obj);
  1184. Xonomy.changed();
  1185. window.setTimeout(function(){ Xonomy.setFocus(parentID, "openingTagName"); }, 100);
  1186. };
  1187. Xonomy.deleteElement=function(htmlID, parameter) {
  1188. Xonomy.clickoff();
  1189. var obj=document.getElementById(htmlID);
  1190. var parentID=obj.parentNode.parentNode.id;
  1191. $(obj).fadeOut(function(){
  1192. var parentNode=obj.parentNode;
  1193. parentNode.removeChild(obj);
  1194. Xonomy.changed();
  1195. if($(parentNode).closest(".layby").length==0) {
  1196. window.setTimeout(function(){ Xonomy.setFocus(parentID, "openingTagName"); }, 100);
  1197. }
  1198. });
  1199. };
  1200. Xonomy.newAttribute=function(htmlID, parameter) {
  1201. Xonomy.clickoff();
  1202. var $element=$("#"+htmlID);
  1203. var html=Xonomy.renderAttribute({type: "attribute", name: parameter.name, value: parameter.value}, $element.data("name"));
  1204. $("#"+htmlID+" > .tag.opening > .attributes").append(html);
  1205. Xonomy.changed();
  1206. //if the attribute we have just added is shy, force rollout:
  1207. if($("#"+htmlID+" > .tag.opening > .attributes").children("[data-name='"+parameter.name+"'].shy").toArray().length>0) {
  1208. if( !$("#"+htmlID).children(".tag.opening").children(".rollouter").hasClass("rolledout") ) {
  1209. $("#"+htmlID).children(".tag.opening").children(".rollouter").addClass("rolledout");
  1210. $("#"+htmlID).children(".tag.opening").children(".attributes").addClass("rolledout").hide().slideDown("fast");
  1211. }
  1212. }
  1213. if(parameter.value=="") Xonomy.click($(html).prop("id"), "attributeValue"); else Xonomy.focus($(html).prop("id"), "attributeValue");
  1214. };
  1215. Xonomy.newElementChild=function(htmlID, parameter) {
  1216. Xonomy.clickoff();
  1217. var jsElement=Xonomy.harvestElement(document.getElementById(htmlID));
  1218. var html=Xonomy.renderElement(Xonomy.xml2js(parameter, jsElement));
  1219. var $html=$(html).hide();
  1220. $("#"+htmlID+" > .children").append($html);
  1221. Xonomy.plusminus(htmlID, true);
  1222. Xonomy.elementReorder($html.attr("id"));
  1223. Xonomy.changed();
  1224. $html.fadeIn();
  1225. window.setTimeout(function(){ Xonomy.setFocus($html.prop("id"), "openingTagName"); }, 100);
  1226. };
  1227. Xonomy.elementReorder=function(htmlID){
  1228. var that=document.getElementById(htmlID);
  1229. var elSpec=Xonomy.docSpec.elements[that.getAttribute("data-name")];
  1230. if(elSpec.mustBeBefore) { //is it after an element it cannot be after? then move it up until it's not!
  1231. var $this=$(that);
  1232. var jsElement=Xonomy.harvestElement(that);
  1233. var mustBeBefore=elSpec.mustBeBefore(jsElement);
  1234. var ok; do {
  1235. ok=true;
  1236. for(var ii=0; ii<mustBeBefore.length; ii++) {
  1237. if( $this.prevAll("*[data-name='"+mustBeBefore[ii]+"']").toArray().length>0 ) {
  1238. $this.prev().before($this);
  1239. ok=false;
  1240. }
  1241. }
  1242. } while(!ok)
  1243. }
  1244. if(elSpec.mustBeAfter) { //is it before an element it cannot be before? then move it down until it's not!
  1245. var $this=$(that);
  1246. var jsElement=Xonomy.harvestElement(that);
  1247. var mustBeAfter=elSpec.mustBeAfter(jsElement);
  1248. var ok; do {
  1249. ok=true;
  1250. for(var ii=0; ii<mustBeAfter.length; ii++) {
  1251. if( $this.nextAll("*[data-name='"+mustBeAfter[ii]+"']").toArray().length>0 ) {
  1252. $this.next().after($this);
  1253. ok=false;
  1254. }
  1255. }
  1256. } while(!ok)
  1257. }
  1258. };
  1259. Xonomy.newElementBefore=function(htmlID, parameter) {
  1260. Xonomy.clickoff();
  1261. var jsElement=Xonomy.harvestElement(document.getElementById(htmlID));
  1262. var html=Xonomy.renderElement(Xonomy.xml2js(parameter, jsElement.parent()));
  1263. var $html=$(html).hide();
  1264. $("#"+htmlID).before($html);
  1265. Xonomy.elementReorder($html.prop("id"));
  1266. Xonomy.changed();
  1267. $html.fadeIn();
  1268. window.setTimeout(function(){ Xonomy.setFocus($html.prop("id"), "openingTagName"); }, 100);
  1269. };
  1270. Xonomy.newElementAfter=function(htmlID, parameter) {
  1271. Xonomy.clickoff();
  1272. var jsElement=Xonomy.harvestElement(document.getElementById(htmlID));
  1273. var html=Xonomy.renderElement(Xonomy.xml2js(parameter, jsElement.parent()));
  1274. var $html=$(html).hide();
  1275. $("#"+htmlID).after($html);
  1276. Xonomy.elementReorder($html.prop("id"));
  1277. Xonomy.changed();
  1278. $html.fadeIn();
  1279. window.setTimeout(function(){ Xonomy.setFocus($html.prop("id"), "openingTagName"); }, 100);
  1280. };
  1281. Xonomy.replace=function(htmlID, jsNode) {
  1282. var what=Xonomy.currentFocus;
  1283. Xonomy.clickoff();
  1284. var html="";
  1285. if(jsNode.type=="element") html=Xonomy.renderElement(jsNode);
  1286. if(jsNode.type=="attribute") html=Xonomy.renderAttribute(jsNode);
  1287. if(jsNode.type=="text") html=Xonomy.renderText(jsNode);
  1288. $("#"+htmlID).replaceWith(html);
  1289. Xonomy.changed();
  1290. window.setTimeout(function(){ Xonomy.setFocus($(html).prop("id"), what); }, 100);
  1291. };
  1292. Xonomy.editRaw=function(htmlID, parameter) {
  1293. var div=document.getElementById(htmlID);
  1294. var jsElement=Xonomy.harvestElement(div);
  1295. if(parameter.fromJs) var txt=parameter.fromJs( jsElement );
  1296. else if(parameter.fromXml) var txt=parameter.fromXml( Xonomy.js2xml(jsElement) );
  1297. else var txt=Xonomy.js2xml(jsElement);
  1298. document.body.appendChild(Xonomy.makeBubble(Xonomy.askLongString(txt))); //create bubble
  1299. Xonomy.showBubble($(div)); //anchor bubble to element
  1300. Xonomy.answer=function(val) {
  1301. var jsNewElement;
  1302. if(parameter.toJs) jsNewElement=parameter.toJs(val, jsElement);
  1303. else if(parameter.toXml) jsNewElement=Xonomy.xml2js(parameter.toXml(val, jsElement), jsElement.parent());
  1304. else jsNewElement=Xonomy.xml2js(val, jsElement.parent());
  1305. var obj=document.getElementById(htmlID);
  1306. var html=Xonomy.renderElement(jsNewElement);
  1307. $(obj).replaceWith(html);
  1308. Xonomy.clickoff();
  1309. Xonomy.changed();
  1310. window.setTimeout(function(){ Xonomy.setFocus($(html).prop("id"), "openingTagName"); }, 100);
  1311. };
  1312. };
  1313. Xonomy.duplicateElement=function(htmlID) {
  1314. Xonomy.clickoff();
  1315. var html=document.getElementById(htmlID).outerHTML.replace(/ id=['"]/g, function(x){return x+"d_"});
  1316. var $html=$(html).hide();
  1317. $("#"+htmlID).after($html);
  1318. Xonomy.changed();
  1319. $html.fadeIn();
  1320. window.setTimeout(function(){ Xonomy.setFocus($html.prop("id"), "openingTagName"); }, 100);
  1321. };
  1322. Xonomy.moveElementUp=function(htmlID){
  1323. Xonomy.clickoff();
  1324. var $me=$("#"+htmlID);
  1325. if($me.closest(".layby > .content").length==0) {
  1326. Xonomy.insertDropTargets(htmlID);
  1327. var $droppers=$(".xonomy .elementDropper").add($me);
  1328. var i=$droppers.index($me[0])-1;
  1329. if(i>=0) {
  1330. $($droppers[i]).replaceWith($me);
  1331. Xonomy.changed();
  1332. $me.hide().fadeIn();
  1333. }
  1334. Xonomy.dragend();
  1335. }
  1336. window.setTimeout(function(){ Xonomy.setFocus(htmlID, "openingTagName"); }, 100);
  1337. };
  1338. Xonomy.moveElementDown=function(htmlID){
  1339. Xonomy.clickoff();
  1340. var $me=$("#"+htmlID);
  1341. if($me.closest(".layby > .content").length==0) {
  1342. Xonomy.insertDropTargets(htmlID);
  1343. var $droppers=$(".xonomy .elementDropper").add($me);
  1344. var i=$droppers.index($me[0])+1;
  1345. if(i<$droppers.length) {
  1346. $($droppers[i]).replaceWith($me);
  1347. Xonomy.changed();
  1348. $me.hide().fadeIn();
  1349. }
  1350. Xonomy.dragend();
  1351. }
  1352. window.setTimeout(function(){ Xonomy.setFocus(htmlID, "openingTagName"); }, 100);
  1353. };
  1354. Xonomy.canMoveElementUp=function(htmlID){
  1355. var ret=false;
  1356. var $me=$("#"+htmlID);
  1357. if($me.closest(".layby > .content").length==0) {
  1358. Xonomy.insertDropTargets(htmlID);
  1359. var $droppers=$(".xonomy .elementDropper").add($me);
  1360. var i=$droppers.index($me[0])-1;
  1361. if(i>=0) ret=true;
  1362. Xonomy.dragend();
  1363. }
  1364. return ret;
  1365. };
  1366. Xonomy.canMoveElementDown=function(htmlID){
  1367. var ret=false;
  1368. var $me=$("#"+htmlID);
  1369. if($me.closest(".layby > .content").length==0) {
  1370. Xonomy.insertDropTargets(htmlID);
  1371. var $droppers=$(".xonomy .elementDropper").add($me);
  1372. var i=$droppers.index($me[0])+1;
  1373. if(i<$droppers.length) ret=true;
  1374. Xonomy.dragend();
  1375. }
  1376. return ret;
  1377. };
  1378. Xonomy.mergeWithPrevious=function(htmlID, parameter){
  1379. var domDead=document.getElementById(htmlID);
  1380. var elDead=Xonomy.harvestElement(domDead);
  1381. var elLive=elDead.getPrecedingSibling();
  1382. Xonomy.mergeElements(elDead, elLive);
  1383. };
  1384. Xonomy.mergeWithNext=function(htmlID, parameter){
  1385. var domDead=document.getElementById(htmlID);
  1386. var elDead=Xonomy.harvestElement(domDead);
  1387. var elLive=elDead.getFollowingSibling();
  1388. Xonomy.mergeElements(elDead, elLive);
  1389. };
  1390. Xonomy.mergeElements=function(elDead, elLive){
  1391. Xonomy.clickoff();
  1392. var domDead=document.getElementById(elDead.htmlID);
  1393. if(elLive && elLive.type=="element") {
  1394. for(var i=0; i<elDead.attributes.length; i++){ //merge attributes
  1395. var atDead=elDead.attributes[i];
  1396. if(!elLive.hasAttribute(atDead.name) || elLive.getAttributeValue(atDead.name)==""){
  1397. elLive.setAttribute(atDead.name, atDead.value);
  1398. if(elLive.hasAttribute(atDead.name)) $("#"+elLive.getAttribute(atDead.name).htmlID).remove();
  1399. $("#"+elLive.htmlID).find(".attributes").first().append($("#"+elDead.attributes[i].htmlID));
  1400. }
  1401. }
  1402. var specDead=Xonomy.docSpec.elements[elDead.name];
  1403. var specLive=Xonomy.docSpec.elements[elLive.name];
  1404. if(specDead.hasText(elDead) || specLive.hasText(elLive)){ //if either element is meant to have text, concatenate their children
  1405. if(elLive.getText()!="" && elDead.getText()!="") {
  1406. elLive.addText(" ");
  1407. $("#"+elLive.htmlID).find(".children").first().append(Xonomy.renderText({type: "text", value: " "}));
  1408. }
  1409. for(var i=0; i<elDead.children.length; i++) {
  1410. elLive.children.push(elDead.children[i]);
  1411. $("#"+elLive.htmlID).find(".children").first().append($("#"+elDead.children[i].htmlID));
  1412. }
  1413. } else { //if no text, merge their children one by one
  1414. for(var i=0; i<elDead.children.length; i++){
  1415. var xmlDeadChild=Xonomy.js2xml(elDead.children[i]);
  1416. var has=false;
  1417. for(y=0; y<elLive.children.length; y++){
  1418. var xmlLiveChild=Xonomy.js2xml(elLive.children[y]);
  1419. if(xmlDeadChild==xmlLiveChild){ has=true; break; }
  1420. }
  1421. if(!has) {
  1422. elLive.children.push(elDead.children[i]);
  1423. $("#"+elLive.htmlID).find(".children").first().append($("#"+elDead.children[i].htmlID));
  1424. Xonomy.elementReorder(elDead.children[i].htmlID);
  1425. }
  1426. }
  1427. }
  1428. domDead.parentNode.removeChild(domDead);
  1429. Xonomy.changed();
  1430. window.setTimeout(function(){ Xonomy.setFocus(elLive.htmlID, "openingTagName"); }, 100);
  1431. } else {
  1432. window.setTimeout(function(){ Xonomy.setFocus(htmlID, "openingTagName"); }, 100);
  1433. }
  1434. };
  1435. Xonomy.insertDropTargets=function(htmlID){
  1436. var $element=$("#"+htmlID);
  1437. $element.addClass("dragging");
  1438. var elementName=$element.attr("data-name");
  1439. var elSpec=Xonomy.docSpec.elements[elementName];
  1440. $(".xonomy .children:visible").append("<div class='elementDropper' ondragover='Xonomy.dragOver(event)' ondragleave='Xonomy.dragOut(event)' ondrop='Xonomy.drop(event)'><div class='inside'></div></div>")
  1441. $(".xonomy .children:visible > .element").before("<div class='elementDropper' ondragover='Xonomy.dragOver(event)' ondragleave='Xonomy.dragOut(event)' ondrop='Xonomy.drop(event)'><div class='inside'></div></div>")
  1442. $(".xonomy .children:visible > .text").before("<div class='elementDropper' ondragover='Xonomy.dragOver(event)' ondragleave='Xonomy.dragOut(event)' ondrop='Xonomy.drop(event)'><div class='inside'></div></div>")
  1443. $(".xonomy .dragging .children:visible > .elementDropper").remove(); //remove drop targets fom inside the element being dragged
  1444. $(".xonomy .dragging").prev(".elementDropper").remove(); //remove drop targets from immediately before the element being dragged
  1445. $(".xonomy .dragging").next(".elementDropper").remove(); //remove drop targets from immediately after the element being dragged
  1446. $(".xonomy .children:visible > .element.readonly .elementDropper").remove(); //remove drop targets from inside read-only elements
  1447. var harvestCache={};
  1448. var harvestElement=function(div){
  1449. var htmlID=$(div).prop("id");
  1450. if(!harvestCache[htmlID]) harvestCache[htmlID]=Xonomy.harvestElement(div);
  1451. return harvestCache[htmlID];
  1452. };
  1453. if(elSpec.localDropOnly(harvestElement($element.toArray()[0]))) {
  1454. if(elSpec.canDropTo) { //remove the drop target from elements that are not the dragged element's parent
  1455. var droppers=$(".xonomy .elementDropper").toArray();
  1456. for(var i=0; i<droppers.length; i++) {
  1457. var dropper=droppers[i];
  1458. if(dropper.parentNode!=ev.target.parentNode.parentNode.parentNode) {
  1459. dropper.parentNode.removeChild(dropper);
  1460. }
  1461. }
  1462. }
  1463. }
  1464. if(elSpec.canDropTo) { //remove the drop target from elements it cannot be dropped into
  1465. var droppers=$(".xonomy .elementDropper").toArray();
  1466. for(var i=0; i<droppers.length; i++) {
  1467. var dropper=droppers[i];
  1468. var parentElementName=$(dropper.parentNode.parentNode).toArray()[0].getAttribute("data-name");
  1469. if($.inArray(parentElementName, elSpec.canDropTo)<0) {
  1470. dropper.parentNode.removeChild(dropper);
  1471. }
  1472. }
  1473. }
  1474. if(elSpec.mustBeBefore) { //remove the drop target from after elements it cannot be after
  1475. var jsElement=harvestElement($element.toArray()[0]);
  1476. var droppers=$(".xonomy .elementDropper").toArray();
  1477. for(var i=0; i<droppers.length; i++) {
  1478. var dropper=droppers[i];
  1479. jsElement.internalParent=harvestElement(dropper.parentNode.parentNode); //pretend the element's parent is the dropper's parent
  1480. var mustBeBefore=elSpec.mustBeBefore(jsElement);
  1481. for(var ii=0; ii<mustBeBefore.length; ii++) {
  1482. if( $(dropper).prevAll("*[data-name='"+mustBeBefore[ii]+"']").toArray().length>0 ) {
  1483. dropper.parentNode.removeChild(dropper);
  1484. }
  1485. }
  1486. }
  1487. }
  1488. if(elSpec.mustBeAfter) { //remove the drop target from before elements it cannot be before
  1489. var jsElement=harvestElement($element.toArray()[0]);
  1490. var droppers=$(".xonomy .elementDropper").toArray();
  1491. for(var i=0; i<droppers.length; i++) {
  1492. var dropper=droppers[i];
  1493. jsElement.internalParent=harvestElement(dropper.parentNode.parentNode); //pretend the element's parent is the dropper's parent
  1494. var mustBeAfter=elSpec.mustBeAfter(jsElement);
  1495. for(var ii=0; ii<mustBeAfter.length; ii++) {
  1496. if( $(dropper).nextAll("*[data-name='"+mustBeAfter[ii]+"']").toArray().length>0 ) {
  1497. dropper.parentNode.removeChild(dropper);
  1498. }
  1499. }
  1500. }
  1501. }
  1502. };
  1503. Xonomy.draggingID=null; //what are we dragging?
  1504. Xonomy.drag=function(ev) { //called when dragging starts
  1505. // Wrapping all the code into a timeout handler is a workaround for a Chrome browser bug
  1506. // (if the DOM is manipulated in the 'dragStart' event then 'dragEnd' event is sometimes fired immediately)
  1507. //
  1508. // for more details @see:
  1509. // http://stackoverflow.com/questions/19639969/html5-dragend-event-firing-immediately
  1510. ev.dataTransfer.effectAllowed="move"; //only allow moving (and not eg. copying]
  1511. var htmlID=ev.target.parentNode.parentNode.id;
  1512. ev.dataTransfer.setData("text", htmlID);
  1513. setTimeout(function() {
  1514. Xonomy.clickoff();
  1515. Xonomy.insertDropTargets(htmlID);
  1516. Xonomy.draggingID=htmlID;
  1517. Xonomy.refresh();
  1518. }, 10);
  1519. };
  1520. Xonomy.dragOver=function(ev) {
  1521. ev.preventDefault();
  1522. ev.dataTransfer.dropEffect="move"; //only allow moving (and not eg. copying]
  1523. if($(ev.currentTarget).hasClass("layby")){
  1524. $(ev.currentTarget).addClass("activeDropper");
  1525. } else {
  1526. $(ev.target.parentNode).addClass("activeDropper");
  1527. }
  1528. };
  1529. Xonomy.dragOut=function(ev) {
  1530. ev.preventDefault();
  1531. if($(ev.currentTarget).hasClass("layby")){
  1532. $(ev.currentTarget).removeClass("activeDropper");
  1533. } else {
  1534. $(".xonomy .activeDropper").removeClass("activeDropper");
  1535. }
  1536. };
  1537. Xonomy.drop=function(ev) {
  1538. ev.preventDefault();
  1539. var node=document.getElementById(Xonomy.draggingID); //the thing we are moving
  1540. if($(ev.currentTarget).hasClass("layby")) {
  1541. $(node).hide();
  1542. $(".xonomy .layby > .content").append(node);
  1543. $(node).fadeIn(function(){ Xonomy.changed(); });
  1544. } else {
  1545. $(node).hide();
  1546. $(ev.target.parentNode).replaceWith(node);
  1547. $(node).fadeIn(function(){ Xonomy.changed(); });
  1548. }
  1549. Xonomy.openCloseLayby();
  1550. Xonomy.recomputeLayby();
  1551. };
  1552. Xonomy.dragend=function(ev) {
  1553. $(".xonomy .attributeDropper").remove();
  1554. $(".xonomy .elementDropper").remove();
  1555. $(".xonomy .dragging").removeClass("dragging");
  1556. Xonomy.refresh();
  1557. $(".xonomy .layby").removeClass("activeDropper");
  1558. };
  1559. Xonomy.openCloseLayby=function(){ //open the layby if it's full, close it if it's empty
  1560. if($(".xonomy .layby > .content > *").length>0){
  1561. $(".xonomy .layby").removeClass("closed").addClass("open");
  1562. } else {
  1563. $(".xonomy .layby").removeClass("open").addClass("closed");
  1564. }
  1565. };
  1566. Xonomy.openLayby=function(){
  1567. $(".xonomy .layby").removeClass("closed").addClass("open");
  1568. };
  1569. Xonomy.closeLayby=function(){
  1570. window.setTimeout(function(){
  1571. $(".xonomy .layby").removeClass("open").addClass("closed");
  1572. }, 10);
  1573. };
  1574. Xonomy.emptyLayby=function(){
  1575. $(".xonomy .layby .content").html("");
  1576. $(".xonomy .layby").removeClass("nonempty").addClass("empty");
  1577. };
  1578. Xonomy.recomputeLayby=function(){
  1579. if($(".xonomy .layby > .content > *").length>0){
  1580. $(".xonomy .layby").removeClass("empty").addClass("nonempty");
  1581. } else {
  1582. $(".xonomy .layby").removeClass("nonempty").addClass("empty");
  1583. }
  1584. }
  1585. Xonomy.changed=function(jsElement) { //called when the document changes
  1586. Xonomy.harvestCache={};
  1587. Xonomy.refresh();
  1588. Xonomy.validate();
  1589. Xonomy.docSpec.onchange(jsElement); //report that the document has changed
  1590. };
  1591. Xonomy.validate=function() {
  1592. var js=Xonomy.harvestElement($(".xonomy .element").toArray()[0], null);
  1593. $(".xonomy .invalid").removeClass("invalid");
  1594. Xonomy.warnings=[];
  1595. Xonomy.docSpec.validate(js); //validate the document
  1596. for(var iWarning=0; iWarning<Xonomy.warnings.length; iWarning++) {
  1597. var warning=Xonomy.warnings[iWarning];
  1598. $("#"+warning.htmlID).addClass("invalid");
  1599. }
  1600. };
  1601. Xonomy.warnings=[]; //array of {htmlID: "", text: ""}
  1602. Xonomy.textByLang=function(str) {
  1603. //str = eg. "en: Delete | de: Löschen | fr: Supprimer"
  1604. if(!str) str="";
  1605. var ret=str;
  1606. var segs=str.split("|");
  1607. for(var i=0; i<segs.length; i++) {
  1608. var seg=$.trim(segs[i]);
  1609. if(seg.indexOf(Xonomy.lang+":")==0) {
  1610. ret=seg.substring((Xonomy.lang+":").length, ret.length);
  1611. }
  1612. }
  1613. ret=$.trim(ret);
  1614. return ret;
  1615. };
  1616. Xonomy.currentHtmlId=null;
  1617. Xonomy.currentFocus=null;
  1618. Xonomy.keyNav=false;
  1619. Xonomy.startKeyNav=function(keyboardEventCatcher, scrollableContainer){
  1620. Xonomy.keyNav=true;
  1621. var $keyboardEventCatcher=$(keyboardEventCatcher); if(!keyboardEventCatcher) $keyboardEventCatcher=$(".xonomy");
  1622. $scrollableContainer=$(scrollableContainer); if(!scrollableContainer) $scrollableContainer=$keyboardEventCatcher;
  1623. $keyboardEventCatcher.attr("tabindex", "0");
  1624. $keyboardEventCatcher.on("keydown", Xonomy.key);
  1625. $(document).on("keydown", function(e) { if([32, 37, 38, 39, 40].indexOf(e.keyCode)>-1 && $("input:focus, select:focus, textarea:focus").length==0) e.preventDefault(); }); //prevent default browser scrolling on arrow keys
  1626. Xonomy.keyboardEventCatcher=$keyboardEventCatcher;
  1627. Xonomy.scrollableContainer=$scrollableContainer;
  1628. };
  1629. Xonomy.setFocus=function(htmlID, what){
  1630. if(Xonomy.keyNav) {
  1631. $(".xonomy .current").removeClass("current");
  1632. $(".xonomy .focused").removeClass("focused");
  1633. if(what=="attributeValue") $("#"+htmlID+" > .valueContainer").addClass("current").addClass("focused");
  1634. else $("#"+htmlID).addClass("current").addClass("focused");
  1635. Xonomy.currentHtmlId=htmlID;
  1636. Xonomy.currentFocus=what;
  1637. if(Xonomy.currentFocus=="openingTagName") $("#"+htmlID+" > .tag.opening").first().addClass("focused");
  1638. if(Xonomy.currentFocus=="closingTagName") $("#"+htmlID+" > .tag.closing").last().addClass("focused");
  1639. if(Xonomy.currentFocus=="childrenCollapsed") $("#"+htmlID+" > .childrenCollapsed").last().addClass("focused");
  1640. if(Xonomy.currentFocus=="rollouter") $("#"+htmlID+" > .tag.opening > .rollouter").last().addClass("focused");
  1641. }
  1642. };
  1643. Xonomy.key=function(event){
  1644. if(!Xonomy.notKeyUp) {
  1645. if(!event.shiftKey && !$("#xonomyBubble").length>0 ) {
  1646. if(event.which==27) { //escape key
  1647. event.preventDefault();
  1648. event.stopImmediatePropagation();
  1649. Xonomy.destroyBubble();
  1650. } else if(event.which==13){ //enter key
  1651. event.preventDefault();
  1652. event.stopImmediatePropagation();
  1653. if(Xonomy.currentFocus=="childrenCollapsed") Xonomy.plusminus(Xonomy.currentHtmlId, true);
  1654. if(Xonomy.currentFocus=="char") {
  1655. Xonomy.charClick($("#"+Xonomy.currentHtmlId)[0]);
  1656. }
  1657. else {
  1658. Xonomy.click(Xonomy.currentHtmlId, Xonomy.currentFocus);
  1659. Xonomy.clickoff();
  1660. }
  1661. } else if((event.ctrlKey || event.metaKey) && event.which==40) { //down key with Ctrl or Cmd (Mac OS)
  1662. event.preventDefault();
  1663. event.stopImmediatePropagation();
  1664. Xonomy.scrollableContainer.scrollTop( Xonomy.scrollableContainer.scrollTop()+60 );
  1665. } else if((event.ctrlKey || event.metaKey) && event.which==38) { //up key with Ctrl or Cmd (Mac OS)
  1666. event.preventDefault();
  1667. event.stopImmediatePropagation();
  1668. Xonomy.scrollableContainer.scrollTop( Xonomy.scrollableContainer.scrollTop()-60 );
  1669. } else if((event.ctrlKey || event.metaKey) && [37, 39].indexOf(event.which)>-1) { //arrow keys with Ctrl or Cmd (Mac OS)
  1670. event.preventDefault();
  1671. event.stopImmediatePropagation();
  1672. var $el=$("#"+Xonomy.currentHtmlId);
  1673. if($el.hasClass("element") && !$el.hasClass("uncollapsible")){
  1674. if(event.which==39 && $el.hasClass("collapsed")) { //expand it!
  1675. Xonomy.plusminus(Xonomy.currentHtmlId);
  1676. }
  1677. if(event.which==37 && !$el.hasClass("collapsed")) { //collapse it!
  1678. Xonomy.plusminus(Xonomy.currentHtmlId);
  1679. }
  1680. }
  1681. } else if([37, 38, 39, 40].indexOf(event.which)>-1 && !event.altKey) { //arrow keys
  1682. event.preventDefault();
  1683. event.stopImmediatePropagation();
  1684. if(!Xonomy.currentHtmlId) { //nothing is current yet
  1685. Xonomy.setFocus($(".xonomy .element").first().prop("id"), "openingTagName");
  1686. } else if($(".xonomy .focused").length==0) { //something is current but nothing is focused yet
  1687. Xonomy.setFocus(Xonomy.currentHtmlId, Xonomy.currentFocus);
  1688. } else { //something is current, do arrow action
  1689. if(event.which==40) Xonomy.goDown(); //down key
  1690. if(event.which==38) Xonomy.goUp(); //up key
  1691. if(event.which==39) Xonomy.goRight(); //right key
  1692. if(event.which==37) Xonomy.goLeft(); //left key
  1693. }
  1694. }
  1695. } else if(!$("#xonomyBubble").length>0) {
  1696. Xonomy.keyboardMenu(event);
  1697. }
  1698. }
  1699. Xonomy.notKeyUp=false;
  1700. };
  1701. Xonomy.keyboardMenu=function(event){
  1702. var $obj=$("#"+Xonomy.currentHtmlId);
  1703. var jsMe=null;
  1704. var menu=null;
  1705. if($obj.hasClass("element")){
  1706. jsMe=Xonomy.harvestElement($obj[0]);
  1707. var elName=$obj.attr("data-name");
  1708. menu=Xonomy.docSpec.elements[elName].menu;
  1709. } else if($obj.hasClass("attribute")) {
  1710. jsMe=Xonomy.harvestAttribute($obj[0]);
  1711. var atName=$obj.attr("data-name");
  1712. var elName=$obj.closest(".element").attr("data-name");
  1713. menu=Xonomy.docSpec.elements[elName].attributes[atName].menu;
  1714. }
  1715. if(menu){
  1716. var findMenuItem=function(menu){
  1717. var ret=null;
  1718. for(var i=0; i<menu.length; i++){
  1719. if(menu[i].menu) ret=findMenuItem(menu[i].menu);
  1720. else if(menu[i].keyTrigger && !menu[i].hideIf(jsMe) && menu[i].keyTrigger(event)) ret=menu[i];
  1721. if(ret) break;
  1722. }
  1723. return ret;
  1724. };
  1725. var menuItem=findMenuItem(menu);
  1726. if(menuItem) {
  1727. Xonomy.callMenuFunction(menuItem, Xonomy.currentHtmlId);
  1728. Xonomy.clickoff();
  1729. return true;
  1730. }
  1731. }
  1732. return false;
  1733. },
  1734. Xonomy.goDown=function(){
  1735. if(Xonomy.currentFocus!="openingTagName" && Xonomy.currentFocus!="closingTagName" && Xonomy.currentFocus!="text" && Xonomy.currentFocus!="char") {
  1736. Xonomy.goRight();
  1737. } else {
  1738. var $el=$("#"+Xonomy.currentHtmlId);
  1739. var $me=$el;
  1740. if(Xonomy.currentFocus=="openingTagName") var $me=$el.find(".tag.opening").first();
  1741. if(Xonomy.currentFocus=="closingTagName") var $me=$el.find(".tag.closing").last();
  1742. var $candidates=$(".xonomy .focusable:visible").not(".attributeName").not(".attributeValue").not(".childrenCollapsed").not(".rollouter");
  1743. $candidates=$candidates.not(".char").add($el);
  1744. if(Xonomy.currentFocus=="openingTagName" && $el.hasClass("oneliner")) $candidates=$candidates.not("#"+Xonomy.currentHtmlId+" .tag.closing").not("#"+Xonomy.currentHtmlId+" .children *");
  1745. if(Xonomy.currentFocus=="openingTagName" && $el.hasClass("oneliner")) $candidates=$candidates.not("#"+Xonomy.currentHtmlId+" .textnode");
  1746. if($el.hasClass("collapsed")) $candidates=$candidates.not("#"+Xonomy.currentHtmlId+" .tag.closing");
  1747. if($el.hasClass("textnode") && $(".xonomy").hasClass("nerd")) var $candidates=$el.closest(".element").find(".tag.closing").last();
  1748. if($el.hasClass("textnode") && $(".xonomy").hasClass("laic")) var $candidates=$el.closest(".element").next().find(".focusable:visible").first();
  1749. var $next=$candidates.eq( $candidates.index($me[0])+1 );
  1750. if($next.hasClass("opening")) Xonomy.setFocus($next.closest(".element").prop("id"), "openingTagName");
  1751. if($next.hasClass("closing")) Xonomy.setFocus($next.closest(".element").prop("id"), "closingTagName");
  1752. if($next.hasClass("textnode")) Xonomy.setFocus($next.prop("id"), "text");
  1753. }
  1754. };
  1755. Xonomy.goUp=function(){
  1756. if(Xonomy.currentFocus!="openingTagName" && Xonomy.currentFocus!="closingTagName" && Xonomy.currentFocus!="char" && Xonomy.currentFocus!="text") {
  1757. Xonomy.goLeft();
  1758. } else {
  1759. var $el=$("#"+Xonomy.currentHtmlId);
  1760. var $me=$el;
  1761. if(Xonomy.currentFocus=="openingTagName") var $me=$el.find(".tag.opening").first();
  1762. if(Xonomy.currentFocus=="closingTagName") var $me=$el.find(".tag.closing").last();
  1763. var $candidates=$(".xonomy .focusable:visible").not(".attributeName").not(".attributeValue").not(".childrenCollapsed").not(".rollouter");
  1764. $candidates=$candidates.not(".element .oneliner .tag.closing");
  1765. $candidates=$candidates.not(".element .oneliner .textnode");
  1766. $candidates=$candidates.not(".element .collapsed .tag.closing");
  1767. $candidates=$candidates.not(".char");
  1768. if($el.hasClass("char")) var $candidates=$el.closest(".textnode").first().add($el);
  1769. if($el.hasClass("textnode")) var $candidates=$el.closest(".element").find(".tag.opening").first().add($el);
  1770. if($me.hasClass("closing") && $el.hasClass("hasText")) $candidates=$candidates.not("#"+Xonomy.currentHtmlId+" .children *:not(:first-child)");
  1771. if($me.hasClass("opening") && $el.closest(".element").prev().hasClass("hasText")) {
  1772. var siblingID=$el.closest(".element").prev().prop("id");
  1773. $candidates=$candidates.not("#"+siblingID+" .children *:not(:first-child)");
  1774. }
  1775. if($candidates.index($me[0])>0) {
  1776. var $next=$candidates.eq( $candidates.index($me[0])-1 );
  1777. if($next.hasClass("opening")) Xonomy.setFocus($next.closest(".element").prop("id"), "openingTagName");
  1778. if($next.hasClass("closing")) Xonomy.setFocus($next.closest(".element").prop("id"), "closingTagName");
  1779. if($next.hasClass("textnode")) Xonomy.setFocus($next.prop("id"), "text");
  1780. }
  1781. }
  1782. };
  1783. Xonomy.goRight=function(){
  1784. var $el=$("#"+Xonomy.currentHtmlId);
  1785. var $me=$el;
  1786. if(Xonomy.currentFocus=="openingTagName") var $me=$el.find(".tag.opening").first();
  1787. if(Xonomy.currentFocus=="closingTagName") var $me=$el.find(".tag.closing").last();
  1788. if(Xonomy.currentFocus=="attributeName") var $me=$el.find(".attributeName").first();
  1789. if(Xonomy.currentFocus=="attributeValue") var $me=$el.find(".attributeValue").first();
  1790. if(Xonomy.currentFocus=="childrenCollapsed") var $me=$el.find(".childrenCollapsed").first();
  1791. if(Xonomy.currentFocus=="rollouter") var $me=$el.find(".rollouter").first();
  1792. var $candidates=$(".xonomy .focusable:visible");
  1793. $candidates=$candidates.not(".char").add(".hasInlineMenu > .children > .textnode .char:visible");
  1794. var $next=$candidates.eq( $candidates.index($me[0])+1 );
  1795. if($next.hasClass("attributeName")) Xonomy.setFocus($next.closest(".attribute").prop("id"), "attributeName");
  1796. if($next.hasClass("attributeValue")) Xonomy.setFocus($next.closest(".attribute").prop("id"), "attributeValue");
  1797. if($next.hasClass("opening")) Xonomy.setFocus($next.closest(".element").prop("id"), "openingTagName");
  1798. if($next.hasClass("closing")) Xonomy.setFocus($next.closest(".element").prop("id"), "closingTagName");
  1799. if($next.hasClass("textnode")) Xonomy.setFocus($next.prop("id"), "text");
  1800. if($next.hasClass("childrenCollapsed")) Xonomy.setFocus($next.closest(".element").prop("id"), "childrenCollapsed");
  1801. if($next.hasClass("rollouter")) Xonomy.setFocus($next.closest(".element").prop("id"), "rollouter");
  1802. if($next.hasClass("char")) Xonomy.setFocus($next.prop("id"), "char");
  1803. };
  1804. Xonomy.goLeft=function(){
  1805. var $el=$("#"+Xonomy.currentHtmlId);
  1806. var $me=$el;
  1807. if(Xonomy.currentFocus=="openingTagName") var $me=$el.find(".tag.opening").first();
  1808. if(Xonomy.currentFocus=="closingTagName") var $me=$el.find(".tag.closing").last();
  1809. if(Xonomy.currentFocus=="attributeName") var $me=$el.find(".attributeName").first();
  1810. if(Xonomy.currentFocus=="attributeValue") var $me=$el.find(".attributeValue").first();
  1811. if(Xonomy.currentFocus=="childrenCollapsed") var $me=$el.find(".childrenCollapsed").first();
  1812. if(Xonomy.currentFocus=="rollouter") var $me=$el.find(".rollouter").first();
  1813. var $candidates=$(".xonomy .focusable:visible");
  1814. $candidates=$candidates.not(".char").add(".hasInlineMenu > .children > .textnode .char:visible");
  1815. var $next=$candidates.eq( $candidates.index($me[0])-1 );
  1816. if($next.hasClass("attributeName")) Xonomy.setFocus($next.closest(".attribute").prop("id"), "attributeName");
  1817. if($next.hasClass("attributeValue")) Xonomy.setFocus($next.closest(".attribute").prop("id"), "attributeValue");
  1818. if($next.hasClass("opening")) Xonomy.setFocus($next.closest(".element").prop("id"), "openingTagName");
  1819. if($next.hasClass("closing")) Xonomy.setFocus($next.closest(".element").prop("id"), "closingTagName");
  1820. if($next.hasClass("textnode")) Xonomy.setFocus($next.prop("id"), "text");
  1821. if($next.hasClass("childrenCollapsed")) Xonomy.setFocus($next.closest(".element").prop("id"), "childrenCollapsed");
  1822. if($next.hasClass("rollouter")) Xonomy.setFocus($next.closest(".element").prop("id"), "rollouter");
  1823. if($next.hasClass("char")) Xonomy.setFocus($next.prop("id"), "char");
  1824. };