1
0
mirror of https://github.com/Jermolene/TiddlyWiki5 synced 2024-06-25 23:03:15 +00:00

Refactored widget renderers to be hosted within HTML element renderers

This arrangement takes better advantage of the similarities between the
now deleted widget renderer and the element renderer. It also obviates
the need for wrapper elements around every widget.
This commit is contained in:
Jeremy Ruston 2013-01-03 16:27:55 +00:00
parent 2124dd1ac1
commit 6d24cedbcc
28 changed files with 429 additions and 564 deletions

View File

@ -37,8 +37,8 @@ exports.parse = function() {
return [{type: "text", text: this.match[0].substr(1)}]; return [{type: "text", text: this.match[0].substr(1)}];
} else { } else {
return [{ return [{
type: "widget", type: "element",
tag: "link", tag: "$link",
attributes: { attributes: {
to: {type: "string", value: this.match[0]} to: {type: "string", value: this.match[0]}
}, },

View File

@ -40,8 +40,8 @@ exports.parse = function() {
classes = this.match[5]; classes = this.match[5];
// Return the list widget // Return the list widget
var node = { var node = {
type: "widget", type: "element",
tag: "list", tag: "$list",
attributes: { attributes: {
filter: {type: "string", value: filter} filter: {type: "string", value: filter}
}, },

View File

@ -40,8 +40,8 @@ exports.parse = function() {
classes = this.match[5]; classes = this.match[5];
// Return the list widget // Return the list widget
var node = { var node = {
type: "widget", type: "element",
tag: "list", tag: "$list",
attributes: { attributes: {
filter: {type: "string", value: filter} filter: {type: "string", value: filter}
} }

View File

@ -32,9 +32,9 @@ exports.init = function(parser) {
this.parser = parser; this.parser = parser;
// Regexp to match // Regexp to match
if(this.is.block) { if(this.is.block) {
this.matchRegExp = /<(\$)?([A-Za-z]+)(\s*[^>]*?)(\/)?>(\r?\n)/mg; this.matchRegExp = /<([A-Za-z\$]+)(\s*[^>]*?)(\/)?>(\r?\n)/mg;
} else { } else {
this.matchRegExp = /<(\$)?([A-Za-z]+)(\s*[^>]*?)(?:(\/>)|(?:>(\r?\n)?))/mg; this.matchRegExp = /<([A-Za-z\$]+)(\s*[^>]*?)(?:(\/>)|(?:>(\r?\n)?))/mg;
} }
}; };
@ -43,11 +43,10 @@ Parse the most recent match
*/ */
exports.parse = function() { exports.parse = function() {
// Get all the details of the match in case this parser is called recursively // Get all the details of the match in case this parser is called recursively
var isWidget = !!this.match[1], var tagName = this.match[1],
tagName = this.match[2], attributeString = this.match[2],
attributeString = this.match[3], isSelfClosing = !!this.match[3],
isSelfClosing = !!this.match[4], hasLineBreak = !!this.match[4];
hasLineBreak = !!this.match[5];
// Move past the tag name and parameters // Move past the tag name and parameters
this.parser.pos = this.matchRegExp.lastIndex; this.parser.pos = this.matchRegExp.lastIndex;
var reAttr = /\s*([A-Za-z\-_]+)(?:\s*=\s*(?:("[^"]*")|('[^']*')|(\{\{[^\}]*\}\})|([^"'\s]+)))?/mg; var reAttr = /\s*([A-Za-z\-_]+)(?:\s*=\s*(?:("[^"]*")|('[^']*')|(\{\{[^\}]*\}\})|([^"'\s]+)))?/mg;
@ -72,8 +71,8 @@ exports.parse = function() {
attrMatch = reAttr.exec(attributeString); attrMatch = reAttr.exec(attributeString);
} }
// Process the end tag // Process the end tag
if(!isSelfClosing && (isWidget || voidElements.indexOf(tagName) === -1)) { if(!isSelfClosing && voidElements.indexOf(tagName) === -1) {
var reEndString = "(</" + (isWidget ? "\\$" : "") + tagName + ">)", var reEndString = "(</" + $tw.utils.escapeRegExp(tagName) + ">)",
reEnd = new RegExp(reEndString,"mg"), reEnd = new RegExp(reEndString,"mg"),
content; content;
if(hasLineBreak) { if(hasLineBreak) {
@ -90,7 +89,7 @@ exports.parse = function() {
content = []; content = [];
} }
var element = { var element = {
type: isWidget ? "widget" : "element", type: "element",
tag: tagName, tag: tagName,
isBlock: this.is.block || hasLineBreak, isBlock: this.is.block || hasLineBreak,
attributes: attributes, attributes: attributes,

View File

@ -34,8 +34,8 @@ exports.parse = function() {
var text = this.match[1], var text = this.match[1],
link = this.match[2] || text; link = this.match[2] || text;
return [{ return [{
type: "widget", type: "element",
tag: "link", tag: "$link",
attributes: { attributes: {
to: {type: "string", value: link} to: {type: "string", value: link}
}, },

View File

@ -41,6 +41,9 @@ exports.parse = function() {
var node = { var node = {
type: "element", type: "element",
tag: "span", tag: "span",
attributes: {
"class": {type: "string", value: "tw-inline-style"}
},
children: tree children: tree
}; };
if(classString) { if(classString) {

View File

@ -40,8 +40,8 @@ exports.parse = function() {
classes = this.match[5]; classes = this.match[5];
// Return the transclude widget // Return the transclude widget
var node = { var node = {
type: "widget", type: "element",
tag: "transclude", tag: "$transclude",
attributes: { attributes: {
target: {type: "string", value: targetTitle} target: {type: "string", value: targetTitle}
}, },

View File

@ -40,8 +40,8 @@ exports.parse = function() {
classes = this.match[5]; classes = this.match[5];
// Return the transclude widget // Return the transclude widget
var node = { var node = {
type: "widget", type: "element",
tag: "transclude", tag: "$transclude",
attributes: { attributes: {
target: {type: "string", value: targetTitle} target: {type: "string", value: targetTitle}
} }

View File

@ -64,8 +64,8 @@ exports.parse = function() {
} }
} }
return [{ return [{
type: "widget", type: "element",
tag: "link", tag: "$link",
attributes: { attributes: {
to: {type: "string", value: linkText} to: {type: "string", value: linkText}
}, },

View File

@ -12,6 +12,31 @@ Element renderer
/*global $tw: false */ /*global $tw: false */
"use strict"; "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.renderContext,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 Element renderer
*/ */
@ -20,50 +45,87 @@ var ElementRenderer = function(renderTree,renderContext,parseTreeNode) {
this.renderTree = renderTree; this.renderTree = renderTree;
this.renderContext = renderContext; this.renderContext = renderContext;
this.parseTreeNode = parseTreeNode; this.parseTreeNode = parseTreeNode;
// Initialise widget classes
if(!this.widgetClasses) {
ElementRenderer.prototype.widgetClasses = $tw.modules.applyMethods("widget");
}
// Compute our dependencies // Compute our dependencies
this.dependencies = {}; this.dependencies = {};
var self = this; var self = this;
$tw.utils.each(this.parseTreeNode.attributes,function(attribute,name) { $tw.utils.each(this.parseTreeNode.attributes,function(attribute,name) {
if(attribute.type === "indirect") { if(attribute.type === "indirect") {
var tr = $tw.utils.parseTextReference(attribute.textReference); var tr = $tw.utils.parseTextReference(attribute.textReference);
if(tr.title) { self.dependencies[tr.title ? tr.title : renderContext.tiddlerTitle] = true;
self.dependencies[tr.title] = true;
} else {
self.dependencies[renderContext.tiddlerTitle] = true;
}
} }
}); });
// Compute our attributes // Compute our attributes
this.attributes = {};
this.computeAttributes(); this.computeAttributes();
// Create the renderers for the child nodes // Create the parasite widget object if required
this.children = this.renderTree.createRenderers(this.renderContext,this.parseTreeNode.children); 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() { ElementRenderer.prototype.computeAttributes = function() {
this.attributes = {}; var changedAttributes = {};
var self = this; var self = this;
$tw.utils.each(this.parseTreeNode.attributes,function(attribute,name) { $tw.utils.each(this.parseTreeNode.attributes,function(attribute,name) {
if(attribute.type === "indirect") { if(attribute.type === "indirect") {
self.attributes[name] = self.renderTree.wiki.getTextReference(attribute.textReference,self.renderContext.tiddlerTitle); var value = self.renderTree.wiki.getTextReference(attribute.textReference,self.renderContext.tiddlerTitle);
if(self.attributes[name] !== value) {
self.attributes[name] = value;
changedAttributes[name] = true;
}
} else { // String attribute } else { // String attribute
self.attributes[name] = attribute.value; 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) { ElementRenderer.prototype.render = function(type) {
var isHtml = type === "text/html", var isHtml = type === "text/html",
output = [],attr,a,v; output = [],attr,a,v;
if(isHtml) { if(isHtml) {
output.push("<",this.parseTreeNode.tag); output.push("<",this.widget.tag);
if(this.attributes) { if(this.widget.attributes) {
attr = []; attr = [];
for(a in this.attributes) { for(a in this.widget.attributes) {
attr.push(a); attr.push(a);
} }
attr.sort(); attr.sort();
for(a=0; a<attr.length; a++) { for(a=0; a<attr.length; a++) {
v = this.attributes[attr[a]]; v = this.widget.attributes[attr[a]];
if(v !== undefined) { if(v !== undefined) {
if($tw.utils.isArray(v)) { if($tw.utils.isArray(v)) {
v = v.join(" "); v = v.join(" ");
@ -78,19 +140,19 @@ ElementRenderer.prototype.render = function(type) {
} }
} }
} }
if(!this.children || this.children.length === 0) { if(!this.widget.children || this.widget.children.length === 0) {
output.push("/"); output.push("/");
} }
output.push(">"); output.push(">");
} }
if(this.children && this.children.length > 0) { if(this.widget.children && this.widget.children.length > 0) {
$tw.utils.each(this.children,function(node) { $tw.utils.each(this.widget.children,function(node) {
if(node.render) { if(node.render) {
output.push(node.render(type)); output.push(node.render(type));
} }
}); });
if(isHtml) { if(isHtml) {
output.push("</",this.parseTreeNode.tag,">"); output.push("</",this.widget.tag,">");
} }
} }
return output.join(""); return output.join("");
@ -98,25 +160,29 @@ ElementRenderer.prototype.render = function(type) {
ElementRenderer.prototype.renderInDom = function() { ElementRenderer.prototype.renderInDom = function() {
// Create the element // Create the element
this.domNode = document.createElement(this.parseTreeNode.tag); this.domNode = document.createElement(this.widget.tag);
// Assign the attributes // Assign the attributes
this.assignAttributes(); this.assignAttributes();
// Render any child nodes // Render any child nodes
var self = this; var self = this;
$tw.utils.each(this.children,function(node) { $tw.utils.each(this.widget.children,function(node) {
if(node.renderInDom) { if(node.renderInDom) {
self.domNode.appendChild(node.renderInDom()); self.domNode.appendChild(node.renderInDom());
} }
}); });
// Assign any specified event handlers // Assign any specified event handlers
$tw.utils.addEventListeners(this.domNode,this.parseTreeNode.events); $tw.utils.addEventListeners(this.domNode,this.widget.events);
// Call postRenderInDom if the widget provides it
if(this.widget.postRenderInDom) {
this.widget.postRenderInDom();
}
// Return the dom node // Return the dom node
return this.domNode; return this.domNode;
}; };
ElementRenderer.prototype.assignAttributes = function() { ElementRenderer.prototype.assignAttributes = function() {
var self = this; var self = this;
$tw.utils.each(this.attributes,function(v,a) { $tw.utils.each(this.widget.attributes,function(v,a) {
if(v !== undefined) { if(v !== undefined) {
if($tw.utils.isArray(v)) { // Ahem, could there be arrays other than className? if($tw.utils.isArray(v)) { // Ahem, could there be arrays other than className?
self.domNode.className = v.join(" "); self.domNode.className = v.join(" ");
@ -131,19 +197,73 @@ ElementRenderer.prototype.assignAttributes = function() {
}); });
}; };
ElementRenderer.prototype.refreshInDom = function(changes) { ElementRenderer.prototype.refreshInDom = function(changedTiddlers) {
// Check if any of our dependencies have changed // Update our attributes if required
if($tw.utils.checkDependencies(this.dependencies,changes)) { var changedAttributes = {};
// Update our attributes if($tw.utils.checkDependencies(this.dependencies,changedTiddlers)) {
this.computeAttributes(); changedAttributes = this.computeAttributes();
this.assignAttributes();
} }
// Refresh any child nodes // Check if the widget has a refreshInDom method
$tw.utils.each(this.children,function(node) { if(this.widget.refreshInDom) {
if(node.refreshInDom) { // Let the widget refresh itself
node.refreshInDom(changes); 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);
}
});
}
};
ElementRenderer.prototype.getContextTiddlerTitle = function() {
var context = this.renderContext;
while(context) {
if($tw.utils.hop(context,"tiddlerTitle")) {
return context.tiddlerTitle;
} }
}); context = context.parentContext;
}
return undefined;
};
/*
Check for render context recursion by returning true if the members of a proposed new render context are already present in the render context chain
*/
ElementRenderer.prototype.checkContextRecursion = function(newRenderContext) {
var context = this.renderContext;
while(context) {
var match = true;
for(var member in newRenderContext) {
if($tw.utils.hop(newRenderContext,member)) {
if(newRenderContext[member] && newRenderContext[member] !== context[member]) {
match = false;
}
}
}
if(match) {
return true;
}
context = context.parentContext;
}
return false;
};
ElementRenderer.prototype.getContextScopeId = function() {
var guidBits = [],
context = this.renderContext;
while(context) {
$tw.utils.each(context,function(field,name) {
if(name !== "parentContext") {
guidBits.push(name + ":" + field + ";");
}
});
guidBits.push("-");
context = context.parentContext;
}
return $tw.utils.toBase64(guidBits.join(""));
}; };
exports.element = ElementRenderer exports.element = ElementRenderer

View File

@ -1,182 +0,0 @@
/*\
title: $:/core/modules/rendertree/renderers/widget.js
type: application/javascript
module-type: wikirenderer
Widget renderer.
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
/*
Widget renderer
*/
var WidgetRenderer = function(renderTree,renderContext,parseTreeNode) {
// Store state information
this.renderTree = renderTree;
this.renderContext = renderContext;
this.parseTreeNode = parseTreeNode;
// Widget classes
if(!this.widgetClasses) {
WidgetRenderer.prototype.widgetClasses = $tw.modules.applyMethods("widget");
}
// Compute our attributes
this.attributes = {};
this.computeAttributes();
// Create the widget object
var WidgetClass = this.widgetClasses[this.parseTreeNode.tag];
if(WidgetClass) {
this.widget = new WidgetClass(this);
} else {
WidgetClass = this.widgetClasses.error;
if(WidgetClass) {
this.widget = new WidgetClass(this,"Unknown widget '" + this.parseTreeNode.tag + "'");
}
}
};
WidgetRenderer.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.renderContext.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;
};
WidgetRenderer.prototype.hasAttribute = function(name) {
return $tw.utils.hop(this.attributes,name);
};
WidgetRenderer.prototype.getAttribute = function(name,defaultValue) {
if($tw.utils.hop(this.attributes,name)) {
return this.attributes[name];
} else {
return defaultValue;
}
};
WidgetRenderer.prototype.render = function(type) {
// Render the widget if we've got one
if(this.widget) {
if(this.widget.render) {
return this.widget.render(type);
} else if(this.widget.children) {
var output = [];
$tw.utils.each(this.widget.children,function(node) {
if(node.render) {
output.push(node.render(type));
}
});
return output.join("");
}
}
};
WidgetRenderer.prototype.renderInDom = function() {
var self = this;
// Create the wrapper element
this.domNode = document.createElement(this.parseTreeNode.isBlock ? "div" : "span");
this.domNode.setAttribute("data-widget-type",this.parseTreeNode.tag);
this.domNode.setAttribute("data-widget-attr",JSON.stringify(this.attributes));
// Render the widget if we've got one
if(this.widget) {
if(this.widget.renderInDom) {
this.widget.renderInDom(this.domNode);
} else if(this.widget.children) {
// Render any child nodes
$tw.utils.each(this.widget.children,function(node) {
if(node.renderInDom) {
self.domNode.appendChild(node.renderInDom());
}
});
}
// Attach any event handlers
if(this.widget.getEventListeners) {
$tw.utils.addEventListeners(this.domNode,this.widget.getEventListeners());
}
}
// Call the postRenderInDom hook if the widget has one
if(this.widget && this.widget.postRenderInDom) {
this.widget.postRenderInDom();
}
// Return the dom node
return this.domNode;
};
WidgetRenderer.prototype.refreshInDom = function(changedTiddlers) {
// Update our attributes
var changedAttributes = this.computeAttributes();
// Refresh the widget
if(this.widget && this.widget.refreshInDom) {
this.widget.refreshInDom(changedAttributes,changedTiddlers);
}
};
WidgetRenderer.prototype.getContextTiddlerTitle = function() {
var context = this.renderContext;
while(context) {
if($tw.utils.hop(context,"tiddlerTitle")) {
return context.tiddlerTitle;
}
context = context.parentContext;
}
return undefined;
};
/*
Check for render context recursion by returning true if the members of a proposed new render context are already present in the render context chain
*/
WidgetRenderer.prototype.checkContextRecursion = function(newRenderContext) {
var context = this.renderContext;
while(context) {
var match = true;
for(var member in newRenderContext) {
if($tw.utils.hop(newRenderContext,member)) {
if(newRenderContext[member] && newRenderContext[member] !== context[member]) {
match = false;
}
}
}
if(match) {
return true;
}
context = context.parentContext;
}
return false;
};
WidgetRenderer.prototype.getContextScopeId = function() {
var guidBits = [],
context = this.renderContext;
while(context) {
$tw.utils.each(context,function(field,name) {
if(name !== "parentContext") {
guidBits.push(name + ":" + field + ";");
}
});
guidBits.push("-");
context = context.parentContext;
}
return $tw.utils.toBase64(guidBits.join(""));
};
exports.widget = WidgetRenderer
})();

View File

@ -1,56 +0,0 @@
/*\
title: $:/core/modules/widgetbase.js
type: application/javascript
module-type: global
Base class for widgets
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
/*
This constructor is always overridden with a blank constructor, and so shouldn't be used
*/
var WidgetBase = function() {
};
/*
To be overridden by individual widgets
*/
WidgetBase.prototype.init = function(renderer) {
this.renderer = renderer;
};
/*
Default render() method just renders child nodes
*/
WidgetBase.prototype.render = function(type) {
var output = [];
$tw.utils.each(this.children,function(node) {
if(node.render) {
output.push(node.render(type));
}
});
return output.join("");
};
/*
Default renderInDom() method just renders child nodes
*/
WidgetBase.prototype.renderInDom = function(parentElement) {
this.parentElement = parentElement;
// Render any child nodes
$tw.utils.each(this.children,function(node) {
if(node.renderInDom) {
parentElement.appendChild(node.renderInDom());
}
});
};
exports.WidgetBase = WidgetBase;
})();

View File

@ -16,10 +16,10 @@ var ButtonWidget = function(renderer) {
// Save state // Save state
this.renderer = renderer; this.renderer = renderer;
// Generate child nodes // Generate child nodes
this.generateChildNodes(); this.generate();
}; };
ButtonWidget.prototype.generateChildNodes = function() { ButtonWidget.prototype.generate = function() {
// Get the parameters from the attributes // Get the parameters from the attributes
this.message = this.renderer.getAttribute("message"); this.message = this.renderer.getAttribute("message");
this.param = this.renderer.getAttribute("param"); this.param = this.renderer.getAttribute("param");
@ -30,7 +30,7 @@ ButtonWidget.prototype.generateChildNodes = function() {
this.qualifyTiddlerTitles = this.renderer.getAttribute("qualifyTiddlerTitles"); this.qualifyTiddlerTitles = this.renderer.getAttribute("qualifyTiddlerTitles");
this["class"] = this.renderer.getAttribute("class"); this["class"] = this.renderer.getAttribute("class");
// Compose the button // Compose the button
var classes = ["tw-tiddlybutton"]; var classes = ["tw-button"];
if(this["class"]) { if(this["class"]) {
$tw.utils.pushTop(classes,this["class"]); $tw.utils.pushTop(classes,this["class"]);
} }
@ -39,15 +39,11 @@ ButtonWidget.prototype.generateChildNodes = function() {
events.push({name: "mouseover", handlerObject: this, handlerMethod: "handleMouseOverOrOutEvent"}); events.push({name: "mouseover", handlerObject: this, handlerMethod: "handleMouseOverOrOutEvent"});
events.push({name: "mouseout", handlerObject: this, handlerMethod: "handleMouseOverOrOutEvent"}); events.push({name: "mouseout", handlerObject: this, handlerMethod: "handleMouseOverOrOutEvent"});
} }
this.children = this.renderer.renderTree.createRenderers(this.renderer.renderContext,[{ // Set the return element
type: "element", this.tag = "button";
tag: "button", this.attributes ={"class": classes.join(" ")};
attributes: { this.children = this.renderer.renderTree.createRenderers(this.renderer.renderContext,this.renderer.parseTreeNode.children);
"class": {type: "string", value: classes.join(" ")} this.events = events;
},
children: this.renderer.parseTreeNode.children,
events: events
}]);
}; };
ButtonWidget.prototype.dispatchMessage = function(event) { ButtonWidget.prototype.dispatchMessage = function(event) {
@ -99,16 +95,11 @@ ButtonWidget.prototype.handleMouseOverOrOutEvent = function(event) {
ButtonWidget.prototype.refreshInDom = function(changedAttributes,changedTiddlers) { ButtonWidget.prototype.refreshInDom = function(changedAttributes,changedTiddlers) {
// Check if any of our attributes have changed, or if a tiddler we're interested in has changed // Check if any of our attributes have changed, or if a tiddler we're interested in has changed
if(changedAttributes.message || changedAttributes.param || changedAttributes.set || changedAttributes.setTo || changedAttributes.popup || changedAttributes.hover || changedAttributes.qualifyTiddlerTitles || changedAttributes["class"] || (this.set && changedTiddlers[this.set]) || (this.popup && changedTiddlers[this.popup])) { if(changedAttributes.message || changedAttributes.param || changedAttributes.set || changedAttributes.setTo || changedAttributes.popup || changedAttributes.hover || changedAttributes.qualifyTiddlerTitles || changedAttributes["class"] || (this.set && changedTiddlers[this.set]) || (this.popup && changedTiddlers[this.popup])) {
// Remove old child nodes // Regenerate and rerender the widget and replace the existing DOM node
$tw.utils.removeChildren(this.parentElement); this.generate();
// Regenerate and render children var oldDomNode = this.renderer.domNode,
this.generateChildNodes(); newDomNode = this.renderer.renderInDom();
var self = this; oldDomNode.parentNode.replaceChild(newDomNode,oldDomNode);
$tw.utils.each(this.children,function(node) {
if(node.renderInDom) {
self.parentElement.appendChild(node.renderInDom());
}
});
} else { } else {
// We don't need to refresh ourselves, so just refresh any child nodes // We don't need to refresh ourselves, so just refresh any child nodes
$tw.utils.each(this.children,function(node) { $tw.utils.each(this.children,function(node) {

View File

@ -17,23 +17,19 @@ var ErrorWidget = function(renderer,errorMessage) {
this.renderer = renderer; this.renderer = renderer;
this.errorMessage = errorMessage; this.errorMessage = errorMessage;
// Generate child nodes // Generate child nodes
this.generateChildNodes(); this.generate();
}; };
ErrorWidget.prototype.generateChildNodes = function() { ErrorWidget.prototype.generate = function() {
// Create the wrapper node // Set the element details
var node = { this.tag = "span";
type: "element", this.attributes = {
tag: "span", "class": "tw-error-widget"
children: [{ };
this.children = this.renderer.renderTree.createRenderers(this.renderer.renderContext,[{
type: "text", type: "text",
text: this.errorMessage text: this.errorMessage
}] }]);
};
// Set up the attributes for the wrapper element
$tw.utils.addClassToParseTreeNode(node,"tw-error-widget");
// Create the renderers for the wrapper and the children
this.children = this.renderer.renderTree.createRenderers(this.renderer.renderContext,[node]);
}; };
exports.error = ErrorWidget; exports.error = ErrorWidget;

View File

@ -16,10 +16,10 @@ var FieldsWidget = function(renderer) {
// Save state // Save state
this.renderer = renderer; this.renderer = renderer;
// Generate child nodes // Generate child nodes
this.generateChildNodes(); this.generate();
}; };
FieldsWidget.prototype.generateChildNodes = function() { FieldsWidget.prototype.generate = function() {
// Get parameters from our attributes // Get parameters from our attributes
this.tiddlerTitle = this.renderer.getAttribute("tiddler",this.renderer.getContextTiddlerTitle()); this.tiddlerTitle = this.renderer.getAttribute("tiddler",this.renderer.getContextTiddlerTitle());
this.template = this.renderer.getAttribute("template"); this.template = this.renderer.getAttribute("template");
@ -46,14 +46,10 @@ FieldsWidget.prototype.generateChildNodes = function() {
} }
} }
} }
// Create the wrapper node // Set the element
var node = { this.tag = "pre";
type: "element", this.attributes = {
tag: this.renderer.parseTreeNode.isBlock ? "div" : "span", "class": "tw-fields"
children: [{
type: "text",
text: text.join("")
}]
}; };
// Set up the attributes for the wrapper element // Set up the attributes for the wrapper element
var classes = []; var classes = [];
@ -61,16 +57,19 @@ FieldsWidget.prototype.generateChildNodes = function() {
$tw.utils.pushTop(classes,this.renderer.getAttribute("class").split(" ")); $tw.utils.pushTop(classes,this.renderer.getAttribute("class").split(" "));
} }
if(classes.length > 0) { if(classes.length > 0) {
$tw.utils.addClassToParseTreeNode(node,classes.join(" ")); this.attributes["class"] = classes.join(" ");
} }
if(this.renderer.hasAttribute("style")) { if(this.renderer.hasAttribute("style")) {
$tw.utils.addAttributeToParseTreeNode(node,"style",this.renderer.getAttribute("style")); this.attributes.style = this.renderer.getAttribute("style");
} }
if(this.renderer.hasAttribute("tooltip")) { if(this.renderer.hasAttribute("tooltip")) {
$tw.utils.addAttributeToParseTreeNode(node,"title",this.renderer.getAttribute("tooltip")); this.attributes.title = this.renderer.getAttribute("tooltip");
} }
// Create the renderers for the wrapper and the children // Create the renderers for the wrapper and the children
this.children = this.renderer.renderTree.createRenderers(this.renderer.renderContext,[node]); this.children = this.renderer.renderTree.createRenderers(this.renderer.renderContext,[{
type: "text",
text: text.join("")
}]);
}; };
exports.fields = FieldsWidget; exports.fields = FieldsWidget;

View File

@ -21,10 +21,10 @@ var LinkWidget = function(renderer) {
// Save state // Save state
this.renderer = renderer; this.renderer = renderer;
// Generate child nodes // Generate child nodes
this.generateChildNodes(); this.generate();
}; };
LinkWidget.prototype.generateChildNodes = function() { LinkWidget.prototype.generate = function() {
// Get the parameters from the attributes // Get the parameters from the attributes
this.to = this.renderer.getAttribute("to"); this.to = this.renderer.getAttribute("to");
this.hover = this.renderer.getAttribute("hover"); this.hover = this.renderer.getAttribute("hover");
@ -51,16 +51,14 @@ LinkWidget.prototype.generateChildNodes = function() {
events.push({name: "mouseover", handlerObject: this, handlerMethod: "handleMouseOverOrOutEvent"}); events.push({name: "mouseover", handlerObject: this, handlerMethod: "handleMouseOverOrOutEvent"});
events.push({name: "mouseout", handlerObject: this, handlerMethod: "handleMouseOverOrOutEvent"}); events.push({name: "mouseout", handlerObject: this, handlerMethod: "handleMouseOverOrOutEvent"});
} }
this.children = this.renderer.renderTree.createRenderers(this.renderer.renderContext,[{ // Set up the element
type: "element", this.tag = "a";
tag: "a", this.attributes = {
attributes: { href: this.isExternal ? this.to : encodeURIComponent(this.to),
href: {type: "string", value: this.isExternal ? this.to : encodeURIComponent(this.to)}, "class": classes.join(" ")
"class": {type: "string", value: classes.join(" ")} };
}, this.children = this.renderer.renderTree.createRenderers(this.renderer.renderContext,this.renderer.parseTreeNode.children);
children: this.renderer.parseTreeNode.children, this.events = events;
events: events
}]);
}; };
LinkWidget.prototype.handleClickEvent = function(event) { LinkWidget.prototype.handleClickEvent = function(event) {
@ -71,7 +69,7 @@ LinkWidget.prototype.handleClickEvent = function(event) {
$tw.utils.dispatchCustomEvent(event.target,"tw-navigate",{ $tw.utils.dispatchCustomEvent(event.target,"tw-navigate",{
navigateTo: this.to, navigateTo: this.to,
navigateFromNode: this, navigateFromNode: this,
navigateFromClientRect: this.children[0].domNode.getBoundingClientRect() navigateFromClientRect: this.renderer.domNode.getBoundingClientRect()
}); });
event.preventDefault(); event.preventDefault();
return false; return false;
@ -81,10 +79,8 @@ LinkWidget.prototype.handleClickEvent = function(event) {
LinkWidget.prototype.handleMouseOverOrOutEvent = function(event) { LinkWidget.prototype.handleMouseOverOrOutEvent = function(event) {
if(this.hover) { if(this.hover) {
$tw.popup.triggerPopup({ $tw.popup.triggerPopup({
textRef: this.hover, title: this.hover,
domNode: this.children[0].domNode, domNode: this.renderer.domNode,
qualifyTiddlerTitles: this.qualifyHoverTitle,
contextTiddlerTitle: this.renderer.getContextTiddlerTitle(),
wiki: this.renderer.renderTree.wiki wiki: this.renderer.renderTree.wiki
}); });
} }
@ -95,20 +91,15 @@ LinkWidget.prototype.handleMouseOverOrOutEvent = function(event) {
LinkWidget.prototype.refreshInDom = function(changedAttributes,changedTiddlers) { LinkWidget.prototype.refreshInDom = function(changedAttributes,changedTiddlers) {
// Set the class for missing tiddlers // Set the class for missing tiddlers
if(this.targetTitle && changedTiddlers[this.targetTitle]) { if(this.targetTitle && changedTiddlers[this.targetTitle]) {
$tw.utils.toggleClass(this.children[0].domNode,"tw-tiddler-missing",!this.renderer.renderTree.wiki.tiddlerExists(this.targetTitle)); $tw.utils.toggleClass(this.renderer.domNode,"tw-tiddler-missing",!this.renderer.renderTree.wiki.tiddlerExists(this.targetTitle));
} }
// Check if any of our attributes have changed, or if a tiddler we're interested in has changed // Check if any of our attributes have changed, or if a tiddler we're interested in has changed
if(changedAttributes.to || changedAttributes.hover || (this.to && changedTiddlers[this.to]) || (this.hover && changedTiddlers[this.hover])) { if(changedAttributes.to || changedAttributes.hover || (this.to && changedTiddlers[this.to]) || (this.hover && changedTiddlers[this.hover])) {
// Remove old child nodes // Regenerate and rerender the widget and replace the existing DOM node
$tw.utils.removeChildren(this.parentElement); this.generate();
// Regenerate and render children var oldDomNode = this.renderer.domNode,
this.generateChildNodes(); newDomNode = this.renderer.renderInDom();
var self = this; oldDomNode.parentNode.replaceChild(newDomNode,oldDomNode);
$tw.utils.each(this.children,function(node) {
if(node.renderInDom) {
self.parentElement.appendChild(node.renderInDom());
}
});
} else { } else {
// We don't need to refresh ourselves, so just refresh any child nodes // We don't need to refresh ourselves, so just refresh any child nodes
$tw.utils.each(this.children,function(node) { $tw.utils.each(this.children,function(node) {

View File

@ -15,8 +15,8 @@ The list widget
var ListWidget = function(renderer) { var ListWidget = function(renderer) {
// Save state // Save state
this.renderer = renderer; this.renderer = renderer;
// Generate child nodes // Generate widget elements
this.generateChildNodes(); this.generate();
}; };
/* /*
@ -30,7 +30,7 @@ var typeMappings = {
shadows: "[is[shadow]sort[title]]" shadows: "[is[shadow]sort[title]]"
}; };
ListWidget.prototype.generateChildNodes = function() { ListWidget.prototype.generate = function() {
// Get our attributes // Get our attributes
this.itemClass = this.renderer.getAttribute("itemClass"); this.itemClass = this.renderer.getAttribute("itemClass");
this.template = this.renderer.getAttribute("template"); this.template = this.renderer.getAttribute("template");
@ -50,15 +50,11 @@ ListWidget.prototype.generateChildNodes = function() {
} }
} }
// Create the list frame element // Create the list frame element
var classes = ["tw-list-frame"]; this.tag = this.renderer.parseTreeNode.isBlock ? "div" : "span";
this.children = this.renderer.renderTree.createRenderers(this.renderer.renderContext,[{ this.attributes = {
type: "element", "class": "tw-list-frame"
tag: this.renderer.parseTreeNode.isBlock ? "div" : "span", };
attributes: { this.children = this.renderer.renderTree.createRenderers(this.renderer.renderContext,listMembers);
"class": {type: "string", value: classes.join(" ")}
},
children: listMembers
}]);
}; };
ListWidget.prototype.getTiddlerList = function() { ListWidget.prototype.getTiddlerList = function() {
@ -138,8 +134,8 @@ ListWidget.prototype.createListElementMacro = function(title) {
} else { } else {
// Use default content // Use default content
templateTree = [{ templateTree = [{
type: "widget", type: "element",
tag: "view", tag: "$view",
attributes: { attributes: {
field: {type: "string", value: "title"}, field: {type: "string", value: "title"},
format: {type: "string", value: "link"} format: {type: "string", value: "link"}
@ -147,10 +143,10 @@ ListWidget.prototype.createListElementMacro = function(title) {
}]; }];
} }
} }
// Create the tiddler macro // Create the transclude widget
return { return {
type: "widget", type: "element",
tag: "transclude", tag: "$transclude",
attributes: { attributes: {
target: {type: "string", value: title}, target: {type: "string", value: title},
template: {type: "string", value: template} template: {type: "string", value: template}
@ -164,11 +160,11 @@ Remove a list element from the list, along with the attendant DOM nodes
*/ */
ListWidget.prototype.removeListElement = function(index) { ListWidget.prototype.removeListElement = function(index) {
// Get the list element // Get the list element
var listElement = this.children[0].children[index]; var listElement = this.children[index];
// Remove the DOM node // Remove the DOM node
listElement.domNode.parentNode.removeChild(listElement.domNode); listElement.domNode.parentNode.removeChild(listElement.domNode);
// Then delete the actual renderer node // Then delete the actual renderer node
this.children[0].children.splice(index,1); this.children.splice(index,1);
}; };
/* /*
@ -177,8 +173,8 @@ startIndex: index to start search (use zero to search from the top)
title: tiddler title to seach for title: tiddler title to seach for
*/ */
ListWidget.prototype.findListElementByTitle = function(startIndex,title) { ListWidget.prototype.findListElementByTitle = function(startIndex,title) {
while(startIndex < this.children[0].children.length) { while(startIndex < this.children.length) {
if(this.children[0].children[startIndex].children[0].attributes.target === title) { if(this.children[startIndex].widget.children[0].attributes.target === title) {
return startIndex; return startIndex;
} }
startIndex++; startIndex++;
@ -189,16 +185,11 @@ ListWidget.prototype.findListElementByTitle = function(startIndex,title) {
ListWidget.prototype.refreshInDom = function(changedAttributes,changedTiddlers) { ListWidget.prototype.refreshInDom = function(changedAttributes,changedTiddlers) {
// Reexecute the widget if any of our attributes have changed // Reexecute the widget if any of our attributes have changed
if(changedAttributes.itemClass || changedAttributes.template || changedAttributes.editTemplate || changedAttributes.emptyMessage || changedAttributes.type || changedAttributes.filter || changedAttributes.template) { if(changedAttributes.itemClass || changedAttributes.template || changedAttributes.editTemplate || changedAttributes.emptyMessage || changedAttributes.type || changedAttributes.filter || changedAttributes.template) {
// Remove old child nodes // Regenerate and rerender the widget and replace the existing DOM node
$tw.utils.removeChildren(this.parentElement); this.generate();
// Regenerate and render children var oldDomNode = this.renderer.domNode,
this.generateChildNodes(); newDomNode = this.renderer.renderInDom();
var self = this; oldDomNode.parentNode.replaceChild(newDomNode,oldDomNode);
$tw.utils.each(this.children,function(node) {
if(node.renderInDom) {
self.parentElement.appendChild(node.renderInDom());
}
});
} else { } else {
// Handle any changes to the list, and refresh any nodes we're reusing // Handle any changes to the list, and refresh any nodes we're reusing
this.handleListChanges(changedTiddlers); this.handleListChanges(changedTiddlers);
@ -207,8 +198,7 @@ ListWidget.prototype.refreshInDom = function(changedAttributes,changedTiddlers)
ListWidget.prototype.handleListChanges = function(changedTiddlers) { ListWidget.prototype.handleListChanges = function(changedTiddlers) {
var t, var t,
prevListLength = this.list.length, prevListLength = this.list.length;
frame = this.children[0];
// Get the list of tiddlers, having saved the previous length // Get the list of tiddlers, having saved the previous length
this.getTiddlerList(); this.getTiddlerList();
// Check if the list is empty // Check if the list is empty
@ -228,10 +218,10 @@ ListWidget.prototype.handleListChanges = function(changedTiddlers) {
this.removeListElement(t); this.removeListElement(t);
} }
// Insert the empty message // Insert the empty message
frame.children = this.renderer.renderTree.createRenderers(this.renderer.renderContext,[this.getEmptyMessage()]); this.children = this.renderer.renderTree.createRenderers(this.renderer.renderContext,[this.getEmptyMessage()]);
$tw.utils.each(frame.children,function(node) { $tw.utils.each(this.children,function(node) {
if(node.renderInDom) { if(node.renderInDom) {
frame.domNode.appendChild(node.renderInDom()); this.renderer.domNode.appendChild(node.renderInDom());
} }
}); });
return; return;
@ -248,19 +238,19 @@ ListWidget.prototype.handleListChanges = function(changedTiddlers) {
var index = this.findListElementByTitle(t,this.list[t]); var index = this.findListElementByTitle(t,this.list[t]);
if(index === undefined) { if(index === undefined) {
// The list element isn't there, so we need to insert it // The list element isn't there, so we need to insert it
frame.children.splice(t,0,this.renderer.renderTree.createRenderer(this.renderer.renderContext,this.createListElement(this.list[t]))); this.children.splice(t,0,this.renderer.renderTree.createRenderer(this.renderer.renderContext,this.createListElement(this.list[t])));
frame.domNode.insertBefore(frame.children[t].renderInDom(),frame.domNode.childNodes[t]); this.renderer.domNode.insertBefore(this.children[t].renderInDom(),this.renderer.domNode.childNodes[t]);
} else { } else {
// Delete any list elements preceding the one we want // Delete any list elements preceding the one we want
for(var n=index-1; n>=t; n--) { for(var n=index-1; n>=t; n--) {
this.removeListElement(n); this.removeListElement(n);
} }
// Refresh the node we're reusing // Refresh the node we're reusing
frame.children[t].refreshInDom(changedTiddlers); this.children[t].refreshInDom(changedTiddlers);
} }
} }
// Remove any left over elements // Remove any left over elements
for(t=frame.children.length-1; t>=this.list.length; t--) { for(t=this.children.length-1; t>=this.list.length; t--) {
this.removeListElement(t); this.removeListElement(t);
} }
}; };

View File

@ -16,19 +16,20 @@ var NavigatorWidget = function(renderer) {
// Save state // Save state
this.renderer = renderer; this.renderer = renderer;
// Generate child nodes // Generate child nodes
this.generateChildNodes(); this.generate();
}; };
NavigatorWidget.prototype.generateChildNodes = function() { NavigatorWidget.prototype.generate = function() {
// Get our parameters // Get our parameters
this.storyTitle = this.renderer.getAttribute("story"); this.storyTitle = this.renderer.getAttribute("story");
this.historyTitle = this.renderer.getAttribute("history"); this.historyTitle = this.renderer.getAttribute("history");
// Render our children // Set the element
this.tag = "div";
this.attributes = {
"class": "tw-navigator"
};
this.children = this.renderer.renderTree.createRenderers(this.renderer.renderContext,this.renderer.parseTreeNode.children); this.children = this.renderer.renderTree.createRenderers(this.renderer.renderContext,this.renderer.parseTreeNode.children);
}; this.events = [
NavigatorWidget.prototype.getEventListeners = function() {
return [
{name: "tw-navigate", handlerObject: this, handlerMethod: "handleNavigateEvent"}, {name: "tw-navigate", handlerObject: this, handlerMethod: "handleNavigateEvent"},
{name: "tw-EditTiddler", handlerObject: this, handlerMethod: "handleEditTiddlerEvent"}, {name: "tw-EditTiddler", handlerObject: this, handlerMethod: "handleEditTiddlerEvent"},
{name: "tw-SaveTiddler", handlerObject: this, handlerMethod: "handleSaveTiddlerEvent"}, {name: "tw-SaveTiddler", handlerObject: this, handlerMethod: "handleSaveTiddlerEvent"},

View File

@ -16,10 +16,10 @@ var RevealWidget = function(renderer) {
// Save state // Save state
this.renderer = renderer; this.renderer = renderer;
// Generate child nodes // Generate child nodes
this.generateChildNodes(); this.generate();
}; };
RevealWidget.prototype.generateChildNodes = function() { RevealWidget.prototype.generate = function() {
// Get the parameters from the attributes // Get the parameters from the attributes
this.state = this.renderer.getAttribute("state"); this.state = this.renderer.getAttribute("state");
this.type = this.renderer.getAttribute("type"); this.type = this.renderer.getAttribute("type");
@ -34,26 +34,27 @@ RevealWidget.prototype.generateChildNodes = function() {
this.stateTitle = this.stateTitle + "-" + this.renderer.getContextScopeId(); this.stateTitle = this.stateTitle + "-" + this.renderer.getContextScopeId();
} }
this.readState(); this.readState();
// Compose the node // Set up the element attributes
var node = { var classes = ["tw-reveal"],
type: "element", styles = [];
tag: "div",
children: this.isOpen ? this.renderer.parseTreeNode.children : [],
events: [{name: "click", handlerObject: this, handlerMethod: "handleClickEvent"}]
};
$tw.utils.addClassToParseTreeNode(node,"tw-reveal");
if(this["class"]) { if(this["class"]) {
$tw.utils.addClassToParseTreeNode(node,this["class"].join(" ")); $tw.utils.pushTop(classes,this["class"]);
} }
switch(this.type) { switch(this.type) {
case "popup": case "popup":
$tw.utils.addStyleToParseTreeNode(node,"position","absolute"); styles.push("position:absolute;");
$tw.utils.addClassToParseTreeNode(node,"tw-popup"); classes.push("tw-popup");
break; break;
} }
$tw.utils.addStyleToParseTreeNode(node,"display",this.isOpen ? (this.isBlock ? "block" : "inline") : "none"); styles.push("display:" + (this.isOpen ? (this.renderer.parseTreeNode.isBlock ? "block" : "inline") : "none") + ";");
// Return the node // Set the element
this.children = this.renderer.renderTree.createRenderers(this.renderer.renderContext,[node]); this.tag = "div";
this.attributes = {
"class": classes.join(" "),
style: styles.join("")
};
this.children = this.renderer.renderTree.createRenderers(this.renderer.renderContext,this.isOpen ? this.renderer.parseTreeNode.children : []);
this.events = [{name: "click", handlerObject: this, handlerMethod: "handleClickEvent"}];
}; };
/* /*
@ -121,25 +122,20 @@ RevealWidget.prototype.handleClickEvent = function(event) {
RevealWidget.prototype.refreshInDom = function(changedAttributes,changedTiddlers) { RevealWidget.prototype.refreshInDom = function(changedAttributes,changedTiddlers) {
// Check if any of our attributes have changed, or if a tiddler we're interested in has changed // Check if any of our attributes have changed, or if a tiddler we're interested in has changed
if(changedAttributes.state || changedAttributes.type || changedAttributes.text || changedAttributes.position || changedAttributes["default"] || changedAttributes.qualifyTiddlerTitles || changedAttributes["class"]) { if(changedAttributes.state || changedAttributes.type || changedAttributes.text || changedAttributes.position || changedAttributes["default"] || changedAttributes.qualifyTiddlerTitles || changedAttributes["class"]) {
// Remove old child nodes // Regenerate and rerender the widget and replace the existing DOM node
$tw.utils.removeChildren(this.parentElement); this.generate();
// Regenerate and render children var oldDomNode = this.renderer.domNode,
this.generateChildNodes(); newDomNode = this.renderer.renderInDom();
var self = this; oldDomNode.parentNode.replaceChild(newDomNode,oldDomNode);
$tw.utils.each(this.children,function(node) {
if(node.renderInDom) {
self.parentElement.appendChild(node.renderInDom());
}
});
} else { } else {
var needChildrenRefresh = true; // Avoid refreshing the children nodes if we don't need to var needChildrenRefresh = true; // Avoid refreshing the children nodes if we don't need to
// Get the open state // Get the open state
this.readState(); this.readState();
// Construct the child nodes if required // Construct the child nodes if required
if(this.isOpen && this.children[0].children.length === 0) { if(this.isOpen && this.children.length === 0) {
this.children[0].children = this.renderer.renderTree.createRenderers(this.renderer.renderContext,this.renderer.parseTreeNode.children); this.children = this.renderer.renderTree.createRenderers(this.renderer.renderContext,this.renderer.parseTreeNode.children);
var parentNode = this.children[0].domNode; var parentNode = this.renderer.domNode;
$tw.utils.each(this.children[0].children,function(child) { $tw.utils.each(this.children,function(child) {
parentNode.appendChild(child.renderInDom()); parentNode.appendChild(child.renderInDom());
}); });
needChildrenRefresh = false; needChildrenRefresh = false;
@ -153,42 +149,44 @@ RevealWidget.prototype.refreshInDom = function(changedAttributes,changedTiddlers
}); });
} }
// Set the visibility of the children // Set the visibility of the children
this.children[0].domNode.style.display = this.isOpen ? (this.isBlock ? "block" : "inline") : "none"; this.renderer.domNode.style.display = this.isOpen ? (this.renderer.parseTreeNode.isBlock ? "block" : "inline") : "none";
} }
// Position the content if required // Position the content if required
this.postRenderInDom(); if(this.isOpen) {
this.postRenderInDom();
}
}; };
RevealWidget.prototype.postRenderInDom = function() { RevealWidget.prototype.postRenderInDom = function() {
switch(this.type) { switch(this.type) {
case "popup": case "popup":
if(this.isOpen) { if(this.isOpen) {
this.children[0].domNode.style.position = "absolute"; this.renderer.domNode.style.position = "absolute";
this.children[0].domNode.style.zIndex = "1000"; this.renderer.domNode.style.zIndex = "1000";
switch(this.position) { switch(this.position) {
case "left": case "left":
this.children[0].domNode.style.left = (this.popup.left - this.children[0].domNode.offsetWidth) + "px"; this.renderer.domNode.style.left = (this.popup.left - this.renderer.domNode.offsetWidth) + "px";
this.children[0].domNode.style.top = this.popup.top + "px"; this.renderer.domNode.style.top = this.popup.top + "px";
break; break;
case "above": case "above":
this.children[0].domNode.style.left = this.popup.left + "px"; this.renderer.domNode.style.left = this.popup.left + "px";
this.children[0].domNode.style.top = (this.popup.top - this.children[0].domNode.offsetHeight) + "px"; this.renderer.domNode.style.top = (this.popup.top - this.renderer.domNode.offsetHeight) + "px";
break; break;
case "aboveright": case "aboveright":
this.children[0].domNode.style.left = (this.popup.left + this.popup.width) + "px"; this.renderer.domNode.style.left = (this.popup.left + this.popup.width) + "px";
this.children[0].domNode.style.top = (this.popup.top + this.popup.height - this.children[0].domNode.offsetHeight) + "px"; this.renderer.domNode.style.top = (this.popup.top + this.popup.height - this.renderer.domNode.offsetHeight) + "px";
break; break;
case "right": case "right":
this.children[0].domNode.style.left = (this.popup.left + this.popup.width) + "px"; this.renderer.domNode.style.left = (this.popup.left + this.popup.width) + "px";
this.children[0].domNode.style.top = this.popup.top + "px"; this.renderer.domNode.style.top = this.popup.top + "px";
break; break;
case "belowleft": case "belowleft":
this.children[0].domNode.style.left = (this.popup.left + this.popup.width - this.children[0].domNode.offsetWidth) + "px"; this.renderer.domNode.style.left = (this.popup.left + this.popup.width - this.renderer.domNode.offsetWidth) + "px";
this.children[0].domNode.style.top = (this.popup.top + this.popup.height) + "px"; this.renderer.domNode.style.top = (this.popup.top + this.popup.height) + "px";
break; break;
default: // Below default: // Below
this.children[0].domNode.style.left = this.popup.left + "px"; this.renderer.domNode.style.left = this.popup.left + "px";
this.children[0].domNode.style.top = (this.popup.top + this.popup.height) + "px"; this.renderer.domNode.style.top = (this.popup.top + this.popup.height) + "px";
break; break;
} }
} }

View File

@ -12,7 +12,7 @@ Attributes:
The simplest case is to just supply a target tiddler: The simplest case is to just supply a target tiddler:
{{{ {{{
<_transclude target="Foo"/> <$transclude target="Foo"/>
}}} }}}
This will render the tiddler Foo within the current tiddler. If the tiddler Foo includes This will render the tiddler Foo within the current tiddler. If the tiddler Foo includes
@ -24,7 +24,7 @@ widget are those of the tiddler doing the transcluding, then you can instead spe
as a template: as a template:
{{{ {{{
<_transclude template="Foo"/> <$transclude template="Foo"/>
}}} }}}
The effect is the same as the previous example: the text of the tiddler Foo is rendered. The The effect is the same as the previous example: the text of the tiddler Foo is rendered. The
@ -33,7 +33,7 @@ difference is that the view widget will access the fields of the tiddler doing t
The `target` and `template` attributes may be combined: The `target` and `template` attributes may be combined:
{{{ {{{
<_transclude template="Bar" target="Foo"/> <$transclude template="Bar" target="Foo"/>
}}} }}}
Here, the text of the tiddler `Bar` will be transcluded, with the widgets within it accessing the fields Here, the text of the tiddler `Bar` will be transcluded, with the widgets within it accessing the fields
@ -50,13 +50,14 @@ var TranscludeWidget = function(renderer) {
// Save state // Save state
this.renderer = renderer; this.renderer = renderer;
// Generate child nodes // Generate child nodes
this.generateChildNodes(); this.generate();
}; };
TranscludeWidget.prototype.generateChildNodes = function() { TranscludeWidget.prototype.generate = function() {
var tr, templateParseTree, templateTiddler; var tr, templateParseTree, templateTiddler;
// Get the render target details // Get the render target details
this.targetTitle = this.renderer.getAttribute("target",this.renderer.getContextTiddlerTitle()); this.targetTitle = this.renderer.getAttribute("target",this.renderer.getContextTiddlerTitle());
this.targetField = this.renderer.getAttribute("field","text");
// Get the render tree for the template // Get the render tree for the template
this.templateTitle = undefined; this.templateTitle = undefined;
if(this.renderer.parseTreeNode.children && this.renderer.parseTreeNode.children.length > 0) { if(this.renderer.parseTreeNode.children && this.renderer.parseTreeNode.children.length > 0) {
@ -71,40 +72,47 @@ TranscludeWidget.prototype.generateChildNodes = function() {
})) { })) {
templateParseTree = [{type: "text", text: "Tiddler recursion error in transclude widget"}]; templateParseTree = [{type: "text", text: "Tiddler recursion error in transclude widget"}];
} else { } else {
var parser = this.renderer.renderTree.wiki.parseTiddler(this.templateTitle,{parseAsInline: !this.renderer.parseTreeNode.isBlock}); var parser;
if(this.targetField === "text") {
parser = this.renderer.renderTree.wiki.parseTiddler(this.templateTitle,{parseAsInline: !this.renderer.parseTreeNode.isBlock})
} else {
var tiddler = this.renderer.renderTree.wiki.getTiddler(this.targetTitle),
text = tiddler ? tiddler.fields[this.targetField] : "";
if(text === undefined) {
text = ""
}
parser = this.renderer.renderTree.wiki.parseText("text/vnd.tiddlywiki",text,{parseAsInline: !this.renderer.parseTreeNode.isBlock});
}
templateParseTree = parser ? parser.tree : []; templateParseTree = parser ? parser.tree : [];
} }
} }
// Create the wrapper node
var node = {
type: "element",
tag: this.renderer.parseTreeNode.isBlock ? "div" : "span",
children: templateParseTree
};
// Set up the attributes for the wrapper element // Set up the attributes for the wrapper element
var classes = []; var classes = ["tw-transclude"];
if(this.renderer.hasAttribute("class")) { if(this.renderer.hasAttribute("class")) {
$tw.utils.pushTop(classes,this.renderer.getAttribute("class").split(" ")); $tw.utils.pushTop(classes,this.renderer.getAttribute("class").split(" "));
} }
if(!this.renderer.renderTree.wiki.tiddlerExists(this.targetTitle)) { if(!this.renderer.renderTree.wiki.tiddlerExists(this.targetTitle)) {
$tw.utils.pushTop(classes,"tw-tiddler-missing"); $tw.utils.pushTop(classes,"tw-tiddler-missing");
} }
if(classes.length > 0) {
$tw.utils.addClassToParseTreeNode(node,classes.join(" "));
}
if(this.renderer.hasAttribute("style")) {
$tw.utils.addAttributeToParseTreeNode(node,"style",this.renderer.getAttribute("style"));
}
if(this.renderer.hasAttribute("tooltip")) {
$tw.utils.addAttributeToParseTreeNode(node,"title",this.renderer.getAttribute("tooltip"));
}
// Create the renderers for the wrapper and the children // Create the renderers for the wrapper and the children
var newRenderContext = { var newRenderContext = {
tiddlerTitle: this.targetTitle, tiddlerTitle: this.targetTitle,
templateTitle: this.templateTitle, templateTitle: this.templateTitle,
parentContext: this.renderer.renderContext parentContext: this.renderer.renderContext
}; };
this.children = this.renderer.renderTree.createRenderers(newRenderContext,[node]); // Set the element
this.tag = this.renderer.parseTreeNode.isBlock ? "div" : "span";
this.attributes = {};
if(classes.length > 0) {
this.attributes["class"] = classes.join(" ");
}
if(this.renderer.hasAttribute("style")) {
this.attributes.style = this.renderer.getAttribute("style");
}
if(this.renderer.hasAttribute("tooltip")) {
this.attributes.title = this.renderer.getAttribute("tooltip");
}
this.children = this.renderer.renderTree.createRenderers(newRenderContext,templateParseTree);
}; };
TranscludeWidget.prototype.refreshInDom = function(changedAttributes,changedTiddlers) { TranscludeWidget.prototype.refreshInDom = function(changedAttributes,changedTiddlers) {
@ -114,16 +122,11 @@ TranscludeWidget.prototype.refreshInDom = function(changedAttributes,changedTidd
} }
// Check if any of our attributes have changed, or if a tiddler we're interested in has changed // Check if any of our attributes have changed, or if a tiddler we're interested in has changed
if(changedAttributes.target || changedAttributes.template || (this.targetTitle && changedTiddlers[this.targetTitle]) || (this.templateTitle && changedTiddlers[this.templateTitle])) { if(changedAttributes.target || changedAttributes.template || (this.targetTitle && changedTiddlers[this.targetTitle]) || (this.templateTitle && changedTiddlers[this.templateTitle])) {
// Remove old child nodes // Regenerate and rerender the widget and replace the existing DOM node
$tw.utils.removeChildren(this.parentElement); this.generate();
// Regenerate and render children var oldDomNode = this.renderer.domNode,
this.generateChildNodes(); newDomNode = this.renderer.renderInDom();
var self = this; oldDomNode.parentNode.replaceChild(newDomNode,oldDomNode);
$tw.utils.each(this.children,function(node) {
if(node.renderInDom) {
self.parentElement.appendChild(node.renderInDom());
}
});
} else { } else {
// We don't need to refresh ourselves, so just refresh any child nodes // We don't need to refresh ourselves, so just refresh any child nodes
$tw.utils.each(this.children,function(node) { $tw.utils.each(this.children,function(node) {

View File

@ -31,28 +31,28 @@ TextViewer.prototype.render = function() {
if(this.value !== undefined && this.value !== null) { if(this.value !== undefined && this.value !== null) {
value = this.value; value = this.value;
} }
return this.viewWidget.renderer.renderTree.createRenderers(this.viewWidget.renderer.renderContext,[{ // Set the element details
this.viewWidget.tag = "span";
this.viewWidget.attributes = {};
this.viewWidget.children = this.viewWidget.renderer.renderTree.createRenderers(this.viewWidget.renderer.renderContext,[{
type: "text", type: "text",
text: value text: value
}]); }]);
}; };
// We'll cache the available field viewers here
var fieldViewers = undefined;
var ViewWidget = function(renderer) { var ViewWidget = function(renderer) {
// Save state // Save state
this.renderer = renderer; this.renderer = renderer;
// Initialise the field viewers if they've not been done already // Initialise the field viewers if they've not been done already
if(!fieldViewers) { if(!this.fieldViewers) {
fieldViewers = {text: TextViewer}; // Start with the built-in text viewer ViewWidget.prototype.fieldViewers = {text: TextViewer}; // Start with the built-in text viewer
$tw.modules.applyMethods("newfieldviewer",fieldViewers); $tw.modules.applyMethods("fieldviewer",this.fieldViewers);
} }
// Generate child nodes // Generate child nodes
this.generateChildNodes(); this.generate();
}; };
ViewWidget.prototype.generateChildNodes = function() { ViewWidget.prototype.generate = function() {
// Get parameters from our attributes // Get parameters from our attributes
this.tiddlerTitle = this.renderer.getAttribute("tiddler",this.renderer.getContextTiddlerTitle()); this.tiddlerTitle = this.renderer.getAttribute("tiddler",this.renderer.getContextTiddlerTitle());
this.fieldName = this.renderer.getAttribute("field","text"); this.fieldName = this.renderer.getAttribute("field","text");
@ -82,13 +82,13 @@ ViewWidget.prototype.generateChildNodes = function() {
} }
} }
// Choose the viewer to use // Choose the viewer to use
var Viewer = fieldViewers.text; var Viewer = this.fieldViewers.text;
if($tw.utils.hop(fieldViewers,this.format)) { if($tw.utils.hop(this.fieldViewers,this.format)) {
Viewer = fieldViewers[this.format]; Viewer = this.fieldViewers[this.format];
} }
this.viewer = new Viewer(this,tiddler,this.fieldName,value); this.viewer = new Viewer(this,tiddler,this.fieldName,value);
// Ask the viewer to create the children // Ask the viewer to create the widget element
this.children = this.viewer.render(); this.viewer.render();
}; };
ViewWidget.prototype.refreshInDom = function(changedAttributes,changedTiddlers) { ViewWidget.prototype.refreshInDom = function(changedAttributes,changedTiddlers) {
@ -96,14 +96,11 @@ ViewWidget.prototype.refreshInDom = function(changedAttributes,changedTiddlers)
if(changedAttributes.tiddler || changedAttributes.field || changedAttributes.format || (this.tiddlerTitle && changedTiddlers[this.tiddlerTitle])) { if(changedAttributes.tiddler || changedAttributes.field || changedAttributes.format || (this.tiddlerTitle && changedTiddlers[this.tiddlerTitle])) {
// Remove old child nodes // Remove old child nodes
$tw.utils.removeChildren(this.parentElement); $tw.utils.removeChildren(this.parentElement);
// Regenerate and render children // Regenerate and rerender the widget and replace the existing DOM node
this.generateChildNodes(); this.generate();
var self = this; var oldDomNode = this.renderer.domNode,
$tw.utils.each(this.children,function(node) { newDomNode = this.renderer.renderInDom();
if(node.renderInDom) { oldDomNode.parentNode.replaceChild(newDomNode,oldDomNode);
self.parentElement.appendChild(node.renderInDom());
}
});
} else { } else {
// We don't need to refresh ourselves, so just refresh any child nodes // We don't need to refresh ourselves, so just refresh any child nodes
$tw.utils.each(this.children,function(node) { $tw.utils.each(this.children,function(node) {

View File

@ -1,7 +1,7 @@
/*\ /*\
title: $:/core/modules/widgets/view/viewers/date.js title: $:/core/modules/widgets/view/viewers/date.js
type: application/javascript type: application/javascript
module-type: newfieldviewer module-type: fieldviewer
A viewer for viewing tiddler fields as a date A viewer for viewing tiddler fields as a date
@ -25,7 +25,12 @@ DateViewer.prototype.render = function() {
if(this.value !== undefined) { if(this.value !== undefined) {
value = $tw.utils.formatDateString(this.value,template); value = $tw.utils.formatDateString(this.value,template);
} }
return this.viewWidget.renderer.renderTree.createRenderers(this.viewWidget.renderer.renderContext,[{ // Set the element details
this.viewWidget.tag = "span";
this.viewWidget.attributes = {
"class": "tw-view-date"
};
this.viewWidget.children = this.viewWidget.renderer.renderTree.createRenderers(this.viewWidget.renderer.renderContext,[{
type: "text", type: "text",
text: value text: value
}]); }]);

View File

@ -1,7 +1,7 @@
/*\ /*\
title: $:/core/modules/widgets/view/viewers/htmlencoded.js title: $:/core/modules/widgets/view/viewers/htmlencoded.js
type: application/javascript type: application/javascript
module-type: newfieldviewer module-type: fieldviewer
A viewer for viewing tiddler fields as HTML encoded text A viewer for viewing tiddler fields as HTML encoded text
@ -28,7 +28,12 @@ HtmlEncodedViewer.prototype.render = function() {
if(this.value !== undefined && this.value !== null) { if(this.value !== undefined && this.value !== null) {
value = this.value; value = this.value;
} }
return this.viewWidget.renderer.renderTree.createRenderers(this.viewWidget.renderer.renderContext,[{ // Set the element details
this.viewWidget.tag = "span";
this.viewWidget.attributes = {
"class": "tw-view-htmlencoded"
};
this.viewWidget.children = this.viewWidget.renderer.renderTree.createRenderers(this.viewWidget.renderer.renderContext,[{
type: "text", type: "text",
text: $tw.utils.htmlEncode(value) text: $tw.utils.htmlEncode(value)
}]); }]);

View File

@ -1,7 +1,7 @@
/*\ /*\
title: $:/core/modules/widgets/view/viewers/htmlwikified.js title: $:/core/modules/widgets/view/viewers/htmlwikified.js
type: application/javascript type: application/javascript
module-type: newfieldviewer module-type: fieldviewer
A viewer for viewing tiddler fields as a textual HTML representation of the wikified text A viewer for viewing tiddler fields as a textual HTML representation of the wikified text
@ -22,16 +22,15 @@ var HtmlWikifiedViewer = function(viewWidget,tiddler,field,value) {
HtmlWikifiedViewer.prototype.render = function() { HtmlWikifiedViewer.prototype.render = function() {
// Parse the field text // Parse the field text
var text = this.viewWidget.renderer.renderTree.wiki.renderText("text/html","text/vnd.tiddlywiki",this.value); var text = this.viewWidget.renderer.renderTree.wiki.renderText("text/html","text/vnd.tiddlywiki",this.value);
// Create a node containing the HTML representation of the field // Set the element details
var node = { this.viewWidget.tag = "pre";
type: "element", this.viewWidget.attributes = {
tag: "pre", "class": "tw-view-htmlwikified"
children: [{ };
this.viewWidget.children = this.viewWidget.renderer.renderTree.createRenderers(this.viewWidget.renderer.renderContext,[{
type: "text", type: "text",
text: text text: text
}] }]);
};
return this.viewWidget.renderer.renderTree.createRenderers(this.viewWidget.renderer.renderContext,[node]);
}; };
exports.htmlwikified = HtmlWikifiedViewer; exports.htmlwikified = HtmlWikifiedViewer;

View File

@ -1,7 +1,7 @@
/*\ /*\
title: $:/core/modules/widgets/view/viewers/jsencoded.js title: $:/core/modules/widgets/view/viewers/jsencoded.js
type: application/javascript type: application/javascript
module-type: newfieldviewer module-type: fieldviewer
A viewer for viewing tiddler fields as JavaScript stringified text A viewer for viewing tiddler fields as JavaScript stringified text
@ -28,10 +28,15 @@ JsEncodedViewer.prototype.render = function() {
if(this.value !== undefined && this.value !== null) { if(this.value !== undefined && this.value !== null) {
value = this.value; value = this.value;
} }
return this.viewWidget.renderer.renderTree.createRenderers(this.viewWidget.renderer.renderContext,[{ // Set the element details
type: "text", this.viewWidget.tag = "pre";
text: $tw.utils.stringify(value) this.viewWidget.attributes = {
}]); "class": "tw-view-jsencoded"
};
this.viewWidget.children = this.viewWidget.renderer.renderTree.createRenderers(this.viewWidget.renderer.renderContext,[{
type: "text",
text: $tw.utils.stringify(value)
}]);
}; };
exports.jsencoded = JsEncodedViewer; exports.jsencoded = JsEncodedViewer;

View File

@ -1,7 +1,7 @@
/*\ /*\
title: $:/core/modules/widgets/view/viewers/link.js title: $:/core/modules/widgets/view/viewers/link.js
type: application/javascript type: application/javascript
module-type: newfieldviewer module-type: fieldviewer
A viewer for viewing tiddler fields as a link A viewer for viewing tiddler fields as a link
@ -20,23 +20,23 @@ var LinkViewer = function(viewWidget,tiddler,field,value) {
}; };
LinkViewer.prototype.render = function() { LinkViewer.prototype.render = function() {
var parseTree = []; var text = this.value === undefined ? "" : this.value;
if(this.value === undefined) { // Set the element details
parseTree.push({type: "text", text: ""}); this.viewWidget.tag = "span";
} else { this.viewWidget.attributes = {
parseTree.push({ "class": "tw-view-link"
type: "widget", };
tag: "link", this.viewWidget.children = this.viewWidget.renderer.renderTree.createRenderers(this.viewWidget.renderer.renderContext,[{
type: "element",
tag: "$link",
attributes: { attributes: {
to: {type: "string", value: this.value} to: {type: "string", value: text}
}, },
children: [{ children: [{
type: "text", type: "text",
text: this.value text: text
}] }]
}) }]);
}
return this.viewWidget.renderer.renderTree.createRenderers(this.viewWidget.renderer.renderContext,parseTree);
}; };
exports.link = LinkViewer; exports.link = LinkViewer;

View File

@ -1,7 +1,7 @@
/*\ /*\
title: $:/core/modules/widgets/view/viewers/wikified.js title: $:/core/modules/widgets/view/viewers/wikified.js
type: application/javascript type: application/javascript
module-type: newfieldviewer module-type: fieldviewer
A viewer for viewing tiddler fields as wikified text A viewer for viewing tiddler fields as wikified text
@ -20,21 +20,22 @@ var WikifiedViewer = function(viewWidget,tiddler,field,value) {
}; };
WikifiedViewer.prototype.render = function() { WikifiedViewer.prototype.render = function() {
var parseTree; // Set the element details
// If we're viewing the text field of a tiddler then we'll transclude it this.viewWidget.tag = this.viewWidget.renderer.parseTreeNode.isBlock ? "div" : "span";
if(this.tiddler && this.field === "text") { this.viewWidget.attributes = {};
parseTree = [{ var node = {
type: "widget", type: "element",
tag: "transclude", tag: "$transclude",
attributes: { attributes: {
target: {type: "string", value: this.tiddler.fields.title} "class": "tw-view-wikified",
field: {type: "string", value: this.field}
}, },
isBlock: this.viewWidget.renderer.parseTreeNode.isBlock isBlock: this.viewWidget.renderer.parseTreeNode.isBlock
}]; };
} else { if(this.tiddler && this.tiddler.fields.title) {
parseTree = this.viewWidget.renderer.renderTree.wiki.parseText("text/vnd.tiddlywiki",this.value).tree; node.attributes.target = {type: "string", value: this.tiddler.fields.title}
} }
return this.viewWidget.renderer.renderTree.createRenderers(this.viewWidget.renderer.renderContext,parseTree); this.viewWidget.children = this.viewWidget.renderer.renderTree.createRenderers(this.viewWidget.renderer.renderContext,[node]);
}; };
exports.wikified = WikifiedViewer; exports.wikified = WikifiedViewer;

View File

@ -8,7 +8,7 @@ title: $:/templates/TagTemplate
* <$view field="title" format="link" /> * <$view field="title" format="link" />
*.divider *.divider
* <div> * <div>
<$list filter="[is[current]tagging[]sort[title]]"/> <$list filter="[is[current]tagging[]sort[title]]"><$view field="title" format="link" /></$list>
</div> </div>
@@ @@