PageSourceSearch

https://www.earthmotorcars.com/v9/media/js/ddc/v1/modules/validator.js

js earthmotorcars.com collected 2026-09-24 12:20:07 UTC 20,856 bytes, 533 lines download raw bytes

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.