summary.js
231 lines
| 1 | import accounting from 'accounting'; |
| 2 | |
| 3 | /** |
| 4 | * @since 2.17.0 |
| 5 | */ |
| 6 | window.GiveDonationSummary = { |
| 7 | init: function () { |
| 8 | GiveDonationSummary.initAmount(); |
| 9 | GiveDonationSummary.initFrequency(); |
| 10 | GiveDonationSummary.initFees(); |
| 11 | GiveDonationSummary.initTotal(); |
| 12 | }, |
| 13 | |
| 14 | /** |
| 15 | * @since 2.17.0 |
| 16 | */ |
| 17 | initAmount: function () { |
| 18 | GiveDonationSummary.observe('[name="give-amount"]', function (targetNode, $form) { |
| 19 | $form.find('[data-tag="amount"]').html(GiveDonationSummary.format_amount(targetNode.value, $form)); |
| 20 | }); |
| 21 | }, |
| 22 | |
| 23 | /** |
| 24 | * Recurring frequency |
| 25 | * |
| 26 | * @since 2.17.0 |
| 27 | */ |
| 28 | initFrequency: function () { |
| 29 | // Donor's Choice Recurring |
| 30 | GiveDonationSummary.observe( |
| 31 | '[name="give-recurring-period"]', |
| 32 | GiveDonationSummary.handleDonorsChoiceRecurringFrequency |
| 33 | ); |
| 34 | |
| 35 | // Admin Defined Recurring |
| 36 | GiveDonationSummary.observe('[name="give-price-id"]', GiveDonationSummary.handleAdminDefinedRecurringFrequency); |
| 37 | |
| 38 | // Admin Defined Recurring - "Set Donation" |
| 39 | GiveDonationSummary.observe( |
| 40 | '[name="_give_is_donation_recurring"]', |
| 41 | GiveDonationSummary.handleAdminDefinedSetDonationFrequency |
| 42 | ); |
| 43 | |
| 44 | // Admin Defined Recurring - "Multi-level" |
| 45 | GiveDonationSummary.observe('[name="give-price-id"]', GiveDonationSummary.handleAdminDefinedRecurringFrequency); |
| 46 | }, |
| 47 | |
| 48 | /** |
| 49 | * @since 2.18.0 |
| 50 | */ |
| 51 | handleDonorsChoiceRecurringFrequency: function (targetNode, $form) { |
| 52 | $form.find('.js-give-donation-summary-frequency-help-text').toggle(!targetNode.checked); |
| 53 | $form.find('[data-tag="frequency"]').toggle(!targetNode.checked); |
| 54 | $form.find('[data-tag="recurring"]').toggle(targetNode.checked).html(targetNode.dataset['periodLabel']); |
| 55 | |
| 56 | // Donor's Choice Period |
| 57 | const donorsChoice = document.querySelector('[name="give-recurring-period-donors-choice"]'); |
| 58 | if (donorsChoice) { |
| 59 | const donorsChoiceValue = donorsChoice.options[donorsChoice.selectedIndex].value || false; |
| 60 | if (donorsChoiceValue) { |
| 61 | $form |
| 62 | .find('[data-tag="recurring"]') |
| 63 | .html(GiveDonationSummaryData.recurringLabelLookup[donorsChoiceValue]); |
| 64 | } |
| 65 | } |
| 66 | }, |
| 67 | |
| 68 | /** |
| 69 | * @since 2.18.0 |
| 70 | */ |
| 71 | handleAdminDefinedRecurringFrequency: function (targetNode, $form) { |
| 72 | const priceID = targetNode.value; |
| 73 | const recurringDetailsEl = document.querySelector('.give_recurring_donation_details'); |
| 74 | |
| 75 | if (!recurringDetailsEl) { |
| 76 | return; |
| 77 | } |
| 78 | |
| 79 | const recurringDetails = JSON.parse(recurringDetailsEl.value); |
| 80 | |
| 81 | if ('undefined' !== typeof recurringDetails['multi']) { |
| 82 | const isRecurring = 'yes' === recurringDetails['multi'][priceID]['_give_recurring']; |
| 83 | const periodLabel = recurringDetails['multi'][priceID]['give_recurring_pretty_text']; |
| 84 | |
| 85 | $form.find('.js-give-donation-summary-frequency-help-text').toggle(!isRecurring); |
| 86 | $form.find('[data-tag="frequency"]').toggle(!isRecurring); |
| 87 | $form.find('[data-tag="recurring"]').toggle(isRecurring).html(periodLabel); |
| 88 | } |
| 89 | }, |
| 90 | |
| 91 | handleAdminDefinedSetDonationFrequency: function (targetNode, $form) { |
| 92 | const isRecurring = targetNode.value; |
| 93 | const adminChoice = document.querySelector('.give-recurring-admin-choice'); |
| 94 | |
| 95 | if (isRecurring && adminChoice) { |
| 96 | $form.find('.js-give-donation-summary-frequency-help-text').toggle(!isRecurring); |
| 97 | $form.find('[data-tag="frequency"]').toggle(!isRecurring); |
| 98 | $form.find('[data-tag="recurring"]').html(adminChoice.textContent); |
| 99 | } |
| 100 | }, |
| 101 | |
| 102 | /** |
| 103 | * @since 2.17.0 |
| 104 | */ |
| 105 | initFees: function () { |
| 106 | GiveDonationSummary.observe('.give_fee_mode_checkbox', GiveDonationSummary.handleFees); |
| 107 | }, |
| 108 | |
| 109 | /** |
| 110 | * @since 2.18.0 |
| 111 | */ |
| 112 | handleFees: function (targetNode, $form) { |
| 113 | $form.find('.fee-break-down-message').hide(); |
| 114 | $form.find('.js-give-donation-summary-fees').toggle(targetNode.checked); |
| 115 | |
| 116 | if (!targetNode.checked) { |
| 117 | return; |
| 118 | } |
| 119 | |
| 120 | // Hack: (Currency Switcher) The total is always stored using a the decimal separator as set by the primary currency. |
| 121 | const formData = new FormData($form[0]); |
| 122 | const fee = formData.get('give-fee-amount'); |
| 123 | $form.find('[data-tag="fees"]').html(GiveDonationSummary.format_amount(fee, $form)); |
| 124 | }, |
| 125 | |
| 126 | /** |
| 127 | * @since 2.17.0 |
| 128 | */ |
| 129 | initTotal: function () { |
| 130 | GiveDonationSummary.observe('.give-final-total-amount', function (targetNode, $form) { |
| 131 | // Hack: (Currency Switcher) The total is always stored using a the decimal seperator as set by the primary currency. |
| 132 | const total = targetNode.dataset.total.replace('.', Give.form.fn.getInfo('decimal_separator', $form)); |
| 133 | $form.find('[data-tag="total"]').html(GiveDonationSummary.format_amount(total, $form)); |
| 134 | }); |
| 135 | |
| 136 | // Hack: Force an initial mutation for the Total Amount observer |
| 137 | const totalAmount = document.querySelector('.give-final-total-amount'); |
| 138 | if (totalAmount) { |
| 139 | totalAmount.dataset.total = totalAmount.dataset.total; |
| 140 | } |
| 141 | }, |
| 142 | |
| 143 | /** |
| 144 | * Hack: Placeholder callback, which is only used when the gateway changes. |
| 145 | */ |
| 146 | handleNavigateBack: function () {}, |
| 147 | |
| 148 | /** |
| 149 | * Hack: Changing gateways re-renders parts of the form via AJAX. |
| 150 | */ |
| 151 | onGatewayLoadSuccess: function () { |
| 152 | const inserted = jQuery('#give_purchase_form_wrap .give-donation-summary-section').detach(); |
| 153 | if (inserted.length) { |
| 154 | jQuery('.give-donation-summary-section').remove(); |
| 155 | inserted.appendTo('#donate-fieldset'); |
| 156 | GiveDonationSummary.initTotal(); |
| 157 | |
| 158 | // Overwrite the handler because updating the gateway breaks the original binding. |
| 159 | GiveDonationSummary.handleNavigateBack = function (e) { |
| 160 | e.stopPropagation(); |
| 161 | e.preventDefault(); |
| 162 | window.formNavigator.back(); |
| 163 | }; |
| 164 | } |
| 165 | }, |
| 166 | |
| 167 | /** |
| 168 | * Observe an element and respond to changes to that element. |
| 169 | * |
| 170 | * @since 2.17.0 |
| 171 | * |
| 172 | * @param {string} selectors |
| 173 | * @param {callable} callback |
| 174 | * @param {boolean} callImmediately |
| 175 | */ |
| 176 | observe: function (selectors, callback, callImmediately = true) { |
| 177 | const targetNode = document.querySelector(selectors); |
| 178 | |
| 179 | if (!targetNode) return; |
| 180 | |
| 181 | const $form = jQuery(targetNode.closest('.give-form')); |
| 182 | |
| 183 | new MutationObserver(function (mutationsList) { |
| 184 | for (const mutation of mutationsList) { |
| 185 | // Use traditional 'for loops' for IE 11 |
| 186 | if (mutation.type === 'attributes') { |
| 187 | /** |
| 188 | * @param targetNode The node matching the element as defined by the specific selectors |
| 189 | * @param $form The closest `.give-form` node to the targetNode, wrapped in jQuery |
| 190 | */ |
| 191 | callback(mutation.target, $form); |
| 192 | } |
| 193 | } |
| 194 | }).observe(targetNode, {attributes: true}); |
| 195 | |
| 196 | if (callImmediately) { |
| 197 | callback(targetNode, $form); |
| 198 | } |
| 199 | }, |
| 200 | |
| 201 | /** |
| 202 | * Helper function to get the formatted amount |
| 203 | * |
| 204 | * @since 2.17.0 |
| 205 | * |
| 206 | * @param {string/number} amount |
| 207 | * @param {jQuery} $form |
| 208 | */ |
| 209 | format_amount: function (amount, $form) { |
| 210 | // Normalize amounts to JS number format |
| 211 | amount = amount |
| 212 | .replace(Give.form.fn.getInfo('thousands_separator', $form), '') |
| 213 | .replace(Give.form.fn.getInfo('decimal_separator', $form), '.'); |
| 214 | |
| 215 | const currency = Give.form.fn.getInfo('currency_code', $form); |
| 216 | const precision = GiveDonationSummaryData.currencyPrecisionLookup[currency]; |
| 217 | |
| 218 | // Format with accounting.js, according to the configuration |
| 219 | return accounting.formatMoney(amount, { |
| 220 | symbol: Give.form.fn.getInfo('currency_symbol', $form), |
| 221 | format: 'before' === Give.form.fn.getInfo('currency_position', $form) ? '%s%v' : '%v%s', |
| 222 | decimal: Give.form.fn.getInfo('decimal_separator', $form), |
| 223 | thousand: Give.form.fn.getInfo('thousands_separator', $form), |
| 224 | precision: precision, |
| 225 | }); |
| 226 | }, |
| 227 | }; |
| 228 | |
| 229 | jQuery(document).on('give:postInit', GiveDonationSummary.init); |
| 230 | jQuery(document).on('Give:onGatewayLoadSuccess', GiveDonationSummary.onGatewayLoadSuccess); |
| 231 |