PluginProbe ʕ •ᴥ•ʔ
GiveWP – Donation Plugin and Fundraising Platform / 2.20.0
GiveWP – Donation Plugin and Fundraising Platform v2.20.0
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 / Classic / resources / js / form.js
give / src / Views / Form / Templates / Classic / resources / js Last commit date
form.js 4 years ago is-feature-active.js 4 years ago not-jquery.js 4 years ago
form.js
514 lines
1 import h from 'vhtml';
2 import {
3 domIsReady,
4 insertAfter,
5 measureText,
6 nodeFromString,
7 pixelsToEm,
8 pixelsToRem,
9 removeNode,
10 } from './not-jquery.js';
11
12 import {
13 IS_CURRENCY_SWITCHING_ACTIVE,
14 IS_DONATION_SUMMARY_ACTIVE,
15 IS_FEE_RECOVERY_ACTIVE,
16 IS_RECURRING_ACTIVE,
17 IS_STRIPE_ACTIVE,
18 } from './is-feature-active.js';
19
20 // This must be called ASAP (since this is used when DOMContentLoaded happens)
21 // It doesn’t use anything inside the body.
22 IS_STRIPE_ACTIVE && setStripeElementStyles();
23
24 // Transforms document for classic template
25 domIsReady(() => {
26 /* TODO: don’t load this script for the receipt in the first place */
27 if (document.getElementById('give-receipt')) return;
28
29 setContainerMode();
30 movePersonalInfoSectionAfterDonationAmountSection();
31 movePaymentFormInsidePaymentDetailsSection();
32 moveDonateNowButtonSectionAfterDonationAmountSection();
33 setDonationAmountSectionDescription();
34 setPersonalInfoTitle();
35 addPersonalInfoDescription();
36 setPaymentDetailsTitle();
37 addPaymentDetailsDescription();
38 setupDonationLevels();
39 moveDefaultGatewayDataIntoActiveGatewaySection();
40 IS_DONATION_SUMMARY_ACTIVE && moveDonationSummaryAfterDonationAmountSection();
41 IS_FEE_RECOVERY_ACTIVE && attachFeeEvents() && updateFeesAmount();
42 IS_RECURRING_ACTIVE && attachRecurringDonationEvents();
43 splitGatewayResponse();
44 IS_CURRENCY_SWITCHING_ACTIVE && setupCurrencySwitcherSelector();
45 IS_RECURRING_ACTIVE && setRecurringPeriodSelectWidth();
46 addSecurePaymentBadgeToDonateNowSection();
47 moveTestModeMessage();
48 IS_CURRENCY_SWITCHING_ACTIVE && moveCurrencySwitcherMessageOutsideOfWrapper();
49 addFancyBorderWhenChecked();
50 IS_DONATION_SUMMARY_ACTIVE && updateDonationSummaryAmountOnChange();
51 });
52
53 /**
54 * Individual transformations
55 */
56
57 function setContainerMode() {
58 document.body.classList.add(`give-container-${window.classicTemplateOptions.visual_appearance.container_style}`);
59 }
60
61 function moveTestModeMessage() {
62 const testModeMessage = document.querySelector('#give_error_test_mode');
63
64 if (testModeMessage) {
65 if (hasSingleGateway()) {
66 document.querySelector('#give_secure_site_wrapper').before(testModeMessage);
67 } else {
68 document.querySelector('.give-payment-mode-label').after(testModeMessage);
69 }
70 }
71 }
72
73 function movePersonalInfoSectionAfterDonationAmountSection() {
74 insertAfter(
75 document.querySelector('.give-personal-info-section'),
76 document.querySelector('.give-donation-amount-section')
77 );
78 }
79
80 function moveDonateNowButtonSectionAfterDonationAmountSection() {
81 insertAfter(
82 document.querySelector('.give-donate-now-button-section'),
83 document.querySelector('.give-payment-details-section')
84 );
85 }
86
87 function moveDonationSummaryAfterDonationAmountSection() {
88 const donationSummary = document.querySelector('.give-donation-form-summary-section');
89 const paymentDetails = document.querySelector('.give-payment-details-section');
90
91 if (donationSummary.closest('.give-donate-now-button-section')) {
92 // Move when inside give-donate-now-button-section
93 insertAfter(donationSummary, paymentDetails);
94 } else if (donationSummary.closest('.give-personal-info-section')) {
95 // Move to before gateway section inside give-personal-info-section
96 paymentDetails.parentNode.insertBefore(donationSummary, paymentDetails);
97 }
98
99 updateDonationSummaryAmount();
100 }
101
102 function setPersonalInfoTitle() {
103 if (classicTemplateOptions.donor_information.headline) {
104 document.querySelector('.give-personal-info-section legend:first-of-type').textContent =
105 classicTemplateOptions.donor_information.headline;
106 }
107 }
108
109 function addPersonalInfoDescription() {
110 if (classicTemplateOptions.donor_information.description) {
111 insertAfter(
112 nodeFromString(
113 h(
114 'p',
115 {className: 'give-personal-info-description'},
116 classicTemplateOptions.donor_information.description
117 )
118 ),
119 document.querySelector('#give_checkout_user_info legend:first-of-type')
120 );
121 }
122 }
123
124 function setPaymentDetailsTitle() {
125 if (classicTemplateOptions.payment_information.headline) {
126 document.querySelector('.give-payment-mode-label').textContent =
127 classicTemplateOptions.payment_information.headline;
128 }
129 }
130
131 function addPaymentDetailsDescription() {
132 if (classicTemplateOptions.payment_information.description) {
133 insertAfter(
134 nodeFromString(
135 `<p class="give-payment-mode-description">${classicTemplateOptions.payment_information.description}</p>`
136 ),
137 document.querySelector('.give-payment-mode-label')
138 );
139 }
140 }
141
142 function movePaymentFormInsidePaymentDetailsSection() {
143 document.querySelector('.give-payment-details-section').append(document.querySelector('#give_purchase_form_wrap'));
144 }
145
146 function setupDonationLevels() {
147 // If currency switching is available, we need to watch and re-run the setup
148 // for the donation levels. Otherwise, we can just run the setup once with
149 // the global currency settings.
150 if (IS_CURRENCY_SWITCHING_ACTIVE) {
151 // This window object has the supported currencies and their symbols.
152 const supportedCurrencies = JSON.parse(window.give_cs_json_obj).supported_currency;
153
154 const selectedCurrencyInput = document.querySelector('input[name=give-cs-form-currency]');
155
156 // For selected currency value changes, re-run the donation level setup
157 // with the new currency symbol.
158 const selectedCurrencyObserver = new MutationObserver(([selectedCurrencyMutation]) => {
159 const currencyCode = selectedCurrencyMutation.target.value;
160
161 const selectedCurrencyConfig = supportedCurrencies[currencyCode];
162 splitDonationLevelAmountsIntoParts({
163 symbol: selectedCurrencyConfig.symbol,
164 decimalSeparator: selectedCurrencyConfig.setting.decimal_separator,
165 precision: selectedCurrencyConfig.setting.number_decimals,
166 });
167 });
168
169 // Run the donation level setup with the selected currency.
170 const selectedCurrencyConfig = supportedCurrencies[selectedCurrencyInput.value];
171 splitDonationLevelAmountsIntoParts({
172 symbol: selectedCurrencyConfig.symbol,
173 decimalSeparator: selectedCurrencyConfig.setting.decimal_separator,
174 precision: selectedCurrencyConfig.setting.number_decimals,
175 });
176
177 // Start observing the selected currency input.
178 selectedCurrencyObserver.observe(selectedCurrencyInput, {attributeFilter: ['value']});
179 } else splitDonationLevelAmountsIntoParts({});
180 }
181
182 function splitDonationLevelAmountsIntoParts({
183 symbol = window.Give.fn.getGlobalVar('currency_sign'),
184 symbolPosition = window.Give.fn.getGlobalVar('currency_pos'),
185 //thousandsSeparator = window.Give.fn.getGlobalVar('thousands_separator'),
186 decimalSeparator = window.Give.fn.getGlobalVar('decimal_separator'),
187 //precision = Number.parseInt(window.Give.fn.getGlobalVar('number_decimals')),
188 }) {
189 document.querySelectorAll('.give-donation-level-btn:not(.give-btn-level-custom)').forEach((node) => {
190 // if the button has custom text display it as a tooltip
191 if (node.innerHTML !== (symbolPosition === 'before' ? symbol + node.value : node.value + symbol)) {
192 addTooltipToLevel(node);
193 }
194
195 const amount = node.getAttribute('value');
196 const [amountWithoutDecimal, decimalForAmount] = amount.split(decimalSeparator);
197
198 // Use the formatted amount as the ARIA label for node and tooltip.
199 const amountWithSymbol = symbolPosition === 'before' ? `${symbol}${amount}` : `${amount}${symbol}`;
200 if (node.parentNode && node.parentNode.getAttribute('aria-label') == node.getAttribute('aria-label')) {
201 node.parentNode.setAttribute('aria-label', amountWithSymbol);
202 }
203 node.setAttribute('aria-label', amountWithSymbol);
204
205 const CurrencySymbol = ({position}) => h('span', {className: `give-currency-symbol-${position}`}, symbol);
206
207 // This is a visual representation of the amount. The decimal separator
208 // omitted since it is not displayed. The ARIA label includes the
209 // properly formatted amount, so we hide the contents for screen
210 // readers.
211 node.innerHTML = h(
212 'span',
213 {
214 className: 'give-formatted-currency',
215 'aria-hidden': true,
216 },
217 symbolPosition === 'before' && h(CurrencySymbol, {position: 'before'}),
218 h(
219 'span',
220 {className: 'give-amount-formatted'},
221 h('span', {className: 'give-amount-without-decimals'}, amountWithoutDecimal),
222 h('span', {className: 'give-amount-decimal'}, decimalForAmount)
223 ),
224 symbolPosition === 'after' && h(CurrencySymbol, {position: 'after'})
225 );
226 });
227 }
228
229 function addTooltipToLevel(node) {
230 const parent = node.parentNode;
231 if (!node.getAttribute('has-tooltip')) {
232 const tooltip = nodeFromString(
233 h('span', {className: 'give-tooltip hint--top hint--bounce', 'aria-label': node.innerHTML})
234 );
235 if (node.innerHTML.length < 50) {
236 tooltip.classList.add('narrow');
237 }
238 parent.replaceChild(tooltip, node);
239 tooltip.appendChild(node);
240 node.setAttribute('has-tooltip', 'true');
241 }
242 }
243
244 function moveDefaultGatewayDataIntoActiveGatewaySection() {
245 if (hasSingleGateway()) {
246 return;
247 }
248
249 addSelectedGatewayDetails(createGatewayDetails());
250
251 document
252 .querySelector('.give-gateway-details')
253 .append(...document.querySelectorAll('#give_purchase_form_wrap fieldset:not(.give-donation-submit)'));
254
255 removeNode(document.querySelector('#give_purchase_form_wrap'));
256 }
257
258 function attachRecurringDonationEvents() {
259 const recurringPeriod = document.querySelector('[name="give-recurring-period"]');
260
261 if (recurringPeriod) {
262 recurringPeriod.addEventListener('change', function (e) {
263 window.GiveDonationSummary.handleDonorsChoiceRecurringFrequency(e.target, jQuery('.give-form'));
264 });
265
266 document.querySelector('.give-recurring-donors-choice-period')?.addEventListener('change', function () {
267 window.GiveDonationSummary.handleDonorsChoiceRecurringFrequency(recurringPeriod, jQuery('.give-form'));
268 });
269
270 // Admin choice
271 document.querySelector('[name="give-price-id"]')?.addEventListener('change', function (e) {
272 window.GiveDonationSummary.handleAdminDefinedRecurringFrequency(e.target, jQuery('.give-form'));
273 });
274 }
275 }
276
277 function updateRecurringDonationFrequency() {
278 const form = jQuery('.give-form');
279 const donorChoice = document.querySelector('[name="give-recurring-period"]');
280 const adminChoice = document.querySelector('[name="give-price-id"]');
281
282 if (donorChoice) {
283 window.GiveDonationSummary.handleDonorsChoiceRecurringFrequency(donorChoice, form);
284 }
285
286 if (adminChoice) {
287 window.GiveDonationSummary.handleAdminDefinedRecurringFrequency(adminChoice, form);
288 }
289 }
290
291 function updateDonationSummaryAmount() {
292 document.querySelector('[data-tag="amount"]').innerHTML = document.querySelector('#give-amount').value;
293 }
294
295 function updateDonationSummaryAmountOnChange() {
296 document.querySelector('#give-amount').addEventListener('change', function(e){
297 document.querySelector('[data-tag="amount"]').innerHTML = GiveDonationSummary.format_amount(e.target.value, jQuery('.give-form'));
298 } );
299 }
300
301
302 function attachFeeEvents() {
303 const coverFeesCheckbox = document.querySelector('.give_fee_mode_checkbox');
304
305 if (coverFeesCheckbox) {
306 coverFeesCheckbox.addEventListener('change', updateFeesAmount);
307 new MutationObserver(updateFeesAmount).observe(document.querySelector('.give-fee-message-label-text'), {
308 childList: true,
309 });
310 } else {
311 jQuery('.js-give-donation-summary-fees').hide();
312 }
313 }
314
315 function updateFeesAmount() {
316 window.GiveDonationSummary.handleFees(document.querySelector('.give_fee_mode_checkbox'), jQuery('.give-form'));
317 }
318
319 function splitGatewayResponse() {
320 jQuery.ajaxPrefilter(function (options, originalOptions) {
321 if (options.url.includes('?payment-mode=')) {
322 // Override beforeSend callback
323 options.beforeSend = function () {
324 jQuery('.give-donate-now-button-section').block({
325 message: null,
326 overlayCSS: {
327 background: '#fff',
328 opacity: 0.6,
329 },
330 });
331
332 // Remove previous gateway data
333 removeNode(document.querySelector('.give-gateway-details'));
334
335 if (originalOptions.beforeSend instanceof Function) {
336 originalOptions.beforeSend();
337 }
338 };
339 // Override the success callback
340 options.success = function (responseHTML) {
341 // Trigger original success callback
342 originalOptions.success(responseHTML);
343
344 removeNode(document.querySelector('#give_purchase_form_wrap'));
345
346 const gatewayDetails = createGatewayDetails();
347 gatewayDetails.innerHTML = responseHTML;
348
349 // The following both removes the sections from gatewayDetails,
350 // but transplants their content to sections in the form.
351
352 // Transplant the existing personal info content with the markup from the gateway’s HTML
353 document
354 .querySelector('.give-personal-info-section')
355 .replaceChildren(
356 ...gatewayDetails.removeChild(gatewayDetails.querySelector('.give-personal-info-section'))
357 .children
358 );
359 setPersonalInfoTitle();
360 addPersonalInfoDescription();
361
362 // Replace the donation button section with the markup from the gateway’s HTML
363 document
364 .querySelector('.give-donate-now-button-section')
365 .replaceWith(
366 ...gatewayDetails.removeChild(gatewayDetails.querySelector('#give_purchase_submit')).children
367 );
368
369 addSecurePaymentBadgeToDonateNowSection();
370
371 // Donation Summary
372 if (IS_DONATION_SUMMARY_ACTIVE) {
373 document
374 .querySelector('.give-donation-form-summary-section')
375 .replaceChildren(
376 ...gatewayDetails.removeChild(
377 gatewayDetails.querySelector('.give-donation-form-summary-section')
378 ).children
379 );
380
381 window.GiveDonationSummary.initTotal();
382 updateDonationSummaryAmount();
383 IS_FEE_RECOVERY_ACTIVE && updateFeesAmount();
384 }
385
386 // Remove previous gateway data (just in case it was added again by multiple clicks)
387 removeNode(document.querySelector('.give-gateway-details'));
388
389 // Add the gateway details to the form
390 addSelectedGatewayDetails(gatewayDetails);
391
392 // Recurring Donations
393 if (IS_RECURRING_ACTIVE) {
394 updateRecurringDonationFrequency();
395 }
396
397 jQuery('.give-donate-now-button-section').unblock();
398 };
399 }
400 });
401 }
402
403 const createGatewayDetails = () => nodeFromString(`<div class="give-gateway-details"></div>`);
404
405 const addSelectedGatewayDetails = (gatewayDetailsNode) =>
406 document.querySelector('.give-gateway-option-selected > .give-gateway-option').after(gatewayDetailsNode);
407
408 window.GiveClassicTemplate = {
409 share: (element) => {
410 let url = parent.window.location.toString();
411 if (window.Give.fn.getParameterByName('giveDonationAction', url)) {
412 url = window.Give.fn.removeURLParameter(url, 'giveDonationAction');
413 url = window.Give.fn.removeURLParameter(url, 'payment-confirmation');
414 url = window.Give.fn.removeURLParameter(url, 'payment-id');
415 }
416
417 if (element.classList.contains('facebook-btn')) {
418 window.Give.share.fn.facebook(url);
419 } else if (element.classList.contains('twitter-btn')) {
420 window.Give.share.fn.twitter(url, classicTemplateOptions.donation_receipt.twitter_message);
421 }
422
423 return false;
424 },
425 };
426
427 function setupCurrencySwitcherSelector() {
428 window.Give_Currency_Switcher.adjust_dropdown_width = () => {
429 const currencySelect = document.querySelector('.give-cs-select-currency');
430 const currencyText = document.querySelector('.give-currency-symbol');
431 currencySelect.style.setProperty('--currency-text-width', pixelsToRem(measureText(currencyText)));
432 currencySelect.style.width = null;
433 };
434
435 window.Give_Currency_Switcher.adjust_dropdown_width();
436 }
437
438 function setRecurringPeriodSelectWidth() {
439 const select = document.querySelector('.give-recurring-donors-choice-period');
440
441 if (select) {
442 function updateWidth() {
443 select.style.setProperty('--selected-text-width', pixelsToEm(measureText(select, 'value'), select));
444 }
445
446 // Update after the fonts load.
447 // Note: FontFaceSet’s loadingdone doesn’t seem to work in Safari.
448 document.fonts.ready.then(updateWidth);
449
450 // Update when the value changes.
451 select.addEventListener('change', updateWidth);
452 }
453 }
454
455 function addSecurePaymentBadgeToDonateNowSection() {
456 if (window.classicTemplateOptions.visual_appearance.secure_badge === 'enabled') {
457 document
458 .querySelector('.give-donate-now-button-section')
459 .lastChild.after(
460 nodeFromString(
461 h(
462 'aside',
463 {className: 'give-secure-donation-badge-bottom'},
464 h('svg', {className: 'give-form-secure-icon'}, h('use', {href: '#give-icon-lock'})),
465 window.classicTemplateOptions.visual_appearance.secure_badge_text
466 )
467 )
468 );
469 }
470 }
471
472 function setDonationAmountSectionDescription() {
473 if (classicTemplateOptions.donation_amount.description) {
474 document
475 .querySelector('.give-amount-heading')
476 .after(
477 nodeFromString(
478 h('p', {className: 'give-amount-description'}, classicTemplateOptions.donation_amount.description)
479 )
480 );
481 }
482 }
483
484 function moveCurrencySwitcherMessageOutsideOfWrapper() {
485 const currencySwitcherMessage = document.querySelector('.give-currency-switcher-msg-wrap');
486 currencySwitcherMessage.parentNode.after(currencySwitcherMessage);
487 }
488
489 function addFancyBorderWhenChecked() {
490 const nodes = document.querySelectorAll(`.give-donation-amount-section input[type="checkbox"]`);
491
492 nodes.forEach((node) =>
493 node.addEventListener('change', (event) => {
494 event.target.parentNode.classList.toggle('checked-within');
495 })
496 );
497 }
498
499 function setStripeElementStyles() {
500 window.give_stripe_vars.element_font_styles = {
501 cssSrc: document.querySelector('#give-google-font-css')?.href,
502 };
503
504 Object.assign(window.give_stripe_vars.element_base_styles, {
505 color: '#828382',
506 fontFamily: window.getComputedStyle(document.body).fontFamily,
507 fontWeight: 400,
508 });
509 }
510
511 function hasSingleGateway() {
512 return document.getElementById('give-gateway-radio-list').children.length === 1;
513 }
514