PageSourceSearch

https://www.rafbf.org/modules/contrib/webform/js/webform.states.js?thjzbs

js rafbf.org collected 2026-09-24 09:18:32 UTC 22,093 bytes, 608 lines download raw bytes

1/**
2 * @file
3 * JavaScript behaviors for custom webform #states.
4 */
5
6(function ($, Drupal, once) {
7  Drupal.webform = Drupal.webform || {};
8  Drupal.webform.states = Drupal.webform.states || {};
9  Drupal.webform.states.slideDown = Drupal.webform.states.slideDown || {};
10  Drupal.webform.states.slideDown.duration = 'slow';
11  Drupal.webform.states.slideUp = Drupal.webform.states.slideUp || {};
12  Drupal.webform.states.slideUp.duration = 'fast';
13
14  /* ************************************************************************ */
15  // jQuery functions.
16  /* ************************************************************************ */
17
18  /**
19   * Check if an element has a specified data attribute.
20   *
21   * @param {string} data
22   *   The data attribute name.
23   *
24   * @return {boolean}
25   *   TRUE if an element has a specified data attribute.
26   */
27  $.fn.hasData = function (data) {
28    return (typeof this.data(data) !== 'undefined');
29  };
30
31  /**
32   * Check if element is within the webform or not.
33   *
34   * @return {boolean}
35   *   TRUE if element is within the webform.
36   */
37  $.fn.isWebform = function () {
38    return $(this).closest('form.webform-submission-form, form[id^="webform"], form[data-is-webform]').length ? true : false;
39  };
40
41  /**
42   * Check if element is to be treated as a webform element.
43   *
44   * @return {boolean}
45   *   TRUE if element is to be treated as a webform element.
46   */
47  $.fn.isWebformElement = function () {
48    return ($(this).isWebform() || $(this).closest('[data-is-webform-element]').length) ? true : false;
49  };
50
51  /* ************************************************************************ */
52  // Trigger.
53  /* ************************************************************************ */
54
55  // The change event is triggered by cut-n-paste and select menus.
56  // Issue #2445271: #states element empty check not triggered on mouse
57  // based paste.
58  // @see https://www.drupal.org/node/2445271
59  Drupal.states.Trigger.states.empty.change = function change() {
60    return this.val() === '';
61  };
62
63  /* ************************************************************************ */
64  // Dependents.
65  /* ************************************************************************ */
66
67  // Adds pattern, less than, and greater than support to #state API.
68  // @see http://drupalsun.com/julia-evans/2012/03/09/extending-form-api-states-regular-expressions
69  Drupal.states.Dependent.comparisons.Object = function (reference, value) {
70    if ('pattern' in reference) {
71      return (new RegExp(reference['pattern'])).test(value);
72    }
73    else if ('!pattern' in reference) {
74      return !((new RegExp(reference['!pattern'])).test(value));
75    }
76    else if ('less' in reference) {
77      return (value !== '' && parseFloat(reference['less']) > parseFloat(value));
78    }
79    else if ('less_equal' in reference) {
80      return (value !== '' && parseFloat(reference['less_equal']) >= parseFloat(value));
81    }
82    else if ('greater' in reference) {
83      return (value !== '' && parseFloat(reference['greater']) < parseFloat(value));
84    }
85    else if ('greater_equal' in reference) {
86      return (value !== '' && parseFloat(reference['greater_equal']) <= parseFloat(value));
87    }
88    else if ('between' in reference || '!between' in reference) {
89      if (value === '') {
90        return false;
91      }
92
93      var between = reference['between'] || reference['!between'];
94      var betweenParts = between.split(':');
95      var greater = betweenParts[0];
96      var less = (typeof betweenParts[1] !== 'undefined') ? betweenParts[1] : null;
97      var isGreaterThan = (greater === null || greater === '' || parseFloat(value) >= parseFloat(greater));
98      var isLessThan = (less === null || less === '' || parseFloat(value) <= parseFloat(less));
99      var result = (isGreaterThan && isLessThan);
100      return (reference['!between']) ? !result : result;
101    }
102    else {
103      return reference.indexOf(value) !== false;
104    }
105  };
106
107  /* ************************************************************************ */
108  // States events.
109  /* ************************************************************************ */
110
111  var $document = $(document);
112
113  $document.on('state:required', function (e) {
114    if (e.trigger && $(e.target).isWebformElement()) {
115      var $target = $(e.target);
116      // Fix #required file upload.
117      // @see Issue #2860529: Conditional required File upload field don't work.
118      toggleRequired($target.find('input[type="file"]'), e.value);
119
120      // Fix #required for radios and likert.
121      // @see Issue #2856795: If radio buttons are required but not filled form is nevertheless submitted.
122      if ($target.is('.js-form-type-radios, .js-form-type-webform-radios-other, .js-webform-type-radios, .js-webform-type-webform-radios-other, .js-webform-type-webform-entity-radios, .webform-likert-table')) {
123        $target.toggleClass('required', e.value);
124        toggleRequired($target.find('input[type="radio"]'), e.value);
125      }
126
127      // Fix #required for checkboxes.
128      // @see Issue #2938414: Checkboxes don't support #states required.
129      // @see checkboxRequiredhandler
130      if ($target.is('.js-form-type-checkboxes, .js-form-type-webform-checkboxes-other, .js-webform-type-checkboxes, .js-webform-type-webform-checkboxes-other')) {
131        $target.toggleClass('required', e.value);
132        var $checkboxes = $target.find('input[type="checkbox"]');
133        if (e.value) {
134          // Add event handler.
135          $checkboxes.on('click', statesCheckboxesRequiredEventHandler);
136          // Initialize and add required attribute.
137          checkboxesRequired($target);
138        }
139        else {
140          // Remove event handler.
141          $checkboxes.off('click', statesCheckboxesRequiredEventHandler);
142          // Remove required attribute.
143          toggleRequired($checkboxes, false);
144        }
145      }
146
147      // Fix #required for tableselect.
148      // @see Issue #3212581: Table select does not trigger client side validation
149      if ($target.is('.js-webform-tableselect')) {
150        $target.toggleClass('required', e.value);
151        var isMultiple = $target.is('[multiple]');
152        if (isMultiple) {
153          // Checkboxes.
154          var $tbody = $target.find('tbody');
155          var $checkboxes = $tbody.find('input[type="checkbox"]');
156          copyRequireMessage($target, $checkboxes);
157          if (e.value) {
158            $checkboxes.on('click change', statesCheckboxesRequiredEventHandler);
159            checkboxesRequired($tbody);
160          }
161          else {
162            $checkboxes.off('click change ', statesCheckboxesRequiredEventHandler);
163            toggleRequired($tbody, false);
164          }
165        }
166        else {
167          // Radios.
168          var $radios = $target.find('input[type="radio"]');
169          copyRequireMessage($target, $radios);
170          toggleRequired($radios, e.value);
171        }
172      }
173
174      // Fix required label for elements without the for attribute.
175      // @see Issue #3145300: Conditional Visible Select Other not working.
176      if ($target.is('.js-form-type-webform-select-other, .js-webform-type-webform-select-other')) {
177        var $select = $target.find('select');
178        toggleRequired($select, e.value);
179        copyRequireMessage($target, $select);
180      }
181      if ($target.find('> label:not([for])').length) {
182        $target.find('> label').toggleClass('js-form-required form-required', e.value);
183      }
184
185      // Fix required label for checkboxes and radios.
186      // @see Issue #2938414: Checkboxes don't support #states required
187      // @see Issue #2731991: Setting required on radios marks all options required.
188      // @see Issue #2856315: Conditional Logic - Requiring Radios in a Fieldset.
189      // Fix #required for fieldsets.
190      // @see Issue #2977569: Hidden fieldsets that become visible with conditional logic cannot be made required.
191      if ($target.is('.js-webform-type-radios, .js-webform-type-checkboxes, fieldset')) {
192        $target.find('legend span.fieldset-legend:not(.visually-hidden),legend span.fieldset__label:not(.visually-hidden)').toggleClass('js-form-required form-required', e.value);
193      }
194
195      // Issue #2986017: Fieldsets shouldn't have required attribute.
196      if ($target.is('fieldset')) {
197        $target.removeAttr('required aria-required');
198      }
199    }
200  });
201
202  $document.on('state:checked', function (e) {
203    if (e.trigger) {
204      $(e.target).trigger('change');
205    }
206  });
207
208  $document.on('state:readonly', function (e) {
209    if (e.trigger && $(e.target).isWebformElement()) {
210      $(e.target).prop('readonly', e.value).closest('.js-form-item, .js-form-wrapper').toggleClass('webform-readonly', e.value).find('input, textarea').prop('readonly', e.value);
211
212      // Trigger webform:readonly.
213      $(e.target).trigger('webform:readonly')
214        .find('select, input, textarea, button').trigger('webform:readonly');
215    }
216  });
217
218  $document.on('state:visible state:visible-slide', function (e) {
219    if (e.trigger && $(e.target).isWebformElement()) {
220      if (e.value) {
221        $(':input', e.target).addBack().each(function () {
222          restoreValueAndRequired(this);
223          triggerEventHandlers(this);
224        });
225      }
226      else {
227        // @see https://www.sitepoint.com/jquery-function-clear-form-data/
228        $(':input', e.target).addBack().each(function () {
229          backupValueAndRequired(this);
230          clearValueAndRequired(this);
231          triggerEventHandlers(this);
232        });
233      }
234    }
235  });
236
237  $document.on('state:visible-slide', function (e) {
238    if (e.trigger && $(e.target).isWebformElement()) {
239      var effect = e.value ? 'slideDown' : 'slideUp';
240      var duration = Drupal.webform.states[effect].duration;
241      $(e.target).closest('.js-form-item, .js-form-submit, .js-form-wrapper')[effect](duration);
242    }
243  });
244  Drupal.states.State.aliases['invisible-slide'] = '!visible-slide';
245
246  $document.on('state:disabled', function (e) {
247    if (e.trigger && $(e.target).isWebformElement()) {
248      // Make sure disabled property is set before triggering webform:disabled.
249      // Copied from: core/misc/states.js
250      $(e.target)
251        .prop('disabled', e.value)
252        .closest('.js-form-item, .js-form-submit, .js-form-wrapper').toggleClass('form-disabled', e.value)
253        .find('select, input, textarea, button').prop('disabled', e.value);
254
255      // Never disable hidden file[fids] because the existing values will
256      // be completely lost when the webform is submitted.
257      var fileElements = $(e.target)
258        .find(':input[type="hidden"][name$="[fids]"]');
259      if (fileElements.length) {
260        // Remove 'disabled' attribute from fieldset which will block
261        // all disabled elements from being submitted.
262        if ($(e.target).is('fieldset')) {
263          $(e.target).prop('disabled', false);
264        }
265        fileElements.removeAttr('disabled');
266      }
267
268      // Trigger webform:disabled.
269      $(e.target).trigger('webform:disabled')
270        .find('select, input, textarea, button').trigger('webform:disabled');
271    }
272  });
273
274  /* ************************************************************************ */
275  // Behaviors.
276  /* ************************************************************************ */
277
278  /**
279   * Adds HTML5 validation to required checkboxes.
280   *
281   * @type {Drupal~behavior}
282   *
283   * @see https://www.drupal.org/project/webform/issues/3068998
284   */
285  Drupal.behaviors.webformCheckboxesRequired = {
286    attach(context) {
287      $(once('webform-checkboxes-required', '.js-form-type-checkboxes.required, .webform-term-checkboxes.required, .js-form-type-webform-checkboxes-other.required, .js-webform-type-checkboxes.required, .js-webform-type-webform-checkboxes-other.required, .js-webform-type-webform-radios-other.checkboxes', context))
288        .each(function () {
289          var $element = $(this);
290          $element.find('input[type="checkbox"]').on('click', statesCheckboxesRequiredEventHandler);
291          setTimeout(function () {checkboxesRequired($element);});
292        });
293    }
294  };
295
296  /**
297   * Adds HTML5 validation to required radios.
298   *
299   * @type {Drupal~behavior}
300   *
301   * @see https://www.drupal.org/project/webform/issues/2856795
302   */
303  Drupal.behaviors.webformRadiosRequired = {
304    attach(context) {
305      $(once('webform-radios-required', '.js-form-type-radios, .js-form-type-webform-radios-other, .js-webform-type-radios, .js-webform-type-webform-radios-other, .js-webform-type-webform-entity-radios, .js-webform-type-webform-scale', context))
306        .each(function () {
307          var $element = $(this);
308          setTimeout(function () {radiosRequired($element);});
309        });
310    }
311  };
312
313 /**
314   * Adds HTML5 validation to required table select.
315   *
316   * @type {Drupal~behavior}
317   *
318   * @see https://www.drupal.org/project/webform/issues/2856795
319   */
320  Drupal.behaviors.webformTableSelectRequired = {
321    attach(context) {
322      $(once('webform-tableselect-required','.js-webform-tableselect.required', context))
323        .each(function () {
324          var $element = $(this);
325          var $tbody = $element.find('tbody');
326          var isMultiple = $element.is('[multiple]');
327
328          if (isMultiple) {
329            // Check all checkbox triggers checkbox 'change' event on
330            // select and deselect all.
331            // @see Drupal.tableSelect
332            $tbody.find('input[type="checkbox"]').on('click change', function () {
333              checkboxesRequired($tbody);
334            });
335          }
336
337          setTimeout(function () {
338            isMultiple ? checkboxesRequired($tbody) : radiosRequired($element);
339          });
340        });
341    }
342  };
343
344  /**
345   * Add HTML5 multiple checkboxes required validation.
346   *
347   * @param {jQuery} $element
348   *   An jQuery object containing HTML5 radios.
349   *
350   * @see https://stackoverflow.com/a/37825072/145846
351   */
352  function checkboxesRequired($element) {
353    var $firstCheckbox = $element.find('input[type="checkbox"]').first();
354    var isChecked = $element.find('input[type="checkbox"]').is(':checked');
355    toggleRequired($firstCheckbox, !isChecked);
356    copyRequireMessage($element, $firstCheckbox);
357  }
358
359  /**
360   * Add HTML5 radios required validation.
361   *
362   * @param {jQuery} $element
363   *   An jQuery object containing HTML5 radios.
364   *
365   * @see https://www.drupal.org/project/webform/issues/2856795
366   */
367  function radiosRequired($element) {
368    var $radios = $element.find('input[type="radio"]');
369    var isRequired = $element.hasClass('required');
370    toggleRequired($radios, isRequired);
371    copyRequireMessage($element, $radios);
372  }
373
374  /* ************************************************************************ */
375  // Event handlers.
376  /* ************************************************************************ */
377
378  /**
379   * Trigger #states API HTML5 multiple checkboxes required validation.
380   *
381   * @see https://stackoverflow.com/a/37825072/145846
382   */
383  function statesCheckboxesRequiredEventHandler() {
384    var $element = $(this).closest('.js-webform-type-checkboxes, .js-webform-type-webform-checkboxes-other, .js-webform-type-webform-term-checkboxes, .js-webform-tableselect tbody');
385    checkboxesRequired($element);
386  }
387
388  /**
389   * Trigger an input's event handlers.
390   *
391   * @param {element} input
392   *   An input.
393   */
394  function triggerEventHandlers(input) {
395    var $input = $(input);
396    var type = input.type;
397    var tag = input.tagName.toLowerCase();
398    // Add 'webform.states' as extra parameter to event handlers.
399    // @see Drupal.behaviors.webformUnsaved
400    var extraParameters = ['webform.states'];
401    if (type === 'checkbox' || type === 'radio') {
402      $input
403        .trigger('change', extraParameters)
404        .trigger('blur', extraParameters);
405    }
406    else if (tag === 'select') {
407      // Do not trigger the onchange event for Address element's country code
408      // when it is initialized.
409      // @see \Drupal\address\Element\Country
410      if ($input.closest('.webform-type-address').length) {
411        if (!$input.data('webform-states-address-initialized')
412          && $input.attr('autocomplete') === 'country'
413          && $input.val() === $input.find("option[selected]").attr('value')) {
414          return;
415        }
416        $input.data('webform-states-address-initialized', true);
417      }
418
419      $input
420        .trigger('change', extraParameters)
421        .trigger('blur', extraParameters);
422    }
423    else if (type !== 'submit' && type !== 'button' && type !== 'file') {
424      // Make sure input mask is removed and then reset when value is restored.
425      // @see https://www.drupal.org/project/webform/issues/3124155
426      // @see https://www.drupal.org/project/webform/issues/3202795
427      var hasInputMask = ($.fn.inputmask && $input.hasClass('js-webform-input-mask'));
428      hasInputMask && $input.inputmask('remove');
429
430      $input
431        .trigger('input', extraParameters)
432        .trigger('change', extraParameters)
433        .trigger('keydown', extraParameters)
434        .trigger('keyup', extraParameters)
435        .trigger('blur', extraParameters);
436
437      hasInputMask && $input.inputmask();
438    }
439  }
440
441  /* ************************************************************************ */
442  // Backup and restore value functions.
443  /* ************************************************************************ */
444
445  /**
446   * Backup an input's current value and required attribute
447   *
448   * @param {element} input
449   *   An input.
450   */
451  function backupValueAndRequired(input) {
452    var $input = $(input);
453    var type = input.type;
454    var tag = input.tagName.toLowerCase(); // Normalize case.
455
456    // Backup required.
457    if ($input.prop('required') && !$input.hasData('webform-required')) {
458      $input.data('webform-required', true);
459    }
460
461    // Backup value.
462    if (!$input.hasData('webform-value')) {
463      if (type === 'checkbox' || type === 'radio') {
464        $input.data('webform-value', $input.prop('checked'));
465      }
466      else if (tag === 'select') {
467        var values = [];
468        $input.find('option:selected').each(function (i, option) {
469          values[i] = option.value;
470        });
471        $input.data('webform-value', values);
472      }
473      else if (type !== 'submit' && type !== 'button') {
474        $input.data('webform-value', input.value);
475      }
476    }
477  }
478
479  /**
480   * Restore an input's value and required attribute.
481   *
482   * @param {element} input
483   *   An input.
484   */
485  function restoreValueAndRequired(input) {
486    var $input = $(input);
487
488    // Restore value.
489    var value = $input.data('webform-value');
490    if (typeof value !== 'undefined') {
491      var type = input.type;
492      var tag = input.tagName.toLowerCase(); // Normalize case.
493
494      if (type === 'checkbox' || type === 'radio') {
495        $input.prop('checked', value);
496      }
497      else if (tag === 'select') {
498        $.each(value, function (i, option_value) {
499          // Prevent "Syntax error, unrecognized expression" error by
500          // escaping single quotes.
501          // @see https://forum.jquery.com/topic/escape-characters-prior-to-using-selector
502          option_value = option_value.replace(/'/g, "\\\'");
503          $input.find("option[value='" + option_value + "']").prop('selected', true);
504        });
505      }
506      else if (type !== 'submit' && type !== 'button') {
507        input.value = value;
508      }
509      $input.removeData('webform-value');
510    }
511
512    // Restore required.
513    var required = $input.data('webform-required');
514    if (typeof required !== 'undefined') {
515      if (required) {
516        $input.prop('required', true);
517      }
518      $input.removeData('webform-required');
519    }
520  }
521
522  /**
523   * Clear an input's value and required attributes.
524   *
525   * @param {element} input
526   *   An input.
527   */
528  function clearValueAndRequired(input) {
529    var $input = $(input);
530
531    // Check for #states no clear attribute.
532    // @see https://css-tricks.com/snippets/jquery/make-an-jquery-hasattr/
533    if ($input.closest('[data-webform-states-no-clear]').length) {
534      return;
535    }
536
537    // Clear value.
538    var type = input.type;
539    var tag = input.tagName.toLowerCase(); // Normalize case.
540    if (type === 'checkbox' || type === 'radio') {
541      $input.prop('checked', false);
542    }
543    else if (tag === 'select') {
544      if ($input.find('option[value=""]').length) {
545        $input.val('');
546      }
547      else {
548        input.selectedIndex = -1;
549      }
550    }
551    else if (type !== 'submit' && type !== 'button') {
552      input.value = (type === 'color') ? '#000000' : '';
553    }
554
555    // Clear required.
556    $input.prop('required', false);
557  }
558
559  /* ************************************************************************ */
560  // Helper functions.
561  /* ************************************************************************ */
562
563  /**
564   * Toggle an input's required attributes.
565   *
566   * @param {element} $input
567   *   An input.
568   * @param {boolean} required
569   *   Is input required.
570   */
571  function toggleRequired($input, required) {
572    var isCheckboxOrRadio = ($input.attr('type') === 'radio' || $input.attr('type') === 'checkbox');
573    if (required) {
574      if (isCheckboxOrRadio) {
575        $input.attr({'required': 'required'});
576      }
577      else {
578        $input.attr({'required': 'required', 'aria-required': 'true'});
579      }
580    }
581    else {
582      if (isCheckboxOrRadio) {
583        $input.removeAttr('required');
584      }
585      else {
586        $input.removeAttr('required aria-required');
587      }
588      // Clear the validation state for the input.
589      // @see Drupal.behaviors.webformRequiredError
590      $input.each(function () {this.setCustomValidity && this.setCustomValidity('')});
591    }
592  }
593
594  /**
595   * Copy the clientside_validation.module's message.
596   *
597   * @param {jQuery} $source
598   *   The source element.
599   * @param {jQuery} $destination
600   *   The destination element.
601   */
602  function copyRequireMessage($source, $destination) {
603    if ($source.attr('data-msg-required')) {
604      $destination.attr('data-msg-required', $source.attr('data-msg-required'));
605    }
606  }
607
608})(jQuery, Drupal, once);

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.