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