PageSourceSearch

https://www.autofinancellc.com/ScriptLib/CX5scripts.js?v=2026.09.30.01

js autofinancellc.com collected 2026-10-07 04:00:37 UTC 59,319 bytes, 1,813 lines download raw bytes

1/* global pmxwebsessionid */
2
3var on_inventory_search_page = false;
4
5function errorlog(variable, name) {
6  if (console) {
7    console.log(name + ':');
8    console.log(variable);
9  }
10}
11
12/**
13 * Function to setup delegates and ensure it does not get duplicated
14 */
15function onOnce(triggerName, fn) {
16  function handlerExists(triggerName, theHandler) {
17    function getFunctionName(fn) {
18      var rgx = /^function\s+([^\(\s]+)/;
19      var matches = rgx.exec(fn.toString());
20      return matches ? matches[1] : '(anonymous)';
21    }
22    exists = false;
23    var handlerName = getFunctionName(theHandler);
24    if ($(document).data('events') !== undefined) {
25      var event = $(document).data('events')[triggerName];
26      if (event !== undefined) {
27        $.each(event, function (i, handler) {
28          if (getFunctionName(handler) == handlerName) {
29            exists = true;
30          }
31        });
32      }
33    }
34    return exists;
35  }
36  if (!handlerExists(triggerName, fn)) {
37    $(document).bind(triggerName, fn);
38  }
39}
40
41/**
42 * function to empty an array
43 */
44function clearArray(arrayObj) {
45  arrayObj.splice(0, arrayObj.length);
46}
47
48/**
49 * function to check array for a key with wildcard
50 */
51function checkArrayForKeyBeginning(cArray, cKey) {
52  //	var isMatch = false;
53  for (aKey in cArray) {
54    if (cKey == aKey.substr(0, cKey.length)) {
55      return true;
56    }
57  }
58  return false;
59}
60
61// this is used to find the differences between 2 hashed arrays
62// ****************************************
63function hashDiff(hashedArray1, hashedArray2) {
64  // ****************************************
65  var dSerialized = {};
66  var nDiff = 0;
67  for (hKey in hashedArray2) {
68    if (hashedArray1[hKey] !== hashedArray2[hKey]) {
69      dSerialized[hKey] = hashedArray2[hKey];
70      nDiff++;
71    }
72  }
73  var diffReturn = new Array();
74  diffReturn['count'] = nDiff;
75  diffReturn['serialized'] = dSerialized;
76  return diffReturn;
77}
78
79// can be used with any serialized array to convert it to a hashed array
80// ****************************************
81function convertSerializedArrayToHash(sArray) {
82  // ****************************************
83  var hArray = {};
84  for (var i = 0; i < sArray.length; i++) {
85    hArray[sArray[i].name] = sArray[i].value;
86  }
87  return hArray;
88}
89
90function initializeTooltips() {
91  $(document).tooltip({
92    items: '[title]:not(iframe)',
93    position: { my: 'left top+15', at: 'left bottom', collission: 'flipfit' },
94  });
95  $(document).on('click.tooltip', '*', function () {
96    $('.ui-tooltip').remove();
97  });
98}
99
100/**
101 * Function to intialize date and time pickers. Default settings only.
102 * @param {Object} selector
103 * @version 2017.07.18 jln Ticket 389343, block days of week if selected on anyDatepicker
104 * @version 2019.08.12 sb Ticket #547019 - Add option to CTA forms (schedule appointment) to disable specific dates/times
105 * @version 2019.08.28 dcs Ticket #557045 - fixed time display format
106 * @version 2019.11.21 sb Ticket #574713, CE5 - Replace JQueryUI Datepickers
107 */
108function initializePickers(selector) {
109  selector = optionalArg(selector, 'body');
110  $(selector + ' .timePicker').timepicker({
111    minTime: '8:00am', // Using string. Can take string or Date object.
112    maxTime: '8:00pm',
113    interval: 15,
114    dynamic: false,
115    timeFormat: 'h:mm p',
116    zindex: 1100,
117  });
118  var lang = false !== cxDoTranslation ? cxDoTranslation : '';
119  if ('undefined' != typeof $.datepicker) {
120    $.datepicker.regional[''].dateFormat = 'mm/dd/yy';
121    $.datepicker.setDefaults($.datepicker.regional[lang]);
122  }
123  //(.datePicker init removed - AppointmentDate is a native input[type=date] now
124  // and no markup emits that class; SC_main.js has the config side's own init)
125  $(selector + ' .pastDatepicker').datepicker({
126    autoSize: true,
127    maxDate: '+0',
128    changeMonth: true,
129    changeYear: true,
130    yearRange: '1900:+0',
131    defaultDate: -7669,
132  });
133
134  //custom date fields (.anyDatepicker) moved to native input[type=date] with
135  //data-disabledays validated in LeadFormObj.js - no jQuery UI init needed
136  $(selector + ' .datepicker,' + selector + ' .pastDatepicker').blur(
137    function () {
138      checkValidDate($(this));
139    },
140  );
141  $('.datepicker-range', selector).each(function () {
142    // date range picker
143    var $from = $('input.datepicker-range-from', this);
144    var $to = $('input.datepicker-range-to', this);
145    if ('date' === $from.attr('type') && 'date' === $to.attr('type')) {
146      //native date pair: keep the range linked by mirroring each value into
147      //the sibling's min/max attribute (Y-m-d strings, no Date parsing);
148      //an optional .datepicker-range-minimum hidden input supplies the floor
149      var range_min = $('input.datepicker-range-minimum', this).val();
150      var range_max = new Date().toISOString().slice(0, 10);
151      if (range_min) {
152        $from.attr('min', range_min);
153        $to.attr('min', range_min);
154      }
155      $from.attr('max', range_max);
156      $to.attr('max', range_max);
157      $from.on('change', function () {
158        if ($(this).val()) {
159          $to.attr('min', $(this).val());
160        }
161      });
162      $to.on('change', function () {
163        if ($(this).val()) {
164          $from.attr('max', $(this).val());
165        }
166      });
167      $from.trigger('change');
168      $to.trigger('change');
169      return;
170    }
171    var sFromId = $from.attr('id');
172    var sToId = $to.attr('id');
173    /**
174     * custom
175     */
176    var datepickerOptions = {
177      changeMonth: true,
178      changeYear: true,
179      dateFormat: 'yy/mm/dd',
180      showOtherMonths: true,
181      selectOtherMonths: true,
182      autoSize: true,
183      showAnim: 'fold',
184      maxDate: 'now',
185      beforeShow: function (fInput, fInst) {
186        /**
187         * if in from box, use the to value or now for maxDate
188         * if in to box, use the from value for minDate
189         */
190        if (sFromId == fInput.id) {
191          var option = 'maxDate';
192          var otherId = sToId;
193        } else {
194          var option = 'minDate';
195          var otherId = sFromId;
196        }
197        var instance = $('#' + otherId).data('datepicker');
198        var otherValue = $('#' + otherId).val();
199        if (0 < otherValue.length) {
200          var date = $.datepicker.parseDate(
201            instance.settings.dateFormat || $.datepicker._defaults.dateFormat,
202            otherValue,
203            instance.settings,
204          );
205          $(this).datepicker('option', option, date);
206        }
207      },
208    };
209    if (undefined != $('input.datepicker-range-minimum', this).val()) {
210      datepickerOptions.minDate = $(
211        'input.datepicker-range-minimum',
212        this,
213      ).val();
214    }
215    $from.datepicker(datepickerOptions);
216    $to.datepicker(datepickerOptions);
217  });
218}
219
220/**
221 * check if date format is valid for input
222 * @param {object} $dateobject
223 * @version 2020.01.08 sb Ticket #582804 - validate dates for bootstrap as well as jquery
224 */
225function checkValidDate($dateobject) {
226  $dateobject.removeClass('ui-state-error');
227  var is_valid = true;
228
229  if (window.jquerybootstrap) {
230    if ('' != $dateobject.val()) {
231      var date_regex =
232        /^(0[1-9]|1[0-2])\/(0[1-9]|1\d|2\d|3[01])\/(19|20)\d{2}$/;
233      if (!date_regex.test($dateobject.val())) {
234        is_valid = false;
235      }
236    }
237  } else {
238    try {
239      format = $dateobject.datepicker('option', 'dateFormat');
240      jQuery.datepicker.parseDate(format, $dateobject.val(), null);
241    } catch (error) {
242      is_valid = false;
243    }
244  }
245
246  if (false === is_valid) {
247    $dateobject.addClass('ui-state-error');
248    $dateobject.val('');
249    cxAlert(
250      'Please enter a valid date in MM/DD/YYYY format, or select the date from the calendar.',
251    );
252    setTimeout(function () {
253      $dateobject.removeClass('ui-state-error');
254    }, 1000);
255  }
256}
257
258/**
259 * initialize the contact us form
260 * @param {String} ajaxurl
261 * @version 2019.02.12 dcs Ticket #517112 - jQuery delegate is deprecated
262 */
263function initializeContactUsOC(ajaxurl) {
264  $(document)
265    .on('click', '.contactLink', function () {
266      event.preventDefault();
267      displayContactUsOC(false, false, ajaxurl);
268    })
269    .on('mouseenter', '.contactLink', function () {
270      $(this).addClass('linkhover');
271    })
272    .on('mouseout', '.contactLink', function () {
273      $(this).removeClass('linkhover');
274    });
275}
276
277/**
278 * contact us form: DMS only
279 *
280 * @param {String} subject
281 * @param {Object} fields
282 * @param {String} ajaxurl
283 * @version 2017.03.13.01 Ticket 348785 LB - Unify lead form validation and submission
284 * @version 2020.08.07 sb Ticket #625333 - Add double-submit "isValidated" check to AE and Contact Us
285 */
286function displayContactUsOC(subject, fields, ajaxurl) {
287  if (!ajaxurl) {
288    ajaxurl = 'index.php';
289  }
290  $(document.body).append('<div id="dmsContactUs"></div>');
291  //'null' is the server-side sentinel for "no fixed subject" - it renders an
292  //editable subject input instead of a disabled one
293  if (!subject) {
294    subject = 'null';
295  }
296  if (!fields) {
297    fields = {
298      fullName: {
299        label: 'Name',
300        required: true,
301      },
302      email: {
303        label: 'Email',
304        required: true,
305      },
306      workPhone: {
307        label: 'Work Phone',
308        required: true,
309      },
310      mobilePhone: {
311        label: 'Cell Phone',
312      },
313      city: {
314        label: 'City',
315      },
316      state: {
317        label: 'State',
318      },
319    };
320  }
321  $.ajax({
322    url: ajaxurl,
323    type: 'POST',
324    data: {
325      fromAjax: 'y',
326      doWhat: 'showContactUs',
327      sid: Math.random(),
328      askForAppointment: false,
329      fields: fields,
330      subject: subject,
331    },
332    success: function (thisData) {
333      $('#dmsContactUs').html(thisData);
334      initializeForm('#dmsContactUs');
335      LeadFormObj.init();
336      $('#CU_email').blur(function () {
337        var e = $(this).val();
338        if ('' !== e) {
339          if (!isValidEmail(e, ajaxurl)) {
340            cxAlert(
341              unsupportedEmailCharacterMessage(e) ||
342                'There is an error with your e-mail address. Please re-enter or remove.',
343            );
344            $(this).focus();
345          }
346        }
347      });
348
349      $('#dmsContactUs').dialog({
350        resizable: false,
351        width: 500,
352        zIndex: 1005,
353        title: 'Contact Us',
354        buttons: {
355          Send: function () {
356            $('#frmContactUsDMS input[name=Subject]').prop('disabled', false);
357            var validated = true;
358            //Check for any required fields on the form and ensure they have values entered
359            $('#frmContactUsDMS [required="required"]').each(function () {
360              $(this).removeClass('ui-state-error');
361              if ('' === $(this).val()) {
362                $(this).addClass('ui-state-error');
363                validated = false;
364              }
365            });
366            if (validated !== true) {
367              $('#divContactUsMessage')
368                .text(
369                  'There is required information missing. Please complete the highlighted fields above.',
370                )
371                .addClass('ui-state-highlight');
372              setTimeout(function () {
373                $('#divContactUsMessage').removeClass('ui-state-highlight');
374              }, 1500);
375            } else {
376              //with all information set, check again for valid email:
377              var e = $('#CU_email').val();
378              if ('' !== e) {
379                if (!isValidEmail(e, ajaxurl)) {
380                  $('#divContactUsMessage')
381                    .text(
382                      unsupportedEmailCharacterMessage(e) ||
383                        'The email address entered appears invalid. Please re-enter or remove.',
384                    )
385                    .addClass('ui-state-highlight');
386                  $('#CU_email').addClass('ui-state-error');
387                  setTimeout(function () {
388                    $('#divContactUsMessage').removeClass('ui-state-highlight');
389                  }, 1500);
390                  return;
391                }
392              }
393
394              $('#frmContactUsDMS input[name=isValidated]').val(
395                getAjaxReturn('storeSessonValidation', '', ajaxurl),
396              );
397
398              // ***** send the data *****
399              // save the form
400              var formData = $('#frmContactUsDMS').serializeArray();
401              formData = convertSerializedArrayToHash(formData);
402              formData['fromAjax'] = 'y';
403              formData['doWhat'] = 'saveContactUs';
404              formData['sid'] = Math.random();
405              $.ajax({
406                url: ajaxurl,
407                type: 'POST',
408                beforeSend: function () {
409                  formSubmitOverlay('show', 'Sending. Please wait');
410                },
411                data: formData,
412                dataType: 'json',
413                success: function (data) {
414                  formSubmitOverlay('hide');
415                  data.message = cxTranslateString(data.message);
416                  $('#divContactUsMessage').text(data.message);
417                  setTimeout(function () {
418                    $('#dmsContactUs').dialog('close');
419                  }, 1500);
420                },
421                error: function (error) {
422                  cxAlert(
423                    'There was an error sending this email:\r\n' +
424                      error.testStatus,
425                  );
426                  formSubmitOverlay('hide');
427                  $('#dmsContactUs').dialog('close');
428                },
429              });
430            }
431          },
432          Cancel: function () {
433            $('#dmsContactUs').dialog('close');
434          },
435        },
436        close: function () {
437          $('#dmsContactUs').html('');
438          $('#dmsContactUs').dialog('destroy');
439          $('#dmsContactUs').remove();
440        },
441      });
442    },
443  });
444}
445
446/**
447 * Mint a single-use nonce to carry the session across one navigation.
448 *
449 * The session id must never go in a url - it is a bearer credential, and a url is kept in the
450 * address bar, history, bookmarks, the Referer of every outbound link and subresource, and the
451 * access log. The nonce is spent by the request that redeems it and expires in
452 * CookieManager::HANDOFF_NONCE_SECONDS, so a copy recovered from any of those is worthless.
453 *
454 * Synchronous on purpose: the caller is about to navigate, and matches cxGetCookie() below.
455 * The session id rides along in the POST body via the $.ajaxSetup() in functions.php.
456 *
457 * @returns {String} the nonce, or '' if one could not be minted
458 * @version 2026.09.23 dcs https://trello.com/c/nmX3NpTS/ - Initial Release
459 */
460function cxGetSessionHandoff() {
461  if ('undefined' === typeof pmxwebsessionid) {
462    return '';
463  }
464  var nonce = '';
465  $.ajax({
466    url: 'index.php',
467    async: false,
468    type: 'POST',
469    // async:false blocks the UI thread, so this MUST be bounded. The analytics tracker
470    // calls this at document-ready, where an unbounded wait would stall first render.
471    timeout: 4000,
472    data: {
473      doWhat: 'cxGetSessionHandoff',
474      sid: Math.random(),
475    },
476    success: function (data) {
477      nonce = $.trim(String(data || ''));
478    },
479    error: function () {
480      // navigate without the session rather than not at all: the visitor gets a clean new
481      // session on the far side, which is the same graceful restart an expired one produces
482      nonce = '';
483    },
484  });
485  return nonce;
486}
487
488/**
489 * get a cookie value
490 *
491 * @param {String} cName
492 * @returns {data|String}
493 */
494function cxGetCookie(cName) {
495  if (document.cookie && document.cookie.length > 0) {
496    var cStart = document.cookie.indexOf(cName + '=');
497    if (cStart !== -1) {
498      cStart = cStart + cName.length + 1;
499      var cEnd = document.cookie.indexOf(';', cStart);
500      if (cEnd === -1) {
501        cEnd = document.cookie.length;
502      }
503      return unescape(document.cookie.substring(cStart, cEnd));
504    }
505  }
506  //@version 2019.07.23 sb Ticket #549988, CE5 - Custom Code CTA forms to avoid cookie related issues
507  else if ('undefined' !== typeof pmxwebsessionid) {
508    var return_val = '';
509    $.ajax({
510      url: 'index.php',
511      async: false,
512      type: 'POST',
513      data: {
514        doWhat: 'cxGetWebsessionCookie',
515        sid: Math.random(),
516        cookiename: cName,
517      },
518      success: function (data) {
519        return_val = data;
520      },
521    });
522    return return_val;
523  } else {
524    return '';
525  }
526}
527
528/**
529 * function cxSetCookie(cName,cValue,expiredays)
530 *
531 * sets a cookie with the specified name value pair
532 *
533 * @param {string} cookie_name
534 * @param {string} cookie_value
535 * @param {int} expire_days
536 *
537 * @author Dan Stotmeister
538 * @version 20151208 nna 263237 added path based on the page cookie path variable. updated to standards.
539 * @version 2020.02.25 dcs Ticket #592901 - use secure flag for cookies in non-dev environment
540 * @version 2021.03.09 dcs Ticket #662317 - changed parameters for cookies
541 */
542function cxSetCookie(cookie_name, cookie_value, expire_days) {
543  var name = cookie_name + '=' + escape(cookie_value);
544  var expire = '';
545  var path = '; path=/';
546  var secure = '';
547  if ($('meta[name=cx5_cookie]').length) {
548    path = '; path=' + $('meta[name=cx5_cookie]').attr('content');
549  }
550  if (location.protocol === 'https:') {
551    secure = ';secure';
552  }
553  if (expire_days !== null) {
554    var expire_date = new Date();
555    expire_date.setDate(expire_date.getDate() + expire_days);
556    expire = '; expires=' + expire_date.toGMTString();
557  }
558  document.cookie = name + expire + path + secure + '; samesite=lax';
559  if (
560    (!document.cookie || document.cookie.length < 1) &&
561    'undefined' !== typeof pmxwebsessionid
562  ) {
563    $.ajax({
564      url: 'index.php',
565      async: false,
566      type: 'POST',
567      data: {
568        doWhat: 'cxSetWebsessionCookie',
569        sid: Math.random(),
570        cookiename: cookie_name,
571        cookieval: cookie_value,
572      },
573    });
574  }
575}
576
577/**
578 * check if the "check" cookie is set. if not, show a warning
579 * @param {String} cookie_name
580 * @param {Object} config (json)
581 * @returns {Boolean}
582 * @version 2019.05.15 dcs Ticket #529406 - added check for cookies being set for iframed type sites
583 * @version 2019.06.21 dcs Ticket #544332 - adjustments on cookie warning
584 */
585function cxCookieCheck(cookie_name, config) {
586  var cookie_check = cxGetCookie(cookie_name);
587  if ('cookie_set' === cookie_check) {
588    return true;
589  }
590  var cookie_warning = '';
591  var parent_class = '';
592  if (isInIframe()) {
593    cookie_warning =
594      '<div id="ce-cookies-warning-highlight">Oops!...</div>' +
595      '<div id="ce-cookies-warning-content">' +
596      '<span id="ce-cookies-warning-message">Our application detected that your browser settings do not have cookies enabled (which are required to securely use this ' +
597      ('boolean' === typeof config.has_form && true === config.has_form
598        ? 'form'
599        : 'application') +
600      ').</span>' +
601      '<span id="ce-cookies-warning-instructions-pre"> Simply </span>' +
602      '<span id="ce-cookies-warning-instructions-link"><a href="' +
603      window.location.href +
604      '" target="_blank">CLICK HERE</a></span>' +
605      ('string' === typeof config.button_text && 0 < config.button_text.length
606        ? '<span id="ce-cookies-warning-instructions-button">, or click the button below</span>'
607        : '') +
608      '<span id="ce-cookies-warning-instructions-post"> to link directly out to the secure application</span>' +
609      ('string' === typeof config.dealer_name && 0 < config.dealer_name.length
610        ? '<span id="ce-cookies-warning-dealer-name"> for ' +
611          config.dealer_name +
612          '</span>'
613        : '') +
614      '</div>' +
615      ('string' === typeof config.button_text && 0 < config.button_text.length
616        ? '<div id="ce-cookies-warning-button-wrapper"><a href="' +
617          window.location.href +
618          '" target="_blank"><div id="ce-cookies-warning-button">' +
619          config.button_text +
620          '</div></a></div>'
621        : '');
622    parent_class = 'third-party';
623  } else {
624    cookie_warning =
625      '<div id="ce-cookies-warning-highlight">Oops!...</div>' +
626      '<div id="ce-cookies-warning-content">' +
627      '<span id="ce-cookies-warning-message">Our application detected that your browser settings do not have any cookies enabled (which are required to securely use this ' +
628      ('boolean' === typeof config.has_form && true === config.has_form
629        ? 'form'
630        : 'application') +
631      ').</span>' +
632      '<span id="ce-cookies-warning-instructions-pre"> In order to proceed with the secure application</span>' +
633      ('string' === typeof config.dealer_name && 0 < config.dealer_name.length
634        ? '<span id="ce-cookies-warning-dealer-name"> for ' +
635          config.dealer_name +
636          '</span>'
637        : '') +
638      '<span id="ce-cookies-warning-instructions-post">, please enable cookies on your browser and refresh this page.</span>' +
639      '</div>';
640    parent_class = 'first-party';
641  }
642  $('body').prepend(
643    '<div id="ce-coookies-warning" class="ce-cookies-warning-' +
644      parent_class +
645      '">' +
646      cookie_warning +
647      '</div>',
648  );
649  $('input, select, button, div.button', 'form').prop('disabled', true);
650  return false;
651}
652
653/**
654 * chekck if the page is loaded in an iframe
655 * @returns {Boolean}
656 */
657function isInIframe() {
658  try {
659    return window.self !== window.top;
660  } catch (e) {
661    return true;
662  }
663}
664
665/**
666 * get the x-position for the center of the current viewing window
667 */
668function getCenterX(boxWidth) {
669  if (!boxWidth) {
670    var boxWidth = 0;
671  }
672  var window_center = Math.floor(($(window).width() - boxWidth) / 2);
673  return window_center;
674}
675
676/**
677 * get the y-position for the center of the current viewing window
678 */
679function getCenterY() {
680  var window_center = Math.floor(
681    $(window).scrollTop() + $(window).height() / 2,
682  );
683  return window_center;
684}
685
686/**
687 * does a comparison to undefined and returns a specified default value - used for creating optional arguments in JS functions
688 */
689function optionalArg(arg, def) {
690  return typeof arg == 'undefined' ? def : arg;
691}
692
693/**
694 * math function for getting greatest common divisor GCD
695 */
696function mathGCD(a, b) {
697  a = Math.abs(a);
698  b = Math.abs(b);
699  if (a < b) {
700    var na = b;
701    b = a;
702    a = na;
703  }
704  if (b == 0) {
705    return a;
706  }
707  var r = a % b;
708  while (r > 0) {
709    a = b;
710    b = r;
711    r = a % b;
712  }
713  return b;
714}
715
716/**
717 * math function for simplifying fractions
718 */
719function mathSimplify(num, den) {
720  g = mathGCD(num, den);
721  return new Array(num / g, den / g);
722}
723
724/**
725 * Switch the enable status of the selector based on its status (selection)
726 */
727function switchEnable(selector, selection) {
728  if (selection) {
729    $(selector).prop('checked', false);
730    $(selector).prop('disabled', true);
731  } else {
732    $(selector).prop('disabled', false);
733  }
734}
735
736/**
737 * capitalize the first character of a string
738 * @param {String} str
739 * @returns {String}
740 */
741function ucfirst(str) {
742  str += '';
743  var uf = str.charAt(0).toUpperCase();
744  return uf + str.substr(1);
745}
746
747/**
748 * global variable tracking whether or not a translation should be done and the language code
749 * @type Boolean|String
750 */
751var cxDoTranslation = false;
752
753/**
754 * translate a string
755 * @param {String} str
756 * @returns {String|Boolean}
757 * @version 2021.11.11 sb Ticket #697456, CE5 Appraisal Express sites on beta are not doing a vin explosion
758 */
759function cxTranslateString(str) {
760  if (false !== cxDoTranslation && 0 < str.length) {
761    var trn = cxTranslate(str);
762    if (false !== trn) {
763      return trn;
764    } else {
765      return str;
766    }
767  } else {
768    return str;
769  }
770}
771
772/**
773 * display a string in a dialog
774 * @param {String} str
775 * @version 2021.11.11 sb Ticket #697456, #698199, CE5 Appraisal Express sites on beta are not doing a vin explosion
776 */
777function cxAlert(str) {
778  if (false != cxDoTranslation && 0 < str.length) {
779    var trn = cxTranslate(str);
780    if (false != trn) {
781      alert(trn);
782    } else {
783      alert(str);
784    }
785  } else {
786    alert(str);
787  }
788}
789
790/**
791 * Display a confirmation dialog with a specified string and a function to callback if they click continue
792 *
793 * @param {string} str
794 * @param {function} continue_callback
795 * @param {function} cancel_callback
796 *
797 * @author Natalie Ash
798 * @version 20180319 nna
799 * @version 20180403 nna 449290 - improve appearance of dialog
800 * @version 2020.04.17 dcs Ticket #603196 - changed order the callbacks are called
801 */
802function cxConfirm(str, continue_callback, cancel_callback) {
803  if (false != cxDoTranslation && 0 < str.length) {
804    var trn = cxTranslate(str);
805    var translated = false != trn ? true : false;
806    str = trn;
807  }
808  $('html').append(
809    '<div id="cx5_simpledialog" class="large hidden margin-10">' +
810      str +
811      '</div>',
812  );
813  $('#cx5_simpledialog').dialog({
814    dialogClass: 'no-title center-button',
815    modal: true,
816    resizable: false,
817    position: { my: 'center', at: 'center', of: window },
818    buttons: [
819      {
820        text: 'Continue',
821        class: 'ui-state-active',
822        click: function () {
823          continue_callback && continue_callback();
824          $('#cx5_simpledialog').dialog('close');
825        },
826      },
827      {
828        text: 'Cancel',
829        click: function () {
830          cancel_callback && cancel_callback();
831          $('#cx5_simpledialog').dialog('close');
832        },
833      },
834    ],
835    close: function () {
836      $('#cx5_simpledialog').html('').dialog('destroy').remove();
837    },
838  });
839}
840
841/**
842 * translate any pre-defined strings
843 * @param {type} str
844 * @returns {String|Boolean}
845 * @version 2019.02.05 dcs Ticket #513284 - added some common translations
846 * @version 2020.02.28 sb Ticket #593533 - Option to use same address for co-applicant
847 * @version 2020.03.11 sb Ticket #595930 - one popup for coapp copy a
847pplicant address prompt
848 */
849function cxTranslate(str) {
850  var trn = false;
851  if ('es' == cxDoTranslation && 0 < str.length) {
852    switch (str) {
853      // save overlays
854      case 'Translating': {
855        trn = 'Traduciendo';
856        break;
857      }
858      case 'Validating': {
859        trn = 'Validando';
860        break;
861      }
862      case 'Sending. Please wait': {
863        trn = 'Env\xEDo. Por favor espere';
864        break;
865      }
866      case 'Updating results, please wait': {
867        trn = 'Actualizando resultados, por favor espere';
868        break;
869      }
870      case 'Searching, please wait': {
871        trn = 'Buscando, por favor espere';
872        break;
873      }
874      case 'Looking up zipcode. Please wait': {
875        trn = 'Buscando el c\xF3ndigo postal. Por favor espera';
876        break;
877      }
878      case 'Next': {
879        trn = 'pr\xF3ximo';
880        break;
881      }
882      case 'Submit': {
883        trn = 'Enviar';
884        break;
885      }
886      case 'Please wait': {
887        trn = 'Por favor espere';
888        break;
889      }
890      case 'Loading, please wait': {
891        trn = 'Cargando, por favor espere';
892        break;
893      }
894      case 'Saving': {
895        trn = 'Guardando';
896        break;
897      }
898      case 'Submitting': {
899        trn = 'de Informaci\xF3n Contribuir';
900        break;
901      }
902      // form validation
903      case 'There is required information missing. Please fill out the indicated fields.': {
904        trn =
905          'Hay informaci\xF3n necesaria que falta. Por favor, rellene los campos indicados.';
906        break;
907      }
908      case 'Date of Birth is invalid. Date must be MM/DD/YYYY format.': {
909        trn =
910          'Fecha de nacimiento no es v\xE1lida. La fecha debe ser MM/DD/AAAA formato.';
911        break;
912      }
913      case 'There is an error with your e-mail address. Please re-enter.': {
914        trn =
915          'Hay un error con su direcci\xF3n de correo electr\xF3nico. Por favor, vuelva a introducir';
916        break;
917      }
918      case 'There is an error with your e-mail address. Please re-enter or remove.': {
919        trn =
920          'Hay un error con su direcci\xF3n de correo electr\xF3nico. Por favor, vuelva a introducir o eliminar.';
921        break;
922      }
923      case 'There is an unsupported character in your e-mail address. Please re-enter your e-mail address without the following character(s):': {
924        trn =
925          'Hay un car\xE1cter no admitido en su direcci\xF3n de correo electr\xF3nico. Por favor, vuelva a introducir su direcci\xF3n de correo electr\xF3nico sin el siguiente car\xE1cter:';
926        break;
927      }
928      case 'Please enter your email address': {
929        trn = 'Por favor, introduzca su direcci\xF3n de correo electr\xF3nico';
930        break;
931      }
932      case 'Please enter your password': {
933        trn = 'Por favor, introduzca su contrase\xF1a';
934        break;
935      }
936      case 'There was an error submitting your information. Please try this again later.':
937      case 'There was an error submitting your information. Please try again later.': {
938        trn =
939          'Hubo un error al enviar su informaci\xF3n. Por favor, int\xE9ntelo de nuevo m\xE1s tarde.';
940        break;
941      }
942      case 'Please enter a complete 5-digit ZIP Code.': {
943        trn =
944          'Por favor, introduzca un c\xF3digo postal completo de 5 d\xEDgitos.';
945        break;
946      }
947      case 'Please enter a valid date in MM/DD/YYYY format, or select the date from the calendar.': {
948        trn =
949          'Por favor, introduzca una fecha v\xE1lida MM/DD/AAAA formato o seleccione la fecha del calendario.';
950        break;
951      }
952      case 'Please answer the verification questions to continue': {
953        trn =
954          'Por favor responda las preguntas de verificaci\xF3n para continuar';
955        break;
956      }
957      case 'Nothing to compare.  Please select at least 2 vehicles.': {
958        trn =
959          'No hay nada que comparar. Por favor, seleccione al menos 2 veh\xEDculos.';
960        break;
961      }
962      case 'Only 3 vehicles allowed for compare.': {
963        trn = 'S\xF3lo 3 veh\xEDculos permitidos para comparar.';
964        break;
965      }
966      case 'Please enter 4 digits': {
967        trn = 'Por favor, introduzca 4 d\xEDgitos';
968        break;
969      }
970      case 'Would you like to use the same address as the applicant?': {
971        trn = 'Desea utilizar la misma direcci\xF3n que el solicitante?';
972        break;
973      }
974      case 'Please Have Your Co-Applicant complete the application. <br><br> Would you like to use the same address as the applicant?': {
975        trn =
976          'Haga que su cosolicitante complete la solicitud. <br><br> ¿Desea usar la misma dirección que el solicitante?';
977        break;
978      }
979    }
980  }
981  return trn;
982}
983
984/**
985 * set the traslation session variable
986 * @param {String} lang
987 * @version 2020.06.25 sb Ticket #627793 - The 'En Espanol' button on IACA via Chrome doesn't work
988 */
989function setTranslateSessionVar(lang) {
990  formSubmitOverlay('show', 'Loading. Please wait');
991  var url = window.location.href;
992  $('body').append(
993    '<form action="' +
994      url +
995      '" name="setTranslateVar" method="post" style="display:none;"><input type="text" name="lang" value="' +
996      lang +
997      '" /></form>',
998  );
999  document.forms['setTranslateVar'].submit();
1000}
1001
1002/**
1003 * change a string to a corresponding boolean value
1004 * @param {String} string
1005 * @returns {Boolean}
1006 */
1007function StringToBool(string) {
1008  string = String(string);
1009  switch (string.toLowerCase().trim()) {
1010    case 'false':
1011    case 'no':
1012    case '0':
1013    case '':
1014      return false;
1015    default:
1016      return true;
1017  }
1018}
1019
1020/**
1021 * Excapes html entities in a string
1022 *
1023 * @param {string} str
1024 * @returns {string}
1025 *
1026 * @version 20160819 nna 316930
1027 */
1028function htmlEscape(str) {
1029  return str
1030    .replace(/&/g, '&amp;')
1031    .replace(/"/g, '&quot;')
1032    .replace(/'/g, '&#39;')
1033    .replace(/</g, '&lt;')
1034    .replace(/>/g, '&gt;')
1035    .replace(/\//g, '&#x2F;');
1036}
1037
1038/**
1039 * decimal_sep: character used as deciaml separtor, it defaults to '.' when omitted
1040 * thousands_sep: char used as thousands separator, it defaults to ',' when omitted
1041 * @version 2019.02.25 dcs Ticket #516446 - code formatting
1042 */
1043$(function () {
1044  Number.prototype.toMoney = function (decimals, decimal_sep, thousands_sep) {
1045    var n = this,
1046      c = isNaN(decimals) ? 2 : Math.abs(decimals), //if decimal is zero we must take it, it means user does not want to show any decimal
1047      d = decimal_sep || '.', //if no decimal separator is passed we use the dot as default decimal separator (we MUST use a decimal separator)
1048      /*
1049	   according to [http://stackoverflow.com/questions/411352/how-best-to-determine-if-an-argument-is-not-sent-to-the-javascript-function]
1050	   the fastest way to check for not defined parameter is to use typeof value === 'undefined' 
1051	   rather than doing value === undefined.
1052	   */
1053      t = typeof thousands_sep === 'undefined' ? ',' : thousands_sep, //if you don't want to use a thousands separator you can pass empty string as thousands_sep value
1054      sign = n < 0 ? '-' : '',
1055      //extracting the absolute value of the integer part of the number and converting to string
1056      i = parseInt((n = Math.abs(n).toFixed(c))) + '',
1057      j = (j = i.length) > 3 ? j % 3 : 0;
1058    return (
1059      sign +
1060      (j ? i.substr(0, j) + t : '') +
1061      i.substr(j).replace(/(\d{3})(?=\d)/g, '$1' + t) +
1062      (c
1063        ? d +
1064          Math.abs(n - i)
1065            .toFixed(c)
1066            .slice(2)
1067        : '')
1068    );
1069  };
1070});
1071
1072/* **
1073 * jquery.alphanumeric.js
1074 * @version 2017.09.20 dcs [Code Review] Allow decimal point for numeric fields
1075 * @version 2019.02.25 dcs Ticket #516446 - code formatting
1076 */
1077(function ($) {
1078  $.fn.alphanumeric = function (p) {
1079    p = $.extend(
1080      {
1081        ichars: '!@#$%^&*()+=[]\\\';,/{}|":<>?~`.- ',
1082        nchars: '',
1083        allow: '',
1084      },
1085      p,
1086    );
1087    return this.each(function () {
1088      if (p.nocaps) {
1089        p.nchars += 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
1090      }
1091      if (p.allcaps) {
1092        p.nchars += 'abcdefghijklmnopqrstuvwxyz';
1093      }
1094      s = p.allow.split('');
1095      for (i = 0; i < s.length; i++) {
1096        if (p.ichars.indexOf(s[i]) != -1) {
1097          s[i] = '\\' + s[i];
1098        }
1099      }
1100      p.allow = s.join('|');
1101      var reg = new RegExp(p.allow, 'gi');
1102      var ch = p.ichars + p.nchars;
1103      ch = ch.replace(reg, '');
1104      $(this).on('keypress', function (e) {
1105        if (!e.charCode) k = String.fromCharCode(e.which);
1106        else k = String.fromCharCode(e.charCode);
1107        if (ch.indexOf(k) != -1) e.preventDefault();
1108        if (e.ctrlKey && k == 'v') e.preventDefault();
1109      });
1110      $(this).bind('contextmenu', function () {
1111        return false;
1112      });
1113    });
1114  };
1115  $.fn.numeric = function (p) {
1116    var az = 'abcdefghijklmnopqrstuvwxyz';
1117    az += az.toUpperCase();
1118
1119    p = $.extend(
1120      {
1121        nchars: az,
1122        allow: '.',
1123      },
1124      p,
1125    );
1126    return this.each(function () {
1127      $(this).alphanumeric(p);
1128    });
1129  };
1130  $.fn.alpha = function (p) {
1131    var nm = '1234567890';
1132
1133    p = $.extend(
1134      {
1135        nchars: nm,
1136      },
1137      p,
1138    );
1139    return this.each(function () {
1140      $(this).alphanumeric(p);
1141    });
1142  };
1143  $.fn.hexcolor = function (p) {
1144    var hc = '#1234567890abcdefABCDEF';
1145
1146    p = $.extend(
1147      {
1148        allow: hc,
1149      },
1150      p,
1151    );
1152    return this.each(function () {
1153      $(this).alphanumeric(p);
1154    });
1155  };
1156  // added integer 2014-05-06 DCS
1157  $.fn.integer = function (p) {
1158    var nn = '1234567890';
1159    p = $.extend(
1160      {
1161        allow: nn,
1162      },
1163      p,
1164    );
1165    return this.each(function () {
1166      $(this).alphanumeric(p);
1167      /*
1168			$(this).on('keypress',function(e){
1169				var reg = new RegExp("^[0-9]$");
1170				if (!e.charCode) k = String.fromCharCode(e.which);
1171					else k = String.fromCharCode(e.charCode);
1172				if (e.ctrlKey&&k=='v') e.preventDefault();
1173				if (false == reg.test(k)) e.preventDefault();
1174			});
1175			*/
1176      $(this).on('blur', function (e) {
1177        var v = $(this).val();
1178        if (true === isNAN(v) || typeof v !== 'number' || '' === v) {
1179          $(this).val(0);
1180        } else if (v % 1 !== 0) {
1181          $(this).val(parseInt(v));
1182        }
1183      });
1184    });
1185  };
1186  //restrict input to 0-11 (months at address/employer fields)
1187  $.fn.monthField = function () {
1188    return this.each(function () {
1189      var $this = $(this);
1190      $this.on('keypress', function (e) {
1191        var k = String.fromCharCode(e.charCode || e.which);
1192        if (!/[0-9]/.test(k)) {
1193          e.preventDefault();
1194          return;
1195        }
1196        if (e.ctrlKey && k == 'v') {
1197          e.preventDefault();
1198          return;
1199        }
1200        var start = this.selectionStart,
1201          end = this.selectionEnd;
1202        var newValue =
1203          this.value.substring(0, start) + k + this.value.substring(end);
1204        if (parseInt(newValue, 10) > 11) {
1205          e.preventDefault();
1206        }
1207      });
1208      $this.on('input', function () {
1209        var digitsOnly = this.value.replace(/[^0-9]/g, '');
1210        var n = parseInt(digitsOnly, 10);
1211        if (digitsOnly === '' || isNaN(n)) {
1212          this.value = digitsOnly;
1213        } else if (n > 11) {
1214          this.value = '11';
1215        } else {
1216          this.value = digitsOnly;
1217        }
1218      });
1219      $this.on('paste contextmenu', function (e) {
1220        e.preventDefault();
1221      });
1222    });
1223  };
1224})(jQuery);
1225
1226/**
1227 * If cookies are disabled, post websessionid to new page rather than just redirecting
1228 * @param {string(url)} url
1229 * @version 2019.07.23 sb Ticket #549988, CE5 - Custom Code CTA forms to avoid cookie related issues
1230 * @version 2019.08.08 sb Ticket #553215 - removed unneccessary form submit
1231 * @version 2019.08.09 sb Ticket #553606 - Score not being shown on the Congratulations page when cookies are disabled
1232 * @version 2026.09.23 dcs https://trello.com/c/nmX3NpTS/ - carry a single-use handoff nonce
1233 *                        rather than the session id, never hand it to another origin, and build
1234 *                        the query string properly.
1235 *
1236 *                        A POST handoff was tried here and reverted: posting to a page url makes
1237 *                        $_POST non-empty, which sends app_config.php into process_post.php, and
1238 *                        with no doWhat to match, its default branch rewrites $vars[0] to 'index'
1239 *                        - so the visitor lands on the home page instead of the target. That is
1240 *                        the same defect #553215 removed the original form submit for in 2019.
1241 *                        Getting the id out of the url needs server-side support and is its own
1242 *                        ticket; until then it stays a query parameter.
1243 *
1244 *                        The id IS still a bearer credential in the url, so it must never leave
1245 *                        our own origin: isaeGoToInvSearch() and the *redirect targets can be
1246 *                        dealer-entered external urls (LeadFormAppraisalExpressObj::864-869), and
1247 *                        appending it there hands a live session to a host we do not control.
1248 */
1249function cxPageRedirect(url) {
1250  if ('undefined' === typeof pmxwebsessionid) {
1251    window.location.assign(url);
1252    return;
1253  }
1254  var target;
1255  try {
1256    target = new URL(url, window.location.href);
1257  } catch (e) {
1258    window.location.assign(url);
1259    return;
1260  }
1261  // never send the session id off-origin
1262  if (target.origin !== window.location.origin) {
1263    window.location.assign(url);
1264    return;
1265  }
1266  // A single-use nonce, never the session id itself. If one cannot be minted, navigate
1267  // without it: the visitor gets a clean new session on the far side, which is the same
1268  // graceful restart an expired session already produces - far better than publishing a
1269  // live credential to get them there.
1270  var handoff = cxGetSessionHandoff();
1271  if (handoff) {
1272    // searchParams, not '?' + - the old concatenation hard-coded '?', so any target that
1273    // already carried a query string produced a malformed double-'?' with the value buried
1274    // inside the first parameter.
1275    target.searchParams.set('pmxws', handoff);
1276  }
1277  window.location.assign(target.href);
1278}
1279
1280/**
1281 * Masked Input plugin for jQuery
1282 * Copyright (c) 2007-2013 Josh Bush (digitalbush.com)
1283 * Licensed under the MIT license (http://digitalbush.com/projects/masked-input-plugin/#license)
1284 * Version: 1.3.1
1285 * @version 2019.02.25 dcs Ticket #516446 - code formatting
1286 */
1287(function ($) {
1288  function getPasteEvent() {
1289    var el = document.createElement('input'),
1290      name = 'onpaste';
1291    el.setAttribute(name, '');
1292    return typeof el[name] === 'function' ? 'paste' : 'input';
1293  }
1294
1295  var pasteEventName = getPasteEvent() + '.mask',
1296    ua = navigator.userAgent,
1297    iPhone = /iphone/i.test(ua),
1298    android = /android/i.test(ua),
1299    caretTimeoutId;
1300
1301  $.mask = {
1302    //Predefined character definitions
1303    definitions: {
1304      9: '[0-9]',
1305      a: '[A-Za-z]',
1306      '*': '[A-Za-z0-9]',
1307    },
1308    dataName: 'rawMaskFn',
1309    placeholder: '_',
1310  };
1311
1312  $.fn.extend({
1313    //Helper Function for Caret positioning
1314    caret: function (begin, end) {
1315      var range;
1316
1317      if (this.length === 0 || this.is(':hidden')) {
1318        return;
1319      }
1320
1321      if (typeof begin === 'number') {
1322        end = typeof end === 'number' ? end : begin;
1323        return this.each(function () {
1324          if (this.setSelectionRange) {
1325            this.setSelectionRange(begin, end);
1326          } else if (this.createTextRange) {
1327            range = this.createTextRange();
1328            range.collapse(true);
1329            range.moveEnd('character', end);
1330            range.moveStart('character', begin);
1331            range.select();
1332          }
1333        });
1334      } else {
1335        if (this[0].setSelectionRange) {
1336          begin = this[0].selectionStart;
1337          end = this[0].selectionEnd;
1338        } else if (document.selection && document.selection.createRange) {
1339          range = document.selection.createRange();
1340          begin = 0 - range.duplicate().moveStart('character', -100000);
1341          end = begin + range.text.length;
1342        }
1343        return { begin: begin, end: end };
1344      }
1345    },
1346    unmask: function () {
1347      return this.trigger('unmask');
1348    },
1349    mask: function (mask, settings) {
1350      var input, defs, tests, partialPosition, firstNonMaskPos, len;
1351
1352      if (!mask && this.length > 0) {
1353        input = $(this[0]);
1354        return input.data($.mask.dataName)();
1355      }
1356      settings = $.extend(
1357        {
1358          placeholder: $.mask.placeholder, // Load default placeholder
1359          completed: null,
1360        },
1361        settings,
1362      );
1363
1364      defs = $.mask.definitions;
1365      tests = [];
1366      partialPosition = len = mask.length;
1367      firstNonMaskPos = null;
1368
1369      $.each(mask.split(''), function (i, c) {
1370        if (c == '?') {
1371          len--;
1372          partialPosition = i;
1373        } else if (defs[c]) {
1374          tests.push(new RegExp(defs[c]));
1375          if (firstNonMaskPos === null) {
1376            firstNonMaskPos = tests.length - 1;
1377          }
1378        } else {
1379          tests.push(null);
1380        }
1381      });
1382
1383      return this.trigger('unmask').each(function () {
1384        var input = $(this),
1385          buffer = $.map(mask.split(''), function (c, i) {
1386            if (c != '?') {
1387              return defs[c] ? settings.placeholder : c;
1388            }
1389          }),
1390          focusText = input.val();
1391
1392        function seekNext(pos) {
1393          while (++pos < len && !tests[pos]);
1394          return pos;
1395        }
1396
1397        function seekPrev(pos) {
1398          while (--pos >= 0 && !tests[pos]);
1399          return pos;
1400        }
1401
1402        function shiftL(begin, end) {
1403          var i, j;
1404
1405          if (begin < 0) {
1406            return;
1407          }
1408
1409          for (i = begin, j = seekNext(end); i < len; i++) {
1410            if (tests[i]) {
1411              if (j < len && tests[i].test(buffer[j])) {
1412                buffer[i] = buffer[j];
1413                buffer[j] = settings.placeholder;
1414              } else {
1415                break;
1416              }
1417
1418              j = seekNext(j);
1419            }
1420          }
1421          writeBuffer();
1422          input.caret(Math.max(firstNonMaskPos, begin));
1423        }
1424
1425        function shiftR(pos) {
1426          var i, c, j, t;
1427
1428          for (i = pos, c = settings.placeholder; i < len; i++) {
1429            if (tests[i]) {
1430              j = seekNext(i);
1431              t = buffer[i];
1432              buffer[i] = c;
1433              if (j < len && tests[j].test(t)) {
1434                c = t;
1435              } else {
1436                break;
1437              }
1438            }
1439          }
1440        }
1441
1442        function keydownEvent(e) {
1443          var k = e.which,
1444            pos,
1445            begin,
1446            end;
1447
1448          //backspace, delete, and escape get special treatment
1449          if (k === 8 || k === 46 || (iPhone && k === 127)) {
1450            pos = input.caret();
1451            begin = pos.begin;
1452            end = pos.end;
1453
1454            if (end - begin === 0) {
1455              begin = k !== 46 ? seekPrev(begin) : (end = seekNext(begin - 1));
1456              end = k === 46 ? seekNext(end) : end;
1457            }
1458            clearBuffer(begin, end);
1459            shiftL(begin, end - 1);
1460
1461            e.preventDefault();
1462          } else if (k == 27) {
1463            //escape
1464            input.val(focusText);
1465            input.caret(0, checkVal());
1466            e.preventDefault();
1467          }
1468        }
1469
1470        function keypressEvent(e) {
1471          var k = e.which,
1472            pos = input.caret(),
1473            p,
1474            c,
1475            next;
1476
1477          if (e.ctrlKey || e.altKey || e.metaKey || k < 32) {
1478            //Ignore
1479            return;
1480          } else if (k) {
1481            if (pos.end - pos.begin !== 0) {
1482              clearBuffer(pos.begin, pos.end);
1483              shiftL(pos.begin, pos.end - 1);
1484            }
1485
1486            p = seekNext(pos.begin - 1);
1487            if (p < len) {
1488              c = String.fromCharCode(k);
1489              if (tests[p].test(c)) {
1490                shiftR(p);
1491
1492                buffer[p] = c;
1493                writeBuffer();
1494                next = seekNext(p);
1495
1496                if (android) {
1497                  setTimeout($.proxy($.fn.caret, input, next), 0);
1498                } else {
1499                  input.caret(next);
1500                }
1501
1502                if (settings.completed && next >= len) {
1503                  settings.completed.call(input);
1504                }
1505              }
1506            }
1507            e.preventDefault();
1508          }
1509        }
1510
1511        function clearBuffer(start, end) {
1512          var i;
1513          for (i = start; i < end && i < len; i++) {
1514            if (tests[i]) {
1515              buffer[i] = settings.placeholder;
1516            }
1517          }
1518        }
1519
1520        function writeBuffer() {
1521          input.val(buffer.join(''));
1522        }
1523
1524        function checkVal(allow) {
1525          //try to place characters where they belong
1526          var test = input.val(),
1527            lastMatch = -1,
1528            i,
1529            c;
1530
1531          for (i = 0, pos = 0; i < len; i++) {
1532            if (tests[i]) {
1533              buffer[i] = settings.placeholder;
1534              while (pos++ < test.length) {
1535                c = test.charAt(pos - 1);
1536                if (tests[i].test(c)) {
1537                  buffer[i] = c;
1538                  lastMatch = i;
1539                  break;
1540                }
1541              }
1542              if (pos > test.length) {
1543                break;
1544              }
1545            } else if (
1546              buffer[i] === test.charAt(pos) &&
1547              i !== partialPosition
1548            ) {
1549              pos++;
1550              lastMatch = i;
1551            }
1552          }
1553          if (allow) {
1554            writeBuffer();
1555          } else if (lastMatch + 1 < partialPosition) {
1556            input.val('');
1557            clearBuffer(0, len);
1558          } else {
1559            writeBuffer();
1560            input.val(input.val().substring(0, lastMatch + 1));
1561          }
1562          return partialPosition ? i : firstNonMaskPos;
1563        }
1564
1565        input.data($.mask.dataName, function () {
1566          return $.map(buffer, function (c, i) {
1567            return tests[i] && c != settings.placeholder ? c : null;
1568          }).join('');
1569        });
1570
1571        if (!input.attr('readonly')) {
1572          input
1573            .one('unmask', function () {
1574              input.unbind('.mask').removeData($.mask.dataName);
1575            })
1576            .bind('focus.mask', function () {
1577              clearTimeout(caretTimeoutId);
1578              var pos, moveCaret;
1579
1580              focusText = input.val();
1581              pos = checkVal();
1582
1583              caretTimeoutId = setTimeout(function () {
1584                writeBuffer();
1585                if (pos == mask.length) {
1586                  input.caret(0, pos);
1587                } else {
1588                  input.caret(pos);
1589                }
1590              }, 10);
1591            })
1592            .bind('blur.mask', function () {
1593              checkVal();
1594              if (input.val() != focusText) input.change();
1595            })
1596            .bind('keydown.mask', keydownEvent)
1597            .bind('keypress.mask', keypressEvent)
1598            .bind(pasteEventName, function () {
1599              setTimeout(function () {
1600                var pos = checkVal(true);
1601                input.caret(pos);
1602                if (settings.completed && pos == input.val().length)
1603                  settings.completed.call(input);
1604              }, 0);
1605            });
1606        }
1607        checkVal(); //Perform initial check for existing values
1608      });
1609    },
1610  });
1611})(jQuery);
1612
1613/**
1614 * if department hours are set for a selected day, set available hours to department hours
1615 * @param {object} input
1616 * @param {object} form_selector
1617 * @version 2019.08.12 sb Ticket #547019 - Add option to CTA forms (schedule appointment) to disable specific dates/times
1618 * @version 2019.08.28 dcs Ticket #557045 - fixed time display format
1619 * @version 2026.09.02 dcs - clear the stale time on every date change, scope to the owning
1620 *                           form, and stop printing "Store closed NaN/NaN/NaN" for an empty
1621 *                           date: https://trello.com/c/EqXsiU18/
1622 */
1623function ctaApptDateCheck(input, form_selector) {
1624  //scope to the form that owns this date input - a page can host several lead forms, and the
1625  //time input shares a fixed name across all of them
1626  var $scope = $(input).closest('form, .leadForm');
1627  if (!$scope.length) $scope = $(form_selector);
1628  if (!$scope.length) $scope = $(document);
1629  var $time = $('input[name=AppointmentTime]', $scope);
1630
1631  //remember the server-rendered "Select Appt Date" text before it is blanked below, so an
1632  //empty date can put it back
1633  if ('undefined' === typeof $time.data('defaultPlaceholder')) {
1634    $time.data('defaultPlaceholder', $time.attr('placeholder') || '');
1635  }
1636
1637  //the date decides which times exist, so a time picked against the previous date is never
1638  //trustworthy - blank it and make the customer re-pick from the rebuilt list
1639  $time.val('').removeClass('field_isRequired');
1640  if ('function' === typeof hideValidationError) {
1641    hideValidationError($time, $scope.find('input[name=leadTypeID]').val());
1642  }
1643
1644  $time.attr('disabled', false);
1645  $time.attr('placeholder', '');
1646  if ('undefined' !== typeof $(input).data('dept')) {
1647    //an ISO value (native date input) must parse as LOCAL time - bare
1648    //new Date('YYYY-MM-DD') is UTC midnight, the previous weekday in US zones
1649    var apptval = String($(input).val() || '').trim();
1650    var apptdate = /^\d{4}-\d{2}-\d{2}$/.test(apptval)
1651      ? new Date(apptval + 'T12:00:00')
1652      : new Date(apptval);
1653    //no date (or a half-typed one) is not a closed day - without this the weekday lookup
1654    //below yields NaN and the placeholder reads "Store closed NaN/NaN/NaN"
1655    if ('' === apptval || isNaN(apptdate.getTime())) {
1656      $time.attr('disabled', true);
1657      $time.attr('placeholder', $time.data('defaultPlaceholder'));
1658      return;
1659    }
1660    var day = [
1661      'sunday',
1662      'monday',
1663      'tuesday',
1664      'wednesday',
1665      'thursday',
1666      'friday',
1667      'saturday',
1668    ][apptdate.getDay()];
1669    //get open and close times for selected date
1670    var timeopen = $(input).data(day + 'Open');
1671    var timeclose = $(input).data(day + 'Close');
1672    //calculate time difference between the two
1673    var timeStart = new Date('01/01/2007 ' + timeopen);
1674    var timeEnd = new Date('01/01/2007 ' + timeclose);
1675    var timeDiff = Math.floor(Math.abs(timeEnd - timeStart) / 1000 / 60);
1676
1677    //if both opening hours and closing hours are set
1678    if (!isNaN(timeDiff)) {
1679      //set data for appointment time check
1680      $time.data('day', day);
1681      $time.data('timeopen', timeopen);
1682      $time.data('timeclosed', timeclose);
1683
1684      //recreate timepicker with department hours
1685      $('.timePicker', $scope).timepicker('destroy');
1686      $('.timePicker', $scope).timepicker({
1687        minTime: timeopen,
1688        maxTime: timeclose,
1689        interval: 15,
1690        dynamic: false,
1691        timeFormat: 'h:mm p',
1692        change: function () {
1693          ctaApptTimeCheck($time);
1694        },
1695        zindex: 1100,
1696      });
1697    }
1698    //else the day is set to one of the "closed" sentinels, or to something this code
1699    //cannot read
1700    else {
1701      //compare each value exactly, trimmed and uppercased the way getClosedDaysCsv does.
1702      //A substring test cannot be used here: CAPPOINT ("Closed - By Appointment Only")
1703      //and CAPPT ("By Appointment Only") differ by two letters and mean opposite things
1704      var openKey = String(timeopen || '').trim().toUpperCase();
1705      var closeKey = String(timeclose || '').trim().toUpperCase();
1706      var eitherIs = function (sentinel) {
1707        return openKey === sentinel || closeKey === sentinel;
1708      };
1709      //the five values DepartmentsHoursObj::getTimes() offers besides a clock time
1710      var isHardClosed =
1711        '' === openKey ||
1712        '' === closeKey ||
1713        eitherIs('CLOSED') ||
1714        eitherIs('CAPPOINT') ||
1715        eitherIs('CALL');
1716      var closedPlaceholder;
1717      //CAPPT/COCALL days are selectable (not hard-closed), so say what the
1718      //sentinel means instead of the misleading "Store closed" (mirrors the
1719      //store-hours popover prose)
1720      if (eitherIs('CAPPT')) {
1721        closedPlaceholder =
1722          'es' == cxDoTranslation ? 'Solo con cita previa' : 'By Appointment Only';
1723      } else if (eitherIs('COCALL')) {
1724        closedPlaceholder =
1725          'es' == cxDoTranslation ? 'Por favor llame' : 'Please Call';
1726      } else if (isHardClosed) {
1727        closedPlaceholder =
1728          ('es' == cxDoTranslation ? 'Cerrado el ' : 'Store closed ') +
1729          (apptdate.getMonth() + 1) +
1730          '/' +
1731          apptdate.getDate() +
1732          '/' +
1733          apptdate.getFullYear();
1734      } else {
1735        //hours set to something unreadable - the admin picker cannot produce this, but a
1736        //crafted POST or a new getTimes() value can. getClosedDaysCsv leaves the weekday
1737        //selectable in that case, and claiming the store is closed asserts something we
1738        //do not know, so ask the customer to call (matches the embed widget's wording)
1739        closedPlaceholder =
1740          'es' == cxDoTranslation ? 'Por favor llame' : 'Please Call';
1741      }
1742      $time.val('');
1743      $time.attr('disabled', true);
1744      $time.attr('placeholder', closedPlaceholder);
1745    }
1746  }
1747}
1748
1749/**
1750 * check if a selected appointment time is within department hours for the day
1751 * @param {object} [$input] the AppointmentTime input to check; omit for the page-wide one
1752 * @version 2019.08.12 sb Ticket #547019 - Add option to CTA forms (schedule appointment) to disable specific dates/times
1753 * @version 2026.09.02 dcs - accept the input so multiple forms on a page do not cross-check
1754 *                           each other: https://trello.com/c/EqXsiU18/
1755 */
1756function ctaApptTimeCheck($input) {
1757  //ctaApptDateCheck passes the input it resolved for this form; the default timepicker init
1758  //in initializePickers() takes no argument, so fall back to the page-wide selector
1759  var $time =
1760    $input && $input.length ? $input : $('input[name=AppointmentTime]');
1761  var data = $time.data();
1762  if (
1763    'undefined' != typeof data.timeopen &&
1764    'undefined' != typeof data.timeclosed &&
1765    'undefined' != typeof data.day
1766  ) {
1767    var time = new Date('01/01/2007 ' + $time.val().replace('.', ':'));
1768    var timeStart = new Date('01/01/2007 ' + data.timeopen);
1769    var timeEnd = new Date('01/01/2007 ' + data.timeclosed);
1770    if (timeStart > time || timeEnd < time) {
1771      //dynamic content, so localized here rather than via the cxTranslate table
1772      cxAlert(
1773        'es' == cxDoTranslation
1774          ? 'El horario de citas para el ' +
1775              ({
1776                sunday: 'domingo',
1777                monday: 'lunes',
1778                tuesday: 'martes',
1779                wednesday: 'mi\xE9rcoles',
1780                thursday: 'jueves',
1781                friday: 'viernes',
1782                saturday: 's\xE1bado',
1783              }[data.day] || data.day) +
1784              ' es de ' +
1785              data.timeopen +
1786              ' a ' +
1787              data.timeclosed +
1788              '. Por favor, seleccione una hora dentro de ese horario.'
1789          : 'Appointment hours on ' +
1790              data.day +
1791              ' are from ' +
1792              data.timeopen +
1793              ' to ' +
1794              data.timeclosed +
1795              '. Please select a time within these hours.',
1796      );
1797      $time.val('');
1798    }
1799  }
1800}
1801
1802/**
1803 * trigger browser to scroll to top of page
1804 * @version 2020.10.22 sb Ticket #639218 - Make CTA next page load bring user back to top of form
1805 */
1806function cx5ScrollToTop() {
1807  $('html, body').animate(
1808    {
1809      scrollTop: 0,
1810    },
1811    200,
1812  );
1813}

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.