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, '&') 1031 .replace(/"/g, '"') 1032 .replace(/'/g, ''') 1033 .replace(/</g, '<') 1034 .replace(/>/g, '>') 1035 .replace(/\//g, '/'); 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.