/*\ title: $:/core/modules/widgets/reveal.js type: application/javascript module-type: widget Reveal widget \*/ (function(){ /*jslint node: true, browser: true */ /*global $tw: false */ "use strict"; var Widget = require("$:/core/modules/widgets/widget.js").widget; var Popup = require("$:/core/modules/utils/dom/popup.js"); var RevealWidget = function(parseTreeNode,options) { this.initialise(parseTreeNode,options); }; /* Inherit from the base widget class */ RevealWidget.prototype = new Widget(); /* Render this widget into the DOM */ RevealWidget.prototype.render = function(parent,nextSibling) { this.parentDomNode = parent; this.computeAttributes(); this.execute(); var tag = this.parseTreeNode.isBlock ? "div" : "span"; if(this.revealTag && $tw.config.htmlUnsafeElements.indexOf(this.revealTag) === -1) { tag = this.revealTag; } var domNode = this.document.createElement(tag); this.domNode = domNode; this.assignDomNodeClasses(); if(this.style) { domNode.setAttribute("style",this.style); } if(this.direction) { domNode.setAttribute("dir",this.direction); } parent.insertBefore(domNode,nextSibling); this.renderChildren(domNode,null); if(!domNode.isTiddlyWikiFakeDom && this.type === "popup" && this.isOpen) { this.positionPopup(domNode); $tw.utils.addClass(domNode,"tc-popup"); // Make sure that clicks don't dismiss popups within the revealed content } if(!this.isOpen) { domNode.setAttribute("hidden","true"); } this.domNodes.push(domNode); }; RevealWidget.prototype.positionPopup = function(domNode) { domNode.style.position = "absolute"; domNode.style.zIndex = "1000"; var left,top; switch(this.position) { case "left": left = this.popup.left - domNode.offsetWidth; top = this.popup.top; break; case "above": left = this.popup.left; top = this.popup.top - domNode.offsetHeight; break; case "aboveright": left = this.popup.left + this.popup.width; top = this.popup.top + this.popup.height - domNode.offsetHeight; break; case "belowright": left = this.popup.left + this.popup.width; top = this.popup.top + this.popup.height; break; case "right": left = this.popup.left + this.popup.width; top = this.popup.top; break; case "belowleft": left = this.popup.left + this.popup.width - domNode.offsetWidth; top = this.popup.top + this.popup.height; break; case "aboveleft": left = this.popup.left - domNode.offsetWidth; top = this.popup.top - domNode.offsetHeight; break; default: // Below left = this.popup.left; top = this.popup.top + this.popup.height; break; } if(!this.positionAllowNegative) { left = Math.max(0,left); top = Math.max(0,top); } if (this.popup.absolute) { // Traverse the offsetParent chain and correct the offset to make it relative to the parent node. for (var offsetParentDomNode = domNode.offsetParent; offsetParentDomNode; offsetParentDomNode = offsetParentDomNode.offsetParent) { left -= offsetParentDomNode.offsetLeft; top -= offsetParentDomNode.offsetTop; } } domNode.style.left = left + "px"; domNode.style.top = top + "px"; }; /* Compute the internal state of the widget */ RevealWidget.prototype.execute = function() { // Get our parameters this.state = this.getAttribute("state"); this.revealTag = this.getAttribute("tag"); this.type = this.getAttribute("type"); this.text = this.getAttribute("text"); this.position = this.getAttribute("position"); this.positionAllowNegative = this.getAttribute("positionAllowNegative") === "yes"; // class attribute handled in assignDomNodeClasses() this.style = this.getAttribute("style",""); this["default"] = this.getAttribute("default",""); this.animate = this.getAttribute("animate","no"); this.retain = this.getAttribute("retain","no"); this.direction = this.getAttribute("dir"); this.openAnimation = this.animate === "no" ? undefined : "open"; this.closeAnimation = this.animate === "no" ? undefined : "close"; this.updatePopupPosition = this.getAttribute("updatePopupPosition","no") === "yes"; // Compute the title of the state tiddler and read it this.stateTiddlerTitle = this.state; this.stateTitle = this.getAttribute("stateTitle"); this.stateField = this.getAttribute("stateField"); this.stateIndex = this.getAttribute("stateIndex"); this.readState(); // Construct the child widgets var childNodes = this.isOpen ? this.parseTreeNode.children : []; this.hasChildNodes = this.isOpen; this.makeChildWidgets(childNodes); }; /* Read the state tiddler */ RevealWidget.prototype.readState = function() { // Read the information from the state tiddler var state, defaultState = this["default"]; if(this.stateTitle) { var stateTitleTiddler = this.wiki.getTiddler(this.stateTitle); if(this.stateField) { state = stateTitleTiddler ? stateTitleTiddler.getFieldString(this.stateField) || defaultState : defaultState; } else if(this.stateIndex) { state = stateTitleTiddler ? this.wiki.extractTiddlerDataItem(this.stateTitle,this.stateIndex) || defaultState : defaultState; } else if(stateTitleTiddler) { state = this.wiki.getTiddlerText(this.stateTitle) || defaultState; } else { state = defaultState; } } else { state = this.stateTiddlerTitle ? this.wiki.getTextReference(this.state,this["default"],this.getVariable("currentTiddler")) : this["default"]; } if(state === null) { state = this["default"]; } switch(this.type) { case "popup": this.readPopupState(state); break; case "match": this.isOpen = this.text === state; break; case "nomatch": this.isOpen = this.text !== state; break; case "lt": this.isOpen = !!(this.compareStateText(state) < 0); break; case "gt": this.isOpen = !!(this.compareStateText(state) > 0); break; case "lteq": this.isOpen = !(this.compareStateText(state) > 0); break; case "gteq": this.isOpen = !(this.compareStateText(state) < 0); break; } }; RevealWidget.prototype.compareStateText = function(state) { return state.localeCompare(this.text,undefined,{numeric: true,sensitivity: "case"}); }; RevealWidget.prototype.readPopupState = function(state) { this.popup = Popup.parseCoordinates(state); // Check if the state matches the location regexp if(this.popup) { // If so, we're open this.isOpen = true; } else { // If not, we're closed this.isOpen = false; } }; RevealWidget.prototype.assignDomNodeClasses = function() { var classes = this.getAttribute("class","").split(" "); classes.push("tc-reveal"); this.domNode.className = classes.join(" "); }; /* Selectively refreshes the widget if needed. Returns true if the widget or any of its children needed re-rendering */ RevealWidget.prototype.refresh = function(changedTiddlers) { var changedAttributes = this.computeAttributes(); if(changedAttributes.state || changedAttributes.type || changedAttributes.text || changedAttributes.position || changedAttributes.positionAllowNegative || changedAttributes["default"] || changedAttributes.animate || changedAttributes.stateTitle || changedAttributes.stateField || changedAttributes.stateIndex || changedAttributes.direction) { this.refreshSelf(); return true; } else { var currentlyOpen = this.isOpen; this.readState(); if(this.isOpen !== currentlyOpen) { if(this.retain === "yes") { this.updateState(); } else { this.refreshSelf(); return true; } } else if(this.type === "popup" && this.isOpen && this.updatePopupPosition && (changedTiddlers[this.state] || changedTiddlers[this.stateTitle])) { this.positionPopup(this.domNode); } if(changedAttributes.style) { this.domNode.style = this.getAttribute("style",""); } if(changedAttributes["class"]) { this.assignDomNodeClasses(); } return this.refreshChildren(changedTiddlers); } }; /* Called by refresh() to dynamically show or hide the content */ RevealWidget.prototype.updateState = function() { var self = this; // Read the current state this.readState(); // Construct the child nodes if needed var domNode = this.domNodes[0]; if(this.isOpen && !this.hasChildNodes) { this.hasChildNodes = true; this.makeChildWidgets(this.parseTreeNode.children); this.renderChildren(domNode,null); } // Animate our DOM node if(!domNode.isTiddlyWikiFakeDom && this.type === "popup" && this.isOpen) { this.positionPopup(domNode); $tw.utils.addClass(domNode,"tc-popup"); // Make sure that clicks don't dismiss popups within the revealed content } if(this.isOpen) { domNode.removeAttribute("hidden"); $tw.anim.perform(this.openAnimation,domNode); } else { $tw.anim.perform(this.closeAnimation,domNode,{callback: function() { //make sure that the state hasn't changed during the close animation self.readState() if(!self.isOpen) { domNode.setAttribute("hidden","true"); } }}); } }; exports.reveal = RevealWidget; })();