diff --git a/build/media_source/system/joomla.asset.json b/build/media_source/system/joomla.asset.json index aed0282a7c631..9ae7a6c230155 100644 --- a/build/media_source/system/joomla.asset.json +++ b/build/media_source/system/joomla.asset.json @@ -99,14 +99,27 @@ "uri": "" }, { - "name": "showon", + "name": "showon.es5", "type": "script", "dependencies": [ "core" ], + "uri": "system/showon-es5.min.js", + "attributes": { + "defer": true, + "nomodule": true + } + }, + { + "name": "showon", + "type": "script", + "dependencies": [ + "core", + "showon.es5" + ], "uri": "system/showon.min.js", "attributes": { - "defer": true + "type": "module" } }, { diff --git a/build/media_source/system/js/showon.es6.js b/build/media_source/system/js/showon.es6.js index c17f45f1e173a..973aea4f2b4e1 100644 --- a/build/media_source/system/js/showon.es6.js +++ b/build/media_source/system/js/showon.es6.js @@ -2,282 +2,280 @@ * @copyright (C) 2018 Open Source Matters, Inc. * @license GNU General Public License version 2 or later; see LICENSE.txt */ -((document) => { - 'use strict'; - /* - * JField 'showon' class +/** + * JField 'showon' class + */ +class Showon { + /** + * Constructor + * + * @param {HTMLElement} cont Container element */ - class Showon { - /* - * Constructor - * - * @param {HTMLElement} cont Container element - */ - constructor(cont) { - const self = this; - this.container = cont || document; - this.fields = { - // origin-field-name: { - // origin: ['collection of all the trigger nodes'], - // targets: ['collection of nodes to be controlled control'] - // } - }; - - this.showonFields = [].slice.call(this.container.querySelectorAll('[data-showon]')); - // Populate the fields data - if (this.showonFields.length) { - // @todo refactor this, dry - this.showonFields.forEach((field) => { - // Set up only once - if (field.hasAttribute('data-showon-initialised')) { - return; - } - field.setAttribute('data-showon-initialised', ''); + constructor(cont) { + const self = this; + this.container = cont || document; + this.fields = { + // origin-field-name: { + // origin: ['collection of all the trigger nodes'], + // targets: ['collection of nodes to be controlled control'] + // } + }; - const jsondata = field.getAttribute('data-showon') || ''; - const showonData = JSON.parse(jsondata); - let localFields; + this.showonFields = [].slice.call(this.container.querySelectorAll('[data-showon]')); + // Populate the fields data + if (this.showonFields.length) { + // @todo refactor this, dry + this.showonFields.forEach((field) => { + // Set up only once + if (field.hasAttribute('data-showon-initialised')) { + return; + } + field.setAttribute('data-showon-initialised', ''); - if (showonData.length) { - localFields = [].slice.call(self.container.querySelectorAll(`[name="${showonData[0].field}"], [name="${showonData[0].field}[]"]`)); + const jsondata = field.getAttribute('data-showon') || ''; + const showonData = JSON.parse(jsondata); + let localFields; - if (!this.fields[showonData[0].field]) { - this.fields[showonData[0].field] = { - origin: [], - targets: [], - }; - } + if (showonData.length) { + localFields = [].slice.call(self.container.querySelectorAll(`[name="${showonData[0].field}"], [name="${showonData[0].field}[]"]`)); - // Add trigger elements - localFields.forEach((cField) => { - if (this.fields[showonData[0].field].origin.indexOf(cField) === -1) { - this.fields[showonData[0].field].origin.push(cField); - } - }); + if (!this.fields[showonData[0].field]) { + this.fields[showonData[0].field] = { + origin: [], + targets: [], + }; + } - // Add target elements - this.fields[showonData[0].field].targets.push(field); + // Add trigger elements + localFields.forEach((cField) => { + if (this.fields[showonData[0].field].origin.indexOf(cField) === -1) { + this.fields[showonData[0].field].origin.push(cField); + } + }); - // Data showon can have multiple values - if (showonData.length > 1) { - showonData.forEach((value, index) => { - if (index === 0) { - return; - } + // Add target elements + this.fields[showonData[0].field].targets.push(field); - localFields = [].slice.call(self.container.querySelectorAll(`[name="${value.field}"], [name="${value.field}[]"]`)); + // Data showon can have multiple values + if (showonData.length > 1) { + showonData.forEach((value, index) => { + if (index === 0) { + return; + } - if (!this.fields[showonData[0].field]) { - this.fields[showonData[0].field] = { - origin: [], - targets: [], - }; - } + localFields = [].slice.call(self.container.querySelectorAll(`[name="${value.field}"], [name="${value.field}[]"]`)); - // Add trigger elements - localFields.forEach((cField) => { - if (this.fields[showonData[0].field].origin.indexOf(cField) === -1) { - this.fields[showonData[0].field].origin.push(cField); - } - }); + if (!this.fields[showonData[0].field]) { + this.fields[showonData[0].field] = { + origin: [], + targets: [], + }; + } - // Add target elements - if (this.fields[showonData[0].field].targets.indexOf(field) === -1) { - this.fields[showonData[0].field].targets.push(field); + // Add trigger elements + localFields.forEach((cField) => { + if (this.fields[showonData[0].field].origin.indexOf(cField) === -1) { + this.fields[showonData[0].field].origin.push(cField); } }); - } + + // Add target elements + if (this.fields[showonData[0].field].targets.indexOf(field) === -1) { + this.fields[showonData[0].field].targets.push(field); + } + }); } - }); + } + }); - // Do some binding - this.linkedOptions = this.linkedOptions.bind(this); + // Do some binding + this.linkedOptions = this.linkedOptions.bind(this); - // Attach events to referenced element, to check condition on change and keyup - Object.keys(this.fields).forEach((key) => { - if (this.fields[key].origin.length) { - this.fields[key].origin.forEach((elem) => { - // Initialize the showon behaviour for the given HTMLElement - self.linkedOptions(key); + // Attach events to referenced element, to check condition on change and keyup + Object.keys(this.fields).forEach((key) => { + if (this.fields[key].origin.length) { + this.fields[key].origin.forEach((elem) => { + // Initialize the showon behaviour for the given HTMLElement + self.linkedOptions(key); - // Setup listeners - elem.addEventListener('change', () => { self.linkedOptions(key); }); - elem.addEventListener('keyup', () => { self.linkedOptions(key); }); - elem.addEventListener('click', () => { self.linkedOptions(key); }); - }); - } - }); - } + // Setup listeners + elem.addEventListener('change', () => { self.linkedOptions(key); }); + elem.addEventListener('keyup', () => { self.linkedOptions(key); }); + elem.addEventListener('click', () => { self.linkedOptions(key); }); + }); + } + }); } + } - /** - * - * @param key - */ - linkedOptions(key) { - // Loop through the elements that need to be either shown or hidden - this.fields[key].targets.forEach((field) => { - const elementShowonDatas = JSON.parse(field.getAttribute('data-showon')) || []; - let showfield = true; - let itemval; - - // Check if target conditions are satisfied - elementShowonDatas.forEach((elementShowonData, index) => { - const condition = elementShowonData || {}; - condition.valid = 0; - - // Test in each of the elements in the field array if condition is valid - this.fields[key].origin.forEach((originField) => { - if (originField.name !== elementShowonData.field) { - return; - } - - const originId = originField.id; + /** + * + * @param key + */ + linkedOptions(key) { + // Loop through the elements that need to be either shown or hidden + this.fields[key].targets.forEach((field) => { + const elementShowonDatas = JSON.parse(field.getAttribute('data-showon')) || []; + let showfield = true; + let itemval; + + // Check if target conditions are satisfied + elementShowonDatas.forEach((elementShowonData, index) => { + const condition = elementShowonData || {}; + condition.valid = 0; + + // Test in each of the elements in the field array if condition is valid + this.fields[key].origin.forEach((originField) => { + if (originField.name.replace('[]', '') !== elementShowonData.field) { + return; + } - // If checkbox or radio box the value is read from properties - if (originField.getAttribute('type') && ['checkbox', 'radio'].indexOf(originField.getAttribute('type').toLowerCase()) !== -1) { - if (!originField.checked) { - // Unchecked fields will return a blank and so always match - // a != condition so we skip them - return; - } + const originId = originField.id; - itemval = document.getElementById(originId).value; - } else { - // Select lists, text-area etc. Note that multiple-select list returns - // an Array here s0 we can always treat 'itemval' as an array - itemval = document.getElementById(originId).value; - // A multi-select $field will return null when no elements are + // selected so we need to define itemval accordingly + if (itemval === null && originField.tagName.toLowerCase() === 'select') { + itemval = []; } + } - // Test if any of the values of the field exists in showon conditions - itemval.forEach((val) => { - // ":" Equal to one or more of the values condition - if (condition.sign === '=' && condition.values.indexOf(val) !== -1) { - condition.valid = 1; - } - // "!:" Not equal to one or more of the values condition - if (condition.sign === '!=' && condition.values.indexOf(val) === -1) { - condition.valid = 1; - } - }); - }); + // Convert to array to allow multiple values in the field (e.g. type=list multiple) + // and normalize as string + if (!(typeof itemval === 'object')) { + itemval = JSON.parse(`["${itemval}"]`); + } - // Verify conditions - // First condition (no operator): current condition must be valid - if (condition.op === '') { - if (condition.valid === 0) { - showfield = false; - } - } else { - // Other conditions (if exists) - // AND operator: both the previous and current conditions must be valid - if (condition.op === 'AND' && condition.valid + elementShowonDatas[index - 1].valid < 2) { - showfield = false; - condition.valid = 0; + // Test if any of the values of the field exists in showon conditions + itemval.forEach((val) => { + // ":" Equal to one or more of the values condition + if (condition.sign === '=' && condition.values.indexOf(val) !== -1) { + condition.valid = 1; } - // OR operator: one of the previous and current conditions must be valid - if (condition.op === 'OR' && condition.valid + elementShowonDatas[index - 1].valid > 0) { - showfield = true; + // "!:" Not equal to one or more of the values condition + if (condition.sign === '!=' && condition.values.indexOf(val) === -1) { condition.valid = 1; } - } + }); }); - // If conditions are satisfied show the target field(s), else hide - if (field.tagName !== 'option') { - if (showfield) { - field.classList.remove('hidden'); - } else { - field.classList.add('hidden'); + // Verify conditions + // First condition (no operator): current condition must be valid + if (condition.op === '') { + if (condition.valid === 0) { + showfield = false; } } else { - // TODO: If chosen or choices.js is active we should update them - field.disabled = !showfield; + // Other conditions (if exists) + // AND operator: both the previous and current conditions must be valid + if (condition.op === 'AND' && condition.valid + elementShowonDatas[index - 1].valid < 2) { + showfield = false; + condition.valid = 0; + } + // OR operator: one of the previous and current conditions must be valid + if (condition.op === 'OR' && condition.valid + elementShowonDatas[index - 1].valid > 0) { + showfield = true; + condition.valid = 1; + } } }); - } - } - - // Provide a public API - window.Joomla = window.Joomla || {}; - if (!Joomla.Showon) { - Joomla.Showon = { - initialise: (container) => new Showon(container), - }; + // If conditions are satisfied show the target field(s), else hide + if (field.tagName !== 'option') { + if (showfield) { + field.classList.remove('hidden'); + } else { + field.classList.add('hidden'); + } + } else { + // TODO: If chosen or choices.js is active we should update them + field.disabled = !showfield; + } + }); } +} + +if (!window.Joomla) { + throw new Error('Joomla API is not properly initialized'); +} +// Provide a public API +if (!Joomla.Showon) { + Joomla.Showon = { + initialise: (container) => new Showon(container), + }; +} - /** - * Initialize 'showon' feature at an initial page load - */ - document.addEventListener('DOMContentLoaded', () => { - Joomla.Showon.initialise(document); - }); +/** + * Initialize 'showon' feature at an initial page load + */ +Joomla.Showon.initialise(document); - /** - * Search for matching parents - * - * @param {HTMLElement} $child - * @param {String} selector - * @returns {HTMLElement[]} - */ - const getMatchedParents = ($child, selector) => { - let $parent = $child; - let $matchingParent; - const parents = []; - - while ($parent) { - $matchingParent = $parent.matches && $parent.matches(selector) ? $parent : null; - if ($matchingParent) { - parents.unshift($matchingParent); - } - $parent = $parent.parentNode; +/** + * Search for matching parents + * + * @param {HTMLElement} $child + * @param {String} selector + * @returns {HTMLElement[]} + */ +const getMatchedParents = ($child, selector) => { + let $parent = $child; + let $matchingParent; + const parents = []; + + while ($parent) { + $matchingParent = $parent.matches && $parent.matches(selector) ? $parent : null; + if ($matchingParent) { + parents.unshift($matchingParent); } + $parent = $parent.parentNode; + } - return parents; - }; + return parents; +}; - /** - * Initialize 'showon' feature when part of the page was updated - */ - document.addEventListener('joomla:updated', ({ target }) => { - // Check is it subform, then wee need to fix some "showon" config - if (target.classList.contains('subform-repeatable-group')) { - const elements = [].slice.call(target.querySelectorAll('[data-showon]')); - - if (elements.length) { - const search = []; - const replace = []; - - // Collect all parent groups of changed group - getMatchedParents(target, '.subform-repeatable-group').forEach(($parent) => { - search.push(new RegExp(`\\[${$parent.dataset.baseName}X\\]`, 'g')); - replace.push(`[${$parent.dataset.group}]`); - }); +/** + * Initialize 'showon' feature when part of the page was updated + */ +document.addEventListener('joomla:updated', ({ target }) => { + // Check is it subform, then wee need to fix some "showon" config + if (target.classList.contains('subform-repeatable-group')) { + const elements = [].slice.call(target.querySelectorAll('[data-showon]')); + + if (elements.length) { + const search = []; + const replace = []; + + // Collect all parent groups of changed group + getMatchedParents(target, '.subform-repeatable-group').forEach(($parent) => { + search.push(new RegExp(`\\[${$parent.dataset.baseName}X\\]`, 'g')); + replace.push(`[${$parent.dataset.group}]`); + }); - // Fix showon field names in a current group - elements.forEach((element) => { - let { showon } = element.dataset; - search.forEach((pattern, i) => { - showon = showon.replace(pattern, replace[i]); - }); - element.dataset.showon = showon; + // Fix showon field names in a current group + elements.forEach((element) => { + let { showon } = element.dataset; + search.forEach((pattern, i) => { + showon = showon.replace(pattern, replace[i]); }); - } + element.dataset.showon = showon; + }); } + } - Joomla.Showon.initialise(target); - }); -})(document); + Joomla.Showon.initialise(target); +});