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" />
@@