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