PluginProbe ʕ •ᴥ•ʔ
GiveWP – Donation Plugin and Fundraising Platform / 2.11.0
GiveWP – Donation Plugin and Fundraising Platform v2.11.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 5 years ago
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