/*\ title: $:/core/modules/utils/dom/modal.js type: application/javascript module-type: utils Modal message mechanism \*/ (function(){ /*jslint node: true, browser: true */ /*global $tw: false */ "use strict"; var widget = require("$:/core/modules/widgets/widget.js"), navigator = require("$:/core/modules/widgets/navigator.js"), dm = $tw.utils.domMaker; var Modal = function(wiki) { this.wiki = wiki; this.modalCount = 0; }; /* Display a modal dialogue title: Title of tiddler to display options: see below Options include: downloadLink: Text of a big download link to include variables: variables to be passed to the modal event: optional DOM event that initiated the modal progress: set to true to add a progress bar onclose: callback for when the modal is closed */ Modal.prototype.display = function(title,options) { options = options || {}; this.srcDocument = options.variables && (options.variables.rootwindow === "true" || options.variables.rootwindow === "yes") ? document : (options.event && options.event.event && options.event.event.target ? options.event.event.target.ownerDocument : document); this.srcWindow = this.srcDocument.defaultView; var self = this, refreshHandler, duration = $tw.utils.getAnimationDuration(), tiddler = this.wiki.getTiddler(title); // Don't do anything if the tiddler doesn't exist if(!tiddler) { return; } // Create the variables var variables = $tw.utils.extend({ currentTiddler: title, "tv-story-list": (options.event && options.event.widget ? options.event.widget.getVariable("tv-story-list") : ""), "tv-history-list": (options.event && options.event.widget ? options.event.widget.getVariable("tv-history-list") : "") },options.variables); // Create the wrapper divs var wrapper = this.srcDocument.createElement("div"), modalBackdrop = this.srcDocument.createElement("div"), modalWrapper = this.srcDocument.createElement("div"), modalHeader = this.srcDocument.createElement("div"), headerTitle = this.srcDocument.createElement("h3"), modalBody = this.srcDocument.createElement("div"), modalProgress = this.srcDocument.createElement("div"), modalLink = this.srcDocument.createElement("a"), modalFooter = this.srcDocument.createElement("div"), modalFooterHelp = this.srcDocument.createElement("span"), modalFooterButtons = this.srcDocument.createElement("span"); // Up the modal count and adjust the body class this.modalCount++; this.adjustPageClass(); // Add classes $tw.utils.addClass(wrapper,"tc-modal-wrapper"); if(tiddler.fields && tiddler.fields.class) { $tw.utils.addClass(wrapper,tiddler.fields.class); } $tw.utils.addClass(modalBackdrop,"tc-modal-backdrop"); $tw.utils.addClass(modalWrapper,"tc-modal"); $tw.utils.addClass(modalHeader,"tc-modal-header"); $tw.utils.addClass(modalBody,"tc-modal-body"); $tw.utils.addClass(modalProgress,"tc-modal-progress"); $tw.utils.addClass(modalFooter,"tc-modal-footer"); // Join them together wrapper.appendChild(modalBackdrop); wrapper.appendChild(modalWrapper); modalHeader.appendChild(headerTitle); modalWrapper.appendChild(modalHeader); modalWrapper.appendChild(modalBody); if(options.progress) { var modalProgressBar = this.srcDocument.createElement("div"); modalProgressBar.className = "tc-modal-progress-bar"; modalProgress.appendChild(modalProgressBar); var modalProgressPercent = this.srcDocument.createElement("div"); modalProgressPercent.className = "tc-modal-progress-percent"; modalProgress.appendChild(modalProgressPercent); modalWrapper.appendChild(modalProgress); } modalFooter.appendChild(modalFooterHelp); modalFooter.appendChild(modalFooterButtons); modalWrapper.appendChild(modalFooter); var navigatorTree = { "type": "navigator", "attributes": { "story": { "name": "story", "type": "string", "value": variables["tv-story-list"] }, "history": { "name": "history", "type": "string", "value": variables["tv-history-list"] } }, "tag": "$navigator", "isBlock": true, "children": [] }; var navigatorWidgetNode = new navigator.navigator(navigatorTree, { wiki: this.wiki, document : this.srcDocument, parentWidget: $tw.rootWidget }); navigatorWidgetNode.render(modalBody,null); // Render the title of the message var headerWidgetNode = this.wiki.makeTranscludeWidget(title,{ field: "subtitle", mode: "inline", children: [{ type: "text", attributes: { text: { type: "string", value: title }}}], parentWidget: navigatorWidgetNode, document: this.srcDocument, variables: variables, importPageMacros: true }); headerWidgetNode.render(headerTitle,null); // Render the body of the message var bodyWidgetNode = this.wiki.makeTranscludeWidget(title,{ parentWidget: navigatorWidgetNode, document: this.srcDocument, variables: variables, importPageMacros: true }); bodyWidgetNode.render(modalBody,null); // Setup the link if present if(options.downloadLink) { modalLink.href = options.downloadLink; modalLink.appendChild(this.srcDocument.createTextNode("Right-click to save changes")); modalBody.appendChild(modalLink); } // Render the footer of the message if(tiddler.fields && tiddler.fields.help) { var link = this.srcDocument.createElement("a"); link.setAttribute("href",tiddler.fields.help); link.setAttribute("target","_blank"); link.setAttribute("rel","noopener noreferrer"); link.appendChild(this.srcDocument.createTextNode("Help")); modalFooterHelp.appendChild(link); modalFooterHelp.style.float = "left"; } var footerWidgetNode = this.wiki.makeTranscludeWidget(title,{ field: "footer", mode: "inline", children: [{ type: "button", attributes: { message: { type: "string", value: "tm-close-tiddler" } }, children: [{ type: "text", attributes: { text: { type: "string", value: $tw.language.getString("Buttons/Close/Caption") }}} ]}], parentWidget: navigatorWidgetNode, document: this.srcDocument, variables: variables, importPageMacros: true }); footerWidgetNode.render(modalFooterButtons,null); // Set up the refresh handler refreshHandler = function(changes) { headerWidgetNode.refresh(changes,modalHeader,null); bodyWidgetNode.refresh(changes,modalBody,null); footerWidgetNode.refresh(changes,modalFooterButtons,null); }; this.wiki.addEventListener("change",refreshHandler); // Add the close event handler var closeHandler = function(event) { // Call the onclose handler if(options.onclose) { options.onclose(event); } // Remove our refresh handler self.wiki.removeEventListener("change",refreshHandler); // Decrease the modal count and adjust the body class self.modalCount--; self.adjustPageClass(); // Force layout and animate the modal message away $tw.utils.forceLayout(modalBackdrop); $tw.utils.forceLayout(modalWrapper); $tw.utils.setStyle(modalBackdrop,[ {opacity: "0"} ]); $tw.utils.setStyle(modalWrapper,[ {transform: "translateY(" + self.srcWindow.innerHeight + "px)"} ]); // Set up an event for the transition end self.srcWindow.setTimeout(function() { if(wrapper.parentNode) { // Remove the modal message from the DOM self.srcDocument.body.removeChild(wrapper); } },duration); // Don't let anyone else handle the tm-close-tiddler message return false; }; headerWidgetNode.addEventListener("tm-close-tiddler",closeHandler,false); bodyWidgetNode.addEventListener("tm-close-tiddler",closeHandler,false); footerWidgetNode.addEventListener("tm-close-tiddler",closeHandler,false); // Set the initial styles for the message $tw.utils.setStyle(modalBackdrop,[ {opacity: "0"} ]); $tw.utils.setStyle(modalWrapper,[ {transformOrigin: "0% 0%"}, {transform: "translateY(" + (-this.srcWindow.innerHeight) + "px)"} ]); // Put the message into the document this.srcDocument.body.appendChild(wrapper); // Set up animation for the styles $tw.utils.setStyle(modalBackdrop,[ {transition: "opacity " + duration + "ms ease-out"} ]); $tw.utils.setStyle(modalWrapper,[ {transition: $tw.utils.roundTripPropertyName("transform") + " " + duration + "ms ease-in-out"} ]); // Force layout $tw.utils.forceLayout(modalBackdrop); $tw.utils.forceLayout(modalWrapper); // Set final animated styles $tw.utils.setStyle(modalBackdrop,[ {opacity: "0.7"} ]); $tw.utils.setStyle(modalWrapper,[ {transform: "translateY(0px)"} ]); // Return the wrapper node return { domNode: wrapper, closeHandler: closeHandler, setProgress: function(numerator,denominator) { // Remove old progress while(modalProgressPercent.hasChildNodes()) { modalProgressPercent.removeChild(modalProgressPercent.firstChild); } // Set new text var percent = (numerator * 100 /denominator).toFixed(2) + "%"; modalProgressPercent.appendChild(self.srcDocument.createTextNode(percent)); // Set bar width modalProgressBar.style.width = percent; } }; }; Modal.prototype.adjustPageClass = function() { var windowContainer = $tw.pageContainer ? ($tw.pageContainer === this.srcDocument.body.firstChild ? $tw.pageContainer : this.srcDocument.body.firstChild) : null; if(windowContainer) { $tw.utils.toggleClass(windowContainer,"tc-modal-displayed",this.modalCount > 0); } $tw.utils.toggleClass(this.srcDocument.body,"tc-modal-prevent-scroll",this.modalCount > 0); }; exports.Modal = Modal; })();