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