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