Current File : /home/pacjaorg/.trash/media.1/system/js/joomla-dialog.js |
/**
* @copyright (C) 2023 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
// Default template for the popup
const popupTemplate = `<div class="joomla-dialog-container">
<header class="joomla-dialog-header"></header>
<section class="joomla-dialog-body"></section>
<footer class="joomla-dialog-footer"></footer>
</div>`;
/**
* JoomlaDialog class for Joomla Dialog implementation.
* With use of <joomla-dialog> custom element as dialog holder.
*/
class JoomlaDialog extends HTMLElement {
/**
* The popup type, supported: inline, iframe, image, ajax.
* @type {string}
*/
// popupType = 'inline';
/**
* An optional text for header.
* @type {string}
*/
// textHeader = '';
/**
* An optional text for close button. Applied when no Buttons provided.
* @type {string}
*/
// textClose = 'Close';
/**
* Content string (html) for inline type popup.
* @type {string}
*/
// popupContent = '';
/**
* Source path for iframe, image, ajax.
* @type {string}
*/
// src = '';
/**
* An optional list of buttons, to be rendered in footer or header, or bottom or top of the popup body.
* Example:
* [{label: 'Yes', onClick: () => popup.close()},
* {label: 'No', onClick: () => popup.close(), className: 'btn btn-danger'},
* {label: 'Click me', onClick: () => popup.close(), location: 'header'}]
* @type {[]}
*/
// popupButtons = [];
/**
* Whether popup can be closed by Esc button.
*
* @type {boolean}
*/
// cancelable = true;
/**
* An optional limit for the popup width, any valid CSS value.
* @type {string}
*/
// width = '';
/**
* An optional height for the popup, any valid CSS value.
* @type {string}
*/
// height = '';
/**
* An optional Class names for header icon.
*
* @type {string}
*/
// iconHeader = '';
/**
* A template for the popup.
* @type {string|HTMLTemplateElement}
*/
// popupTemplate = popupTemplate;
/**
* The element where to attach the dialog, for cases when no parentElement exist, see show().
* This allows to keep the dialog in the same branch of DOM as the popupContent.
* @type {string|HTMLElement}
*/
// preferredParent = null;
/**
* Class constructor
* @param {Object} config
*/
constructor(config) {
super();
// Define default params (doing it here because browser support of public props)
this.popupType = this.getAttribute('type') || 'inline';
this.textHeader = this.getAttribute('text-header') || '';
this.iconHeader = '';
this.textClose = Joomla.Text._('JCLOSE', 'Close');
this.popupContent = '';
this.src = this.getAttribute('src') || '';
this.popupButtons = [];
this.cancelable = !this.hasAttribute('not-cancelable');
this.width = this.getAttribute('width') || '';
this.height = this.getAttribute('height') || '';
this.popupTemplate = popupTemplate;
this.preferredParent = null;
// @internal. Parent of the popupContent for cases when it is HTMLElement. Need for recovery on destroy().
this.popupContentSrcLocation = null;
if (!config) return;
// Check configurable properties
['popupType', 'textHeader', 'textClose', 'popupContent', 'src', 'popupButtons', 'cancelable', 'width', 'height', 'popupTemplate', 'iconHeader', 'id', 'preferredParent'].forEach(key => {
if (config[key] !== undefined) {
this[key] = config[key];
}
});
// Check class name
if (config.className) {
this.classList.add(...config.className.split(' '));
}
// Check dataset properties
if (config.data) {
Object.entries(config.data).forEach(([k, v]) => {
this.dataset[k] = v;
});
}
}
/**
* Internal. Connected Callback.
*/
connectedCallback() {
this.renderLayout();
}
/**
* Internal. Render a main layout, based on given template.
* @returns {JoomlaDialog}
*/
renderLayout() {
if (this.dialog) return this;
// On close callback
const onClose = () => {
this.dispatchEvent(new CustomEvent('joomla-dialog:close', {
bubbles: true
}));
};
const onCancel = event => {
if (!this.cancelable) {
// Prevent closing by Esc
event.preventDefault();
}
};
// Check for existing layout
if (this.firstElementChild && this.firstElementChild.nodeName === 'DIALOG') {
this.dialog = this.firstElementChild;
this.dialog.addEventListener('cancel', onCancel);
this.dialog.addEventListener('close', onClose);
this.popupTmplB = this.querySelector('.joomla-dialog-body') || this.dialog;
this.popupContentElement = this.popupTmplB;
return this;
}
// Render a template
let templateContent;
if (this.popupTemplate.tagName && this.popupTemplate.tagName === 'TEMPLATE') {
templateContent = this.popupTemplate.content.cloneNode(true);
} else {
const template = document.createElement('template');
template.innerHTML = this.popupTemplate;
templateContent = template.content;
}
this.dialog = document.createElement('dialog');
this.dialog.appendChild(templateContent);
this.dialog.addEventListener('cancel', onCancel);
this.dialog.addEventListener('close', onClose);
this.appendChild(this.dialog);
// Get template parts
this.popupTmplH = this.dialog.querySelector('.joomla-dialog-header');
this.popupTmplB = this.dialog.querySelector('.joomla-dialog-body');
this.popupTmplF = this.dialog.querySelector('.joomla-dialog-footer');
this.popupContentElement = null;
if (!this.popupTmplB) {
throw new Error('The popup body not found in the template.');
}
// Set the header
if (this.popupTmplH && this.textHeader) {
const h = document.createElement('h3');
h.insertAdjacentHTML('afterbegin', this.textHeader);
this.popupTmplH.insertAdjacentElement('afterbegin', h);
if (this.iconHeader) {
const i = document.createElement('span');
i.ariaHidden = true;
i.classList.add('header-icon');
i.classList.add(...this.iconHeader.split(' '));
this.popupTmplH.insertAdjacentElement('afterbegin', i);
}
}
// Set the body
this.renderBodyContent();
this.setAttribute('type', this.popupType);
// Create buttons if any
const buttons = this.popupButtons || [];
// Add at least one button to close the popup
if (!buttons.length) {
buttons.push({
label: '',
ariaLabel: this.textClose,
className: 'button-close btn-close',
data: {
buttonClose: ''
},
onClick: () => this.close(),
location: 'header'
});
}
// Buttons holders
const btnHHolder = document.createElement('div');
const btnFHolder = document.createElement('div');
btnHHolder.classList.add('buttons-holder');
btnFHolder.classList.add('buttons-holder');
this.popupButtons.forEach(btnData => {
const btn = document.createElement('button');
btn.type = 'button';
btn.textContent = btnData.label || '';
btn.ariaLabel = btnData.ariaLabel || null;
if (btnData.className) {
btn.classList.add(...btnData.className.split(' '));
} else {
btn.classList.add('button', 'button-primary', 'btn', 'btn-primary');
}
if (btnData.data) {
Object.entries(btnData.data).forEach(([k, v]) => {
btn.dataset[k] = v;
});
if (btnData.data.dialogClose !== undefined) {
btnData.onClick = () => this.close();
}
if (btnData.data.dialogDestroy !== undefined) {
btnData.onClick = () => this.destroy();
}
}
if (btnData.onClick) {
btn.addEventListener('click', btnData.onClick);
}
if (btnData.location === 'header') {
btnHHolder.appendChild(btn);
} else {
btnFHolder.appendChild(btn);
}
});
if (btnHHolder.children.length) {
if (this.popupType === 'image' && !this.textHeader) {
this.popupTmplB.insertAdjacentElement('afterbegin', btnHHolder);
} else if (this.popupTmplH) {
this.popupTmplH.insertAdjacentElement('beforeend', btnHHolder);
} else {
this.popupTmplB.insertAdjacentElement('afterbegin', btnHHolder);
}
}
if (btnFHolder.children.length) {
(this.popupTmplF || this.popupTmplB).insertAdjacentElement('beforeend', btnFHolder);
}
// Adjust the sizes if requested
if (this.width) {
this.dialog.style.width = '100%';
this.dialog.style.maxWidth = this.width;
}
if (this.height) {
this.dialog.style.height = this.height;
}
// Mark an empty template elements
if (this.popupTmplH && !this.popupTmplH.children.length) {
this.popupTmplH.classList.add('empty');
}
if (this.popupTmplF && !this.popupTmplF.children.length) {
this.popupTmplF.classList.add('empty');
}
return this;
}
/**
* Internal. Render the body content, based on popupType.
* @returns {JoomlaDialog}
*/
renderBodyContent() {
if (!this.popupTmplB || this.popupContentElement) return this;
// Callback for loaded content event listener
const onLoad = () => {
this.classList.add('loaded');
this.classList.remove('loading');
this.popupContentElement.removeEventListener('load', onLoad);
this.dispatchEvent(new CustomEvent('joomla-dialog:load'));
if (this.popupType === 'inline' || this.popupType === 'ajax') {
// Dispatch joomla:updated for inline content
this.popupContentElement.dispatchEvent(new CustomEvent('joomla:updated', {
bubbles: true,
cancelable: true
}));
}
};
this.classList.add('loading');
switch (this.popupType) {
// Create an Inline content
case 'inline':
{
let inlineContent = this.popupContent;
// Check for content selector: src: '#content-selector' or src: '.content-selector'
if (!inlineContent && this.src && (this.src[0] === '.' || this.src[0] === '#')) {
inlineContent = document.querySelector(this.src);
this.popupContent = inlineContent;
}
if (inlineContent instanceof HTMLElement) {
// Render content provided as HTMLElement
if (inlineContent.nodeName === 'TEMPLATE') {
this.popupTmplB.appendChild(inlineContent.content.cloneNode(true));
} else {
// Store parent reference to be able to recover after the popup is destroyed
this.popupContentSrcLocation = {
parent: inlineContent.parentElement,
nextSibling: inlineContent.nextSibling
};
this.popupTmplB.appendChild(inlineContent);
}
} else {
// Render content string
this.popupTmplB.insertAdjacentHTML('afterbegin', Joomla.sanitizeHtml(inlineContent));
}
this.popupContentElement = this.popupTmplB;
onLoad();
break;
}
// Create an IFrame content
case 'iframe':
{
const frame = document.createElement('iframe');
frame.addEventListener('load', onLoad);
frame.src = this.src;
// Enlarge default size of iframe, make sure it is usable without extra styling
frame.width = '100%';
frame.height = '720';
if (!this.width) {
frame.style.maxWidth = '100%';
frame.width = '1024';
}
frame.classList.add('iframe-content');
this.popupContentElement = frame;
this.popupTmplB.appendChild(frame);
break;
}
// Create an Image content
case 'image':
{
const img = document.createElement('img');
img.addEventListener('load', onLoad);
img.src = this.src;
this.popupContentElement = img;
this.popupTmplB.appendChild(img);
break;
}
// Create an AJAX content
case 'ajax':
{
fetch(this.src).then(response => {
if (response.status !== 200) {
throw new Error(response.statusText);
}
return response.text();
}).then(text => {
this.popupTmplB.insertAdjacentHTML('afterbegin', Joomla.sanitizeHtml(text));
this.popupContentElement = this.popupTmplB;
onLoad();
}).catch(error => {
throw error;
});
break;
}
default:
{
throw new Error('Unknown popup type requested');
}
}
return this;
}
/**
* Internal. Find an Element to be used as parent element,
* for cases when Dialog does not have one already. See show().
*
* @returns {HTMLElement|boolean}
*/
findPreferredParent() {
let parent;
if (this.preferredParent instanceof HTMLElement) {
// We have configured one already
parent = this.preferredParent;
} else if (this.preferredParent) {
// Query Document
parent = document.querySelector(this.preferredParent);
} else if (this.popupType === 'inline') {
// Pick the parent element of the Content
let inlineContent = this.popupContent;
// Check for content selector: src: '#content-selector' or src: '.content-selector'
if (!inlineContent && this.src && (this.src[0] === '.' || this.src[0] === '#')) {
inlineContent = document.querySelector(this.src);
parent = inlineContent ? inlineContent.parentElement : false;
}
}
return parent || false;
}
/**
* Return the popup header element.
* @returns {HTMLElement|boolean}
*/
getHeader() {
this.renderLayout();
return this.popupTmplH || false;
}
/**
* Return the popup body element.
* @returns {HTMLElement}
*/
getBody() {
this.renderLayout();
return this.popupTmplB;
}
/**
* Return the popup content element, or body for inline and ajax types.
* @returns {HTMLElement}
*/
getBodyContent() {
this.renderLayout();
return this.popupContentElement || this.popupTmplB;
}
/**
* Return the popup footer element.
* @returns {HTMLElement|boolean}
*/
getFooter() {
this.renderLayout();
return this.popupTmplF || false;
}
/**
* Open the popup as modal window.
* Will append the element to Document body if not appended before.
*
* @returns {JoomlaDialog}
*/
show() {
// Check whether the element already attached to DOM
if (!this.parentElement) {
// Check for preferred parent to attach to DOM
const parent = this.findPreferredParent();
(parent || document.body).appendChild(this);
}
this.dialog.showModal();
this.dispatchEvent(new CustomEvent('joomla-dialog:open', {
bubbles: true
}));
return this;
}
/**
* Alias for show() method.
* @returns {JoomlaDialog}
*/
open() {
return this.show();
}
/**
* Closes the popup
*
* @returns {JoomlaDialog}
*/
close() {
if (!this.dialog) {
throw new Error('Calling close for non opened dialog is discouraged.');
}
this.dialog.close();
return this;
}
/**
* Alias for close() method.
* @returns {JoomlaDialog}
*/
hide() {
return this.close();
}
/**
* Destroys the popup.
*/
destroy() {
if (!this.dialog) {
return;
}
this.dialog.close();
this.removeChild(this.dialog);
this.parentElement.removeChild(this);
// Restore original location of the popup content element
if (this.popupContentSrcLocation && this.popupContent) {
const {
parent,
nextSibling
} = this.popupContentSrcLocation;
parent.insertBefore(this.popupContent, nextSibling);
}
this.dialog = null;
this.popupTmplH = null;
this.popupTmplB = null;
this.popupTmplF = null;
this.popupContentElement = null;
this.popupContentSrcLocation = null;
}
/**
* Helper method to show an Alert.
*
* @param {String} message
* @param {String} title
* @returns {Promise}
*/
static alert(message, title) {
return new Promise(resolve => {
const popup = new this();
popup.popupType = 'inline';
popup.popupContent = message;
popup.textHeader = title || Joomla.Text._('INFO', 'Info');
popup.popupButtons = [{
label: Joomla.Text._('JOK', 'Okay'),
data: {
buttonOk: ''
},
onClick: () => popup.close()
}];
popup.classList.add('joomla-dialog-alert');
popup.addEventListener('joomla-dialog:close', () => {
popup.destroy();
resolve();
});
popup.show();
});
}
/**
* Helper method to show a Confirmation popup.
*
* @param {String} message
* @param {String} title
* @returns {Promise}
*/
static confirm(message, title) {
return new Promise(resolve => {
let result = false;
const popup = new this();
popup.popupType = 'inline';
popup.popupContent = message;
popup.textHeader = title || Joomla.Text._('INFO', 'Info');
popup.popupButtons = [{
label: Joomla.Text._('JYES', 'Yes'),
data: {
buttonOk: ''
},
onClick: () => {
result = true;
popup.destroy();
}
}, {
label: Joomla.Text._('JNO', 'No'),
data: {
buttonCancel: ''
},
onClick: () => {
result = false;
popup.destroy();
},
className: 'button button-secondary btn btn-outline-secondary'
}];
popup.cancelable = false;
popup.classList.add('joomla-dialog-confirm');
popup.addEventListener('joomla-dialog:close', () => resolve(result));
popup.show();
});
}
}
customElements.define('joomla-dialog', JoomlaDialog);
export { JoomlaDialog as default };