PageSourceSearch

https://www.ideva.es/modules/contrib/webform/js/webform.states.js?v=9.5.4

js ideva.es collected 2026-10-05 19:13:05 UTC 23,106 bytes, 647 lines download raw bytes

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

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.