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