diff --git a/administrator/components/com_config/tmpl/component/default.php b/administrator/components/com_config/tmpl/component/default.php index eb04970f2b824..37407f53a80e5 100644 --- a/administrator/components/com_config/tmpl/component/default.php +++ b/administrator/components/com_config/tmpl/component/default.php @@ -66,7 +66,13 @@ showon)) : ?> useScript('showon'); ?> - showon, $this->formControl)) . '\''; ?> + showon, $this->formControl)) . '\''; ?> + + + + requireon)) : ?> + useScript('requireon'); ?> + requireon, $this->formControl)) . '\''; ?> label) ? 'COM_CONFIG_' . $name . '_FIELDSET_LABEL' : $fieldSet->label; ?> diff --git a/build/media_source/system/joomla.asset.json b/build/media_source/system/joomla.asset.json index 56bb579487c6a..f69110baa6bc3 100644 --- a/build/media_source/system/joomla.asset.json +++ b/build/media_source/system/joomla.asset.json @@ -145,6 +145,30 @@ "type": "module" } }, + { + "name": "requireon.es5", + "type": "script", + "dependencies": [ + "core" + ], + "uri": "system/requireon-es5.min.js", + "attributes": { + "defer": true, + "nomodule": true + } + }, + { + "name": "requireon", + "type": "script", + "dependencies": [ + "core", + "requireon.es5" + ], + "uri": "system/requireon.min.js", + "attributes": { + "type": "module" + } + }, { "name": "switcher", "type": "style", diff --git a/build/media_source/system/js/requireon.es6.js b/build/media_source/system/js/requireon.es6.js new file mode 100644 index 0000000000000..f7f02a619a670 --- /dev/null +++ b/build/media_source/system/js/requireon.es6.js @@ -0,0 +1,306 @@ +/** + * @copyright (C) 2018 Open Source Matters, Inc. + * @license GNU General Public License version 2 or later; see LICENSE.txt + */ + +/** + * JField 'requireon' class + */ +class Requireon { + /** + * 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.requireonFields = [].slice.call(this.container.querySelectorAll('[data-requireon]')); // Populate the fields data + + if (this.requireonFields.length) { + // @todo refactor this, dry + this.requireonFields.forEach((field) => { + // Set up only once + if (field.hasAttribute('data-requireon-initialised')) { + return; + } + + field.setAttribute('data-requireon-initialised', ''); + const jsondata = field.getAttribute('data-requireon') || ''; + const requireonData = JSON.parse(jsondata); + + let localFields; + + if (requireonData.length) { + localFields = [].slice.call(self.container.querySelectorAll(`[name="${requireonData[0].field}"], [name="${requireonData[0].field}[]"]`)); + + if (!this.fields[requireonData[0].field]) { + this.fields[requireonData[0].field] = { + origin: [], + targets: [], + }; + } // Add trigger elements + + localFields.forEach((cField) => { + if (this.fields[requireonData[0].field].origin.indexOf(cField) === -1) { + this.fields[requireonData[0].field].origin.push(cField); + } + }); // Add target elements + + this.fields[requireonData[0].field].targets.push(field); // Data requireon can have multiple values + + if (requireonData.length > 1) { + requireonData.forEach((value, index) => { + if (index === 0) { + return; + } + + localFields = [].slice.call(self.container.querySelectorAll(`[name="${value.field}"], [name="${value.field}[]"]`)); + + if (!this.fields[requireonData[0].field]) { + this.fields[requireonData[0].field] = { + origin: [], + targets: [], + }; + } // Add trigger elements + + localFields.forEach((cField) => { + if (this.fields[requireonData[0].field].origin.indexOf(cField) === -1) { + this.fields[requireonData[0].field].origin.push(cField); + } + }); // Add target elements + + if (this.fields[requireonData[0].field].targets.indexOf(field) === -1) { + this.fields[requireonData[0].field].targets.push(field); + } + }); + } + } + }); // 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 requireon 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); + }); + }); + } + }); + } + } + + /** + * + * @param key + */ + linkedOptions(key) { + // Loop through the elements that need to be either mandatory or optional + this.fields[key].targets.forEach((field) => { + const elementRequireonDatas = JSON.parse(field.getAttribute('data-requireon')) || []; + let requirefield = true; + let itemval; // Check if target conditions are satisfied + + elementRequireonDatas.forEach((elementRequireonData, index) => { + const condition = elementRequireonData || {}; + 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('[]', '') !== elementRequireonData.field) { + return; + } + + const originId = originField.id; // If checkbox or radio box the value is read from properties + + if (originField.getAttribute('type') && ['checkbox', 'radio'].includes(originField.getAttribute('type').toLowerCase())) { + if (!originField.checked) { + // Unchecked fields will return a blank and so always match + // a != condition so we skip them + return; + } + + itemval = document.getElementById(originId).value; + } else if (originField.nodeName === 'SELECT' && originField.hasAttribute('multiple')) { + itemval = Array.from(originField.querySelectorAll('option:checked')).map((el) => el.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