form.js
888 lines
| 1 | /* globals jQuery, Give */ |
| 2 | ( function( $ ) { |
| 3 | const templateL10n = window.sequoiaTemplateL10n; |
| 4 | const templateOptions = window.sequoiaTemplateOptions; |
| 5 | const $container = $( '.give-embed-form' ); |
| 6 | const $advanceButton = $( '.advance-btn', $container ); |
| 7 | const $backButton = $( '.back-btn' ); |
| 8 | const $navigatorTitle = $( '.give-form-navigator .title' ); |
| 9 | const $paymentGatewayContainer = $( '#give-payment-mode-select' ); |
| 10 | let gatewayAnimating = false; |
| 11 | const { wp: parentWp } = window.parent; |
| 12 | |
| 13 | // Make donation form block selectable in editor |
| 14 | if ( parentWp && parentWp.data ) { |
| 15 | $container.on( 'click', function() { |
| 16 | const blockId = window.frameElement.closest( '.wp-block' ).getAttribute( 'data-block' ); |
| 17 | parentWp.data.dispatch( 'core/block-editor' ).selectBlock( blockId ); |
| 18 | } ); |
| 19 | } |
| 20 | |
| 21 | const navigator = { |
| 22 | currentStep: templateOptions.introduction.enabled === 'enabled' ? 0 : 1, |
| 23 | animating: false, |
| 24 | firstFocus: false, |
| 25 | goToStep: ( step ) => { |
| 26 | // Adjust body height before animating step, to prevent choppy iframe resizing |
| 27 | // Compare next step to current step, and increase body height if next step is taller. |
| 28 | const nextStepHeight = steps[ step ].title ? $( steps[ step ].selector ).height() + 50 : $( steps[ step ].selector ).height(); |
| 29 | const currentStepHeight = steps[ navigator.currentStep ].title ? $( steps[ navigator.currentStep ].selector ).height() + 50 : $( steps[ navigator.currentStep ].selector ).height(); |
| 30 | if ( nextStepHeight > currentStepHeight ) { |
| 31 | $( '.give-form-templates' ).css( 'min-height', `${ nextStepHeight + 123 }px` ); |
| 32 | } else { |
| 33 | // Delay setting body height if next step is shorter than current step |
| 34 | setTimeout( function() { |
| 35 | $( '.give-form-templates' ).css( 'min-height', `${ nextStepHeight + 123 }px` ); |
| 36 | }, 200 ); |
| 37 | } |
| 38 | |
| 39 | $( '.step-tracker' ).removeClass( 'current' ); |
| 40 | $( '.step-tracker[data-step="' + step + '"]' ).addClass( 'current' ); |
| 41 | |
| 42 | if ( templateOptions.introduction.enabled === 'disabled' ) { |
| 43 | if ( $( '.step-tracker' ).length === 3 ) { |
| 44 | $( '.step-tracker' ).remove(); |
| 45 | } |
| 46 | |
| 47 | step = step > 0 ? step : 1; |
| 48 | if ( step === 1 ) { |
| 49 | $( '.back-btn', $container ).hide(); |
| 50 | } else { |
| 51 | $( '.back-btn', $container ).show(); |
| 52 | } |
| 53 | |
| 54 | $( '.give-form-navigator', $container ).addClass( 'nav-visible' ); |
| 55 | $( steps[ step ].selector ).css( 'padding-top', '50px' ); |
| 56 | } else if ( step === 0 ) { |
| 57 | $( '.give-form-navigator', $container ).removeClass( 'nav-visible' ); |
| 58 | $( steps[ step ].selector ).css( 'padding-top', '' ); |
| 59 | } else { |
| 60 | $( '.give-form-navigator', $container ).addClass( 'nav-visible' ); |
| 61 | $( steps[ step ].selector ).css( 'padding-top', '50px' ); |
| 62 | } |
| 63 | |
| 64 | if ( steps[ step ].title ) { |
| 65 | $navigatorTitle.text( steps[ step ].title ); |
| 66 | } |
| 67 | |
| 68 | const hide = steps.map( ( obj, index ) => { |
| 69 | if ( index === step || index === navigator.currentStep ) { |
| 70 | return null; |
| 71 | } |
| 72 | return obj.selector; |
| 73 | } ); |
| 74 | const hideSelector = hide.filter( Boolean ).join( ', ' ); |
| 75 | |
| 76 | $( hideSelector ).hide(); |
| 77 | |
| 78 | if ( navigator.currentStep !== step ) { |
| 79 | const directionClasses = 'slide-in-right slide-in-left slide-out-right slide-out-left'; |
| 80 | const outDirection = navigator.currentStep < step ? 'left' : 'right'; |
| 81 | const inDirection = navigator.currentStep < step ? 'right' : 'left'; |
| 82 | $( steps[ navigator.currentStep ].selector ).removeClass( directionClasses ).addClass( `slide-out-${ outDirection }` ); |
| 83 | $( steps[ step ].selector ).show().removeClass( directionClasses ).addClass( `slide-in-${ inDirection }` ); |
| 84 | } |
| 85 | navigator.currentStep = step; |
| 86 | |
| 87 | setTimeout( function() { |
| 88 | setupTabOrder(); |
| 89 | // Do not auto-focus form on the page load if the first step is disabled |
| 90 | if ( ! navigator.firstFocus && templateOptions.introduction.enabled === 'disabled' ) { |
| 91 | return navigator.firstFocus = true; |
| 92 | } |
| 93 | if ( steps[ navigator.currentStep ].firstFocus ) { |
| 94 | $( steps[ navigator.currentStep ].firstFocus ).focus(); |
| 95 | } |
| 96 | }, 200 ); |
| 97 | }, |
| 98 | init: () => { |
| 99 | steps.forEach( ( step ) => { |
| 100 | if ( step.setup !== undefined ) { |
| 101 | step.setup(); |
| 102 | } |
| 103 | $( step.selector ).css( 'position', 'absolute' ); |
| 104 | } ); |
| 105 | $advanceButton.on( 'click', function( e ) { |
| 106 | e.preventDefault(); |
| 107 | navigator.forward(); |
| 108 | } ); |
| 109 | $backButton.on( 'click', function( e ) { |
| 110 | e.preventDefault(); |
| 111 | navigator.back(); |
| 112 | } ); |
| 113 | $( '.step-tracker' ).on( 'click', function( e ) { |
| 114 | e.preventDefault(); |
| 115 | navigator.goToStep( parseInt( $( e.target ).attr( 'data-step' ) ) ); |
| 116 | } ); |
| 117 | setupHeightChangeCallback( function( height ) { |
| 118 | if ( gatewayAnimating === false ) { |
| 119 | $( '.form-footer' ).css( 'transition', 'margin-top 0.2s ease' ); |
| 120 | } else { |
| 121 | $( '.form-footer' ).css( 'transition', '' ); |
| 122 | } |
| 123 | $( '.form-footer' ).css( 'margin-top', `${ height }px` ); |
| 124 | } ); |
| 125 | |
| 126 | // If Fee Recovery input is not inside any step, move it to above payment options |
| 127 | if ( $( '.give-fee-recovery-donors-choice' ).parent().hasClass( 'give-form' ) ) { |
| 128 | $( '#give_checkout_user_info' ).after( $( '.give-fee-recovery-donors-choice' ) ); |
| 129 | } |
| 130 | navigator.goToStep( getInitialStep() ); |
| 131 | |
| 132 | // Fields API: Run setup for custom checkbox fields. |
| 133 | const customCheckboxes = document.querySelectorAll( '[data-field-type="checkbox"]' ); |
| 134 | Array.from( customCheckboxes ).forEach( ( el ) => { |
| 135 | const containerSelector = '[data-field-name="' + el.getAttribute( 'data-field-name' ) + '"]'; |
| 136 | setupCheckbox( { |
| 137 | container: containerSelector, |
| 138 | label: containerSelector + ' label', |
| 139 | input: containerSelector + ' input[type="checkbox"]', |
| 140 | } ); |
| 141 | } ); |
| 142 | }, |
| 143 | back: () => { |
| 144 | const prevStep = navigator.currentStep !== 0 ? navigator.currentStep - 1 : 0; |
| 145 | navigator.goToStep( prevStep ); |
| 146 | navigator.currentStep = prevStep; |
| 147 | }, |
| 148 | forward: () => { |
| 149 | const nextStep = navigator.currentStep !== null ? navigator.currentStep + 1 : 1; |
| 150 | navigator.goToStep( nextStep ); |
| 151 | navigator.currentStep = nextStep; |
| 152 | }, |
| 153 | }; |
| 154 | |
| 155 | const steps = [ |
| 156 | { |
| 157 | id: 'introduction', |
| 158 | title: null, |
| 159 | selector: '.give-section.introduction', |
| 160 | label: templateOptions.introduction.donate_label, |
| 161 | showErrors: false, |
| 162 | tabOrder: [ |
| 163 | '.introduction .advance-btn', |
| 164 | '.step-tracker', |
| 165 | ], |
| 166 | }, |
| 167 | { |
| 168 | id: 'choose-amount', |
| 169 | title: templateOptions.payment_amount.header_label, |
| 170 | selector: '.give-section.choose-amount', |
| 171 | label: templateOptions.payment_amount.next_label, |
| 172 | showErrors: false, |
| 173 | tabOrder: [ |
| 174 | 'select.give-cs-select-currency', |
| 175 | 'input.give-amount-top', |
| 176 | '.give-donation-levels-wrap button', |
| 177 | '.give-recurring-period', |
| 178 | '.give-recurring-donors-choice-period', |
| 179 | '.give_fee_mode_checkbox', |
| 180 | '.choose-amount .advance-btn', |
| 181 | '.step-tracker', |
| 182 | '.back-btn', |
| 183 | ], |
| 184 | firstFocus: '.give-default-level', |
| 185 | setup: () => { |
| 186 | // Dynamically set grid columns based on number of buttons |
| 187 | const buttonCount = $( '.give-donation-level-btn' ).length; |
| 188 | if ( buttonCount === 1 ) { |
| 189 | $( '.give-donation-levels-wrap' ).attr( 'style', 'display: none!important;' ); |
| 190 | } else if ( buttonCount % 2 === 0 && buttonCount < 6 ) { |
| 191 | $( '.give-donation-levels-wrap' ).css( 'grid-template-columns', 'repeat(2, minmax(0, 1fr))' ); |
| 192 | } |
| 193 | |
| 194 | $( '#give-amount' ).on( 'blur', function() { |
| 195 | if ( ! Give.form.fn.isValidDonationAmount( $( 'form' ) ) ) { |
| 196 | $( '.advance-btn' ).attr( 'disabled', true ); |
| 197 | } else { |
| 198 | $( '.advance-btn' ).attr( 'disabled', false ); |
| 199 | } |
| 200 | } ); |
| 201 | $( '.give-donation-level-btn' ).each( function() { |
| 202 | const hasTooltip = $( this ).attr( 'has-tooltip' ); |
| 203 | if ( hasTooltip ) { |
| 204 | return; |
| 205 | } |
| 206 | |
| 207 | const value = $( this ).attr( 'value' ); |
| 208 | const text = $( this ).text(); |
| 209 | const symbol = window.give_global_vars.currency_sign; |
| 210 | const position = window.give_global_vars.currency_pos; |
| 211 | |
| 212 | if ( value !== 'custom' ) { |
| 213 | const html = position === 'before' ? `<div class="currency currency--before">${ symbol }</div>${ value }` : `${ value }<div class="currency currency--after">${ symbol }</div>`; |
| 214 | $( this ).html( html ); |
| 215 | } |
| 216 | |
| 217 | // Setup string to check tooltip label ga |
| 218 | const compare = position === 'before' ? symbol + value : value + symbol; |
| 219 | // Setup tooltip unless for custom donation level, or if level label matches value |
| 220 | if ( value !== 'custom' && text !== compare ) { |
| 221 | const wrap = `<span class="give-tooltip hint--top hint--bounce ${ text.length < 50 ? 'narrow' : '' }" style="width: 100%" aria-label="${ text.length < 50 ? text : text.substr( 0, 50 ) + '...' }" rel="tooltip"></span>`; |
| 222 | $( this ).wrap( wrap ); |
| 223 | $( this ).attr( 'has-tooltip', true ); |
| 224 | } |
| 225 | } ); |
| 226 | }, |
| 227 | }, |
| 228 | { |
| 229 | id: 'payment', |
| 230 | title: templateOptions.payment_information.header_label, |
| 231 | label: templateOptions.payment_information.checkout_label, |
| 232 | selector: '.give-section.payment', |
| 233 | showErrors: true, |
| 234 | tabOrder: [ |
| 235 | '.payment input, .payment a, .payment button, .payment select, .payment multiselect, .payment textarea, .payment .button', |
| 236 | '.give-submit', |
| 237 | '.step-tracker', |
| 238 | '.back-btn', |
| 239 | ], |
| 240 | firstFocus: '#give-first', |
| 241 | setup: () => { |
| 242 | // Setup payment information screen |
| 243 | |
| 244 | $( '.give-section.payment' ).on( 'click', '.give-cancel-login, .give-checkout-register-cancel', clearLoginNotices ); |
| 245 | |
| 246 | // Show Sequoia loader on click/touchend |
| 247 | $( '.give-section.payment' ).on( 'click touchend', 'input[name="give_login_submit"]', function() { |
| 248 | //Override submit loader with Sequoia loader |
| 249 | $( 'input[name="give_login_submit"] + .give-loading-animation' ).removeClass( 'give-loading-animation' ).addClass( 'sequoia-loader spinning' ); |
| 250 | } ); |
| 251 | |
| 252 | // Remove purchase_loading text |
| 253 | window.give_global_vars.purchase_loading = ''; |
| 254 | |
| 255 | // Move errors |
| 256 | $( '.give_error' ).each( function() { |
| 257 | moveErrorNotice( $( this ) ); |
| 258 | } ); |
| 259 | |
| 260 | // Setup anonymous donations opt-in event listeners |
| 261 | setupCheckbox( { |
| 262 | container: '#give-anonymous-donation-wrap label', |
| 263 | label: '#give-anonymous-donation-wrap label', |
| 264 | input: '#give-anonymous-donation', |
| 265 | } ); |
| 266 | |
| 267 | // Setup recurring donations opt-in event listeners |
| 268 | setupCheckbox( { |
| 269 | container: '.give-recurring-donors-choice', |
| 270 | label: '.give-recurring-donors-choice label', |
| 271 | input: 'input[name="give-recurring-period"]', |
| 272 | } ); |
| 273 | |
| 274 | // Setup fee recovery opt-in event listeners |
| 275 | setupCheckbox( { |
| 276 | container: '.give-fee-recovery-donors-choice', |
| 277 | label: '.give-fee-message-label-text', |
| 278 | input: 'input[name="give_fee_mode_checkbox"]', |
| 279 | } ); |
| 280 | |
| 281 | // Setup activecampaign opt-in event listeners |
| 282 | setupCheckbox( { |
| 283 | container: '.give-activecampaign-fieldset', |
| 284 | label: '.give-activecampaign-optin-label', |
| 285 | input: '.give-activecampaign-optin-input', |
| 286 | } ); |
| 287 | |
| 288 | // Setup mailchimp opt-in event listeners |
| 289 | setupCheckbox( { |
| 290 | container: '.give-mailchimp-fieldset', |
| 291 | label: '.give-mc-message-text', |
| 292 | input: 'input[name="give_mailchimp_signup"]', |
| 293 | } ); |
| 294 | |
| 295 | // Setup constant contact opt-in event listeners |
| 296 | setupCheckbox( { |
| 297 | container: '.give-constant-contact-fieldset', |
| 298 | label: '.give-constant-contact-fieldset span', |
| 299 | input: 'input[name="give_constant_contact_signup"]', |
| 300 | } ); |
| 301 | |
| 302 | // Setup terms and conditions opt-in event listeners |
| 303 | setupCheckbox( { |
| 304 | container: '#give_terms_agreement', |
| 305 | label: '#give_terms_agreement label', |
| 306 | input: 'input[name="give_agree_to_terms"]', |
| 307 | } ); |
| 308 | |
| 309 | // Show Sequoia loader on click/touchend |
| 310 | $( 'body.give-form-templates' ).on( 'click touchend', 'form.give-form input[name="give-purchase"].give-submit', function() { |
| 311 | //Override submit loader with Sequoia loader |
| 312 | $( '#give-purchase-button + .give-loading-animation' ).removeClass( 'give-loading-animation' ).addClass( 'sequoia-loader' ); |
| 313 | |
| 314 | // Only show spinner if form is valid |
| 315 | if ( $( 'form' ).get( 0 ).checkValidity() ) { |
| 316 | $( '.sequoia-loader' ).addClass( 'spinning' ); |
| 317 | } |
| 318 | } ); |
| 319 | |
| 320 | // Go to choose amount step when donation maximum error is clicked |
| 321 | $( 'body.give-form-templates' ).on( 'click touchend', '#give_error_invalid_donation_maximum', function() { |
| 322 | // Go to choose amount step |
| 323 | navigator.goToStep( 1 ); |
| 324 | } ); |
| 325 | |
| 326 | // Go to choose amount step when invalid donation error is clicked |
| 327 | $( 'body.give-form-templates' ).on( 'click touchend', '#give_error_invalid_donation_amount', function() { |
| 328 | // Go to choose amount step |
| 329 | navigator.goToStep( 1 ); |
| 330 | } ); |
| 331 | |
| 332 | // Setup gateway icons |
| 333 | setupGatewayIcons(); |
| 334 | |
| 335 | // Setup Form Field Manager input listeners |
| 336 | setupFFMInputs(); |
| 337 | |
| 338 | // Setup Icons for inputs |
| 339 | setupInputIcons(); |
| 340 | |
| 341 | const observer = new window.MutationObserver( function( mutations ) { |
| 342 | mutations.forEach( function( mutation ) { |
| 343 | if ( ! mutation.addedNodes ) { |
| 344 | return; |
| 345 | } |
| 346 | |
| 347 | for ( let i = 0; i < mutation.addedNodes.length; i++ ) { |
| 348 | // do things to your newly added nodes here |
| 349 | const node = mutation.addedNodes[ i ]; |
| 350 | |
| 351 | if ( $( node ).find( '.give_error' ).length > 0 ) { |
| 352 | moveErrorNotice( $( node ).find( '.give_error' ) ); |
| 353 | scrollToIframeTop(); |
| 354 | } |
| 355 | |
| 356 | if ( $( node ).children().hasClass( 'give_errors' ) ) { |
| 357 | if ( ! $( node ).parent().hasClass( 'donation-errors' ) ) { |
| 358 | $( node ).children( '.give_errors' ).each( function() { |
| 359 | const notice = $( this ); |
| 360 | moveErrorNotice( notice ); |
| 361 | } ); |
| 362 | } |
| 363 | scrollToIframeTop(); |
| 364 | } |
| 365 | |
| 366 | if ( $( node ).hasClass( 'give_errors' ) && ! $( node ).parent().hasClass( 'donation-errors' ) ) { |
| 367 | moveErrorNotice( $( node ) ); |
| 368 | $( '.sequoia-loader' ).removeClass( 'spinning' ); |
| 369 | scrollToIframeTop(); |
| 370 | } |
| 371 | |
| 372 | if ( $( node ).attr( 'id' ) === 'give_tributes_address_state' ) { |
| 373 | const placeholder = $( node ).attr( 'placeholder' ); |
| 374 | $( node ).prepend( `<option selected disabled>${ placeholder }</option>` ); |
| 375 | } |
| 376 | |
| 377 | if ( $( node ).attr( 'name' ) === 'give_tributes_address_state' && $( node ).attr( 'class' ).includes( 'give-input' ) ) { |
| 378 | $( node ).attr( 'placeholder', $( node ).siblings( 'label' ).text().trim() ); |
| 379 | } |
| 380 | |
| 381 | if ( $( node ).attr( 'id' ) && $( node ).attr( 'id' ).includes( 'give-checkout-login-register' ) ) { |
| 382 | setupRegistrationFormInputFields(); |
| 383 | } |
| 384 | |
| 385 | if ( $( node ).prop( 'tagName' ) && $( node ).prop( 'tagName' ).toLowerCase() === 'select' ) { |
| 386 | const placeholder = $( node ).attr( 'placeholder' ); |
| 387 | if ( placeholder ) { |
| 388 | $( node ).prepend( `<option value="" disabled selected>${ placeholder }</option>` ); |
| 389 | } |
| 390 | } |
| 391 | } |
| 392 | } ); |
| 393 | } ); |
| 394 | |
| 395 | observer.observe( document.body, { |
| 396 | childList: true, |
| 397 | subtree: true, |
| 398 | attributes: false, |
| 399 | characterData: false, |
| 400 | } ); |
| 401 | }, |
| 402 | }, |
| 403 | ]; |
| 404 | |
| 405 | navigator.init(); |
| 406 | |
| 407 | // Check if only a single gateway is enabled |
| 408 | if ( $paymentGatewayContainer.length && $paymentGatewayContainer.css( 'display' ) !== 'none' ) { |
| 409 | // Move payment information section when document load. |
| 410 | moveFieldsUnderPaymentGateway(); |
| 411 | |
| 412 | // Move payment information section when gateway updated. |
| 413 | $( document ).on( 'give_gateway_loaded', function() { |
| 414 | setTimeout( setupTabOrder, 200 ); |
| 415 | |
| 416 | moveFieldsUnderPaymentGateway(); |
| 417 | setupRegistrationFormInputFields(); |
| 418 | setupFFMInputs(); |
| 419 | setupInputIcons(); |
| 420 | setupSelectInputs(); |
| 421 | |
| 422 | $( '#give_purchase_form_wrap' ).slideDown( 200, function() { |
| 423 | gatewayAnimating = false; |
| 424 | } ); |
| 425 | } ); |
| 426 | $( document ).on( 'Give:onPreGatewayLoad', function() { |
| 427 | gatewayAnimating = true; |
| 428 | $( '#give_purchase_form_wrap' ).slideUp( 200 ); |
| 429 | } ); |
| 430 | |
| 431 | // Clear gateway related errors |
| 432 | $( document ).on( 'Give:onPreGatewayLoad', function() { |
| 433 | const persistedNotices = [ |
| 434 | 'give_error_test_mode', |
| 435 | ]; |
| 436 | |
| 437 | $( '.give_errors, .give_notices, .give_error' ).each( function() { |
| 438 | if ( ! persistedNotices.includes( $( this ).attr( 'id' ) ) ) { |
| 439 | $( this ).slideUp( 200, function() { |
| 440 | $( this ).remove(); |
| 441 | } ); |
| 442 | } |
| 443 | } ); |
| 444 | } ); |
| 445 | } else { |
| 446 | $( '#give_purchase_form_wrap' ).addClass( 'give-single-gateway-wrap' ); |
| 447 | } |
| 448 | |
| 449 | // Refresh personal information section. |
| 450 | $( document ).on( 'give_gateway_loaded', refreshPersonalInformationSection ); |
| 451 | |
| 452 | // Setup fields. |
| 453 | setupSelectInputs(); |
| 454 | setupRegistrationFormInputFields(); |
| 455 | setupFFMInputs(); |
| 456 | setupInputIcons(); |
| 457 | |
| 458 | if ( 'enabled' === templateOptions.payment_amount.decimals_enabled ) { |
| 459 | updateFormDonationLevelsLabels(); |
| 460 | } |
| 461 | |
| 462 | /** |
| 463 | * Limited scope of optional input labels, specifically to User Info, see issue #5160. |
| 464 | */ |
| 465 | setupOptionalInputLables( |
| 466 | Array.from( document.querySelectorAll( '#give_checkout_user_info input[type="text"]' ) ) |
| 467 | ); |
| 468 | |
| 469 | /** |
| 470 | * Denote non-required fields as optional. |
| 471 | * |
| 472 | * @since 2.8.0 |
| 473 | * |
| 474 | * @param {array} inputs An iteratable list of input elements. |
| 475 | */ |
| 476 | function setupOptionalInputLables( inputs ) { |
| 477 | inputs.filter( function( input ) { |
| 478 | return ! input.required; |
| 479 | } ).map( function( input ) { |
| 480 | input.placeholder += templateL10n.optionalLabel; |
| 481 | } ); |
| 482 | } |
| 483 | |
| 484 | /** |
| 485 | * Move error notices to error notice container at the top of the payment section |
| 486 | * @since 2.7.0 |
| 487 | * @param {node} node The error notice node to be moved |
| 488 | */ |
| 489 | function moveErrorNotice( node ) { |
| 490 | //If the error is inside stripe payment button, do nothing |
| 491 | if ( $( node ).parent().hasClass( 'give-stripe-payment-request-button' ) ) { |
| 492 | return; |
| 493 | } |
| 494 | |
| 495 | // First check if specific donation notice container has been set up |
| 496 | if ( $( '.donation-errors' ).length === 0 ) { |
| 497 | $( '.payment' ).prepend( '<div class="donation-errors"></div>' ); |
| 498 | } |
| 499 | |
| 500 | // If a specific notice does not already exist, proceed with moving the error |
| 501 | if ( typeof $( '.donation-errors' ).html() !== undefined && ! $( '.donation-errors' ).html().includes( $( node ).html() ) ) { |
| 502 | $( node ).appendTo( '.donation-errors' ); |
| 503 | } else { |
| 504 | // If the specific notice already exists, do not add it |
| 505 | $( node ).remove(); |
| 506 | } |
| 507 | } |
| 508 | |
| 509 | /** |
| 510 | * Setup registration form input fields. |
| 511 | * @since 2.9.6 |
| 512 | */ |
| 513 | function setupRegistrationFormInputFields() { |
| 514 | const handleInput = function( evt ) { |
| 515 | if ( ! $( evt.target ).is( 'input' ) ) { |
| 516 | return; |
| 517 | } |
| 518 | |
| 519 | if ( $( evt.target ).hasClass( 'give-disabled' ) ) { |
| 520 | return; |
| 521 | } |
| 522 | |
| 523 | // Registration account fields only contains checkboxes. |
| 524 | $( evt.target ).closest( 'label' ).toggleClass( 'checked' ); |
| 525 | }; |
| 526 | |
| 527 | $( '[id*="give-register-account-fields"]' ) |
| 528 | .off( 'click', handleInput ) |
| 529 | .on( 'click', handleInput ); |
| 530 | } |
| 531 | |
| 532 | /** |
| 533 | * Add listeners and starting states to FFM inputs |
| 534 | * @since 2.7.0 |
| 535 | */ |
| 536 | function setupFFMInputs() { |
| 537 | $( '#give-ffm-section' ) |
| 538 | .off( 'click', handleFFMInput ) |
| 539 | .on( 'click', handleFFMInput ); |
| 540 | |
| 541 | $( '#give-ffm-section input' ).each( function() { |
| 542 | switch ( $( this ).prop( 'type' ) ) { |
| 543 | case 'checkbox': { |
| 544 | if ( $( this ).prop( 'checked' ) ) { |
| 545 | $( this ).parent().addClass( 'checked' ); |
| 546 | } else { |
| 547 | $( this ).parent().removeClass( 'checked' ); |
| 548 | } |
| 549 | break; |
| 550 | } |
| 551 | case 'radio': { |
| 552 | if ( $( this ).prop( 'checked' ) ) { |
| 553 | $( this ).parent().addClass( 'selected' ); |
| 554 | } else { |
| 555 | $( this ).parent().removeClass( 'selected' ); |
| 556 | } |
| 557 | break; |
| 558 | } |
| 559 | } |
| 560 | } ); |
| 561 | } |
| 562 | |
| 563 | /** |
| 564 | * Move form field under payment gateway |
| 565 | * @since 2.7.0 |
| 566 | */ |
| 567 | function moveFieldsUnderPaymentGateway() { |
| 568 | // Check if donate fieldset area has been created, if not set it up below payment gateways |
| 569 | // This area is necessary for correctly placing various elements (fee recovery notice, newsletters, submit button, etc) |
| 570 | if ( $( '#donate-fieldset' ).length === 0 ) { |
| 571 | $( '.give-section.payment' ).append( '<fieldset id="donate-fieldset"></fieldset>' ); |
| 572 | } |
| 573 | |
| 574 | // Elements to move into donate fieldset (located at bottom of form) |
| 575 | // The elements will appear in order of array |
| 576 | const donateFieldsetElements = [ |
| 577 | '.give-constant-contact-fieldset', |
| 578 | '.give-activecampaign-fieldset', |
| 579 | '.give-mailchimp-fieldset', |
| 580 | '#give_terms_agreement', |
| 581 | '.give-donation-submit', |
| 582 | ]; |
| 583 | |
| 584 | // Handle moving elements into donate fieldset |
| 585 | donateFieldsetElements.forEach( function( selector ) { |
| 586 | if ( $( `#donate-fieldset ${ selector }` ).length === 0 ) { |
| 587 | $( `#donate-fieldset ${ selector }` ).remove(); |
| 588 | $( '#donate-fieldset' ).append( $( `#give_purchase_form_wrap ${ selector }` ) ); |
| 589 | } else if ( $( `#donate-fieldset ${ selector }` ).html() !== $( `#give_purchase_form_wrap ${ selector }` ).html() && $( `#give_purchase_form_wrap ${ selector }` ).html() !== undefined ) { |
| 590 | $( `#donate-fieldset ${ selector }` ).remove(); |
| 591 | $( '#donate-fieldset' ).append( $( `#give_purchase_form_wrap ${ selector }` ) ); |
| 592 | } else { |
| 593 | $( `#give_purchase_form_wrap ${ selector }` ).remove(); |
| 594 | } |
| 595 | } ); |
| 596 | |
| 597 | // Move purchase fields (credit card, billing, etc) |
| 598 | $( 'li.give-gateway-option-selected' ).after( $( '#give_purchase_form_wrap' ) ); |
| 599 | |
| 600 | // Add gateway class to fields wrapper, indicating which gateway is active |
| 601 | const gatewayClass = 'gateway-' + $( '.give-gateway-option-selected input' ).attr( 'value' ).replace( '_', '-' ); |
| 602 | $( '#give_purchase_form_wrap' ).attr( 'class', gatewayClass ); |
| 603 | } |
| 604 | |
| 605 | /** |
| 606 | * Refresh personal information section |
| 607 | * |
| 608 | * @since 2.7.0 |
| 609 | * @since 2.10.2 Rename function |
| 610 | * @param {boolean} ev Event object |
| 611 | * @param {object} response Response object |
| 612 | * @param {number} formID Form ID |
| 613 | */ |
| 614 | function refreshPersonalInformationSection( ev, response, formID ) { |
| 615 | if ( navigator.currentStep === 2 ) { |
| 616 | $( '.give-form-templates' ).css( 'min-height', '' ); |
| 617 | } |
| 618 | |
| 619 | const $form = $( `#${ formID }` ); |
| 620 | |
| 621 | // This function will run only for embed donation form. |
| 622 | // Show payment information section fields. |
| 623 | if ( $form.parent().hasClass( 'give-embed-form' ) ) { |
| 624 | const data = { |
| 625 | action: 'give_cancel_login', |
| 626 | form_id: $form.find( '[name="give-form-id"]' ).val(), |
| 627 | }; |
| 628 | |
| 629 | // AJAX get the payment fields. |
| 630 | $.post( Give.fn.getGlobalVar( 'ajaxurl' ), data, function( postResponse ) { |
| 631 | const container = $form.find( '[id^=give-checkout-login-register]' ); |
| 632 | |
| 633 | // Remove existing personal information field if response contains new fields. |
| 634 | if ( container.length && postResponse.fields.includes( 'give_checkout_user_info' ) ) { |
| 635 | $form.find( '#give_checkout_user_info' ).remove(); |
| 636 | } |
| 637 | |
| 638 | container.replaceWith( $.parseJSON( postResponse.fields ) ); |
| 639 | container.css( { display: 'block' } ); |
| 640 | |
| 641 | $form.find( '.give-submit-button-wrap' ).show(); |
| 642 | } ).done( function() { |
| 643 | // Trigger float-labels |
| 644 | window.give_fl_trigger(); |
| 645 | |
| 646 | setupInputIcons(); |
| 647 | } ); |
| 648 | } |
| 649 | } |
| 650 | |
| 651 | function setupInputIcon( selector, icon ) { |
| 652 | $( selector ).each( function() { |
| 653 | if ( $( this ).html() !== '' && $( this ).html().includes( `<i class="fas fa-${ icon }"></i>` ) === false ) { |
| 654 | const property = isRTL() ? 'padding-right' : 'padding-left'; |
| 655 | $( this ).prepend( `<i class="fas fa-${ icon }"></i>` ); |
| 656 | $( this ).children( 'input, selector' ).each( function() { |
| 657 | $( this ).attr( 'style', property + ': 33px!important;' ); |
| 658 | } ); |
| 659 | } |
| 660 | } ); |
| 661 | } |
| 662 | |
| 663 | function setupInputIcons() { |
| 664 | setupInputIcon( '#give-first-name-wrap', 'user' ); |
| 665 | setupInputIcon( '#give-email-wrap', 'envelope' ); |
| 666 | setupInputIcon( '#give-company-wrap', 'building' ); |
| 667 | setupInputIcon( '#date_field-wrap', 'calendar-alt' ); |
| 668 | setupInputIcon( '#url_field-wrap', 'globe' ); |
| 669 | setupInputIcon( '#phone_field-wrap', 'phone' ); |
| 670 | setupInputIcon( '#give-phone-wrap', 'phone' ); |
| 671 | setupInputIcon( '#email_field-wrap', 'envelope' ); |
| 672 | } |
| 673 | |
| 674 | /** |
| 675 | * Setup tab order for elements in form |
| 676 | * |
| 677 | * @since 2.7.0 |
| 678 | */ |
| 679 | function setupTabOrder() { |
| 680 | $( 'select, button, input, textarea, multiselect, a' ).attr( 'tabindex', -1 ); |
| 681 | |
| 682 | const tabOrder = steps[ navigator.currentStep ].tabOrder; |
| 683 | tabOrder.forEach( ( selector, index ) => { |
| 684 | $( selector ).attr( 'tabindex', index + 1 ); |
| 685 | } ); |
| 686 | } |
| 687 | |
| 688 | /** |
| 689 | * Loop through gateway li elements and setup fontawesome icons |
| 690 | * |
| 691 | * @since 2.7.0 |
| 692 | */ |
| 693 | function setupGatewayIcons() { |
| 694 | $( '#give-gateway-radio-list li' ).each( function() { |
| 695 | const value = $( 'input', this ).val(); |
| 696 | let icon; |
| 697 | switch ( value ) { |
| 698 | case 'manual': |
| 699 | icon = 'fas fa-tools'; |
| 700 | break; |
| 701 | case 'offline': |
| 702 | icon = 'fas fa-wallet'; |
| 703 | break; |
| 704 | case 'paypal': |
| 705 | icon = 'fab fa-paypal'; |
| 706 | break; |
| 707 | case 'stripe': |
| 708 | icon = 'far fa-credit-card'; |
| 709 | break; |
| 710 | case 'stripe_checkout': |
| 711 | icon = 'far fa-credit-card'; |
| 712 | break; |
| 713 | case 'stripe_sepa': |
| 714 | icon = 'fas fa-university'; |
| 715 | break; |
| 716 | case 'stripe_ach': |
| 717 | icon = 'fas fa-university'; |
| 718 | break; |
| 719 | case 'stripe_ideal': |
| 720 | icon = 'fas fa-university'; |
| 721 | break; |
| 722 | case 'stripe_becs': |
| 723 | icon = 'fas fa-university'; |
| 724 | break; |
| 725 | case 'paypalpro_payflow': |
| 726 | icon = 'far fa-credit-card'; |
| 727 | break; |
| 728 | case 'paypal-commerce': |
| 729 | icon = 'far fa-credit-card'; |
| 730 | break; |
| 731 | case 'stripe_google_pay': |
| 732 | icon = 'fab fa-google'; |
| 733 | break; |
| 734 | case 'stripe_apple_pay': |
| 735 | icon = 'fab fa-apple'; |
| 736 | break; |
| 737 | default: |
| 738 | icon = 'fas fa-hand-holding-heart'; |
| 739 | break; |
| 740 | } |
| 741 | $( this ).append( `<i class="${ icon }"></i>` ); |
| 742 | } ); |
| 743 | } |
| 744 | |
| 745 | /** |
| 746 | * Setup prominent checkboxes (that use persistent borders on select) |
| 747 | * |
| 748 | * @since 2.7.0 |
| 749 | * @param {object} args Argument object containing: container, label, input selectors |
| 750 | */ |
| 751 | function setupCheckbox( { container, label, input } ) { |
| 752 | // If checkbox is opted in by default, add border on load |
| 753 | if ( $( input ).prop( 'checked' ) === true ) { |
| 754 | $( container ).addClass( 'active' ); |
| 755 | } |
| 756 | |
| 757 | // Persist checkbox input border when selected |
| 758 | $( document ).on( 'click', label, function( evt ) { |
| 759 | if ( container === label ) { |
| 760 | evt.stopPropagation(); |
| 761 | evt.preventDefault(); |
| 762 | |
| 763 | $( input ).prop( 'checked', ! $( input ).prop( 'checked' ) ); |
| 764 | } |
| 765 | |
| 766 | $( container ).toggleClass( 'active' ); |
| 767 | } ); |
| 768 | } |
| 769 | |
| 770 | function setupHeightChangeCallback( callback ) { |
| 771 | let lastHeight = 0; |
| 772 | function checkHeightChange() { |
| 773 | const selector = $( steps[ navigator.currentStep ].selector ); |
| 774 | const changed = lastHeight !== $( selector ).outerHeight(); |
| 775 | if ( changed ) { |
| 776 | callback( $( selector ).outerHeight() ); |
| 777 | lastHeight = $( selector ).outerHeight(); |
| 778 | } |
| 779 | window.requestAnimationFrame( checkHeightChange ); |
| 780 | } |
| 781 | window.requestAnimationFrame( checkHeightChange ); |
| 782 | } |
| 783 | |
| 784 | /** |
| 785 | * Get initial step to show donor. |
| 786 | * |
| 787 | * @since 2.7.0 |
| 788 | * @returns {number} Step to start on |
| 789 | */ |
| 790 | function getInitialStep() { |
| 791 | return Give.fn.getParameterByName( 'showDonationProcessingError' ) || Give.fn.getParameterByName( 'showFailedDonationError' ) ? 2 : 0; |
| 792 | } |
| 793 | |
| 794 | /** |
| 795 | * Handle updating label classes for FFM radios and checkboxes |
| 796 | * |
| 797 | * @since 2.7.0 |
| 798 | * @param {object} evt Reference to FFM input element click event |
| 799 | */ |
| 800 | function handleFFMInput( evt ) { |
| 801 | if ( $( evt.target ).is( 'input' ) ) { |
| 802 | switch ( $( evt.target ).prop( 'type' ) ) { |
| 803 | case 'checkbox': { |
| 804 | $( evt.target ).closest( 'label' ).toggleClass( 'checked' ); |
| 805 | break; |
| 806 | } |
| 807 | case 'radio': { |
| 808 | $( evt.target ).closest( 'label' ).addClass( 'selected' ); |
| 809 | $( evt.target ).parent().siblings().removeClass( 'selected' ); |
| 810 | break; |
| 811 | } |
| 812 | } |
| 813 | } |
| 814 | } |
| 815 | |
| 816 | function clearLoginNotices() { |
| 817 | $( '#give_error_must_log_in' ).remove(); |
| 818 | } |
| 819 | |
| 820 | /** |
| 821 | * Setup select inputs |
| 822 | * |
| 823 | * @since 2.7.0 |
| 824 | */ |
| 825 | function setupSelectInputs() { |
| 826 | if ( $( 'select option[selected="selected"][value=""]' ).length > 0 ) { |
| 827 | $( 'select option[selected="selected"][value=""]' ).each( function() { |
| 828 | if ( $( this ).parent().siblings( 'label' ).length ) { |
| 829 | $( this ).text( $( this ).parent().siblings( 'label' ).text().replace( '*', '' ).trim() ); |
| 830 | $( this ).attr( 'disabled', true ); |
| 831 | } |
| 832 | } ); |
| 833 | } |
| 834 | } |
| 835 | |
| 836 | /** |
| 837 | * Check if document is RTL |
| 838 | * |
| 839 | * @since 2.8.0 |
| 840 | * @return {boolean} Whether or not document is RTL |
| 841 | */ |
| 842 | function isRTL() { |
| 843 | return $( 'html' ).attr( 'dir' ) === 'rtl'; |
| 844 | } |
| 845 | |
| 846 | /** |
| 847 | * Scroll to parent window to iframe top |
| 848 | * |
| 849 | * @since 2.9.0 |
| 850 | */ |
| 851 | function scrollToIframeTop() { |
| 852 | if ( 'parentIFrame' in window ) { |
| 853 | window.parentIFrame.sendMessage( { action: 'giveScrollIframeInToView' } ); |
| 854 | } |
| 855 | } |
| 856 | |
| 857 | /** |
| 858 | * Update decimal donation levels amount |
| 859 | * |
| 860 | * @since 2.11.0 |
| 861 | */ |
| 862 | function updateFormDonationLevelsLabels() { |
| 863 | $( '.give-form' ).each( ( i, form ) => { |
| 864 | const donationForm = $( form ); |
| 865 | const donationLevels = Give.form.fn.getVariablePrices( donationForm ); |
| 866 | const symbol = Give.form.fn.getInfo( 'currency_symbol', donationForm ); |
| 867 | const position = Give.form.fn.getInfo( 'currency_position', donationForm ); |
| 868 | const precision = Give.form.fn.getInfo( 'number_decimals', donationForm ); |
| 869 | |
| 870 | $.each( donationLevels, function( j, level ) { |
| 871 | if ( 'custom' === level.price_id ) { |
| 872 | return; |
| 873 | } |
| 874 | |
| 875 | const amount = Give.fn.numberHasDecimal( level.amount ) |
| 876 | ? Give.fn.formatCurrency( level.amount, { symbol, position, precision }, donationForm ) |
| 877 | : level.amount; |
| 878 | |
| 879 | const donationLevelLabel = '<div class="currency currency--' + position + '">' + symbol + '</div>' + amount; |
| 880 | |
| 881 | donationForm |
| 882 | .find( '.give-btn-level-' + level.price_id ) |
| 883 | .html( donationLevelLabel ); |
| 884 | } ); |
| 885 | } ); |
| 886 | } |
| 887 | }( jQuery ) ); |
| 888 |