Last active
August 29, 2015 14:26
-
-
Save samdobson/eea73120e5acfcbe28aa to your computer and use it in GitHub Desktop.
Xonomy 1.2 modified to allow specification of asker for text nodes and readonly text nodes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
var Xonomy={}; | |
Xonomy.xmlEscape=function(str) { | |
return String(str) | |
.replace(/&/g, '&') | |
.replace(/"/g, '"') | |
.replace(/'/g, ''') | |
.replace(/</g, '<') | |
.replace(/>/g, '>'); | |
}; | |
Xonomy.xmlUnscape=function(value){ | |
return String(value) | |
.replace(/"/g, '"') | |
.replace(/'/g, "'") | |
.replace(/</g, '<') | |
.replace(/>/g, '>') | |
.replace(/&/g, '&'); | |
}; | |
Xonomy.isNamespaceDeclaration=function(attributeName) { | |
//Tells you whether an attribute name is a namespace declaration. | |
var ret=false; | |
if(attributeName=="xmlns") ret=true; | |
if(attributeName.length>=6 && attributeName.substring(0, 6)=="xmlns:") ret=true; | |
return ret; | |
}; | |
Xonomy.namespaces={}; //eg. "xmlns:mbm": "http://lexonista.com" | |
Xonomy.xml2js=function(xml) { | |
if(typeof(xml)=="string") xml=$.parseXML(xml); | |
if(xml.documentElement) xml=xml.documentElement; | |
var js={}; | |
js.type="element"; | |
js.name=xml.nodeName; | |
js.htmlID=""; | |
js.attributes=[]; | |
for(var i=0; i<xml.attributes.length; i++) { | |
var attr=xml.attributes[i]; | |
if(!Xonomy.isNamespaceDeclaration(attr.nodeName)) { | |
js["attributes"].push({type: "attribute", name: attr.nodeName, value: attr.value, htmlID: ""}); | |
} else { | |
Xonomy.namespaces[attr.nodeName]=attr.value; | |
} | |
} | |
js.children=[]; | |
for(var i=0; i<xml.childNodes.length; i++) { | |
var child=xml.childNodes[i]; | |
if(child.nodeType==1) { //element node | |
js["children"].push(Xonomy.xml2js(child)); | |
} | |
if(child.nodeType==3) { //text node | |
js["children"].push({type: "text", value: child.nodeValue, htmlID: ""}); | |
} | |
} | |
js=Xonomy.enrichElement(js); | |
return js; | |
}; | |
Xonomy.js2xml=function(js) { | |
var xml="<"+js.name; | |
for(var i=0; i<js.attributes.length; i++) { | |
var att=js.attributes[i]; | |
xml+=" "+att.name+"='"+Xonomy.xmlEscape(att.value)+"'"; | |
} | |
if(js.children.length>0) { | |
xml+=">"; | |
for(var i=0; i<js.children.length; i++) { | |
var child=js.children[i]; | |
if(child.type=="text") xml+=Xonomy.xmlEscape(child.value); //text node | |
else if(child.type=="element") xml+=Xonomy.js2xml(child); //element node | |
} | |
xml+="</"+js.name+">"; | |
} else { | |
xml+="/>"; | |
} | |
return xml; | |
}; | |
Xonomy.enrichElement=function(jsElement) { | |
jsElement.hasAttribute=function(name) { | |
var ret=false; | |
for(var i=0; i<this.attributes.length; i++) { | |
if(this.attributes[i].name==name) ret=true; | |
} | |
return ret; | |
}; | |
jsElement.getAttributeValue=function(name, ifNull) { | |
var ret=ifNull; | |
for(var i=0; i<this.attributes.length; i++) { | |
if(this.attributes[i].name==name) ret=this.attributes[i].value; | |
} | |
return ret; | |
}; | |
jsElement.hasChildElement=function(name) { | |
var ret=false; | |
for(var i=0; i<this.children.length; i++) { | |
if(this.children[i].name==name) ret=true; | |
} | |
return ret; | |
}; | |
return jsElement; | |
}; | |
Xonomy.verifyDocSpec=function() { //make sure the docSpec object has everything it needs | |
if(!Xonomy.docSpec || typeof(Xonomy.docSpec)!="object") Xonomy.docSpec={}; | |
if(!Xonomy.docSpec.elements || typeof(Xonomy.docSpec.elements)!="object") Xonomy.docSpec.elements={}; | |
if(!Xonomy.docSpec.onchange || typeof(Xonomy.docSpec.onchange)!="function") Xonomy.docSpec.onchange=function(){}; | |
if(!Xonomy.docSpec.validate || typeof(Xonomy.docSpec.validate)!="function") Xonomy.docSpec.validate=function(){}; | |
}; | |
Xonomy.verifyDocSpecElement=function(name) { //make sure the DocSpec object has such an element, that the element has everything it needs | |
if(!Xonomy.docSpec.elements[name] || typeof(Xonomy.docSpec.elements[name])!="object") Xonomy.docSpec.elements[name]={}; | |
var spec=Xonomy.docSpec.elements[name]; | |
if(!spec.attributes || typeof(spec.attributes)!="object") spec.attributes={}; | |
if(!spec.menu || typeof(spec.menu)!="object") spec.menu=[]; | |
if(!spec.inlineMenu || typeof(spec.inlineMenu)!="object") spec.inlineMenu=[]; | |
if(!spec.canDropTo || typeof(spec.canDropTo)!="object") spec.canDropTo=[]; | |
if(!spec.mustBeAfter || typeof(spec.mustBeAfter)!="object") spec.mustBeAfter=[]; | |
if(!spec.mustBeBefore || typeof(spec.mustBeBefore)!="object") spec.mustBeBefore=[]; | |
if(!spec.oneliner || typeof(spec.oneliner)!="boolean") spec.oneliner=false; | |
if(!spec.hasText || typeof(spec.hasText)!="boolean") spec.hasText=false; | |
if(!spec.collapsed || typeof(spec.collapsed)!="function") spec.collapsed=function(){return false}; | |
for(var i=0; i<spec.menu.length; i++) Xonomy.verifyDocSpecMenuItem(spec.menu[i]); | |
for(var i=0; i<spec.inlineMenu.length; i++) Xonomy.verifyDocSpecMenuItem(spec.inlineMenu[i]); | |
}; | |
Xonomy.verifyDocSpecAttribute=function(elementName, attributeName) { //make sure the DocSpec object has such an attribute, that the attribute has everything it needs | |
var elSpec=Xonomy.docSpec.elements[elementName]; | |
if(!elSpec.attributes[attributeName] || typeof(elSpec.attributes[attributeName])!="object") elSpec.attributes[attributeName]={}; | |
var spec=elSpec.attributes[attributeName]; | |
if(!spec.asker || typeof(spec.asker)!="function") spec.asker=function(){return ""}; | |
if(!spec.menu || typeof(spec.menu)!="object") spec.menu=[]; | |
for(var i=0; i<spec.menu.length; i++) Xonomy.verifyDocSpecMenuItem(spec.menu[i]); | |
}; | |
Xonomy.verifyDocSpecMenuItem=function(menuItem) { //make sure the menu item has all it needs | |
if(!menuItem.caption) menuItem.caption="?"; | |
if(!menuItem.action || typeof(menuItem.action)!="function") menuItem.action=function(){}; | |
if(!menuItem.hideIf) menuItem.hideIf=function(){return false;}; | |
}; | |
Xonomy.nextID=function() { | |
return "xonomy"+(++Xonomy.lastIDNum); | |
}; | |
Xonomy.lastIDNum=0; | |
Xonomy.docSpec=null; | |
Xonomy.refresh=function() { | |
$(".xonomy .children ").each(function(){ //determine whether each element does or doesn't have children: | |
if(this.childNodes.length==0 && !$(this.parentNode).hasClass("hasText")) $(this.parentNode).addClass("noChildren"); | |
else { | |
$(this.parentNode).removeClass("noChildren"); | |
Xonomy.updateCollapsoid(this.parentNode.id); | |
} | |
}); | |
var merged=false; while(!merged) { //merge adjacent text nodes | |
merged=true; var textnodes=$(".xonomy .textnode").toArray(); | |
for(var i=0; i<textnodes.length; i++) { | |
var $this=$(textnodes[i]); | |
if($this.next().hasClass("textnode")) { | |
var js1=Xonomy.harvestText($this.toArray()[0]); | |
var js2=Xonomy.harvestText($this.next().toArray()[0]); | |
js1.value+=js2.value; | |
$this.next().remove(); | |
$this.replaceWith(Xonomy.renderText(js1)); | |
merged=false; | |
break; | |
} | |
} | |
} | |
$(".xonomy .element ").each(function(){ //reorder elements if necessary | |
var elSpec=Xonomy.docSpec.elements[this.getAttribute("data-name")]; | |
if(elSpec.mustBeBefore) { //is it after an element it cannot be after? then move it up until it's not! | |
var $this=$(this); | |
var ok; do { | |
ok=true; | |
for(var ii=0; ii<elSpec.mustBeBefore.length; ii++) { | |
if( $this.prevAll("*[data-name='"+elSpec.mustBeBefore[ii]+"']").toArray().length>0 ) { | |
$this.prev().before($this); | |
ok=false; | |
} | |
} | |
} while(!ok) | |
} | |
if(elSpec.mustBeAfter) { //is it before an element it cannot be before? then move it down until it's not! | |
var $this=$(this); | |
var ok; do { | |
ok=true; | |
for(var ii=0; ii<elSpec.mustBeAfter.length; ii++) { | |
if( $this.nextAll("*[data-name='"+elSpec.mustBeAfter[ii]+"']").toArray().length>0 ) { | |
$this.next().after($this); | |
ok=false; | |
} | |
} | |
} while(!ok) | |
} | |
}); | |
$(".xonomy .attribute ").each(function(){ //reorder attributes if necessary | |
var atName=this.getAttribute("data-name"); | |
var elName=this.parentNode.parentNode.parentNode.getAttribute("data-name"); | |
var elSpec=Xonomy.docSpec.elements[elName]; | |
var mustBeAfter=[]; for(var sibName in elSpec.attributes) { | |
if(sibName==atName) break; else mustBeAfter.push(sibName); | |
} | |
var mustBeBefore=[]; var seen=false; for(var sibName in elSpec.attributes) { | |
if(sibName==atName) seen=true; else if(seen) mustBeBefore.push(sibName); | |
} | |
if(mustBeBefore.length>0) { //is it after an attribute it cannot be after? then move it up until it's not! | |
var $this=$(this); | |
var ok; do { | |
ok=true; | |
for(var ii=0; ii<mustBeBefore.length; ii++) { | |
if( $this.prevAll("*[data-name='"+mustBeBefore[ii]+"']").toArray().length>0 ) { | |
$this.prev().before($this); | |
ok=false; | |
} | |
} | |
} while(!ok) | |
} | |
if(mustBeAfter.length>0) { //is it before an attribute it cannot be before? then move it down until it's not! | |
var $this=$(this); | |
var ok; do { | |
ok=true; | |
for(var ii=0; ii<mustBeAfter.length; ii++) { | |
if( $this.nextAll("*[data-name='"+mustBeAfter[ii]+"']").toArray().length>0 ) { | |
$this.next().after($this); | |
ok=false; | |
} | |
} | |
} while(!ok) | |
} | |
}); | |
}; | |
Xonomy.harvest=function() { //harvests the contents of an editor | |
//Returns xml-as-string. | |
var rootElement=$(".xonomy .element").first().toArray()[0]; | |
var js=Xonomy.harvestElement(rootElement); | |
for(var key in Xonomy.namespaces) { | |
js.attributes.push({ | |
type: "attribute", | |
name: key, | |
value: Xonomy.namespaces[key], | |
parent: js | |
}); | |
} | |
return Xonomy.js2xml(js); | |
} | |
Xonomy.harvestElement=function(htmlElement, jsParent) { | |
var js={}; | |
js.type="element"; | |
js.name=htmlElement.getAttribute("data-name"); | |
js.htmlID=htmlElement.id; | |
js.attributes=[]; | |
var htmlAttributes=$(htmlElement).find(".tag.opening > .attributes").toArray()[0]; | |
for(var i=0; i<htmlAttributes.childNodes.length; i++) { | |
var htmlAttribute=htmlAttributes.childNodes[i]; | |
if($(htmlAttribute).hasClass("attribute")) js["attributes"].push(Xonomy.harvestAttribute(htmlAttribute, js)); | |
} | |
js.children=[]; | |
var htmlChildren=$(htmlElement).children(".children").toArray()[0]; | |
for(var i=0; i<htmlChildren.childNodes.length; i++) { | |
var htmlChild=htmlChildren.childNodes[i]; | |
if($(htmlChild).hasClass("element")) js["children"].push(Xonomy.harvestElement(htmlChild, js)); | |
else if($(htmlChild).hasClass("textnode")) js["children"].push(Xonomy.harvestText(htmlChild, js)); | |
} | |
js.parent=jsParent; | |
if(!js.parent) js.parent=Xonomy.harvestParentOf(js); | |
js=Xonomy.enrichElement(js); | |
return js; | |
}; | |
Xonomy.harvestAttribute=function(htmlAttribute, jsParent) { | |
var js={ | |
type: "attribute", | |
name: htmlAttribute.getAttribute("data-name"), | |
htmlID: htmlAttribute.id, | |
value: htmlAttribute.getAttribute("data-value"), | |
parent: jsParent | |
}; | |
if(!js.parent) js.parent=Xonomy.harvestParentOf(js); | |
return js; | |
} | |
Xonomy.harvestText=function(htmlText, jsParent) { | |
var js={ | |
type: "text", | |
htmlID: htmlText.id, | |
value: htmlText.getAttribute("data-value"), | |
parent: jsParent | |
}; | |
if(!js.parent) js.parent=Xonomy.harvestParentOf(js); | |
return js; | |
} | |
Xonomy.harvestParentOf=function(js) { | |
var jsParent=null; | |
var $parent=$("#"+js.htmlID).parent().closest(".element"); | |
if($parent.toArray().length==1) { | |
jsParent=Xonomy.harvestElement($parent.toArray()[0]); | |
for(var i=0; i<jsParent.attributes.length; i++) if(jsParent.attributes[i].htmlID==js.htmlID) jsParent.attributes[i]=js; | |
for(var i=0; i<jsParent.children.length; i++) if(jsParent.children[i].htmlID==js.htmlID) jsParent.children[i]=js; | |
} | |
return jsParent; | |
}; | |
Xonomy.render=function(data, editor, docSpec) { //renders the contents of an editor | |
//The data can be a Xonomy-compliant XML document, a Xonomy-compliant xml-as-string, | |
//or a Xonomy-compliant JavaScript object. | |
//The editor can be an HTML element, or the string ID of one. | |
Xonomy.docSpec=docSpec; | |
Xonomy.verifyDocSpec(); | |
//Clear namespace cache: | |
Xonomy.namespaces={}; | |
//Convert doc to a JavaScript object, if it isn't a JavaScript object already: | |
if(typeof(data)=="string") data=$.parseXML(data); | |
if(data.documentElement) data=Xonomy.xml2js(data); | |
//Make sure editor refers to an HTML element, if it doesn't already: | |
if(typeof(editor)=="string") editor=document.getElementById(editor); | |
if(!$(editor).hasClass("xonomy")) $(editor).addClass("xonomy"); //make sure it has class "xonomy" | |
$(editor).hide(); | |
editor.innerHTML=Xonomy.renderElement(data, editor); | |
$(editor).show(); | |
//Make sure the "click off" handler is attached: | |
$(document.body).off("click", Xonomy.clickoff); | |
$(document.body).on("click", Xonomy.clickoff); | |
//Make sure the "drag end" handler is attached: | |
$(document.body).off("dragend", Xonomy.dragend); | |
$(document.body).on("dragend", Xonomy.dragend); | |
Xonomy.refresh(); | |
Xonomy.validate(); | |
}; | |
Xonomy.renderElement=function(element) { | |
var htmlID=Xonomy.nextID(); | |
Xonomy.verifyDocSpecElement(element.name); | |
var spec=Xonomy.docSpec.elements[element.name]; | |
var classNames="element"; | |
if(spec.canDropTo && spec.canDropTo.length>0) classNames+=" draggable"; | |
if(spec.hasText) classNames+=" hasText"; | |
if(spec.inlineMenu && spec.inlineMenu.length>0) classNames+=" hasInlineMenu"; | |
if(spec.oneliner) classNames+=" oneliner"; | |
if(spec.collapsed(element)) classNames+=" collapsed"; | |
var html=""; | |
html+='<div data-name="'+element.name+'" id="'+htmlID+'" class="'+classNames+'">'; | |
html+='<span class="connector">'; | |
html+='<span class="plusminus" onclick="Xonomy.plusminus(\''+htmlID+'\')"></span>'; | |
html+='<span class="draghandle" draggable="true" ondragstart="Xonomy.drag(event)"></span>'; | |
html+='</span>'; | |
html+='<span class="tag opening">'; | |
html+='<span class="punc"><</span>'; | |
html+='<span class="name" onclick="Xonomy.click(\''+htmlID+'\', \'openingTagName\')">'+element.name+'</span>'; | |
html+='<span class="warner"><span class="inside" onclick="Xonomy.click(\''+htmlID+'\', \'warner\')"></span></span>'; | |
html+='<span class="attributes">'; | |
for(var i=0; i<element.attributes.length; i++) { | |
Xonomy.verifyDocSpecAttribute(element.name, element.attributes[i].name); | |
html+=Xonomy.renderAttribute(element.attributes[i]); | |
} | |
html+='</span>'; | |
html+='<span class="punc slash">/</span>'; | |
html+='<span class="punc">></span>'; | |
html+='</span>'; | |
html+='<span class="childrenCollapsed" onclick="Xonomy.plusminus(\''+htmlID+'\', true)">···</span>'; | |
html+='<div class="children">'; | |
var prevChildType=""; | |
if(spec.hasText && (element.children.length==0 || element.children[0].type=="element")) { | |
html+=Xonomy.renderText({type: "text", value: ""}); //if inline layout, insert empty text node between two elements | |
} | |
for(var i=0; i<element.children.length; i++) { | |
var child=element.children[i]; | |
if(spec.hasText && prevChildType=="element" && child.type=="element") { | |
html+=Xonomy.renderText({type: "text", value: ""}); //if inline layout, insert empty text node between two elements | |
} | |
if(child.type=="text") html+=Xonomy.renderText(child); //text node | |
else if(child.type=="element") html+=Xonomy.renderElement(child); //element node | |
prevChildType=child.type; | |
} | |
if(spec.hasText && element.children.length>1 && element.children[element.children.length-1].type=="element") { | |
html+=Xonomy.renderText({type: "text", value: ""}); //if inline layout, insert empty text node between two elements | |
} | |
html+='</div>'; | |
html+='<span class="tag closing">'; | |
html+='<span class="punc"><</span>'; | |
html+='<span class="punc">/</span>'; | |
html+='<span class="name" onclick="Xonomy.click(\''+htmlID+'\', \'closingTagName\')">'+element.name+'</span>'; | |
html+='<span class="punc">></span>'; | |
html+='</span>'; | |
html+='</div>'; | |
return html; | |
}; | |
Xonomy.renderAttribute=function(attribute) { | |
var htmlID=Xonomy.nextID(); | |
var readonly=false; //TBE | |
classNames="attribute"; if(readonly) classNames+=" readonly"; | |
var html=""; | |
html+='<span data-name="'+attribute.name+'" data-value="'+Xonomy.xmlEscape(attribute.value)+'" id="'+htmlID+'" class="'+classNames+'">'; | |
html+='<span class="punc"> </span>'; | |
var onclick=''; if(!readonly) onclick=' onclick="Xonomy.click(\''+htmlID+'\', \'attributeName\')"'; | |
html+='<span class="name"'+onclick+'>'+attribute.name+'</span>'; | |
html+='<span class="warner"><span class="inside" onclick="Xonomy.click(\''+htmlID+'\', \'warner\')"></span></span>'; | |
html+='<span class="punc">=</span>'; | |
var onclick=''; if(!readonly) onclick=' onclick="Xonomy.click(\''+htmlID+'\', \'attributeValue\')"'; | |
html+='<span class="valueContainer"'+onclick+'>'; | |
html+='<span class="punc">"</span>'; | |
html+='<span class="value">'+Xonomy.xmlEscape(attribute.value)+'</span>'; | |
html+='<span class="punc">"</span>'; | |
html+='</span>'; | |
html+='</span>'; | |
return html; | |
}; | |
Xonomy.renderText=function(text) { | |
var htmlID=Xonomy.nextID(); | |
var classNames="textnode"; | |
if($.trim(text.value)=="") classNames+=" whitespace"; | |
if(text.value=="") classNames+=" empty" | |
var html=""; | |
html+='<div id="'+htmlID+'" data-value="'+Xonomy.xmlEscape(text.value)+'" class="'+classNames+'">'; | |
html+='<span class="connector"></span>'; | |
html+='<span class="value" onclick="Xonomy.click(\''+htmlID+'\', \'text\')">'+Xonomy.chewText(text.value)+'</span>'; | |
html+='</div>'; | |
return html; | |
} | |
Xonomy.chewText=function(txt) { | |
var ret=""; | |
ret+="<span class='word'>"; //start word | |
for(var i=0; i<txt.length; i++) { | |
if(txt[i]==" ") ret+="</span>"; //end word | |
var t=Xonomy.xmlEscape(txt[i]) | |
if(i==0 && t==" ") t=" "; //leading space | |
if(i==txt.length-1 && t==" ") t=" "; //trailing space | |
ret+="<span class='char'>"+t+"<span class='selector'><span class='inside' onclick='Xonomy.charClick(this.parentNode.parentNode)'></span></span></span>"; | |
if(txt[i]==" ") ret+="<span class='word'>"; //start word | |
} | |
ret+="</span>"; //end word | |
return ret; | |
}; | |
Xonomy.charClick=function(c) { | |
Xonomy.clickoff(); | |
Xonomy.notclick=true; | |
if( | |
$(".xonomy .char.on").toArray().length==1 && //if there is precisely one previously selected character | |
$(".xonomy .char.on").closest(".element").is($(c).closest(".element")) //and if it has the same parent element as this character | |
) { | |
var $element=$(".xonomy .char.on").closest(".element"); | |
var chars=$element.find(".char").toArray(); | |
var iFrom=$.inArray($(".xonomy .char.on").toArray()[0], chars); | |
var iTill=$.inArray(c, chars); | |
if(iFrom>iTill) {var temp=iFrom; iFrom=iTill; iTill=temp;} | |
for(var i=0; i<chars.length; i++) { //highlight all chars between start and end | |
if(i>=iFrom && i<=iTill) $(chars[i]).addClass("on"); | |
} | |
//Save for later the info Xonomy needs to know what to wrap: | |
Xonomy.textFromID=$(chars[iFrom]).closest(".textnode").attr("id"); | |
Xonomy.textTillID=$(chars[iTill]).closest(".textnode").attr("id"); | |
Xonomy.textFromIndex=$.inArray(chars[iFrom], $("#"+Xonomy.textFromID).find(".char").toArray()); | |
Xonomy.textTillIndex=$.inArray(chars[iTill], $("#"+Xonomy.textTillID).find(".char").toArray()); | |
//Show inline menu etc: | |
var htmlID=$element.attr("id"); | |
var content=Xonomy.inlineMenu(htmlID); //compose bubble content | |
if(content!="") { | |
document.body.appendChild(Xonomy.makeBubble(content)); //create bubble | |
Xonomy.showBubble($("#"+htmlID+" .char.on").last()); //anchor bubble to highlighted chars | |
} | |
Xonomy.clearChars=true; | |
} else { | |
$(".xonomy .char.on").removeClass("on"); | |
$(c).addClass("on"); | |
} | |
}; | |
Xonomy.wrap=function(htmlID, param) { | |
Xonomy.clickoff(); | |
var xml=param.template; | |
var ph=param.placeholder; | |
if(Xonomy.textFromID==Xonomy.textTillID) { //abc --> a<XYZ>b</XYZ>c | |
var jsOld=Xonomy.harvestText(document.getElementById(Xonomy.textFromID)); | |
var txtOpen=jsOld.value.substring(0, Xonomy.textFromIndex); | |
var txtMiddle=jsOld.value.substring(Xonomy.textFromIndex, Xonomy.textTillIndex+1); | |
var txtClose=jsOld.value.substring(Xonomy.textTillIndex+1); | |
xml=xml.replace(ph, Xonomy.xmlEscape(txtMiddle)); | |
var html=""; | |
html+=Xonomy.renderText({type: "text", value: txtOpen}); | |
html+=Xonomy.renderElement(Xonomy.xml2js(xml)); | |
html+=Xonomy.renderText({type: "text", value: txtClose}); | |
$("#"+Xonomy.textFromID).replaceWith(html); | |
} else { //ab<...>cd --> a<XYZ>b<...>c</XYZ>d | |
var jsOldOpen=Xonomy.harvestText(document.getElementById(Xonomy.textFromID)); | |
var jsOldClose=Xonomy.harvestText(document.getElementById(Xonomy.textTillID)); | |
var txtOpen=jsOldOpen.value.substring(0, Xonomy.textFromIndex); | |
var txtMiddleOpen=jsOldOpen.value.substring(Xonomy.textFromIndex); | |
var txtMiddleClose=jsOldClose.value.substring(0, Xonomy.textTillIndex+1); | |
var txtClose=jsOldClose.value.substring(Xonomy.textTillIndex+1); | |
xml=xml.replace(ph, Xonomy.xmlEscape(txtMiddleOpen)+ph); | |
$("#"+Xonomy.textFromID).nextUntil("#"+Xonomy.textTillID).each(function(){ | |
if($(this).hasClass("element")) xml=xml.replace(ph, Xonomy.js2xml(Xonomy.harvestElement(this))+ph); | |
else if($(this).hasClass("textnode")) xml=xml.replace(ph, Xonomy.js2xml(Xonomy.harvestText(this))+ph); | |
}); | |
xml=xml.replace(ph, Xonomy.xmlEscape(txtMiddleClose)); | |
$("#"+Xonomy.textFromID).nextUntil("#"+Xonomy.textTillID).remove(); | |
$("#"+Xonomy.textTillID).remove(); | |
var html=""; | |
html+=Xonomy.renderText({type: "text", value: txtOpen}); | |
html+=Xonomy.renderElement(Xonomy.xml2js(xml)); | |
html+=Xonomy.renderText({type: "text", value: txtClose}); | |
$("#"+Xonomy.textFromID).replaceWith(html); | |
} | |
Xonomy.refresh(); | |
}; | |
Xonomy.unwrap=function(htmlID, param) { | |
Xonomy.clickoff(); | |
$("#"+htmlID).replaceWith($("#"+htmlID+" > .children > *")); | |
Xonomy.refresh(); | |
}; | |
Xonomy.plusminus=function(htmlID, forceExpand) { | |
var $element=$("#"+htmlID); | |
var $children=$element.children(".children"); | |
if($element.hasClass("collapsed")) { | |
$children.hide(); | |
$element.removeClass("collapsed"); | |
if($element.hasClass("oneliner")) $children.fadeIn("fast"); else $children.slideDown("fast"); | |
} else if(!forceExpand) { | |
Xonomy.updateCollapsoid(htmlID); | |
if($element.hasClass("oneliner")) $children.fadeOut("fast", function(){ $element.addClass("collapsed"); }); | |
else $children.slideUp("fast", function(){ $element.addClass("collapsed"); }); | |
} | |
}; | |
Xonomy.updateCollapsoid=function(htmlID) { | |
var $element=$("#"+htmlID); | |
var whisper=""; | |
$element.find(".textnode").each(function(){ | |
var txt=Xonomy.harvestText(this).value; | |
for(var i=0; i<txt.length; i++) { | |
if(whisper.length<25) whisper+=txt[i]; | |
} | |
}); | |
if(whisper!="...") whisper+="..."; | |
//if(whisper=="") whisper="···aha"; | |
$element.children(".childrenCollapsed").html(whisper); | |
}; | |
Xonomy.click=function(htmlID, what) { | |
if(!Xonomy.notclick) { | |
Xonomy.clickoff(); | |
$(".xonomy .char.on").removeClass("on"); | |
if(what=="openingTagName" || what=="closingTagName") { | |
$("#"+htmlID).addClass("current"); //make the element current | |
var content=Xonomy.elementMenu(htmlID); //compose bubble content | |
if(content!="") { | |
document.body.appendChild(Xonomy.makeBubble(content)); //create bubble | |
Xonomy.showBubble($("#"+htmlID+" > .tag.opening > .name")); //anchor bubble to opening tag | |
} | |
} | |
if(what=="attributeName") { | |
$("#"+htmlID).addClass("current"); //make the attribute current | |
var content=Xonomy.attributeMenu(htmlID); //compose bubble content | |
if(content!="") { | |
document.body.appendChild(Xonomy.makeBubble(content)); //create bubble | |
Xonomy.showBubble($("#"+htmlID+" > .name")); //anchor bubble to attribute name | |
} | |
} | |
if(what=="attributeValue") { | |
$("#"+htmlID+" > .valueContainer").addClass("current"); //make attribute value current | |
var name=$("#"+htmlID).attr("data-name"); //obtain attribute's name | |
var value=$("#"+htmlID).attr("data-value"); //obtain current value | |
var elName=$("#"+htmlID).closest(".element").attr("data-name"); | |
Xonomy.verifyDocSpecAttribute(elName, name); | |
var spec=Xonomy.docSpec.elements[elName].attributes[name]; | |
var content=spec.asker(value, spec.askerParameter); //compose bubble content | |
if(content!="") { | |
document.body.appendChild(Xonomy.makeBubble(content)); //create bubble | |
Xonomy.showBubble($("#"+htmlID+" > .valueContainer > .value")); //anchor bubble to value | |
Xonomy.answer=function(val) { | |
var obj=document.getElementById(htmlID); | |
var html=Xonomy.renderAttribute({type: "attribute", name: name, value: val}); | |
$(obj).replaceWith(html); | |
Xonomy.clickoff(); | |
Xonomy.changed(); | |
}; | |
} | |
} | |
if(what=="text") { | |
$("#"+htmlID).addClass("current"); | |
var value=$("#"+htmlID).attr("data-value"); //obtain current value | |
var elName=$("#"+htmlID).closest(".element").attr("data-name"); | |
var spec=Xonomy.docSpec.elements[elName]; | |
if (typeof(spec.asker) != "function") { | |
var content=Xonomy.askLongString(value); //compose bubble content | |
} else { | |
var content=spec.asker(value, spec.askerParameter); //use specified asker | |
} | |
if (typeof spec.readonly === "undefined" || !spec.readonly) { | |
document.body.appendChild(Xonomy.makeBubble(content)); //create bubble | |
Xonomy.showBubble($("#"+htmlID+" > .value")); //anchor bubble to value | |
Xonomy.answer=function(val) { | |
var obj=document.getElementById(htmlID); | |
var html=Xonomy.renderText({type: "text", value: val}); | |
$(obj).replaceWith(html); | |
Xonomy.clickoff(); | |
Xonomy.changed(); | |
}; | |
} | |
} | |
if(what=="warner") { | |
//$("#"+htmlID).addClass("current"); | |
var content=""; //compose bubble content | |
for(var iWarning=0; iWarning<Xonomy.warnings.length; iWarning++) { | |
var warning=Xonomy.warnings[iWarning]; | |
if(warning.htmlID==htmlID) { | |
content+="<div class='warning'>"+Xonomy.formatCaption(warning.text)+"</div>"; | |
} | |
} | |
document.body.appendChild(Xonomy.makeBubble(content)); //create bubble | |
Xonomy.showBubble($("#"+htmlID+" .warner .inside").first()); //anchor bubble to warner | |
} | |
Xonomy.notclick=true; | |
} | |
}; | |
Xonomy.notclick=false; //should the latest click-off event be ignored? | |
Xonomy.clearChars=false; //if true, un-highlight any highlighted characters at the next click-off event | |
Xonomy.clickoff=function() { //event handler for the document-wide click-off event. | |
if(!Xonomy.notclick) { | |
Xonomy.destroyBubble(); | |
$(".xonomy .current").removeClass("current"); | |
if(Xonomy.clearChars) { | |
$(".xonomy .char.on").removeClass("on"); | |
Xonomy.clearChars=false; | |
} | |
} | |
Xonomy.notclick=false; | |
}; | |
Xonomy.destroyBubble=function() { | |
if(document.getElementById("xonomyBubble")) { | |
var bubble=document.getElementById("xonomyBubble"); | |
bubble.parentNode.removeChild(bubble); | |
} | |
}; | |
Xonomy.makeBubble=function(content) { | |
Xonomy.destroyBubble(); | |
var bubble=document.createElement("div"); | |
bubble.id="xonomyBubble"; | |
bubble.innerHTML="<div class='arrow'></div>" | |
+"<div class='inside' onclick='Xonomy.notclick=true;'>" | |
+"<div id='xonomyBubbleContent'>"+content+"</div>" | |
+"</div>"; | |
return bubble; | |
}; | |
Xonomy.showBubble=function($anchor) { | |
var $bubble=$("#xonomyBubble"); | |
var offset=$anchor.offset(); var left=offset.left; var top=offset.top; | |
var screenWidth=$("body").width(); | |
if(left<screenWidth/2) { | |
var width=$anchor.width(); if(width>40) width=40; | |
var height=$anchor.height(); if(height>25) height=25; | |
$bubble.css({top: (top+height)+"px", left: (left+width-15)+"px"}); | |
} else { | |
var width=$anchor.width(); if(width>40) width=40; | |
var height=$anchor.height(); if(height>25) height=25; | |
$bubble.addClass("rightAnchored"); | |
$bubble.css({top: (top+height)+"px", right: (screenWidth-left)+"px"}); | |
} | |
$bubble.slideDown("fast", function() { | |
$bubble.find(".focusme").first().focus(); //if the context menu contains anything with the class name 'focusme', focus it. | |
}); | |
}; | |
Xonomy.askString=function(defaultString) { | |
var html=""; | |
html+="<form onsubmit='Xonomy.answer(this.val.value); return false'>"; | |
html+="<input name='val' class='textbox focusme' value='"+Xonomy.xmlEscape(defaultString)+"'/>"; | |
html+=" <input type='submit' value='OK'>"; | |
html+="</form>"; | |
return html; | |
}; | |
Xonomy.askLongString=function(defaultString) { | |
var html=""; | |
html+="<form onsubmit='Xonomy.answer(this.val.value); return false'>"; | |
html+="<textarea name='val' class='textbox focusme' spellcheck='false'>"+Xonomy.xmlEscape(defaultString)+"</textarea>"; | |
html+="<div class='submitline'><input type='submit' value='OK'></div>"; | |
html+="</form>"; | |
return html; | |
}; | |
Xonomy.askPicklist=function(defaultString, picklist) { | |
var html=""; | |
html+="<div class='menu'>"; | |
for(var i=0; i<picklist.length; i++) { | |
var item=picklist[i]; | |
if(typeof(item)=="string") item={value: item, caption: ""}; | |
html+="<div class='menuItem techno"+(item.value==defaultString?" current":"")+"' onclick='Xonomy.answer(\""+Xonomy.xmlEscape(item.value)+"\")'>"; | |
html+="<span class='punc'>\"</span>"; | |
html+=Xonomy.xmlEscape(item.value); | |
html+="<span class='punc'>\"</span>"; | |
if(item.caption!="") html+=" <span class='explainer'>"+Xonomy.xmlEscape(item.caption)+"</span>"; | |
html+="</div>"; | |
} | |
html+="</div>"; | |
return html; | |
}; | |
Xonomy.attributeMenu=function(htmlID) { | |
var name=$("#"+htmlID).attr("data-name"); //obtain attribute's name | |
var elName=$("#"+htmlID).closest(".element").attr("data-name"); //obtain element's name | |
Xonomy.verifyDocSpecAttribute(elName, name); | |
var spec=Xonomy.docSpec.elements[elName].attributes[name]; | |
var html=""; | |
for(var i=0; i<spec.menu.length; i++) { | |
var item=spec.menu[i]; | |
var includeIt=!item.hideIf(Xonomy.harvestAttribute(document.getElementById(htmlID))); | |
if(includeIt) { | |
html+="<div class='menuItem' onclick='Xonomy.callMenuFunction(Xonomy.docSpec.elements[\""+elName+"\"].attributes[\""+name+"\"].menu["+i+"], \""+htmlID+"\")'>"; | |
html+=Xonomy.formatCaption(item.caption); | |
html+="</div>"; | |
} | |
} | |
if(html!="") html="<div class='menu'>"+html+"</div>"; | |
return html; | |
}; | |
Xonomy.elementMenu=function(htmlID) { | |
var elName=$("#"+htmlID).attr("data-name"); //obtain element's name | |
var spec=Xonomy.docSpec.elements[elName]; | |
var html=""; | |
for(var i=0; i<spec.menu.length; i++) { | |
var item=spec.menu[i]; | |
var includeIt=!item.hideIf(Xonomy.harvestElement(document.getElementById(htmlID))); | |
if(includeIt) { | |
html+="<div class='menuItem' onclick='Xonomy.callMenuFunction(Xonomy.docSpec.elements[\""+elName+"\"].menu["+i+"], \""+htmlID+"\")'>"; | |
html+=Xonomy.formatCaption(item.caption); | |
html+="</div>"; | |
} | |
} | |
if(html!="") html="<div class='menu'>"+html+"</div>"; | |
return html; | |
}; | |
Xonomy.inlineMenu=function(htmlID) { | |
var elName=$("#"+htmlID).attr("data-name"); //obtain element's name | |
var spec=Xonomy.docSpec.elements[elName]; | |
var html=""; | |
for(var i=0; i<spec.inlineMenu.length; i++) { | |
var item=spec.inlineMenu[i]; | |
var includeIt=!item.hideIf(Xonomy.harvestElement(document.getElementById(htmlID))); | |
if(includeIt) { | |
html+="<div class='menuItem' onclick='Xonomy.callMenuFunction(Xonomy.docSpec.elements[\""+elName+"\"].inlineMenu["+i+"], \""+htmlID+"\")'>"; | |
html+=Xonomy.formatCaption(item.caption); | |
html+="</div>"; | |
} | |
} | |
if(html!="") html="<div class='menu'>"+html+"</div>"; | |
return html; | |
}; | |
Xonomy.callMenuFunction=function(menuItem, htmlID) { | |
menuItem.action(htmlID, menuItem.actionParameter); | |
Xonomy.changed(); | |
}; | |
Xonomy.formatCaption=function(caption) { | |
caption=caption.replace(/\<([^\>]+)\>/g, "<span class='techno'><span class='punc'><</span><span class='elName'>$1</span><span class='punc'>></span></span>"); | |
caption=caption.replace(/\@"([^\"]+)"/g, "<span class='techno'><span class='punc'>\"</span><span class='atValue'>$1</span><span class='punc'>\"</span></span>"); | |
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>"); | |
caption=caption.replace(/\@([^ =]+)/g, "<span class='techno'><span class='atName'>$1</span></span>"); | |
return caption; | |
}; | |
Xonomy.deleteAttribute=function(htmlID, parameter) { | |
Xonomy.clickoff(); | |
var obj=document.getElementById(htmlID); | |
obj.parentNode.removeChild(obj); | |
Xonomy.refresh(); | |
}; | |
Xonomy.deleteElement=function(htmlID, parameter) { | |
Xonomy.clickoff(); | |
var obj=document.getElementById(htmlID); | |
$(obj).slideUp(function(){ | |
obj.parentNode.removeChild(obj); | |
Xonomy.refresh(); | |
}); | |
}; | |
Xonomy.newAttribute=function(htmlID, parameter) { | |
Xonomy.clickoff(); | |
var html=Xonomy.renderAttribute({type: "attribute", name: parameter.name, value: parameter.value}); | |
$("#"+htmlID+" > .tag.opening > .attributes").append(html); | |
Xonomy.refresh(); | |
}; | |
Xonomy.newElementChild=function(htmlID, parameter) { | |
Xonomy.clickoff(); | |
var html=Xonomy.renderElement(Xonomy.xml2js(parameter)); | |
var $html=$(html).hide(); | |
$("#"+htmlID+" > .children").append($html); | |
Xonomy.plusminus(htmlID, true); | |
Xonomy.refresh(); | |
$html.slideDown(); | |
}; | |
Xonomy.newElementBefore=function(htmlID, parameter) { | |
Xonomy.clickoff(); | |
var html=Xonomy.renderElement(Xonomy.xml2js(parameter)); | |
var $html=$(html).hide(); | |
$("#"+htmlID).before($html); | |
Xonomy.refresh(); | |
$html.slideDown(); | |
}; | |
Xonomy.newElementAfter=function(htmlID, parameter) { | |
Xonomy.clickoff(); | |
var html=Xonomy.renderElement(Xonomy.xml2js(parameter)); | |
var $html=$(html).hide(); | |
$("#"+htmlID).after($html); | |
Xonomy.refresh(); | |
$html.slideDown(); | |
}; | |
Xonomy.replace=function(htmlID, jsNode) { | |
Xonomy.clickoff(); | |
var html=""; | |
if(jsNode.type=="element") html=Xonomy.renderElement(jsNode); | |
if(jsNode.type=="attribute") html=Xonomy.renderAttribute(jsNode); | |
if(jsNode.type=="text") html=Xonomy.renderText(jsNode); | |
$("#"+htmlID).replaceWith(html); | |
Xonomy.refresh(); | |
}; | |
Xonomy.draggingID=null; //what are we dragging? | |
Xonomy.drag=function(ev) { //called when dragging starts | |
Xonomy.clickoff(); | |
var htmlID=ev.target.parentNode.parentNode.id; | |
var $element=$("#"+htmlID); | |
var elementName=$element.attr("data-name"); | |
var elSpec=Xonomy.docSpec.elements[elementName]; | |
$element.addClass("dragging"); | |
$(".xonomy .children").append("<div class='elementDropper' ondragover='Xonomy.dragOver(event)' ondragleave='Xonomy.dragOut(event)' ondrop='Xonomy.drop(event)'><div class='inside'></div></div>") | |
$(".xonomy .children .element").before("<div class='elementDropper' ondragover='Xonomy.dragOver(event)' ondragleave='Xonomy.dragOut(event)' ondrop='Xonomy.drop(event)'><div class='inside'></div></div>") | |
$(".xonomy .children .text").before("<div class='elementDropper' ondragover='Xonomy.dragOver(event)' ondragleave='Xonomy.dragOut(event)' ondrop='Xonomy.drop(event)'><div class='inside'></div></div>") | |
$(".xonomy .dragging .elementDropper").remove(); //remove drop targets inside the element being dragged | |
$(".xonomy .dragging").prev(".elementDropper").remove(); //remove drop targets from immediately before the element being dragged | |
$(".xonomy .dragging").next(".elementDropper").remove(); //remove drop targets from immediately after the element being dragged | |
if(elSpec.canDropTo) { //remove the drop target from elements it cannot be dropped into | |
var droppers=$(".xonomy .elementDropper").toArray(); | |
for(var i=0; i<droppers.length; i++) { | |
var dropper=droppers[i]; | |
var parentElementName=$(dropper.parentNode.parentNode).toArray()[0].getAttribute("data-name"); | |
if($.inArray(parentElementName, elSpec.canDropTo)<0) { | |
dropper.parentNode.removeChild(dropper); | |
} | |
} | |
} | |
if(elSpec.mustBeBefore) { //remove the drop target from after elements it cannot be after | |
var droppers=$(".xonomy .elementDropper").toArray(); | |
for(var i=0; i<droppers.length; i++) { | |
var dropper=droppers[i]; | |
for(var ii=0; ii<elSpec.mustBeBefore.length; ii++) { | |
if( $(dropper).prevAll("*[data-name='"+elSpec.mustBeBefore[ii]+"']").toArray().length>0 ) { | |
dropper.parentNode.removeChild(dropper); | |
} | |
} | |
} | |
} | |
if(elSpec.mustBeAfter) { //remove the drop target from before elements it cannot be before | |
var droppers=$(".xonomy .elementDropper").toArray(); | |
for(var i=0; i<droppers.length; i++) { | |
var dropper=droppers[i]; | |
for(var ii=0; ii<elSpec.mustBeAfter.length; ii++) { | |
if( $(dropper).nextAll("*[data-name='"+elSpec.mustBeAfter[ii]+"']").toArray().length>0 ) { | |
dropper.parentNode.removeChild(dropper); | |
} | |
} | |
} | |
} | |
Xonomy.draggingID=htmlID; | |
ev.dataTransfer.setData("text", htmlID); | |
Xonomy.refresh(); | |
}; | |
Xonomy.dragOver=function(ev) { | |
ev.preventDefault(); | |
$(ev.target.parentNode).addClass("activeDropper"); | |
}; | |
Xonomy.dragOut=function(ev) { | |
ev.preventDefault(); | |
$(".xonomy .activeDropper").removeClass("activeDropper"); | |
}; | |
Xonomy.drop=function(ev) { | |
ev.preventDefault(); | |
var node=document.getElementById(Xonomy.draggingID); //the thing we are moving | |
$(ev.target.parentNode).replaceWith(node); | |
Xonomy.changed(); | |
}; | |
Xonomy.dragend=function(ev) { | |
$(".xonomy .attributeDropper").remove(); | |
$(".xonomy .elementDropper").remove(); | |
$(".xonomy .dragging").removeClass("dragging"); | |
Xonomy.refresh(); | |
}; | |
Xonomy.changed=function() { //called when the document changes | |
Xonomy.validate(); | |
Xonomy.docSpec.onchange(); //report that the document has changed | |
}; | |
Xonomy.validate=function() { | |
var js=Xonomy.harvestElement($(".xonomy .element").toArray()[0], null); | |
$(".xonomy .invalid").removeClass("invalid"); | |
Xonomy.warnings=[]; | |
Xonomy.docSpec.validate(js); //validate the document | |
for(var iWarning=0; iWarning<Xonomy.warnings.length; iWarning++) { | |
var warning=Xonomy.warnings[iWarning]; | |
$("#"+warning.htmlID).addClass("invalid"); | |
} | |
}; | |
Xonomy.warnings=[]; //array of {htmlID: "", text: ""} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment