1/* global wpforms_settings, grecaptcha, wpformsRecaptchaCallback, wpforms_validate, wpforms_datepicker, wpforms_timepicker, Mailcheck */ 2 3'use strict'; 4 5var wpforms = window.wpforms || ( function( document, window, $ ) { 6 7 var app = { 8 9 /** 10 * Start the engine. 11 * 12 * @since 1.2.3 13 */ 14 init: function() { 15 16 // Document ready. 17 $( document ).ready( app.ready ); 18 19 // Page load. 20 $( window ).on( 'load', app.load ); 21 22 app.bindUIActions(); 23 app.bindOptinMonster(); 24 }, 25 26 /** 27 * Document ready. 28 * 29 * @since 1.2.3 30 */ 31 ready: function() { 32 33 // Clear URL - remove wpforms_form_id. 34 app.clearUrlQuery(); 35 36 // Set user identifier. 37 app.setUserIndentifier(); 38 39 app.loadValidation(); 40 app.loadDatePicker(); 41 app.loadTimePicker(); 42 app.loadInputMask(); 43 app.loadSmartPhoneField(); 44 app.loadPayments(); 45 app.loadMailcheck(); 46 47 // Randomize elements. 48 $( '.wpforms-randomize' ).each( function() { 49 var $list = $( this ), 50 $listItems = $list.children(); 51 while ( $listItems.length ) { 52 $list.append( $listItems.splice( Math.floor( Math.random() * $listItems.length ), 1 )[0] ); 53 } 54 } ); 55 56 $( document ).trigger( 'wpformsReady' ); 57 }, 58 59 /** 60 * Page load. 61 * 62 * @since 1.2.3 63 */ 64 load: function() { 65 66 }, 67 68 //--------------------------------------------------------------------// 69 // Initializing 70 //--------------------------------------------------------------------// 71 72 /** 73 * Remove wpforms_form_id from URL. 74 * 75 * @since 1.5.2 76 */ 77 clearUrlQuery: function() { 78 var loc = window.location, 79 query = loc.search; 80 81 if ( query.indexOf( 'wpforms_form_id=' ) !== -1 ) { 82 query = query.replace( /([&?]wpforms_form_id=[0-9]*$|wpforms_form_id=[0-9]*&|[?&]wpforms_form_id=[0-9]*(?=#))/, '' ); 83 history.replaceState( {}, null, loc.origin + loc.pathname + query ); 84 } 85 }, 86 87 88 /** 89 * Load jQuery Validation. 90 * 91 * @since 1.2.3 92 */ 93 loadValidation: function() { 94 95 // Only load if jQuery validation library exists. 96 if ( typeof $.fn.validate !== 'undefined' ) { 97 98 // jQuery Validation library will not correctly validate 99 // fields that do not have a name attribute, so we use the 100 // `wpforms-input-temp-name` class to add a temporary name 101 // attribute before validation is initialized, then remove it 102 // before the form submits. 103 $( '.wpforms-input-temp-name' ).each( function( index, el ) { 104 var random = Math.floor( Math.random() * 9999 ) + 1; 105 $( this ).attr( 'name', 'wpf-temp-' + random ); 106 } ); 107 108 // Prepend URL field contents with http:// if user input doesn't contain a schema. 109 $( '.wpforms-validate input[type=url]' ).change( function() { 110 var url = $( this ).val(); 111 if ( ! url ) { 112 return false; 113 } 114 if ( url.substr( 0, 7 ) !== 'http://' && url.substr( 0, 8 ) !== 'https://' ) { 115 $( this ).val( 'http://' + url ); 116 } 117 } ); 118 119 $.validator.messages.required = wpforms_settings.val_required; 120 $.validator.messages.url = wpforms_settings.val_url; 121 $.validator.messages.email = wpforms_settings.val_email; 122 $.validator.messages.number = wpforms_settings.val_number; 123 124 // Payments: Validate method for Credit Card Number. 125 if ( typeof $.fn.payment !== 'undefined' ) { 126 $.validator.addMethod( 'creditcard', function( value, element ) { 127 128 //var type = $.payment.cardType(value); 129 var valid = $.payment.validateCardNumber( value ); 130 return this.optional( element ) || valid; 131 }, wpforms_settings.val_creditcard ); 132 133 // @todo validate CVC and expiration 134 } 135 136 // Validate method for file extensions. 137 $.validator.addMethod( 'extension', function( value, element, param ) { 138 param = 'string' === typeof param ? param.replace( /,/g, '|' ) : 'png|jpe?g|gif'; 139 return this.optional( element ) || value.match( new RegExp( '\\.(' + param + ')$', 'i' ) ); 140 }, wpforms_settings.val_fileextension ); 141 142 // Validate method for file size. 143 $.validator.addMethod( 'maxsize', function( value, element, param ) { 144 var maxSize = param, 145 optionalValue = this.optional( element ), 146 i, len, file; 147 if ( optionalValue ) { 148 return optionalValue; 149 } 150 if ( element.files && element.files.length ) { 151 i = 0; 152 len = element.files.length; 153 for ( ; i < len; i++ ) { 154 file = element.files[i]; 155 if ( file.size > maxSize ) { 156 return false;
157 } 158 } 159 } 160 return true; 161 }, wpforms_settings.val_filesize ); 162 163 // Validate email addresses. 164 $.validator.methods.email = function( value, element ) { 165 return this.optional( element ) || /^[a-z0-9.!#$%&'*+\/=?^_`{|}~-]+@((?=[a-z0-9-]{1,63}\.)(xn--)?[a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{2,63}$/i.test( value ); 166 }; 167 168 // Validate confirmations. 169 $.validator.addMethod( 'confirm', function( value, element, param ) { 170 return $.validator.methods.equalTo.call( this, value, element, param ); 171 }, wpforms_settings.val_confirm ); 172 173 // Validate required payments. 174 $.validator.addMethod( 'required-payment', function( value, element ) { 175 return app.amountSanitize( value ) > 0; 176 }, wpforms_settings.val_requiredpayment ); 177 178 // Validate 12-hour time. 179 $.validator.addMethod( 'time12h', function( value, element ) { 180 return this.optional( element ) || /^((0?[1-9]|1[012])(:[0-5]\d){1,2}(\ ?[AP]M))$/i.test( value ); 181 }, wpforms_settings.val_time12h ); 182 183 // Validate 24-hour time. 184 $.validator.addMethod( 'time24h', function( value, element ) { 185 return this.optional( element ) || /^(([0-1]?[0-9])|([2][0-3])):([0-5]?[0-9])(\ ?[AP]M)?$/i.test( value ); 186 }, wpforms_settings.val_time24h ); 187 188 // Validate checkbox choice limit. 189 $.validator.addMethod( 'check-limit', function( value, element ) { 190 var $ul = $( element ).closest( 'ul' ), 191 $checked = $ul.find( 'input[type="checkbox"]:checked' ), 192 choiceLimit = parseInt( $ul.attr( 'data-choice-limit' ) || 0, 10 ); 193 194 if ( 0 === choiceLimit ) { 195 return true; 196 } 197 return $checked.length <= choiceLimit; 198 }, function( params, element ) { 199 var choiceLimit = parseInt( $( element ).closest( 'ul' ).attr( 'data-choice-limit' ) || 0, 10 ); 200 return wpforms_settings.val_checklimit.replace( '{#}', choiceLimit ); 201 } ); 202 203 // Validate Smart Phone Field. 204 if ( typeof $.fn.intlTelInput !== 'undefined' ) { 205 $.validator.addMethod( 'smart-phone-field', function( value, element ) { 206 if ( value.match( /[^\d()\-+\s]/ ) ) { 207 return false; 208 } 209 return this.optional( element ) || $( element ).intlTelInput( 'isValidNumber' ); 210 }, wpforms_settings.val_phone ); 211 } 212 213 // Validate US Phone Field. 214 $.validator.addMethod( 'us-phone-field', function( value, element ) { 215 if ( value.match( /[^\d()\-+\s]/ ) ) { 216 return false; 217 } 218 return this.optional( element ) || value.replace( /[^\d]/g, '' ).length === 10; 219 }, wpforms_settings.val_phone ); 220 221 // Validate International Phone Field. 222 $.validator.addMethod( 'int-phone-field', function( value, element ) { 223 if ( value.match( /[^\d()\-+\s]/ ) ) { 224 return false; 225 } 226 return this.optional( element ) || value.replace( /[^\d]/g, '' ).length > 0; 227 }, wpforms_settings.val_phone ); 228 229 // Finally load jQuery Validation library for our forms. 230 $( '.wpforms-validate' ).each( function() { 231 var form = $( this ), 232 formID = form.data( 'formid' ), 233 properties; 234 235 // TODO: cleanup this BC with wpforms_validate. 236 if ( typeof window['wpforms_' + formID] !== 'undefined' && window['wpforms_' + formID].hasOwnProperty( 'validate' ) ) { 237 properties = window['wpforms_' + formID].validate; 238 } else if ( typeof wpforms_validate !== 'undefined' ) { 239 properties = wpforms_validate; 240 } else { 241 properties = { 242 errorClass: 'wpforms-error', 243 validClass: 'wpforms-valid', 244 errorPlacement: function( error, element ) { 245 if ( 'radio' === element.attr( 'type' ) || 'checkbox' === element.attr( 'type' ) ) { 246 if ( element.hasClass( 'wpforms-likert-scale-option' ) ) { 247 if ( element.closest( 'table' ).hasClass( 'single-row' ) ) { 248 element.closest( 'table' ).after( error ); 249 } else { 250 element.closest( 'tr' ).find( 'th' ).append( error ); 251 } 252 } else if ( element.hasClass( 'wpforms-net-promoter-score-option' ) ) { 253 element.closest( 'table' ).after( error ); 254 } else { 255 element.closest( '.wpforms-field-checkbox' ).find( 'label.wpforms-error' ).remove(); 256 element.parent().parent().parent().append( error ); 257 } 258 } else if ( element.is( 'select' ) && element.attr( 'class' ).match( /date-month|date-day|date-year/ ) ) { 259 if ( 0 === element.parent().find( 'label.wpforms-error:visible' ).length ) { 260 element.parent().find( 'select:last' ).after( error ); 261 } 262 } else if ( element.hasClass( 'wpforms-smart-phone-field' ) ) { 263 element.parent().after( error ); 264 } else if ( element.hasClass( 'wpforms-validation-group-member' ) ) { 265 element.closest( '.wpforms-field' ).append( error ); 266 } else { 267 error.insertAfter( element ); 268 } 269 }, 270 highlight: function( element, errorClass, validClass ) { 271 var $element = $( element ), 272 $field = $element.closest( '.wpforms-field' ), 273 inputName = $element.attr( 'name' ); 274 if ( 'radio' === $element.attr( 'type' ) || 'checkbox' === $element.attr( 'type' ) ) { 275 $field.find( 'input[name="' + inputName + '"]' ).addClass( errorClass ).removeClass( validClass ); 276 } else { 277 $element.addClass( errorClass ).removeClass( validClass ); 278 } 279 $field.addClass( 'wpforms-has-error' ); 280 }, 281 unhighlight: function( element, errorClass, validClass ) { 282 var $element = $( element ), 283 $field = $element.closest( '.wpforms-field' ), 284 inputName = $element.attr( 'name' ); 285 if ( 'radio' === $element.attr( 'type' ) || 'checkbox' === $element.attr( 'type' ) ) { 286 $field.find( 'input[name="' + inputName + '"]' ).addClass( validClass ).removeClass( errorClass ); 287 } else { 288 $element.addClass( validClass ).removeClass( errorClass ); 289 } 290 $field.removeClass( 'wpforms-has-error' ); 291 }, 292 submitHandler: function( form ) { 293 294 var $form = $( form ), 295 $submit = $form.find( '.wpforms-submit' ), 296 altText = $submit.data( 'alt-text' ), 297 recaptchaID = $submit.get( 0 ).recaptchaID; 298 299 $submit.prop( 'disabled', true ); 300 $form.find( '#wpforms-field_recaptcha-error' ).remove(); 301 302 if ( ! app.empty( recaptchaID ) || recaptchaID === 0 ) { 303 304 // Form contains invisible reCAPTCHA. 305 grecaptcha.execute( recaptchaID ).then( null, function( reason ) { 306 307 reason = ( null === reason ) ? '' : '<br>' + reason; 308 $form.find( '.wpforms-recaptcha-container' ).append( '<label id="wpforms-field_recaptcha-error" class="wpforms-error"> ' + wpforms_settings.val_recaptcha_fail_msg + reason + '</label>' ); 309 $submit.prop( 'disabled', false ); 310 } ); 311 return false;
312 } 313 314 // Normal form. 315 if ( altText ) { 316 $submit.text( altText ); 317 } 318 319 // Remove name attributes if needed. 320 $( '.wpforms-input-temp-name' ).removeAttr( 'name' ); 321 322 app.formSubmit( $form ); 323 }, 324 invalidHandler: function( event, validator ) { 325 326 if ( typeof validator.errorList[0] !== 'undefined' ) { 327 app.scrollToError( $( validator.errorList[0].element ) ); 328 } 329 }, 330 onkeyup: function( element, event ) { 331 332 // This code is copied from JQuery Validate 'onkeyup' method with only one change: 'wpforms-novalidate-onkeyup' class check. 333 var excludedKeys = [ 16, 17, 18, 20, 35, 36, 37, 38, 39, 40, 45, 144, 225 ]; 334 335 if ( $( element ).hasClass( 'wpforms-novalidate-onkeyup' ) ) { 336 return; // Disable onkeyup validation for some elements (e.g. remote calls). 337 } 338 339 if ( 9 === event.which && '' === this.elementValue( element ) || $.inArray( event.keyCode, excludedKeys ) !== -1 ) { 340 return; 341 } else if ( element.name in this.submitted || element.name in this.invalid ) { 342 this.element( element ); 343 } 344 }, 345 onfocusout: function( element ) { 346 347 // This code is copied from JQuery Validate 'onfocusout' method with only one change: 'wpforms-novalidate-onkeyup' class check. 348 var validate = false; 349 350 if ( $( element ).hasClass( 'wpforms-novalidate-onkeyup' ) && ! element.value ) { 351 validate = true; // Empty value error handling for elements with onkeyup validation disabled. 352 } 353 354 if ( ! this.checkable( element ) && ( element.name in this.submitted || ! this.optional( element ) ) ) { 355 validate = true; 356 } 357 358 if ( validate ) { 359 this.element( element ); 360 } 361 }, 362 onclick: function( element ) { 363 var validate = false, 364 type = ( element || {} ).type, 365 $el = $( element ); 366 367 if ( [ 'checkbox', 'radio' ].indexOf( type ) > -1 ) { 368 if ( $el.hasClass( 'wpforms-likert-scale-option' ) ) { 369 $el = $el.closest( 'tr' ); 370 } else { 371 $el = $el.closest( '.wpforms-field' ); 372 } 373 $el.find( 'label.wpforms-error' ).remove(); 374 validate = true; 375 } 376 377 if ( validate ) { 378 this.element( element ); 379 } 380 }, 381 }; 382 } 383 form.validate( properties ); 384 } ); 385 } 386 }, 387 388 /** 389 * Load jQuery Date Picker. 390 * 391 * @since 1.2.3 392 */ 393 loadDatePicker: function() { 394 395 // Only load if jQuery datepicker library exists. 396 if ( typeof $.fn.flatpickr !== 'undefined' ) { 397 $( '.wpforms-datepicker-wrap' ).each( function() { 398 var element = $( this ), 399 form = element.closest( '.wpforms-form' ), 400 formID = form.data( 'formid' ), 401 fieldID = element.closest( '.wpforms-field' ).data( 'field-id' ), 402 properties; 403 404 if ( typeof window['wpforms_' + formID + '_' + fieldID] !== 'undefined' && window['wpforms_' + formID + '_' + fieldID].hasOwnProperty( 'datepicker' ) ) { 405 properties = window['wpforms_' + formID + '_' + fieldID].datepicker; 406 } else if ( typeof window['wpforms_' + formID] !== 'undefined' && window['wpforms_' + formID].hasOwnProperty( 'datepicker' ) ) { 407 properties = window['wpforms_' + formID].datepicker; 408 } else if ( typeof wpforms_datepicker !== 'undefined' ) { 409 properties = wpforms_datepicker; 410 } else { 411 properties = { 412 disableMobile: true, 413 }; 414 } 415 416 // Redefine locale only if user doesn't do that manually and we have the locale. 417 if ( 418 ! properties.hasOwnProperty( 'locale' ) && 419 typeof wpforms_settings !== 'undefined' && 420 wpforms_settings.hasOwnProperty( 'locale' ) 421 ) { 422 properties.locale = wpforms_settings.locale; 423 } 424 425 properties.wrap = true; 426 properties.dateFormat = element.find( 'input' ).data( 'date-format' ); 427 428 // Toggle clear date icon. 429 properties.onChange = function( selectedDates, dateStr, instance ) { 430 431 var display = dateStr === '' ? 'none' : 'block'; 432 element.find( '.wpforms-datepicker-clear' ).css( 'display', display ); 433 }; 434 435 element.flatpickr( properties ); 436 } ); 437 } 438 }, 439 440 /** 441 * Load jQuery Time Picker. 442 * 443 * @since 1.2.3 444 */ 445 loadTimePicker: function() { 446 447 // Only load if jQuery timepicker library exists. 448 if ( typeof $.fn.timepicker !== 'undefined' ) { 449 $( '.wpforms-timepicker' ).each( function() { 450 var element = $( this ), 451 form = element.closest( '.wpforms-form' ), 452 formID = form.data( 'formid' ), 453 fieldID = element.closest( '.wpforms-field' ).data( 'field-id' ), 454 properties; 455 456 if ( 457 typeof window['wpforms_' + formID + '_' + fieldID] !== 'undefined' && 458 window['wpforms_' + formID + '_' + fieldID].hasOwnProperty( 't
458imepicker' ) 459 ) { 460 properties = window['wpforms_' + formID + '_' + fieldID].timepicker; 461 } else if ( 462 typeof window['wpforms_' + formID] !== 'undefined' && 463 window['wpforms_' + formID].hasOwnProperty( 'timepicker' ) 464 ) { 465 properties = window['wpforms_' + formID].timepicker; 466 } else if ( typeof wpforms_timepicker !== 'undefined' ) { 467 properties = wpforms_timepicker; 468 } else { 469 properties = { 470 scrollDefault: 'now', 471 forceRoundTime: true, 472 }; 473 } 474 475 element.timepicker( properties ); 476 } ); 477 } 478 }, 479 480 /** 481 * Load jQuery input masks. 482 * 483 * @since 1.2.3 484 */ 485 loadInputMask: function() { 486 487 // Only load if jQuery input mask library exists. 488 if ( typeof $.fn.inputmask === 'undefined' ) { 489 return; 490 } 491 492 $( '.wpforms-masked-input' ).inputmask(); 493 }, 494 495 /** 496 * Load smart phone field. 497 * 498 * @since 1.5.2 499 */ 500 loadSmartPhoneField: function() { 501 502 // Only load if library exists. 503 if ( typeof $.fn.intlTelInput === 'undefined' ) { 504 return; 505 } 506 507 var inputOptions = {}; 508 509 // Determine the country by IP if no GDPR restrictions enabled. 510 if ( ! wpforms_settings.gdpr ) { 511 inputOptions.geoIpLookup = app.currentIpToCountry; 512 } 513 514 // Try to kick in an alternative solution if GDPR restrictions are enabled. 515 if ( wpforms_settings.gdpr ) { 516 var lang = this.getFirstBrowserLanguage(), 517 countryCode = lang.indexOf( '-' ) > -1 ? lang.split( '-' ).pop() : ''; 518 } 519 520 // Make sure the library recognizes browser country code to avoid console error. 521 if ( countryCode ) { 522 var countryData = window.intlTelInputGlobals.getCountryData(); 523 524 countryData = countryData.filter( function( country ) { 525 return country.iso2 === countryCode.toLowerCase(); 526 } ); 527 countryCode = countryData.length ? countryCode : ''; 528 } 529 530 // Set default country. 531 inputOptions.initialCountry = wpforms_settings.gdpr && countryCode ? countryCode : 'auto'; 532 533 $( '.wpforms-smart-phone-field' ).each( function( i, el ) { 534 535 var $el = $( el ); 536 537 // Hidden input allows to include country code into submitted data. 538 inputOptions.hiddenInput = $el.closest( '.wpforms-field-phone' ).data( 'field-id' ); 539 inputOptions.utilsScript = wpforms_settings.wpforms_plugin_url + 'pro/assets/js/vendor/jquery.intl-tel-input-utils.js'; 540 541 $el.intlTelInput( inputOptions ); 542 543 // Remove original input name not to interfere with a hidden input. 544 $el.removeAttr( 'name' ); 545 546 // Instantly update a hidden form input with a correct data. 547 // Previously "blur" only was used, which is broken in case Enter was used to submit the form. 548 $el.on( 'blur input', function() { 549 if ( $el.intlTelInput( 'isValidNumber' ) || ! app.empty( window.WPFormsEditEntry ) ) { 550 $el.siblings( 'input[type="hidden"]' ).val( $el.intlTelInput( 'getNumber' ) ); 551 } 552 } ); 553 } ); 554 }, 555 556 /** 557 * Payments: Do various payment-related tasks on load. 558 * 559 * @since 1.2.6 560 */ 561 loadPayments: function() { 562 563 // Update Total field(s) with latest calculation. 564 $( '.wpforms-payment-total' ).each( function( index, el ) { 565 app.amountTotal( this ); 566 } ); 567 568 // Credit card validation. 569 if ( typeof $.fn.payment !== 'undefined' ) { 570 $( '.wpforms-field-credit-card-cardnumber' ).payment( 'formatCardNumber' ); 571 $( '.wpforms-field-credit-card-cardcvc' ).payment( 'formatCardCVC' ); 572 } 573 }, 574 575 /** 576 * Load mailcheck. 577 * 578 * @since 1.5.3 579 */ 580 loadMailcheck: function() { 581 582 // Skip loading if `wpforms_mailcheck_enabled` filter return false. 583 if ( ! wpforms_settings.mailcheck_enabled ) { 584 return; 585 } 586 587 // Only load if library exists. 588 if ( typeof $.fn.mailcheck === 'undefined' ) { 589 return; 590 } 591 592 if ( wpforms_settings.mailcheck_domains.length > 0 ) { 593 Mailcheck.defaultDomains = Mailcheck.defaultDomains.concat( wpforms_settings.mailcheck_domains ); 594 } 595 if ( wpforms_settings.mailcheck_toplevel_domains.length > 0 ) { 596 Mailcheck.defaultTopLevelDomains = Mailcheck.defaultTopLevelDomains.concat( wpforms_settings.mailcheck_toplevel_domains ); 597 } 598
599 // Mailcheck suggestion. 600 $( document ).on( 'blur', '.wpforms-field-email input', function() { 601 var $t = $( this ), 602 id = $t.attr( 'id' ); 603 604 $t.mailcheck( { 605 suggested: function( el, suggestion ) { 606 $( '#' + id + '_suggestion' ).remove(); 607 var sugg = '<a href="#" class="mailcheck-suggestion" data-id="' + id + '" title="' + wpforms_settings.val_email_suggestion_title + '">' + suggestion.full + '</a>'; 608 sugg = wpforms_settings.val_email_suggestion.replace( '{suggestion}', sugg ); 609 $( el ).after( '<label class="wpforms-error mailcheck-error" id="' + id + '_suggestion">' + sugg + '</label>' ); 610 }, 611 empty: function() { 612 $( '#' + id + '_suggestion' ).remove(); 613 }, 614 } ); 615 } ); 616 617 // Apply Mailcheck suggestion. 618 $( document ).on( 'click', '.wpforms-field-email .mailcheck-suggestion', function( e ) { 619 var $t = $( this ), 620 id = $t.attr( 'data-id' ); 621 e.preventDefault(); 622 $( '#' + id ).val( $t.text() ); 623 $t.parent().remove(); 624 } ); 625 626 }, 627 628 //--------------------------------------------------------------------// 629 // Binds. 630 //--------------------------------------------------------------------// 631 632 /** 633 * Element bindings. 634 * 635 * @since 1.2.3 636 */ 637 bindUIActions: function() { 638 639 // Pagebreak navigation. 640 $( document ).on( 'click', '.wpforms-page-button', function( event ) { 641 event.preventDefault(); 642 app.pagebreakNav( this ); 643 } ); 644 645 // Payments: Update Total field(s) when latest calculation. 646 $( document ).on( 'change input', '.wpforms-payment-price', function() { 647 app.amountTotal( this, true ); 648 } ); 649 650 // Payments: Restrict user input payment fields. 651 $( document ).on( 'input', '.wpforms-payment-user-input', function() { 652 var $this = $( this ), 653 amount = $this.val(); 654 $this.val( amount.replace( /[^0-9.,]/g, '' ) ); 655 } ); 656 657 // Payments: Sanitize/format user input amounts. 658 $( document ).on( 'focusout', '.wpforms-payment-user-input', function() { 659 var $this = $( this ), 660 amount = $this.val(), 661 sanitized = app.amountSanitize( amount ), 662 formatted = app.amountFormat( sanitized ); 663 $this.val( formatted ); 664 } ); 665 666 // Payments: Update Total field(s) when conditials are processed. 667 $( document ).on( 'wpformsProcessConditionals', function( e, el ) { 668 app.amountTotal( el, true ); 669 } ); 670 671 // Payment radio/checkbox fields: preselect the selected payment (from dynamic/fallback population). 672 $( document ).ready( function() { 673 674 // Radios. 675 $( '.wpforms-field-radio .wpforms-image-choices-item input:checked' ).change(); 676 $( '.wpforms-field-payment-multiple .wpforms-image-choices-item input:checked' ).change(); 677 678 // Checkboxes. 679 $( '.wpforms-field-checkbox .wpforms-image-choices-item input' ).change(); 680 $( '.wpforms-field-payment-checkbox .wpforms-image-choices-item input' ).change(); 681 } ); 682 683 // Rating field: hover effect. 684 $( '.wpforms-field-rating-item' ).hover( 685 function() { 686 $( this ).parent().find( '.wpforms-field-rating-item' ).removeClass( 'selected hover' ); 687 $( this ).prevAll().addBack().addClass( 'hover' ); 688 }, 689 function() { 690 $( this ).parent().find( '.wpforms-field-rating-item' ).removeClass( 'selected hover' ); 691 $( this ).parent().find( 'input:checked' ).parent().prevAll().addBack().addClass( 'selected' ); 692 } 693 ); 694 695 // Rating field: toggle selected state. 696 $( document ).on( 'change', '.wpforms-field-rating-item input', function() { 697 698 var $this = $( this ), 699 $wrap = $this.closest( '.wpforms-field-rating-items' ), 700 $items = $wrap.find( '.wpforms-field-rating-item' ); 701 702 $items.removeClass( 'hover selected' ); 703 $this.parent().prevAll().addBack().addClass( 'selected' ); 704 } ); 705 706 // Rating field: preselect the selected rating (from dynamic/fallback population). 707 $( document ).ready( function() { 708 $( '.wpforms-field-rating-item input:checked' ).change(); 709 } ); 710 711 // Checkbox/Radio/Payment checkbox: make labels keyboard-accessible. 712 $( document ).on( 'keypress', '.wpforms-image-choices-item label', function( event ) { 713 var $this = $( this ), 714 $field = $this.closest( '.wpforms-field' ); 715 716 if ( $field.hasClass( 'wpforms-conditional-hide' ) ) { 717 event.preventDefault(); 718 return false;
719 } 720 721 // Cause the input to be clicked when clicking the label. 722 if ( 13 === event.which ) { 723 $( '#' + $this.attr( 'for' ) ).click(); 724 } 725 } ); 726 727 // IE: Click on the `image choice` image should trigger the click event on the input (checkbox or radio) field. 728 if ( window.document.documentMode ) { 729 $( document ).on( 'click', '.wpforms-image-choices-item img', function() { 730 731 $( this ).closest( 'label' ).find( 'input' ).click(); 732 } ); 733 } 734 735 $( document ).on( 'change', '.wpforms-field-checkbox input, .wpforms-field-radio input, .wpforms-field-payment-multiple input, .wpforms-field-payment-checkbox input, .wpforms-field-gdpr-checkbox input', function( event ) { 736 737 var $this = $( this ), 738 $field = $this.closest( '.wpforms-field' ); 739 740 if ( $field.hasClass( 'wpforms-conditional-hide' ) ) { 741 event.preventDefault(); 742 return false; 743 } 744 745 switch ( $this.attr( 'type' ) ) { 746 case 'radio': 747 $this.closest( 'ul' ).find( 'li' ).removeClass( 'wpforms-selected' ).find( 'input[type=radio]' ).removeProp( 'checked' ); 748 $this 749 .prop( 'checked', true ) 750 .closest( 'li' ).addClass( 'wpforms-selected' ); 751 break; 752 753 case 'checkbox': 754 if ( $this.is( ':checked' ) ) { 755 $this.closest( 'li' ).addClass( 'wpforms-selected' ); 756 $this.prop( 'checked', true ); 757 } else { 758 $this.closest( 'li' ).removeClass( 'wpforms-selected' ); 759 $this.prop( 'checked', false ); 760 } 761 break; 762 } 763 } ); 764 765 // Upload fields: Check combined file size. 766 $( document ).on( 'change', '.wpforms-field-file-upload input[type=file]:not(".dropzone-input")', function() { 767 var $this = $( this ), 768 $uploads = $this.closest( 'form.wpforms-form' ).find( '.wpforms-field-file-upload input:not(".dropzone-input")' ), 769 totalSize = 0, 770 postMaxSize = Number( wpforms_settings.post_max_size ), 771 errorMsg = '<div class="wpforms-error-container-post_max_size">' + wpforms_settings.val_post_max_size + '</div>', 772 errorCntTpl = '<div class="wpforms-error-container">{errorMsg}</span></div>', 773 $submitCnt = $this.closest( 'form.wpforms-form' ).find( '.wpforms-submit-container' ), 774 $submitBtn = $submitCnt.find( 'button.wpforms-submit' ), 775 $errorCnt = $submitCnt.prev(); 776 777 // Calculating totalSize. 778 $uploads.each( function() { 779 var $upload = $( this ), 780 i = 0, 781 len = $upload[0].files.length; 782 for ( ; i < len; i++ ) { 783 totalSize += $upload[0].files[i].size; 784 } 785 } ); 786 787 // Checking totalSize. 788 if ( totalSize > postMaxSize ) { 789 790 // Convert sizes to Mb. 791 totalSize = Number( ( totalSize / 1048576 ).toFixed( 3 ) ); 792 postMaxSize = Number( ( postMaxSize / 1048576 ).toFixed( 3 ) ); 793 794 // Preparing error message. 795 errorMsg = errorMsg.replace( /{totalSize}/, totalSize ).replace( /{maxSize}/, postMaxSize ); 796 797 // Output error message. 798 if ( $errorCnt.hasClass( 'wpforms-error-container' ) ) { 799 $errorCnt.find( '.wpforms-error-container-post_max_size' ).remove(); 800 $errorCnt.append( errorMsg ); 801 } else { 802 $submitCnt.before( errorCntTpl.replace( /{errorMsg}/, errorMsg ) ); 803 } 804 805 // Disable submit button. 806 $submitBtn.prop( 'disabled', true ); 807 } else { 808 809 // Remove error and release submit button. 810 $errorCnt.find( '.wpforms-error-container-post_max_size' ).remove(); 811 $submitBtn.prop( 'disabled', false ); 812 } 813 814 } ); 815 816 // Number Slider field: update hints. 817 $( document ).on( 'change input', '.wpforms-field-number-slider input[type=range]', function( event ) { 818 var hintEl = $( event.target ).siblings( '.wpforms-field-number-slider-hint' ); 819 820 hintEl.html( hintEl.data( 'hint' ).replace( '{value}', '<b>' + event.target.value + '</b>' ) ); 821 } ); 822 823 // Enter key event. 824 $( document ).on( 'keydown', '.wpforms-form input', function( e ) { 825 826 if ( e.keyCode !== 13 ) { 827 return; 828 } 829 830 var $t = $( this ), 831 $page = $t.closest( '.wpforms-page' ); 832 833 if ( $page.length === 0 ) { 834 return; 835 } 836 837 if ( [ 'text', 'tel', 'number', 'email', 'url', 'radio', 'checkbox' ].indexOf( $t.attr( 'type' ) ) < 0 ) { 838 return; 839 } 840 841 if ( $t.hasClass( 'wpforms-datepicker' ) ) { 842 $t.flatpickr( 'close' ); 843 } 844 845 e.preventDefault(); 846 847 if ( $page.hasClass( 'last' ) ) { 848 $page.closest( '.wpforms-form' ).find( '.wpforms-submit' ).click(); 849 return; 850 } 851 852 $page.find( '.wpforms-page-next' ).click(); 853 } ); 854 855 // Allow only numbers, minus and decimal point to be entered into the Numbers field. 856 $( document ).on( 'input', '.wpforms-field-number input', function( e ) { 857 this.value = this.value.replace( /[^-0-9.]/g, '' ); 858 } ); 859 }, 860 861 /** 862 * Scroll to and focus on the field with error. 863 * 864 * @since 1.5.8 865 * 866 * @param {jQuery} $el Form, container or input element jQuery object. 867 */ 868 scrollToError: function( $el ) { 869 870 if ( $el.length === 0 ) { 871 return; 872 } 873 874 // Look for a field with an error inside an $el. 875 var $field = $el.find( '.wpforms-field.wpforms-has-error' ); 876 877 // Look outside in not found inside. 878 if ( $field.length === 0 ) { 879 $field = $el.closest( '.wpforms-field' ); 880 } 881 882 if ( $field.length === 0 ) { 883 return; 884 } 885 886 var offset = $field.offset(); 887 888 if ( typeof offset === 'undefined' ) { 889 return; 890 } 891 892 app.animateScrollTop( offset.top - 75, 750 ).done( function() { 893 var $error = $field.find( '.wpforms-error' ).first(); 894 if ( app.isFunction( $error.focus ) ) { 895 $error.focus(); 896 } 897 } ); 898 }, 899 900 /** 901 * Update Pagebreak navigation. 902 * 903 * @since 1.2.2 904 * 905 * @param {jQuery} el jQuery element object. 906 */ 907 pagebreakNav: function( el ) { 908 909 var $this = $( el ), 910 valid = true, 911 action = $this.data( 'action' ), 912 page = $this.data( 'page' ), 913 page2 = page, 914 next = page + 1, 915 prev = page - 1, 916 formID = $this.data( 'formid' ), 917 $form = $this.closest( '.wpforms-form' ), 918 $page = $form.find( '.wpforms-page-' + page ), 919 $submit = $form.find( '.wpforms-submit-container' ), 920 $indicator = $form.find( '.wpforms-page-indicator' ), 921 $reCAPTCHA = $form.find( '.wpforms-recaptcha-container' ), 922 pageScroll = false;
923 924 // Page scroll. 925 // TODO: cleanup this BC with wpform_pageScroll. 926 if ( false === window.wpforms_pageScroll ) { 927 pageScroll = false; 928 } else if ( ! app.empty( window.wpform_pageScroll ) ) { 929 pageScroll = window.wpform_pageScroll; 930 } else { 931 pageScroll = $indicator.data( 'scroll' ) !== 0 ? 75 : false; 932 } 933 934 // Toggling between the pages. 935 if ( 'next' === action ) { 936 937 // Validate. 938 if ( typeof $.fn.validate !== 'undefined' ) { 939 $page.find( ':input' ).each( function( index, el ) { 940 if ( ! $( el ).valid() ) { 941 valid = false; 942 } 943 } ); 944 945 // Scroll to first/top error on page. 946 app.scrollToError( $page ); 947 } 948 949 // Move to the next page. 950 if ( valid ) { 951 page2 = next; 952 $page.hide(); 953 var $nextPage = $form.find( '.wpforms-page-' + next ); 954 $nextPage.show(); 955 if ( $nextPage.hasClass( 'last' ) ) { 956 $reCAPTCHA.show(); 957 $submit.show(); 958 } 959 if ( pageScroll ) { 960 961 // Scroll to top of the form. 962 app.animateScrollTop( $form.offset().top - pageScroll, 750 ); 963 } 964 $this.trigger( 'wpformsPageChange', [ page2, $form ] ); 965 } 966 } else if ( 'prev' === action ) { 967 968 // Move to the prev page. 969 page2 = prev; 970 $page.hide(); 971 $form.find( '.wpforms-page-' + prev ).show(); 972 $reCAPTCHA.hide(); 973 $submit.hide(); 974 if ( pageScroll ) { 975 976 // Scroll to the top of the form. 977 app.animateScrollTop( $form.offset().top - pageScroll ); 978 } 979 $this.trigger( 'wpformsPageChange', [ page2, $form ] ); 980 } 981 982 if ( $indicator ) { 983 var theme = $indicator.data( 'indicator' ), 984 color = $indicator.data( 'indicator-color' ); 985 if ( 'connector' === theme || 'circles' === theme ) { 986 $indicator.find( '.wpforms-page-indicator-page' ).removeClass( 'active' ); 987 $indicator.find( '.wpforms-page-indicator-page-' + page2 ).addClass( 'active' ); 988 $indicator.find( '.wpforms-page-indicator-page-number' ).removeAttr( 'style' ); 989 $indicator.find( '.active .wpforms-page-indicator-page-number' ).css( 'background-color', color ); 990 if ( 'connector' === theme ) { 991 $indicator.find( '.wpforms-page-indicator-page-triangle' ).removeAttr( 'style' ); 992 $indicator.find( '.active .wpforms-page-indicator-page-triangle' ).css( 'border-top-color', color ); 993 } 994 } else if ( 'progress' === theme ) { 995 var $pageTitle = $indicator.find( '.wpforms-page-indicator-page-title' ), 996 $pageSep = $indicator.find( '.wpforms-page-indicator-page-title-sep' ), 997 totalPages = $form.find( '.wpforms-page' ).length, 998 width = ( page2 / totalPages ) * 100; 999 $indicator.find( '.wpforms-page-indicator-page-progress' ).css( 'width', width + '%' ); 1000 $indicator.find( '.wpforms-page-indicator-steps-current' ).text( page2 ); 1001 if ( $pageTitle.data( 'page-' + page2 + '-title' ) ) { 1002 $pageTitle.css( 'display', 'inline' ).text( $pageTitle.data( 'page-' + page2 + '-title' ) ); 1003 $pageSep.css( 'display', 'inline' ); 1004 } else { 1005 $pageTitle.css( 'display', 'none' ); 1006 $pageSep.css( 'display', 'none' ); 1007 } 1008 } 1009 } 1010 }, 1011 1012 /** 1013 * OptinMonster compatibility. 1014 * 1015 * Re-initialize after OptinMonster loads to accommodate changes that 1016 * have occurred to the DOM. 1017 * 1018 * @since 1.5.0 1019 */ 1020 bindOptinMonster: function() { 1021 1022 // OM v5. 1023 document.addEventListener( 'om.Campaign.load', function( event ) { 1024 app.ready(); 1025 app.optinMonsterRecaptchaReset( event.detail.Campaign.data.id ); 1026 } ); 1027 1028 // OM Legacy. 1029 $( document ).on( 'OptinMonsterOnShow', function( event, data, object ) { 1030 app.ready(); 1031 app.optinMonsterRecaptchaReset( data.optin ); 1032 } ); 1033 }, 1034 1035 /** 1036 * Reset/recreate reCAPTCHA v2 inside OptinMonster. 1037 * 1038 * @since 1.5.0 1039 */ 1040 optinMonsterRecaptchaReset: function( optinId ) { 1041 1042 var $form = $( '#om-' + optinId ).find( '.wpforms-form' ), 1043 $recaptchaContainer = $form.find( '.wpforms-recaptcha-container' ), 1044 $recaptcha = $form.find( '.g-recaptcha' ), 1045 recaptchaSiteKey = $recaptcha.attr( 'data-sitekey' ), 1046 recaptchaID = 'recaptcha-' + Date.now(); 1047 1048 if ( $form.length && $recaptcha.length ) { 1049 1050 $recaptcha.remove(); 1051 $recaptchaContainer.prepend( '<div class="g-recaptcha" id="' + recaptchaID + '" data-sitekey="' + recaptchaSiteKey + '"></div>' ); 1052 1053 grecaptcha.render( 1054 recaptchaID, 1055 { 1056 sitekey: recaptchaSiteKey, 1057 callback: function() { 1058 wpformsRecaptchaCallback( $( '#' + recaptchaID ) ); 1059 }, 1060 } 1061 ); 1062 } 1063 }, 1064 1065 //--------------------------------------------------------------------// 1066 // Other functions. 1067 //--------------------------------------------------------------------// 1068 1069 /** 1070 * Payments: Calculate total. 1071 * 1072 * @since 1.2.3
1073 * @since 1.5.1 Added support for payment-checkbox field. 1074 */ 1075 amountTotal: function( el, validate ) { 1076 1077 validate = validate || false; 1078 1079 var $form = $( el ).closest( '.wpforms-form' ), 1080 total = 0, 1081 totalFormatted, 1082 totalFormattedSymbol, 1083 currency = app.getCurrency(); 1084 1085 $( '.wpforms-payment-price', $form ).each( function( index, el ) { 1086 1087 var amount = 0, 1088 $this = $( this ); 1089 1090 if ( $this.closest( '.wpforms-field-payment-single' ).hasClass( 'wpforms-conditional-hide' ) ) { 1091 return; 1092 } 1093 if ( 'text' === $this.attr( 'type' ) || 'hidden' === $this.attr( 'type' ) ) { 1094 amount = $this.val(); 1095 } else if ( ( 'radio' === $this.attr( 'type' ) || 'checkbox' === $this.attr( 'type' ) ) && $this.is( ':checked' ) ) { 1096 amount = $this.data( 'amount' ); 1097 } else if ( $this.is( 'select' ) && $this.find( 'option:selected' ).length > 0 ) { 1098 amount = $this.find( 'option:selected' ).data( 'amount' ); 1099 } 1100 if ( ! app.empty( amount ) ) { 1101 amount = app.amountSanitize( amount ); 1102 total = Number( total ) + Number( amount ); 1103 } 1104 } ); 1105 1106 totalFormatted = app.amountFormat( total ); 1107 1108 if ( 'left' === currency.symbol_pos ) { 1109 totalFormattedSymbol = currency.symbol + ' ' + totalFormatted; 1110 } else { 1111 totalFormattedSymbol = totalFormatted + ' ' + currency.symbol; 1112 } 1113 1114 $form.find( '.wpforms-payment-total' ).each( function( index, el ) { 1115 if ( 'hidden' === $( this ).attr( 'type' ) || 'text' === $( this ).attr( 'type' ) ) { 1116 $( this ).val( totalFormattedSymbol ); 1117 if ( 'text' === $( this ).attr( 'type' ) && validate && $form.data( 'validator' ) ) { 1118 $( this ).valid(); 1119 } 1120 } else { 1121 $( this ).text( totalFormattedSymbol ); 1122 } 1123 } ); 1124 }, 1125 1126 /** 1127 * Sanitize amount and convert to standard format for calculations. 1128 * 1129 * @since 1.2.6 1130 */ 1131 amountSanitize: function( amount ) { 1132 1133 var currency = app.getCurrency(); 1134 1135 amount = amount.toString().replace( /[^0-9.,]/g, '' ); 1136 1137 if ( ',' === currency.decimal_sep && ( amount.indexOf( currency.decimal_sep ) !== -1 ) ) { 1138 if ( '.' === currency.thousands_sep && amount.indexOf( currency.thousands_sep ) !== -1 ) { 1139 amount = amount.replace( currency.thousands_sep, '' ); 1140 } else if ( '' === currency.thousands_sep && amount.indexOf( '.' ) !== -1 ) { 1141 amount = amount.replace( '.', '' ); 1142 } 1143 amount = amount.replace( currency.decimal_sep, '.' ); 1144 } else if ( ',' === currency.thousands_sep && ( amount.indexOf( currency.thousands_sep ) !== -1 ) ) { 1145 amount = amount.replace( currency.thousands_sep, '' ); 1146 } 1147 1148 return app.numberFormat( amount, 2, '.', '' ); 1149 }, 1150 1151 /** 1152 * Format amount. 1153 * 1154 * @since 1.2.6 1155 */ 1156 amountFormat: function( amount ) { 1157 1158 var currency = app.getCurrency(); 1159 1160 amount = String( amount ); 1161 1162 // Format the amount 1163 if ( ',' === currency.decimal_sep && ( amount.indexOf( currency.decimal_sep ) !== -1 ) ) { 1164 var sepFound = amount.indexOf( currency.decimal_sep ), 1165 whole = amount.substr( 0, sepFound ), 1166 part = amount.substr( sepFound + 1, amount.strlen - 1 ); 1167 amount = whole + '.' + part; 1168 } 1169 1170 // Strip , from the amount (if set as the thousands separator) 1171 if ( ',' === currency.thousands_sep && ( amount.indexOf( currency.thousands_sep ) !== -1 ) ) { 1172 amount = amount.replace( ',', '' ); 1173 } 1174 1175 if ( app.empty( amount ) ) { 1176 amount = 0; 1177 } 1178 1179 return app.numberFormat( amount, 2, currency.decimal_sep, currency.thousands_sep ); 1180 }, 1181 1182 /** 1183 * Get site currency settings. 1184 * 1185 * @since 1.2.6 1186 */ 1187 getCurrency: function() { 1188 1189 var currency = { 1190 code: 'USD', 1191 thousands_sep: ',', 1192 decimal_sep: '.', 1193 symbol: '$', 1194 symbol_pos: 'left', 1195 }; 1196 1197 // Backwards compatibility. 1198 if ( typeof wpforms_settings.currency_code !== 'undefined' ) { 1199 currency.code = wpforms_settings.currency_code; 1200 } 1201 if ( typeof wpforms_settings.currency_thousands !== 'undefined' ) { 1202 currency.thousands_sep = wpforms_settings.currency_thousands; 1203 } 1204 if ( typeof wpforms_settings.currency_decimal !== 'undefined' ) { 1205 currency.decimal_sep = wpforms_settings.currency_decimal; 1206 } 1207 if ( typeof wpforms_settings.currency_symbol !== 'undefined' ) { 1208 currency.symbol = wpforms_settings.currency_symbol; 1209 } 1210 if ( typeof wpforms_settings.currency_symbol_pos !== 'undefined' ) { 1211 currency.symbol_pos = wpforms_settings.currency_symbol_pos; 1212 } 1213 1214 return currency; 1215 }, 1216 1217 /** 1218 * Format number. 1219 * 1220 * @link http://locutus.io/php/number_format/ 1221 * @since 1.2.6 1222 */ 1223 numberFormat: function( number, decimals, decimalSep, thousandsSep ) { 1224 1225 number = ( number + '' ).replace( /[^0-9+\-Ee.]/g, '' ); 1226 var n = ! isFinite( +number ) ? 0 : +number; 1227 var prec = ! isFinite( +decimals ) ? 0 : Math.abs( decimals ); 1228 var sep = ( 'undefined' === typeof thousandsSep ) ? ',' : thousandsSep; 1229 var dec = ( 'undefined' === typeof decimalSep ) ? '.' : decimalSep; 1230 var s; 1231 1232 var toFixedFix = function( n, prec ) { 1233 var k = Math.pow( 10, prec ); 1234 return '' + ( Math.round( n * k ) / k ).toFixed( prec ); 1235 }; 1236 1237 // @todo: for IE parseFloat(0.55).toFixed(0) = 0; 1238 s = ( prec ? toFixedFix( n, prec ) : '' + Math.round( n ) ).split( '.' ); 1239 if ( s[0].length > 3 ) { 1240 s[0] = s[0].replace( /\B(?=(?:\d{3})+(?!\d))/g, sep ); 1241 } 1242 if ( ( s[1] || '' ).length < prec ) { 1243 s[1] = s[1] || ''; 1244 s[1] += new Array( prec - s[1].length + 1 ).join( '0' ); 1245 } 1246 1247 return s.join( dec ); 1248 }, 1249 1250 /** 1251 * Empty check similar to PHP. 1252 * 1253 * @link http://locutus.io/php/empty/ 1254 * @since 1.2.6 1255 */ 1256 empty: function( mixedVar ) { 1257 1258 var undef; 1259 var key; 1260 var i; 1261 var len; 1262 var emptyValues = [ undef, null, false, 0, '', '0' ]; 1263 1264 for ( i = 0, len = emptyValues.length; i < len; i++ ) { 1265 if ( mixedVar === emptyValues[i] ) { 1266 return true; 1267 } 1268 } 1269 1270 if ( 'object' === typeof mixedVar ) { 1271 for ( key in mixedVar ) { 1272 if ( mixedVar.hasOwnProperty( key ) ) { 1273 return false;
1274 } 1275 } 1276 return true; 1277 } 1278 1279 return false; 1280 }, 1281 1282 /** 1283 * Set cookie container user UUID. 1284 * 1285 * @since 1.3.3 1286 */ 1287 setUserIndentifier: function() { 1288 1289 if ( ( ( ! window.hasRequiredConsent && typeof wpforms_settings !== 'undefined' && wpforms_settings.uuid_cookie ) || ( window.hasRequiredConsent && window.hasRequiredConsent() ) ) && ! app.getCookie( '_wpfuuid' ) ) { 1290 1291 // Generate UUID - http://stackoverflow.com/a/873856/1489528 1292 var s = new Array( 36 ), 1293 hexDigits = '0123456789abcdef', 1294 uuid; 1295 1296 for ( var i = 0; i < 36; i++ ) { 1297 s[i] = hexDigits.substr( Math.floor( Math.random() * 0x10 ), 1 ); 1298 } 1299 s[14] = '4'; 1300 s[19] = hexDigits.substr( ( s[19] & 0x3 ) | 0x8, 1 ); 1301 s[8] = s[13] = s[18] = s[23] = '-'; 1302 1303 uuid = s.join( '' ); 1304 1305 app.createCookie( '_wpfuuid', uuid, 3999 ); 1306 } 1307 }, 1308 1309 /** 1310 * Create cookie. 1311 * 1312 * @since 1.3.3 1313 * 1314 * @param {string} name Cookie name. 1315 * @param {string} value Cookie value. 1316 * @param {string} days Whether it should expire and when. 1317 */ 1318 createCookie: function( name, value, days ) { 1319 1320 var expires = ''; 1321 1322 // If we have a days value, set it in the expiry of the cookie. 1323 if ( days ) { 1324 1325 // If -1 is our value, set a session based cookie instead of a persistent cookie. 1326 if ( '-1' === days ) { 1327 expires = ''; 1328 } else { 1329 var date = new Date(); 1330 date.setTime( date.getTime() + ( days * 24 * 60 * 60 * 1000 ) ); 1331 expires = '; expires=' + date.toGMTString(); 1332 } 1333 } else { 1334 expires = '; expires=Thu, 01 Jan 1970 00:00:01 GMT'; 1335 } 1336 1337 // Write the cookie. 1338 document.cookie = name + '=' + value + expires + '; path=/; samesite=strict'; 1339 }, 1340 1341 /** 1342 * Retrieve cookie. 1343 * 1344 * @since 1.3.3 1345 * 1346 * @param {string} name Cookie name. 1347 * 1348 * @returns {string|null} Cookie value or null when it doesn't exist. 1349 */ 1350 getCookie: function( name ) { 1351 1352 var nameEQ = name + '=', 1353 ca = document.cookie.split( ';' ); 1354 1355 for ( var i = 0; i < ca.length; i++ ) { 1356 var c = ca[i]; 1357 while ( ' ' === c.charAt( 0 ) ) { 1358 c = c.substring( 1, c.length ); 1359 } 1360 if ( 0 === c.indexOf( nameEQ ) ) { 1361 return c.substring( nameEQ.length, c.length ); 1362 } 1363 } 1364 1365 return null; 1366 }, 1367 1368 /** 1369 * Delete cookie. 1370 * 1371 * @since 1.3.3 1372 * 1373 * @param {string} name Cookie name. 1374 */ 1375 removeCookie: function( name ) { 1376 1377 app.createCookie( name, '', -1 ); 1378 }, 1379 1380 /** 1381 * Get user browser preferred language. 1382 * 1383 * @since 1.5.2 1384 * 1385 * @returns {String} Language code. 1386 */ 1387 getFirstBrowserLanguage: function() { 1388 var nav = window.navigator, 1389 browserLanguagePropertyKeys = [ 'language', 'browserLanguage', 'systemLanguage', 'userLanguage' ], 1390 i, 1391 language; 1392 1393 // Support for HTML 5.1 "navigator.languages". 1394 if ( Array.isArray( nav.languages ) ) { 1395 for ( i = 0; i < nav.languages.length; i++ ) { 1396 language = nav.languages[ i ]; 1397 if ( language && language.length ) { 1398 return language; 1399 } 1400 } 1401 } 1402 1403 // Support for other well known properties in browsers. 1404 for ( i = 0; i < browserLanguagePropertyKeys.length; i++ ) { 1405 language = nav[ browserLanguagePropertyKeys[ i ] ]; 1406 if ( language && language.length ) { 1407 return language; 1408 } 1409 } 1410 1411 return ''; 1412 }, 1413 1414 /** 1415 * Asynchronously fetches country code using current IP 1416 * and executes a callback provided with a country code parameter. 1417 * 1418 * @since 1.5.2 1419 * 1420 * @param {Function} callback Executes once the fetch is completed. 1421 */ 1422 currentIpToCountry: function( callback ) { 1423 1424 var fallback = function() { 1425 1426 $.get( 'https://ipapi.co/jsonp', function() {}, 'jsonp' ) 1427 .always( function( resp ) { 1428 var countryCode = ( resp && resp.country ) ? resp.country : ''; 1429 if ( ! countryCode ) { 1430 var lang = app.getFirstBrowserLanguage(); 1431 countryCode = lang.indexOf( '-' ) > -1 ? lang.split( '-' ).pop() : ''; 1432 } 1433 callback( countryCode ); 1434 } ); 1435 }; 1436 1437 $.get( 'https://geo.wpforms.com/v3/geolocate/json' ) 1438 .done( function( resp ) { 1439 if ( resp && resp.country_iso ) { 1440 callback( resp.country_iso ); 1441 } else { 1442 fallback(); 1443 } 1444 } ) 1445 .fail( function( resp ) { 1446 fallback(); 1447 } ); 1448 }, 1449 1450 /** 1451 * Form submit. 1452 * 1453 * @since 1.5.3 1454 * 1455 * @param {jQuery} $form Form element. 1456 */ 1457 formSubmit: function( $form ) { 1458 1459 if ( $form.hasClass( 'wpforms-ajax-form' ) && typeof FormData !== 'undefined' ) { 1460 app.formSubmitAjax( $form ); 1461 } else { 1462 app.formSubmitNormal( $form ); 1463 } 1464 }, 1465 1466 /** 1467 * Normal form submit with page reload. 1468 * 1469 * @since 1.5.3 1470 * 1471 * @param {jQuery} $form Form element. 1472 */ 1473 formSubmitNormal: function( $form ) { 1474 1475 if ( ! $form.length ) { 1476 return; 1477 } 1478 1479 var $submit = $form.find( '.wpforms-submit' ), 1480 recaptchaID = $submit.get( 0 ).recaptchaID; 1481 1482 if ( ! app.empty( recaptchaID ) || recaptchaID === 0 ) { 1483 $submit.get( 0 ).recaptchaID = false;
1484 } 1485 1486 $form.get( 0 ).submit(); 1487 }, 1488 1489 /** 1490 * Reset form recaptcha. 1491 * 1492 * @since 1.5.3 1493 * 1494 * @param {jQuery} $form Form element. 1495 */ 1496 resetFormRecaptcha: function( $form ) { 1497 1498 if ( ! $form || ! $form.length ) { 1499 return; 1500 } 1501 1502 if ( typeof grecaptcha === 'undefined' ) { 1503 return; 1504 } 1505 1506 var recaptchaID; 1507 1508 // Check for invisible recaptcha first. 1509 recaptchaID = $form.find( '.wpforms-submit' ).get( 0 ).recaptchaID; 1510 1511 // Check for v2 recaptcha if invisible recaptcha is not found. 1512 if ( app.empty( recaptchaID ) && recaptchaID !== 0 ) { 1513 recaptchaID = $form.find( '.g-recaptcha' ).data( 'recaptcha-id' ); 1514 } 1515 1516 // Reset recaptcha. 1517 if ( ! app.empty( recaptchaID ) || recaptchaID === 0 ) { 1518 grecaptcha.reset( recaptchaID ); 1519 } 1520 }, 1521 1522 /** 1523 * Console log AJAX error. 1524 * 1525 * @since 1.5.3 1526 * 1527 * @param {string} error Error text (optional). 1528 */ 1529 consoleLogAjaxError: function( error ) { 1530 1531 if ( error ) { 1532 console.error( 'WPForms AJAX submit error:\n%s', error ); // eslint-disable-line no-console 1533 } else { 1534 console.error( 'WPForms AJAX submit error' ); // eslint-disable-line no-console 1535 } 1536 }, 1537 1538 /** 1539 * Display form AJAX errors. 1540 * 1541 * @since 1.5.3 1542 * 1543 * @param {jQuery} $form Form element. 1544 * @param {object} errors Errors in format { general: { generalErrors }, field: { fieldErrors } }. 1545 */ 1546 displayFormAjaxErrors: function( $form, errors ) { 1547 1548 if ( 'string' === typeof errors ) { 1549 app.displayFormAjaxGeneralErrors( $form, errors ); 1550 return; 1551 } 1552 1553 errors = errors && ( 'errors' in errors ) ? errors.errors : null; 1554 1555 if ( app.empty( errors ) || ( app.empty( errors.general ) && app.empty( errors.field ) ) ) { 1556 app.consoleLogAjaxError(); 1557 return; 1558 } 1559 1560 if ( ! app.empty( errors.general ) ) { 1561 app.displayFormAjaxGeneralErrors( $form, errors.general ); 1562 } 1563 1564 if ( ! app.empty( errors.field ) ) { 1565 app.displayFormAjaxFieldErrors( $form, errors.field ); 1566 } 1567 }, 1568 1569 /** 1570 * Display form AJAX general errors that cannot be displayed using jQuery Validation plugin. 1571 * 1572 * @since 1.5.3 1573 * 1574 * @param {jQuery} $form Form element. 1575 * @param {object} errors Errors in format { errorType: errorText }. 1576 */ 1577 displayFormAjaxGeneralErrors: function( $form, errors ) { 1578 1579 if ( ! $form || ! $form.length ) { 1580 return; 1581 } 1582 1583 if ( app.empty( errors ) ) { 1584 return; 1585 } 1586 1587 // Safety net for random errors thrown by a third-party code. Should never be used intentionally. 1588 if ( 'string' === typeof errors ) { 1589 $form.find( '.wpforms-submit-container' ).before( '<div class="wpforms-error-container">' + errors + '</div>' ); 1590 return; 1591 } 1592 1593 $.each( errors, function( type, html ) { 1594 switch ( type ) { 1595 case 'header': 1596 $form.prepend( html ); 1597 break; 1598 case 'footer': 1599 $form.find( '.wpforms-submit-container' ).before( html ); 1600 break; 1601 case 'recaptcha': 1602 $form.find( '.wpforms-recaptcha-container' ).append( html ); 1603 break; 1604 } 1605 } ); 1606 }, 1607 1608 /** 1609 * Clear forms AJAX general errors that cannot be cleared using jQuery Validation plugin. 1610 * 1611 * @since 1.5.3 1612 * 1613 * @param {jQuery} $form Form element. 1614 */ 1615 clearFormAjaxGeneralErrors: function( $form ) { 1616 1617 $form.find( '.wpforms-error-container' ).remove(); 1618 $form.find( '#wpforms-field_recaptcha-error' ).remove(); 1619 }, 1620 1621 /** 1622 * Display form AJAX field errors using jQuery Validation plugin. 1623 * 1624 * @since 1.5.3 1625 * 1626 * @param {jQuery} $form Form element. 1627 * @param {object} errors Errors in format { fieldName: errorText }. 1628 */ 1629 displayFormAjaxFieldErrors: function( $form, errors ) { 1630 1631 if ( ! $form || ! $form.length ) { 1632 return; 1633 } 1634 1635 if ( app.empty( errors ) ) { 1636 return; 1637 } 1638 1639 var validator = $form.data( 'validator' ); 1640 1641 if ( ! validator ) { 1642 return; 1643 } 1644 1645 validator.showErrors( errors ); 1646 validator.focusInvalid(); 1647 }, 1648 1649 /** 1650 * Submit a form using AJAX. 1651 * 1652 * @since 1.5.3 1653 * 1654 * @param {jQuery} $form Form element. 1655 * 1656 * @returns {JQueryXHR|JQueryDeferred} Promise like object for async callbacks. 1657 */ 1658 formSubmitAjax: function( $form ) { 1659 1660 if ( ! $form.length ) { 1661 return $.Deferred().reject(); // eslint-disable-line new-cap 1662 } 1663 1664 var $container = $form.closest( '.wpforms-container' ), 1665 $spinner = $form.find( '.wpforms-submit-spinner' ), 1666 $confirmationScroll, 1667 formData, 1668 args; 1669 1670 $container.css( 'opacity', 0.6 ); 1671 $spinner.show(); 1672 1673 app.clearFormAjaxGeneralErrors( $form ); 1674 1675 formData = new FormData( $form.get( 0 ) ); 1676 formData.append( 'action', 'wpforms_submit' ); 1677 formData.append( 'page_url', window.location.href ); 1678 1679 args = { 1680 type : 'post', 1681 dataType : 'json', 1682 url : wpforms_settings.ajaxurl, 1683 data : formData, 1684 cache : false, 1685 contentType: false, 1686 processData: false, 1687 }; 1688 1689 args.success = function( json ) { 1690 1691 if ( ! json ) { 1692 app.consoleLogAjaxError(); 1693 return; 1694 } 1695 1696 if ( json.data && json.data.action_required ) { 1697 $form.trigger( 'wpformsAjaxSubmitActionRequired', json ); 1698 return; 1699 } 1700 1701 if ( ! json.success ) { 1702 app.resetFormRecaptcha( $form ); 1703 app.displayFormAjaxErrors( $form, json.data ); 1704 $form.trigger( 'wpformsAjaxSubmitFailed', json ); 1705 return; 1706 } 1707 1708 $form.trigger( 'wpformsAjaxSubmitSuccess', json ); 1709 1710 if ( ! json.data ) { 1711 return; 1712 } 1713 1714 if ( json.data.redirect_url ) { 1715 $form.trigger( 'wpformsAjaxSubmitBeforeRedirect', json ); 1716 window.location = json.data.redirect_url; 1717 return; 1718 } 1719 1720 if ( json.data.confirmation ) { 1721 $container.html( json.data.confirmation ); 1722 $confirmationScroll = $container.find( 'div.wpforms-confirmation-scroll' ); 1723 if ( $confirmationScroll.length ) { 1724 app.animateScrollTop( $confirmationScroll.offset().top - 100 ); 1725 } 1726 } 1727 }; 1728 1729 args.error = function( jqHXR, textStatus, error ) { 1730 1731 app.consoleLogAjaxError( error ); 1732 1733 $form.trigger( 'wpformsAjaxSubmitError', [ jqHXR, textStatus, error ] ); 1734 }; 1735 1736 args.complete = function( jqHXR, textStatus ) { 1737
1738 // Do not make form active if the action is required. 1739 if ( jqHXR.responseJSON && jqHXR.responseJSON.data && jqHXR.responseJSON.data.action_required ) { 1740 return; 1741 } 1742 1743 var $submit = $form.find( '.wpforms-submit' ), 1744 submitText = $submit.data( 'submit-text' ); 1745 1746 if ( submitText ) { 1747 $submit.text( submitText ); 1748 } 1749 $submit.prop( 'disabled', false ); 1750 1751 $container.css( 'opacity', '' ); 1752 $spinner.hide(); 1753 1754 $form.trigger( 'wpformsAjaxSubmitCompleted', [ jqHXR, textStatus ] ); 1755 }; 1756 1757 $form.trigger( 'wpformsAjaxBeforeSubmit' ); 1758 1759 return $.ajax( args ); 1760 }, 1761 1762 /** 1763 * Scroll to position with animation. 1764 * 1765 * @since 1.5.3 1766 * 1767 * @param {number} position Position (in pixels) to scroll to, 1768 * @param {number} duration Animation duration. 1769 * @param {Function} complete Function to execute after animation is complete. 1770 * 1771 * @returns {JQueryPromise} Promise object for async callbacks. 1772 */ 1773 animateScrollTop: function( position, duration, complete ) { 1774 1775 duration = duration || 1000; 1776 complete = app.isFunction( complete ) ? complete : function() {}; 1777 return $( 'html, body' ).animate( { scrollTop: parseInt( position, 10 ) }, { duration: duration, complete: complete } ).promise(); 1778 }, 1779 1780 /** 1781 * Check if object is a function. 1782 * 1783 * @since 1.5.8 1784 * 1785 * @param {mixed} object Object to check if it is function. 1786 * 1787 * @returns {boolean} True if object is a function. 1788 */ 1789 isFunction: function( object ) { 1790 1791 return !! ( object && object.constructor && object.call && object.apply ); 1792 }, 1793 }; 1794 1795 return app; 1796 1797}( document, window, jQuery ) ); 1798 1799// Initialize. 1800wpforms.init();
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.