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