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