PageSourceSearch

https://bdsmovement.net/modules/contrib/webform/js/webform.states.js?v=10.2.6

js bdsmovement.net collected 2026-09-24 08:24:47 UTC 23,143 bytes, 644 lines download raw bytes

1/**
2 * @file
3 * JavaScript behaviors for custom webform #states.
4 */
5
6(function ($, Drupal, once) {
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),legend span.fieldset__label: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      $(once('webform-checkboxes-required', '.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        .each(function () {
328          var $element = $(this);
329          $element.find('input[type="checkbox"]').on('click', statesCheckboxesRequiredEventHandler);
330          setTimeout(function () {checkboxesRequired($element);});
331        });
332    }
333  };
334
335  /**
336   * Adds HTML5 validation to required radios.
337   *
338   * @type {Drupal~behavior}
339   *
340   * @see https://www.drupal.org/project/webform/issues/2856795
341   */
342  Drupal.behaviors.webformRadiosRequired = {
343    attach: function (context) {
344      $(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))
345        .each(function () {
346          var $element = $(this);
347          setTimeout(function () {radiosRequired($element);});
348        });
349    }
350  };
351
352 /**
353   * Adds HTML5 validation to required table select.
354   *
355   * @type {Drupal~behavior}
356   *
357   * @see https://www.drupal.org/project/webform/issues/2856795
358   */
359  Drupal.behaviors.webformTableSelectRequired = {
360    attach: function (context) {
361      $(once('webform-tableselect-required','.js-webform-tableselect.required', context))
362        .each(function () {
363          var $element = $(this);
364          var $tbody = $element.find('tbody');
365          var isMultiple = $element.is('[multiple]');
366
367          if (isMultiple) {
368            // Check all checkbox triggers checkbox 'change' event on
369            // select and deselect all.
370            // @see Drupal.tableSelect
371            $tbody.find('input[type="checkbox"]').on('click change', function () {
372              checkboxesRequired($tbody);
373            });
374          }
375
376          setTimeout(function () {
377            isMultiple ? checkboxesRequired($tbody) : radiosRequired($element);
378          });
379        });
380    }
381  };
382
383  /**
384   * Add HTML5 multiple checkboxes required validation.
385   *
386   * @param {jQuery} $element
387   *   An jQuery object containing HTML5 radios.
388   *
389   * @see https://stackoverflow.com/a/37825072/145846
390   */
391  function checkboxesRequired($element) {
392    var $firstCheckbox = $element.find('input[type="checkbox"]').first();
393    var isChecked = $element.find('input[type="checkbox"]').is(':checked');
394    toggleRequired($firstCheckbox, !isChecked);
395    copyRequireMessage($element, $firstCheckbox);
396  }
397
398  /**
399   * Add HTML5 radios required validation.
400   *
401   * @param {jQuery} $element
402   *   An jQuery object containing HTML5 radios.
403   *
404   * @see https://www.drupal.org/project/webform/issues/2856795
405   */
406  function radiosRequired($element) {
407    var $radios = $element.find('input[type="radio"]');
408    var isRequired = $element.hasClass('required');
409    toggleRequired($radios, isRequired);
410    copyRequireMessage($element, $radios);
411  }
412
413  /* ************************************************************************ */
414  // Event handlers.
415  /* ************************************************************************ */
416
417  /**
418   * Trigger #states API HTML5 multiple checkboxes required validation.
419   *
420   * @see https://stackoverflow.com/a/37825072/145846
421   */
422  function statesCheckboxesRequiredEventHandler() {
423    var $element = $(this).closest('.js-webform-type-checkboxes, .js-webform-type-webform-checkboxes-other');
424    checkboxesRequired($element);
425  }
426
427  /**
428   * Trigger an input's event handlers.
429   *
430   * @param {element} input
431   *   An input.
432   */
433  function triggerEventHandlers(input) {
434    var $input = $(input);
435    var type = input.type;
436    var tag = input.tagName.toLowerCase();
437    // Add 'webform.states' as extra parameter to event handlers.
438    // @see Drupal.behaviors.webformUnsaved
439    var extraParameters = ['webform.states'];
440    if (type === 'checkbox' || type === 'radio') {
441      $input
442        .trigger('change', extraParameters)
443        .trigger('blur', extraParameters);
444    }
445    else if (tag === 'select') {
446      // Do not trigger the onchange event for Address element's country code
447      // when it is initialized.
448      // @see \Drupal\address\Element\Country
449      if ($input.closest('.webform-type-address').length) {
450        if (!$input.data('webform-states-address-initialized')
451          && $input.attr('autocomplete') === 'country'
452          && $input.val() === $input.find("option[selected]").attr('value')) {
453          return;
454        }
455        $input.data('webform-states-address-initialized', true);
456      }
457
458      $input
459        .trigger('change', extraParameters)
460        .trigger('blur', extraParameters);
461    }
462    else if (type !== 'submit' && type !== 'button' && type !== 'file') {
463      // Make sure input mask is removed and then reset when value is restored.
464      // @see https://www.drupal.org/project/webform/issues/3124155
465      // @see https://www.drupal.org/project/webform/issues/3202795
466      var hasInputMask = ($.fn.inputmask && $input.hasClass('js-webform-input-mask'));
467      hasInputMask && $input.inputmask('remove');
468
469      $input
470        .trigger('input', extraParameters)
471        .trigger('change', extraParameters)
472        .trigger('keydown', extraParameters)
473        .trigger('keyup', extraParameters)
474        .trigger('blur', extraParameters);
475
476      hasInputMask && $input.inputmask();
477    }
478  }
479
480  /* ************************************************************************ */
481  // Backup and restore value functions.
482  /* ************************************************************************ */
483
484  /**
485   * Backup an input's current value and required attribute
486   *
487   * @param {element} input
488   *   An input.
489   */
490  function backupValueAndRequired(input) {
491    var $input = $(input);
492    var type = input.type;
493    var tag = input.tagName.toLowerCase(); // Normalize case.
494
495    // Backup required.
496    if ($input.prop('required') && !$input.hasData('webform-required')) {
497      $input.data('webform-required', true);
498    }
499
500    // Backup value.
501    if (!$input.hasData('webform-value')) {
502      if (type === 'checkbox' || type === 'radio') {
503        $input.data('webform-value', $input.prop('checked'));
504      }
505      else if (tag === 'select') {
506        var values = [];
507        $input.find('option:selected').each(function (i, option) {
508          values[i] = option.value;
509        });
510        $input.data('webform-value', values);
511      }
512      else if (type !== 'submit' && type !== 'button') {
513        $input.data('webform-value', input.value);
514      }
515    }
516  }
517
518  /**
519   * Restore an input's value and required attribute.
520   *
521   * @param {element} input
522   *   An input.
523   */
524  function restoreValueAndRequired(input) {
525    var $input = $(input);
526
527    // Restore value.
528    var value = $input.data('webform-value');
529    if (typeof value !== 'undefined') {
530      var type = input.type;
531      var tag = input.tagName.toLowerCase(); // Normalize case.
532
533      if (type === 'checkbox' || type === 'radio') {
534        $input.prop('checked', value);
535      }
536      else if (tag === 'select') {
537        $.each(value, function (i, option_value) {
538          // Prevent "Syntax error, unrecognized expression" error by
539          // escaping single quotes.
540          // @see https://forum.jquery.com/topic/escape-characters-prior-to-using-selector
541          option_value = option_value.replace(/'/g, "\\\'");
542          $input.find("option[value='" + option_value + "']").prop('selected', true);
543        });
544      }
545      else if (type !== 'submit' && type !== 'button') {
546        input.value = value;
547      }
548      $input.removeData('webform-value');
549    }
550
551    // Restore required.
552    var required = $input.data('webform-required');
553    if (typeof required !== 'undefined') {
554      if (required) {
555        $input.prop('required', true);
556      }
557      $input.removeData('webform-required');
558    }
559  }
560
561  /**
562   * Clear an input's value and required attributes.
563   *
564   * @param {element} input
565   *   An input.
566   */
567  function clearValueAndRequired(input) {
568    var $input = $(input);
569
570    // Check for #states no clear attribute.
571    // @see https://css-tricks.com/snippets/jquery/make-an-jquery-hasattr/
572    if ($input.closest('[data-webform-states-no-clear]').length) {
573      return;
574    }
575
576    // Clear value.
577    var type = input.type;
578    var tag = input.tagName.toLowerCase(); // Normalize case.
579    if (type === 'checkbox' || type === 'radio') {
580      $input.prop('checked', false);
581    }
582    else if (tag === 'select') {
583      if ($input.find('option[value=""]').length) {
584        $input.val('');
585      }
586      else {
587        input.selectedIndex = -1;
588      }
589    }
590    else if (type !== 'submit' && type !== 'button') {
591      input.value = (type === 'color') ? '#000000' : '';
592    }
593
594    // Clear required.
595    $input.prop('required', false);
596  }
597
598  /* ************************************************************************ */
599  // Helper functions.
600  /* ************************************************************************ */
601
602  /**
603   * Toggle an input's required attributes.
604   *
605   * @param {element} $input
606   *   An input.
607   * @param {boolean} required
608   *   Is input required.
609   */
610  function toggleRequired($input, required) {
611    var isCheckboxOrRadio = ($input.attr('type') === 'radio' || $input.attr('type') === 'checkbox');
612    if (required) {
613      if (isCheckboxOrRadio) {
614        $input.attr({'required': 'required'});
615      }
616      else {
617        $input.attr({'required': 'required', 'aria-required': 'true'});
618      }
619    }
620    else {
621      if (isCheckboxOrRadio) {
622        $input.removeAttr('required');
623      }
624      else {
625        $input.removeAttr('required aria-required');
626      }
627    }
628  }
629
630  /**
631   * Copy the clientside_validation.module's message.
632   *
633   * @param {jQuery} $source
634   *   The source element.
635   * @param {jQuery} $destination
636   *   The destination element.
637   */
638  function copyRequireMessage($source, $destination) {
639    if ($source.attr('data-msg-required')) {
640      $destination.attr('data-msg-required', $source.attr('data-msg-required'));
641    }
642  }
643
644})(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.