From d7e2ac28b0bd18ce6f7dff5da2dbb8ee7f31affd Mon Sep 17 00:00:00 2001 From: Fedik Date: Sat, 18 Mar 2023 15:09:22 +0200 Subject: [PATCH 01/21] JoomlaPopup script Squashed commit of the following: commit cd372442bf50c01048920e49b5ed859bc9885d02 Author: Fedik Date: Sat Mar 18 14:14:43 2023 +0200 Popup customisable buttons location commit 64c1197e8000f6e6e898beafff3b5192426eb7bf Author: Fedik Date: Sat Mar 18 11:51:33 2023 +0200 Popup setCurrent commit 43e20c6fdcc8086a87dc4ae5aa44c8b8f9dd1cd0 Author: Fedik Date: Sat Mar 18 11:39:51 2023 +0200 Popup setCurrent, and cancelable commit 8e68503aca19cd177bdbc9858a643fcdadc6e264 Author: Fedik Date: Sat Mar 18 11:29:25 2023 +0200 Popup handle anchor click, and caching commit c784170937be81060dbaa63738f513228e8252af Author: Fedik Date: Sat Mar 18 10:52:50 2023 +0200 Popup helper, aliases commit 225267fda2f5e617f09a1900f0d184a0a2f173b4 Author: Fedik Date: Fri Mar 17 18:09:40 2023 +0200 Popup basic styling commit 5071c3bd2d422bf3ab08ec3f717f6780049dc888 Author: Fedik Date: Fri Mar 17 18:01:30 2023 +0200 Popup basic styling commit a5aeb0a4a40c5f85c19c0c69716e3078f8fdf528 Author: Fedik Date: Fri Mar 17 16:40:17 2023 +0200 Popup basic styling commit 79a2a934e609577ab6671c075506a60bb4ed18cf Author: Fedik Date: Fri Mar 17 16:00:15 2023 +0200 Popup basic styling commit 0b739fb131f6beb4a8b87110c608fb267a561b4f Author: Fedik Date: Fri Mar 17 15:40:03 2023 +0200 Popup basic styling commit 2f8d5a171a295dddc8e33af7d1bf05a2c6da51ae Author: Fedik Date: Fri Mar 17 14:54:17 2023 +0200 Can cancel commit b5512f95be1749f2135b69be91451a9a7582920b Merge: 5d835276aa cb1d5171ac Author: Fedik Date: Fri Mar 17 14:36:58 2023 +0200 Merge branch '4.3-dev' into jpopup commit 5d835276aacc8fbe23e627528dffc63995102165 Author: Fedik Date: Sun Mar 12 18:45:10 2023 +0200 Joomla popup base css commit c9abff7e5deaf1d3eb3d687917d6a4dff4b4190e Author: Fedik Date: Sun Mar 12 17:48:00 2023 +0200 Joomla popup destroy commit 5b42c281b0c81b386ab01bedd03eb3dae6bba80d Author: Fedik Date: Sun Mar 12 17:45:37 2023 +0200 Joomla popup lang commit d3adea8e65eeb7a14fb2bb86fb95b341b11a8236 Author: Fedik Date: Sun Mar 12 17:37:05 2023 +0200 Joomla popup jscs commit 6923750e1057e68caae7c4e9994d7e76a493cf54 Author: Fedik Date: Sun Mar 12 17:35:34 2023 +0200 Joomla popup alert/confirm handle cancel commit 25b6b165c5b9330c04e58c6891aaf645a206311d Author: Fedik Date: Sun Mar 12 17:25:31 2023 +0200 Joomla popup cleanup commit fd015071f518ea6cd9af8a14165b6e1c05e31df1 Author: Fedik Date: Sun Mar 12 17:12:00 2023 +0200 Joomla popup jscs commit 623bffe0c4275954738054991ece89601b63b528 Author: Fedik Date: Sun Mar 12 17:09:40 2023 +0200 Joomla popup alert promise commit e726f5fc8e5864e4ee77122971ac80b6e30865f1 Author: Fedik Date: Sun Mar 12 16:51:40 2023 +0200 Joomla popup jscs commit 92a05b661ed0a80b2c8986b5e418317817b87307 Author: Fedik Date: Sun Mar 12 16:32:47 2023 +0200 Joomla popup confirm with promise commit 42895bc1b9958302606e6f8c075fc01612e18e85 Author: Fedik Date: Sun Mar 12 16:26:11 2023 +0200 Joomla popup events commit e64f68adf015e2dc3194fb9e9ef04e80f03295cc Author: Fedik Date: Sun Mar 12 16:22:23 2023 +0200 Joomla popup events commit 4bee83514e0c2df805fd35d6e4d800d899fe6513 Author: Fedik Date: Sun Mar 12 15:39:47 2023 +0200 Joomla popup auto-create commit 394ce0822d6c476e35fd8b17050066efc2e3a092 Author: Fedik Date: Sun Mar 12 15:07:13 2023 +0200 Joomla popup commit ac6624229d97e8e120613374951c7e868a7f05eb Author: Fedik Date: Sun Mar 12 14:53:16 2023 +0200 Joomla popup docs commit e4d87bf89446d7fe17ef3dfdcc63cedb3de38847 Author: Fedik Date: Sun Mar 12 14:26:11 2023 +0200 Joomla popup image, ajax commit 4b10bbee0f0bb405cf5d34f877baf629b4156476 Author: Fedik Date: Sun Mar 12 13:17:31 2023 +0200 Joomla popup iframe commit 839cf4358f13eb06f705ba8d64c47892c6478559 Author: Fedik Date: Sun Mar 12 12:39:11 2023 +0200 Joomla popup sizes and close commit 2371cd03bc81d1f6891739ec4848f8d8f76ded2f Author: Fedik Date: Sun Mar 12 12:00:54 2023 +0200 Joomla popup render layout commit 7aafc4d9d2e4ea300deb4c6814927db614d3cb56 Author: Fedik Date: Sun Mar 12 11:19:35 2023 +0200 Joomla popup statics commit 658dd88f739a2bd63abd749b741c13c5f2d05c33 Author: Fedik Date: Sat Mar 11 20:21:22 2023 +0200 Joomla popup commit 27486abd47dab96f445a3960f1f0948a389a1067 Author: Fedik Date: Sat Mar 11 19:03:11 2023 +0200 Joomla popup commit 7e4398d100813ccc93c2cbf37067062c4ce835a9 Author: Fedik Date: Sat Mar 11 17:07:02 2023 +0200 Joomla popup --- build/media_source/system/joomla.asset.json | 12 + .../system/js/joomla-popup.w-c.es6.js | 597 ++++++++++++++++++ .../atum/scss/blocks/_modals.scss | 136 ++++ 3 files changed, 745 insertions(+) create mode 100644 build/media_source/system/js/joomla-popup.w-c.es6.js diff --git a/build/media_source/system/joomla.asset.json b/build/media_source/system/joomla.asset.json index ec9194a0f72d6..77e353d5168c0 100644 --- a/build/media_source/system/joomla.asset.json +++ b/build/media_source/system/joomla.asset.json @@ -594,6 +594,18 @@ "webcomponent.toolbar-button-legacy" ] }, + { + "name": "popup", + "type": "script", + "uri": "system/joomla-popup.min.js", + "attributes": { + "type": "module" + }, + "dependencies": [ + "wcpolyfill", + "core" + ] + }, { "name": "list-view", "type": "script", diff --git a/build/media_source/system/js/joomla-popup.w-c.es6.js b/build/media_source/system/js/joomla-popup.w-c.es6.js new file mode 100644 index 0000000000000..a43c30d2ba56f --- /dev/null +++ b/build/media_source/system/js/joomla-popup.w-c.es6.js @@ -0,0 +1,597 @@ +/** + * @copyright (C) 2023 Open Source Matters, Inc. + * @license GNU General Public License version 2 or later; see LICENSE.txt + */ + +// Default template for the popup +const popupTemplate = `
+
+
+
+
`; + +/** + * JoomlaPopup class for Joomla Dialog implementation. + * With use of custom element as dialog holder. + */ +class JoomlaPopup 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 bottom of the popup body. + * Example: + * [{label: 'Yes', onClick: () => popup.close()}, + * {label: 'No', onClick: () => popup.close(), className: 'btn btn-danger'}] + * @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; + + /** + * Class constructor + * @param {Object} config + */ + constructor(config) { + super(); + + // Define default params (doing it here because browser support of public props) + this.popupType = 'inline'; + this.textHeader = ''; + this.iconHeader = ''; + this.textClose = 'Close'; + this.popupContent = ''; + this.src = ''; + this.popupButtons = []; + this.cancelable = true; + this.width = ''; + this.height = ''; + this.popupTemplate = popupTemplate; + + if (!config) return; + + // Check configurable properties + ['popupType', 'textHeader', 'textClose', 'popupContent', 'src', + 'popupButtons', 'cancelable', 'width', 'height', 'popupTemplate', 'iconHeader', 'id'].forEach((key) => { + if (config[key] !== undefined) { + this[key] = config[key]; + } + }); + + if (config.className) { + this.classList.add(...config.className.split(' ')); + } + } + + connectedCallback() { + this.renderLayout(); + } + + /** + * Render a main layout, based on given template. + * @returns {JoomlaPopup} + */ + renderLayout() { + if (this.dialog) return this; + + // On close callback + const onClose = () => { + this.dispatchEvent(new CustomEvent('joomla-popup:close')); + }; + const onCancel = (event) => { + if (!this.cancelable) { + 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-popup-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-popup-header'); + this.popupTmplB = this.dialog.querySelector('.joomla-popup-body'); + this.popupTmplF = this.dialog.querySelector('.joomla-popup-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', + 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 || ''; + + if (btnData.className) { + btn.classList.add(...btnData.className.split(' ')); + } else { + btn.classList.add('button', 'button-primary', 'btn', 'btn-primary'); + } + + 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; + } + + /** + * Render the body content, based on popupType. + * @returns {JoomlaPopup} + */ + 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-popup:load')); + }; + + this.classList.add('loading'); + + switch (this.popupType) { + // Create an Inline content + case 'inline': { + this.popupTmplB.insertAdjacentHTML('afterbegin', this.popupContent); + 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; + frame.style.width = '100%'; + frame.style.height = '100%'; + 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', text); + this.popupContentElement = this.popupTmplB; + onLoad(); + }).catch((error) => { + throw error; + }); + break; + } + + default: { + throw new Error('Unknown popup type requested'); + } + } + + return this; + } + + /** + * 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.popupTmplB || false; + } + + /** + * Open the popup as modal window. + * Will append the element to Document body if not appended before. + * + * @returns {JoomlaPopup} + */ + show() { + if (!this.parentElement) { + document.body.appendChild(this); + } + + this.dialog.showModal(); + this.dispatchEvent(new CustomEvent('joomla-popup:open')); + return this; + } + + /** + * Alias for show() method. + * @returns {JoomlaPopup} + */ + open() { + return this.show(); + } + + /** + * Closes the popup + * + * @returns {JoomlaPopup} + */ + 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 {JoomlaPopup} + */ + hide() { + return this.close(); + } + + /** + * Destroys the popup. + */ + destroy() { + if (!this.dialog) { + return; + } + + this.dialog.close(); + this.removeChild(this.dialog); + this.parentElement.removeChild(this); + this.dialog = null; + this.popupTmplH = null; + this.popupTmplB = null; + this.popupTmplF = null; + this.popupContentElement = 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'), + onClick: () => popup.destroy(), + }]; + popup.cancelable = false; + popup.classList.add('joomla-popup-alert'); + popup.addEventListener('joomla-popup:close', () => 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'), + onClick: () => { + result = true; + popup.destroy(); + }, + }, + { + label: Joomla.Text._('JNO', 'No'), + onClick: () => { + result = false; + popup.destroy(); + }, + className: 'button btn btn-outline-secondary', + }, + ]; + popup.cancelable = false; + popup.classList.add('joomla-popup-confirm'); + popup.addEventListener('joomla-popup:close', () => resolve(result)); + popup.show(); + }); + } +} + +window.JoomlaPopup = JoomlaPopup; +customElements.define('joomla-popup', JoomlaPopup); + +/** + * Auto create a popup dynamically on click, eg: + * + * + * + * Click + */ +const delegateSelector = '[data-joomla-popup]'; +const configDataAttr = 'joomlaPopup'; +const configCacheFlag = 'joomlaPopupCache'; + +document.addEventListener('click', (event) => { + const triggerEl = event.target.closest(delegateSelector); + if (!triggerEl) return; + event.preventDefault(); + + // Check for cached instance + const cacheable = !!triggerEl.dataset[configCacheFlag]; + if (cacheable && triggerEl.JoomlaPopupInstance) { + Joomla.Modal.setCurrent(triggerEl.JoomlaPopupInstance); + triggerEl.JoomlaPopupInstance.show(); + return; + } + // Parse config + const config = triggerEl.dataset[configDataAttr] ? JSON.parse(triggerEl.dataset[configDataAttr]) : {}; + + // Check click on anchor + if (triggerEl.nodeName === 'A') { + if (!config.popupType) { + config.popupType = triggerEl.hash ? 'inline' : 'iframe'; + } + if (!config.src && config.popupType === 'iframe') { + config.src = triggerEl.href; + } else if (!config.popupContent && config.popupType === 'inline') { + config.popupContent = triggerEl.hash; + } + } + + // Check for content selector + if (config.popupContent && (config.popupContent[0] === '.' || config.popupContent[0] === '#')) { + const content = document.querySelector(config.popupContent); + config.popupContent = content ? content.innerHTML.trim() : config.popupContent; + } + + if (config.popupContent) { + config.popupContent = Joomla.sanitizeHtml(config.popupContent); + } + + // Check for template selector + if (config.popupTemplate && (config.popupTemplate[0] === '.' || config.popupTemplate[0] === '#')) { + const template = document.querySelector(config.popupTemplate); + if (template && template.nodeName === 'TEMPLATE') { + config.popupTemplate = template; + } + } else if (config.popupTemplate) { + config.popupTemplate = Joomla.sanitizeHtml(config.popupTemplate); + } + + const popup = new JoomlaPopup(config); + if (cacheable) { + triggerEl.JoomlaPopupInstance = popup; + } + + popup.addEventListener('joomla-popup:close', () => { + Joomla.Modal.setCurrent(null); + if (!cacheable) { + popup.destroy(); + } + }); + + Joomla.Modal.setCurrent(popup); + popup.show(); +}); + +export default JoomlaPopup; diff --git a/build/media_source/templates/administrator/atum/scss/blocks/_modals.scss b/build/media_source/templates/administrator/atum/scss/blocks/_modals.scss index 854979b697231..8a79b15dc0b0d 100755 --- a/build/media_source/templates/administrator/atum/scss/blocks/_modals.scss +++ b/build/media_source/templates/administrator/atum/scss/blocks/_modals.scss @@ -165,3 +165,139 @@ } } } + +// Styling for joomla-popup element +joomla-popup{ + dialog { + border: 1px solid var(--border-color-translucent); + border-radius: 0.3rem; + padding: 0; + box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15); + width: 80vw; + max-width: 1700px; + height: 80vh; + } + + &[type="image"]{ + dialog { + width: fit-content; + height: fit-content; + } + } + + &[type="iframe"] { + dialog { + overflow: hidden; + } + } + + // Extra loading animation for iframe and ajax types + &[type="iframe"], + &[type="ajax"]{ + &.loading{ + dialog:after{ + content: ''; + display: block; + position: absolute; + width: 66px; + height: 66px; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + background: url(../../../../system/images/ajax-loader.gif) no-repeat center; + } + } + } + +} +.joomla-popup-container{ + position: relative; + box-sizing: border-box; + display: flex; + flex-flow: column; + height: 100%; + + .buttons-holder{ + display: flex; + align-items: center; + justify-content: flex-end; + margin-inline-start: auto; + + button{ + margin-inline: 0.375rem; + } + } +} +.joomla-popup-header{ + position: relative; + display: flex; + align-items: center; + justify-content: space-between; + padding: 1rem; + border-bottom: 1px solid var(--border-color); + + h3{ + margin: 0; + } + + .header-icon{ + margin-inline-end: 0.375rem; + font-size: $h3-font-size; + } + + &.empty{ + display: none; + } +} +.joomla-popup-body{ + position: relative; + box-sizing: border-box; + flex: 1 1 auto; + + .buttons-holder{ + position: absolute; + right: 1rem; + top: 1rem; + } + + // Apply overflow for potentially large content + joomla-popup[type="inline"], + joomla-popup[type="ajax"] &{ + overflow: auto; + } + + // Content basic loading animation + joomla-popup.loading &{ + opacity: 0; + } + joomla-popup.loaded &{ + transition: opacity 0.4s ease; + opacity: 1; + } +} +.joomla-popup-footer{ + position: relative; + padding: 1rem; + border-top: 1px solid var(--border-color); + + .buttons-holder button{ + padding-inline: 22px; + } + + &.empty{ + display: none; + } +} + +.joomla-popup-alert, +.joomla-popup-confirm{ + dialog { + width: 600px; + max-width: 80vw; + height: fit-content; + } + + .joomla-popup-body{ + padding: 1rem; + } +} From 63ff698676678fa7146b72296ddb916cc90e28c9 Mon Sep 17 00:00:00 2001 From: Fedik Date: Sat, 18 Mar 2023 17:01:10 +0200 Subject: [PATCH 02/21] js/css cs --- .../system/js/joomla-popup.w-c.es6.js | 2 +- .../atum/scss/blocks/_modals.scss | 71 +++++++++---------- 2 files changed, 36 insertions(+), 37 deletions(-) diff --git a/build/media_source/system/js/joomla-popup.w-c.es6.js b/build/media_source/system/js/joomla-popup.w-c.es6.js index a43c30d2ba56f..4aa3c050a9218 100644 --- a/build/media_source/system/js/joomla-popup.w-c.es6.js +++ b/build/media_source/system/js/joomla-popup.w-c.es6.js @@ -208,7 +208,7 @@ class JoomlaPopup extends HTMLElement { className: 'button-close btn-close', onClick: () => this.close(), location: 'header', - }) + }); } // Buttons holders diff --git a/build/media_source/templates/administrator/atum/scss/blocks/_modals.scss b/build/media_source/templates/administrator/atum/scss/blocks/_modals.scss index 8a79b15dc0b0d..00826a20b16c9 100755 --- a/build/media_source/templates/administrator/atum/scss/blocks/_modals.scss +++ b/build/media_source/templates/administrator/atum/scss/blocks/_modals.scss @@ -167,18 +167,18 @@ } // Styling for joomla-popup element -joomla-popup{ +joomla-popup { dialog { - border: 1px solid var(--border-color-translucent); - border-radius: 0.3rem; - padding: 0; - box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15); width: 80vw; max-width: 1700px; height: 80vh; + padding: 0; + border: 1px solid var(--border-color-translucent); + border-radius: .3rem; + box-shadow: 0 .5rem 1rem rgba(0, 0, 0, .15); } - &[type="image"]{ + &[type="image"] { dialog { width: fit-content; height: fit-content; @@ -193,42 +193,41 @@ joomla-popup{ // Extra loading animation for iframe and ajax types &[type="iframe"], - &[type="ajax"]{ - &.loading{ - dialog:after{ - content: ''; - display: block; + &[type="ajax"] { + &.loading { + dialog:after { position: absolute; - width: 66px; - height: 66px; top: 50%; left: 50%; + display: block; + width: 66px; + height: 66px; + content: ""; + background: url("../../../../system/images/ajax-loader.gif") no-repeat center; transform: translate(-50%, -50%); - background: url(../../../../system/images/ajax-loader.gif) no-repeat center; } } } - } -.joomla-popup-container{ +.joomla-popup-container { position: relative; box-sizing: border-box; display: flex; flex-flow: column; height: 100%; - .buttons-holder{ + .buttons-holder { display: flex; align-items: center; justify-content: flex-end; margin-inline-start: auto; - button{ - margin-inline: 0.375rem; + button { + margin-inline: .375rem; } } } -.joomla-popup-header{ +.joomla-popup-header { position: relative; display: flex; align-items: center; @@ -236,68 +235,68 @@ joomla-popup{ padding: 1rem; border-bottom: 1px solid var(--border-color); - h3{ + h3 { margin: 0; } - .header-icon{ - margin-inline-end: 0.375rem; + .header-icon { + margin-inline-end: .375rem; font-size: $h3-font-size; } - &.empty{ + &.empty { display: none; } } -.joomla-popup-body{ +.joomla-popup-body { position: relative; box-sizing: border-box; flex: 1 1 auto; - .buttons-holder{ + .buttons-holder { position: absolute; - right: 1rem; top: 1rem; + right: 1rem; } // Apply overflow for potentially large content joomla-popup[type="inline"], - joomla-popup[type="ajax"] &{ + joomla-popup[type="ajax"] & { overflow: auto; } // Content basic loading animation - joomla-popup.loading &{ + joomla-popup.loading & { opacity: 0; } - joomla-popup.loaded &{ - transition: opacity 0.4s ease; + joomla-popup.loaded & { opacity: 1; + transition: opacity .4s ease; } } -.joomla-popup-footer{ +.joomla-popup-footer { position: relative; padding: 1rem; border-top: 1px solid var(--border-color); - .buttons-holder button{ + .buttons-holder button { padding-inline: 22px; } - &.empty{ + &.empty { display: none; } } .joomla-popup-alert, -.joomla-popup-confirm{ +.joomla-popup-confirm { dialog { width: 600px; max-width: 80vw; height: fit-content; } - .joomla-popup-body{ + .joomla-popup-body { padding: 1rem; } } From 123928ceb40d9b9033b0cc249b1adf01eea10b70 Mon Sep 17 00:00:00 2001 From: Fedik Date: Sun, 19 Mar 2023 13:48:05 +0200 Subject: [PATCH 03/21] Rename popup to dialog --- build/media_source/system/joomla.asset.json | 4 +- ...up.w-c.es6.js => joomla-dialog.w-c.es6.js} | 80 +++++++++---------- .../atum/scss/blocks/_modals.scss | 26 +++--- 3 files changed, 55 insertions(+), 55 deletions(-) rename build/media_source/system/js/{joomla-popup.w-c.es6.js => joomla-dialog.w-c.es6.js} (86%) diff --git a/build/media_source/system/joomla.asset.json b/build/media_source/system/joomla.asset.json index 77e353d5168c0..9010def43247b 100644 --- a/build/media_source/system/joomla.asset.json +++ b/build/media_source/system/joomla.asset.json @@ -595,9 +595,9 @@ ] }, { - "name": "popup", + "name": "dialog", "type": "script", - "uri": "system/joomla-popup.min.js", + "uri": "system/joomla-dialog.min.js", "attributes": { "type": "module" }, diff --git a/build/media_source/system/js/joomla-popup.w-c.es6.js b/build/media_source/system/js/joomla-dialog.w-c.es6.js similarity index 86% rename from build/media_source/system/js/joomla-popup.w-c.es6.js rename to build/media_source/system/js/joomla-dialog.w-c.es6.js index 4aa3c050a9218..118e91c396dfd 100644 --- a/build/media_source/system/js/joomla-popup.w-c.es6.js +++ b/build/media_source/system/js/joomla-dialog.w-c.es6.js @@ -4,17 +4,17 @@ */ // Default template for the popup -const popupTemplate = `
-
-
-
+const popupTemplate = `
+
+
+
`; /** - * JoomlaPopup class for Joomla Dialog implementation. - * With use of custom element as dialog holder. + * JoomlaDialog class for Joomla Dialog implementation. + * With use of custom element as dialog holder. */ -class JoomlaPopup extends HTMLElement { +class JoomlaDialog extends HTMLElement { /** * The popup type, supported: inline, iframe, image, ajax. * @type {string} @@ -127,14 +127,14 @@ class JoomlaPopup extends HTMLElement { /** * Render a main layout, based on given template. - * @returns {JoomlaPopup} + * @returns {JoomlaDialog} */ renderLayout() { if (this.dialog) return this; // On close callback const onClose = () => { - this.dispatchEvent(new CustomEvent('joomla-popup:close')); + this.dispatchEvent(new CustomEvent('joomla-dialog:close')); }; const onCancel = (event) => { if (!this.cancelable) { @@ -147,7 +147,7 @@ class JoomlaPopup extends HTMLElement { this.dialog = this.firstElementChild; this.dialog.addEventListener('cancel', onCancel); this.dialog.addEventListener('close', onClose); - this.popupTmplB = this.querySelector('.joomla-popup-body') || this.dialog; + this.popupTmplB = this.querySelector('.joomla-dialog-body') || this.dialog; this.popupContentElement = this.popupTmplB; return this; } @@ -169,9 +169,9 @@ class JoomlaPopup extends HTMLElement { this.appendChild(this.dialog); // Get template parts - this.popupTmplH = this.dialog.querySelector('.joomla-popup-header'); - this.popupTmplB = this.dialog.querySelector('.joomla-popup-body'); - this.popupTmplF = this.dialog.querySelector('.joomla-popup-footer'); + 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) { @@ -278,7 +278,7 @@ class JoomlaPopup extends HTMLElement { /** * Render the body content, based on popupType. - * @returns {JoomlaPopup} + * @returns {JoomlaDialog} */ renderBodyContent() { if (!this.popupTmplB || this.popupContentElement) return this; @@ -288,7 +288,7 @@ class JoomlaPopup extends HTMLElement { this.classList.add('loaded'); this.classList.remove('loading'); this.popupContentElement.removeEventListener('load', onLoad); - this.dispatchEvent(new CustomEvent('joomla-popup:load')); + this.dispatchEvent(new CustomEvent('joomla-dialog:load')); }; this.classList.add('loading'); @@ -394,7 +394,7 @@ class JoomlaPopup extends HTMLElement { * Open the popup as modal window. * Will append the element to Document body if not appended before. * - * @returns {JoomlaPopup} + * @returns {JoomlaDialog} */ show() { if (!this.parentElement) { @@ -402,13 +402,13 @@ class JoomlaPopup extends HTMLElement { } this.dialog.showModal(); - this.dispatchEvent(new CustomEvent('joomla-popup:open')); + this.dispatchEvent(new CustomEvent('joomla-dialog:open')); return this; } /** * Alias for show() method. - * @returns {JoomlaPopup} + * @returns {JoomlaDialog} */ open() { return this.show(); @@ -417,7 +417,7 @@ class JoomlaPopup extends HTMLElement { /** * Closes the popup * - * @returns {JoomlaPopup} + * @returns {JoomlaDialog} */ close() { if (!this.dialog) { @@ -430,7 +430,7 @@ class JoomlaPopup extends HTMLElement { /** * Alias for close() method. - * @returns {JoomlaPopup} + * @returns {JoomlaDialog} */ hide() { return this.close(); @@ -472,8 +472,8 @@ class JoomlaPopup extends HTMLElement { onClick: () => popup.destroy(), }]; popup.cancelable = false; - popup.classList.add('joomla-popup-alert'); - popup.addEventListener('joomla-popup:close', () => resolve()); + popup.classList.add('joomla-dialog-alert'); + popup.addEventListener('joomla-dialog:close', () => resolve()); popup.show(); }); } @@ -510,26 +510,26 @@ class JoomlaPopup extends HTMLElement { }, ]; popup.cancelable = false; - popup.classList.add('joomla-popup-confirm'); - popup.addEventListener('joomla-popup:close', () => resolve(result)); + popup.classList.add('joomla-dialog-confirm'); + popup.addEventListener('joomla-dialog:close', () => resolve(result)); popup.show(); }); } } -window.JoomlaPopup = JoomlaPopup; -customElements.define('joomla-popup', JoomlaPopup); +window.JoomlaDialog = JoomlaDialog; +customElements.define('joomla-dialog', JoomlaDialog); /** * Auto create a popup dynamically on click, eg: * - * - * - * Click + * + * + * Click */ -const delegateSelector = '[data-joomla-popup]'; -const configDataAttr = 'joomlaPopup'; -const configCacheFlag = 'joomlaPopupCache'; +const delegateSelector = '[data-joomla-dialog]'; +const configDataAttr = 'joomlaDialog'; +const configCacheFlag = 'joomlaDialogCache'; document.addEventListener('click', (event) => { const triggerEl = event.target.closest(delegateSelector); @@ -538,14 +538,14 @@ document.addEventListener('click', (event) => { // Check for cached instance const cacheable = !!triggerEl.dataset[configCacheFlag]; - if (cacheable && triggerEl.JoomlaPopupInstance) { - Joomla.Modal.setCurrent(triggerEl.JoomlaPopupInstance); - triggerEl.JoomlaPopupInstance.show(); + if (cacheable && triggerEl.JoomlaDialogInstance) { + Joomla.Modal.setCurrent(triggerEl.JoomlaDialogInstance); + triggerEl.JoomlaDialogInstance.show(); return; } // Parse config const config = triggerEl.dataset[configDataAttr] ? JSON.parse(triggerEl.dataset[configDataAttr]) : {}; - +console.log(config, triggerEl.dataset, configDataAttr) // Check click on anchor if (triggerEl.nodeName === 'A') { if (!config.popupType) { @@ -578,12 +578,12 @@ document.addEventListener('click', (event) => { config.popupTemplate = Joomla.sanitizeHtml(config.popupTemplate); } - const popup = new JoomlaPopup(config); + const popup = new JoomlaDialog(config); if (cacheable) { - triggerEl.JoomlaPopupInstance = popup; + triggerEl.JoomlaDialogInstance = popup; } - popup.addEventListener('joomla-popup:close', () => { + popup.addEventListener('joomla-dialog:close', () => { Joomla.Modal.setCurrent(null); if (!cacheable) { popup.destroy(); @@ -594,4 +594,4 @@ document.addEventListener('click', (event) => { popup.show(); }); -export default JoomlaPopup; +export default JoomlaDialog; diff --git a/build/media_source/templates/administrator/atum/scss/blocks/_modals.scss b/build/media_source/templates/administrator/atum/scss/blocks/_modals.scss index 00826a20b16c9..336426c93a81c 100755 --- a/build/media_source/templates/administrator/atum/scss/blocks/_modals.scss +++ b/build/media_source/templates/administrator/atum/scss/blocks/_modals.scss @@ -166,8 +166,8 @@ } } -// Styling for joomla-popup element -joomla-popup { +// Styling for joomla-dialog element +joomla-dialog { dialog { width: 80vw; max-width: 1700px; @@ -209,7 +209,7 @@ joomla-popup { } } } -.joomla-popup-container { +.joomla-dialog-container { position: relative; box-sizing: border-box; display: flex; @@ -227,7 +227,7 @@ joomla-popup { } } } -.joomla-popup-header { +.joomla-dialog-header { position: relative; display: flex; align-items: center; @@ -248,7 +248,7 @@ joomla-popup { display: none; } } -.joomla-popup-body { +.joomla-dialog-body { position: relative; box-sizing: border-box; flex: 1 1 auto; @@ -260,21 +260,21 @@ joomla-popup { } // Apply overflow for potentially large content - joomla-popup[type="inline"], - joomla-popup[type="ajax"] & { + joomla-dialog[type="inline"], + joomla-dialog[type="ajax"] & { overflow: auto; } // Content basic loading animation - joomla-popup.loading & { + joomla-dialog.loading & { opacity: 0; } - joomla-popup.loaded & { + joomla-dialog.loaded & { opacity: 1; transition: opacity .4s ease; } } -.joomla-popup-footer { +.joomla-dialog-footer { position: relative; padding: 1rem; border-top: 1px solid var(--border-color); @@ -288,15 +288,15 @@ joomla-popup { } } -.joomla-popup-alert, -.joomla-popup-confirm { +.joomla-dialog-alert, +.joomla-dialog-confirm { dialog { width: 600px; max-width: 80vw; height: fit-content; } - .joomla-popup-body { + .joomla-dialog-body { padding: 1rem; } } From e08f3bc05d464186159e92a81c209da8a28feb9e Mon Sep 17 00:00:00 2001 From: Fedik Date: Sun, 19 Mar 2023 14:09:49 +0200 Subject: [PATCH 04/21] Clean html --- .../system/js/joomla-dialog.w-c.es6.js | 21 ++++++++++++------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/build/media_source/system/js/joomla-dialog.w-c.es6.js b/build/media_source/system/js/joomla-dialog.w-c.es6.js index 118e91c396dfd..027b777738f73 100644 --- a/build/media_source/system/js/joomla-dialog.w-c.es6.js +++ b/build/media_source/system/js/joomla-dialog.w-c.es6.js @@ -296,7 +296,12 @@ class JoomlaDialog extends HTMLElement { switch (this.popupType) { // Create an Inline content case 'inline': { - this.popupTmplB.insertAdjacentHTML('afterbegin', this.popupContent); + if (this.popupContent instanceof HTMLElement) { + const inlineContent = this.popupContent.nodeName === 'TEMPLATE' ? this.popupContent.content : this.popupContent; + this.popupTmplB.appendChild(inlineContent); + } else { + this.popupTmplB.insertAdjacentHTML('afterbegin', Joomla.sanitizeHtml(this.popupContent)); + } this.popupContentElement = this.popupTmplB; onLoad(); break; @@ -333,7 +338,7 @@ class JoomlaDialog extends HTMLElement { } return response.text(); }).then((text) => { - this.popupTmplB.insertAdjacentHTML('afterbegin', text); + this.popupTmplB.insertAdjacentHTML('afterbegin', Joomla.sanitizeHtml(text)); this.popupContentElement = this.popupTmplB; onLoad(); }).catch((error) => { @@ -545,7 +550,7 @@ document.addEventListener('click', (event) => { } // Parse config const config = triggerEl.dataset[configDataAttr] ? JSON.parse(triggerEl.dataset[configDataAttr]) : {}; -console.log(config, triggerEl.dataset, configDataAttr) + // Check click on anchor if (triggerEl.nodeName === 'A') { if (!config.popupType) { @@ -564,18 +569,18 @@ console.log(config, triggerEl.dataset, configDataAttr) config.popupContent = content ? content.innerHTML.trim() : config.popupContent; } - if (config.popupContent) { - config.popupContent = Joomla.sanitizeHtml(config.popupContent); - } - // Check for template selector if (config.popupTemplate && (config.popupTemplate[0] === '.' || config.popupTemplate[0] === '#')) { const template = document.querySelector(config.popupTemplate); if (template && template.nodeName === 'TEMPLATE') { config.popupTemplate = template; + } else { + // Remove invalid template + delete config.popupTemplate; } } else if (config.popupTemplate) { - config.popupTemplate = Joomla.sanitizeHtml(config.popupTemplate); + // Template as string not allowed here + delete config.popupTemplate; } const popup = new JoomlaDialog(config); From b078769d9045edcd3e63fa67b1b32dceaab97d1c Mon Sep 17 00:00:00 2001 From: Fedik Date: Sun, 19 Mar 2023 14:27:15 +0200 Subject: [PATCH 05/21] Update comments --- build/media_source/system/js/joomla-dialog.w-c.es6.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/build/media_source/system/js/joomla-dialog.w-c.es6.js b/build/media_source/system/js/joomla-dialog.w-c.es6.js index 027b777738f73..45c14ae9edf14 100644 --- a/build/media_source/system/js/joomla-dialog.w-c.es6.js +++ b/build/media_source/system/js/joomla-dialog.w-c.es6.js @@ -46,10 +46,11 @@ class JoomlaDialog extends HTMLElement { // src = ''; /** - * An optional list of buttons, to be rendered in footer, or bottom of the popup body. + * 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: 'No', onClick: () => popup.close(), className: 'btn btn-danger'}, + * {label: 'Click me', onClick: () => popup.close(), location: 'header'}] * @type {[]} */ // popupButtons = []; From ac322bb4a0d90a80b19a4a5da09947ce1aa7763d Mon Sep 17 00:00:00 2001 From: Fedik Date: Sun, 26 Mar 2023 13:23:22 +0300 Subject: [PATCH 06/21] Cancelable alert --- build/media_source/system/js/joomla-dialog.w-c.es6.js | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/build/media_source/system/js/joomla-dialog.w-c.es6.js b/build/media_source/system/js/joomla-dialog.w-c.es6.js index 45c14ae9edf14..85adccdf735e9 100644 --- a/build/media_source/system/js/joomla-dialog.w-c.es6.js +++ b/build/media_source/system/js/joomla-dialog.w-c.es6.js @@ -475,11 +475,13 @@ class JoomlaDialog extends HTMLElement { popup.textHeader = title || Joomla.Text._('INFO', 'Info'); popup.popupButtons = [{ label: Joomla.Text._('JOK', 'Okay'), - onClick: () => popup.destroy(), + onClick: () => popup.close(), }]; - popup.cancelable = false; popup.classList.add('joomla-dialog-alert'); - popup.addEventListener('joomla-dialog:close', () => resolve()); + popup.addEventListener('joomla-dialog:close', () => { + popup.destroy(); + resolve(); + }); popup.show(); }); } @@ -601,3 +603,5 @@ document.addEventListener('click', (event) => { }); export default JoomlaDialog; + +JoomlaDialog.alert('message') From 6f931d0cbbbf5ebdc3f75585592c89f7cc7e5309 Mon Sep 17 00:00:00 2001 From: Fedik Date: Sun, 26 Mar 2023 13:44:45 +0300 Subject: [PATCH 07/21] Src for inline content --- .../system/js/joomla-dialog.w-c.es6.js | 25 +++++++++++-------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/build/media_source/system/js/joomla-dialog.w-c.es6.js b/build/media_source/system/js/joomla-dialog.w-c.es6.js index 85adccdf735e9..ecd1fd6a9fbb9 100644 --- a/build/media_source/system/js/joomla-dialog.w-c.es6.js +++ b/build/media_source/system/js/joomla-dialog.w-c.es6.js @@ -297,10 +297,21 @@ class JoomlaDialog extends HTMLElement { switch (this.popupType) { // Create an Inline content case 'inline': { + // Check for content selector: src: '#content-selector' or src: '.content-selector' + if (!this.popupContent && this.src && (this.src[0] === '.' || this.src[0] === '#')) { + const srcContent = document.querySelector(this.src); + if (srcContent) { + // Use