gd-bs
Version:
Bootstrap JavaScript, TypeScript and Web Components library.
410 lines (349 loc) • 14 kB
text/typescript
import { IModal, IModalProps, IModalOptions } from "./types";
import { Base } from "../base";
import { ClassNames } from "../classNames";
import { appendContent } from "../common";
import { HTML } from "./templates";
/**
* Modal Types
*/
export enum ModalTypes {
Small = 1,
Medium = 2,
Large = 3,
XLarge = 4,
Full = 5,
FullSmall = 6,
FullMedium = 7,
FullLarge = 8,
FullXLarge = 9
}
/**
* Modal Classes
*/
export const ModalClassNames = new ClassNames([
"modal-sm",
"",
"modal-lg",
"modal-xl",
"modal-fullscreen",
"modal-fullscreen-sm-down",
"modal-fullscreen-md-down",
"modal-fullscreen-lg-down",
"modal-fullscreen-xl-down"
]);
/**
* Modal
* @param props The modal properties.
*/
class _Modal extends Base<IModalProps> implements IModal {
private _autoClose: boolean = null;
private _eventCreated: boolean = false;
private _options: IModalOptions = null;
private _tranisitioningFl: boolean = false;
// Constructor
constructor(props: IModalProps, template: string = HTML) {
super(template, props);
// Configure the collapse
this.configure();
// Configure the events
this.configureEvents();
// Configure the parent
this.configureParent();
}
// Configure the card group
private configure() {
// Set the modal attributes
this.props.id ? this.el.id = this.props.id : null;
this.props.disableFade ? null : this.el.classList.add("fade");
// Update the dialog
let dialog = this.el.querySelector(".modal-dialog") as HTMLElement;
if (dialog) {
// Add the class name, based on the type
let className = ModalClassNames.getByType(this.props.type);
className ? dialog.classList.add(className) : null;
// Update the title
this.setTitle(this.props.title);
// See if we are hiding the close button
if (this.props.hideCloseButton) {
// Remove the close button
let closeButton = dialog.querySelector(".btn-close") as HTMLElement;
closeButton ? closeButton.classList.add("d-none") : null;
}
}
// Set the header
appendContent(this.el.querySelector(".modal-header"), this.props.header);
// Set the body
appendContent(this.el.querySelector(".modal-body"), this.props.body);
// Set the footer
appendContent(this.el.querySelector(".modal-footer"), this.props.footer);
// Get the modal options
this._options = this.props.options;
if (this._options) {
// Set the backdrop
if (typeof (this._options.backdrop) === "boolean") {
this.el.setAttribute("data-bs-backdrop", this._options.backdrop ? "true" : "false");
}
// Set the center option
if (dialog && typeof (this._options.centered) === "boolean") {
dialog.classList.add("modal-dialog-centered");
}
// Set the focus
if (typeof (this._options.focus) === "boolean") {
this.el.setAttribute("data-bs-focus", this._options.focus ? "true" : "false");
}
// Set the keyboard
if (typeof (this._options.keyboard) === "boolean") {
this.el.setAttribute("data-bs-keyboard", this._options.keyboard ? "true" : "false");
}
// Set the scrollable option
if (dialog && typeof (this._options.scrollable) === "boolean") {
dialog.classList.add("modal-dialog-scrollable");
}
// See if we are showing the modal
if (this._options.visible) {
// Toggle the modal
this.toggle();
}
}
}
// Configures the auto-close event
private configureAutoCloseEvent() {
// See if the event exists
if (this._eventCreated) { return; }
// Ensure the body exists
if (document.body) {
// Add a click event to the modal
document.body.addEventListener("click", (ev: MouseEvent) => {
// See if the auto close flag is set
if (this._autoClose) {
let elContent = (this.el as HTMLElement).querySelector(".modal-content");
// Do nothing if we are tranisitionsing
if (this._tranisitioningFl) { return; }
// Do nothing if we clicked within the modal
if (ev.composedPath().includes(elContent)) { return; }
else {
// Get the mouse coordinates
let x = ev.clientX;
let y = ev.clientY;
let elCoordinate = elContent.getBoundingClientRect();
// See if we clicked within the modal
if (x <= elCoordinate.right && x >= elCoordinate.left && y <= elCoordinate.bottom && y >= elCoordinate.top) { return; }
// Else, see if something was selected
else if (x == 0 && y == 0) { return; }
}
// Close the modal if it's visible
if (this.isVisible) { this.toggle(); }
}
});
// Set the flag
this._eventCreated = true;
} else {
// Add the load event
window.addEventListener("load", () => {
// Configure the event
this.configureAutoCloseEvent();
});
}
}
// Configure the events
private configureEvents() {
// Execute the events
this.props.onRenderHeader ? this.props.onRenderHeader(this.el.querySelector(".modal-header")) : null;
this.props.onRenderBody ? this.props.onRenderBody(this.el.querySelector(".modal-body")) : null;
this.props.onRenderFooter ? this.props.onRenderFooter(this.el.querySelector(".modal-footer")) : null;
// Get the close button
let elClose = this.el.querySelector(".btn-close");
if (elClose) {
// Add a click event
elClose.addEventListener("click", () => {
// Hide the modal
this.hide();
});
}
// See if the keyboard option is set
if (this._options && this._options.keyboard) {
// Add a click event
(this.el as HTMLElement).addEventListener("keydown", ev => {
// See if the escape key was clicked and the modal is visible
if (ev.code === "27" && this.isVisible) {
// Toggle the modal
this.toggle();
}
});
}
// Set the flag to determine if the modal is sticky
this.setAutoClose(this.props.options && typeof (this.props.options.autoClose) === "boolean" ? this.props.options.autoClose : true);
}
/**
* Public Interface
*/
// Hides the modal
hide() {
// See if a transition is currently happening
if (this._tranisitioningFl) {
// Wait for the transition to complete
let id = setInterval(() => {
// See if the transition is complete
if (!this._tranisitioningFl) {
// Stop the loop
clearInterval(id);
// Toggle the modal
this.isVisible ? this.toggle() : null;
}
}, 250);
} else {
// Toggle the modal
this.isVisible ? this.toggle() : null;
}
}
// Returns true if the modal is visible
get isVisible() { return this.el.classList.contains("show"); }
// Updates the auto close flag
setAutoClose(value: boolean) {
// Set the flag
this._autoClose = value;
// Configure the event if we are setting the value
this._autoClose ? this.configureAutoCloseEvent() : null;
}
// Updates the backdrop flag
setBackdrop(value: boolean) {
// Set the backdrop
this.el.setAttribute("data-bs-backdrop", value ? "true" : "false");
}
// Updates the visibility of the close button
setCloseButtonVisibility(showFl: boolean) {
// Get the close button
let closeButton = this.el.querySelector(".btn-close") as HTMLElement;
if (closeButton) {
// See if we are showing the button
if (showFl) {
// Show the button
closeButton.classList.remove("d-none");
} else {
// Hide the button
closeButton.classList.add("d-none");
}
}
}
// Updates the focus flag
setFocus(value: boolean) {
// Set the focus
if (typeof (this._options.focus) === "boolean") {
this.el.setAttribute("data-bs-focus", value ? "true" : "false");
}
}
// Updates the center option
setIsCentered(value: boolean) {
// Get the dialog
let dialog = this.el.querySelector(".modal-dialog") as HTMLElement;
if (dialog) {
// Add/Remove the class name
dialog.classList[value ? "add" : "remove"]("modal-dialog-centered");
}
}
// Updates the keyboard flag
setKeyboard(value: boolean) {
// Set the keyboard
if (typeof (this._options.keyboard) === "boolean") {
this.el.setAttribute("data-bs-keyboard", value ? "true" : "false");
}
}
// Updates the scrollable option
setScrollable(value: boolean) {
// Get the dialog
let dialog = this.el.querySelector(".modal-dialog") as HTMLElement;
if (dialog) {
// Add/Remove the class name
dialog.classList[value ? "add" : "remove"]("modal-dialog-scrollable");
}
}
// Updates the title
setTitle(title: string | Element) {
// Get the title
let elTitle = this.el.querySelector(".modal-title") as HTMLElement;
if (elTitle) {
// Clear the element
while (elTitle.firstChild) { elTitle.removeChild(elTitle.firstChild); }
// Append the content
appendContent(elTitle, title);
}
}
// Updates the type
setType(modalType: number) {
let dialog = this.el.querySelector(".modal-dialog") as HTMLElement;
// Parse the class names
ModalClassNames.parse(className => {
// Remove the class names
className ? dialog.classList.remove(className) : null;
});
// Set the class name
let className = ModalClassNames.getByType(modalType);
className ? dialog.classList.add(className) : null;
}
// Shows the modal
show() {
// See if a transition is currently happening
if (this._tranisitioningFl) {
// Wait for the transition to complete
let id = setInterval(() => {
// See if the transition is complete
if (!this._tranisitioningFl) {
// Stop the loop
clearInterval(id);
// Toggle the modal
this.isVisible ? null : this.toggle();
}
}, 250);
} else {
// Toggle the modal
this.isVisible ? null : this.toggle();
}
}
// Toggles the modal
toggle() {
let backdrop = document.querySelector(".modal-backdrop");
// Set the flag
this._tranisitioningFl = true;
// See if this modal is visible
if (this.isVisible) {
// Hide the modal
this.el.classList.remove("show");
// Wait for the animation to complete
setTimeout(() => {
// Hide the modal
this.el.style.display = "";
// Remove the backdrop
backdrop ? document.body.removeChild(backdrop) : null;
backdrop = null;
// Set the flag
this._tranisitioningFl = false;
// Call the event
this.props.onClose ? this.props.onClose(this.el) : null;
}, 250);
} else {
// Start the animation
this.el.classList.add("modal-open")
this.el.style.display = "block";
// Create the backdrop if we are showing it
let showBackdrop = this._options && typeof (this._options.backdrop) === "boolean" ? this._options.backdrop : true;
if (showBackdrop && backdrop == null) {
backdrop = document.createElement("div");
backdrop.classList.add("modal-backdrop");
backdrop.classList.add("fade");
backdrop.classList.add("show");
document.body.appendChild(backdrop);
}
// Set the focus
(this.el as HTMLElement).focus();
// Wait for the animation to complete
setTimeout(() => {
// Show the modal
this.el.classList.remove("modal-open");
this.el.classList.add("show");
// Set the flag
this._tranisitioningFl = false;
}, 250);
}
}
}
export const Modal = (props: IModalProps, template?: string): IModal => { return new _Modal(props, template); }