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