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