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[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. 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')) { 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 var $checkboxes = $target.find('input[type="checkbox"]'); 171 if (e.value) { 172 // Add event handler. 173 $checkboxes.on('click', statesCheckboxesRequiredEventHandler); 174 // Initialize and add required attribute. 175 checkboxesRequired($target); 176 } 177 else { 178 // Remove event handler. 179 $checkboxes.off('click', statesCheckboxesRequiredEventHandler); 180 // Remove required attribute. 181 toggleRequired($checkboxes, false); 182 } 183 } 184 185 // Fix required label for elements without the for attribute. 186 // @see Issue #3145300: Conditional Visible Select Other not working. 187 if ($target.is('.js-form-type-webform-select-other, .js-webform-type-webform-select-other')) { 188 var $select = $target.find('select'); 189 toggleRequired($select, e.value); 190 copyRequireMessage($target, $select); 191 } 192 if ($target.find('> label:not([for])').length) { 193 $target.find('> label').toggleClass('js-form-required form-required', e.value); 194 } 195 196 // Fix required label for checkboxes and radios. 197 // @see Issue #2938414: Checkboxes don't support #states required 198 // @see Issue #2731991: Setting required on radios marks all options required. 199 // @see Issue #2856315: Conditional Logic - Requiring Radios in a Fieldset. 200 // Fix #required for fieldsets. 201 // @see Issue #2977569: Hidden fieldsets that become visible with conditional logic cannot be made required. 202 if ($target.is('.js-webform-type-radios, .js-webform-type-checkboxes, fieldset')) { 203 $target.find('legend span.fieldset-legend:not(.visually-hidden)').toggleClass('js-form-required form-required', e.value); 204 } 205 206 // Issue #2986017: Fieldsets shouldn't have required attribute. 207 if ($target.is('fieldset')) { 208 $target.removeAttr('required aria-required'); 209 } 210 } 211 }); 212 213 $document.on('state:checked', function (e) { 214 if (e.trigger) { 215 $(e.target).trigger('change'); 216 } 217 }); 218 219 $document.on('state:readonly', function (e) { 220 if (e.trigger && $(e.target).isWebformElement()) { 221 $(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); 222 223 // Trigger webform:readonly. 224 $(e.target).trigger('webform:readonly') 225 .find('select, input, textarea, button').trigger('webform:readonly'); 226 } 227 }); 228 229 $document.on('state:visible state:visible-slide', function (e) { 230 if (e.trigger && $(e.target).isWebformElement()) { 231 if (e.value) { 232 $(':input', e.target).addBack().each(function () { 233 restoreValueAndRequired(this); 234 triggerEventHandlers(this); 235 }); 236 } 237 else { 238 // @see https://www.sitepoint.com/jquery-function-clear-form-data/ 239 $(':input', e.target).addBack().each(function () { 240 backupValueAndRequired(this); 241 clearValueAndRequired(this); 242 triggerEventHandlers(this); 243 }); 244 } 245 } 246 }); 247 248 $document.on('state:visible-slide', function (e) { 249 if (e.trigger && $(e.target).isWebformElement()) { 250 var effect = e.value ? 'slideDown' : 'slideUp'; 251 var duration = Drupal.webform.states[effect].duration; 252 $(e.target).closest('.js-form-item, .js-form-submit, .js-form-wrapper')[effect](duration); 253 } 254 }); 255 Drupal.states.State.aliases['invisible-slide'] = '!visible-slide'; 256 257 $document.on('state:disabled', function (e) { 258 if (e.trigger && $(e.target).isWebformElement()) { 259 // Make sure disabled property is set before triggering webform:disabled. 260 // Copied from: core/misc/states.js 261 $(e.target) 262 .prop('disabled', e.value) 263 .closest('.js-form-item, .js-form-submit, .js-form-wrapper').toggleClass('form-disabled', e.value) 264 .find('select, input, textarea, button').prop('disabled', e.value); 265 266 // Never disable hidden file[fids] because the existing values will 267 // be completely lost when the webform is submitted. 268 var fileElements = $(e.target) 269 .find(':input[type="hidden"][name$="[fids]"]'); 270 if (fileElements.length) { 271 // Remove 'disabled' attribute from fieldset which will block 272 // all disabled elements from being submitted. 273 if ($(e.target).is('fieldset')) { 274 $(e.target).prop('disabled', false); 275 } 276 fileElements.removeAttr('disabled'); 277 } 278 279 // Trigger webform:disabled. 280 $(e.target).trigger('webform:disabled') 281 .find('select, input, textarea, button').trigger('webform:disabled'); 282 } 283 }); 284 285 /* ************************************************************************ */ 286 // Behaviors. 287 /* ************************************************************************ */ 288 289 /** 290 * Adds HTML5 validation to required checkboxes. 291 * 292 * @type {Drupal~behavior} 293 * 294 * @see https://www.drupal.org/project/webform/issues/3068998 295 */ 296 Drupal.behaviors.webformCheckboxesRequired = { 297 attach: function (context) { 298 $('.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) 299 .once('webform-checkboxes-required') 300 .each(function () { 301 var $element = $(this); 302 $element.find('input[type="checkbox"]').on('click', statesCheckboxesRequiredEventHandler); 303 setTimeout(function () {checkboxesRequired($element);}); 304 }); 305 } 306 }; 307 308 /** 309 * Adds HTML5 validation to required radios. 310 * 311 * @type {Drupal~behavior} 312 * 313 * @see https://www.drupal.org/project/webform/issues/2856795 314 */ 315 Drupal.behaviors.webformRadiosRequired = { 316 attach: function (context) { 317 $('.js-form-type-radios, .js-form-type-webform-radios-other, .js-webform-type-radios, .js-webform-type-webform-radios-other', context) 318 .once('webform-radios-required') 319 .each(function () { 320 var $element = $(this); 321 setTimeout(function () {radiosRequired($element);}); 322 }); 323 } 324 }; 325 326 /** 327 * Add HTML5 multiple checkboxes required validation. 328 * 329 * @param {jQuery} $element
330 * An jQuery object containing HTML5 radios. 331 * 332 * @see https://stackoverflow.com/a/37825072/145846 333 */ 334 function checkboxesRequired($element) { 335 var $firstCheckbox = $element.find('input[type="checkbox"]').first(); 336 var isChecked = $element.find('input[type="checkbox"]').is(':checked'); 337 toggleRequired($firstCheckbox, !isChecked); 338 copyRequireMessage($element, $firstCheckbox); 339 } 340 341 /** 342 * Add HTML5 radios required validation. 343 * 344 * @param {jQuery} $element 345 * An jQuery object containing HTML5 radios. 346 * 347 * @see https://www.drupal.org/project/webform/issues/2856795 348 */ 349 function radiosRequired($element) { 350 var $radios = $element.find('input[type="radio"]'); 351 var isRequired = $element.hasClass('required'); 352 toggleRequired($radios, isRequired); 353 copyRequireMessage($element, $radios); 354 } 355 356 /* ************************************************************************ */ 357 // Event handlers. 358 /* ************************************************************************ */ 359 360 /** 361 * Trigger #states API HTML5 multiple checkboxes required validation. 362 * 363 * @see https://stackoverflow.com/a/37825072/145846 364 */ 365 function statesCheckboxesRequiredEventHandler() { 366 var $element = $(this).closest('.js-webform-type-checkboxes, .js-webform-type-webform-checkboxes-other'); 367 checkboxesRequired($element); 368 } 369 370 /** 371 * Trigger an input's event handlers. 372 * 373 * @param {element} input 374 * An input. 375 */ 376 function triggerEventHandlers(input) { 377 var $input = $(input); 378 var type = input.type; 379 var tag = input.tagName.toLowerCase(); 380 // Add 'webform.states' as extra parameter to event handlers. 381 // @see Drupal.behaviors.webformUnsaved 382 var extraParameters = ['webform.states']; 383 if (type === 'checkbox' || type === 'radio') { 384 $input 385 .trigger('change', extraParameters) 386 .trigger('blur', extraParameters); 387 } 388 else if (tag === 'select') { 389 $input 390 .trigger('change', extraParameters) 391 .trigger('blur', extraParameters); 392 } 393 else if (type !== 'submit' && type !== 'button' && type !== 'file') { 394 $input 395 .trigger('input', extraParameters) 396 .trigger('change', extraParameters) 397 .trigger('keydown', extraParameters) 398 .trigger('keyup', extraParameters) 399 .trigger('blur', extraParameters); 400 401 // Make sure input mask is reset when value is restored. 402 // @see https://www.drupal.org/project/webform/issues/3124155 403 if ($input.attr('data-inputmask-mask')) { 404 setTimeout(function () {$input.inputmask('remove').inputmask();}); 405 } 406 } 407 } 408 409 /* ************************************************************************ */ 410 // Backup and restore value functions. 411 /* ************************************************************************ */ 412 413 /** 414 * Backup an input's current value and required attribute 415 * 416 * @param {element} input 417 * An input. 418 */ 419 function backupValueAndRequired(input) { 420 var $input = $(input); 421 var type = input.type; 422 var tag = input.tagName.toLowerCase(); // Normalize case. 423 424 // Backup required. 425 if ($input.prop('required') && !$input.hasData('webform-required')) { 426 $input.data('webform-required', true); 427 } 428 429 // Backup value. 430 if (!$input.hasData('webform-value')) { 431 if (type === 'checkbox' || type === 'radio') { 432 $input.data('webform-value', $input.prop('checked')); 433 } 434 else if (tag === 'select') { 435 var values = []; 436 $input.find('option:selected').each(function (i, option) { 437 values[i] = option.value; 438 }); 439 $input.data('webform-value', values); 440 } 441 else if (type !== 'submit' && type !== 'button') { 442 $input.data('webform-value', input.value); 443 } 444 } 445 } 446 447 /** 448 * Restore an input's value and required attribute. 449 * 450 * @param {element} input 451 * An input. 452 */ 453 function restoreValueAndRequired(input) { 454 var $input = $(input); 455 456 // Restore value. 457 var value = $input.data('webform-value'); 458 if (typeof value !== 'undefined') { 459 var type = input.type; 460 var tag = input.tagName.toLowerCase(); // Normalize case. 461 462 if (type === 'checkbox' || type === 'radio') { 463 $input.prop('checked', value); 464 } 465 else if (tag === 'select') { 466 $.each(value, function (i, option_value) { 467 $input.find("option[value='" + option_value + "']").prop('selected', true); 468 }); 469 } 470 else if (type !== 'submit' && type !== 'button') { 471 input.value = value; 472 } 473 $input.removeData('webform-value'); 474 } 475 476 // Restore required. 477 var required = $input.data('webform-required'); 478 if (typeof required !== 'undefined') { 479 if (required) { 480 $input.prop('required', true); 481 } 482 $input.removeData('webform-required'); 483 } 484 } 485 486 /** 487 * Clear an input's value and required attributes. 488 * 489 * @param {element} input 490 * An input. 491 */ 492 function clearValueAndRequired(input) { 493 var $input = $(input); 494 495 // Check for #states no clear attribute.
496 // @see https://css-tricks.com/snippets/jquery/make-an-jquery-hasattr/ 497 if ($input.closest('[data-webform-states-no-clear]').length) { 498 return; 499 } 500 501 // Clear value. 502 var type = input.type; 503 var tag = input.tagName.toLowerCase(); // Normalize case. 504 if (type === 'checkbox' || type === 'radio') { 505 $input.prop('checked', false); 506 } 507 else if (tag === 'select') { 508 if ($input.find('option[value=""]').length) { 509 $input.val(''); 510 } 511 else { 512 input.selectedIndex = -1; 513 } 514 } 515 else if (type !== 'submit' && type !== 'button') { 516 input.value = (type === 'color') ? '#000000' : ''; 517 } 518 519 // Clear required. 520 $input.prop('required', false); 521 } 522 523 /* ************************************************************************ */ 524 // Helper functions. 525 /* ************************************************************************ */ 526 527 /** 528 * Toggle an input's required attributes. 529 * 530 * @param {element} $input 531 * An input. 532 * @param {boolean} required 533 * Is input required. 534 */ 535 function toggleRequired($input, required) { 536 if (required) { 537 $input.attr({'required': 'required', 'aria-required': 'true'}); 538 } 539 else { 540 $input.removeAttr('required aria-required'); 541 } 542 } 543 544 /** 545 * Copy the clientside_validation.module's message. 546 * 547 * @param {jQuery} $source 548 * The source element. 549 * @param {jQuery} $destination 550 * The destination element. 551 */ 552 function copyRequireMessage($source, $destination) { 553 if ($source.attr('data-msg-required')) { 554 $destination.attr('data-msg-required', $source.attr('data-msg-required')); 555 } 556 } 557 558})(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.