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