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