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 * Check if an element has a specified data attribute. 19 * 20 * @param {string} data 21 * The data attribute name. 22 * 23 * @return {boolean} 24 * TRUE if an element has a specified data attribute. 25 */ 26 $.fn.hasData = function (data) { 27 return (typeof this.data(data) !== 'undefined'); 28 }; 29 30 /** 31 * Check if element is within the webform or not. 32 * 33 * @return {boolean} 34 * TRUE if element is within the webform. 35 */ 36 $.fn.isWebform = function () { 37 return $(this).closest('form[id^="webform"]').length ? true : false; 38 }; 39 40 // The change event is triggered by cut-n-paste and select menus. 41 // Issue #2445271: #states element empty check not triggered on mouse 42 // based paste. 43 // @see https://www.drupal.org/node/2445271 44 Drupal.states.Trigger.states.empty.change = function change() { 45 return this.val() === ''; 46 }; 47 48 // Apply solution included in #1962800 patch. 49 // Issue #1962800: Form #states not working with literal integers as 50 // values in IE11. 51 // @see https://www.drupal.org/project/drupal/issues/1962800 52 // @see https://www.drupal.org/files/issues/core-states-not-working-with-integers-ie11_1962800_46.patch 53 // 54 // This issue causes pattern, less than, and greater than support to break. 55 // @see https://www.drupal.org/project/webform/issues/2981724 56 var states = Drupal.states; 57 Drupal.states.Dependent.prototype.compare = function compare(reference, selector, state) { 58 var value = this.values[selector][state.name]; 59 60 var name = reference.constructor.name; 61 if (!name) { 62 name = $.type(reference); 63 64 name = name.charAt(0).toUpperCase() + name.slice(1); 65 } 66 if (name in states.Dependent.comparisons) { 67 return states.Dependent.comparisons[name](reference, value); 68 } 69 70 if (reference.constructor.name in states.Dependent.comparisons) { 71 return states.Dependent.comparisons[reference.constructor.name](reference, value); 72 } 73 74 return _compare2(reference, value); 75 }; 76 function _compare2(a, b) { 77 if (a === b) { 78 return typeof a === 'undefined' ? a : true; 79 } 80 81 return typeof a === 'undefined' || typeof b === 'undefined'; 82 } 83 84 // Adds pattern, less than, and greater than support to #state API. 85 // @see http://drupalsun.com/julia-evans/2012/03/09/extending-form-api-states-regular-expressions 86 Drupal.states.Dependent.comparisons.Object = function (reference, value) { 87 if ('pattern' in reference) { 88 return (new RegExp(reference['pattern'])).test(value); 89 } 90 else if ('!pattern' in reference) { 91 return !((new RegExp(reference['!pattern'])).test(value)); 92 } 93 else if ('less' in reference) { 94 return (value !== '' && parseFloat(reference['less']) > parseFloat(value)); 95 } 96 else if ('greater' in reference) { 97 return (value !== '' && parseFloat(reference['greater']) < parseFloat(value)); 98 } 99 else { 100 return reference.indexOf(value) !== false; 101 } 102 }; 103 104 var $document = $(document); 105 106 $document.on('state:required', function (e) { 107 if (e.trigger && $(e.target).isWebform()) { 108 var $target = $(e.target); 109 // Fix #required file upload. 110 // @see Issue #2860529: Conditional required File upload field don't work. 111 if (e.value) { 112 $target.find('input[type="file"]').attr({'required': 'required', 'aria-required': 'true'}); 113 } 114 else { 115 $target.find('input[type="file"]').removeAttr('required aria-required'); 116 } 117 118 // Fix required label for checkboxes and radios. 119 // @see Issue #2938414: Checkboxes don't support #states required 120 // @see Issue #2731991: Setting required on radios marks all options required. 121 // @see Issue #2856315: Conditional Logic - Requiring Radios in a Fieldset. 122 // Fix #required for fieldsets. 123 // @see Issue #2977569: Hidden fieldsets that become visible with conditional logic cannot be made required. 124 if ($target.is('.js-webform-type-radios, .js-webform-type-checkboxes, fieldset')) { 125 if (e.value) { 126 $target.find('legend span.fieldset-legend:not(.visually-hidden)').addClass('js-form-required form-required'); 127 } 128 else { 129 $target.find('legend span.fieldset-legend:not(.visually-hidden)').removeClass('js-form-required form-required'); 130 } 131 } 132 133 // Fix #required for radios. 134 // @see Issue #2856795: If radio buttons are required but not filled form is nevertheless submitted. 135 if ($target.is('.js-webform-type-radios, .js-form-type-webform-radios-other')) { 136 if (e.value) { 137 $target.find('input[type="radio"]').attr({'required': 'required', 'aria-required': 'true'}); 138 } 139 else { 140 $target.find('input[type="radio"]').removeAttr('required aria-required'); 141 } 142 } 143 144 // Fix #required for checkboxes. 145 // @see Issue #2938414: Checkboxes don't support #states required. 146 // @see checkboxRequiredhandler 147 if ($target.is('.js-webform-type-checkboxes, .js-form-type-webform-checkboxes-other')) { 148 var $checkboxes = $target.find('input[type="checkbox"]'); 149 if (e.value) { 150 // Bind the event handler and add custom HTML5 required validation 151 // to all checkboxes. 152 $checkboxes.bind('click', checkboxRequiredhandler); 153 if (!$checkboxes.is(':checked')) { 154 $checkboxes.attr({'required': 'required', 'aria-required': 'true'}); 155 } 156 } 157 else { 158 // Remove custom HTML5 required validation from all checkboxes 159 // and unbind the event handler. 160 $checkboxes 161 .removeAttr('required aria-required') 162 .unbind('click', checkboxRequiredhandler); 163 } 164 } 165 166 // Issue #2986017: Fieldsets shouldn't have required attribute. 167 if ($target.is('fieldset')) { 168 $target.removeAttr('required aria-required'); 169 } 170 } 171 172 }); 173 174 $document.on('state:readonly', function (e) { 175 if (e.trigger && $(e.target).isWebform()) { 176 $(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); 177 } 178 }); 179 180 $document.on('state:visible state:visible-slide', function (e) { 181 if (e.trigger && $(e.target).isWebform()) { 182 if (e.value) { 183 $(':input', e.target).addBack().each(function () { 184 restoreValueAndRequired(this); 185 triggerEventHandlers(this); 186 }); 187 } 188 else { 189 // @see https://www.sitepoint.com/jquery-function-clear-form-data/ 190 $(':input', e.target).addBack().each(function () { 191 backupValueAndRequired(this); 192 clearValueAndRequired(this); 193 triggerEventHandlers(this); 194 }); 195 } 196 } 197 }); 198 199 $document.bind('state:visible-slide', function (e) { 200 if (e.trigger && $(e.target).isWebform()) { 201 var effect = e.value ? 'slideDown' : 'slideUp'; 202 var duration = Drupal.webform.states[effect].duration; 203 $(e.target).closest('.js-form-item, .js-form-submit, .js-form-wrapper')[effect](duration); 204 } 205 }); 206 Drupal.states.State.aliases['invisible-slide'] = '!visible-slide'; 207 208 $document.on('state:disabled', function (e) { 209 if (e.trigger && $(e.target).isWebform()) { 210 // Make sure disabled property is set before triggering webform:disabled. 211 // Copied from: core/misc/states.js 212 $(e.target) 213 .prop('disabled', e.value) 214 .closest('.js-form-item, .js-form-submit, .js-form-wrapper').toggleClass('form-disabled', e.value) 215 .find('select, input, textarea, button').prop('disabled', e.value); 216 217 // Trigger webform:disabled. 218 $(e.target).trigger('webform:disabled') 219 .find('select, input, textarea, button').trigger('webform:disabled'); 220 } 221 }); 222 223 /** 224 * Trigger custom HTML5 multiple checkboxes validation. 225 * 226 * @see https://stackoverflow.com/a/37825072/145846 227 */ 228 function checkboxRequiredhandler() { 229 var $checkboxes = $(this).closest('.js-webform-type-checkboxes, .js-form-type-webform-checkboxes-other').find('input[type="checkbox"]'); 230 if ($checkboxes.is(':checked')) { 231 $checkboxes.removeAttr('required aria-required'); 232 } 233 else { 234 $checkboxes.attr({'required': 'required', 'aria-required': 'true'}); 235 } 236 } 237 238 /** 239 * Trigger an input's event handlers. 240 * 241 * @param {element} input 242 * An input. 243 */ 244 function triggerEventHandlers(input) { 245 var $input = $(input); 246 var type = input.type; 247 var tag = input.tagName.toLowerCase(); 248 // Add 'webform.states' as extra parameter to event handlers. 249 // @see Drupal.behaviors.webformUnsaved 250 var extraParameters = ['webform.states']; 251 if (type === 'checkbox' || type === 'radio') { 252 $input 253 .trigger('change', extraParameters) 254 .trigger('blur', extraParameters); 255 } 256 else if (tag === 'select') { 257 $input 258 .trigger('change', extraParameters) 259 .trigger('blur', extraParameters); 260 } 261 else if (type !== 'submit' && type !== 'button' && type !== 'file') { 262 $input 263 .trigger('input', extraParameters) 264 .trigger('change', extraParameters) 265 .trigger('keydown', extraParameters) 266 .trigger('keyup', extraParameters) 267 .trigger('blur', extraParameters); 268 } 269 } 270 271 /** 272 * Backup an input's current value and required attribute 273 * 274 * @param {element} input 275 * An input. 276 */ 277 function backupValueAndRequired(input) { 278 var $input = $(input); 279 var type = input.type; 280 var tag = input.tagName.toLowerCase(); // Normalize case. 281 282 // Backup required. 283 if ($input.prop('required') && !$input.hasData('webform-required')) { 284 $input.data('webform-required', true); 285 } 286 287 // Backup value. 288 if (!$input.hasData('webform-value')) { 289 if (type === 'checkbox' || type === 'radio') { 290 $input.data('webform-value', $input.prop('checked')); 291 } 292 else if (tag === 'select') { 293 var values = []; 294 $input.find('option:selected').each(function (i, option) { 295 values[i] = option.value; 296 }); 297 $input.data('webform-value', values); 298 } 299 else if (type !== 'submit' && type !== 'button') { 300 $input.data('webform-value', input.value); 301 } 302 } 303 } 304 305 /** 306 * Restore an input's value and required attribute. 307 * 308 * @param {element} input 309 * An input. 310 */ 311 function restoreValueAndRequired(input) { 312 var $input = $(input); 313 314 // Restore value. 315 var value = $input.data('webform-value'); 316 if (typeof value !== 'undefined') { 317 var type = input.type; 318 var tag = input.tagName.toLowerCase(); // Normalize case. 319 320 if (type === 'checkbox' || type === 'radio') { 321 $input.prop('checked', value); 322 } 323 else if (tag === 'select') { 324 $.each(value, function (i, option_value) { 325 $input.find("option[value='" + option_value + "']").prop('selected', true); 326 }); 327 } 328 else if (type !== 'submit' && type !== 'button') { 329 input.value = value; 330 } 331 $input.removeData('webform-value'); 332 } 333 334 // Restore required. 335 var required = $input.data('webform-required'); 336 if (typeof required !== 'undefined') { 337 if (required) { 338 $input.prop('required', true); 339 } 340 $input.removeData('webform-required'); 341 } 342 } 343 344 /** 345 * Clear an input's value and required attributes. 346 * 347 * @param {element} input 348 * An input. 349 */ 350 function clearValueAndRequired(input) { 351 var $input = $(input); 352 353 // Check for #states no clear attribute.
354 // @see https://css-tricks.com/snippets/jquery/make-an-jquery-hasattr/ 355 if ($input.closest('[data-webform-states-no-clear]').length) { 356 return; 357 } 358 359 // Clear value. 360 var type = input.type; 361 var tag = input.tagName.toLowerCase(); // Normalize case. 362 if (type === 'checkbox' || type === 'radio') { 363 $input.prop('checked', false); 364 } 365 else if (tag === 'select') { 366 if ($input.find('option[value=""]').length) { 367 $input.val(''); 368 } 369 else { 370 input.selectedIndex = -1; 371 } 372 } 373 else if (type !== 'submit' && type !== 'button') { 374 input.value = (type === 'color') ? '#000000' : ''; 375 } 376 377 // Clear required. 378 $input.prop('required', false); 379 } 380 381})(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.