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