PluginProbe ʕ •ᴥ•ʔ
GiveWP – Donation Plugin and Fundraising Platform / 2.13.0
GiveWP – Donation Plugin and Fundraising Platform v2.13.0
4.16.4 4.16.3 4.16.2 4.16.1 4.16.0 4.15.5 4.15.4 4.15.3 4.15.2 4.15.1 4.15.0 2.3.0 2.3.1 2.3.2 2.30.0 2.31.0 2.31.1 2.32.0 2.33.0 2.33.1 2.33.2 2.33.3 2.33.4 2.33.5 2.4.0 2.4.1 2.4.2 2.4.3 2.4.4 2.4.5 2.4.6 2.4.7 2.5.0 2.5.1 2.5.10 2.5.11 2.5.12 2.5.13 2.5.2 2.5.3 2.5.4 2.5.5 2.5.6 2.5.7 2.5.8 2.5.9 2.6.0 2.6.1 2.6.2 2.6.3 2.7.0 2.7.1 2.7.2 2.7.3 2.7.4 2.7.5 2.8.0 2.8.1 2.9.0 2.9.1 2.9.2 2.9.3 2.9.4 2.9.5 2.9.6 2.9.7 3.0.0 3.0.1 3.0.2 3.0.3 3.0.4 3.1.0 3.1.1 3.1.2 3.10.0 3.11.0 3.12.0 3.12.1 3.12.2 3.12.3 3.13.0 3.14.0 3.14.1 3.14.2 3.15.0 3.15.1 3.16.0 3.16.1 3.16.2 3.16.3 3.16.4 3.16.5 3.17.0 3.17.1 3.17.2 3.18.0 3.19.0 3.19.1 3.19.2 3.19.3 3.19.4 3.2.0 3.2.1 3.2.2 3.20.0 3.21.0 3.21.1 3.22.0 3.22.1 3.22.2 3.3.0 3.3.1 3.4.0 3.4.1 3.4.2 3.5.0 3.5.1 3.6.0 3.6.1 3.6.2 3.7.0 3.8.0 3.9.0 4.0.0 4.1.0 4.1.1 4.10.0 4.10.1 4.11.0 4.12.0 4.13.0 4.13.1 4.13.2 4.14.0 4.14.1 4.14.2 4.14.3 4.14.4 4.14.5 4.14.6 4.2.0 4.2.1 4.3.0 4.3.1 4.3.2 4.4.0 4.5.0 4.6.1 4.7.0 4.7.1 4.8.0 4.8.1 4.9.0 trunk 1.9.0 2.0.0 2.0.1 2.0.2 2.0.3 2.0.4 2.0.5 2.0.6 2.0.7 2.1.0 2.1.1 2.1.2 2.1.3 2.1.4 2.1.5 2.1.6 2.1.7 2.1.8 2.10.0 2.10.1 2.10.2 2.10.3 2.10.4 2.11.0 2.11.1 2.11.2 2.11.3 2.12.0 2.12.1 2.12.2 2.12.3 2.13.0 2.13.1 2.13.2 2.13.3 2.13.4 2.14.0 2.15.0 2.16.0 2.16.1 2.17.0 2.17.1 2.17.3 2.18.0 2.18.1 2.19.1 2.19.2 2.19.3 2.19.4 2.19.5 2.19.6 2.19.7 2.19.8 2.2.0 2.2.1 2.2.2 2.2.3 2.2.4 2.2.5 2.2.6 2.20.0 2.20.1 2.20.2 2.21.0 2.21.1 2.21.2 2.21.3 2.21.4 2.22.0 2.22.1 2.22.2 2.22.3 2.23.0 2.23.1 2.23.2 2.24.0 2.24.1 2.24.2 2.25.0 2.25.1 2.25.2 2.25.3 2.26.0 2.27.0 2.27.1 2.27.2 2.27.3 2.28.0 2.29.0 2.29.1 2.29.2
give / src / Views / Form / Templates / Sequoia / assets / js / form.js
give / src / Views / Form / Templates / Sequoia / assets / js Last commit date
form.js 4 years ago
form.js
904 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 = document.createElement('span');
222 wrap.classList.add('give-tooltip', 'hint--top', 'hint--bounce');
223 if( text.length < 50 ) {
224 wrap.classList.add( 'narrow' );
225 }
226 wrap.style.width = '100%';
227 wrap.setAttribute('aria-label', text.length < 50 ? text : text.substr( 0, 50 ) + '...');
228 $( this ).wrap( wrap );
229 $( this ).attr( 'has-tooltip', true );
230 }
231 } );
232 },
233 },
234 {
235 id: 'payment',
236 title: templateOptions.payment_information.header_label,
237 label: templateOptions.payment_information.checkout_label,
238 selector: '.give-section.payment',
239 showErrors: true,
240 tabOrder: [
241 '.payment input, .payment a, .payment button, .payment select, .payment multiselect, .payment textarea, .payment .button',
242 '.give-submit',
243 '.step-tracker',
244 '.back-btn',
245 ],
246 firstFocus: '#give-first',
247 setup: () => {
248 // Setup payment information screen
249
250 $( '.give-section.payment' ).on( 'click', '.give-cancel-login, .give-checkout-register-cancel', clearLoginNotices );
251
252 // Show Sequoia loader on click/touchend
253 $( '.give-section.payment' ).on( 'click touchend', 'input[name="give_login_submit"]', function() {
254 //Override submit loader with Sequoia loader
255 $( 'input[name="give_login_submit"] + .give-loading-animation' ).removeClass( 'give-loading-animation' ).addClass( 'sequoia-loader spinning' );
256 } );
257
258 // Remove purchase_loading text
259 window.give_global_vars.purchase_loading = '';
260
261 // Move errors
262 $( '.give_error' ).each( function() {
263 moveErrorNotice( $( this ) );
264 } );
265
266 // Setup anonymous donations opt-in event listeners
267 setupCheckbox( {
268 container: '#give-anonymous-donation-wrap label',
269 label: '#give-anonymous-donation-wrap label',
270 input: '#give-anonymous-donation',
271 } );
272
273 // Setup recurring donations opt-in event listeners
274 setupCheckbox( {
275 container: '.give-recurring-donors-choice',
276 label: '.give-recurring-donors-choice label',
277 input: 'input[name="give-recurring-period"]',
278 } );
279
280 // Setup fee recovery opt-in event listeners
281 setupCheckbox( {
282 container: '.give-fee-recovery-donors-choice',
283 label: '.give-fee-message-label-text',
284 input: 'input[name="give_fee_mode_checkbox"]',
285 } );
286
287 // Setup activecampaign opt-in event listeners
288 setupCheckbox( {
289 container: '.give-activecampaign-fieldset',
290 label: '.give-activecampaign-optin-label',
291 input: '.give-activecampaign-optin-input',
292 } );
293
294 // Setup mailchimp opt-in event listeners
295 setupCheckbox( {
296 container: '.give-mailchimp-fieldset',
297 label: '.give-mc-message-text',
298 input: 'input[name="give_mailchimp_signup"]',
299 } );
300
301 // Setup constant contact opt-in event listeners
302 setupCheckbox( {
303 container: '.give-constant-contact-fieldset',
304 label: '.give-constant-contact-fieldset span',
305 input: 'input[name="give_constant_contact_signup"]',
306 } );
307
308 // Setup terms and conditions opt-in event listeners
309 setupCheckbox( {
310 container: '#give_terms_agreement',
311 label: '#give_terms_agreement label',
312 input: 'input[name="give_agree_to_terms"]',
313 } );
314
315 // Show Sequoia loader on click/touchend
316 $( 'body.give-form-templates' ).on( 'click touchend', 'form.give-form input[name="give-purchase"].give-submit', function() {
317 //Override submit loader with Sequoia loader
318 $( '#give-purchase-button + .give-loading-animation' ).removeClass( 'give-loading-animation' ).addClass( 'sequoia-loader' );
319
320 // Only show spinner if form is valid
321 if ( $( 'form' ).get( 0 ).checkValidity() ) {
322 $( '.sequoia-loader' ).addClass( 'spinning' );
323 }
324 } );
325
326 // Go to choose amount step when donation maximum error is clicked
327 $( 'body.give-form-templates' ).on( 'click touchend', '#give_error_invalid_donation_maximum', function() {
328 // Go to choose amount step
329 navigator.goToStep( 1 );
330 } );
331
332 // Go to choose amount step when invalid donation error is clicked
333 $( 'body.give-form-templates' ).on( 'click touchend', '#give_error_invalid_donation_amount', function() {
334 // Go to choose amount step
335 navigator.goToStep( 1 );
336 } );
337
338 // Setup gateway icons
339 setupGatewayIcons();
340
341 // Setup Form Field Manager input listeners
342 setupFFMInputs();
343
344 // Setup Icons for inputs
345 setupInputIcons();
346
347 const observer = new window.MutationObserver( function( mutations ) {
348 mutations.forEach( function( mutation ) {
349 if ( ! mutation.addedNodes ) {
350 return;
351 }
352
353 for ( let i = 0; i < mutation.addedNodes.length; i++ ) {
354 // do things to your newly added nodes here
355 const node = mutation.addedNodes[ i ];
356
357 if ( $( node ).find( '.give_error' ).length > 0 ) {
358 moveErrorNotice( $( node ).find( '.give_error' ) );
359 scrollToIframeTop();
360 }
361
362 if ( $( node ).children().hasClass( 'give_errors' ) ) {
363 if ( ! $( node ).parent().hasClass( 'donation-errors' ) ) {
364 $( node ).children( '.give_errors' ).each( function() {
365 const notice = $( this );
366 moveErrorNotice( notice );
367 } );
368 }
369 scrollToIframeTop();
370 }
371
372 if ( $( node ).hasClass( 'give_errors' ) && ! $( node ).parent().hasClass( 'donation-errors' ) ) {
373 moveErrorNotice( $( node ) );
374 $( '.sequoia-loader' ).removeClass( 'spinning' );
375 scrollToIframeTop();
376 }
377
378 if ( $( node ).attr( 'id' ) === 'give_tributes_address_state' ) {
379 const placeholder = $( node ).attr( 'placeholder' );
380 $( node ).prepend( `<option selected disabled>${ placeholder }</option>` );
381 }
382
383 if ( $( node ).attr( 'name' ) === 'give_tributes_address_state' && $( node ).attr( 'class' ).includes( 'give-input' ) ) {
384 $( node ).attr( 'placeholder', $( node ).siblings( 'label' ).text().trim() );
385 }
386
387 if ( $( node ).attr( 'id' ) && $( node ).attr( 'id' ).includes( 'give-checkout-login-register' ) ) {
388 setupRegistrationFormInputFields();
389 }
390
391 if ( $( node ).prop( 'tagName' ) && $( node ).prop( 'tagName' ).toLowerCase() === 'select' ) {
392 const placeholder = $( node ).attr( 'placeholder' );
393 if ( placeholder ) {
394 $( node ).prepend( `<option value="" disabled selected>${ placeholder }</option>` );
395 }
396 }
397 }
398 } );
399 } );
400
401 observer.observe( document.body, {
402 childList: true,
403 subtree: true,
404 attributes: false,
405 characterData: false,
406 } );
407 },
408 },
409 ];
410
411 navigator.init();
412
413 // Check if only a single gateway is enabled
414 if ( $paymentGatewayContainer.length && $paymentGatewayContainer.css( 'display' ) !== 'none' ) {
415 // Move payment information section when document load.
416 moveFieldsUnderPaymentGateway();
417
418 // Move payment information section when gateway updated.
419 $( document ).on( 'give_gateway_loaded', function() {
420 setTimeout( setupTabOrder, 200 );
421
422 moveFieldsUnderPaymentGateway();
423 setupRegistrationFormInputFields();
424 setupFFMInputs();
425 setupInputIcons();
426 setupSelectInputs();
427
428 $( '#give_purchase_form_wrap' ).slideDown( 200, function() {
429 gatewayAnimating = false;
430 } );
431 } );
432 $( document ).on( 'Give:onPreGatewayLoad', function() {
433 gatewayAnimating = true;
434 $( '#give_purchase_form_wrap' ).slideUp( 200 );
435 } );
436
437 // Clear gateway related errors
438 $( document ).on( 'Give:onPreGatewayLoad', function() {
439 const persistedNotices = [
440 'give_error_test_mode',
441 ];
442
443 $( '.give_errors, .give_notices, .give_error' ).each( function() {
444 if ( ! persistedNotices.includes( $( this ).attr( 'id' ) ) ) {
445 $( this ).slideUp( 200, function() {
446 $( this ).remove();
447 } );
448 }
449 } );
450 } );
451 } else {
452 $( '#give_purchase_form_wrap' ).addClass( 'give-single-gateway-wrap' );
453 }
454
455 // Refresh personal information section.
456 $( document ).on( 'give_gateway_loaded', refreshPersonalInformationSection );
457
458 // Setup fields.
459 setupSelectInputs();
460 setupRegistrationFormInputFields();
461 setupFFMInputs();
462 setupInputIcons();
463
464 if ( 'enabled' === templateOptions.payment_amount.decimals_enabled ) {
465 updateFormDonationLevelsLabels();
466 }
467
468 /**
469 * Limited scope of optional input labels, specifically to User Info, see issue #5160.
470 */
471 setupOptionalInputLables(
472 Array.from( document.querySelectorAll( '#give_checkout_user_info input[type="text"]' ) )
473 );
474
475 /**
476 * Denote non-required fields as optional.
477 *
478 * @since 2.8.0
479 *
480 * @param {array} inputs An iteratable list of input elements.
481 */
482 function setupOptionalInputLables( inputs ) {
483 inputs.filter( function( input ) {
484 return ! input.required;
485 } ).map( function( input ) {
486 input.placeholder += templateL10n.optionalLabel;
487 } );
488 }
489
490 /**
491 * Move error notices to error notice container at the top of the payment section
492 * @since 2.7.0
493 * @since 2.12.0 Prevents a race condition that caused an error message not to be visible.
494 * @param {node} node The error notice node to be moved
495 */
496 function moveErrorNotice( node ) {
497 //If the error is inside stripe payment button, do nothing
498 if ( $( node ).parent().hasClass( 'give-stripe-payment-request-button' ) ) {
499 return;
500 }
501
502 // First check if specific donation notice container has been set up
503 if ( $( '.donation-errors' ).length === 0 ) {
504 $( '.payment' ).prepend( '<div class="donation-errors"></div>' );
505 }
506
507 /**
508 * @link https://github.com/impress-org/givewp/issues/5867
509 *
510 * This 1 millisecond timeout prevents a race condition between
511 * the `.donation-errors` element being appended to the form
512 * and relocation of the specific error message node.
513 */
514 setTimeout(function(){
515 // If a specific notice does not already exist, proceed with moving the error
516 if ( typeof $( '.donation-errors' ).html() !== undefined && ! $( '.donation-errors' ).html().includes( $( node ).html() ) ) {
517 $( node ).appendTo( '.donation-errors' );
518 } else {
519 // If the specific notice already exists, do not add it
520 $( node ).remove();
521 }
522 }, 1);
523 }
524
525 /**
526 * Setup registration form input fields.
527 * @since 2.9.6
528 */
529 function setupRegistrationFormInputFields() {
530 const handleInput = function( evt ) {
531 if ( ! $( evt.target ).is( 'input' ) ) {
532 return;
533 }
534
535 if ( $( evt.target ).hasClass( 'give-disabled' ) ) {
536 return;
537 }
538
539 // Registration account fields only contains checkboxes.
540 $( evt.target ).closest( 'label' ).toggleClass( 'checked' );
541 };
542
543 $( '[id*="give-register-account-fields"]' )
544 .off( 'click', handleInput )
545 .on( 'click', handleInput );
546 }
547
548 /**
549 * Add listeners and starting states to FFM inputs
550 * @since 2.7.0
551 */
552 function setupFFMInputs() {
553 $( '#give-ffm-section' )
554 .off( 'click', handleFFMInput )
555 .on( 'click', handleFFMInput );
556
557 $( '#give-ffm-section input' ).each( function() {
558 switch ( $( this ).prop( 'type' ) ) {
559 case 'checkbox': {
560 if ( $( this ).prop( 'checked' ) ) {
561 $( this ).parent().addClass( 'checked' );
562 } else {
563 $( this ).parent().removeClass( 'checked' );
564 }
565 break;
566 }
567 case 'radio': {
568 if ( $( this ).prop( 'checked' ) ) {
569 $( this ).parent().addClass( 'selected' );
570 } else {
571 $( this ).parent().removeClass( 'selected' );
572 }
573 break;
574 }
575 }
576 } );
577 }
578
579 /**
580 * Move form field under payment gateway
581 * @since 2.7.0
582 */
583 function moveFieldsUnderPaymentGateway() {
584 // Check if donate fieldset area has been created, if not set it up below payment gateways
585 // This area is necessary for correctly placing various elements (fee recovery notice, newsletters, submit button, etc)
586 if ( $( '#donate-fieldset' ).length === 0 ) {
587 $( '.give-section.payment' ).append( '<fieldset id="donate-fieldset"></fieldset>' );
588 }
589
590 // Elements to move into donate fieldset (located at bottom of form)
591 // The elements will appear in order of array
592 const donateFieldsetElements = [
593 '.give-constant-contact-fieldset',
594 '.give-activecampaign-fieldset',
595 '.give-mailchimp-fieldset',
596 '#give_terms_agreement',
597 '.give-donation-submit',
598 ];
599
600 // Handle moving elements into donate fieldset
601 donateFieldsetElements.forEach( function( selector ) {
602 if ( $( `#donate-fieldset ${ selector }` ).length === 0 ) {
603 $( `#donate-fieldset ${ selector }` ).remove();
604 $( '#donate-fieldset' ).append( $( `#give_purchase_form_wrap ${ selector }` ) );
605 } else if ( $( `#donate-fieldset ${ selector }` ).html() !== $( `#give_purchase_form_wrap ${ selector }` ).html() && $( `#give_purchase_form_wrap ${ selector }` ).html() !== undefined ) {
606 $( `#donate-fieldset ${ selector }` ).remove();
607 $( '#donate-fieldset' ).append( $( `#give_purchase_form_wrap ${ selector }` ) );
608 } else {
609 $( `#give_purchase_form_wrap ${ selector }` ).remove();
610 }
611 } );
612
613 // Move purchase fields (credit card, billing, etc)
614 $( 'li.give-gateway-option-selected' ).after( $( '#give_purchase_form_wrap' ) );
615
616 // Add gateway class to fields wrapper, indicating which gateway is active
617 const gatewayClass = 'gateway-' + $( '.give-gateway-option-selected input' ).attr( 'value' ).replace( '_', '-' );
618 $( '#give_purchase_form_wrap' ).attr( 'class', gatewayClass );
619 }
620
621 /**
622 * Refresh personal information section
623 *
624 * @since 2.7.0
625 * @since 2.10.2 Rename function
626 * @param {boolean} ev Event object
627 * @param {object} response Response object
628 * @param {number} formID Form ID
629 */
630 function refreshPersonalInformationSection( ev, response, formID ) {
631 if ( navigator.currentStep === 2 ) {
632 $( '.give-form-templates' ).css( 'min-height', '' );
633 }
634
635 const $form = $( `#${ formID }` );
636
637 // This function will run only for embed donation form.
638 // Show payment information section fields.
639 if ( $form.parent().hasClass( 'give-embed-form' ) ) {
640 const data = {
641 action: 'give_cancel_login',
642 form_id: $form.find( '[name="give-form-id"]' ).val(),
643 };
644
645 // AJAX get the payment fields.
646 $.post( Give.fn.getGlobalVar( 'ajaxurl' ), data, function( postResponse ) {
647 const container = $form.find( '[id^=give-checkout-login-register]' );
648
649 // Remove existing personal information field if response contains new fields.
650 if ( container.length && postResponse.fields.includes( 'give_checkout_user_info' ) ) {
651 $form.find( '#give_checkout_user_info' ).remove();
652 }
653
654 container.replaceWith( $.parseJSON( postResponse.fields ) );
655 container.css( { display: 'block' } );
656
657 $form.find( '.give-submit-button-wrap' ).show();
658 } ).done( function() {
659 // Trigger float-labels
660 window.give_fl_trigger();
661
662 setupInputIcons();
663 } );
664 }
665 }
666
667 function setupInputIcon( selector, icon ) {
668 $( selector ).each( function() {
669 if ( $( this ).html() !== '' && $( this ).html().includes( `<i class="fas fa-${ icon }"></i>` ) === false ) {
670 const property = isRTL() ? 'padding-right' : 'padding-left';
671 $( this ).prepend( `<i class="fas fa-${ icon }"></i>` );
672 $( this ).children( 'input, selector' ).each( function() {
673 $( this ).attr( 'style', property + ': 33px!important;' );
674 } );
675 }
676 } );
677 }
678
679 function setupInputIcons() {
680 setupInputIcon( '#give-first-name-wrap', 'user' );
681 setupInputIcon( '#give-email-wrap', 'envelope' );
682 setupInputIcon( '#give-company-wrap', 'building' );
683 setupInputIcon( '#date_field-wrap', 'calendar-alt' );
684 setupInputIcon( '#url_field-wrap', 'globe' );
685 setupInputIcon( '#phone_field-wrap', 'phone' );
686 setupInputIcon( '#give-phone-wrap', 'phone' );
687 setupInputIcon( '#email_field-wrap', 'envelope' );
688 }
689
690 /**
691 * Setup tab order for elements in form
692 *
693 * @since 2.7.0
694 */
695 function setupTabOrder() {
696 $( 'select, button, input, textarea, multiselect, a' ).attr( 'tabindex', -1 );
697
698 const tabOrder = steps[ navigator.currentStep ].tabOrder;
699 tabOrder.forEach( ( selector, index ) => {
700 $( selector ).attr( 'tabindex', index + 1 );
701 } );
702 }
703
704 /**
705 * Loop through gateway li elements and setup fontawesome icons
706 *
707 * @since 2.7.0
708 */
709 function setupGatewayIcons() {
710 $( '#give-gateway-radio-list li' ).each( function() {
711 const value = $( 'input', this ).val();
712 let icon;
713 switch ( value ) {
714 case 'manual':
715 icon = 'fas fa-tools';
716 break;
717 case 'offline':
718 icon = 'fas fa-wallet';
719 break;
720 case 'paypal':
721 icon = 'fab fa-paypal';
722 break;
723 case 'stripe':
724 icon = 'far fa-credit-card';
725 break;
726 case 'stripe_checkout':
727 icon = 'far fa-credit-card';
728 break;
729 case 'stripe_sepa':
730 icon = 'fas fa-university';
731 break;
732 case 'stripe_ach':
733 icon = 'fas fa-university';
734 break;
735 case 'stripe_ideal':
736 icon = 'fas fa-university';
737 break;
738 case 'stripe_becs':
739 icon = 'fas fa-university';
740 break;
741 case 'paypalpro_payflow':
742 icon = 'far fa-credit-card';
743 break;
744 case 'paypal-commerce':
745 icon = 'far fa-credit-card';
746 break;
747 case 'stripe_google_pay':
748 icon = 'fab fa-google';
749 break;
750 case 'stripe_apple_pay':
751 icon = 'fab fa-apple';
752 break;
753 default:
754 icon = 'fas fa-hand-holding-heart';
755 break;
756 }
757 $( this ).append( `<i class="${ icon }"></i>` );
758 } );
759 }
760
761 /**
762 * Setup prominent checkboxes (that use persistent borders on select)
763 *
764 * @since 2.7.0
765 * @param {object} args Argument object containing: container, label, input selectors
766 */
767 function setupCheckbox( { container, label, input } ) {
768 // If checkbox is opted in by default, add border on load
769 if ( $( input ).prop( 'checked' ) === true ) {
770 $( container ).addClass( 'active' );
771 }
772
773 // Persist checkbox input border when selected
774 $( document ).on( 'click', label, function( evt ) {
775 if ( container === label ) {
776 evt.stopPropagation();
777 evt.preventDefault();
778
779 $( input ).prop( 'checked', ! $( input ).prop( 'checked' ) );
780 }
781
782 $( container ).toggleClass( 'active' );
783 } );
784 }
785
786 function setupHeightChangeCallback( callback ) {
787 let lastHeight = 0;
788 function checkHeightChange() {
789 const selector = $( steps[ navigator.currentStep ].selector );
790 const changed = lastHeight !== $( selector ).outerHeight();
791 if ( changed ) {
792 callback( $( selector ).outerHeight() );
793 lastHeight = $( selector ).outerHeight();
794 }
795 window.requestAnimationFrame( checkHeightChange );
796 }
797 window.requestAnimationFrame( checkHeightChange );
798 }
799
800 /**
801 * Get initial step to show donor.
802 *
803 * @since 2.7.0
804 * @returns {number} Step to start on
805 */
806 function getInitialStep() {
807 return Give.fn.getParameterByName( 'showDonationProcessingError' ) || Give.fn.getParameterByName( 'showFailedDonationError' ) ? 2 : 0;
808 }
809
810 /**
811 * Handle updating label classes for FFM radios and checkboxes
812 *
813 * @since 2.7.0
814 * @param {object} evt Reference to FFM input element click event
815 */
816 function handleFFMInput( evt ) {
817 if ( $( evt.target ).is( 'input' ) ) {
818 switch ( $( evt.target ).prop( 'type' ) ) {
819 case 'checkbox': {
820 $( evt.target ).closest( 'label' ).toggleClass( 'checked' );
821 break;
822 }
823 case 'radio': {
824 $( evt.target ).closest( 'label' ).addClass( 'selected' );
825 $( evt.target ).parent().siblings().removeClass( 'selected' );
826 break;
827 }
828 }
829 }
830 }
831
832 function clearLoginNotices() {
833 $( '#give_error_must_log_in' ).remove();
834 }
835
836 /**
837 * Setup select inputs
838 *
839 * @since 2.7.0
840 */
841 function setupSelectInputs() {
842 if ( $( 'select option[selected="selected"][value=""]' ).length > 0 ) {
843 $( 'select option[selected="selected"][value=""]' ).each( function() {
844 if ( $( this ).parent().siblings( 'label' ).length ) {
845 $( this ).text( $( this ).parent().siblings( 'label' ).text().replace( '*', '' ).trim() );
846 $( this ).attr( 'disabled', true );
847 }
848 } );
849 }
850 }
851
852 /**
853 * Check if document is RTL
854 *
855 * @since 2.8.0
856 * @return {boolean} Whether or not document is RTL
857 */
858 function isRTL() {
859 return $( 'html' ).attr( 'dir' ) === 'rtl';
860 }
861
862 /**
863 * Scroll to parent window to iframe top
864 *
865 * @since 2.9.0
866 */
867 function scrollToIframeTop() {
868 if ( 'parentIFrame' in window ) {
869 window.parentIFrame.sendMessage( { action: 'giveScrollIframeInToView' } );
870 }
871 }
872
873 /**
874 * Update decimal donation levels amount
875 *
876 * @since 2.11.0
877 */
878 function updateFormDonationLevelsLabels() {
879 $( '.give-form' ).each( ( i, form ) => {
880 const donationForm = $( form );
881 const donationLevels = Give.form.fn.getVariablePrices( donationForm );
882 const symbol = Give.form.fn.getInfo( 'currency_symbol', donationForm );
883 const position = Give.form.fn.getInfo( 'currency_position', donationForm );
884 const precision = Give.form.fn.getInfo( 'number_decimals', donationForm );
885
886 $.each( donationLevels, function( j, level ) {
887 if ( 'custom' === level.price_id ) {
888 return;
889 }
890
891 const amount = Give.fn.numberHasDecimal( level.amount )
892 ? Give.fn.formatCurrency( level.amount, { symbol, position, precision }, donationForm )
893 : level.amount;
894
895 const donationLevelLabel = '<div class="currency currency--' + position + '">' + symbol + '</div>' + amount;
896
897 donationForm
898 .find( '.give-btn-level-' + level.price_id )
899 .html( donationLevelLabel );
900 } );
901 } );
902 }
903 }( jQuery ) );
904