[ Index ] |
PHP Cross Reference of Joomla 4.2.2 documentation |
[Summary view] [Print] [Text view]
1 /** 2 * @copyright (C) 2018 Open Source Matters, Inc. <https://www.joomla.org> 3 * @license GNU General Public License version 2 or later; see LICENSE.txt 4 */ 5 6 /** 7 * JField 'showon' class 8 */ 9 class Showon { 10 /** 11 * Constructor 12 * 13 * @param {HTMLElement} cont Container element 14 */ 15 constructor(cont) { 16 const self = this; 17 this.container = cont || document; 18 this.fields = {// origin-field-name: { 19 // origin: ['collection of all the trigger nodes'], 20 // targets: ['collection of nodes to be controlled control'] 21 // } 22 }; 23 this.showonFields = [].slice.call(this.container.querySelectorAll('[data-showon]')); // Populate the fields data 24 25 if (this.showonFields.length) { 26 // @todo refactor this, dry 27 this.showonFields.forEach(field => { 28 // Set up only once 29 if (field.hasAttribute('data-showon-initialised')) { 30 return; 31 } 32 33 field.setAttribute('data-showon-initialised', ''); 34 const jsondata = field.getAttribute('data-showon') || ''; 35 const showonData = JSON.parse(jsondata); 36 let localFields; 37 38 if (showonData.length) { 39 localFields = [].slice.call(self.container.querySelectorAll(`[name="$showonData[0].field}"], [name="$showonData[0].field}[]"]`)); 40 41 if (!this.fields[showonData[0].field]) { 42 this.fields[showonData[0].field] = { 43 origin: [], 44 targets: [] 45 }; 46 } // Add trigger elements 47 48 49 localFields.forEach(cField => { 50 if (this.fields[showonData[0].field].origin.indexOf(cField) === -1) { 51 this.fields[showonData[0].field].origin.push(cField); 52 } 53 }); // Add target elements 54 55 this.fields[showonData[0].field].targets.push(field); // Data showon can have multiple values 56 57 if (showonData.length > 1) { 58 showonData.forEach((value, index) => { 59 if (index === 0) { 60 return; 61 } 62 63 localFields = [].slice.call(self.container.querySelectorAll(`[name="$value.field}"], [name="$value.field}[]"]`)); 64 65 if (!this.fields[showonData[0].field]) { 66 this.fields[showonData[0].field] = { 67 origin: [], 68 targets: [] 69 }; 70 } // Add trigger elements 71 72 73 localFields.forEach(cField => { 74 if (this.fields[showonData[0].field].origin.indexOf(cField) === -1) { 75 this.fields[showonData[0].field].origin.push(cField); 76 } 77 }); // Add target elements 78 79 if (this.fields[showonData[0].field].targets.indexOf(field) === -1) { 80 this.fields[showonData[0].field].targets.push(field); 81 } 82 }); 83 } 84 } 85 }); // Do some binding 86 87 this.linkedOptions = this.linkedOptions.bind(this); // Attach events to referenced element, to check condition on change and keyup 88 89 Object.keys(this.fields).forEach(key => { 90 if (this.fields[key].origin.length) { 91 this.fields[key].origin.forEach(elem => { 92 // Initialize the showon behaviour for the given HTMLElement 93 self.linkedOptions(key); // Setup listeners 94 95 elem.addEventListener('change', () => { 96 self.linkedOptions(key); 97 }); 98 elem.addEventListener('keyup', () => { 99 self.linkedOptions(key); 100 }); 101 elem.addEventListener('click', () => { 102 self.linkedOptions(key); 103 }); 104 }); 105 } 106 }); 107 } 108 } 109 /** 110 * 111 * @param key 112 */ 113 114 115 linkedOptions(key) { 116 // Loop through the elements that need to be either shown or hidden 117 this.fields[key].targets.forEach(field => { 118 const elementShowonDatas = JSON.parse(field.getAttribute('data-showon')) || []; 119 let showfield = true; 120 let itemval; // Check if target conditions are satisfied 121 122 elementShowonDatas.forEach((elementShowonData, index) => { 123 const condition = elementShowonData || {}; 124 condition.valid = 0; // Test in each of the elements in the field array if condition is valid 125 126 this.fields[key].origin.forEach(originField => { 127 if (originField.name.replace('[]', '') !== elementShowonData.field) { 128 return; 129 } 130 131 const originId = originField.id; // If checkbox or radio box the value is read from properties 132 133 if (originField.getAttribute('type') && ['checkbox', 'radio'].includes(originField.getAttribute('type').toLowerCase())) { 134 if (!originField.checked) { 135 // Unchecked fields will return a blank and so always match 136 // a != condition so we skip them 137 return; 138 } 139 140 itemval = document.getElementById(originId).value; 141 } else if (originField.nodeName === 'SELECT' && originField.hasAttribute('multiple')) { 142 itemval = Array.from(originField.querySelectorAll('option:checked')).map(el => el.value); 143 } else { 144 // Select lists, text-area etc. Note that multiple-select list returns 145 // an Array here s0 we can always treat 'itemval' as an array 146 itemval = document.getElementById(originId).value; // A multi-select <select> $field will return null when no elements are 147 // selected so we need to define itemval accordingly 148 149 if (itemval === null && originField.tagName.toLowerCase() === 'select') { 150 itemval = []; 151 } 152 } // Convert to array to allow multiple values in the field (e.g. type=list multiple) 153 // and normalize as string 154 155 156 if (!(typeof itemval === 'object')) { 157 itemval = JSON.parse(`["$itemval}"]`); 158 } // Test if any of the values of the field exists in showon conditions 159 160 161 itemval.forEach(val => { 162 // ":" Equal to one or more of the values condition 163 if (condition.sign === '=' && condition.values.indexOf(val) !== -1) { 164 condition.valid = 1; 165 } // "!:" Not equal to one or more of the values condition 166 167 168 if (condition.sign === '!=' && condition.values.indexOf(val) === -1) { 169 condition.valid = 1; 170 } 171 }); 172 }); // Verify conditions 173 // First condition (no operator): current condition must be valid 174 175 if (condition.op === '') { 176 if (condition.valid === 0) { 177 showfield = false; 178 } 179 } else { 180 // Other conditions (if exists) 181 // AND operator: both the previous and current conditions must be valid 182 if (condition.op === 'AND' && condition.valid + elementShowonDatas[index - 1].valid < 2) { 183 showfield = false; 184 condition.valid = 0; 185 } // OR operator: one of the previous and current conditions must be valid 186 187 188 if (condition.op === 'OR' && condition.valid + elementShowonDatas[index - 1].valid > 0) { 189 showfield = true; 190 condition.valid = 1; 191 } 192 } 193 }); // If conditions are satisfied show the target field(s), else hide 194 195 if (field.tagName !== 'option') { 196 if (showfield) { 197 field.classList.remove('hidden'); 198 field.dispatchEvent(new CustomEvent('joomla:showon-show', { 199 bubbles: true 200 })); 201 } else { 202 field.classList.add('hidden'); 203 field.dispatchEvent(new CustomEvent('joomla:showon-hide', { 204 bubbles: true 205 })); 206 } 207 } else { 208 // @todo: If chosen or choices.js is active we should update them 209 field.disabled = !showfield; 210 } 211 }); 212 } 213 214 } 215 216 if (!window.Joomla) { 217 throw new Error('Joomla API is not properly initialized'); 218 } // Provide a public API 219 220 221 if (!Joomla.Showon) { 222 Joomla.Showon = { 223 initialise: container => new Showon(container) 224 }; 225 } 226 /** 227 * Initialize 'showon' feature at an initial page load 228 */ 229 230 231 Joomla.Showon.initialise(document); 232 /** 233 * Search for matching parents 234 * 235 * @param {HTMLElement} $child 236 * @param {String} selector 237 * @returns {HTMLElement[]} 238 */ 239 240 const getMatchedParents = ($child, selector) => { 241 let $parent = $child; 242 let $matchingParent; 243 const parents = []; 244 245 while ($parent) { 246 $matchingParent = $parent.matches && $parent.matches(selector) ? $parent : null; 247 248 if ($matchingParent) { 249 parents.unshift($matchingParent); 250 } 251 252 $parent = $parent.parentNode; 253 } 254 255 return parents; 256 }; 257 /** 258 * Initialize 'showon' feature when part of the page was updated 259 */ 260 261 262 document.addEventListener('joomla:updated', ({ 263 target 264 }) => { 265 // Check is it subform, then wee need to fix some "showon" config 266 if (target.classList.contains('subform-repeatable-group')) { 267 const elements = [].slice.call(target.querySelectorAll('[data-showon]')); 268 269 if (elements.length) { 270 const search = []; 271 const replace = []; // Collect all parent groups of changed group 272 273 getMatchedParents(target, '.subform-repeatable-group').forEach($parent => { 274 search.push(new RegExp(`\\[${$parent.dataset.baseName}X\\]`, 'g')); 275 replace.push(`[${$parent.dataset.group}]`); 276 }); // Fix showon field names in a current group 277 278 elements.forEach(element => { 279 let { 280 showon 281 } = element.dataset; 282 search.forEach((pattern, i) => { 283 showon = showon.replace(pattern, replace[i]); 284 }); 285 element.dataset.showon = showon; 286 }); 287 } 288 } 289 290 Joomla.Showon.initialise(target); 291 });
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Wed Sep 7 05:41:13 2022 | Chilli.vc Blog - For Webmaster,Blog-Writer,System Admin and Domainer |