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