conditional-fields.js
222 lines
| 1 | document.addEventListener('readystatechange', (event) => { |
| 2 | if (event.target.readyState !== 'complete') { |
| 3 | return null; |
| 4 | } |
| 5 | |
| 6 | const state = {}; |
| 7 | |
| 8 | /** |
| 9 | * Get list of watched fields. |
| 10 | * @since 2.15.0 |
| 11 | * |
| 12 | * @return object |
| 13 | */ |
| 14 | function getWatchedElementNames(donationForm) { |
| 15 | const fields = {}; |
| 16 | |
| 17 | donationForm.querySelectorAll('[data-field-visibility-conditions]').forEach(function (fieldContainer) { |
| 18 | const visibilityConditions = JSON.parse(fieldContainer.getAttribute('data-field-visibility-conditions')); |
| 19 | const visibilityCondition = visibilityConditions[0]; // Currently we support only one visibility condition. |
| 20 | let fieldContainerSelector = `[data-field-name="${fieldContainer.getAttribute('data-field-name')}"]`; |
| 21 | let {field} = visibilityCondition; |
| 22 | |
| 23 | // Get field. It will tell use real name of field. |
| 24 | field = document.querySelector(`[name="${field}"], [name="${field}[]"]`); |
| 25 | |
| 26 | if (field) { |
| 27 | fields[field.name] = { |
| 28 | ...fields[field.name], |
| 29 | [fieldContainerSelector]: visibilityConditions, |
| 30 | }; |
| 31 | } |
| 32 | }); |
| 33 | |
| 34 | return fields; |
| 35 | } |
| 36 | |
| 37 | /** |
| 38 | * @since 2.15.0 |
| 39 | * |
| 40 | * @param operator |
| 41 | * @param firstData |
| 42 | * @param secondData |
| 43 | * |
| 44 | * @return boolean |
| 45 | */ |
| 46 | function compareWithOperator(operator, firstData, secondData) { |
| 47 | return { |
| 48 | '=': firstData === secondData, |
| 49 | '!=': firstData !== secondData, |
| 50 | '>': firstData > secondData, |
| 51 | '>=': firstData >= secondData, |
| 52 | '<': firstData < secondData, |
| 53 | '<=': firstData <= secondData, |
| 54 | }[operator]; |
| 55 | } |
| 56 | |
| 57 | /** |
| 58 | * Handle fields visibility. |
| 59 | * @since 2.15.0 |
| 60 | */ |
| 61 | function handleVisibility(donationForm, watchedFieldName, visibilityConditionsForWatchedField) { |
| 62 | for (const [fieldContainerSelector, visibilityConditions] of Object.entries( |
| 63 | visibilityConditionsForWatchedField |
| 64 | )) { |
| 65 | const fieldWrapper = donationForm.querySelector(fieldContainerSelector); |
| 66 | const fieldName = fieldWrapper.getAttribute('data-field-name'); |
| 67 | const visibilityCondition = visibilityConditions[0]; // Currently we support only one visibility condition. |
| 68 | let visible = false; |
| 69 | const {comparisonOperator} = visibilityCondition; |
| 70 | let {value} = visibilityCondition; |
| 71 | |
| 72 | const inputs = donationForm.querySelectorAll(`[name="${watchedFieldName}"]`); |
| 73 | let hasFieldController = !!inputs.length; |
| 74 | |
| 75 | if (hasFieldController) { |
| 76 | inputs.forEach((input) => { |
| 77 | const fieldType = input.getAttribute('type'); |
| 78 | let inputValue = input.value; |
| 79 | |
| 80 | // Make an exception for the amount field and parse the value |
| 81 | if (input.name === 'give-amount') { |
| 82 | inputValue = Give.fn.unFormatCurrency( |
| 83 | input.value, |
| 84 | Give.form.fn.getInfo('decimal_separator', donationForm) |
| 85 | ); |
| 86 | |
| 87 | value = Math.abs(parseFloat(value)); |
| 88 | } |
| 89 | |
| 90 | const comparisonResult = compareWithOperator(comparisonOperator, inputValue, value); |
| 91 | |
| 92 | if (fieldType === 'checkbox') { |
| 93 | if ( |
| 94 | (comparisonResult && input.checked && comparisonOperator === '=') || |
| 95 | (!input.checked && comparisonOperator === '!=') |
| 96 | ) { |
| 97 | visible = true; |
| 98 | } |
| 99 | } else if (fieldType === 'radio') { |
| 100 | if (input.checked && comparisonResult) { |
| 101 | visible = true; |
| 102 | } |
| 103 | } else if (comparisonResult) { |
| 104 | visible = true; |
| 105 | } |
| 106 | }); |
| 107 | |
| 108 | // Show or Hide field wrapper. |
| 109 | if (visible) { |
| 110 | const field = fieldWrapper.querySelector(`[name="${fieldName}"][data-required]`); |
| 111 | fieldWrapper.classList.remove('give-hidden'); |
| 112 | |
| 113 | // Make hidden flagged required field required. |
| 114 | if (field) { |
| 115 | field.setAttribute('required', ''); |
| 116 | field.removeAttribute('data-required'); |
| 117 | } |
| 118 | } else { |
| 119 | const field = fieldWrapper.querySelector(`[name="${fieldName}"][required]`); |
| 120 | fieldWrapper.classList.add('give-hidden'); |
| 121 | |
| 122 | // Make hidden required field non-required. |
| 123 | if (field) { |
| 124 | field.removeAttribute('required'); |
| 125 | field.setAttribute('data-required', '1'); |
| 126 | } |
| 127 | } |
| 128 | } |
| 129 | } |
| 130 | } |
| 131 | |
| 132 | /** |
| 133 | * Setup state for condition visibility settings. |
| 134 | * state contains list of watched elements per donation form. |
| 135 | * |
| 136 | * @since 2.15.0 |
| 137 | */ |
| 138 | function addVisibilityConditionsToStateForDonationForm(donationForm) { |
| 139 | const uniqueDonationFormId = donationForm.getAttribute('data-id'); |
| 140 | const watchedFields = getWatchedElementNames(donationForm); |
| 141 | |
| 142 | // Add donation form to state only if visibility conditions exiting for at least form field. |
| 143 | if (uniqueDonationFormId && Object.keys(watchedFields).length) { |
| 144 | state[uniqueDonationFormId] = watchedFields; |
| 145 | } |
| 146 | } |
| 147 | |
| 148 | /** |
| 149 | * @since 2.15.0 |
| 150 | * @param donationForm |
| 151 | */ |
| 152 | function applyVisibilityConditionsToDonationForm(donationForm) { |
| 153 | const uniqueDonationFormId = donationForm.getAttribute('data-id'); |
| 154 | |
| 155 | if (uniqueDonationFormId && uniqueDonationFormId in state) { |
| 156 | const formState = state[uniqueDonationFormId]; |
| 157 | |
| 158 | for (const [watchedFieldName, visibilityConditions] of Object.entries(formState)) { |
| 159 | handleVisibility( |
| 160 | document.querySelector(`form[data-id="${uniqueDonationFormId}"]`).closest('.give-form'), |
| 161 | watchedFieldName, |
| 162 | visibilityConditions |
| 163 | ); |
| 164 | } |
| 165 | } |
| 166 | } |
| 167 | |
| 168 | /** |
| 169 | * @since 2.15.0 |
| 170 | */ |
| 171 | function addChangeEventToWatchedElementsForDonationForm(donationFormUniqueId) { |
| 172 | const donationForm = document |
| 173 | .querySelector(`form.give-form[data-id="${donationFormUniqueId}"`) |
| 174 | .closest('form.give-form'); |
| 175 | |
| 176 | if (!donationForm || !state.hasOwnProperty(donationFormUniqueId)) { |
| 177 | return; |
| 178 | } |
| 179 | |
| 180 | for (const [watchedElementName, VisibilityConditions] of Object.entries(state[donationFormUniqueId])) { |
| 181 | document.querySelectorAll(`[name = "${watchedElementName}"]`).forEach((field) => { |
| 182 | jQuery(field).on( |
| 183 | 'input change blur', |
| 184 | handleVisibility.bind(null, donationForm, watchedElementName, VisibilityConditions) |
| 185 | ); |
| 186 | }); |
| 187 | } |
| 188 | } |
| 189 | |
| 190 | /** |
| 191 | * @since 2.15.0 |
| 192 | */ |
| 193 | function bootVisibilityConditionsFormAllDonationForm() { |
| 194 | document.querySelectorAll('form.give-form').forEach(addVisibilityConditionsToStateForDonationForm); |
| 195 | |
| 196 | // Apply visibility conditions. |
| 197 | // Add change event to watched field. |
| 198 | for (const [donationFormUniqueId, donationFormState] of Object.entries(state)) { |
| 199 | for (const [watchedFieldName, visibilityConditions] of Object.entries(donationFormState)) { |
| 200 | handleVisibility( |
| 201 | document.querySelector(`form[data-id="${donationFormUniqueId}"]`).closest('.give-form'), |
| 202 | watchedFieldName, |
| 203 | visibilityConditions |
| 204 | ); |
| 205 | } |
| 206 | |
| 207 | addChangeEventToWatchedElementsForDonationForm(donationFormUniqueId); |
| 208 | } |
| 209 | } |
| 210 | |
| 211 | bootVisibilityConditionsFormAllDonationForm(); |
| 212 | |
| 213 | // Apply visibility conditions to donation form when donor switch gateway. |
| 214 | document.addEventListener('give_gateway_loaded', (event) => { |
| 215 | const donationForm = document.getElementById(event.detail.formIdAttribute); |
| 216 | const uniqueDonationFormId = donationForm.getAttribute('data-id'); |
| 217 | addVisibilityConditionsToStateForDonationForm(donationForm); |
| 218 | applyVisibilityConditionsToDonationForm(donationForm); |
| 219 | addChangeEventToWatchedElementsForDonationForm(uniqueDonationFormId); |
| 220 | }); |
| 221 | }); |
| 222 |