PluginProbe ʕ •ᴥ•ʔ
GiveWP – Donation Plugin and Fundraising Platform / 2.10.4
GiveWP – Donation Plugin and Fundraising Platform v2.10.4
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 5 years ago
form.js
853 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 /**
459 * Limited scope of optional input labels, specifically to User Info, see issue #5160.
460 */
461 setupOptionalInputLables(
462 Array.from( document.querySelectorAll( '#give_checkout_user_info input[type="text"]' ) )
463 );
464
465 /**
466 * Denote non-required fields as optional.
467 *
468 * @since 2.8.0
469 *
470 * @param {array} inputs An iteratable list of input elements.
471 */
472 function setupOptionalInputLables( inputs ) {
473 inputs.filter( function( input ) {
474 return ! input.required;
475 } ).map( function( input ) {
476 input.placeholder += templateL10n.optionalLabel;
477 } );
478 }
479
480 /**
481 * Move error notices to error notice container at the top of the payment section
482 * @since 2.7.0
483 * @param {node} node The error notice node to be moved
484 */
485 function moveErrorNotice( node ) {
486 //If the error is inside stripe payment button, do nothing
487 if ( $( node ).parent().hasClass( 'give-stripe-payment-request-button' ) ) {
488 return;
489 }
490
491 // First check if specific donation notice container has been set up
492 if ( $( '.donation-errors' ).length === 0 ) {
493 $( '.payment' ).prepend( '<div class="donation-errors"></div>' );
494 }
495
496 // If a specific notice does not already exist, proceed with moving the error
497 if ( typeof $( '.donation-errors' ).html() !== undefined && ! $( '.donation-errors' ).html().includes( $( node ).html() ) ) {
498 $( node ).appendTo( '.donation-errors' );
499 } else {
500 // If the specific notice already exists, do not add it
501 $( node ).remove();
502 }
503 }
504
505 /**
506 * Setup registration form input fields.
507 * @since 2.9.6
508 */
509 function setupRegistrationFormInputFields() {
510 const handleInput = function( evt ) {
511 if ( ! $( evt.target ).is( 'input' ) ) {
512 return;
513 }
514
515 if ( $( evt.target ).hasClass( 'give-disabled' ) ) {
516 return;
517 }
518
519 // Registration account fields only contains checkboxes.
520 $( evt.target ).closest( 'label' ).toggleClass( 'checked' );
521 };
522
523 $( '[id*="give-register-account-fields"]' )
524 .off( 'click', handleInput )
525 .on( 'click', handleInput );
526 }
527
528 /**
529 * Add listeners and starting states to FFM inputs
530 * @since 2.7.0
531 */
532 function setupFFMInputs() {
533 $( '#give-ffm-section' )
534 .off( 'click', handleFFMInput )
535 .on( 'click', handleFFMInput );
536
537 $( '#give-ffm-section input' ).each( function() {
538 switch ( $( this ).prop( 'type' ) ) {
539 case 'checkbox': {
540 if ( $( this ).prop( 'checked' ) ) {
541 $( this ).parent().addClass( 'checked' );
542 } else {
543 $( this ).parent().removeClass( 'checked' );
544 }
545 break;
546 }
547 case 'radio': {
548 if ( $( this ).prop( 'checked' ) ) {
549 $( this ).parent().addClass( 'selected' );
550 } else {
551 $( this ).parent().removeClass( 'selected' );
552 }
553 break;
554 }
555 }
556 } );
557 }
558
559 /**
560 * Move form field under payment gateway
561 * @since 2.7.0
562 */
563 function moveFieldsUnderPaymentGateway() {
564 // Check if donate fieldset area has been created, if not set it up below payment gateways
565 // This area is necessary for correctly placing various elements (fee recovery notice, newsletters, submit button, etc)
566 if ( $( '#donate-fieldset' ).length === 0 ) {
567 $( '.give-section.payment' ).append( '<fieldset id="donate-fieldset"></fieldset>' );
568 }
569
570 // Elements to move into donate fieldset (located at bottom of form)
571 // The elements will appear in order of array
572 const donateFieldsetElements = [
573 '.give-constant-contact-fieldset',
574 '.give-activecampaign-fieldset',
575 '.give-mailchimp-fieldset',
576 '#give_terms_agreement',
577 '.give-donation-submit',
578 ];
579
580 // Handle moving elements into donate fieldset
581 donateFieldsetElements.forEach( function( selector ) {
582 if ( $( `#donate-fieldset ${ selector }` ).length === 0 ) {
583 $( `#donate-fieldset ${ selector }` ).remove();
584 $( '#donate-fieldset' ).append( $( `#give_purchase_form_wrap ${ selector }` ) );
585 } else if ( $( `#donate-fieldset ${ selector }` ).html() !== $( `#give_purchase_form_wrap ${ selector }` ).html() && $( `#give_purchase_form_wrap ${ selector }` ).html() !== undefined ) {
586 $( `#donate-fieldset ${ selector }` ).remove();
587 $( '#donate-fieldset' ).append( $( `#give_purchase_form_wrap ${ selector }` ) );
588 } else {
589 $( `#give_purchase_form_wrap ${ selector }` ).remove();
590 }
591 } );
592
593 // Move purchase fields (credit card, billing, etc)
594 $( 'li.give-gateway-option-selected' ).after( $( '#give_purchase_form_wrap' ) );
595
596 // Add gateway class to fields wrapper, indicating which gateway is active
597 const gatewayClass = 'gateway-' + $( '.give-gateway-option-selected input' ).attr( 'value' ).replace( '_', '-' );
598 $( '#give_purchase_form_wrap' ).attr( 'class', gatewayClass );
599 }
600
601 /**
602 * Refresh personal information section
603 *
604 * @since 2.7.0
605 * @since 2.10.2 Rename function
606 * @param {boolean} ev Event object
607 * @param {object} response Response object
608 * @param {number} formID Form ID
609 */
610 function refreshPersonalInformationSection( ev, response, formID ) {
611 if ( navigator.currentStep === 2 ) {
612 $( '.give-form-templates' ).css( 'min-height', '' );
613 }
614
615 const $form = $( `#${ formID }` );
616
617 // This function will run only for embed donation form.
618 // Show payment information section fields.
619 if ( $form.parent().hasClass( 'give-embed-form' ) ) {
620 const data = {
621 action: 'give_cancel_login',
622 form_id: $form.find( '[name="give-form-id"]' ).val(),
623 };
624
625 // AJAX get the payment fields.
626 $.post( Give.fn.getGlobalVar( 'ajaxurl' ), data, function( postResponse ) {
627 const container = $form.find( '[id^=give-checkout-login-register]' );
628
629 // Remove existing personal information field if response contains new fields.
630 if ( container.length && postResponse.fields.includes( 'give_checkout_user_info' ) ) {
631 $form.find( '#give_checkout_user_info' ).remove();
632 }
633
634 container.replaceWith( $.parseJSON( postResponse.fields ) );
635 container.css( { display: 'block' } );
636
637 $form.find( '.give-submit-button-wrap' ).show();
638 } ).done( function() {
639 // Trigger float-labels
640 window.give_fl_trigger();
641
642 setupInputIcons();
643 } );
644 }
645 }
646
647 function setupInputIcon( selector, icon ) {
648 $( selector ).each( function() {
649 if ( $( this ).html() !== '' && $( this ).html().includes( `<i class="fas fa-${ icon }"></i>` ) === false ) {
650 const property = isRTL() ? 'padding-right' : 'padding-left';
651 $( this ).prepend( `<i class="fas fa-${ icon }"></i>` );
652 $( this ).children( 'input, selector' ).each( function() {
653 $( this ).attr( 'style', property + ': 33px!important;' );
654 } );
655 }
656 } );
657 }
658
659 function setupInputIcons() {
660 setupInputIcon( '#give-first-name-wrap', 'user' );
661 setupInputIcon( '#give-email-wrap', 'envelope' );
662 setupInputIcon( '#give-company-wrap', 'building' );
663 setupInputIcon( '#date_field-wrap', 'calendar-alt' );
664 setupInputIcon( '#url_field-wrap', 'globe' );
665 setupInputIcon( '#phone_field-wrap', 'phone' );
666 setupInputIcon( '#give-phone-wrap', 'phone' );
667 setupInputIcon( '#email_field-wrap', 'envelope' );
668 }
669
670 /**
671 * Setup tab order for elements in form
672 *
673 * @since 2.7.0
674 */
675 function setupTabOrder() {
676 $( 'select, button, input, textarea, multiselect, a' ).attr( 'tabindex', -1 );
677
678 const tabOrder = steps[ navigator.currentStep ].tabOrder;
679 tabOrder.forEach( ( selector, index ) => {
680 $( selector ).attr( 'tabindex', index + 1 );
681 } );
682 }
683
684 /**
685 * Loop through gateway li elements and setup fontawesome icons
686 *
687 * @since 2.7.0
688 */
689 function setupGatewayIcons() {
690 $( '#give-gateway-radio-list li' ).each( function() {
691 const value = $( 'input', this ).val();
692 let icon;
693 switch ( value ) {
694 case 'manual':
695 icon = 'fas fa-tools';
696 break;
697 case 'offline':
698 icon = 'fas fa-wallet';
699 break;
700 case 'paypal':
701 icon = 'fab fa-paypal';
702 break;
703 case 'stripe':
704 icon = 'far fa-credit-card';
705 break;
706 case 'stripe_checkout':
707 icon = 'far fa-credit-card';
708 break;
709 case 'stripe_sepa':
710 icon = 'fas fa-university';
711 break;
712 case 'stripe_ach':
713 icon = 'fas fa-university';
714 break;
715 case 'stripe_ideal':
716 icon = 'fas fa-university';
717 break;
718 case 'stripe_becs':
719 icon = 'fas fa-university';
720 break;
721 case 'paypalpro_payflow':
722 icon = 'far fa-credit-card';
723 break;
724 case 'paypal-commerce':
725 icon = 'far fa-credit-card';
726 break;
727 case 'stripe_google_pay':
728 icon = 'fab fa-google';
729 break;
730 case 'stripe_apple_pay':
731 icon = 'fab fa-apple';
732 break;
733 default:
734 icon = 'fas fa-hand-holding-heart';
735 break;
736 }
737 $( this ).append( `<i class="${ icon }"></i>` );
738 } );
739 }
740
741 /**
742 * Setup prominent checkboxes (that use persistent borders on select)
743 *
744 * @since 2.7.0
745 * @param {object} args Argument object containing: container, label, input selectors
746 */
747 function setupCheckbox( { container, label, input } ) {
748 // If checkbox is opted in by default, add border on load
749 if ( $( input ).prop( 'checked' ) === true ) {
750 $( container ).addClass( 'active' );
751 }
752
753 // Persist checkbox input border when selected
754 $( document ).on( 'click', label, function( evt ) {
755 if ( container === label ) {
756 evt.stopPropagation();
757 evt.preventDefault();
758
759 $( input ).prop( 'checked', ! $( input ).prop( 'checked' ) );
760 }
761
762 $( container ).toggleClass( 'active' );
763 } );
764 }
765
766 function setupHeightChangeCallback( callback ) {
767 let lastHeight = 0;
768 function checkHeightChange() {
769 const selector = $( steps[ navigator.currentStep ].selector );
770 const changed = lastHeight !== $( selector ).outerHeight();
771 if ( changed ) {
772 callback( $( selector ).outerHeight() );
773 lastHeight = $( selector ).outerHeight();
774 }
775 window.requestAnimationFrame( checkHeightChange );
776 }
777 window.requestAnimationFrame( checkHeightChange );
778 }
779
780 /**
781 * Get initial step to show donor.
782 *
783 * @since 2.7.0
784 * @returns {number} Step to start on
785 */
786 function getInitialStep() {
787 return Give.fn.getParameterByName( 'showDonationProcessingError' ) || Give.fn.getParameterByName( 'showFailedDonationError' ) ? 2 : 0;
788 }
789
790 /**
791 * Handle updating label classes for FFM radios and checkboxes
792 *
793 * @since 2.7.0
794 * @param {object} evt Reference to FFM input element click event
795 */
796 function handleFFMInput( evt ) {
797 if ( $( evt.target ).is( 'input' ) ) {
798 switch ( $( evt.target ).prop( 'type' ) ) {
799 case 'checkbox': {
800 $( evt.target ).closest( 'label' ).toggleClass( 'checked' );
801 break;
802 }
803 case 'radio': {
804 $( evt.target ).closest( 'label' ).addClass( 'selected' );
805 $( evt.target ).parent().siblings().removeClass( 'selected' );
806 break;
807 }
808 }
809 }
810 }
811
812 function clearLoginNotices() {
813 $( '#give_error_must_log_in' ).remove();
814 }
815
816 /**
817 * Setup select inputs
818 *
819 * @since 2.7.0
820 */
821 function setupSelectInputs() {
822 if ( $( 'select option[selected="selected"][value=""]' ).length > 0 ) {
823 $( 'select option[selected="selected"][value=""]' ).each( function() {
824 if ( $( this ).parent().siblings( 'label' ).length ) {
825 $( this ).text( $( this ).parent().siblings( 'label' ).text().replace( '*', '' ).trim() );
826 $( this ).attr( 'disabled', true );
827 }
828 } );
829 }
830 }
831
832 /**
833 * Check if document is RTL
834 *
835 * @since 2.8.0
836 * @return {boolean} Whether or not document is RTL
837 */
838 function isRTL() {
839 return $( 'html' ).attr( 'dir' ) === 'rtl';
840 }
841
842 /**
843 * Scroll to parent window to iframe top
844 *
845 * @since 2.9.0
846 */
847 function scrollToIframeTop() {
848 if ( 'parentIFrame' in window ) {
849 window.parentIFrame.sendMessage( { action: 'giveScrollIframeInToView' } );
850 }
851 }
852 }( jQuery ) );
853