1(function () { 2 var window = this, 3 $ = window.jQuery, 4 DDC = (window.DDC = (window.DDC || {})), 5 formsV2 = DDC.hasFeature.formsV2, 6 shakeTimeout; 7 8 DDC.modules.validator.setRequired = function (required, validate) { 9 // takes an element and makes it required 10 // adds <span role='presentation' aria-hidden='true' class='asterisk'>*</span>, .required class, [required=required] attribute, 11 var $required = $(required).filter('input, select, textarea').eq(0), 12 $requiredIndicatorElement = $('<span>', {'aria-hidden': 'true', text: '*', 'class': 'asterisk'}); 13 if ($required.size()) { 14 var $formGroup = $required.closest('.form-group'); 15 if ($formGroup.size() && !$formGroup.find('label span:eq(0) span.asterisk').size() && !$formGroup.find('span.asterisk').size()) { 16 if($formGroup.find('label span').size()) { 17 $formGroup.find('span:eq(0)').append($requiredIndicatorElement); 18 }else { 19 $formGroup.find('label').append($requiredIndicatorElement); 20 } 21 } else if (!$('span:eq(0)', $required.closest('label')).find('span.asterisk').size()) { 22 $('span:eq(0)', $required.closest('label')).append($requiredIndicatorElement); 23 } 24 $required.addClass('required').attr('required', 'true').attr('aria-required', 'true'); 25 } 26 if (validate) { 27 $required.closest('form').data('validator').checkValidity($required); 28 } 29 },
30 DDC.modules.validator.clearRequired = function (set) { 31 var conf, label; 32 // removes required-ness from a set of form elements, including any validation error messages 33 $set = $(set); 34 if ($set.size() && $set.closest('form').data('validator')) { 35 conf = $set.closest('form').data('validator').getConf(); 36 $set.filter('input, select, textarea').removeClass(conf.errorClass).removeClass('required').removeAttr('required').removeAttr('aria-required').each(function () { 37 38 if (formsV2) { 39 var $formGroup = $(this).closest('.form-group'); 40 label = $formGroup.find('label'); 41 errorWrapper = $formGroup; 42 } else { 43 label = $(this).closest('label'); 44 errorWrapper = label; 45 } 46 47 label.find('span.asterisk').filter(function () { 48 return $(this).text() === '*'; 49 }).add($('.errors', errorWrapper)).remove(); 50 }); 51 } 52 }, 53 DDC.modules.validator.refresh = function () { 54 var selectedForm, 55 emailRequired = '', 56 phoneRequired = '', 57 formId = '', 58 emailRe = /^([a-z0-9_\.\-\+]+)@(?!.*[\.]{2})([\da-z\.\-]+)\.([a-z\.]{2,6})$/i, 59 // http://net.tutsplus.com/tutorials/other/8-regular-expressions-you-should-know/ 60 contactMeByHandler = function (select) { 61 62 // JIRA-11518: Cannot set both email and phone as required fields 63 // This allows phone to be set to required if the form definition says 64 // both it should be required. 65 66 var targets = $(), 67 activeTarget, 68 $select = $(select), 69 $opts = $select.find('option'), 70 $form = $select.closest('form'), 71 $textingOption = $form.find('[data-id="texting-preferred"]'), 72 selector = $select.val().toLowerCase(), 73 getTargetFromLabel = function(labelSelector) { 74 // TODO: replace class-based coupling to form field id-based coupling i.e. $('[name="$formFieldId"]', form).find('input,textarea,select') 75 labelSelector = labelSelector === "cell" || labelSelector === "text" ? "phone" : labelSelector; 76 if (formsV2) { 77 target = $form.find('label.' + labelSelector).closest(".form-group").find('input, textarea, select'); 78 } else { 79 target = $form.find('.' + labelSelector).find('input, textarea, select'); 80 } 81 82 return target; 83 }; 84 85 if (selector) { 86 activeTarget = getTargetFromLabel(selector); 87 } 88 89 if($textingOption.length > 0){ 90 textingPreferredField = $textingOption.first().closest(".form-group") 91 if(selector.toLowerCase() == 'email'){ 92 textingPreferredField.addClass('hide'); 93 toggleRequired($textingOption, false); 94 } else if(selector.toLowerCase() != 'email'){ 95 textingPreferredField.removeClass('hide'); 96 toggleRequired($textingOption, true); 97 } 98 } 99 100 // only works for select elements right now 101 if ($opts.size()) { 102 $opts.each(function () { 103 var optionTarget; 104 105 selector = $(this).val().toLowerCase(); 106 107 if (!selector) { 108 return; 109 } 110 111 optionTarget = getTargetFromLabel(selector); 112 113 if (optionTarget.size()) { 114 // JIRA-11518: If check added for JIRA task 115 if ( ( optionTarget.attr('name').indexOf('email') !== -1 && $form.attr('data-email-required') == 'false' ) || 116 ( optionTarget.attr('name').indexOf('phone') !== -1 && $form.attr('data-phone-required') == 'false' ) ) { 117 targets = targets.add(optionTarget); 118 } 119 } 120 }); 121 } 122 123 if ( targets.size() > 0 ) {
124 DDC.modules.validator.clearRequired(targets); 125 } 126 if (activeTarget) { 127 DDC.modules.validator.setRequired(activeTarget); 128 } 129 }, 130 131 acceptTextDisclaimerHandler = function (select, isRequired, id) { 132 var targets = $(), 133 activeTarget, 134 activeFormGroup, 135 activeClass, 136 $select = $(select), 137 $form = $select.closest('form'), 138 getTarget = function(selector) { 139 if (formsV2) { 140 target = $form.find(selector).parent(); 141 } 142 143 return target; 144 }; 145 146 activeTarget = getTarget('input[data-id=' + id + ']'); 147 activeFormGroup = '.form-group'; 148 activeClass = 'hide'; 149 150 if (activeTarget) { 151 // Check if this is a *NotRequired field 152 var isNotRequiredField = id.indexOf('NotRequired') !== -1; 153 154 if(isRequired) { 155 if (isNotRequiredField) { 156 // For *NotRequired fields, only show/hide them, never set them as required 157 activeTarget.closest(activeFormGroup).removeClass(activeClass); 158 } else { 159 // For regular fields, set as required and show
160 DDC.modules.validator.setRequired(activeTarget); 161 activeTarget.closest(activeFormGroup).removeClass(activeClass); 162 toggleRequired( activeTarget.find('input[data-id=' + id + ']'),true); 163 } 164 } else { 165 if (isNotRequiredField) { 166 // For *NotRequired fields, just hide them 167 activeTarget.closest(activeFormGroup).addClass(activeClass); 168 } else { 169 // For regular fields, clear required and hide 170 DDC.modules.validator.clearRequired(activeTarget); 171 activeTarget.closest(activeFormGroup).addClass(activeClass); 172 toggleRequired(activeTarget.find('input[data-id=' + id + ']'),false); 173 } 174 } 175 } 176 }, 177 toggleRequired = function(element, state) { 178 element.attr('aria-required', state); 179 if(state){ 180 element.addClass('required'); 181 element.attr('required', "true"); 182 } else { 183 element.removeClass('required'); 184 element.removeAttr('required'); 185 } 186 }, 187 188 consentCheckboxHandler = function(checkboxField, consentField){ 189 if(checkboxField.is(":checked")){ 190 consentField.attr('value', checkboxField.attr('data-true')); 191 } else { 192 consentField.attr('value', checkboxField.attr('data-false')); 193 } 194 }; 195 196 if ($.tools && $.tools.validator && $.tools.validator.fn) { 197 198 var checkDate = function(el, value, inputDateFormatString, returnDateFormatString) { 199 if (value === '') { 200 return true; 201 } 202 203 var 204 $el = $(el), 205 $datepicker = $.datepicker, 206 getDate, 207 minDateAttr, 208 maxDateAttr, 209 minDateObj, 210 maxDateObj 211 ; 212 213 try { 214 getDate = $datepicker.parseDate(inputDateFormatString, value); 215 } catch (e) { 216 if(!DDC.isLive){ 217 console.log(e); 218 } 219 return DDC.i18n.getLabel('INVALID_DATE'); 220 } 221 222 minDateAttr = ( $el.attr('data-min-date') ) ? $el.attr('data-min-date') : $el.attr('min'); 223 maxDateAttr = ( $el.attr('data-max-date') ) ? $el.attr('data-max-date') : $el.attr('max'); 224 225 minDateObj = $datepicker._determineDate(getDate, minDateAttr, new Date()); 226 maxDateObj = $datepicker._determineDate(getDate, maxDateAttr, new Date()); 227 228 if (minDateAttr && minDateObj > getDate) { 229 return DDC.i18n.getLabel('PLEASE_ENTER_A_VALUE_LARGER_THAN_ONE_DOLLAR').replace('$1', $datepicker.formatDate(returnDateFormatString, minDateObj)).replace('1$', $datepicker.formatDate(returnDateFormatString, minDateObj)); 230 } else if (maxDateAttr && maxDateObj < getDate) { 231 return DDC.i18n.getLabel('PLEASE_ENTER_A_VALUE_SMALLER_THAN_ONE_DOLLAR').replace('$1', $datepicker.formatDate(returnDateFormatString, maxDateObj)).replace('1$', $datepicker.formatDate(returnDateFormatString, maxDateObj)); 232 } 233 234 return true; 235 }; 236 237 $.tools.validator.fn(':input[type="date"].date', function (el, value) { 238 var locale = $('html').attr('lang').toString().toLowerCase(); 239 240 // mobile input 241 var inputDateFormatString = 'yy-mm-dd'; 242 var returnDateFormatString = (locale === 'es-mx' ? 'dd/mm/yy' : 'mm/dd/yy'); 243 244 return checkDate(el, value, inputDateFormatString, returnDateFormatString); 245 }); 246 247 $.tools.validator.fn(':input[type="text"].date', function (el, value) { 248 var dateFormatString = el.attr("data-date-format") ? el.attr("data-date-format") : 'mm/dd/yy'; 249 250 return checkDate(el, value, dateFormatString, dateFormatString); 251 }); 252 253 $.tools.validator.fn(':email', function (el, value) { 254 if (!el.hasClass('emailconfirm')) { 255 return !value || emailRe.test(value); 256 } 257 return el.closest('.email').prevAll('.email:first').find(':input').val() === value; 258 }); 259 260 // Custom validation logic for checkbox groups 261 // on any checkbox's validation, ensure at least one is checked 262 $.tools.validator.fn('span.group-required input[type="checkbox"]', function (el, value) { 263 return $('span.group-required input[type="checkbox"]:checked').length > 0 ? true : DDC.i18n.getLabel("PLEASE_SELECT_ONE_OR_MORE"); 264 }); 265 } 266 267 if ($.tools && $.tools.validator && $.tools.validator.addEffect) { 268 var validationMessage = DDC.i18n.getLabel('YOU_HAVE_XX_INVALID_ENTRIES_IN_THE_FORM') || ''; 269 270 // Add display-under effect
271 $.tools.validator.addEffect("append-under", function (errors, event) { 272 // The .validation-errors element gets updated with a message 273 // containing the number of invalid entries found. This is 274 // currently only setup on viewmodel forms, but could be 275 // extended to other instances. 276 var conf = this.getConf(), 277 $form = this.getForm(), 278 $validationErrors = $form.find('.validation-errors'), 279 errorCount = $form.data('error-count') || 0; 280 281 if ($validationErrors.size() && errorCount !== errors.length) { 282 $form.data('error-count', errors.length); 283 $validationErrors.html("<p role='alert'>"+ validationMessage.replace('XX', errors.length) +"</p>"); 284 } 285 286 // Update the invalid inputs display 287 $.each(errors, function (index, error) { 288 var input = error.input.addClass(conf.errorClass), 289 msg = input.data('msg.el'); 290 291 $(input).attr('aria-invalid', true); 292 293 if (!msg) { 294 msg = conf.message.clone(); 295 296 if (formsV2 && DDC.mobile && input.is('select')) { 297 input.closest('.select-wrap').after(msg); 298 } else if (formsV2 ) { 299 input.after(msg); 300 } else { 301 msg.appendTo(input.closest('label')); 302 } 303 304 if(input.attr('type') == 'checkbox'){ 305 input.closest('label').addClass("alert alert-danger pl-6"); 306 } 307 308 input.data('msg.el', msg); 309 } 310 311 msg.addClass('hide').children().remove(); 312 $.each(error.messages, function (i, v) { 313 conf.messageItem.clone().append(v).appendTo(msg); 314 }); 315 msg.removeClass('hide'); 316 }); 317 }, function (inputs) { 318 // Hide the invalid message after resolution 319 var conf = this.getConf(), 320 $form = this.getForm(), 321 $validationErrors = $form.find('.validation-errors'), 322 errorCount = $form.data('error-count') || 0; 323 324 if (inputs && inputs.size()) { 325 inputs.removeClass(conf.errorClass).each(function () { 326 $(this).attr('aria-invalid', false); 327 328 // Update form error-count data attribute and .validation-errors html 329 if ($validationErrors.size() && errorCount > 0) { 330 $form.data('error-count', errorCount - 1); 331 var replacement = $form.data('error-count') === 0 ? DDC.i18n.getLabel('NO').toLowerCase() : $form.data('error-count'); 332 $validationErrors.html("<p role='alert'>"+ validationMessage.replace('XX', replacement) +"</p>"); 333 } 334 335 if ($(this).data('msg.el')) { 336 $(this).data('msg.el').addClass('hide'); 337 if($(this).attr('type') == 'checkbox'){ 338 $(this).closest('label').removeClass("alert alert-danger pl-6") 339 } 340 } 341 }); 342 } 343 }); 344 } 345
346 $(DDC.modules.validator.selector).each(function (i) { 347 var $form = $(this); 348 349 if (!$form.data('validator-initialized')) { 350 emailRequired = false; 351 phoneRequired = false; 352 emailField = $form.find('[name$="email"]'); 353 phoneField = $form.find('[name$="phone"]'); 354 formId = $form.find('[name$="formId"]'); 355 356 if (emailField && emailField.attr('required') === 'required') { 357 emailRequired = true; 358 } 359 360 if (phoneField && phoneField.attr('required') === 'required') { 361 phoneRequired = true; 362 } 363 364 // DE736383 / US1550942: 'change' fires on blur, which happens during a 365 // submit-button mousedown. The validator's effect[1] then hides the error 366 // message synchronously between mousedown and mouseup, collapsing the form 367 // and pulling the submit button out from under the cursor â so the first 368 // click silently misses the button and a second click is needed. Using 369 // 'input' clears errors as the user types instead, well before the click. 370 $form.data('validator-initialized', true) 371 .attr('data-email-required', emailRequired) 372 .attr('data-phone-required', phoneRequired) 373 .attr('novalidate','novalidate').validator({ 374 errorInputEvent: 'input', 375 effect: 'append-under', 376 message: $('<strong class="errors"/>'), 377 messageItem: $('.templates .error:eq(0)', $form).size() ? $('.templates .error:eq(0)', $form) : $('<small/>'), 378 onFail: function () { 379 //unmark all checkboxes that are claiming to be invalid, all the times 380 var form = this.getForm(); 381 if (shakeTimeout) clearTimeout(shakeTimeout); 382 shakeTimeout = setTimeout(function () { 383 $(form).closest('.ui-dialog').effect('shake', 40, function () { 384 // autofocus the first invalid field 385 $('.invalid:first', this).focus(); 386 }); 387 }, 50); 388 } 389 }); 390 391 // Hide all *NotRequired checkbox fields by default on page load 392 $form.find('input[data-id*="NotRequired"]').each(function() { 393 var $field = $(this); 394 var $formGroup = $field.closest('.form-group'); 395 if (!$formGroup.hasClass('hide')) { 396 $formGroup.addClass('hide'); 397 } 398 }); 399 400 // special handling for .preferred-contact field 401 $form.find('.preferred-contact[required]').on('change', function (e) { 402 contactMeByHandler($(e.target)); 403 var isTextValue = $(e.target).val().toLowerCase() === 'text' || $(e.target).val().toLowerCase() === 'phone'; 404 acceptTextDisclaimerHandler($(e.target), isTextValue, 'tcpaCheckbox'); 405 406 // Handle all *NotRequired fields - show them when phone/text is selected 407 $form.find('input[data-id*="NotRequired"]').each(function() { 408 var fieldId = $(this).attr('data-id'); 409 // Show the field when phone/text is selected, hide otherwise 410 acceptTextDisclaimerHandler($(e.target), isTextValue, fieldId); 411 }); 412 }).each(function () { 413 // make sure states are properly set 414 if ($(this).val()) { 415 contactMeByHandler($(this)); 416 var isTextValue = $(this).val().toLowerCase() === 'text' || $(this).val().toLowerCase() === 'phone'; 417 acceptTextDisclaimerHandler($(this), isTextValue, 'tcpaCheckbox'); 418 419 // Handle all *NotRequired fields - show them when phone/text is selected 420 $form.find('input[data-id*="NotRequired"]').each(function() { 421 var fieldId = $(this).attr('data-id'); 422 // Show the field when phone/text is selected, hide otherwise 423 acceptTextDisclaimerHandler($(this), isTextValue, fieldId); 424 }); 425 } 426 }); 427 428 // special handing for type="tel" fields 429 $form.find('input[type="tel"]').on('change', function (e) { 430 var $input = $(e.target); 431 $input.val($input.val().replace(/[^0-9]/g, '')); 432 }); 433 434 const ccpaCheckbox = $form.find('[data-id=ccpaCheckbox]'); 435 const ccpaConsentVerbiage = ccpaCheckbox.parent().text().trim().replaceAll('/', ' '); 436 $form.find('[data-id=ccpaConsentVerbiage]').val(ccpaConsentVerbiage); 437 438 const tcpaCheckbox = $form.find('[data-id=tcpaCheckbox]'); 439 $form.find('[data-id=tcpaConsentVerbiage]').val(tcpaCheckbox.parent().text().trim().replaceAll('/', ' ')); 440 441 const tcpaCheckboxNotRequired = $form.find('[data-id=tcpaCheckboxNotRequired]'); 442 $form.find('[data-id=tcpaConsentVerbiageNotRequired]').val(tcpaCheckboxNotRequired.parent().text().trim().replaceAll('/', ' ')); 443 444 // Initialize consent values on page load to handle browser back button 445 // This ensures hidden fields match checkbox states after browser navigation 446 consentCheckboxHandler(ccpaCheckbox, $form.find('[data-id=ccpaConsent]')); 447 consentCheckboxHandler(tcpaCheckbox, $form.find('[data-id=tcpaConsent]')); 448 449 // Handle all *NotRequired consent fields generically 450 $form.find('input[data-id*="NotRequired"]').each(function() { 451 var $checkbox = $(this); 452 var fieldId = $checkbox.attr('data-id'); 453 var consentFieldId = fieldId.replace('Checkbox', 'Consent'); 454 var $consentField = $form.find('[data-id=' + consentFieldId + ']'); 455 456 if ($consentField.length > 0) { 457 consentCheckboxHandler($checkbox, $consentField); 458 } 459 }); 460 461 $form.find('[data-id=ccpaCheckbox]').on('change', function(e) { 462 consentCheckboxHandler($(e.target), $form.find('[data-id=ccpaConsent]')); 463 }); 464 465 $form.find('[data-id=tcpaCheckbox]').on('change', function(e) { 466 consentCheckboxHandler($(e.target), $form.find('[data-id=tcpaConsent]')); 467 }); 468 469 $form.find('[data-id=tcpaCheckboxNotRequired]').on('change', function(e) { 470 consentCheckboxHandler($(e.target), $form.find('[data-id=tcpaConsentNotRequired]')); 471 }); $form.find('[name*="phone"]').on('change', function(e) { 472 var showTCPA = $(e.target).val().trim() === '' ? false : true; 473 acceptTextDisclaimerHandler($(e.target), showTCPA, 'tcpaCheckbox'); 474 475 // Handle all *NotRequired fields - show them when phone number is entered 476 $form.find('input[data-id*="NotRequired"]').each(function() { 477 var fieldId = $(this).attr('data-id'); 478 // Show the field when phone is entered, hide otherwise 479 acceptTextDisclaimerHandler($(e.target), showTCPA, fieldId); 480 }); 481 }); 482
483 // Add extra fields to form on submit if the lmc cookie is set 484 // or if the additionalFormFields array contains entries. 485 $form.submit(function(e) { 486 487 // Don't append additional fields to "SMS Share" and "Send to Friend" type forms 488 // as these can contain information specific to the Dealer which should not be 489 // externally visible. 490 if (formId && formId.val() && (formId.val().toLowerCase().indexOf('share') > -1 || formId.val().toLowerCase().indexOf('email') > -1)) { 491 return true; 492 } 493 494 // Add additional custom form fields if they exist. 495 if (DDC.additionalFormFields !== undefined && DDC.additionalFormFields.length > 0) { 496 for (var i = DDC.additionalFormFields.length - 1; i >= 0; i--) { 497 var newField = DDC.additionalFormFields[i]; 498 499 if (!newField.type || !newField.name || !newField.value) { 500 continue; 501 } 502 503 var source = DDC.additionalFormFields[i].source || 'thirdparty'; 504 var fieldTab = DDC.additionalFormFields[i].tabIndex || '999'; 505 var formattedName = newField.name; 506 var formattedValue = newField.value; 507 508 if (newField.type === 'custom') { 509 formattedName = '_custom=' + source + '=' + fieldTab + '=' + newField.name; 510 formattedValue = newField.name + ': ' + newField.value; 511 } 512 513 if (!$(this).find('input[name="' + formattedName + '"]').length) { 514 var additionalElement = $( '<input>' ) 515 .attr('type', 'hidden') 516 .attr('class', 'hidden') 517 .attr('name', formattedName) 518 .attr('value', formattedValue) 519 .attr('data-id', ''); 520 521 $(this).append(additionalElement); 522 } 523 } 524 } 525 return true; 526 }); 527 528 // If a widget needs to know when its specific validator is ready (as opposed to 'validatorReady' which will only happen once per page) 529 $.publish('validatorInitialized', [$form]); 530 } 531 }); 532 }; 533}());
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.