PluginProbe ʕ •ᴥ•ʔ
GiveWP – Donation Plugin and Fundraising Platform / 3.19.4
GiveWP – Donation Plugin and Fundraising Platform v3.19.4
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 / DonationForms / AsyncData / resources / loadAsyncData.js
give / src / DonationForms / AsyncData / resources Last commit date
loadAsyncData.js 1 year ago loadAsyncData.scss 1 year ago
loadAsyncData.js
351 lines
1 /**
2 * This file contains all the logic to load async data on the project's available form list views, including form grid and admin form list views.
3 *
4 * The async data are loaded (only for the items visible on the screen) on the following conditions:
5 *
6 * 1) At the page's first load
7 * 2) When the user adds a block in the WP block editor
8 * 3) When the user scrolls the mouse
9 * 4) When the user resizes the screen
10 *
11 * @since 3.16.0
12 */
13 document.addEventListener('DOMContentLoaded', () => {
14 /**
15 * We are declaring it at the top to use it in more than one function.
16 */
17 let throttleTimer = false;
18 let abortLoadAsyncData = false;
19 const giveListTable = document.querySelector('.giveListTable');
20 const giveListTableIsLoadingEvent = new Event('giveListTableIsLoading');
21
22 /**
23 * This function check if the element is visible on the screen.
24 *
25 * @since 3.16.0
26 */
27 function isInViewport(element) {
28 const {top, bottom} = element.getBoundingClientRect();
29 const vHeight = window.innerHeight || document.documentElement.clientHeight;
30
31 return (top > 0 || bottom > 0) && top < vHeight;
32 }
33
34 /**
35 * Check if an element is a placeholder waiting to have the value updated.
36 *
37 * @since 3.16.0
38 */
39 function isPlaceholder(element) {
40 return !!element && Boolean(element.querySelector('.js-give-async-data'));
41 }
42
43 /**
44 * This function fetch the async data from the server and set the values to the proper elements in the DOM.
45 *
46 * @since 3.16.0
47 */
48 const loadFormData = (
49 formId,
50 itemElement,
51 amountRaisedElement = null,
52 progressBarElement = null,
53 goalAchievedElement = null,
54 donationsElement = null,
55 earningsElement = null
56 ) => {
57 // If we don't have any of these elements with a placeholder waiting to be updated, then return.
58 if (
59 !isPlaceholder(amountRaisedElement) &&
60 !isPlaceholder(donationsElement) &&
61 !isPlaceholder(earningsElement)
62 ) {
63 return;
64 }
65
66 // Limit requests to run one per time.
67 if (window.GiveDonationFormsAsyncData.throttlingEnabled && throttleTimer) {
68 window.GiveDonationFormsAsyncData.scriptDebug && console.log('throttleTimer start: ', throttleTimer);
69 return;
70 }
71
72 throttleTimer = true;
73 window.GiveDonationFormsAsyncData.scriptDebug &&
74 console.log('request start: ', new Date().toLocaleTimeString());
75
76 window.GiveDonationFormsAsyncData.scriptDebug && console.log('item: ', itemElement);
77
78 // This class ensures that once the element has the fetch request triggered we'll not try to fetch it again.
79 itemElement.classList.add('give-async-data-fetch-triggered');
80
81 // It can be used to abort the async request when necessary.
82 const controller = new AbortController();
83 const signal = controller.signal;
84
85 fetch(
86 `${window.GiveDonationFormsAsyncData.ajaxUrl}?action=givewp_get_form_async_data_for_list_view&formId=${formId}&nonce=${window.GiveDonationFormsAsyncData.ajaxNonce}`,
87 {signal}
88 )
89 .then(function (response) {
90 return response.json();
91 })
92 .then(function (response) {
93 window.GiveDonationFormsAsyncData.scriptDebug && console.log('Response: ', response);
94
95 // Replace the placeholders with the real data returned by the server.
96 if (response.success) {
97 if (isPlaceholder(amountRaisedElement)) {
98 amountRaisedElement.innerHTML = response.data.amountRaised;
99 }
100
101 if (
102 !!progressBarElement &&
103 progressBarElement.style.width !== response.data.percentComplete + '%'
104 ) {
105 progressBarElement.style.width = response.data.percentComplete + '%';
106 }
107
108 if (!!goalAchievedElement && response.data.percentComplete >= 100) {
109 goalAchievedElement.style.opacity = '1';
110 }
111
112 if (isPlaceholder(donationsElement)) {
113 donationsElement.innerHTML = response.data.donationsCount;
114 }
115
116 if (isPlaceholder(earningsElement)) {
117 earningsElement.innerHTML = response.data.revenue;
118 }
119 }
120 })
121 .catch((error) => {
122 // When there is an error remove the class that prevents fetch request duplication, so we can try fetching it again in the next try.
123 itemElement.classList.remove('give-async-data-fetch-triggered');
124 window.GiveDonationFormsAsyncData.scriptDebug && console.log('Error: ', error);
125 })
126 .finally(() => {
127 window.GiveDonationFormsAsyncData.scriptDebug &&
128 console.log('request end: ', new Date().toLocaleTimeString());
129 if (window.GiveDonationFormsAsyncData.throttlingEnabled && throttleTimer) {
130 throttleTimer = false;
131 window.GiveDonationFormsAsyncData.scriptDebug && console.log('throttleTimer end: ', throttleTimer);
132 maybeLoadAsyncData();
133 }
134 window.GiveDonationFormsAsyncData.scriptDebug && console.log('Request finalized.');
135 });
136
137 // Make sure to abort all unfinished async requests when leave or refresh the page.
138 addEventListener('beforeunload', (event) => {
139 abortLoadAsyncData = true;
140 controller.abort('Async request aborted due to exit page.');
141 });
142
143 // Make sure to abort all unfinished async requests when changing the giveListTable pagination.
144 if (giveListTable) {
145 giveListTable.addEventListener('giveListTableIsLoading', (event) => {
146 abortLoadAsyncData = true;
147 controller.abort('Async request aborted due to table loading.');
148 });
149 }
150 };
151
152 /**
153 * Handle the async data logic for ALL form list views available.
154 *
155 * @since 3.16.0
156 */
157 const maybeLoadAsyncData = () => {
158 // If the async requests were aborted on the "beforeunload" or "giveListTableIsLoading" event, we don't want to create more async requests
159 if (abortLoadAsyncData) {
160 window.GiveDonationFormsAsyncData.scriptDebug && console.log('abortLoadAsyncData');
161 return;
162 }
163
164 handleAdminFormsListViewItems();
165 handleAdminLegacyFormsListViewItems();
166 handleFormGridItems();
167 };
168
169 /**
170 * Check for changes in the "giveListTable" classes to trigger the "giveListTableIsLoadingEvent" when appropriated.
171 *
172 * @since 3.16.0
173 */
174 function maybeTriggerGiveListTableIsLoadingEvent() {
175 if (giveListTable) {
176 const observer = new MutationObserver(function (mutations) {
177 if (giveListTable.classList.contains('giveListTableIsLoading')) {
178 giveListTable.dispatchEvent(giveListTableIsLoadingEvent);
179 }
180
181 if (giveListTable.classList.contains('giveListTableIsLoaded')) {
182 abortLoadAsyncData = false;
183 maybeLoadAsyncData();
184 }
185 });
186
187 // Configuration of the observer
188 const config = {
189 attributes: true,
190 childList: false,
191 characterData: false,
192 };
193
194 // Pass in the target node, as well as the observer options
195 observer.observe(giveListTable, config);
196 }
197 }
198
199 /**
200 * Load the async data of all forms (visible on the screen) from the NEW admin form list view - giveListTable.
201 *
202 * @since 3.16.0
203 */
204 function handleAdminFormsListViewItems() {
205 const adminFormsListViewItems = document.querySelectorAll('tr:not(.give-async-data-fetch-triggered)');
206 if (adminFormsListViewItems.length > 0) {
207 maybeTriggerGiveListTableIsLoadingEvent();
208
209 adminFormsListViewItems.forEach((itemElement) => {
210 const select = itemElement.querySelector('.giveListTableSelect');
211
212 if (!select) {
213 return;
214 }
215
216 const formId = select.getAttribute('data-id');
217 const amountRaisedElement = itemElement.querySelector("[id^='giveDonationFormsProgressBar'] > span");
218 const progressBarElement = itemElement.querySelector('.goalProgress > span');
219 const goalAchievedElement = itemElement.querySelector('.goalProgress--achieved');
220 const donationsElement = itemElement.querySelector('.column-donations-count-value');
221 const earningsElement = itemElement.querySelector('.column-earnings-value');
222
223 if (isInViewport(itemElement)) {
224 loadFormData(
225 formId,
226 itemElement,
227 amountRaisedElement,
228 progressBarElement,
229 goalAchievedElement,
230 donationsElement,
231 earningsElement
232 );
233 }
234 });
235 }
236 }
237
238 /**
239 * Load the async data of all forms (visible on the screen) from the LEGACY admin form list view.
240 *
241 * @since 3.16.0
242 */
243 function handleAdminLegacyFormsListViewItems() {
244 const adminLegacyFormsListViewItems = document.querySelectorAll(
245 '.type-give_forms:not(.give-async-data-fetch-triggered)'
246 );
247 if (adminLegacyFormsListViewItems.length > 0) {
248 adminLegacyFormsListViewItems.forEach((itemElement) => {
249 if (!itemElement.hasAttribute('id') || !itemElement.id.includes('post-')) {
250 return;
251 }
252
253 const formId = itemElement.id.split('post-')[1];
254 const goalElement = itemElement.querySelector('.column-goal');
255 const amountRaisedElement = goalElement.querySelector('.give-goal-text > span');
256 const progressBarElement = goalElement.querySelector('.give-admin-progress-bar > span');
257 const goalAchievedElement = goalElement.querySelector('.give-admin-goal-achieved');
258 const donationsElement = itemElement.querySelector('.column-donations > a');
259 const earningsElement = itemElement.querySelector('.column-earnings > a');
260
261 if (isInViewport(itemElement)) {
262 loadFormData(
263 formId,
264 itemElement,
265 amountRaisedElement,
266 progressBarElement,
267 goalAchievedElement,
268 donationsElement,
269 earningsElement
270 );
271 }
272 });
273 }
274 }
275
276 /**
277 * Load the async data in all form grid items that have the progress bar enabled.
278 *
279 * @since 3.16.0
280 */
281 function handleFormGridItems() {
282 const formGridItems = document.querySelectorAll('.give-grid__item:not(.give-async-data-fetch-triggered)');
283
284 if (formGridItems.length > 0) {
285 formGridItems.forEach((itemElement) => {
286 const giveCard = itemElement.querySelector('.give-card');
287
288 if (!giveCard || !giveCard.hasAttribute('id') || !giveCard.id.includes('give-card-')) {
289 return;
290 }
291
292 const formId = giveCard.id.split('give-card-')[1];
293 const formGridRaised = itemElement.querySelector('.form-grid-raised');
294
295 if (!formGridRaised) {
296 return;
297 }
298
299 const amountRaisedElement = formGridRaised
300 .querySelector('div:nth-child(1)')
301 .querySelector('span:nth-child(1)');
302 const progressBarElement = itemElement.querySelector('.give-progress-bar').querySelector('span');
303 const donationsElement = formGridRaised
304 .querySelector('div:nth-child(2)')
305 .querySelector('span:nth-child(1)');
306
307 if (isInViewport(itemElement)) {
308 loadFormData(formId, itemElement, amountRaisedElement, progressBarElement, null, donationsElement);
309 }
310 });
311 }
312 }
313
314 // Trigger the async logic at the page's first load.
315 maybeLoadAsyncData();
316
317 // Trigger the async logic every time the user scrolls the mouse.
318 window.addEventListener(
319 'scroll',
320 () => {
321 maybeLoadAsyncData();
322 },
323 true
324 );
325
326 // Trigger the async logic every time the user resize the screen.
327 window.addEventListener(
328 'resize',
329 () => {
330 maybeLoadAsyncData();
331 },
332 true
333 );
334
335 // Trigger the async logic every time the user add a new Form Grid Block to the WordPress Block Editor - Gutenberg.
336 window.onload = function () {
337 const wpBlockEditorContent = document.querySelector('.wp-block-post-content');
338 if (!!wpBlockEditorContent) {
339 // create an Observer instance
340 const resizeObserver = new ResizeObserver((entries) => {
341 window.GiveDonationFormsAsyncData.scriptDebug &&
342 console.log('WP Block Editor height changed:', entries[0].target.clientHeight);
343 maybeLoadAsyncData();
344 });
345
346 // start observing a DOM node
347 resizeObserver.observe(wpBlockEditorContent);
348 }
349 };
350 });
351