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