1
0
mirror of https://github.com/Jermolene/TiddlyWiki5 synced 2024-07-07 20:44:23 +00:00
TiddlyWiki5/core/modules/rendertree/renderers/element.js
Jeremy Ruston 551ebdc005 Major refactoring of rendering mechanism
We now use a fake DOM implementation on the server to let us share more
rendering code between the text output vs. DOM output paths.
2013-05-17 10:12:25 +01:00

202 lines
6.0 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.renderInDom = function() {
// Check if our widget is providing an element
if(this.widget.tag) {
// Create the element
this.domNode = this.renderTree.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 and we're in the browser
if($tw.browser && 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
})();