1
0
mirror of https://github.com/Jermolene/TiddlyWiki5 synced 2024-12-26 18:10:27 +00:00
TiddlyWiki5/core/modules/utils/dom/modal.js
Mario Pietsch a409536ad0
Prevent scrolling of the page when modals are displayed (#5816)
* prevent scroll-chaining in modals

* make body overflow hidden to prevent background scrolling
2021-06-27 16:24:06 +01:00

252 lines
7.9 KiB
JavaScript

/*\
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");
var navigator = require("$:/core/modules/widgets/navigator.js");
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
*/
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"),
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(modalFooter,"tc-modal-footer");
// Join them together
wrapper.appendChild(modalBackdrop);
wrapper.appendChild(modalWrapper);
modalHeader.appendChild(headerTitle);
modalWrapper.appendChild(modalHeader);
modalWrapper.appendChild(modalBody);
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) {
// 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)"}
]);
};
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;
})();