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