PageSourceSearch

https://www.msstate.edu/modules/webform/js/webform.states.js?v=8.9.20

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