1
0
mirror of https://github.com/Jermolene/TiddlyWiki5 synced 2024-09-18 02:09:43 +00:00
TiddlyWiki5/core/modules/rendertree/renderers/element.js
Jeremy Ruston 0a3389c277 Add namespace support to the element renderer
This allows us to create SVG elements with the correct namespace, which
allows us to have inline SVG images (coming up next)
2013-05-15 18:44:23 +01:00

244 lines
6.9 KiB
JavaScript

/*\
title: $:/core/modules/rendertree/renderers/element.js
type: application/javascript
module-type: wikirenderer
Element renderer
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
/*
Element widget. A degenerate widget that renders ordinary HTML elements
*/
var ElementWidget = function(renderer) {
this.renderer = renderer;
this.tag = this.renderer.parseTreeNode.tag;
this.attributes = this.renderer.attributes;
this.children = this.renderer.renderTree.createRenderers(this.renderer,this.renderer.parseTreeNode.children);
this.events = this.renderer.parseTreeNode.events;
};
ElementWidget.prototype.refreshInDom = function(changedAttributes,changedTiddlers) {
// Check if any of our attribute dependencies have changed
if($tw.utils.count(changedAttributes) > 0) {
// Update our attributes
this.renderer.assignAttributes();
}
// Refresh any child nodes
$tw.utils.each(this.children,function(node) {
if(node.refreshInDom) {
node.refreshInDom(changedTiddlers);
}
});
};
/*
Element renderer
*/
var ElementRenderer = function(renderTree,parentRenderer,parseTreeNode) {
// Store state information
this.renderTree = renderTree;
this.parentRenderer = parentRenderer;
this.parseTreeNode = parseTreeNode;
// Initialise widget classes
if(!this.widgetClasses) {
ElementRenderer.prototype.widgetClasses = $tw.modules.applyMethods("widget");
}
// Select the namespace for the tag
var tagNameSpaces = {
svg: "http://www.w3.org/2000/svg"
};
this.namespace = tagNameSpaces[this.parseTreeNode.tag];
if(this.namespace) {
this.context = this.context || {};
this.context.namespace = this.namespace;
} else {
this.namespace = this.renderTree.getContextVariable(this.parentRenderer,"namespace","http://www.w3.org/1999/xhtml");
}
// Get the context tiddler title
this.tiddlerTitle = this.renderTree.getContextVariable(this.parentRenderer,"tiddlerTitle");
// Compute our dependencies
this.dependencies = {};
var self = this;
$tw.utils.each(this.parseTreeNode.attributes,function(attribute,name) {
if(attribute.type === "indirect") {
var tr = $tw.utils.parseTextReference(attribute.textReference);
self.dependencies[tr.title ? tr.title : self.tiddlerTitle] = true;
}
});
// Compute our attributes
this.attributes = {};
this.computeAttributes();
// Create the parasite widget object if required
if(this.parseTreeNode.tag.charAt(0) === "$") {
// Choose the class
var WidgetClass = this.widgetClasses[this.parseTreeNode.tag.substr(1)];
// Instantiate the widget
if(WidgetClass) {
this.widget = new WidgetClass(this);
} else {
WidgetClass = this.widgetClasses.error;
if(WidgetClass) {
this.widget = new WidgetClass(this,"Unknown widget '<" + this.parseTreeNode.tag + ">'");
}
}
}
// If we haven't got a widget, use the generic HTML element widget
if(!this.widget) {
this.widget = new ElementWidget(this);
}
};
ElementRenderer.prototype.computeAttributes = function() {
var changedAttributes = {};
var self = this;
$tw.utils.each(this.parseTreeNode.attributes,function(attribute,name) {
if(attribute.type === "indirect") {
var value = self.renderTree.wiki.getTextReference(attribute.textReference,"",self.tiddlerTitle);
if(self.attributes[name] !== value) {
self.attributes[name] = value;
changedAttributes[name] = true;
}
} else { // String attribute
if(self.attributes[name] !== attribute.value) {
self.attributes[name] = attribute.value;
changedAttributes[name] = true;
}
}
});
return changedAttributes;
};
ElementRenderer.prototype.hasAttribute = function(name) {
return $tw.utils.hop(this.attributes,name);
};
ElementRenderer.prototype.getAttribute = function(name,defaultValue) {
if($tw.utils.hop(this.attributes,name)) {
return this.attributes[name];
} else {
return defaultValue;
}
};
ElementRenderer.prototype.render = function(type) {
var isHtml = type === "text/html",
output = [],attr,a,v;
if(isHtml) {
output.push("<",this.widget.tag);
if(this.widget.attributes) {
attr = [];
for(a in this.widget.attributes) {
attr.push(a);
}
attr.sort();
for(a=0; a<attr.length; a++) {
v = this.widget.attributes[attr[a]];
if(v !== undefined) {
if($tw.utils.isArray(v)) {
v = v.join(" ");
} else if(typeof v === "object") {
var s = [];
for(var p in v) {
s.push(p + ":" + v[p] + ";");
}
v = s.join("");
}
output.push(" ",attr[a],"='",$tw.utils.htmlEncode(v),"'");
}
}
}
output.push(">\n");
}
if($tw.config.htmlVoidElements.indexOf(this.widget.tag) === -1) {
$tw.utils.each(this.widget.children,function(node) {
if(node.render) {
output.push(node.render(type));
}
});
if(isHtml) {
output.push("</",this.widget.tag,">");
}
}
return output.join("");
};
ElementRenderer.prototype.renderInDom = function() {
// Check if our widget is providing an element
if(this.widget.tag) {
// Create the element
this.domNode = document.createElementNS(this.namespace,this.widget.tag);
// Assign any specified event handlers
$tw.utils.addEventListeners(this.domNode,this.widget.events);
// Assign the attributes
this.assignAttributes();
// Render any child nodes
var self = this;
$tw.utils.each(this.widget.children,function(node) {
if(node.renderInDom) {
self.domNode.appendChild(node.renderInDom());
}
});
// Call postRenderInDom if the widget provides it
if(this.widget.postRenderInDom) {
this.widget.postRenderInDom();
}
// Return the dom node
return this.domNode;
} else {
// If we're not generating an element, just render our first child
return this.widget.children[0].renderInDom();
}
};
ElementRenderer.prototype.assignAttributes = function() {
var self = this;
$tw.utils.each(this.widget.attributes,function(v,a) {
if(v !== undefined) {
if($tw.utils.isArray(v)) { // Ahem, could there be arrays other than className?
self.domNode.className = v.join(" ");
} else if (typeof v === "object") { // ...or objects other than style?
for(var p in v) {
self.domNode.style[$tw.utils.unHyphenateCss(p)] = v[p];
}
} else {
// Setting certain attributes can cause a DOM error (eg xmlns on the svg element)
try {
self.domNode.setAttributeNS(null,a,v);
} catch(e) {
}
}
}
});
};
ElementRenderer.prototype.refreshInDom = function(changedTiddlers) {
// Update our attributes if required
var changedAttributes = {};
if($tw.utils.checkDependencies(this.dependencies,changedTiddlers)) {
changedAttributes = this.computeAttributes();
}
// Check if the widget has a refreshInDom method
if(this.widget.refreshInDom) {
// Let the widget refresh itself
this.widget.refreshInDom(changedAttributes,changedTiddlers);
} else {
// If not, assign the attributes and refresh any child nodes
this.assignAttributes();
$tw.utils.each(this.widget.children,function(node) {
if(node.refreshInDom) {
node.refreshInDom(changedTiddlers);
}
});
}
};
exports.element = ElementRenderer
})();