1(function ($) { 2 3 $(document).ready(function () { 4 // ----------------- Global Settings ----------------- 5 let currencySymbol = "$"; // Change to any currency symbol dynamically 6 let decimalPlaces = 2; // Adjust between 2 or 3 dynamically 7 8 // ----------------- Formatting Functions ----------------- 9 function formatString(digits, pattern) { 10 11 12 let formatted = ""; 13 let index = 0; 14 for (let char of pattern) { 15 if (char === "#") { 16 if (index < digits.length) { 17 formatted += digits[index++]; 18 } else { 19 break; 20 } 21 } else { 22 formatted += char; 23 } 24 } 25 26 27 return formatted; 28 } 29 30 function stripCNPJ(value) { 31 var s = String(value).toUpperCase().replace(/[^A-Z0-9]/g, ""); 32 if (s.length <= 12) return s; 33 return s.slice(0, 12) + s.slice(12).replace(/\D/g, "").slice(0, 2); 34 } 35 36 // ----------------- Money Mask Formatting ----------------- 37 function formatMoneyInput(value, type, prefix, input) { 38 let decimalSeparator = type === "C" ? "." : ","; 39 let thousandSeparator = type === "C" ? "," : "."; 40 let rawDigits = value.replace(/\D/g, ""); 41 decimalPlaces = 2; 42 if (input !== "") { 43 decimalPlaces = Number(input.dataset.decimalPlaces); 44 } 45 46 if (rawDigits.length === 0) { 47 return `${prefix}0${decimalSeparator}${"0".repeat(decimalPlaces)}`; 48 } 49 50 while (rawDigits.length < decimalPlaces + 1) { 51 rawDigits = "0" + rawDigits; 52 } 53 54 let cents = rawDigits.slice(-decimalPlaces); 55 let wholeNumber = 56 rawDigits.slice(0, -decimalPlaces).replace(/^0+/, "") || "0"; 57 58 wholeNumber = wholeNumber.replace( 59 /\B(?=(\d{3})+(?!\d))/g, 60 thousandSeparator 61 ); 62 return `${prefix}${wholeNumber}${decimalSeparator}${cents}`; 63 } 64 65 function handleMoneyInput(event) { 66 let input = event.target; 67 let oldValue = input.value; 68 let oldCursorPos = input.selectionStart; 69 70 let moneyPrefix = input.dataset.moneymaskPrefix; 71 let moneymaskFormat = input.dataset.moneymaskFormat; 72 let type = moneymaskFormat === "dot" ? "D" : "C"; 73 74 let newValue = ''; 75 76 if(input.value != '$0,00'){ 77 78 newValue = formatMoneyInput(input.value, type, moneyPrefix, input); 79 }else{ 80 newValue = ""; 81 } 82 83 84 85 if (input.value === newValue) return; 86 87 input.value = newValue; 88 let newCursorPos = oldCursorPos + (newValue.length - oldValue.length); 89 setCaretPosition(input, newCursorPos); 90 } 91 92 // ----------------- Handling Focus & Blur for Money Mask ----------------- 93 $(document).on("focus", ".mask-moneyc", function () { 94 95 96 let moneymaskFormat = $(this)[0].dataset.moneymaskFormat; 97 let type = moneymaskFormat === "dot" ? "D" : "C"; 98 let decimalSeparator = type === "C" ? "." : ","; 99 let baseFormat = `${currencySymbol}0${decimalSeparator}${"0".repeat( 100 decimalPlaces 101 )}`; 102 103 104 if ($(this).val().trim() === "") { 105 $(this).val(baseFormat); 106 // setCaretPosition(this, $(this).val().length - (decimalPlaces + 1)); 107 } 108 }); 109 110 $(document).on("blur", ".mask-moneyc", function () { 111 let moneymaskFormat = $(this)[0].dataset.moneymaskFormat; 112 let type = moneymaskFormat === "dot" ? "D" : "C"; 113 let decimalSeparator = type === "C" ? "." : ","; 114 let baseFormat = `${currencySymbol}0${decimalSeparator}${"0".repeat( 115 decimalPlaces 116 )}`; 117 118 let val = $(this).val().trim(); 119 120 let numericValue = val 121 .replace(new RegExp(`[^0-9${decimalSeparator}]`, "g"), "") 122 .replace(decimalSeparator, "."); 123 124 if (parseFloat(numericValue) === 0 || val === baseFormat) { 125 $(this).val(""); 126 } 127 }); 128 129 // ----------------- Formatting Credit Card ----------------- 130 function formatCreditCard(digits, formatType) { 131 let cardType = detectCardType(digits); 132 133 if (cardType === "American Express") { 134 digits = digits.slice(0, 15); 135 return formatType === "space" 136 ? formatString(digits, "#### ###### #####") 137 : formatString(digits, "####-######-#####"); 138 } else { 139 digits = digits.slice(0, 16); 140 return formatType === "space" 141 ? formatString(digits, "#### #### #### ####") 142 : formatString(digits, "####-####-####-####"); 143 } 144 } 145 146 // ----------------- Card Logos ----------------- 147 const cardLogos = { 148 Visa: fmeData.pluginUrl + "assets/svg-icons/visa-logo.svg", 149 MasterCard: fmeData.pluginUrl + "assets/svg-icons/mastercard-logo.svg", 150 "American Express": fmeData.pluginUrl + "assets/svg-icons/amex-logo.svg", 151 Discover: fmeData.pluginUrl + "assets/svg-icons/discover-logo.svg", 152 JCB: fmeData.pluginUrl + "assets/svg-icons/jcb-logo.svg", 153 "Diners Club": fmeData.pluginUrl + "assets/svg-icons/cc-logo.svg", 154 Maestro: fmeData.pluginUrl + "assets/svg-icons/maestro-logo.svg", 155 UnionPay: fmeData.pluginUrl + "assets/svg-icons/cc-logo.svg", 156 RuPay: fmeData.pluginUrl + "assets/svg-icons/repay-logo.svg", 157 Unknown: fmeData.pluginUrl + "assets/svg-icons/cc-logo.svg", 158 }; 159 160 // ----------------- Function to Detect Card Type -----------------
161 function detectCardType(number) { 162 const cleaned = number.replace(/\D/g, ""); // Remove non-numeric characters 163 164 const cardPatterns = { 165 Visa: /^4/, 166 MasterCard: /^5[1-5]/, // MasterCard starts with 51-55 167 "American Express": /^3[47]/, // AmEx starts with 34 or 37 168 Discover: /^6(?:011|5)/, // Discover starts with 6011 or 65 169 JCB: /^(?:2131|1800|35)/, // JCB starts with 2131, 1800, or 35 170 "Diners Club": /^3(?:0[0-5]|[689])/, // Diners Club starts with 300-305, 36, or 38-39 171 UnionPay: /^(62|81)/, // UnionPay starts with 62 or 81 172 RuPay: /^(60|65|81|82|508)/, // RuPay starts with 60, 65, 81, 82, or 508 173 Maestro: /^(50|5[6-9]|6[0-9])/, // Maestro starts with 50, 56-59, 60-69 174 }; 175 176 for (let card in cardPatterns) { 177 if (cardPatterns[card].test(cleaned)) { 178 return card; 179 } 180 } 181 182 return "Unknown"; // Default if no match is found 183 } 184 185 // ----------------- Function to Update Card Logo Dynamically ----------------- 186 function updateCardLogo(inputSelector) { 187 let input = $(inputSelector); 188 let logo = input.siblings(".card-logo"); // Select logo next to input 189 let cardNumber = input.val().replace(/\D/g, ""); // Remove non-digit characters 190 191 if (cardNumber === "") { 192 logo.hide(); // Hide the logo if the input is empty 193 } else { 194 let cardType = detectCardType(cardNumber); 195 if (cardType in cardLogos) { 196 logo.attr("src", cardLogos[cardType]).show(); 197 } else { 198 logo.hide(); // Hide logo if card type is unknown 199 } 200 } 201 } 202 203 // ----------------- Universal Field Formatting ----------------- 204 const formatFunctions = { 205 ".mask-cnpj": (digits) => formatString(digits, "##.###.###/####-##"), 206 ".mask-cpf": (digits) => formatString(digits, "###.###.###-##"), 207 ".mask-cep": (digits) => formatString(digits, "#####-###"), 208 ".mask-phus": (digits) => formatString(digits, "(###) ###-####"), 209 ".mask-ph8": (digits) => formatString(digits, "####-####"), 210 ".mask-ddd8": (digits) => formatString(digits, "(##) ####-####"), 211 ".mask-ddd9": (digits) => formatString(digits, "(##) #####-####"), 212 ".mask-dmy": (digits) => formatString(digits, "##/##/####"), 213 ".mask-mdy": (digits) => formatString(digits, "##/##/####"), 214 ".mask-hms": (digits) => formatString(digits, "##:##:##"), 215 ".mask-hm": (digits) => formatString(digits, "##:##"), 216 ".mask-dmyhm": (digits) => formatString(digits, "##/##/#### ##:##"), 217 ".mask-mdyhm": (digits) => formatString(digits, "##/##/#### ##:##"), 218 ".mask-my": (digits) => formatString(digits, "##/####"), 219 ".mask-ccs": (digits) => formatCreditCard(digits, "space"), 220 ".mask-cch": (digits) => formatCreditCard(digits, "hyphen"), 221 ".mask-ccmy": (digits) => formatString(digits, "##/##"), 222 ".mask-ccmyy": (digits) => formatString(digits, "##/####"), 223 ".mask-moneyc": (digits) => formatMoneyInput(digits, "C", "$", ""), 224 ".mask-moneyd": (digits) => formatMoneyInput(digits, "D", "$", ""), 225 ".mask-ipv4": (digits) => formatString(digits, "###.###.###.###"), // New IPv4 Masking 226 }; 227 228 // Apply formatting dynamically for all fields, including money inputs 229 Object.entries(formatFunctions).forEach(([selector, formatFunction]) => { 230 $(document).on("input focus", selector, function (event) { 231 var input = this; 232 var oldCaret = getCaretPosition(input); 233 234 // Handle money input separately 235 if ( 236 $(input).hasClass("mask-moneyc") || 237 $(input).hasClass("mask-moneyd") 238 ) { 239 240 241 let type = $(input).hasClass("mask-moneyc") ? "C" : "D"; 242 handleMoneyInput(event); 243 return; 244 } 245 246 var isCnpj = $(input).hasClass("mask-cnpj"); 247 var rawDigits = isCnpj ? stripCNPJ(input.value) : input.value.replace(/\D/g, ""); 248 var digitIndex = getDigitIndexFromCaret(input.value, oldCaret, isCnpj); 249 var newVal = formatFunction(rawDigits); 250 var newCaret = mapDigitIndexToCaret(newVal, digitIndex, isCnpj); 251 252 253 if(newVal != '('){ 254 255 input.value = newVal; 256 }else{ 257 input.value = ""; 258 } 259 260 261 setCaretPosition(input, newCaret || 0); // Keep caret in place 262 263 // Update card logo dynamically for credit card fields 264 if ($(input).hasClass("mask-ccs") || $(input).hasClass("mask-cch")) { 265 updateCardLogo(input); 266 } 267 }); 268 }); 269 270 // ----------------- Helper Functions for Caret Management ----------------- 271 function getCaretPosition(input) { 272 return input.selectionStart; 273 } 274 function getDigitIndexFromCaret(formattedStr, caretPos, alphanumeric) { 275 var count = 0; 276 var pattern = alphanumeric ? /[A-Za-z0-9]/ : /\d/; 277 for (var i = 0; i < caretPos; i++) { 278 if (pattern.test(formattedStr.charAt(i))) { 279 count++; 280 } 281 } 282 return count; 283 } 284 function mapDigitIndexToCaret(formattedStr, digitIndex, alphanumeric) { 285 var count = 0; 286 var pattern = alphanumeric ? /[A-Za-z0-9]/ : /\d/; 287 for (var i = 0; i < formattedStr.length; i++) { 288 if (pattern.test(formattedStr.charAt(i))) { 289 if (count === digitIndex) { 290 return i; 291 } 292 count++; 293 } 294 } 295 return formattedStr.length; 296 } 297 function setCaretPosition(elem, pos) { 298 if (elem.setSelectionRange) { 299 elem.focus(); 300 elem.setSelectionRange(pos, pos); 301 } else if (elem.createTextRange) { 302 var range = elem.createTextRange(); 303 range.collapse(true); 304 range.moveEnd("character", pos); 305 range.moveStart("character", pos); 306 range.select(); 307 } 308 } 309 310 // ----------------- Backspace Handling ----------------- 311 $(document).on("keydown", "input", function (e) { 312 // jQuery(".e-form__buttons__wrapper__button[data-direction='next']").off("
312click"); 313 314 315 if (e.key === "Backspace") { 316 var input = this; 317 318 // Special handling for money inputs 319 if ( 320 $(input).hasClass("mask-moneyc") || 321 $(input).hasClass("mask-moneyd") 322 ) { 323 let decimalSeparator = $(input).hasClass("mask-moneyc") ? "." : ","; 324 let baseFormat = `${currencySymbol}0${decimalSeparator}${"0".repeat( 325 decimalPlaces 326 )}`; 327 328 if (input.value === baseFormat) { 329 e.preventDefault(); // Prevent deletion beyond the base format 330 return; 331 } 332 333 // Handle money input dynamically and prevent default formatting conflict 334 handleMoneyInput(e); 335 return; 336 } 337 338 // Standard backspace handling for all other masked inputs 339 if (input.selectionStart !== input.selectionEnd) { 340 e.preventDefault(); 341 input.value = ""; 342 setCaretPosition(input, 0); 343 return; 344 } 345 346 var caretPos = getCaretPosition(input); 347 var isCnpj = $(input).hasClass("mask-cnpj"); 348 var digitIndex = getDigitIndexFromCaret(input.value, caretPos, isCnpj); 349 if (digitIndex === 0) return; 350 351 var rawDigits = isCnpj ? stripCNPJ(input.value) : input.value.replace(/\D/g, ""); 352 var newDigits = 353 rawDigits.slice(0, digitIndex - 1) + rawDigits.slice(digitIndex); 354 355 // Find matching class for format function 356 let matchedClass = Object.keys(formatFunctions).find((cls) => 357 $(input).hasClass(cls.substring(1)) 358 ); 359 360 if (matchedClass) { 361 var formatted = formatFunctions[matchedClass](newDigits); 362 input.value = formatted; 363 var newCaretPos = mapDigitIndexToCaret(formatted, digitIndex - 1, isCnpj); 364 setCaretPosition(input, newCaretPos); 365 } 366 } 367 }); 368 369 // ----------------- Error Validation ----------------- 370 function validateInput( 371 selector, 372 errorClass, 373 validationFunction, 374 errorMessage 375 ) { 376 $(document).on("blur", selector, function () { 377 var input = $(this); 378 379 if(input.hasClass("hide-fme-mask-input")){ 380 return; 381 } 382 383 var val = input.val(); 384 var errorElement = input 385 .closest(".elementor-field-group") 386 .find("." + errorClass); 387 388 // Remove predefined symbol if it's the ONLY character left 389 if (val.length === 1 && !/\d/.test(val)) { 390 input.val(""); 391 errorElement.hide().text(""); 392 return; 393 } 394 395 // Show error message if validation fails 396 if (val !== "" && !validationFunction(val)) { 397 errorElement 398 .text(errorMessage) 399 .css("display", "flex") 400 .hide() 401 .fadeIn(200); 402 } else { 403 errorElement.fadeOut(100, function () { 404 $(this).css("display", "none"); 405 }); 406 } 407 }); 408 409 $(document).on("input", selector, function () { 410 var input = $(this); 411 412 nextbtnVisibility(errorClass, input, validationFunction); 413 414 var errorElement = input 415 .closest(".elementor-field-group") 416 .find("." + errorClass); 417 418 if (errorElement.is(":visible")) { 419 var val = input.val(); 420 if (validationFunction(val)) { 421 errorElement.fadeOut(100, function () { 422 $(this).css("display", "none"); 423 }); 424 } 425 } 426 }); 427 } 428 429 // Apply validation dynamically for all fields 430 const validations = { 431 ".mask-cnpj": { 432 errorClass: "error-cnpj", 433 validate: isValidCNPJ, 434 msg: fmeData.errorMessages["mask-cnpj"], 435 }, 436 ".mask-cpf": { 437 errorClass: "error-cpf", 438 validate: isValidCPF, 439 msg: fmeData.errorMessages["mask-cpf"], 440 }, 441 ".mask-cep": { 442 errorClass: "error-cep", 443 validate: isValidCEP, 444 msg: fmeData.errorMessages["mask-cep"], 445 }, 446 ".mask-phus": { 447 errorClass: "error-phus", 448 validate: isValidPhoneUSA, 449 msg: fmeData.errorMessages["mask-phus"], 450 }, 451 ".mask-ph8": { 452 errorClass: "error-ph8", 453 validate: isValidPhone8, 454 msg: fmeData.errorMessages["mask-ph8"], 455 }, 456 ".mask-ddd8": { 457 errorClass: "error-ddd8", 458 validate: isValidPhoneDDD8, 459 msg: fmeData.errorMessages["mask-ddd8"], 460 }, 461 ".mask-ddd9": { 462 errorClass: "error-ddd9", 463 validate: isValidPhoneDDD9, 464 msg: fmeData.errorMessages["mask-ddd9"], 465 }, 466 ".mask-dmy": { 467 errorClass: "error-dmy", 468 validate: isValidDateDMY, 469 msg: fmeData.errorMessages["mask-dmy"], 470 }, 471 ".mask-mdy": { 472 errorClass: "error-mdy", 473 validate: isValidDateMDY, 474 msg: fmeData.errorMessages["mask-mdy"], 475 }, 476 ".mask-hms": { 477 errorClass: "error-hms", 478 validate: isValidTimeHMS, 479 msg: fmeData.errorMessages["mask-hms"], 480 }, 481 ".mask-hm": { 482 errorClass: "error-hm", 483 validate: isValidTimeHM, 484 msg: fmeData.errorMessages["mask-hm"], 485 }, 486 ".mask-dmyhm": { 487 errorClass: "error-dmyhm", 488 validate: isValidDateDMYHM, 489 msg: fmeData.errorMessages["mask-dmyhm"], 490 }, 491 ".mask-mdyhm": { 492 errorClass: "error-mdyhm", 493 validate: isValidDateMDYHM, 494 msg: fmeData.errorMessages["mask-mdyhm"], 495 }, 496 ".mask-my": { 497 errorClass: "error-my", 498 validate: isValidDateMY, 499 msg: fmeData.errorMessages["mask-my"], 500 }, 501 ".mask-ccs": { 502 errorClass: "error-ccs", 503 validate: isValidCreditCard, 504 msg: fmeData.errorMessages["mask-ccs"], 505 }, 506 ".mask-cch": { 507 errorClass: "error-cch", 508 validate: isValidCreditCard, 509 msg: fmeData.errorMessages["mask-cch"], 510 }, 511 ".mask-ccmy": { 512 errorClass: "error-ccmy", 513 validate: isValidExpiryMMYY, 514 msg: fmeData.errorMessages["mask-ccmy"], 515 }, 516 ".mask-ccmyy": { 517 errorClass: "error-ccmyy", 518 validate: isValidExpiryMMYYYY, 519 msg: fmeData.errorMessages["mask-ccmyy"], 520 }, 521 ".mask-ipv4": { 522 errorClass: "error-ipv4", 523 validate: isValidIPv4, 524 msg: fmeData.errorMessages["mask-ipv4"], 525 }, 526 }; 527 528 for (const [selector, { errorClass, validate, msg }] of Object.entries( 529 validations 530 )) { 531 validateInput(selector, errorClass, validate, msg); 532 } 533 534 // ----------------- Validation Functions ----------------- 535 function isValidPhoneUSA(phoneStr) { 536 return /^\(\d{3}\) \d{3}-\d{4}$/.test(phoneStr); 537 } 538 function isValidPhone8(phoneStr) { 539 return /^\d{4}-\d{4}$/.test(phoneStr); 540 } 541 function isValidPhoneDDD8(phoneStr) { 542 return /^\(\d{2}\) \d{4}-\d{4}$/.test(phoneStr); 543 } 544 function isValidPhoneDDD9(phoneStr) { 545 return /^\(\d{2}\) 9\d{4}-\d{4}$/.test(phoneStr); 546 } 547 548 // ----------------- Universal Date & Time Validation Function ----------------- 549 function isValidDateTime(value, format) { 550 let regexPattern; 551 let expectedParts; 552 553 switch (format) { 554 case "DMY": // dd/mm/yyyy 555 regexPattern = /^(\d{2})\/(\d{2})\/(\d{4})$/; 556 expectedParts = ["day", "month", "year"]; 557 break;
558 case "MDY": // mm/dd/yyyy 559 regexPattern = /^(\d{2})\/(\d{2})\/(\d{4})$/; 560 expectedParts = ["month", "day", "year"]; 561 break; 562 case "HMS": // hh:mm:ss 563 regexPattern = /^(\d{2}):(\d{2}):(\d{2})$/; 564 expectedParts = ["hour", "minute", "second"]; 565 break; 566 case "HM": // hh:mm 567 regexPattern = /^(\d{2}):(\d{2})$/; 568 expectedParts = ["hour", "minute"]; 569 break; 570 case "DMY-HM": // dd/mm/yyyy hh:mm 571 regexPattern = /^(\d{2})\/(\d{2})\/(\d{4}) (\d{2}):(\d{2})$/; 572 expectedParts = ["day", "month", "year", "hour", "minute"]; 573 break; 574 case "MDY-HM": // mm/dd/yyyy hh:mm 575 regexPattern = /^(\d{2})\/(\d{2})\/(\d{4}) (\d{2}):(\d{2})$/; 576 expectedParts = ["month", "day", "year", "hour", "minute"]; 577 break; 578 case "MY": // mm/yyyy 579 regexPattern = /^(\d{2})\/(\d{4})$/; 580 expectedParts = ["month", "year"]; 581 break; 582 default: 583 return false; // Unsupported format 584 } 585 586 // Match input with regex 587 let match = value.match(regexPattern); 588 if (!match) return false; 589 590 // Extract values dynamically 591 let parts = {}; 592 expectedParts.forEach((part, index) => { 593 parts[part] = parseInt(match[index + 1], 10); 594 }); 595 596 // Validate Date (Day, Month, Year) 597 if (parts.year && (parts.year < 1500 || parts.year > 3000)) return false; 598 if (parts.month && (parts.month < 1 || parts.month > 12)) return false; 599 if (parts.day) { 600 let daysInMonth = new Date(parts.year, parts.month, 0).getDate(); 601 if (parts.day < 1 || parts.day > daysInMonth) return false; 602 } 603 604 // Validate Time (Hour, Minute, Second) 605 if (parts.hour && (parts.hour < 0 || parts.hour >= 24)) return false; 606 if (parts.minute && (parts.minute < 0 || parts.minute >= 60)) 607 return false; 608 if (parts.second && (parts.second < 0 || parts.second >= 60)) 609 return false; 610 611 return true; 612 } 613 614 // ----------------- Expiry Date Validation ----------------- 615 function isValidExpiryDate(value, format) { 616 let regexPattern = 617 format === "MM/YY" ? /^(\d{2})\/(\d{2})$/ : /^(\d{2})\/(\d{4})$/; 618 let match = value.match(regexPattern); 619 620 if (!match) return false; // Invalid format 621 622 let month = parseInt(match[1], 10); 623 let year = parseInt(match[2], 10); 624 625 let currentYear = new Date().getFullYear(); 626 let currentMonth = new Date().getMonth() + 1; // JS months are 0-based 627 628 // Adjust year format if MM/YY (Assume 20XX for past/future dates) 629 if (format === "MM/YY") { 630 year += 2000; 631 } 632 633 // Validate month (01-12) 634 if (month < 1 || month > 12) return false; 635 636 // Validate expiry date (must be in the future) 637 if ( 638 year < currentYear || 639 (year === currentYear && month < currentMonth) 640 ) { 641 return false; 642 } 643 644 return true; 645 } 646 647 // ----------------- Exportable Functions Using Universal Validator ----------------- 648 function isValidDateDMY(dateStr) { 649 return isValidDateTime(dateStr, "DMY"); 650 } 651 function isValidDateMDY(dateStr) { 652 return isValidDateTime(dateStr, "MDY"); 653 } 654 function isValidTimeHMS(timeStr) { 655 return isValidDateTime(timeStr, "HMS"); 656 } 657 function isValidTimeHM(timeStr) { 658 return isValidDateTime(timeStr, "HM"); 659 } 660 function isValidDateDMYHM(dateTimeStr) { 661 return isValidDateTime(dateTimeStr, "DMY-HM"); 662 } 663 function isValidDateMDYHM(dateTimeStr) { 664 return isValidDateTime(dateTimeStr, "MDY-HM"); 665 } 666 function isValidDateMY(dateStr) { 667 return isValidDateTime(dateStr, "MY"); 668 } 669 function isValidExpiryMMYY(dateStr) { 670 return isValidExpiryDate(dateStr, "MM/YY"); 671 } 672 function isValidExpiryMMYYYY(dateStr) { 673 return isValidExpiryDate(dateStr, "MM/YYYY"); 674 } 675 676 // Apply validation for credit card fields 677 function isValidCreditCard(cardNumber) { 678 const cleaned = cardNumber.replace(/\D/g, ""); // Remove non-digits 679 if (cleaned.length < 15 || cleaned.length > 16) return false; // AMEX: 15, Others: 16 680 let sum = 0; 681 let shouldDouble = false;
682 // Luhn Algorithm to validate card number 683 for (let i = cleaned.length - 1; i >= 0; i--) { 684 let digit = parseInt(cleaned.charAt(i), 10); 685 686 if (shouldDouble) { 687 digit *= 2; 688 if (digit > 9) digit -= 9; 689 } 690 691 sum += digit; 692 shouldDouble = !shouldDouble; 693 } 694 return sum % 10 === 0; 695 } 696 697 // Validate CNPJ (Brazilian Business ID - 14 chars, numeric or alphanumeric) 698 function isValidCNPJ(cnpj) { 699 cnpj = cnpj.toUpperCase().replace(/[.\-\/]/g, ''); 700 if (!/^[A-Z0-9]{12}\d{2}$/.test(cnpj)) return false; 701 702 // Eliminate obvious invalid CNPJs (repeated characters) 703 if (/^(.)\1{13}$/.test(cnpj)) return false; 704 705 let calcCheckDigit = (cnpj, length) => { 706 let weights = 707 length === 12 708 ? [5, 4, 3, 2, 9, 8, 7, 6, 5, 4, 3, 2] 709 : [6, 5, 4, 3, 2, 9, 8, 7, 6, 5, 4, 3, 2]; 710 let sum = 0; 711 712 for (let i = 0; i < weights.length; i++) { 713 sum += (cnpj.charCodeAt(i) - 48) * weights[i]; 714 } 715 716 let remainder = sum % 11; 717 return remainder < 2 ? 0 : 11 - remainder; 718 }; 719 720 // Validate both check digits 721 let firstCheck = calcCheckDigit(cnpj, 12); 722 let secondCheck = calcCheckDigit(cnpj.slice(0, 12) + firstCheck, 13); 723 724 return firstCheck === parseInt(cnpj.charAt(12), 10) && secondCheck === parseInt(cnpj.charAt(13), 10); 725 } 726 727 // Validate CPF (Brazilian Individual ID - 11 digits) 728 function isValidCPF(cpf) { 729 cpf = cpf.replace(/\D/g, ""); // Remove non-numeric characters 730 if (cpf.length !== 11) return false; 731 732 // Eliminate obvious invalid CPFs (repeated digits) 733 if (/^(\d)\1+$/.test(cpf)) return false; 734 735 let calculateDigit = (cpf, length) => { 736 let sum = 0; 737 for (let i = 0; i < length; i++) { 738 sum += parseInt(cpf.charAt(i)) * (length + 1 - i); 739 } 740 let result = (sum * 10) % 11; 741 return result === 10 ? 0 : result; 742 }; 743 744 let firstDigit = calculateDigit(cpf, 9); 745 let secondDigit = calculateDigit(cpf, 10); // Now correctly includes first digit in the calculation 746 747 return ( 748 firstDigit === parseInt(cpf.charAt(9)) && 749 secondDigit === parseInt(cpf.charAt(10)) 750 ); 751 } 752 753 // Validate CEP (Brazilian Postal Code - XXXXX-XXX) 754 function isValidCEP(cep) { 755 return /^\d{5}-\d{3}$/.test(cep); // Matches format XXXXX-XXX 756 } 757 758 // Validate IPv4 (Format: 192.168.001.001) 759 function isValidIPv4(ip) { 760 // Ensure correct format (###.###.###.###) 761 const ipv4Pattern = /^(?:\d{1,3}\.){3}\d{1,3}$/; 762 763 if (!ipv4Pattern.test(ip)) return false; // Check general format 764 765 // Split by dots and validate each octet (0-255) 766 const octets = ip.split("."); 767 return octets.every((octet) => { 768 let num = parseInt(octet, 10); // Convert to number 769 return num >= 0 && num <= 255; // Each octet must be between 0-255 770 }); 771 } 772 773 // ----------------- Form Submit ----------------- 774 775 // start - step field mask error handling 776 777 let maskErrorArr = {}; 778 let nextBtnOriginalClicks = {}; 779 let recaptchaEvent = {}; 780 let submitBtnEvent = {}; 781 782 783 const nextbtnVisibility = (errorClass, input, validationFunction) => { 784 785 const closesWidget = input.closest(".elementor-widget-form"); 786 const widgetId = closesWidget.data('id'); 787 const inuptId = input.attr('id'); 788 const fieldStep=input.closest(".elementor-field-type-step"); 789 const form = input.closest(".elementor-form"); 790 791 const currectStepFields = form.find(".elementor-form-fields-wrapper.elementor-labels-above").children("div:not(.elementor-hidden)").find('input, textarea, select'); 792 793 const submtBtnTag = form.find("button[type='submit']"); 794 795 if (!closesWidget.length || !fieldStep.length) { 796 return; 797 } 798 799 const nextBtn = fieldStep.find(".e-form__buttons__wrapper__button[data-direction='next']"); 800 801 802 803 if ((nextBtn.length == 0 && submtBtnTag.length > 0 ) && (submtBtnTag.length == 0 && nextBtn.length == 0) ) { 804 return; 805 } 806 807 808 809 let val = input.val(); 810 811 if (val.length === 1 && !/\d/.test(val)) { 812 val = ''; 813 } 814 815 // Show error message if validation fails 816 if ( val !== "" && !validationFunction(val)) { 817 818 819 if (closesWidget.length > 0) { 820 821 822 if (!maskErrorArr[widgetId]) { 823 maskErrorArr[widgetId] = []; 824 } 825 826 if (!maskErrorArr[widgetId].includes(inuptId)) { 827 maskErrorArr[widgetId].push(inuptId); 828 } 829 } 830 831 if (!nextBtnOriginalClicks[widgetId] || !nextBtnOriginalClicks[widgetId].length) { 832 833 834 835 if(nextBtn.length > 0){ 836 837 const origninalClicks = jQuery._data(nextBtn[0], "events"); 838 839 if (origninalClicks && (!nextBtnOriginalClicks[widgetId] || !nextBtnOriginalClicks[widgetId].length === 0)) { 840 nextBtnOriginalClicks[widgetId] = origninalClicks && origninalClicks.click ? origninalClicks.click.map(h => h.handler) : []; 841 842 } 843 } 844 845 846 } 847 } else { 848 if (maskErrorArr[widgetId] && maskErrorArr[widgetId].includes(inuptId)) { 849 maskErrorArr[widgetId] = maskErrorArr[widgetId].filter(item => item !== inuptId); 850 } 851 } 852 853 854 if (maskErrorArr[widgetId] && maskErrorArr[widgetId].length > 0) { 855 856 if(nextBtn.length > 0){ 857 858 const origninalClicks = jQuery._data(nextBtn[0], "events"); 859 860 if (origninalClicks && (!nextBtnOriginalClicks[widgetId] || !nextBtnOriginalClicks[widgetId].length)) { 861 862 863 nextBtnOriginalClicks[widgetId] = origninalClicks && origninalClicks.click ? origninalClicks.click.map(h => h.handler) : []; 864 865 } 866 867 if (nextBtnOriginalClicks[widgetId] && nextBtnOriginalClicks[widgetId].length > 0) { 868 nextBtn.off("click"); 869 } 870 871 872 } 873 874 875 876 } else { 877 878 879 // when no error in mask validation 880 881 if(recaptchaEvent[widgetId]){ 882 submtBtnTag.on("click", recaptchaEvent[widgetId]); 883 } 884 885 if(submitBtnEvent[widgetId]){ 886 form.on("submit", submitBtnEvent[widgetId]); 887 } 888 889 890 891 if (nextBtnOriginalClicks[widgetId] && nextBtnOriginalClicks[widgetId].length > 0) { 892 893 894 895 896 let isfieldsValid = true 897 898 for (let i = 0; i < currectStepFields.length; i++) { 899 900 901 if (currectStepFields[i].checkValidity() == false) { 902 903 isfieldsValid = false;
904 break; 905 906 } 907 908 } 909 910 if (isfieldsValid) { 911 912 913 914 915 916 if(nextBtn.length > 0){ 917 918 // 2ï¸â£ First, clear existing handlers to avoid duplication 919 nextBtn.off("click"); 920 921 // Re-attach original click handlers only once 922 nextBtnOriginalClicks[widgetId].forEach(fn => { 923 nextBtn.one("click", fn); // use .one instead of .on 924 }); 925 } 926 927 928 } 929 930 } 931 932 } 933 } 934 935 936 937 938 // handle next button event before click 939 940 $(document).on("mousedown", ".e-form__buttons__wrapper__button[data-direction='next']", function(e){ 941 942 const form = $(this).closest(".elementor-form"); 943 944 const mask_error_div = form.find(".elementor-form-fields-wrapper.elementor-labels-above").children("div:not(.elementor-hidden)").find('div.mask-error'); 945 946 const closesWidget = $(this).closest(".elementor-widget-form"); 947 const widgetId = closesWidget.data('id'); 948 949 let mask_error = false; 950 951 // finding mask error 952 for(let i=0; i<mask_error_div.length; i++){ 953 if(mask_error_div[i].value != "" && mask_error_div[i].style.display == 'flex'){ 954 mask_error = true; 955 break; 956 } 957 } 958 959 // reattcach next button event when no mask error 960 961 if (maskErrorArr[widgetId] && !maskErrorArr[widgetId].length > 0 && !mask_error) { 962 // Re-attach original click handlers only once 963 964 if (nextBtnOriginalClicks[widgetId] && nextBtnOriginalClicks[widgetId].length > 0) { 965 $(this).off("click"); 966 967 nextBtnOriginalClicks[widgetId].forEach(fn => { 968 $(this).one("click", fn); // use .one instead of .on 969 }); 970 } 971 972 973 } 974 975 }) 976 977 978 // handling next button event when previous button click 979 980 $(document).on("click", ".e-form__buttons__wrapper__button[data-direction='previous']", function(e){ 981 982 const form = $(this).closest(".elementor-form"); 983 984 const closesWidget = form.closest(".elementor-widget-form"); 985 const widgetId = closesWidget.data('id'); 986 maskErrorArr[widgetId] = []; 987 988 const currectStepFields = form.find(".elementor-form-fields-wrapper.elementor-labels-above").children("div:not(.elementor-hidden)") 989 990 const nextBtn = currectStepFields.find(".e-form__buttons__wrapper__button[data-direction='next']"); 991 992 // attaching events to next button 993 994 if (nextBtnOriginalClicks[widgetId] && nextBtnOriginalClicks[widgetId].length > 0) { 995 nextBtn.off("click"); 996 997 nextBtnOriginalClicks[widgetId].forEach(fn => { 998 nextBtn.one("click", fn); // use .one instead of .on 999 }); 1000 1001 } 1002 1003 }) 1004 1005 // handle mask validation on submit button of step field form 1006 $(document).on("mousedown", ".elementor-field-type-submit", function (e){ 1007 1008 var $submitBtn = $(this); 1009 1010 var $form = $submitBtn.closest("form"); 1011 1012 const closesWidget = $form.closest(".elementor-widget-form"); 1013 const widgetId = closesWidget.data('id'); 1014 1015 const currectStepFields = $form.find(".elementor-form-fields-wrapper.elementor-labels-above").children("div:not(.elementor-hidden)") 1016 1017 const previousBtn = currectStepFields.find(".e-form__buttons__wrapper__button[data-direction='previous']"); 1018 1019 const inputMaskFields = currectStepFields.find("input.fme-mask-input"); 1020 1021 // run only first time when click on submit button 1022 // if maskerror found in the form widget this will remove submit button and form events to prevent submit 1023 1024 1025 if(previousBtn && previousBtn.length && inputMaskFields && inputMaskFields.length && maskErrorArr[widgetId] && maskErrorArr[widgetId].length){ 1026 1027 var $subBtnTag = $submitBtn.find("button"); 1028 1029 1030 // getting submit button recaptcha and click events 1031 1032 var $form = $submitBtn.closest("form"); 1033 1034 const origninalclick = jQuery._data($subBtnTag[0], "events"); 1035 1036 1037 if(origninalclick && origninalclick.click){ 1038 1039 origninalclick.click.forEach((ele) => { 1040 if(ele.handler.toString().trim().includes("onV3FormSubmit")){ 1041 1042 if(!recaptchaEvent[widgetId]){ 1043 1044 recaptchaEvent[widgetId] = ele.handler; 1045 1046 } 1047 1048 } 1049 }) 1050 1051 } 1052 1053 // getting form apply step events 1054 1055 const origninalSubmit = jQuery._data($form[0], "events"); 1056 1057 if(origninalSubmit && origninalSubmit.submit){ 1058 1059 origninalSubmit.submit.forEach((ele) => { 1060 if(ele.handler.toString().trim().includes("resetForm")){ 1061 1062 if(!submitBtnEvent[widgetId]){ 1063 1064 submitBtnEvent[widgetId] = ele.handler; 1065 1066 } 1067 1068 } 1069 }) 1070 } 1071 1072 if(submitBtnEvent[widgetId]){ 1073 // removing form apply step event 1074 $form.off("submit", submitBtnEvent[widgetId]); 1075 } 1076 1077 1078 if(recaptchaEvent[widgetId]){ 1079 // removing submit button click events 1080 $subBtnTag.off("click", recaptchaEvent[widgetId]); 1081 } 1082 1083 1084 } 1085 }) 1086 1087 // end - step field mask error handling 1088 1089 1090 // handling form submit of mask field in elementor form 1091 1092 $(document).on("click", ".elementor-field-type-submit", function (e) { 1093 1094 var $submitBtn = $(this); 1095 1096 if ($submitBtn.find('button').hasClass('cfkef-prevent-submit') || $submitBtn.find('button').hasClass('confirmation-pending') 1097 ) { 1098 return 1099 } 1100 1101 var $form = $submitBtn.closest("form"); 1102 1103 const closesWidget = $form.closest(".elementor-widget-form"); 1104 const widgetId = closesWidget.data('id'); 1105 const submtBtnTag = $form.find("button[type='submit']"); 1106 1107 // Prevent double-clicks 1108 if ($submitBtn.data("clicked")) { 1109 e.preventDefault(); 1110 return; 1111 } 1112 $submitBtn.data("clicked", true); // Mark as clicked 1113
1114 // Trigger blur on inputs to ensure validation runs 1115 $form.find("input").trigger("blur"); 1116 1117 // Add Elementor waiting class 1118 $form[0].classList.add("elementor-form-waiting"); 1119 1120 // Wait for mask errors or blur logic to complete 1121 setTimeout(() => { 1122 let hasVisibleMaskError = false; 1123 1124 // Check for visible mask error messages 1125 const $errors = $form.find(".mask-error").filter(function () { 1126 return $(this).text().trim() !== "" && $(this).css("display") == "flex"; 1127 }); 1128 1129 if ($errors.length > 0) { 1130 hasVisibleMaskError = true; 1131 const $firstError = $errors.first(); 1132 $("html, body").animate({ 1133 scrollTop: $firstError.offset().top - 200 1134 }, 300); 1135 } 1136 1137 // â Check for empty required masked fields 1138 const $emptyRequiredMasked = $form.find("input[required]").filter(function () { 1139 if(!$(this).hasClass('hide-fme-mask-input')){ 1140 const val = $(this).val().trim(); 1141 const isVisible = $(this).css("display") == "flex"; 1142 return (val === "" || /^[\s_\-\(\)\.:/]+$/.test(val)); 1143 } 1144 }); 1145 1146 if ($emptyRequiredMasked.length > 0) { 1147 hasVisibleMaskError = true; 1148 const $firstEmpty = $emptyRequiredMasked.first(); 1149 $("html, body").animate({ 1150 scrollTop: $firstEmpty.offset().top - 200 1151 }, 300); 1152 $firstEmpty.focus(); 1153 } 1154 1155 // â Validation failed 1156 if (hasVisibleMaskError) { 1157 // $form[0].classList.remove("elementor-form-waiting"); 1158 $submitBtn.data("clicked", false); 1159 e.preventDefault(); 1160 return; 1161 } 1162 1163 if (!hasVisibleMaskError) { 1164 // â All good â submit the form 1165 1166 1167 if(recaptchaEvent[widgetId]){ 1168 submtBtnTag.on("click", recaptchaEvent[widgetId]); 1169 submtBtnTag.trigger("click"); 1170 } 1171 1172 if(submitBtnEvent[widgetId]){ 1173 $form.on("submit", submitBtnEvent[widgetId]); 1174 submtBtnTag.trigger("click"); 1175 1176 } 1177 1178 $form[0].classList.remove("elementor-form-waiting"); 1179 $submitBtn.data("clicked", false); 1180 if(!recaptchaEvent[widgetId]){ 1181 1182 let error_messages = $form.find('.elementor-form-fields-wrapper').find('.elementor-message'); 1183 1184 if(error_messages && error_messages.length == 0){ 1185 1186 $form[0].requestSubmit(); 1187 } 1188 1189 1190 } 1191 } 1192 1193 }, 500); 1194 }); 1195 }); 1196 1197 1198})(jQuery);
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.