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