diff --git a/build/media_source/system/joomla.asset.json b/build/media_source/system/joomla.asset.json index ec9194a0f72d6..468c117cf5ba8 100644 --- a/build/media_source/system/joomla.asset.json +++ b/build/media_source/system/joomla.asset.json @@ -324,7 +324,10 @@ { "name": "webcomponent.field-media", "type": "style", - "uri": "system/fields/joomla-field-media.min.css" + "uri": "system/fields/joomla-field-media.min.css", + "dependencies": [ + "dialog" + ] }, { "name": "webcomponent.media-select", @@ -363,7 +366,10 @@ "defer": true }, "dependencies": [ - "wcpolyfill" + "core", + "wcpolyfill", + "dialog", + "webcomponent.media-select" ] }, { @@ -374,6 +380,10 @@ "type": "module" }, "dependencies": [ + "core", + "wcpolyfill", + "dialog", + "webcomponent.media-select", "webcomponent.field-media-legacy" ] }, @@ -511,6 +521,8 @@ "defer": true }, "dependencies": [ + "core", + "dialog", "wcpolyfill" ] }, @@ -522,6 +534,8 @@ "type": "module" }, "dependencies": [ + "core", + "dialog", "webcomponent.field-user-legacy" ] }, @@ -604,6 +618,23 @@ "dependencies": [ "core" ] - } + }, + { + "name": "dialog", + "type": "script", + "uri": "system/ui/joomla-dialog.min.js", + "attributes": { + "type": "module" + }, + "dependencies": [ + "wcpolyfill", + "core" + ] + }, + { + "name": "dialog", + "type": "style", + "uri": "system/ui/joomla-dialog.min.css" + } ] } diff --git a/build/media_source/system/js/fields/joomla-field-media.w-c.es6.js b/build/media_source/system/js/fields/joomla-field-media.w-c.es6.js index aff9cae4f8bcf..d42324edf2042 100644 --- a/build/media_source/system/js/fields/joomla-field-media.w-c.es6.js +++ b/build/media_source/system/js/fields/joomla-field-media.w-c.es6.js @@ -56,10 +56,6 @@ class JoomlaFieldMedia extends HTMLElement { set url(value) { this.setAttribute('url', value); } - get modalContainer() { return this.getAttribute('modal-container'); } - - set modalContainer(value) { this.setAttribute('modal-container', value); } - get input() { return this.getAttribute('input'); } set input(value) { this.setAttribute('input', value); } @@ -72,10 +68,6 @@ class JoomlaFieldMedia extends HTMLElement { set buttonClear(value) { this.setAttribute('button-clear', value); } - get buttonSaveSelected() { return this.getAttribute('button-save-selected'); } - - set buttonSaveSelected(value) { this.setAttribute('button-save-selected', value); } - get modalWidth() { return parseInt(this.getAttribute('modal-width'), 10); } set modalWidth(value) { this.setAttribute('modal-width', value); } @@ -101,41 +93,32 @@ class JoomlaFieldMedia extends HTMLElement { // attributeChangedCallback(attr, oldValue, newValue) {} connectedCallback() { - this.button = this.querySelector(this.buttonSelect); - this.inputElement = this.querySelector(this.input); - this.buttonClearEl = this.querySelector(this.buttonClear); - this.modalElement = this.querySelector('.joomla-modal'); - this.buttonSaveSelectedElement = this.querySelector(this.buttonSaveSelected); - this.previewElement = this.querySelector('.field-media-preview'); - - if (!this.button || !this.inputElement || !this.buttonClearEl || !this.modalElement - || !this.buttonSaveSelectedElement) { - throw new Error('Misconfiguaration...'); - } - - this.button.addEventListener('click', this.show); + requestAnimationFrame(() => { + this.button = this.querySelector(this.buttonSelect); + this.inputElement = this.querySelector(this.input); + this.buttonClearEl = this.querySelector(this.buttonClear); + this.previewElement = this.querySelector('.field-media-preview'); + + if (!this.button || !this.inputElement || !this.buttonClearEl) { + throw new Error('Misconfiguaration...'); + } - // Bootstrap modal init - if (this.modalElement - && window.bootstrap - && window.bootstrap.Modal - && !window.bootstrap.Modal.getInstance(this.modalElement)) { - Joomla.initialiseModal(this.modalElement, { isJoomla: true }); - } + this.button.addEventListener('click', this.show); - if (this.buttonClearEl) { - this.buttonClearEl.addEventListener('click', this.clearValue); - } + if (this.buttonClearEl) { + this.buttonClearEl.addEventListener('click', this.clearValue); + } - this.supportedExtensions = Joomla.getOptions('media-picker', {}); + this.supportedExtensions = Joomla.getOptions('media-picker', {}); - if (!Object.keys(this.supportedExtensions).length) { - throw new Error('Joomla API is not properly initiated'); - } + if (!Object.keys(this.supportedExtensions).length) { + throw new Error('Joomla API is not properly initiated'); + } - this.inputElement.removeAttribute('readonly'); - this.inputElement.addEventListener('change', this.validateValue); - this.updatePreview(); + this.inputElement.removeAttribute('readonly'); + this.inputElement.addEventListener('change', this.validateValue); + this.updatePreview(); + }); } disconnectedCallback() { @@ -154,16 +137,32 @@ class JoomlaFieldMedia extends HTMLElement { event.preventDefault(); event.stopPropagation(); - this.modalClose(); - return false; + return this.modalClose(); } show() { - this.modalElement.open(); + // eslint-disable-next-line + this.dialog = new JoomlaDialog({ + popupType: 'iframe', + textHeader: Joomla.Text._('JLIB_FORM_CHANGE_IMAGE'), + src: this.url, + popupButtons: [ + { + label: '', ariaLabel: Joomla.Text._('JCLOSE'), className: 'button-close btn-close', location: 'header', onClick: () => this.modalClose(), + }, + { + label: Joomla.Text._('JSELECT'), onClick: (event) => this.onSelected(event), + }, + { + label: Joomla.Text._('JCANCEL'), className: 'btn btn-outline-danger ms-2', onClick: () => this.modalClose(), + }, + ], + }); Joomla.selectedMediaFile = {}; - this.buttonSaveSelectedElement.addEventListener('click', this.onSelected); + this.dialog.show(); + Joomla.Modal.setCurrent(this.dialog); } async modalClose() { @@ -176,7 +175,8 @@ class JoomlaFieldMedia extends HTMLElement { } Joomla.selectedMediaFile = {}; - Joomla.Modal.getCurrent().close(); + this.dialog.destroy(); + Joomla.Modal.setCurrent(null); } setValue(value) { @@ -363,4 +363,5 @@ class JoomlaFieldMedia extends HTMLElement { } } } + customElements.define('joomla-field-media', JoomlaFieldMedia); diff --git a/build/media_source/system/js/fields/joomla-field-user.w-c.es6.js b/build/media_source/system/js/fields/joomla-field-user.w-c.es6.js index 8e870432ca8ba..eb6d7f35cfbf1 100644 --- a/build/media_source/system/js/fields/joomla-field-user.w-c.es6.js +++ b/build/media_source/system/js/fields/joomla-field-user.w-c.es6.js @@ -7,24 +7,21 @@ this.onchangeStr = ''; // Bind events + this.modalOpen = this.modalOpen.bind(this); + this.modalClose = this.modalClose.bind(this); this.buttonClick = this.buttonClick.bind(this); this.iframeLoad = this.iframeLoad.bind(this); - this.modalClose = this.modalClose.bind(this); this.setValue = this.setValue.bind(this); } static get observedAttributes() { - return ['url', 'modal', 'modal-width', 'modal-height', 'input', 'input-name', 'button-select']; + return ['url', 'modal-width', 'modal-height', 'input', 'input-name', 'button-select']; } get url() { return this.getAttribute('url'); } set url(value) { this.setAttribute('url', value); } - get modalClass() { return this.getAttribute('modal'); } - - set modalClass(value) { this.setAttribute('modal', value); } - get modalWidth() { return this.getAttribute('modal-width'); } set modalWidth(value) { this.setAttribute('modal-width', value); } @@ -46,93 +43,63 @@ set buttonSelectClass(value) { this.setAttribute('button-select', value); } connectedCallback() { - // Set up elements - this.modal = this.querySelector(this.modalClass); - this.modalBody = this.querySelector('.modal-body'); - this.input = this.querySelector(this.inputId); - this.inputName = this.querySelector(this.inputNameClass); - this.buttonSelect = this.querySelector(this.buttonSelectClass); - - // Bootstrap modal init - if (this.modal - && window.bootstrap - && window.bootstrap.Modal - && !window.bootstrap.Modal.getInstance(this.modal)) { - Joomla.initialiseModal(this.modal, { isJoomla: true }); - } - - if (this.buttonSelect) { - this.buttonSelect.addEventListener('click', this.modalOpen.bind(this)); - this.modal.addEventListener('hide', this.removeIframe.bind(this)); - - // Check for onchange callback, - this.onchangeStr = this.input.getAttribute('data-onchange'); - if (this.onchangeStr) { - /* eslint-disable */ - this.onUserSelect = new Function(this.onchangeStr); - this.input.addEventListener('change', this.onUserSelect); - /* eslint-enable */ + requestAnimationFrame(() => { + // Set up elements + this.input = this.querySelector(this.inputId); + this.inputName = this.querySelector(this.inputNameClass); + this.buttonSelect = this.querySelector(this.buttonSelectClass); + + if (this.buttonSelect) { + this.buttonSelect.addEventListener('click', this.modalOpen.bind(this)); } - } + }); } disconnectedCallback() { - if (this.onchangeStr && this.input) { - this.input.removeEventListener('change', this.onUserSelect); - } - if (this.buttonSelect) { - this.buttonSelect.removeEventListener('click', this); - } - - if (this.modal) { - this.modal.removeEventListener('hide', this); + this.buttonSelect.removeEventListener('click', this.modalOpen); } } buttonClick({ target }) { this.setValue(target.getAttribute('data-user-value'), target.getAttribute('data-user-name')); - this.modalClose(); + Joomla.Modal.getCurrent().close(); } iframeLoad() { - const iframeDoc = this.iframeEl.contentWindow.document; - const buttons = [].slice.call(iframeDoc.querySelectorAll('.button-select')); + const iframeDoc = this.dialog.querySelector('iframe').contentWindow.document; - buttons.forEach((button) => { - button.addEventListener('click', this.buttonClick); - }); + iframeDoc.querySelectorAll('.button-select').forEach((button) => button.addEventListener('click', this.buttonClick)); } // Opens the modal modalOpen() { - // Reconstruct the iframe - this.removeIframe(); - const iframe = document.createElement('iframe'); - iframe.setAttribute('name', 'field-user-modal'); - iframe.src = this.url.replace('{field-user-id}', this.input.getAttribute('id')); - iframe.setAttribute('width', this.modalWidth); - iframe.setAttribute('height', this.modalHeight); - - this.modalBody.appendChild(iframe); + // eslint-disable-next-line + this.dialog = new JoomlaDialog({ + popupType: 'iframe', + textHeader: Joomla.Text._('JLIB_FORM_CHANGE_IMAGE'), + src: this.url.replace('{field-user-id}', this.input.getAttribute('id')), + popupButtons: [ + { + label: '', ariaLabel: Joomla.Text._('JCLOSE'), className: 'button-close btn-close', location: 'header', onClick: () => this.modalClose(), + }, + { + label: Joomla.Text._('JCANCEL'), className: 'btn btn-outline-danger ms-2', onClick: () => this.modalClose(), + }, + ], + }); - this.modal.open(); + Joomla.selectedMediaFile = {}; - this.iframeEl = this.modalBody.querySelector('iframe'); + this.dialog.addEventListener('joomla-dialog:load', this.iframeLoad); - // handle the selection on the iframe - this.iframeEl.addEventListener('load', this.iframeLoad); + this.dialog.show(); + Joomla.Modal.setCurrent(this.dialog); } - // Closes the modal modalClose() { - Joomla.Modal.getCurrent().close(); - this.modalBody.innerHTML = ''; - } - - // Remove the iframe - removeIframe() { - this.modalBody.innerHTML = ''; + this.dialog.destroy(); + Joomla.Modal.setCurrent(null); } // Sets the value diff --git a/build/media_source/system/js/ui/joomla-dialog.w-c.es6.js b/build/media_source/system/js/ui/joomla-dialog.w-c.es6.js new file mode 100644 index 0000000000000..027b777738f73 --- /dev/null +++ b/build/media_source/system/js/ui/joomla-dialog.w-c.es6.js @@ -0,0 +1,602 @@ +/** + * @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 = `
+
+
+
+
`; + +/** + * JoomlaDialog class for Joomla Dialog implementation. + * With use of 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 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 {JoomlaDialog} + */ + renderLayout() { + if (this.dialog) return this; + + // On close callback + const onClose = () => { + this.dispatchEvent(new CustomEvent('joomla-dialog: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-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', + 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 {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')); + }; + + this.classList.add('loading'); + + switch (this.popupType) { + // Create an Inline content + case 'inline': { + 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; + } + + // 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', Joomla.sanitizeHtml(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 {JoomlaDialog} + */ + show() { + if (!this.parentElement) { + document.body.appendChild(this); + } + + this.dialog.showModal(); + this.dispatchEvent(new CustomEvent('joomla-dialog:open')); + 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); + 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-dialog-alert'); + popup.addEventListener('joomla-dialog: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-dialog-confirm'); + popup.addEventListener('joomla-dialog:close', () => resolve(result)); + popup.show(); + }); + } +} + +window.JoomlaDialog = JoomlaDialog; +customElements.define('joomla-dialog', JoomlaDialog); + +/** + * Auto create a popup dynamically on click, eg: + * + * + * + * Click + */ +const delegateSelector = '[data-joomla-dialog]'; +const configDataAttr = 'joomlaDialog'; +const configCacheFlag = 'joomlaDialogCache'; + +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.JoomlaDialogInstance) { + Joomla.Modal.setCurrent(triggerEl.JoomlaDialogInstance); + triggerEl.JoomlaDialogInstance.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; + } + + // 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) { + // Template as string not allowed here + delete config.popupTemplate; + } + + const popup = new JoomlaDialog(config); + if (cacheable) { + triggerEl.JoomlaDialogInstance = popup; + } + + popup.addEventListener('joomla-dialog:close', () => { + Joomla.Modal.setCurrent(null); + if (!cacheable) { + popup.destroy(); + } + }); + + Joomla.Modal.setCurrent(popup); + popup.show(); +}); + +export default JoomlaDialog; diff --git a/build/media_source/system/scss/ui/joomla-dialog.scss b/build/media_source/system/scss/ui/joomla-dialog.scss new file mode 100644 index 0000000000000..513c87bbd40e4 --- /dev/null +++ b/build/media_source/system/scss/ui/joomla-dialog.scss @@ -0,0 +1,136 @@ +// Styling for joomla-dialog element +joomla-dialog { + dialog { + 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"] { + 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 { + position: absolute; + top: 50%; + left: 50%; + display: block; + width: 66px; + height: 66px; + content: ""; + background: url("../../../../media/system/images/ajax-loader.gif") no-repeat center; + transform: translate(-50%, -50%); + } + } + } +} + +.joomla-dialog-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: .375rem; + } + } +} +.joomla-dialog-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: .375rem; + font-size: 1.5rem; + // font-size: $h3-font-size; + } + + &.empty { + display: none; + } +} +.joomla-dialog-body { + position: relative; + box-sizing: border-box; + flex: 1 1 auto; + + .buttons-holder { + position: absolute; + top: 1rem; + right: 1rem; + } + + // Apply overflow for potentially large content + joomla-dialog[type="inline"], + joomla-dialog[type="ajax"] & { + overflow: auto; + } + + // Content basic loading animation + joomla-dialog.loading & { + opacity: 0; + } + joomla-dialog.loaded & { + opacity: 1; + transition: opacity .4s ease; + } +} +.joomla-dialog-footer { + position: relative; + padding: 1rem; + border-top: 1px solid var(--border-color); + + .buttons-holder button { + padding-inline: 22px; + } + + &.empty { + display: none; + } +} + +.joomla-dialog-alert, +.joomla-dialog-confirm { + dialog { + width: 600px; + max-width: 80vw; + height: fit-content; + } + + .joomla-dialog-body { + padding: 1rem; + } +} diff --git a/layouts/joomla/form/field/media.php b/layouts/joomla/form/field/media.php index 86f6e620289a4..ac8c0fc117ccd 100644 --- a/layouts/joomla/form/field/media.php +++ b/layouts/joomla/form/field/media.php @@ -114,9 +114,11 @@ // Correctly route the url to ensure it's correctly using sef modes and subfolders $url = Route::_($url); $doc = Factory::getDocument(); -$wam = $doc->getWebAssetManager(); -$wam->useScript('webcomponent.media-select'); +$doc->getWebAssetManager() + ->useStyle('webcomponent.field-media') + ->useScript('webcomponent.field-media'); + $doc->addScriptOptions('media-picker-api', ['apiBaseUrl' => Uri::base() . 'index.php?option=com_media&format=json']); Text::script('JFIELD_MEDIA_LAZY_LABEL'); @@ -139,25 +141,10 @@ Text::script('JFIELD_MEDIA_DOWNLOAD_FILE'); Text::script('JLIB_APPLICATION_ERROR_SERVER'); Text::script('JLIB_FORM_MEDIA_PREVIEW_EMPTY', true); +Text::script('JLIB_FORM_CHANGE_IMAGE'); +Text::script('JSELECT'); +Text::script('JCANCEL'); -$modalHTML = HTMLHelper::_( - 'bootstrap.renderModal', - 'imageModal_' . $id, - [ - 'url' => $url, - 'title' => Text::_('JLIB_FORM_CHANGE_IMAGE'), - 'closeButton' => true, - 'height' => '100%', - 'width' => '100%', - 'modalWidth' => '80', - 'bodyHeight' => '60', - 'footer' => '' - . '', - ] -); - -$wam->useStyle('webcomponent.field-media') - ->useScript('webcomponent.field-media'); if (count($doc->getScriptOptions('media-picker')) === 0) { $doc->addScriptOptions('media-picker', [ @@ -167,15 +154,16 @@ 'documents' => $documentsExt, ]); } - ?> - + base-path="" root-folder="get('file_path', 'images'); ?>" url="" modal-container=".modal" - modal-width="100%" - modal-height="400px" + modal-width="96vw" + modal-height="80vh" input=".field-media-input" button-select=".button-select" button-clear=".button-clear" @@ -184,9 +172,8 @@ preview-container=".field-media-preview" preview-width="" preview-height="" - supported-extensions=" $imagesAllowedExt, 'audios' => $audiosAllowedExt, 'videos' => $videosAllowedExt, 'documents' => $documentsAllowedExt])); ?> -"> - + supported-extensions=" $imagesAllowedExt, 'audios' => $audiosAllowedExt, 'videos' => $videosAllowedExt, 'documents' => $documentsAllowedExt])); ?>" + >
diff --git a/layouts/joomla/form/field/user.php b/layouts/joomla/form/field/user.php index 119128186f0ab..84ad956937e31 100644 --- a/layouts/joomla/form/field/user.php +++ b/layouts/joomla/form/field/user.php @@ -11,7 +11,6 @@ defined('_JEXEC') or die; use Joomla\CMS\Factory; -use Joomla\CMS\HTML\HTMLHelper; use Joomla\CMS\Language\Text; use Joomla\CMS\Uri\Uri; use Joomla\Utilities\ArrayHelper; @@ -50,10 +49,10 @@ * @var string $dataAttribute Miscellaneous data attributes preprocessed for HTML output * @var array $dataAttributes Miscellaneous data attribute for eg, data-*. */ -$modalHTML = ''; + $uri = new Uri('index.php?option=com_users&view=users&layout=modal&tmpl=component&required=0'); -$uri->setVar('field', $this->escape($id)); +$uri->setVar('field', '{field-user-id}'); if ($required) { $uri->setVar('required', 1); @@ -89,34 +88,25 @@ } if (!$readonly) { - $modalHTML = HTMLHelper::_( - 'bootstrap.renderModal', - 'userModal_' . $id, - [ - 'url' => $uri, - 'title' => Text::_('JLIB_FORM_CHANGE_USER'), - 'closeButton' => true, - 'height' => '100%', - 'width' => '100%', - 'modalWidth' => 80, - 'bodyHeight' => 60, - 'footer' => '', - ] - ); + Text::script('JLIB_FORM_CHANGE_USER'); + Text::script('JCANCEL'); Factory::getDocument()->getWebAssetManager() + ->useStyle('dialog') ->useScript('webcomponent.field-user'); } + +$onchange = !empty($onchange) ? 'onchange="' . $this->escape($onchange) . '"' : ''; ?> - - + +
readonly> @@ -126,11 +116,10 @@
- + - + escape($onchange); ?>>