PluginProbe ʕ •ᴥ•ʔ
GiveWP – Donation Plugin and Fundraising Platform / 2.17.1
GiveWP – Donation Plugin and Fundraising Platform v2.17.1
4.16.4 4.16.3 4.16.2 4.16.1 4.16.0 4.15.5 4.15.4 4.15.3 4.15.2 4.15.1 4.15.0 2.3.0 2.3.1 2.3.2 2.30.0 2.31.0 2.31.1 2.32.0 2.33.0 2.33.1 2.33.2 2.33.3 2.33.4 2.33.5 2.4.0 2.4.1 2.4.2 2.4.3 2.4.4 2.4.5 2.4.6 2.4.7 2.5.0 2.5.1 2.5.10 2.5.11 2.5.12 2.5.13 2.5.2 2.5.3 2.5.4 2.5.5 2.5.6 2.5.7 2.5.8 2.5.9 2.6.0 2.6.1 2.6.2 2.6.3 2.7.0 2.7.1 2.7.2 2.7.3 2.7.4 2.7.5 2.8.0 2.8.1 2.9.0 2.9.1 2.9.2 2.9.3 2.9.4 2.9.5 2.9.6 2.9.7 3.0.0 3.0.1 3.0.2 3.0.3 3.0.4 3.1.0 3.1.1 3.1.2 3.10.0 3.11.0 3.12.0 3.12.1 3.12.2 3.12.3 3.13.0 3.14.0 3.14.1 3.14.2 3.15.0 3.15.1 3.16.0 3.16.1 3.16.2 3.16.3 3.16.4 3.16.5 3.17.0 3.17.1 3.17.2 3.18.0 3.19.0 3.19.1 3.19.2 3.19.3 3.19.4 3.2.0 3.2.1 3.2.2 3.20.0 3.21.0 3.21.1 3.22.0 3.22.1 3.22.2 3.3.0 3.3.1 3.4.0 3.4.1 3.4.2 3.5.0 3.5.1 3.6.0 3.6.1 3.6.2 3.7.0 3.8.0 3.9.0 4.0.0 4.1.0 4.1.1 4.10.0 4.10.1 4.11.0 4.12.0 4.13.0 4.13.1 4.13.2 4.14.0 4.14.1 4.14.2 4.14.3 4.14.4 4.14.5 4.14.6 4.2.0 4.2.1 4.3.0 4.3.1 4.3.2 4.4.0 4.5.0 4.6.1 4.7.0 4.7.1 4.8.0 4.8.1 4.9.0 trunk 1.9.0 2.0.0 2.0.1 2.0.2 2.0.3 2.0.4 2.0.5 2.0.6 2.0.7 2.1.0 2.1.1 2.1.2 2.1.3 2.1.4 2.1.5 2.1.6 2.1.7 2.1.8 2.10.0 2.10.1 2.10.2 2.10.3 2.10.4 2.11.0 2.11.1 2.11.2 2.11.3 2.12.0 2.12.1 2.12.2 2.12.3 2.13.0 2.13.1 2.13.2 2.13.3 2.13.4 2.14.0 2.15.0 2.16.0 2.16.1 2.17.0 2.17.1 2.17.3 2.18.0 2.18.1 2.19.1 2.19.2 2.19.3 2.19.4 2.19.5 2.19.6 2.19.7 2.19.8 2.2.0 2.2.1 2.2.2 2.2.3 2.2.4 2.2.5 2.2.6 2.20.0 2.20.1 2.20.2 2.21.0 2.21.1 2.21.2 2.21.3 2.21.4 2.22.0 2.22.1 2.22.2 2.22.3 2.23.0 2.23.1 2.23.2 2.24.0 2.24.1 2.24.2 2.25.0 2.25.1 2.25.2 2.25.3 2.26.0 2.27.0 2.27.1 2.27.2 2.27.3 2.28.0 2.29.0 2.29.1 2.29.2
give / src / Views / Form / Templates / Sequoia / assets / js / form.js
give / src / Views / Form / Templates / Sequoia / assets / js Last commit date
form.js 4 years ago
form.js
975 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 // Attach the navigator to the window as an API for changing parts.
22 const navigator = (window.formNavigator = {
23 currentStep: templateOptions.introduction.enabled === 'enabled' ? 0 : 1,
24 animating: false,
25 firstFocus: false,
26 goToStep: (step) => {
27 // Adjust body height before animating step, to prevent choppy iframe resizing
28 // Compare next step to current step, and increase body height if next step is taller.
29 const nextStepHeight = steps[step].title
30 ? $(steps[step].selector).height() + 50
31 : $(steps[step].selector).height();
32 const currentStepHeight = steps[navigator.currentStep].title
33 ? $(steps[navigator.currentStep].selector).height() + 50
34 : $(steps[navigator.currentStep].selector).height();
35 if (nextStepHeight > currentStepHeight) {
36 $('.give-form-templates').css('min-height', `${nextStepHeight + 123}px`);
37 } else {
38 // Delay setting body height if next step is shorter than current step
39 setTimeout(function () {
40 $('.give-form-templates').css('min-height', `${nextStepHeight + 123}px`);
41 }, 200);
42 }
43
44 $('.step-tracker').removeClass('current');
45 $('.step-tracker[data-step="' + step + '"]').addClass('current');
46
47 // Handle introduction step (whether disabled or not).
48 if (templateOptions.introduction.enabled === 'disabled') {
49 if ($('.step-tracker').length === 3) {
50 $('.step-tracker').remove();
51 }
52
53 step = step > 0 ? step : 1;
54 if (step === 1) {
55 $('.back-btn', $container).hide();
56 } else {
57 $('.back-btn', $container).show();
58 }
59
60 $('.give-form-navigator', $container).addClass('nav-visible');
61 $(steps[step].selector).css('padding-top', '50px');
62 } else if (step === 0) {
63 // First step is enabled.
64 $('.give-form-navigator', $container).removeClass('nav-visible');
65 $(steps[step].selector).css('padding-top', '');
66 } else {
67 // Other steps besides intro and payment amounts:
68 $('.give-form-navigator', $container).addClass('nav-visible');
69 $(steps[step].selector).css('padding-top', '50px');
70 // Scroll to top after animation finished
71 // @see https://github.com/impress-org/givewp/issues/5969
72 scrollToIframeTop();
73 }
74
75 if (steps[step].title) {
76 $navigatorTitle.text(steps[step].title);
77 }
78
79 const hide = steps.map((obj, index) => {
80 if (index === step || index === navigator.currentStep) {
81 return null;
82 }
83 return obj.selector;
84 });
85 const hideSelector = hide.filter(Boolean).join(', ');
86
87 $(hideSelector).hide();
88
89 if (navigator.currentStep !== step) {
90 const directionClasses = 'slide-in-right slide-in-left slide-out-right slide-out-left';
91 const outDirection = navigator.currentStep < step ? 'left' : 'right';
92 const inDirection = navigator.currentStep < step ? 'right' : 'left';
93 $(steps[navigator.currentStep].selector)
94 .removeClass(directionClasses)
95 .addClass(`slide-out-${outDirection}`);
96 $(steps[step].selector).show().removeClass(directionClasses).addClass(`slide-in-${inDirection}`);
97 }
98 navigator.currentStep = step;
99
100 setTimeout(function () {
101 setupTabOrder();
102 // Do not auto-focus form on the page load if the first step is disabled
103 if (!navigator.firstFocus && templateOptions.introduction.enabled === 'disabled') {
104 return (navigator.firstFocus = true);
105 }
106 if (steps[navigator.currentStep].firstFocus) {
107 $(steps[navigator.currentStep].firstFocus).trigger('focus');
108 }
109 }, 200);
110 },
111 init: () => {
112 steps.forEach((step) => {
113 if (step.setup !== undefined) {
114 step.setup();
115 }
116 $(step.selector).css('position', 'absolute');
117 });
118 $advanceButton.on('click', function (e) {
119 e.preventDefault();
120 navigator.forward();
121 });
122 $backButton.on('click', function (e) {
123 e.preventDefault();
124 navigator.back();
125 });
126 $('.step-tracker').on('click', function (e) {
127 e.preventDefault();
128 navigator.goToStep(parseInt($(e.target).attr('data-step')));
129 });
130 setupHeightChangeCallback(function (height) {
131 if (gatewayAnimating === false) {
132 $('.form-footer').css('transition', 'margin-top 0.2s ease');
133 } else {
134 $('.form-footer').css('transition', '');
135 }
136 $('.form-footer').css('margin-top', `${height}px`);
137 });
138
139 // If Fee Recovery input is not inside any step, move it to above payment options
140 if ($('.give-fee-recovery-donors-choice').parent().hasClass('give-form')) {
141 $('#give_checkout_user_info').after($('.give-fee-recovery-donors-choice'));
142 }
143 navigator.goToStep(getInitialStep());
144 },
145 back: () => {
146 const prevStep = navigator.currentStep !== 0 ? navigator.currentStep - 1 : 0;
147 navigator.goToStep(prevStep);
148 navigator.currentStep = prevStep;
149 },
150 forward: () => {
151 const nextStep = navigator.currentStep !== null ? navigator.currentStep + 1 : 1;
152 navigator.goToStep(nextStep);
153 navigator.currentStep = nextStep;
154 },
155 });
156
157 const steps = [
158 {
159 id: 'introduction',
160 title: null,
161 selector: '.give-section.introduction',
162 label: templateOptions.introduction.donate_label,
163 showErrors: false,
164 tabOrder: ['.introduction .advance-btn', '.step-tracker'],
165 },
166 {
167 id: 'choose-amount',
168 title: templateOptions.payment_amount.header_label,
169 selector: '.give-section.choose-amount',
170 label: templateOptions.payment_amount.next_label,
171 showErrors: false,
172 tabOrder: [
173 'select.give-cs-select-currency',
174 'input.give-amount-top',
175 '.give-donation-levels-wrap button',
176 '.give-recurring-period',
177 '.give-recurring-donors-choice-period',
178 '.give_fee_mode_checkbox',
179 '.choose-amount .advance-btn',
180 '.step-tracker',
181 '.back-btn',
182 ],
183 firstFocus: '.give-default-level',
184 setup: () => {
185 // Dynamically set grid columns based on number of buttons
186 const buttonCount = $('.give-donation-level-btn').length;
187 if (buttonCount === 1) {
188 $('.give-donation-levels-wrap').attr('style', 'display: none!important;');
189 } else if (buttonCount % 2 === 0 && buttonCount < 6) {
190 $('.give-donation-levels-wrap').css('grid-template-columns', 'repeat(2, minmax(0, 1fr))');
191 }
192
193 $('#give-amount').on('blur', function () {
194 if (!Give.form.fn.isValidDonationAmount($('form'))) {
195 $('.advance-btn').attr('disabled', true);
196 } else {
197 $('.advance-btn').attr('disabled', false);
198 }
199 });
200 $('.give-donation-level-btn').each(function () {
201 const hasTooltip = $(this).attr('has-tooltip');
202 if (hasTooltip) {
203 return;
204 }
205
206 const value = $(this).attr('value');
207 const text = $(this).text();
208 const symbol = window.give_global_vars.currency_sign;
209 const position = window.give_global_vars.currency_pos;
210
211 if (value !== 'custom') {
212 const html =
213 position === 'before'
214 ? `<div class="currency currency--before">${symbol}</div>${value}`
215 : `${value}<div class="currency currency--after">${symbol}</div>`;
216 $(this).html(html);
217 }
218
219 // Setup string to check tooltip label ga
220 const compare = position === 'before' ? symbol + value : value + symbol;
221 // Setup tooltip unless for custom donation level, or if level label matches value
222 if (value !== 'custom' && text !== compare) {
223 const wrap = document.createElement('span');
224 wrap.classList.add('give-tooltip', 'hint--top', 'hint--bounce');
225 if (text.length < 50) {
226 wrap.classList.add('narrow');
227 }
228 wrap.style.width = '100%';
229 wrap.setAttribute('aria-label', text.length < 50 ? text : text.substr(0, 50) + '...');
230 $(this).wrap(wrap);
231 $(this).attr('has-tooltip', true);
232 }
233 });
234 },
235 },
236 {
237 id: 'payment',
238 title: templateOptions.payment_information.header_label,
239 label: templateOptions.payment_information.checkout_label,
240 selector: '.give-section.payment',
241 showErrors: true,
242 tabOrder: [
243 '.payment input, .payment a, .payment button, .payment select, .payment multiselect, .payment textarea, .payment .button',
244 '.give-submit',
245 '.step-tracker',
246 '.back-btn',
247 ],
248 firstFocus: '#give-first',
249 setup: () => {
250 // Setup payment information screen
251
252 $('.give-section.payment').on(
253 'click',
254 '.give-cancel-login, .give-checkout-register-cancel',
255 clearLoginNotices
256 );
257
258 // Show Sequoia loader on click/touchend
259 $('.give-section.payment').on('click touchend', 'input[name="give_login_submit"]', function () {
260 //Override submit loader with Sequoia loader
261 $('input[name="give_login_submit"] + .give-loading-animation')
262 .removeClass('give-loading-animation')
263 .addClass('sequoia-loader spinning');
264 });
265
266 // Remove purchase_loading text
267 window.give_global_vars.purchase_loading = '';
268
269 // Move errors
270 $('.give_error').each(function () {
271 moveErrorNotice($(this));
272 });
273
274 // Setup anonymous donations opt-in event listeners
275 setupCheckbox({
276 container: '#give-anonymous-donation-wrap label',
277 label: '#give-anonymous-donation-wrap label',
278 input: '#give-anonymous-donation',
279 });
280
281 // Setup recurring donations opt-in event listeners
282 setupCheckbox({
283 container: '.give-recurring-donors-choice',
284 label: '.give-recurring-donors-choice label',
285 input: 'input[name="give-recurring-period"]',
286 });
287
288 // Setup fee recovery opt-in event listeners
289 setupCheckbox({
290 container: '.give-fee-recovery-donors-choice',
291 label: '.give-fee-message-label-text',
292 input: 'input[name="give_fee_mode_checkbox"]',
293 });
294
295 // Setup activecampaign opt-in event listeners
296 setupCheckbox({
297 container: '.give-activecampaign-fieldset',
298 label: '.give-activecampaign-optin-label',
299 input: '.give-activecampaign-optin-input',
300 });
301
302 // Setup mailchimp opt-in event listeners
303 setupCheckbox({
304 container: '.give-mailchimp-fieldset',
305 label: '.give-mc-message-text',
306 input: 'input[name="give_mailchimp_signup"]',
307 });
308
309 // Setup constant contact opt-in event listeners
310 setupCheckbox({
311 container: '.give-constant-contact-fieldset',
312 label: '.give-constant-contact-fieldset span',
313 input: 'input[name="give_constant_contact_signup"]',
314 });
315
316 // Setup terms and conditions opt-in event listeners
317 setupCheckbox({
318 container: '#give_terms_agreement',
319 label: '#give_terms_agreement label',
320 input: 'input[name="give_agree_to_terms"]',
321 });
322
323 // Show Sequoia loader on click/touchend
324 $('body.give-form-templates').on(
325 'click touchend',
326 'form.give-form input[name="give-purchase"].give-submit',
327 function () {
328 //Override submit loader with Sequoia loader
329 $('#give-purchase-button + .give-loading-animation')
330 .removeClass('give-loading-animation')
331 .addClass('sequoia-loader');
332
333 // Only show spinner if form is valid
334 if ($('form').get(0).checkValidity()) {
335 $('.sequoia-loader').addClass('spinning');
336 }
337 }
338 );
339
340 // Go to choose amount step when donation maximum error is clicked
341 $('body.give-form-templates').on('click touchend', '#give_error_invalid_donation_maximum', function () {
342 // Go to choose amount step
343 navigator.goToStep(1);
344 });
345
346 // Go to choose amount step when invalid donation error is clicked
347 $('body.give-form-templates').on('click touchend', '#give_error_invalid_donation_amount', function () {
348 // Go to choose amount step
349 navigator.goToStep(1);
350 });
351
352 // Setup gateway icons
353 setupGatewayIcons();
354
355 // Setup Form Field Manager input listeners
356 setupFFMInputs();
357
358 // Setup Icons for inputs
359 setupInputIcons();
360
361 const observer = new window.MutationObserver(function (mutations) {
362 mutations.forEach(function (mutation) {
363 if (!mutation.addedNodes) {
364 return;
365 }
366
367 for (let i = 0; i < mutation.addedNodes.length; i++) {
368 // do things to your newly added nodes here
369 const node = mutation.addedNodes[i];
370
371 if ($(node).find('.give_error').length > 0) {
372 moveErrorNotice($(node).find('.give_error'));
373 scrollToIframeTop();
374 }
375
376 if ($(node).children().hasClass('give_errors')) {
377 if (!$(node).parent().hasClass('donation-errors')) {
378 $(node)
379 .children('.give_errors')
380 .each(function () {
381 const notice = $(this);
382 moveErrorNotice(notice);
383 });
384 }
385 scrollToIframeTop();
386 }
387
388 if ($(node).hasClass('give_errors') && !$(node).parent().hasClass('donation-errors')) {
389 moveErrorNotice($(node));
390 $('.sequoia-loader').removeClass('spinning');
391 scrollToIframeTop();
392 }
393
394 if ($(node).attr('id') === 'give_tributes_address_state') {
395 const placeholder = $(node).attr('placeholder');
396 $(node).prepend(`<option selected disabled>${placeholder}</option>`);
397 }
398
399 if (
400 $(node).attr('name') === 'give_tributes_address_state' &&
401 $(node).attr('class').includes('give-input')
402 ) {
403 $(node).attr('placeholder', $(node).siblings('label').text().trim());
404 }
405
406 if ($(node).attr('id') && $(node).attr('id').includes('give-checkout-login-register')) {
407 setupRegistrationFormInputFields();
408 }
409
410 if ($(node).prop('tagName') && $(node).prop('tagName').toLowerCase() === 'select') {
411 const placeholder = $(node).attr('placeholder');
412 if (placeholder) {
413 $(node).prepend(`<option value="" disabled selected>${placeholder}</option>`);
414 }
415 }
416 }
417 });
418 });
419
420 observer.observe(document.body, {
421 childList: true,
422 subtree: true,
423 attributes: false,
424 characterData: false,
425 });
426 },
427 },
428 ];
429
430 navigator.init();
431
432 // Check if only a single gateway is enabled
433 if ($paymentGatewayContainer.length && $paymentGatewayContainer.css('display') !== 'none') {
434 // Move payment information section when document load.
435 moveFieldsUnderPaymentGateway();
436
437 // Move payment information section when gateway updated.
438 $(document).on('give_gateway_loaded', function () {
439 setTimeout(setupTabOrder, 200);
440
441 moveFieldsUnderPaymentGateway();
442 setupLegacyConsumerCheckboxAndRadio();
443 setupRegistrationFormInputFields();
444 setupFFMInputs();
445 setupInputIcons();
446 setupSelectInputs();
447
448 $('#give_purchase_form_wrap').slideDown(200, function () {
449 gatewayAnimating = false;
450 });
451 });
452 $(document).on('Give:onPreGatewayLoad', function () {
453 gatewayAnimating = true;
454 $('#give_purchase_form_wrap').slideUp(200);
455 });
456
457 // Clear gateway related errors
458 $(document).on('Give:onPreGatewayLoad', function () {
459 const persistedNotices = ['give_error_test_mode'];
460
461 $('.give_errors, .give_notices, .give_error').each(function () {
462 if (!persistedNotices.includes($(this).attr('id'))) {
463 $(this).slideUp(200, function () {
464 $(this).remove();
465 });
466 }
467 });
468 });
469 } else {
470 $('#give_purchase_form_wrap').addClass('give-single-gateway-wrap');
471 }
472
473 // Refresh personal information section.
474 $(document).on('give_gateway_loaded', refreshPersonalInformationSection);
475
476 // Setup fields.
477 setupLegacyConsumerCheckboxAndRadio();
478 setupSelectInputs();
479 setupRegistrationFormInputFields();
480 setupFFMInputs();
481 setupInputIcons();
482
483 /**
484 * Limited scope of optional input labels, specifically to User Info, see issue #5160.
485 */
486 setupOptionalInputLabels(Array.from(document.querySelectorAll('#give_checkout_user_info input[type="text"]')));
487
488 /**
489 * Denote non-required fields as optional.
490 *
491 * @since 2.8.0
492 *
493 * @param {array} inputs An iteratable list of input elements.
494 */
495 function setupOptionalInputLabels(inputs) {
496 inputs
497 .filter(function (input) {
498 return !input.required;
499 })
500 .map(function (input) {
501 input.placeholder += templateL10n.optionalLabel;
502 });
503 }
504
505 /**
506 * Move error notices to error notice container at the top of the payment section
507 * @since 2.7.0
508 * @since 2.12.0 Prevents a race condition that caused an error message not to be visible.
509 * @param {node} node The error notice node to be moved
510 */
511 function moveErrorNotice(node) {
512 //If the error is inside stripe payment button, do nothing
513 if ($(node).parent().hasClass('give-stripe-payment-request-button')) {
514 return;
515 }
516
517 // First check if specific donation notice container has been set up
518 if ($('.donation-errors').length === 0) {
519 $('.payment').prepend('<div class="donation-errors"></div>');
520 }
521
522 /**
523 * @link https://github.com/impress-org/givewp/issues/5867
524 *
525 * This 1 millisecond timeout prevents a race condition between
526 * the `.donation-errors` element being appended to the form
527 * and relocation of the specific error message node.
528 */
529 setTimeout(function () {
530 // If a specific notice does not already exist, proceed with moving the error
531 if (
532 typeof $('.donation-errors').html() !== undefined &&
533 !$('.donation-errors').html().includes($(node).html())
534 ) {
535 $(node).appendTo('.donation-errors');
536 } else {
537 // If the specific notice already exists, do not add it
538 $(node).remove();
539 }
540 }, 1);
541 }
542
543 /**
544 * Setup registration form input fields.
545 * @since 2.9.6
546 */
547 function setupRegistrationFormInputFields() {
548 const handleInput = function (evt) {
549 if (!$(evt.target).is('input')) {
550 return;
551 }
552
553 if ($(evt.target).hasClass('give-disabled')) {
554 return;
555 }
556
557 // Registration account fields only contains checkboxes.
558 $(evt.target).closest('label').toggleClass('checked');
559 };
560
561 $('[id*="give-register-account-fields"]').off('click', handleInput).on('click', handleInput);
562 }
563
564 /**
565 * Add listeners and starting states to FFM inputs
566 * @since 2.7.0
567 */
568 function setupFFMInputs() {
569 $('#give-ffm-section').off('click', handleFFMInput).on('click', handleFFMInput);
570
571 $('#give-ffm-section input').each(function () {
572 switch ($(this).prop('type')) {
573 case 'checkbox': {
574 if ($(this).prop('checked')) {
575 $(this).parent().addClass('checked');
576 } else {
577 $(this).parent().removeClass('checked');
578 }
579 break;
580 }
581 case 'radio': {
582 if ($(this).prop('checked')) {
583 $(this).parent().addClass('selected');
584 } else {
585 $(this).parent().removeClass('selected');
586 }
587 break;
588 }
589 }
590 });
591 }
592
593 /**
594 * Handle updating label classes for FFM radios and checkboxes
595 *
596 * @since 2.7.0
597 * @param {object} evt Reference to FFM input element click event
598 */
599 function handleFFMInput(evt) {
600 if ($(evt.target).is('input')) {
601 switch ($(evt.target).prop('type')) {
602 case 'checkbox': {
603 $(evt.target).closest('label').toggleClass('checked');
604 break;
605 }
606 case 'radio': {
607 $(evt.target).closest('label').addClass('selected');
608 $(evt.target).parent().siblings().removeClass('selected');
609 break;
610 }
611 }
612 }
613 }
614
615 /**
616 * Move form field under payment gateway
617 * @since 2.7.0
618 */
619 function moveFieldsUnderPaymentGateway() {
620 // Check if donate fieldset area has been created, if not set it up below payment gateways
621 // This area is necessary for correctly placing various elements (fee recovery notice, newsletters, submit button, etc)
622 if ($('#donate-fieldset').length === 0) {
623 $('.give-section.payment').append('<fieldset id="donate-fieldset"></fieldset>');
624 }
625
626 // Elements to move into donate fieldset (located at bottom of form)
627 // The elements will appear in order of array
628 const donateFieldsetElements = [
629 '.give-constant-contact-fieldset',
630 '.give-activecampaign-fieldset',
631 '.give-mailchimp-fieldset',
632 '#give_terms_agreement',
633 '.give-donation-submit',
634 ];
635
636 // Handle moving elements into donate fieldset
637 donateFieldsetElements.forEach(function (selector) {
638 if ($(`#donate-fieldset ${selector}`).length === 0) {
639 $(`#donate-fieldset ${selector}`).remove();
640 $('#donate-fieldset').append($(`#give_purchase_form_wrap ${selector}`));
641 } else if (
642 $(`#donate-fieldset ${selector}`).html() !== $(`#give_purchase_form_wrap ${selector}`).html() &&
643 $(`#give_purchase_form_wrap ${selector}`).html() !== undefined
644 ) {
645 $(`#donate-fieldset ${selector}`).remove();
646 $('#donate-fieldset').append($(`#give_purchase_form_wrap ${selector}`));
647 } else {
648 $(`#give_purchase_form_wrap ${selector}`).remove();
649 }
650 });
651
652 // Move purchase fields (credit card, billing, etc)
653 $('li.give-gateway-option-selected').after($('#give_purchase_form_wrap'));
654
655 // Add gateway class to fields wrapper, indicating which gateway is active
656 const gatewayClass = 'gateway-' + $('.give-gateway-option-selected input').attr('value').replace('_', '-');
657 $('#give_purchase_form_wrap').attr('class', gatewayClass);
658 }
659
660 /**
661 * Refresh personal information section
662 *
663 * @since 2.7.0
664 * @since 2.10.2 Rename function
665 * @param {boolean} ev Event object
666 * @param {object} response Response object
667 * @param {number} formID Form ID
668 */
669 function refreshPersonalInformationSection(ev, response, formID) {
670 if (navigator.currentStep === 2) {
671 $('.give-form-templates').css('min-height', '');
672 }
673
674 const $form = $(`#${formID}`);
675
676 // This function will run only for embed donation form.
677 // Show payment information section fields.
678 if ($form.parent().hasClass('give-embed-form')) {
679 const data = {
680 action: 'give_cancel_login',
681 form_id: $form.find('[name="give-form-id"]').val(),
682 };
683
684 // AJAX get the payment fields.
685 $.post(Give.fn.getGlobalVar('ajaxurl'), data, function (postResponse) {
686 const container = $form.find('[id^=give-checkout-login-register]');
687
688 // Remove existing personal information field if response contains new fields.
689 if (container.length && postResponse.fields.includes('give_checkout_user_info')) {
690 $form.find('#give_checkout_user_info').remove();
691 }
692
693 container.replaceWith($.parseJSON(postResponse.fields));
694 container.css({display: 'block'});
695
696 $form.find('.give-submit-button-wrap').show();
697 }).done(function () {
698 // Trigger float-labels
699 window.give_fl_trigger();
700
701 setupInputIcons();
702 });
703 }
704 }
705
706 function setupInputIcon(selector, icon) {
707 $(selector).each(function () {
708 if ($(this).html() !== '' && $(this).html().includes(`<i class="fas fa-${icon}"></i>`) === false) {
709 const property = isRTL() ? 'padding-right' : 'padding-left';
710 $(this).prepend(`<i class="fas fa-${icon}"></i>`);
711 $(this)
712 .children('input, selector')
713 .each(function () {
714 $(this).attr('style', property + ': 33px!important;');
715 });
716 }
717 });
718 }
719
720 function setupInputIcons() {
721 setupInputIcon('#give-first-name-wrap', 'user');
722 setupInputIcon('#give-email-wrap', 'envelope');
723 setupInputIcon('#give-company-wrap', 'building');
724 setupInputIcon('#date_field-wrap', 'calendar-alt');
725 setupInputIcon('#url_field-wrap', 'globe');
726 setupInputIcon('#phone_field-wrap', 'phone');
727 setupInputIcon('#give-phone-wrap', 'phone');
728 setupInputIcon('#email_field-wrap', 'envelope');
729 }
730
731 /**
732 * Setup tab order for elements in form
733 *
734 * @since 2.7.0
735 */
736 function setupTabOrder() {
737 $('select, button, input, textarea, multiselect, a').attr('tabindex', -1);
738
739 const tabOrder = steps[navigator.currentStep].tabOrder;
740 tabOrder.forEach((selector, index) => {
741 $(selector).attr('tabindex', index + 1);
742 });
743 }
744
745 /**
746 * Loop through gateway li elements and setup fontawesome icons
747 *
748 * @since 2.7.0
749 */
750 function setupGatewayIcons() {
751 $('#give-gateway-radio-list li').each(function () {
752 const value = $('input', this).val();
753 let icon;
754 switch (value) {
755 case 'manual':
756 icon = 'fas fa-tools';
757 break;
758 case 'offline':
759 icon = 'fas fa-wallet';
760 break;
761 case 'paypal':
762 icon = 'fab fa-paypal';
763 break;
764 case 'stripe':
765 icon = 'far fa-credit-card';
766 break;
767 case 'stripe_checkout':
768 icon = 'far fa-credit-card';
769 break;
770 case 'stripe_sepa':
771 icon = 'fas fa-university';
772 break;
773 case 'stripe_ach':
774 icon = 'fas fa-university';
775 break;
776 case 'stripe_ideal':
777 icon = 'fas fa-university';
778 break;
779 case 'stripe_becs':
780 icon = 'fas fa-university';
781 break;
782 case 'paypalpro_payflow':
783 icon = 'far fa-credit-card';
784 break;
785 case 'paypal-commerce':
786 icon = 'far fa-credit-card';
787 break;
788 case 'stripe_google_pay':
789 icon = 'fab fa-google';
790 break;
791 case 'stripe_apple_pay':
792 icon = 'fab fa-apple';
793 break;
794 default:
795 icon = 'fas fa-hand-holding-heart';
796 break;
797 }
798 $(this).append(`<i class="${icon}"></i>`);
799 });
800 }
801
802 /**
803 * Setup prominent checkboxes (field api) (that use persistent borders on select)
804 *
805 * @since 2.14.0
806 */
807 function setupLegacyConsumerCheckboxAndRadio() {
808 const customCheckboxes = document.querySelectorAll('[data-field-type="checkbox"] input');
809 const customRadios = document.querySelectorAll('[data-field-type="radio"] input');
810 Array.from(customCheckboxes).forEach((el) => {
811 const uniqueInputSelector = `#${el.getAttribute('id')}`;
812 const uniqueLabelSelector = `label[for=${el.getAttribute('id')}]`;
813 setupCheckbox({
814 container: uniqueLabelSelector,
815 label: uniqueLabelSelector,
816 input: uniqueInputSelector,
817 });
818 });
819
820 Array.from(customRadios).forEach((el) => {
821 const uniqueInputSelector = `#${el.getAttribute('id')}`;
822 const uniqueLabelSelector = `label[for=${el.getAttribute('id')}]`;
823 setupRadio({
824 label: uniqueLabelSelector,
825 input: uniqueInputSelector,
826 });
827 });
828 }
829
830 /**
831 * Setup prominent checkboxes (that use persistent borders on select)
832 *
833 * @since 2.7.0
834 * @since 2.15.0 update click handler callback
835 * @param {object} args Argument object containing: container, label, input selectors
836 */
837 function setupCheckbox({container, label, input}) {
838 // If checkbox is opted in by default, add border on load
839 if ($(input).prop('checked') === true) {
840 $(container).addClass('active');
841 }
842
843 // Persist checkbox input border when selected
844 $(document).on('click', label, function (evt) {
845 if ('INPUT' === evt.target.nodeName) {
846 return;
847 }
848
849 $(container).toggleClass('active');
850 });
851 }
852
853 /**
854 * Handle updating label classes for FFM radios and checkboxes
855 *
856 * @since 2.7.0
857 * @since 2.15.0 update click handler callback
858 * @param {object} evt Reference to FFM input element click event
859 */
860 function setupRadio({label, input}) {
861 // If checkbox is opted in by default, add border on load
862 if ($(input).prop('checked') === true) {
863 $(label).addClass('active');
864 }
865
866 // Persist checkbox input border when selected
867 $(document).on('click', label, function (evt) {
868 if ('INPUT' === evt.target.nodeName) {
869 return;
870 }
871
872 $(evt.target.parentElement).find('label').not(evt.target).removeClass('active');
873 $(evt.target).toggleClass('active');
874 });
875 }
876
877 function setupHeightChangeCallback(callback) {
878 let lastHeight = 0;
879 function checkHeightChange() {
880 const selector = $(steps[navigator.currentStep].selector);
881 const changed = lastHeight !== $(selector).outerHeight();
882 if (changed) {
883 callback($(selector).outerHeight());
884 lastHeight = $(selector).outerHeight();
885 }
886 window.requestAnimationFrame(checkHeightChange);
887 }
888 window.requestAnimationFrame(checkHeightChange);
889 }
890
891 /**
892 * Get initial step to show donor.
893 *
894 * @since 2.7.0
895 * @returns {number} Step to start on
896 */
897 function getInitialStep() {
898 return Give.fn.getParameterByName('showDonationProcessingError') ||
899 Give.fn.getParameterByName('showFailedDonationError')
900 ? 2
901 : 0;
902 }
903
904 function clearLoginNotices() {
905 $('#give_error_must_log_in').remove();
906 }
907
908 /**
909 * Setup select inputs
910 *
911 * @since 2.7.0
912 */
913 function setupSelectInputs() {
914 if ($('select option[selected="selected"][value=""]').length > 0) {
915 $('select option[selected="selected"][value=""]').each(function () {
916 if ($(this).parent().siblings('label').length) {
917 $(this).text($(this).parent().siblings('label').text().replace('*', '').trim());
918 $(this).attr('disabled', true);
919 }
920 });
921 }
922 }
923
924 /**
925 * Check if document is RTL
926 *
927 * @since 2.8.0
928 * @return {boolean} Whether or not document is RTL
929 */
930 function isRTL() {
931 return $('html').attr('dir') === 'rtl';
932 }
933
934 /**
935 * Scroll to parent window to iframe top
936 *
937 * @since 2.9.0
938 */
939 function scrollToIframeTop() {
940 if ('parentIFrame' in window) {
941 window.parentIFrame.sendMessage({action: 'giveScrollIframeInToView'});
942 }
943 }
944 })(jQuery);
945
946 /**
947 * Support to FFM restore custom field value logic.
948 *
949 * FFM restore value of custom fields correctly but field state does not reflect correctly
950 * in donation form because we are using custom styling for Radio and Checkbox.
951 *
952 * Below code will add class to Checkbox and Radio label if checked.
953 *
954 * @since 2.15.0
955 */
956 document.addEventListener('readystatechange', function (evt) {
957 if (evt.target.readyState !== 'complete') {
958 return;
959 }
960
961 const customCheckboxes = document.querySelectorAll('[data-field-type="checkbox"] input');
962 const customRadios = document.querySelectorAll('[data-field-type="radio"] input');
963
964 const addActiveClass = (el) => {
965 if (el.checked) {
966 el.parentElement.classList.add('active');
967 } else {
968 el.parentElement.classList.remove('active');
969 }
970 };
971
972 customCheckboxes.forEach(addActiveClass);
973 customRadios.forEach(addActiveClass);
974 });
975