319 lines
10 KiB
JavaScript
319 lines
10 KiB
JavaScript
class AlertElement extends HTMLElement {
|
|
constructor() {
|
|
super();
|
|
|
|
// Bindings
|
|
this.close = this.close.bind(this);
|
|
this.destroyCloseButton = this.destroyCloseButton.bind(this);
|
|
this.createCloseButton = this.createCloseButton.bind(this);
|
|
this.onMutation = this.onMutation.bind(this);
|
|
this.observer = new MutationObserver(this.onMutation);
|
|
this.observer.observe(this, {
|
|
attributes: false,
|
|
childList: true,
|
|
subtree: true
|
|
});
|
|
|
|
// Handle the fade in animation
|
|
this.addEventListener('animationend', event => {
|
|
if (event.animationName === 'joomla-alert-fade-in' && event.target === this) {
|
|
this.dispatchEvent(new CustomEvent('joomla.alert.shown'));
|
|
this.style.removeProperty('animationName');
|
|
}
|
|
});
|
|
|
|
// Handle the fade out animation
|
|
this.addEventListener('animationend', event => {
|
|
if (event.animationName === 'joomla-alert-fade-out' && event.target === this) {
|
|
this.dispatchEvent(new CustomEvent('joomla.alert.closed'));
|
|
this.remove();
|
|
}
|
|
});
|
|
}
|
|
|
|
/* Attributes to monitor */
|
|
static get observedAttributes() {
|
|
return ['type', 'role', 'dismiss', 'auto-dismiss', 'close-text'];
|
|
}
|
|
get type() {
|
|
return this.getAttribute('type');
|
|
}
|
|
set type(value) {
|
|
this.setAttribute('type', value);
|
|
}
|
|
get role() {
|
|
return this.getAttribute('role');
|
|
}
|
|
set role(value) {
|
|
this.setAttribute('role', value);
|
|
}
|
|
get closeText() {
|
|
return this.getAttribute('close-text');
|
|
}
|
|
set closeText(value) {
|
|
this.setAttribute('close-text', value);
|
|
}
|
|
get dismiss() {
|
|
return this.getAttribute('dismiss');
|
|
}
|
|
set dismiss(value) {
|
|
this.setAttribute('dismiss', value);
|
|
}
|
|
get autodismiss() {
|
|
return this.getAttribute('auto-dismiss');
|
|
}
|
|
set autodismiss(value) {
|
|
this.setAttribute('auto-dismiss', value);
|
|
}
|
|
|
|
/* Lifecycle, element appended to the DOM */
|
|
connectedCallback() {
|
|
this.dispatchEvent(new CustomEvent('joomla.alert.show'));
|
|
this.style.animationName = 'joomla-alert-fade-in';
|
|
|
|
// Default to info
|
|
if (!this.type || !['info', 'warning', 'danger', 'success'].includes(this.type)) {
|
|
this.setAttribute('type', 'info');
|
|
}
|
|
// Default to alert
|
|
if (!this.role || !['alert', 'alertdialog'].includes(this.role)) {
|
|
this.setAttribute('role', 'alert');
|
|
}
|
|
|
|
// Hydrate the button
|
|
if (this.firstElementChild && this.firstElementChild.tagName === 'BUTTON') {
|
|
this.button = this.firstElementChild;
|
|
if (this.button.classList.contains('joomla-alert--close')) {
|
|
this.button.classList.add('joomla-alert--close');
|
|
}
|
|
if (this.button.innerHTML === '') {
|
|
this.button.innerHTML = '<span aria-hidden="true">×</span>';
|
|
}
|
|
if (!this.button.hasAttribute('aria-label')) {
|
|
this.button.setAttribute('aria-label', this.closeText);
|
|
}
|
|
}
|
|
|
|
// Append button
|
|
if (this.hasAttribute('dismiss') && !this.button) {
|
|
this.createCloseButton();
|
|
}
|
|
if (this.hasAttribute('auto-dismiss')) {
|
|
this.autoDismiss();
|
|
}
|
|
}
|
|
|
|
/* Lifecycle, element removed from the DOM */
|
|
disconnectedCallback() {
|
|
if (this.button) {
|
|
this.button.removeEventListener('click', this.close);
|
|
}
|
|
this.observer.disconnect();
|
|
}
|
|
|
|
/* Respond to attribute changes */
|
|
attributeChangedCallback(attr, oldValue, newValue) {
|
|
switch (attr) {
|
|
case 'type':
|
|
if (!newValue || newValue && ['info', 'warning', 'danger', 'success'].indexOf(newValue) === -1) {
|
|
this.type = 'info';
|
|
}
|
|
break;
|
|
case 'role':
|
|
if (!newValue || newValue && ['alert', 'alertdialog'].indexOf(newValue) === -1) {
|
|
this.role = 'alert';
|
|
}
|
|
break;
|
|
case 'dismiss':
|
|
if ((!newValue || newValue === '') && (!oldValue || oldValue === '')) {
|
|
if (this.button && !this.hasAttribute('dismiss')) {
|
|
this.destroyCloseButton();
|
|
} else if (!this.button && this.hasAttribute('dismiss')) {
|
|
this.createCloseButton();
|
|
}
|
|
} else if (this.button && newValue === 'false') {
|
|
this.destroyCloseButton();
|
|
} else if (!this.button && newValue !== 'false') {
|
|
this.createCloseButton();
|
|
}
|
|
break;
|
|
case 'close-text':
|
|
if (!newValue || newValue !== oldValue) {
|
|
if (this.button) {
|
|
this.button.setAttribute('aria-label', newValue);
|
|
}
|
|
}
|
|
break;
|
|
case 'auto-dismiss':
|
|
this.autoDismiss();
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* Observe added elements */
|
|
onMutation(mutationsList) {
|
|
// eslint-disable-next-line no-restricted-syntax
|
|
for (const mutation of mutationsList) {
|
|
if (mutation.type === 'childList') {
|
|
if (mutation.addedNodes.length) {
|
|
// Make sure that the button is always the first element
|
|
if (this.button && this.firstElementChild !== this.button) {
|
|
this.prepend(this.button);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Method to close the alert */
|
|
close() {
|
|
this.dispatchEvent(new CustomEvent('joomla.alert.close'));
|
|
this.style.animationName = 'joomla-alert-fade-out';
|
|
}
|
|
|
|
/* Method to create the close button */
|
|
createCloseButton() {
|
|
this.button = document.createElement('button');
|
|
this.button.setAttribute('type', 'button');
|
|
this.button.classList.add('joomla-alert--close');
|
|
this.button.innerHTML = '<span aria-hidden="true">×</span>';
|
|
this.button.setAttribute('aria-label', this.closeText);
|
|
this.insertAdjacentElement('afterbegin', this.button);
|
|
|
|
/* Add the required listener */
|
|
this.button.addEventListener('click', this.close);
|
|
}
|
|
|
|
/* Method to remove the close button */
|
|
destroyCloseButton() {
|
|
if (this.button) {
|
|
this.button.removeEventListener('click', this.close);
|
|
this.button.parentNode.removeChild(this.button);
|
|
this.button = null;
|
|
}
|
|
}
|
|
|
|
/* Method to auto-dismiss */
|
|
autoDismiss() {
|
|
const timer = parseInt(this.getAttribute('auto-dismiss'), 10);
|
|
setTimeout(this.close, timer >= 10 ? timer : 3000);
|
|
}
|
|
}
|
|
if (!customElements.get('joomla-alert')) {
|
|
customElements.define('joomla-alert', AlertElement);
|
|
}
|
|
|
|
/**
|
|
* @copyright (C) 2020 Open Source Matters, Inc. <https://www.joomla.org>
|
|
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
|
*/
|
|
|
|
/**
|
|
* Returns the container of the Messages
|
|
*
|
|
* @param {string|HTMLElement} container The container
|
|
*
|
|
* @returns {HTMLElement}
|
|
*/
|
|
const getMessageContainer = container => {
|
|
let messageContainer;
|
|
if (container instanceof HTMLElement) {
|
|
return container;
|
|
}
|
|
if (typeof container === 'undefined' || container && container === '#system-message-container') {
|
|
messageContainer = document.getElementById('system-message-container');
|
|
} else {
|
|
messageContainer = document.querySelector(container);
|
|
}
|
|
return messageContainer;
|
|
};
|
|
|
|
/**
|
|
* Render messages send via JSON
|
|
* Used by some javascripts such as validate.js
|
|
*
|
|
* @param {object} messages JavaScript object containing the messages to render.
|
|
* Example:
|
|
* const messages = {
|
|
* "message": ["This will be a green message", "So will this"],
|
|
* "error": ["This will be a red message", "So will this"],
|
|
* "info": ["This will be a blue message", "So will this"],
|
|
* "notice": ["This will be same as info message", "So will this"],
|
|
* "warning": ["This will be a orange message", "So will this"],
|
|
* "my_custom_type": ["This will be same as info message", "So will this"]
|
|
* };
|
|
* @param {string} selector The selector of the container where the message will be rendered
|
|
* @param {bool} keepOld If we shall discard old messages
|
|
* @param {int} timeout The milliseconds before the message self destruct
|
|
* @return void
|
|
*/
|
|
Joomla.renderMessages = (messages, selector, keepOld, timeout) => {
|
|
const messageContainer = getMessageContainer(selector);
|
|
if (typeof keepOld === 'undefined' || keepOld && keepOld === false) {
|
|
Joomla.removeMessages(messageContainer);
|
|
}
|
|
[].slice.call(Object.keys(messages)).forEach(type => {
|
|
let alertClass = type;
|
|
|
|
// Array of messages of this type
|
|
const typeMessages = messages[type];
|
|
const messagesBox = document.createElement('joomla-alert');
|
|
if (['success', 'info', 'danger', 'warning'].indexOf(type) < 0) {
|
|
alertClass = type === 'notice' ? 'info' : type;
|
|
alertClass = type === 'message' ? 'success' : alertClass;
|
|
alertClass = type === 'error' ? 'danger' : alertClass;
|
|
alertClass = type === 'warning' ? 'warning' : alertClass;
|
|
}
|
|
messagesBox.setAttribute('type', alertClass);
|
|
messagesBox.setAttribute('close-text', Joomla.Text._('JCLOSE'));
|
|
messagesBox.setAttribute('dismiss', true);
|
|
if (timeout && parseInt(timeout, 10) > 0) {
|
|
messagesBox.setAttribute('auto-dismiss', timeout);
|
|
}
|
|
|
|
// Title
|
|
const title = Joomla.Text._(type);
|
|
|
|
// Skip titles with untranslated strings
|
|
if (typeof title !== 'undefined') {
|
|
const titleWrapper = document.createElement('div');
|
|
titleWrapper.className = 'alert-heading';
|
|
titleWrapper.innerHTML = Joomla.sanitizeHtml(`<span class="${type}"></span><span class="visually-hidden">${Joomla.Text._(type) ? Joomla.Text._(type) : type}</span>`);
|
|
messagesBox.appendChild(titleWrapper);
|
|
}
|
|
|
|
// Add messages to the message box
|
|
const messageWrapper = document.createElement('div');
|
|
messageWrapper.className = 'alert-wrapper';
|
|
typeMessages.forEach(typeMessage => {
|
|
messageWrapper.innerHTML += Joomla.sanitizeHtml(`<div class="alert-message">${typeMessage}</div>`);
|
|
});
|
|
messagesBox.appendChild(messageWrapper);
|
|
messageContainer.appendChild(messagesBox);
|
|
});
|
|
};
|
|
|
|
/**
|
|
* Remove messages
|
|
*
|
|
* @param {element} container The element of the container of the message
|
|
* to be removed
|
|
*
|
|
* @return {void}
|
|
*/
|
|
Joomla.removeMessages = container => {
|
|
const messageContainer = getMessageContainer(container);
|
|
const alerts = [].slice.call(messageContainer.querySelectorAll('joomla-alert'));
|
|
if (alerts.length) {
|
|
alerts.forEach(alert => {
|
|
alert.close();
|
|
});
|
|
}
|
|
};
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
const messages = Joomla.getOptions('joomla.messages');
|
|
if (messages) {
|
|
Object.keys(messages).map(message => Joomla.renderMessages(messages[message], undefined, true, undefined));
|
|
}
|
|
});
|