From 8fdeefd7d169f32caed4252f4663691e29ef39ab Mon Sep 17 00:00:00 2001 From: Jeremy Ruston Date: Mon, 31 Dec 2012 18:36:39 +0000 Subject: [PATCH] Added reveal macro --- core/modules/rendertree/renderers/widget.js | 20 +++ core/modules/startup.js | 1 - core/modules/utils/dom/popup.js | 39 ++--- core/modules/utils/utils.js | 11 ++ core/modules/widgets/button.js | 8 +- core/modules/widgets/popup.js | 97 ---------- core/modules/widgets/reveal.js | 185 ++++++++++++++++++++ core/templates/TagTemplate.tid | 17 +- 8 files changed, 247 insertions(+), 131 deletions(-) delete mode 100644 core/modules/widgets/popup.js create mode 100644 core/modules/widgets/reveal.js diff --git a/core/modules/rendertree/renderers/widget.js b/core/modules/rendertree/renderers/widget.js index c32d46fea..480ab25f5 100644 --- a/core/modules/rendertree/renderers/widget.js +++ b/core/modules/rendertree/renderers/widget.js @@ -92,6 +92,10 @@ WidgetRenderer.prototype.renderInDom = function() { $tw.utils.addEventListeners(this.domNode,this.widget.getEventListeners()); } } + // Call the postRenderInDom hook if the widget has one + if(this.widget.postRenderInDom) { + this.widget.postRenderInDom(); + } // Return the dom node return this.domNode; }; @@ -138,6 +142,22 @@ WidgetRenderer.prototype.checkContextRecursion = function(newRenderContext) { 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/startup.js b/core/modules/startup.js index 5e9a81eee..a5c50a836 100644 --- a/core/modules/startup.js +++ b/core/modules/startup.js @@ -42,7 +42,6 @@ exports.startup = function() { }); // Install the popup manager $tw.popup = new $tw.utils.Popup({ - wiki: $tw.wiki, rootElement: document.body }); // Install the modal message mechanism diff --git a/core/modules/utils/dom/popup.js b/core/modules/utils/dom/popup.js index 74ce1c590..5bfec3562 100644 --- a/core/modules/utils/dom/popup.js +++ b/core/modules/utils/dom/popup.js @@ -14,64 +14,55 @@ Module that creates a $tw.utils.Popup object prototype that manages popups in th /* Creates a Popup object with these options: - wiki: the wiki to use for resolving tiddler titles rootElement: the DOM element to which the popup zapper should be attached */ var Popup = function(options) { options = options || {}; - this.wiki = options.wiki; this.rootElement = options.rootElement || document.body; - this.popupTextRef = null; }; -Popup.prototype.popup = function(stateTextRef) { +Popup.prototype.show = function(options) { this.cancel(); - this.popupTextRef = stateTextRef; - this.rootElement.addEventListener("click",this,true); + this.title = options.title; + this.wiki = options.wiki; + // this.rootElement.addEventListener("click",this,true); }; Popup.prototype.handleEvent = function(event) { if(event.type === "click") { - this.rootElement.removeEventListener("click",this,true); this.cancel(); } }; Popup.prototype.cancel = function() { - if(this.popupTextRef) { - this.wiki.deleteTextReference(this.popupTextRef); - this.popupTextRef = null; + // this.rootElement.removeEventListener("click",this,true); + if(this.title) { + this.wiki.deleteTiddler(this.title); + this.title = null; } }; /* Trigger a popup open or closed. Parameters are in a hashmap: - textRef: text reference where the popup details are stored + title: title of the tiddler where the popup details are stored domNode: dom node to which the popup will be positioned - qualifyTiddlerTitles: "yes" if state tiddler titles are to be qualified - contextTiddlerTitle: title of tiddler providing context for text references - contextParents: parent stack wiki: wiki */ Popup.prototype.triggerPopup = function(options) { - // Get the textref of the popup state tiddler - var textRef = options.textRef; - if(options.qualifyTiddlerTitles === "yes") { - textRef = textRef + "(" + options.contextParents.join(",") + "," + options.contextTiddlerTitle + ")"; - } // Get the current popup state tiddler - var value = options.wiki.getTextReference(textRef,"",options.contextTiddlerTitle); + var value = options.wiki.getTextReference(options.title,""); +console.log("Value is",value) // Check if the popup is open by checking whether it matches "(,)" var popupLocationRegExp = /^\((-?[0-9\.E]+),(-?[0-9\.E]+),(-?[0-9\.E]+),(-?[0-9\.E]+)\)$/; if(popupLocationRegExp.test(value)) { this.cancel(); } else { // Set the position if we're opening it - options.wiki.setTextReference(textRef, + this.cancel(); + options.wiki.setTextReference(options.title, "(" + options.domNode.offsetLeft + "," + options.domNode.offsetTop + "," + - options.domNode.offsetWidth + "," + options.domNode.offsetHeight + ")", - options.contextTiddlerTitle,true); - this.popup(textRef); + options.domNode.offsetWidth + "," + options.domNode.offsetHeight + ")"); + this.show(options); } }; diff --git a/core/modules/utils/utils.js b/core/modules/utils/utils.js index 20dae6401..78dcb8818 100644 --- a/core/modules/utils/utils.js +++ b/core/modules/utils/utils.js @@ -24,6 +24,17 @@ exports.trim = function(str) { } }; +/* +Convert a string to base64 encoding +*/ +exports.toBase64 = function(str) { + if($tw.browser) { + return window.btoa(str); + } else { + new Buffer(str).toString("base64"); + } +}; + /* Return the number of keys in an object */ diff --git a/core/modules/widgets/button.js b/core/modules/widgets/button.js index bf1fd71ad..4bf856e03 100644 --- a/core/modules/widgets/button.js +++ b/core/modules/widgets/button.js @@ -60,11 +60,13 @@ exports.dispatchMessage = function(event) { }; exports.triggerPopup = function(event) { + var title = this.popup; + if(this.qualifyTiddlerTitles) { + title = title + "-" + this.renderer.getContextScopeId(); + } $tw.popup.triggerPopup({ - textRef: this.popup, domNode: this.renderer.domNode, - qualifyTiddlerTitles: this.qualifyTiddlerTitles, - renderContext: this.renderer.renderContext, + title: title, wiki: this.renderer.renderTree.wiki }); }; diff --git a/core/modules/widgets/popup.js b/core/modules/widgets/popup.js deleted file mode 100644 index cded6b8d7..000000000 --- a/core/modules/widgets/popup.js +++ /dev/null @@ -1,97 +0,0 @@ -/*\ -title: $:/core/modules/widget/popup.js -type: application/javascript -module-type: widget - -Implements the popup widget. - -\*/ -(function(){ - -/*jslint node: true, browser: true */ -/*global $tw: false */ -"use strict"; - -exports.name = "popup"; - -exports.init = function(renderer) { - // Save state - this.renderer = renderer; - // Generate child nodes - this.generateChildNodes(); -}; - -exports.generateChildNodes = function() { - // Get the parameters from the attributes - this.hover = this.renderer.getAttribute("hover"); - this["class"] = this.renderer.getAttribute("class"); - // Find the handle and body in the parse tree children - var handle = $tw.utils.findParseTreeNode(this.renderer.parseTreeNode.children,{type: "widget", tag: "handle"}), - body = $tw.utils.findParseTreeNode(this.renderer.parseTreeNode.children,{type: "widget", tag: "body"}); - // Compose the elements - var classes = ["tw-popup-wrapper"]; - if(this["class"]) { - $tw.utils.pushTop(classes,this["class"]); - } - var events = [{name: "click", handlerObject: this, handlerMethod: "handleClickEvent"}]; - if(this.hover === "yes") { - 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: handle.children, - events: events - }, - { - type: "element", - tag: "div", - children: body.children, - attributes: { - style: { - type: "string", - value: "display:none;" - } - } - } - ]); -}; - -exports.triggerPopup = function(event) { -}; - -exports.handleClickEvent = function(event) { - this.triggerPopup(event); - event.preventDefault(); - return false; -}; - -exports.refreshInDom = function(changedAttributes,changedTiddlers) { - // Check if any of our attributes have changed, or if a tiddler we're interested in has changed - if(changedAttributes.hover || 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()); - } - }); - } else { - // We don't need to refresh ourselves, so just refresh any child nodes - $tw.utils.each(this.children,function(node) { - if(node.refreshInDom) { - node.refreshInDom(changedTiddlers); - } - }); - } -}; - -})(); diff --git a/core/modules/widgets/reveal.js b/core/modules/widgets/reveal.js new file mode 100644 index 000000000..b54e5c716 --- /dev/null +++ b/core/modules/widgets/reveal.js @@ -0,0 +1,185 @@ +/*\ +title: $:/core/modules/widget/reveal.js +type: application/javascript +module-type: widget + +Implements the reveal widget. + +\*/ +(function(){ + +/*jslint node: true, browser: true */ +/*global $tw: false */ +"use strict"; + +exports.name = "reveal"; + +exports.init = function(renderer) { + // Save state + this.renderer = renderer; + // Generate child nodes + this.generateChildNodes(); +}; + +exports.generateChildNodes = function() { + // Get the parameters from the attributes + this.state = this.renderer.getAttribute("state"); + this.type = this.renderer.getAttribute("type"); + this.text = this.renderer.getAttribute("text"); + this.position = this.renderer.getAttribute("position"); + this["default"] = this.renderer.getAttribute("default"); + this.qualifyTiddlerTitles = this.renderer.getAttribute("qualifyTiddlerTitles"); + this["class"] = this.renderer.getAttribute("class"); + // Compute the title of the state tiddler and read it + this.stateTitle = this.state; + if(this.qualifyTiddlerTitles) { + this.stateTitle = this.stateTitle + "-" + this.renderer.getContextScopeId(); + } + this.readState(); + // Compose the node + var node = { + type: "element", + tag: "div", + children: this.renderer.parseTreeNode.children, + events: [{name: "click", handlerObject: this, handlerMethod: "handleClickEvent"}] + }; + $tw.utils.addClassToParseTreeNode(node,"tw-reveal"); + if(this["class"]) { + $tw.utils.addClassToParseTreeNode(node,this["class"].join(" ")); + } + switch(this.type) { + case "popup": + $tw.utils.addStyleToParseTreeNode(node,"position","absolute"); + $tw.utils.addClassToParseTreeNode(node,"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]); +}; + +/* +Read the state tiddler +*/ +exports.readState = function() { + // Start with the default value for being open or closed + if(this["default"]) { + this.isOpen = this["default"] === "open"; + } + // Read the information from the state tiddler + if(this.stateTitle) { + var state = this.renderer.renderTree.wiki.getTextReference(this.stateTitle); + switch(this.type) { + case "popup": + this.readPopupState(state); + break; + case "match": + this.readMatchState(state); + break; + case "nomatch": + this.readMatchState(state); + this.isOpen = !this.isOpen; + break; + } + } +}; + +exports.readMatchState = function(state) { + this.isOpen = state === this.text; +}; + +exports.readPopupState = function(state) { + var popupLocationRegExp = /^\((-?[0-9\.E]+),(-?[0-9\.E]+),(-?[0-9\.E]+),(-?[0-9\.E]+)\)$/, + match = popupLocationRegExp.exec(state); + // Check if the state matches the location regexp + if(match) { + // If so, we're open + this.isOpen = true; + // Get the location + this.popup = { + left: parseFloat(match[1]), + top: parseFloat(match[2]), + width: parseFloat(match[3]), + height: parseFloat(match[4]) + }; + } else { + // If not, we're closed + this.isOpen = false; + } +}; + +exports.handleClickEvent = function(event) { + if(event.type === "click" && this.type === "popup") { + // Cancel the popup if we get a click on it + if(this.stateTitle) { + this.renderer.renderTree.wiki.deleteTextReference(this.stateTitle); + } + event.preventDefault(); + return false; + } + return true; +}; + +exports.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"] || (this.stateTitle && changedTiddlers[this.stateTitle])) { + // 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()); + } + }); + } else { + // We don't need to refresh ourselves, so just refresh any child nodes + $tw.utils.each(this.children,function(node) { + if(node.refreshInDom) { + node.refreshInDom(changedTiddlers); + } + }); + } + // Position the content if required + this.postRenderInDom(); +}; + +exports.postRenderInDom = function() { + switch(this.type) { + case "popup": + if(this.isOpen) { + this.children[0].domNode.style.position = "absolute"; + this.children[0].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"; + 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"; + 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"; + 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"; + 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"; + 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"; + break; + } + } + break; + } +}; + +})(); diff --git a/core/templates/TagTemplate.tid b/core/templates/TagTemplate.tid index 5ea3f2ccd..ef453d98e 100644 --- a/core/templates/TagTemplate.tid +++ b/core/templates/TagTemplate.tid @@ -1,9 +1,14 @@ title: $:/templates/TagTemplate -<$popup class="btn-invisible"><$handle> -@@.label <$view field="title" format="text" />@@ -<$body> -Some popup - - +<$button popup="tagpopup" qualifyTiddlerTitles="yes" class="btn-invisible">@@.label <$view field="title" format="text" />@@ +<$reveal state="tagpopup" type="popup" position="below" qualifyTiddlerTitles="yes" >
+@@.dropdown-menu + +* <$view field="title" format="link" /> +*.divider +* <$list filter="[is[current]tagging[]]" >
  • <$view field="title" format="link" />
  • + +@@ + +