[ Index ]

PHP Cross Reference of Joomla 4.2.2 documentation

title

Body

[close]

/media/system/js/ -> showon.js (source)

   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  });


Generated: Wed Sep 7 05:41:13 2022 Chilli.vc Blog - For Webmaster,Blog-Writer,System Admin and Domainer