PluginProbe
VikBooking Hotel Booking Engine & PMS / trunk
VikBooking Hotel Booking Engine & PMS vtrunk
1.8.15 1.8.14 1.8.13 1.8.12 1.8.11 1.8.10 1.8.9 1.8.6 1.8.7 1.8.8 trunk 1.6.0 1.6.1 1.6.2 1.6.3 1.6.4 1.6.5 1.6.6 1.6.7 1.6.8 1.6.9 1.7.0 1.7.1 1.7.2 1.7.3 All 36 releases
vikbooking / admin / views / taskmanager / tmpl / default_board.php

default_board.php in VikBooking Hotel Booking Engine & PMS trunk, at admin/views/taskmanager/tmpl/default_board.php

446 lines 15.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * @package VikBooking
4 * @subpackage core
5 * @author E4J s.r.l.
6 * @copyright Copyright (C) 2025 E4J s.r.l. All Rights Reserved.
7 * @license http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
8 * @link https://vikwp.com
9 */
10
11 // No direct access
12 defined('ABSPATH') or die('No script kiddies please!');
13
14 ?>
15
16 <div class="vbo-tm-board-areas-list">
17 <?php
18 foreach ($this->visibleAreas as $areaRecord) {
19 // wrap the area record into a task area object
20 $taskArea = VBOTaskArea::getInstance((array) $areaRecord);
21 ?>
22 <div class="vbo-tm-board-area-container" data-area-id="<?php echo $taskArea->getID(); ?>">
23 <?php
24 // build layout data
25 $layout_data = [
26 'data' => [
27 'taskArea' => $taskArea,
28 'filters' => $this->filters,
29 ],
30 ];
31
32 // render task list area
33 echo JLayoutHelper::render('taskmanager.board.area', $layout_data);
34 ?>
35 </div>
36 <?php
37 }
38 ?>
39 </div>
40
41 <script type="text/javascript">
42 /**
43 * Register function for rendering a task area.
44 */
45 const vboTmBoardRenderArea = (areaId) => {
46 let areaContainer = jQuery('<div></div>')
47 .addClass('vbo-tm-board-area-container')
48 .attr('data-area-id', areaId);
49
50 areaContainer.append(
51 jQuery('<div></div>')
52 .addClass('vbo-tm-board-area-loading')
53 .html('<?php VikBookingIcons::e('circle-notch', 'fa-spin fa-fw'); ?>')
54 );
55
56 areaContainer.appendTo('.vbo-tm-board-areas-list');
57
58 // make the request to display the requested task area
59 VBOCore.doAjax(
60 "<?php echo VikBooking::ajaxUrl('index.php?option=com_vikbooking&task=taskmanager.renderLayout'); ?>",
61 {
62 type: 'board.area',
63 data: {
64 area_id: areaId,
65 filters: vboTmFilters || {},
66 },
67 },
68 (resp) => {
69 try {
70 // decode the response (if needed)
71 let obj_res = typeof resp === 'string' ? JSON.parse(resp) : resp;
72
73 // set HTML response (use jQuery rather than plain JS to avoid issues with injected scripts execution)
74 areaContainer.html(obj_res['html']);
75
76 // silently update task area status
77 vboTmToggleAreaDisplay(areaId, 1);
78
79 // load area tasks list
80 vboTmBoardRenderAreaTasks(areaId);
81 } catch (err) {
82 console.error('Error decoding the response', err, resp);
83 }
84 },
85 (error) => {
86 // display error message
87 alert(error.responseText);
88
89 // dispatch the event for updating the active areas
90 VBOCore.emitEvent('vbo-tm-area-update-status');
91 }
92 );
93 };
94
95 /**
96 * Register function for rendering the given area tasks with filters and start offset.
97 */
98 const vboTmBoardRenderAreaTasks = (areaId, filters, start) => {
99 let tasksList;
100 let loadingList;
101 let nextPage = false;
102
103 if (start) {
104 // existing tasks list for loading a new page
105 tasksList = jQuery('.vbo-tm-board-area-container[data-area-id="' + areaId + '"]')
106 .find('.vbo-tm-board-area-tasks-list');
107
108 if (tasksList.length) {
109 // set the current loading list
110 loadingList = document
111 .querySelector('.vbo-tm-board-area-container[data-area-id="' + areaId + '"]')
112 .querySelector('.vbo-tm-board-area-tasks-list');
113
114 // populate loading skeletons
115 vboTmBoardSetSkeletons(areaId, 10);
116 } else {
117 // start a new list
118 tasksList = null;
119 }
120 }
121
122 if (!tasksList) {
123 // load the first page of tasks
124 tasksList = jQuery('<div></div>')
125 .addClass('vbo-tm-board-area-tasks-list');
126
127 tasksList.append(
128 jQuery('<div></div>')
129 .addClass('vbo-tm-board-area-tasks-loading')
130 .html('<?php VikBookingIcons::e('circle-notch', 'fa-spin fa-fw'); ?>')
131 );
132
133 tasksList.appendTo('.vbo-tm-board-area-container[data-area-id="' + areaId + '"]');
134 } else {
135 // turn flag on for loading a next page
136 nextPage = true;
137 }
138
139 // make the request to display the requested task area
140 VBOCore.doAjax(
141 "<?php echo VikBooking::ajaxUrl('index.php?option=com_vikbooking&task=taskmanager.renderLayout'); ?>",
142 {
143 type: 'board.tasks',
144 data: {
145 area_id: areaId,
146 filters: filters || vboTmFilters || {},
147 start: start || 0,
148 limit: 10,
149 },
150 },
151 (resp) => {
152 try {
153 // decode the response (if needed)
154 let obj_res = typeof resp === 'string' ? JSON.parse(resp) : resp;
155
156 // set HTML response (use jQuery rather than plain JS to avoid issues with injected scripts execution)
157 if (!nextPage) {
158 // set the loading result for the first page of tasks
159 tasksList.html(obj_res['html']);
160 } else {
161 if (loadingList) {
162 // turn flag off for loading a new page
163 loadingList.pageLoading = false;
164
165 // delete loading skeletons
166 vboTmBoardUnsetSkeletons(areaId);
167 }
168
169 if ((obj_res['html'] + '').indexOf('data-no-results="1"') < 0 || !tasksList.find('[data-no-results="1"]').length) {
170 // append the loading result of the next tasks by preventing multiple "no-results" elements due to infinite scroll loading
171 tasksList.append(obj_res['html']);
172 }
173 }
174
175 // dispatch event for TM contents loaded
176 VBOCore.emitEvent('vbo-tm-contents-loaded', {
177 element: tasksList,
178 });
179 } catch (err) {
180 console.error('Error decoding the response', err, resp);
181 }
182 },
183 (error) => {
184 // display error message
185 alert(error.responseText);
186
187 if (loadingList) {
188 // turn flag off for loading a new page
189 loadingList.pageLoading = false;
190 }
191 }
192 );
193 };
194
195 /**
196 * Register function to set some task loading skeletons.
197 */
198 const vboTmBoardSetSkeletons = (areaId, quantity) => {
199 let container = document.querySelector('.vbo-tm-board-area-container[data-area-id="' + areaId + '"]');
200 let tasksList = container ? container.querySelector('.vbo-tm-board-area-tasks-list') : null;
201
202 if (!tasksList) {
203 return;
204 }
205
206 // ensure the quantity is a valid number greater than zero
207 quantity = !isNaN(quantity) && quantity > 0 ? quantity : 1;
208
209 // build skeleton HTML string
210 let skeletonHtml = '';
211 skeletonHtml += '<div class="vbo-tm-board-area-task-wrap vbo-tm-board-area-task-wrap-skeleton">';
212 skeletonHtml += ' <div class="vbo-tm-board-area-task-head">';
213 skeletonHtml += ' <div class="vbo-skeleton-loading vbo-tm-board-area-task-title"></div>';
214 skeletonHtml += ' <div class="vbo-tm-board-area-task-notes">';
215 skeletonHtml += ' <div class="vbo-skeleton-loading vbo-tm-board-area-task-notes-line"></div>';
216 skeletonHtml += ' <div class="vbo-skeleton-loading vbo-tm-board-area-task-notes-line"></div>';
217 skeletonHtml += ' </div>';
218 skeletonHtml += ' </div>';
219 skeletonHtml += ' <div class="vbo-tm-board-area-task-body">';
220 skeletonHtml += ' <div class="vbo-tm-board-area-task-status">';
221 skeletonHtml += ' <span class="vbo-skeleton-loading vbo-tm-board-area-task-status-badge"></span>';
222 skeletonHtml += ' </div>';
223 skeletonHtml += ' <div class="vbo-tm-board-area-task-assignees">';
224 skeletonHtml += ' <span class="vbo-skeleton-loading vbo-tm-board-area-task-assignee"></span>';
225 skeletonHtml += ' <span class="vbo-skeleton-loading vbo-tm-board-area-task-assignee"></span>';
226 skeletonHtml += ' </div>';
227 skeletonHtml += ' </div>';
228 skeletonHtml += '</div>';
229
230 for (let i = 1; i <= quantity; i++) {
231 tasksList.insertAdjacentHTML('beforeend', skeletonHtml);
232 }
233 };
234
235 /**
236 * Register function to unset the task loading skeletons.
237 */
238 const vboTmBoardUnsetSkeletons = (areaId) => {
239 let container = document.querySelector('.vbo-tm-board-area-container[data-area-id="' + areaId + '"]');
240 let tasksList = container ? container.querySelector('.vbo-tm-board-area-tasks-list') : null;
241
242 if (!tasksList) {
243 return;
244 }
245
246 tasksList
247 .querySelectorAll('.vbo-tm-board-area-task-wrap.vbo-tm-board-area-task-wrap-skeleton')
248 .forEach((skeleton) => {
249 skeleton.remove();
250 });
251 };
252
253 /**
254 * Register function for hiding a task area.
255 */
256 const vboTmBoardHideArea = (areaId) => {
257 // remove element blocks from DOM
258 document
259 .querySelector('.vbo-tm-board-area-container[data-area-id="' + areaId + '"]')
260 .remove();
261
262 // silently update task area status
263 vboTmToggleAreaDisplay(areaId, 0);
264 };
265
266 /**
267 * Register function for hiding the given area tasks.
268 */
269 const vboTmBoardHideAreaTasks = (areaId) => {
270 // remove tasks list element from DOM
271 let list = document
272 .querySelector('.vbo-tm-board-area-container[data-area-id="' + areaId + '"]')
273 .querySelector('.vbo-tm-board-area-tasks-list');
274
275 if (list) {
276 list.remove();
277 }
278 };
279
280 /**
281 * Register function for setting up the infinite scroll loading for an area.
282 */
283 const vboTmBoardSetupInfiniteScroll = (areaId, start, count) => {
284 let tasksList = document
285 .querySelector('.vbo-tm-board-area-container[data-area-id="' + areaId + '"]')
286 .querySelector('.vbo-tm-board-area-tasks-list');
287
288 if (!tasksList) {
289 return;
290 }
291
292 if (count < 10) {
293 // no extra loading needed
294 tasksList
295 .removeEventListener('scroll', vboTmBoardHandleInfiniteScroll);
296 return;
297 }
298
299 // get wrapper dimensions
300 let listViewHeight = tasksList.offsetHeight;
301 let listGlobHeight = tasksList.scrollHeight;
302
303 if (listViewHeight >= listGlobHeight) {
304 // no scrolling detected
305 return;
306 }
307
308 // inject/update custom object properties
309 tasksList.areaId = areaId;
310 tasksList.offsetStart = start;
311
312 if (!start && !tasksList.scrollListener) {
313 // inject custom object property to identify the scroll event listener
314 tasksList.scrollListener = true;
315
316 // register infinite scroll event handler
317 tasksList
318 .addEventListener('scroll', vboTmBoardHandleInfiniteScroll);
319 }
320 };
321
322 /**
323 * Register function for infinite scroll loading handler.
324 */
325 const vboTmBoardHandleInfiniteScroll = (e) => {
326 // access the injected area ID property
327 let areaId = e.currentTarget.areaId;
328
329 if (!areaId) {
330 return;
331 }
332
333 // access the involved tasks list
334 let tasksList = document
335 .querySelector('.vbo-tm-board-area-container[data-area-id="' + areaId + '"]')
336 .querySelector('.vbo-tm-board-area-tasks-list');
337
338 if (!tasksList) {
339 return;
340 }
341
342 // make sure the loading of a next page isn't running
343 if (tasksList.pageLoading) {
344 // abort
345 return;
346 }
347
348 // register throttling callback
349 VBOCore.throttleTimer(() => {
350 // get wrapper dimensions
351 let listViewHeight = tasksList.offsetHeight;
352 let listGlobHeight = tasksList.scrollHeight;
353 let listScrollTop = tasksList.scrollTop;
354
355 if (!listScrollTop || listViewHeight >= listGlobHeight) {
356 // no scrolling detected at all
357 return;
358 }
359
360 // calculate missing distance to the end of the list
361 let listEndDistance = listGlobHeight - (listViewHeight + listScrollTop);
362
363 if (listEndDistance < 140) {
364 // inject custom property to identify a next page is loading
365 tasksList.pageLoading = true;
366
367 // load the next page of notifications
368 vboTmBoardRenderAreaTasks(areaId, (vboTmFilters || {}), (tasksList.offsetStart || 0) + 10);
369 }
370 }, 100);
371 };
372
373 jQuery(function() {
374
375 /**
376 * Load all area tasks upon page loading.
377 */
378 document.querySelectorAll('.vbo-tm-board-area-container').forEach((area) => {
379 let areaId = area.getAttribute('data-area-id');
380 // render area tasks
381 vboTmBoardRenderAreaTasks(areaId);
382 });
383
384 /**
385 * Register to the event for rendering a task area.
386 */
387 document.addEventListener('vbo-tm-area-render-task', (e) => {
388 if (!e || !e?.detail?.areaId) {
389 return;
390 }
391
392 let areaId = e.detail.areaId;
393
394 vboTmBoardRenderArea(areaId);
395 });
396
397 /**
398 * Register to the event for hiding a task area.
399 */
400 document.addEventListener('vbo-tm-area-hide-task', (e) => {
401 if (!e || !e?.detail?.areaId) {
402 return;
403 }
404
405 let areaId = e.detail.areaId;
406
407 vboTmBoardHideArea(areaId);
408 });
409
410 /**
411 * Register listener for the filters changed event.
412 */
413 document.addEventListener('vbo-tm-filters-changed', (e) => {
414 // obtain the global filters
415 let filters = e?.detail?.filters;
416
417 // iterate over all the visible areas
418 document.querySelectorAll('.vbo-tm-board-area-container').forEach((area) => {
419 // get area ID
420 let areaId = area.getAttribute('data-area-id');
421
422 // hide the area tasks
423 vboTmBoardHideAreaTasks(areaId);
424
425 // re-render area tasks
426 vboTmBoardRenderAreaTasks(areaId, filters);
427 });
428 });
429
430 /**
431 * Register listener for the tasks loaded event to configure the infinite scroll loading.
432 */
433 document.addEventListener('vbo-tm-board-tasks-loaded', (e) => {
434 let areaId = e?.detail?.areaId || 0;
435 let start = e?.detail?.start || 0;
436 let count = e?.detail?.count || 0;
437
438 if (document.querySelector('.vbo-tm-board-area-container[data-area-id="' + areaId + '"]')) {
439 // setup infinite scroll loading for the current area
440 vboTmBoardSetupInfiniteScroll(areaId, start, count);
441 }
442 });
443
444 });
445 </script>
446