From 6d24cedbcc08193a5828018a8c14dbe66b69a07d Mon Sep 17 00:00:00 2001 From: Jeremy Ruston Date: Thu, 3 Jan 2013 16:27:55 +0000 Subject: [PATCH] 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. --- .../parsers/wikiparser/rules/extlink.js | 4 +- .../rules/filteredtranscludeblock.js | 4 +- .../rules/filteredtranscludeinline.js | 4 +- core/modules/parsers/wikiparser/rules/html.js | 19 +- .../parsers/wikiparser/rules/prettylink.js | 4 +- .../parsers/wikiparser/rules/styleinline.js | 3 + .../wikiparser/rules/transcludeblock.js | 4 +- .../wikiparser/rules/transcludeinline.js | 4 +- .../parsers/wikiparser/rules/wikilink.js | 4 +- core/modules/rendertree/renderers/element.js | 186 ++++++++++++++---- core/modules/rendertree/renderers/widget.js | 182 ----------------- core/modules/widgetbase.js | 56 ------ core/modules/widgets/button.js | 35 ++-- core/modules/widgets/error.js | 22 +-- core/modules/widgets/fields.js | 27 ++- core/modules/widgets/link.js | 47 ++--- core/modules/widgets/list/list.js | 70 +++---- core/modules/widgets/navigator.js | 15 +- core/modules/widgets/reveal.js | 90 +++++---- core/modules/widgets/transclude.js | 69 +++---- core/modules/widgets/view/view.js | 41 ++-- core/modules/widgets/view/viewers/date.js | 9 +- .../widgets/view/viewers/htmlencoded.js | 9 +- .../widgets/view/viewers/htmlwikified.js | 17 +- .../modules/widgets/view/viewers/jsencoded.js | 15 +- core/modules/widgets/view/viewers/link.js | 26 +-- core/modules/widgets/view/viewers/wikified.js | 25 +-- core/templates/TagTemplate.tid | 2 +- 28 files changed, 429 insertions(+), 564 deletions(-) delete mode 100644 core/modules/rendertree/renderers/widget.js delete mode 100644 core/modules/widgetbase.js diff --git a/core/modules/parsers/wikiparser/rules/extlink.js b/core/modules/parsers/wikiparser/rules/extlink.js index 19af88241..047cc7557 100644 --- a/core/modules/parsers/wikiparser/rules/extlink.js +++ b/core/modules/parsers/wikiparser/rules/extlink.js @@ -37,8 +37,8 @@ exports.parse = function() { return [{type: "text", text: this.match[0].substr(1)}]; } else { return [{ - type: "widget", - tag: "link", + type: "element", + tag: "$link", attributes: { to: {type: "string", value: this.match[0]} }, diff --git a/core/modules/parsers/wikiparser/rules/filteredtranscludeblock.js b/core/modules/parsers/wikiparser/rules/filteredtranscludeblock.js index 0c26cfeb1..51ed7ba58 100644 --- a/core/modules/parsers/wikiparser/rules/filteredtranscludeblock.js +++ b/core/modules/parsers/wikiparser/rules/filteredtranscludeblock.js @@ -40,8 +40,8 @@ exports.parse = function() { classes = this.match[5]; // Return the list widget var node = { - type: "widget", - tag: "list", + type: "element", + tag: "$list", attributes: { filter: {type: "string", value: filter} }, diff --git a/core/modules/parsers/wikiparser/rules/filteredtranscludeinline.js b/core/modules/parsers/wikiparser/rules/filteredtranscludeinline.js index 101d9d8d7..0c9b5c137 100644 --- a/core/modules/parsers/wikiparser/rules/filteredtranscludeinline.js +++ b/core/modules/parsers/wikiparser/rules/filteredtranscludeinline.js @@ -40,8 +40,8 @@ exports.parse = function() { classes = this.match[5]; // Return the list widget var node = { - type: "widget", - tag: "list", + type: "element", + tag: "$list", attributes: { filter: {type: "string", value: filter} } diff --git a/core/modules/parsers/wikiparser/rules/html.js b/core/modules/parsers/wikiparser/rules/html.js index 946579472..af7d3c108 100644 --- a/core/modules/parsers/wikiparser/rules/html.js +++ b/core/modules/parsers/wikiparser/rules/html.js @@ -32,9 +32,9 @@ exports.init = function(parser) { this.parser = parser; // Regexp to match if(this.is.block) { - this.matchRegExp = /<(\$)?([A-Za-z]+)(\s*[^>]*?)(\/)?>(\r?\n)/mg; + this.matchRegExp = /<([A-Za-z\$]+)(\s*[^>]*?)(\/)?>(\r?\n)/mg; } 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() { // Get all the details of the match in case this parser is called recursively - var isWidget = !!this.match[1], - tagName = this.match[2], - attributeString = this.match[3], - isSelfClosing = !!this.match[4], - hasLineBreak = !!this.match[5]; + var tagName = this.match[1], + attributeString = this.match[2], + isSelfClosing = !!this.match[3], + hasLineBreak = !!this.match[4]; // Move past the tag name and parameters this.parser.pos = this.matchRegExp.lastIndex; var reAttr = /\s*([A-Za-z\-_]+)(?:\s*=\s*(?:("[^"]*")|('[^']*')|(\{\{[^\}]*\}\})|([^"'\s]+)))?/mg; @@ -72,8 +71,8 @@ exports.parse = function() { attrMatch = reAttr.exec(attributeString); } // Process the end tag - if(!isSelfClosing && (isWidget || voidElements.indexOf(tagName) === -1)) { - var reEndString = "()", + if(!isSelfClosing && voidElements.indexOf(tagName) === -1) { + var reEndString = "()", reEnd = new RegExp(reEndString,"mg"), content; if(hasLineBreak) { @@ -90,7 +89,7 @@ exports.parse = function() { content = []; } var element = { - type: isWidget ? "widget" : "element", + type: "element", tag: tagName, isBlock: this.is.block || hasLineBreak, attributes: attributes, diff --git a/core/modules/parsers/wikiparser/rules/prettylink.js b/core/modules/parsers/wikiparser/rules/prettylink.js index 11432963a..2eb5eac98 100644 --- a/core/modules/parsers/wikiparser/rules/prettylink.js +++ b/core/modules/parsers/wikiparser/rules/prettylink.js @@ -34,8 +34,8 @@ exports.parse = function() { var text = this.match[1], link = this.match[2] || text; return [{ - type: "widget", - tag: "link", + type: "element", + tag: "$link", attributes: { to: {type: "string", value: link} }, diff --git a/core/modules/parsers/wikiparser/rules/styleinline.js b/core/modules/parsers/wikiparser/rules/styleinline.js index 119913fdf..bac894901 100644 --- a/core/modules/parsers/wikiparser/rules/styleinline.js +++ b/core/modules/parsers/wikiparser/rules/styleinline.js @@ -41,6 +41,9 @@ exports.parse = function() { var node = { type: "element", tag: "span", + attributes: { + "class": {type: "string", value: "tw-inline-style"} + }, children: tree }; if(classString) { diff --git a/core/modules/parsers/wikiparser/rules/transcludeblock.js b/core/modules/parsers/wikiparser/rules/transcludeblock.js index 8e4bf5575..ea8f5c345 100644 --- a/core/modules/parsers/wikiparser/rules/transcludeblock.js +++ b/core/modules/parsers/wikiparser/rules/transcludeblock.js @@ -40,8 +40,8 @@ exports.parse = function() { classes = this.match[5]; // Return the transclude widget var node = { - type: "widget", - tag: "transclude", + type: "element", + tag: "$transclude", attributes: { target: {type: "string", value: targetTitle} }, diff --git a/core/modules/parsers/wikiparser/rules/transcludeinline.js b/core/modules/parsers/wikiparser/rules/transcludeinline.js index e3a089725..12920114f 100644 --- a/core/modules/parsers/wikiparser/rules/transcludeinline.js +++ b/core/modules/parsers/wikiparser/rules/transcludeinline.js @@ -40,8 +40,8 @@ exports.parse = function() { classes = this.match[5]; // Return the transclude widget var node = { - type: "widget", - tag: "transclude", + type: "element", + tag: "$transclude", attributes: { target: {type: "string", value: targetTitle} } diff --git a/core/modules/parsers/wikiparser/rules/wikilink.js b/core/modules/parsers/wikiparser/rules/wikilink.js index 45f077f2a..5e744aea8 100644 --- a/core/modules/parsers/wikiparser/rules/wikilink.js +++ b/core/modules/parsers/wikiparser/rules/wikilink.js @@ -64,8 +64,8 @@ exports.parse = function() { } } return [{ - type: "widget", - tag: "link", + type: "element", + tag: "$link", attributes: { to: {type: "string", value: linkText} }, diff --git a/core/modules/rendertree/renderers/element.js b/core/modules/rendertree/renderers/element.js index 22f604aa2..091db0eb4 100644 --- a/core/modules/rendertree/renderers/element.js +++ b/core/modules/rendertree/renderers/element.js @@ -12,6 +12,31 @@ Element renderer /*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.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 */ @@ -20,50 +45,87 @@ var ElementRenderer = function(renderTree,renderContext,parseTreeNode) { this.renderTree = renderTree; this.renderContext = renderContext; this.parseTreeNode = parseTreeNode; + // Initialise widget classes + if(!this.widgetClasses) { + ElementRenderer.prototype.widgetClasses = $tw.modules.applyMethods("widget"); + } // 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); - if(tr.title) { - self.dependencies[tr.title] = true; - } else { - self.dependencies[renderContext.tiddlerTitle] = true; - } + self.dependencies[tr.title ? tr.title : renderContext.tiddlerTitle] = true; } }); // Compute our attributes + this.attributes = {}; this.computeAttributes(); - // Create the renderers for the child nodes - this.children = this.renderTree.createRenderers(this.renderContext,this.parseTreeNode.children); + // 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() { - this.attributes = {}; + var changedAttributes = {}; var self = this; $tw.utils.each(this.parseTreeNode.attributes,function(attribute,name) { 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 - 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) { var isHtml = type === "text/html", output = [],attr,a,v; if(isHtml) { - output.push("<",this.parseTreeNode.tag); - if(this.attributes) { + output.push("<",this.widget.tag); + if(this.widget.attributes) { attr = []; - for(a in this.attributes) { + for(a in this.widget.attributes) { attr.push(a); } attr.sort(); for(a=0; a"); } - if(this.children && this.children.length > 0) { - $tw.utils.each(this.children,function(node) { + if(this.widget.children && this.widget.children.length > 0) { + $tw.utils.each(this.widget.children,function(node) { if(node.render) { output.push(node.render(type)); } }); if(isHtml) { - output.push(""); + output.push(""); } } return output.join(""); @@ -98,25 +160,29 @@ ElementRenderer.prototype.render = function(type) { ElementRenderer.prototype.renderInDom = function() { // Create the element - this.domNode = document.createElement(this.parseTreeNode.tag); + this.domNode = document.createElement(this.widget.tag); // Assign the attributes this.assignAttributes(); // Render any child nodes var self = this; - $tw.utils.each(this.children,function(node) { + $tw.utils.each(this.widget.children,function(node) { if(node.renderInDom) { self.domNode.appendChild(node.renderInDom()); } }); // 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 this.domNode; }; ElementRenderer.prototype.assignAttributes = function() { var self = this; - $tw.utils.each(this.attributes,function(v,a) { + $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(" "); @@ -131,19 +197,73 @@ ElementRenderer.prototype.assignAttributes = function() { }); }; -ElementRenderer.prototype.refreshInDom = function(changes) { - // Check if any of our dependencies have changed - if($tw.utils.checkDependencies(this.dependencies,changes)) { - // Update our attributes - this.computeAttributes(); - this.assignAttributes(); +ElementRenderer.prototype.refreshInDom = function(changedTiddlers) { + // Update our attributes if required + var changedAttributes = {}; + if($tw.utils.checkDependencies(this.dependencies,changedTiddlers)) { + changedAttributes = this.computeAttributes(); } - // Refresh any child nodes - $tw.utils.each(this.children,function(node) { - if(node.refreshInDom) { - node.refreshInDom(changes); + // 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); + } + }); + } +}; + +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 diff --git a/core/modules/rendertree/renderers/widget.js b/core/modules/rendertree/renderers/widget.js deleted file mode 100644 index 4033a3a1e..000000000 --- a/core/modules/rendertree/renderers/widget.js +++ /dev/null @@ -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 - -})(); diff --git a/core/modules/widgetbase.js b/core/modules/widgetbase.js deleted file mode 100644 index 6d30ad8c5..000000000 --- a/core/modules/widgetbase.js +++ /dev/null @@ -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; - -})(); diff --git a/core/modules/widgets/button.js b/core/modules/widgets/button.js index 556b4fc75..1cf607c9c 100644 --- a/core/modules/widgets/button.js +++ b/core/modules/widgets/button.js @@ -16,10 +16,10 @@ var ButtonWidget = function(renderer) { // Save state this.renderer = renderer; // Generate child nodes - this.generateChildNodes(); + this.generate(); }; -ButtonWidget.prototype.generateChildNodes = function() { +ButtonWidget.prototype.generate = function() { // Get the parameters from the attributes this.message = this.renderer.getAttribute("message"); this.param = this.renderer.getAttribute("param"); @@ -30,7 +30,7 @@ ButtonWidget.prototype.generateChildNodes = function() { this.qualifyTiddlerTitles = this.renderer.getAttribute("qualifyTiddlerTitles"); this["class"] = this.renderer.getAttribute("class"); // Compose the button - var classes = ["tw-tiddlybutton"]; + var classes = ["tw-button"]; if(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: "mouseout", handlerObject: this, handlerMethod: "handleMouseOverOrOutEvent"}); } - this.children = this.renderer.renderTree.createRenderers(this.renderer.renderContext,[{ - type: "element", - tag: "button", - attributes: { - "class": {type: "string", value: classes.join(" ")} - }, - children: this.renderer.parseTreeNode.children, - events: events - }]); + // Set the return element + this.tag = "button"; + this.attributes ={"class": classes.join(" ")}; + this.children = this.renderer.renderTree.createRenderers(this.renderer.renderContext,this.renderer.parseTreeNode.children); + this.events = events; }; ButtonWidget.prototype.dispatchMessage = function(event) { @@ -99,16 +95,11 @@ ButtonWidget.prototype.handleMouseOverOrOutEvent = function(event) { ButtonWidget.prototype.refreshInDom = function(changedAttributes,changedTiddlers) { // 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])) { - // Remove old child nodes - $tw.utils.removeChildren(this.parentElement); - // Regenerate and render children - this.generateChildNodes(); - var self = this; - $tw.utils.each(this.children,function(node) { - if(node.renderInDom) { - self.parentElement.appendChild(node.renderInDom()); - } - }); + // Regenerate and rerender the widget and replace the existing DOM node + this.generate(); + var oldDomNode = this.renderer.domNode, + newDomNode = this.renderer.renderInDom(); + oldDomNode.parentNode.replaceChild(newDomNode,oldDomNode); } else { // We don't need to refresh ourselves, so just refresh any child nodes $tw.utils.each(this.children,function(node) { diff --git a/core/modules/widgets/error.js b/core/modules/widgets/error.js index 8de734e80..e9a5cef9b 100644 --- a/core/modules/widgets/error.js +++ b/core/modules/widgets/error.js @@ -17,23 +17,19 @@ var ErrorWidget = function(renderer,errorMessage) { this.renderer = renderer; this.errorMessage = errorMessage; // Generate child nodes - this.generateChildNodes(); + this.generate(); }; -ErrorWidget.prototype.generateChildNodes = function() { - // Create the wrapper node - var node = { - type: "element", - tag: "span", - children: [{ +ErrorWidget.prototype.generate = function() { + // Set the element details + this.tag = "span"; + this.attributes = { + "class": "tw-error-widget" + }; + this.children = this.renderer.renderTree.createRenderers(this.renderer.renderContext,[{ type: "text", 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; diff --git a/core/modules/widgets/fields.js b/core/modules/widgets/fields.js index d35911ac0..95bfd3417 100644 --- a/core/modules/widgets/fields.js +++ b/core/modules/widgets/fields.js @@ -16,10 +16,10 @@ var FieldsWidget = function(renderer) { // Save state this.renderer = renderer; // Generate child nodes - this.generateChildNodes(); + this.generate(); }; -FieldsWidget.prototype.generateChildNodes = function() { +FieldsWidget.prototype.generate = function() { // Get parameters from our attributes this.tiddlerTitle = this.renderer.getAttribute("tiddler",this.renderer.getContextTiddlerTitle()); this.template = this.renderer.getAttribute("template"); @@ -46,14 +46,10 @@ FieldsWidget.prototype.generateChildNodes = function() { } } } - // Create the wrapper node - var node = { - type: "element", - tag: this.renderer.parseTreeNode.isBlock ? "div" : "span", - children: [{ - type: "text", - text: text.join("") - }] + // Set the element + this.tag = "pre"; + this.attributes = { + "class": "tw-fields" }; // Set up the attributes for the wrapper element var classes = []; @@ -61,16 +57,19 @@ FieldsWidget.prototype.generateChildNodes = function() { $tw.utils.pushTop(classes,this.renderer.getAttribute("class").split(" ")); } if(classes.length > 0) { - $tw.utils.addClassToParseTreeNode(node,classes.join(" ")); + this.attributes["class"] = classes.join(" "); } 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")) { - $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 - 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; diff --git a/core/modules/widgets/link.js b/core/modules/widgets/link.js index 390b13592..98f537263 100644 --- a/core/modules/widgets/link.js +++ b/core/modules/widgets/link.js @@ -21,10 +21,10 @@ var LinkWidget = function(renderer) { // Save state this.renderer = renderer; // Generate child nodes - this.generateChildNodes(); + this.generate(); }; -LinkWidget.prototype.generateChildNodes = function() { +LinkWidget.prototype.generate = function() { // Get the parameters from the attributes this.to = this.renderer.getAttribute("to"); 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: "mouseout", handlerObject: this, handlerMethod: "handleMouseOverOrOutEvent"}); } - this.children = this.renderer.renderTree.createRenderers(this.renderer.renderContext,[{ - type: "element", - tag: "a", - attributes: { - href: {type: "string", value: this.isExternal ? this.to : encodeURIComponent(this.to)}, - "class": {type: "string", value: classes.join(" ")} - }, - children: this.renderer.parseTreeNode.children, - events: events - }]); + // Set up the element + this.tag = "a"; + this.attributes = { + href: this.isExternal ? this.to : encodeURIComponent(this.to), + "class": classes.join(" ") + }; + this.children = this.renderer.renderTree.createRenderers(this.renderer.renderContext,this.renderer.parseTreeNode.children); + this.events = events; }; LinkWidget.prototype.handleClickEvent = function(event) { @@ -71,7 +69,7 @@ LinkWidget.prototype.handleClickEvent = function(event) { $tw.utils.dispatchCustomEvent(event.target,"tw-navigate",{ navigateTo: this.to, navigateFromNode: this, - navigateFromClientRect: this.children[0].domNode.getBoundingClientRect() + navigateFromClientRect: this.renderer.domNode.getBoundingClientRect() }); event.preventDefault(); return false; @@ -81,10 +79,8 @@ LinkWidget.prototype.handleClickEvent = function(event) { LinkWidget.prototype.handleMouseOverOrOutEvent = function(event) { if(this.hover) { $tw.popup.triggerPopup({ - textRef: this.hover, - domNode: this.children[0].domNode, - qualifyTiddlerTitles: this.qualifyHoverTitle, - contextTiddlerTitle: this.renderer.getContextTiddlerTitle(), + title: this.hover, + domNode: this.renderer.domNode, wiki: this.renderer.renderTree.wiki }); } @@ -95,20 +91,15 @@ LinkWidget.prototype.handleMouseOverOrOutEvent = function(event) { LinkWidget.prototype.refreshInDom = function(changedAttributes,changedTiddlers) { // Set the class for missing tiddlers 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 if(changedAttributes.to || changedAttributes.hover || (this.to && changedTiddlers[this.to]) || (this.hover && changedTiddlers[this.hover])) { - // Remove old child nodes - $tw.utils.removeChildren(this.parentElement); - // Regenerate and render children - this.generateChildNodes(); - var self = this; - $tw.utils.each(this.children,function(node) { - if(node.renderInDom) { - self.parentElement.appendChild(node.renderInDom()); - } - }); + // Regenerate and rerender the widget and replace the existing DOM node + this.generate(); + var oldDomNode = this.renderer.domNode, + newDomNode = this.renderer.renderInDom(); + oldDomNode.parentNode.replaceChild(newDomNode,oldDomNode); } else { // We don't need to refresh ourselves, so just refresh any child nodes $tw.utils.each(this.children,function(node) { diff --git a/core/modules/widgets/list/list.js b/core/modules/widgets/list/list.js index e02387aa3..06679126c 100644 --- a/core/modules/widgets/list/list.js +++ b/core/modules/widgets/list/list.js @@ -15,8 +15,8 @@ The list widget var ListWidget = function(renderer) { // Save state this.renderer = renderer; - // Generate child nodes - this.generateChildNodes(); + // Generate widget elements + this.generate(); }; /* @@ -30,7 +30,7 @@ var typeMappings = { shadows: "[is[shadow]sort[title]]" }; -ListWidget.prototype.generateChildNodes = function() { +ListWidget.prototype.generate = function() { // Get our attributes this.itemClass = this.renderer.getAttribute("itemClass"); this.template = this.renderer.getAttribute("template"); @@ -50,15 +50,11 @@ ListWidget.prototype.generateChildNodes = function() { } } // Create the list frame element - var classes = ["tw-list-frame"]; - this.children = this.renderer.renderTree.createRenderers(this.renderer.renderContext,[{ - type: "element", - tag: this.renderer.parseTreeNode.isBlock ? "div" : "span", - attributes: { - "class": {type: "string", value: classes.join(" ")} - }, - children: listMembers - }]); + this.tag = this.renderer.parseTreeNode.isBlock ? "div" : "span"; + this.attributes = { + "class": "tw-list-frame" + }; + this.children = this.renderer.renderTree.createRenderers(this.renderer.renderContext,listMembers); }; ListWidget.prototype.getTiddlerList = function() { @@ -138,8 +134,8 @@ ListWidget.prototype.createListElementMacro = function(title) { } else { // Use default content templateTree = [{ - type: "widget", - tag: "view", + type: "element", + tag: "$view", attributes: { field: {type: "string", value: "title"}, format: {type: "string", value: "link"} @@ -147,10 +143,10 @@ ListWidget.prototype.createListElementMacro = function(title) { }]; } } - // Create the tiddler macro + // Create the transclude widget return { - type: "widget", - tag: "transclude", + type: "element", + tag: "$transclude", attributes: { target: {type: "string", value: title}, 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) { // Get the list element - var listElement = this.children[0].children[index]; + var listElement = this.children[index]; // Remove the DOM node listElement.domNode.parentNode.removeChild(listElement.domNode); // 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 */ ListWidget.prototype.findListElementByTitle = function(startIndex,title) { - while(startIndex < this.children[0].children.length) { - if(this.children[0].children[startIndex].children[0].attributes.target === title) { + while(startIndex < this.children.length) { + if(this.children[startIndex].widget.children[0].attributes.target === title) { return startIndex; } startIndex++; @@ -189,16 +185,11 @@ ListWidget.prototype.findListElementByTitle = function(startIndex,title) { ListWidget.prototype.refreshInDom = function(changedAttributes,changedTiddlers) { // 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) { - // Remove old child nodes - $tw.utils.removeChildren(this.parentElement); - // Regenerate and render children - this.generateChildNodes(); - var self = this; - $tw.utils.each(this.children,function(node) { - if(node.renderInDom) { - self.parentElement.appendChild(node.renderInDom()); - } - }); + // Regenerate and rerender the widget and replace the existing DOM node + this.generate(); + var oldDomNode = this.renderer.domNode, + newDomNode = this.renderer.renderInDom(); + oldDomNode.parentNode.replaceChild(newDomNode,oldDomNode); } else { // Handle any changes to the list, and refresh any nodes we're reusing this.handleListChanges(changedTiddlers); @@ -207,8 +198,7 @@ ListWidget.prototype.refreshInDom = function(changedAttributes,changedTiddlers) ListWidget.prototype.handleListChanges = function(changedTiddlers) { var t, - prevListLength = this.list.length, - frame = this.children[0]; + prevListLength = this.list.length; // Get the list of tiddlers, having saved the previous length this.getTiddlerList(); // Check if the list is empty @@ -228,10 +218,10 @@ ListWidget.prototype.handleListChanges = function(changedTiddlers) { this.removeListElement(t); } // Insert the empty message - frame.children = this.renderer.renderTree.createRenderers(this.renderer.renderContext,[this.getEmptyMessage()]); - $tw.utils.each(frame.children,function(node) { + this.children = this.renderer.renderTree.createRenderers(this.renderer.renderContext,[this.getEmptyMessage()]); + $tw.utils.each(this.children,function(node) { if(node.renderInDom) { - frame.domNode.appendChild(node.renderInDom()); + this.renderer.domNode.appendChild(node.renderInDom()); } }); return; @@ -248,19 +238,19 @@ ListWidget.prototype.handleListChanges = function(changedTiddlers) { var index = this.findListElementByTitle(t,this.list[t]); if(index === undefined) { // 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]))); - frame.domNode.insertBefore(frame.children[t].renderInDom(),frame.domNode.childNodes[t]); + this.children.splice(t,0,this.renderer.renderTree.createRenderer(this.renderer.renderContext,this.createListElement(this.list[t]))); + this.renderer.domNode.insertBefore(this.children[t].renderInDom(),this.renderer.domNode.childNodes[t]); } else { // Delete any list elements preceding the one we want for(var n=index-1; n>=t; n--) { this.removeListElement(n); } // Refresh the node we're reusing - frame.children[t].refreshInDom(changedTiddlers); + this.children[t].refreshInDom(changedTiddlers); } } // 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); } }; diff --git a/core/modules/widgets/navigator.js b/core/modules/widgets/navigator.js index 7e49a34f5..f9836e48a 100644 --- a/core/modules/widgets/navigator.js +++ b/core/modules/widgets/navigator.js @@ -16,19 +16,20 @@ var NavigatorWidget = function(renderer) { // Save state this.renderer = renderer; // Generate child nodes - this.generateChildNodes(); + this.generate(); }; -NavigatorWidget.prototype.generateChildNodes = function() { +NavigatorWidget.prototype.generate = function() { // Get our parameters this.storyTitle = this.renderer.getAttribute("story"); 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); -}; - -NavigatorWidget.prototype.getEventListeners = function() { - return [ + this.events = [ {name: "tw-navigate", handlerObject: this, handlerMethod: "handleNavigateEvent"}, {name: "tw-EditTiddler", handlerObject: this, handlerMethod: "handleEditTiddlerEvent"}, {name: "tw-SaveTiddler", handlerObject: this, handlerMethod: "handleSaveTiddlerEvent"}, diff --git a/core/modules/widgets/reveal.js b/core/modules/widgets/reveal.js index 3cbf23eec..136ed297b 100644 --- a/core/modules/widgets/reveal.js +++ b/core/modules/widgets/reveal.js @@ -16,10 +16,10 @@ var RevealWidget = function(renderer) { // Save state this.renderer = renderer; // Generate child nodes - this.generateChildNodes(); + this.generate(); }; -RevealWidget.prototype.generateChildNodes = function() { +RevealWidget.prototype.generate = function() { // Get the parameters from the attributes this.state = this.renderer.getAttribute("state"); this.type = this.renderer.getAttribute("type"); @@ -34,26 +34,27 @@ RevealWidget.prototype.generateChildNodes = function() { this.stateTitle = this.stateTitle + "-" + this.renderer.getContextScopeId(); } this.readState(); - // Compose the node - var node = { - type: "element", - tag: "div", - children: this.isOpen ? this.renderer.parseTreeNode.children : [], - events: [{name: "click", handlerObject: this, handlerMethod: "handleClickEvent"}] - }; - $tw.utils.addClassToParseTreeNode(node,"tw-reveal"); + // Set up the element attributes + var classes = ["tw-reveal"], + styles = []; if(this["class"]) { - $tw.utils.addClassToParseTreeNode(node,this["class"].join(" ")); + $tw.utils.pushTop(classes,this["class"]); } switch(this.type) { case "popup": - $tw.utils.addStyleToParseTreeNode(node,"position","absolute"); - $tw.utils.addClassToParseTreeNode(node,"tw-popup"); + styles.push("position:absolute;"); + classes.push("tw-popup"); break; } - $tw.utils.addStyleToParseTreeNode(node,"display",this.isOpen ? (this.isBlock ? "block" : "inline") : "none"); - // Return the node - this.children = this.renderer.renderTree.createRenderers(this.renderer.renderContext,[node]); + styles.push("display:" + (this.isOpen ? (this.renderer.parseTreeNode.isBlock ? "block" : "inline") : "none") + ";"); + // Set the element + 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) { // 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"]) { - // Remove old child nodes - $tw.utils.removeChildren(this.parentElement); - // Regenerate and render children - this.generateChildNodes(); - var self = this; - $tw.utils.each(this.children,function(node) { - if(node.renderInDom) { - self.parentElement.appendChild(node.renderInDom()); - } - }); + // Regenerate and rerender the widget and replace the existing DOM node + this.generate(); + var oldDomNode = this.renderer.domNode, + newDomNode = this.renderer.renderInDom(); + oldDomNode.parentNode.replaceChild(newDomNode,oldDomNode); } else { var needChildrenRefresh = true; // Avoid refreshing the children nodes if we don't need to // Get the open state this.readState(); // Construct the child nodes if required - if(this.isOpen && this.children[0].children.length === 0) { - this.children[0].children = this.renderer.renderTree.createRenderers(this.renderer.renderContext,this.renderer.parseTreeNode.children); - var parentNode = this.children[0].domNode; - $tw.utils.each(this.children[0].children,function(child) { + if(this.isOpen && this.children.length === 0) { + this.children = this.renderer.renderTree.createRenderers(this.renderer.renderContext,this.renderer.parseTreeNode.children); + var parentNode = this.renderer.domNode; + $tw.utils.each(this.children,function(child) { parentNode.appendChild(child.renderInDom()); }); needChildrenRefresh = false; @@ -153,42 +149,44 @@ RevealWidget.prototype.refreshInDom = function(changedAttributes,changedTiddlers }); } // 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 - this.postRenderInDom(); + if(this.isOpen) { + this.postRenderInDom(); + } }; RevealWidget.prototype.postRenderInDom = function() { switch(this.type) { case "popup": if(this.isOpen) { - this.children[0].domNode.style.position = "absolute"; - this.children[0].domNode.style.zIndex = "1000"; + this.renderer.domNode.style.position = "absolute"; + this.renderer.domNode.style.zIndex = "1000"; switch(this.position) { case "left": - this.children[0].domNode.style.left = (this.popup.left - this.children[0].domNode.offsetWidth) + "px"; - this.children[0].domNode.style.top = this.popup.top + "px"; + this.renderer.domNode.style.left = (this.popup.left - this.renderer.domNode.offsetWidth) + "px"; + this.renderer.domNode.style.top = this.popup.top + "px"; break; case "above": - this.children[0].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.left = this.popup.left + "px"; + this.renderer.domNode.style.top = (this.popup.top - this.renderer.domNode.offsetHeight) + "px"; break; case "aboveright": - this.children[0].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.left = (this.popup.left + this.popup.width) + "px"; + this.renderer.domNode.style.top = (this.popup.top + this.popup.height - this.renderer.domNode.offsetHeight) + "px"; break; case "right": - this.children[0].domNode.style.left = (this.popup.left + this.popup.width) + "px"; - this.children[0].domNode.style.top = this.popup.top + "px"; + this.renderer.domNode.style.left = (this.popup.left + this.popup.width) + "px"; + this.renderer.domNode.style.top = this.popup.top + "px"; break; case "belowleft": - this.children[0].domNode.style.left = (this.popup.left + this.popup.width - this.children[0].domNode.offsetWidth) + "px"; - this.children[0].domNode.style.top = (this.popup.top + this.popup.height) + "px"; + this.renderer.domNode.style.left = (this.popup.left + this.popup.width - this.renderer.domNode.offsetWidth) + "px"; + this.renderer.domNode.style.top = (this.popup.top + this.popup.height) + "px"; break; default: // Below - this.children[0].domNode.style.left = this.popup.left + "px"; - this.children[0].domNode.style.top = (this.popup.top + this.popup.height) + "px"; + this.renderer.domNode.style.left = this.popup.left + "px"; + this.renderer.domNode.style.top = (this.popup.top + this.popup.height) + "px"; break; } } diff --git a/core/modules/widgets/transclude.js b/core/modules/widgets/transclude.js index cd523a2b2..40dd15a2b 100644 --- a/core/modules/widgets/transclude.js +++ b/core/modules/widgets/transclude.js @@ -12,7 +12,7 @@ Attributes: 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 @@ -24,7 +24,7 @@ widget are those of the tiddler doing the transcluding, then you can instead spe 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 @@ -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: {{{ -<_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 @@ -50,13 +50,14 @@ var TranscludeWidget = function(renderer) { // Save state this.renderer = renderer; // Generate child nodes - this.generateChildNodes(); + this.generate(); }; -TranscludeWidget.prototype.generateChildNodes = function() { +TranscludeWidget.prototype.generate = function() { var tr, templateParseTree, templateTiddler; // Get the render target details this.targetTitle = this.renderer.getAttribute("target",this.renderer.getContextTiddlerTitle()); + this.targetField = this.renderer.getAttribute("field","text"); // Get the render tree for the template this.templateTitle = undefined; 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"}]; } 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 : []; } } - // 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 - var classes = []; + var classes = ["tw-transclude"]; if(this.renderer.hasAttribute("class")) { $tw.utils.pushTop(classes,this.renderer.getAttribute("class").split(" ")); } if(!this.renderer.renderTree.wiki.tiddlerExists(this.targetTitle)) { $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 var newRenderContext = { tiddlerTitle: this.targetTitle, templateTitle: this.templateTitle, 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) { @@ -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 if(changedAttributes.target || changedAttributes.template || (this.targetTitle && changedTiddlers[this.targetTitle]) || (this.templateTitle && changedTiddlers[this.templateTitle])) { - // Remove old child nodes - $tw.utils.removeChildren(this.parentElement); - // Regenerate and render children - this.generateChildNodes(); - var self = this; - $tw.utils.each(this.children,function(node) { - if(node.renderInDom) { - self.parentElement.appendChild(node.renderInDom()); - } - }); + // Regenerate and rerender the widget and replace the existing DOM node + this.generate(); + var oldDomNode = this.renderer.domNode, + newDomNode = this.renderer.renderInDom(); + oldDomNode.parentNode.replaceChild(newDomNode,oldDomNode); } else { // We don't need to refresh ourselves, so just refresh any child nodes $tw.utils.each(this.children,function(node) { diff --git a/core/modules/widgets/view/view.js b/core/modules/widgets/view/view.js index 48cbdb16d..972182162 100644 --- a/core/modules/widgets/view/view.js +++ b/core/modules/widgets/view/view.js @@ -31,28 +31,28 @@ TextViewer.prototype.render = function() { if(this.value !== undefined && this.value !== null) { 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", text: value }]); }; -// We'll cache the available field viewers here -var fieldViewers = undefined; - var ViewWidget = function(renderer) { // Save state this.renderer = renderer; // Initialise the field viewers if they've not been done already - if(!fieldViewers) { - fieldViewers = {text: TextViewer}; // Start with the built-in text viewer - $tw.modules.applyMethods("newfieldviewer",fieldViewers); + if(!this.fieldViewers) { + ViewWidget.prototype.fieldViewers = {text: TextViewer}; // Start with the built-in text viewer + $tw.modules.applyMethods("fieldviewer",this.fieldViewers); } // Generate child nodes - this.generateChildNodes(); + this.generate(); }; -ViewWidget.prototype.generateChildNodes = function() { +ViewWidget.prototype.generate = function() { // Get parameters from our attributes this.tiddlerTitle = this.renderer.getAttribute("tiddler",this.renderer.getContextTiddlerTitle()); this.fieldName = this.renderer.getAttribute("field","text"); @@ -82,13 +82,13 @@ ViewWidget.prototype.generateChildNodes = function() { } } // Choose the viewer to use - var Viewer = fieldViewers.text; - if($tw.utils.hop(fieldViewers,this.format)) { - Viewer = fieldViewers[this.format]; + var Viewer = this.fieldViewers.text; + if($tw.utils.hop(this.fieldViewers,this.format)) { + Viewer = this.fieldViewers[this.format]; } this.viewer = new Viewer(this,tiddler,this.fieldName,value); - // Ask the viewer to create the children - this.children = this.viewer.render(); + // Ask the viewer to create the widget element + this.viewer.render(); }; 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])) { // Remove old child nodes $tw.utils.removeChildren(this.parentElement); - // Regenerate and render children - this.generateChildNodes(); - var self = this; - $tw.utils.each(this.children,function(node) { - if(node.renderInDom) { - self.parentElement.appendChild(node.renderInDom()); - } - }); + // Regenerate and rerender the widget and replace the existing DOM node + this.generate(); + var oldDomNode = this.renderer.domNode, + newDomNode = this.renderer.renderInDom(); + oldDomNode.parentNode.replaceChild(newDomNode,oldDomNode); } else { // We don't need to refresh ourselves, so just refresh any child nodes $tw.utils.each(this.children,function(node) { diff --git a/core/modules/widgets/view/viewers/date.js b/core/modules/widgets/view/viewers/date.js index 327ea4d92..2c96b25f7 100644 --- a/core/modules/widgets/view/viewers/date.js +++ b/core/modules/widgets/view/viewers/date.js @@ -1,7 +1,7 @@ /*\ title: $:/core/modules/widgets/view/viewers/date.js type: application/javascript -module-type: newfieldviewer +module-type: fieldviewer A viewer for viewing tiddler fields as a date @@ -25,7 +25,12 @@ DateViewer.prototype.render = function() { if(this.value !== undefined) { 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", text: value }]); diff --git a/core/modules/widgets/view/viewers/htmlencoded.js b/core/modules/widgets/view/viewers/htmlencoded.js index c7e687323..57146c40d 100644 --- a/core/modules/widgets/view/viewers/htmlencoded.js +++ b/core/modules/widgets/view/viewers/htmlencoded.js @@ -1,7 +1,7 @@ /*\ title: $:/core/modules/widgets/view/viewers/htmlencoded.js type: application/javascript -module-type: newfieldviewer +module-type: fieldviewer 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) { 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", text: $tw.utils.htmlEncode(value) }]); diff --git a/core/modules/widgets/view/viewers/htmlwikified.js b/core/modules/widgets/view/viewers/htmlwikified.js index c65df6cd4..5810a9f34 100644 --- a/core/modules/widgets/view/viewers/htmlwikified.js +++ b/core/modules/widgets/view/viewers/htmlwikified.js @@ -1,7 +1,7 @@ /*\ title: $:/core/modules/widgets/view/viewers/htmlwikified.js type: application/javascript -module-type: newfieldviewer +module-type: fieldviewer 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() { // Parse the field text 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 - var node = { - type: "element", - tag: "pre", - children: [{ + // Set the element details + this.viewWidget.tag = "pre"; + this.viewWidget.attributes = { + "class": "tw-view-htmlwikified" + }; + this.viewWidget.children = this.viewWidget.renderer.renderTree.createRenderers(this.viewWidget.renderer.renderContext,[{ type: "text", text: text - }] - }; - return this.viewWidget.renderer.renderTree.createRenderers(this.viewWidget.renderer.renderContext,[node]); + }]); }; exports.htmlwikified = HtmlWikifiedViewer; diff --git a/core/modules/widgets/view/viewers/jsencoded.js b/core/modules/widgets/view/viewers/jsencoded.js index 9d5a61dc3..76cae5640 100644 --- a/core/modules/widgets/view/viewers/jsencoded.js +++ b/core/modules/widgets/view/viewers/jsencoded.js @@ -1,7 +1,7 @@ /*\ title: $:/core/modules/widgets/view/viewers/jsencoded.js type: application/javascript -module-type: newfieldviewer +module-type: fieldviewer 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) { value = this.value; } - return this.viewWidget.renderer.renderTree.createRenderers(this.viewWidget.renderer.renderContext,[{ - type: "text", - text: $tw.utils.stringify(value) - }]); + // Set the element details + this.viewWidget.tag = "pre"; + 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; diff --git a/core/modules/widgets/view/viewers/link.js b/core/modules/widgets/view/viewers/link.js index 77d1cc6cd..0472ddb8c 100644 --- a/core/modules/widgets/view/viewers/link.js +++ b/core/modules/widgets/view/viewers/link.js @@ -1,7 +1,7 @@ /*\ title: $:/core/modules/widgets/view/viewers/link.js type: application/javascript -module-type: newfieldviewer +module-type: fieldviewer A viewer for viewing tiddler fields as a link @@ -20,23 +20,23 @@ var LinkViewer = function(viewWidget,tiddler,field,value) { }; LinkViewer.prototype.render = function() { - var parseTree = []; - if(this.value === undefined) { - parseTree.push({type: "text", text: ""}); - } else { - parseTree.push({ - type: "widget", - tag: "link", + var text = this.value === undefined ? "" : this.value; + // Set the element details + this.viewWidget.tag = "span"; + this.viewWidget.attributes = { + "class": "tw-view-link" + }; + this.viewWidget.children = this.viewWidget.renderer.renderTree.createRenderers(this.viewWidget.renderer.renderContext,[{ + type: "element", + tag: "$link", attributes: { - to: {type: "string", value: this.value} + to: {type: "string", value: text} }, children: [{ type: "text", - text: this.value + text: text }] - }) - } - return this.viewWidget.renderer.renderTree.createRenderers(this.viewWidget.renderer.renderContext,parseTree); + }]); }; exports.link = LinkViewer; diff --git a/core/modules/widgets/view/viewers/wikified.js b/core/modules/widgets/view/viewers/wikified.js index 1e0e5acda..566985851 100644 --- a/core/modules/widgets/view/viewers/wikified.js +++ b/core/modules/widgets/view/viewers/wikified.js @@ -1,7 +1,7 @@ /*\ title: $:/core/modules/widgets/view/viewers/wikified.js type: application/javascript -module-type: newfieldviewer +module-type: fieldviewer A viewer for viewing tiddler fields as wikified text @@ -20,21 +20,22 @@ var WikifiedViewer = function(viewWidget,tiddler,field,value) { }; WikifiedViewer.prototype.render = function() { - var parseTree; - // If we're viewing the text field of a tiddler then we'll transclude it - if(this.tiddler && this.field === "text") { - parseTree = [{ - type: "widget", - tag: "transclude", + // Set the element details + this.viewWidget.tag = this.viewWidget.renderer.parseTreeNode.isBlock ? "div" : "span"; + this.viewWidget.attributes = {}; + var node = { + type: "element", + tag: "$transclude", 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 - }]; - } else { - parseTree = this.viewWidget.renderer.renderTree.wiki.parseText("text/vnd.tiddlywiki",this.value).tree; + }; + if(this.tiddler && this.tiddler.fields.title) { + 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; diff --git a/core/templates/TagTemplate.tid b/core/templates/TagTemplate.tid index 47d757be3..9436daa9e 100644 --- a/core/templates/TagTemplate.tid +++ b/core/templates/TagTemplate.tid @@ -8,7 +8,7 @@ title: $:/templates/TagTemplate * <$view field="title" format="link" /> *.divider *
-<$list filter="[is[current]tagging[]sort[title]]"/> +<$list filter="[is[current]tagging[]sort[title]]"><$view field="title" format="link" />
@@