/** * Copyright since 2007 PrestaShop SA and Contributors * PrestaShop is an International Registered Trademark & Property of PrestaShop SA * * NOTICE OF LICENSE * * This source file is subject to the Open Software License (OSL 3.0) * that is bundled with this package in the file LICENSE.md. * It is also available through the world-wide-web at this URL: * https://opensource.org/licenses/OSL-3.0 * If you did not receive a copy of the license and are unable to * obtain it through the world-wide-web, please send an email * to license@prestashop.com so we can send you a copy immediately. * * DISCLAIMER * * Do not edit or add to this file if you wish to upgrade PrestaShop to newer * versions in the future. If you wish to customize PrestaShop for your * needs please refer to https://devdocs.prestashop.com/ for more information. * * @author PrestaShop SA and Contributors * @copyright Since 2007 PrestaShop SA and Contributors * @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0) */ /* eslint max-classes-per-file: ["error", 2] */ export interface ModalContainerType { container: HTMLElement; dialog: HTMLElement; content: HTMLElement; body: HTMLElement; message: HTMLElement; header: HTMLElement; title?: HTMLElement; closeIcon?: HTMLButtonElement; } export interface ModalCoreType { show: () => void; hide: () => void; } export interface ModalType extends ModalCoreType { modal: ModalContainerType; render: (content: string) => void; } export type CssProps = Record; export type ModalParams = { id: string; closable?: boolean; modalTitle?: string dialogStyle?: CssProps; closeCallback?: () => void; } export type InputModalParams = Partial; /** * This class is used to build the modal DOM elements, it is not usable as is because it doesn't even have a show * method and the elements are created but not added to the DOM. It just creates a basic DOM structure of a * Bootstrap modal, thus keeping the logic class of the modal separated. * * This is the most basic modal container (only the modal and dialog box, with a close icon * and an optional title). No footer and no content is handled. * * @param {ModalParams} params */ export class ModalContainer implements ModalContainerType { container!: HTMLElement; dialog!: HTMLElement; content!: HTMLElement; message!: HTMLElement; header!: HTMLElement; title?: HTMLElement; closeIcon?: HTMLButtonElement; body!: HTMLElement; constructor(inputParams: InputModalParams) { const params: ModalParams = { id: 'confirm-modal', closable: false, ...inputParams, }; this.buildModalContainer(params); } protected buildModalContainer(params: ModalParams): void { // Main modal element this.container = document.createElement('div'); this.container.classList.add('modal', 'fade'); this.container.id = params.id; // Modal dialog element this.dialog = document.createElement('div'); this.dialog.classList.add('modal-dialog'); if (params.dialogStyle) { Object.keys(params.dialogStyle).forEach((key: string) => { // @ts-ignore this.dialog.style[key] = params.dialogStyle[key]; }); } // Modal content element this.content = document.createElement('div'); this.content.classList.add('modal-content'); // Modal message element this.message = document.createElement('p'); this.message.classList.add('modal-message'); // Modal header element this.header = document.createElement('div'); this.header.classList.add('modal-header'); // Modal title element if (params.modalTitle) { this.title = document.createElement('h4'); this.title.classList.add('modal-title'); this.title.innerHTML = params.modalTitle; } // Modal close button icon this.closeIcon = document.createElement('button'); this.closeIcon.classList.add('close'); this.closeIcon.setAttribute('type', 'button'); this.closeIcon.dataset.dismiss = 'modal'; this.closeIcon.innerHTML = '×'; // Modal body element this.body = document.createElement('div'); this.body.classList.add('modal-body', 'text-left', 'font-weight-normal'); // Constructing the modal if (this.title) { this.header.appendChild(this.title); } this.header.appendChild(this.closeIcon); this.content.append(this.header, this.body); this.body.appendChild(this.message); this.dialog.appendChild(this.content); this.container.appendChild(this.dialog); } } /** * Modal component * * @param {InputModalParams} params * @param {Function} closeCallback */ export class Modal implements ModalType { modal!: ModalContainerType; protected $modal!: JQuery; constructor( inputParams: InputModalParams, ) { const params: ModalParams = { id: 'confirm-modal', closable: false, dialogStyle: {}, ...inputParams, }; this.initContainer(params); } protected initContainer(params: ModalParams): void { // Construct the modal, check if it already exists This allows child classes to use their custom container if (!this.modal) { this.modal = new ModalContainer(params); } // jQuery modal object this.$modal = $(this.modal.container); const {id, closable} = params; this.$modal.modal({ backdrop: closable ? true : 'static', keyboard: closable !== undefined ? closable : true, show: false, }); this.$modal.on('hidden.bs.modal', () => { const modal = document.querySelector(`#${id}`); if (modal) { modal.remove(); } if (params.closeCallback) { params.closeCallback(); } }); document.body.appendChild(this.modal.container); } setTitle(modalTitle: string): this { if (!this.modal.title) { this.modal.title = document.createElement('h4'); this.modal.title.classList.add('modal-title'); if (this.modal.closeIcon) { this.modal.header.insertBefore(this.modal.title, this.modal.closeIcon); } else { this.modal.header.appendChild(this.modal.title); } } this.modal.title.innerHTML = modalTitle; return this; } render(content: string): this { this.modal.message.innerHTML = content; return this; } show(): this { this.$modal.modal('show'); return this; } hide(): this { this.$modal.modal('hide'); // Sometimes modal animation is still in progress and hiding fails, so we attach event listener for that case. this.$modal.on('shown.bs.modal', () => { this.$modal.modal('hide'); this.$modal.off('shown.bs.modal'); }); return this; } } export default Modal;