PluginProbe
Booking Calendar / 11.0
Booking Calendar v11.0
11.8.3 11.8.2 11.8.1 11.8 11.7 11.6.1 11.6 11.5 11.4.3 11.4.2 11.4.1 11.4 11.3 11.2.1 11.2 11.1 11.0 10.15.7 10.15.6 10.1.3 10.10 10.10.1 10.10.2 10.11 10.11.2 All 203 releases
booking / includes / __js / client / cal / wpbc_cal_loader.js

wpbc_cal_loader.js in Booking Calendar 11.0, at includes/__js/client/cal/wpbc_cal_loader.js

329 lines 9.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 /**
2 * WPBC calendar loader bootstrap.
3 * ==============================================================================================
4 * - Finds every .calendar_loader_frame[data-wpbc-rid] on the page (now or later).
5 * - For each loader element, waits a "grace" period (data-wpbc-grace, default 8000 ms):
6 * - If the real calendar appears: do nothing (loader naturally replaced).
7 * - If not: show a helpful message (missing jQuery/_wpbc/datepick) or a duplicate notice.
8 * - Works with multiple calendars and even duplicate RIDs on the same page.
9 * - No inline JS needed in the shortcode/template output.
10 *
11 * File: ../includes/__js/client/cal/wpbc_cal_loader.js
12 *
13 * @since 10.14.5
14 * @modified 2025-09-07 12:21
15 * @version 1.0.0
16 *
17 */
18 /**
19 * WPBC calendar loader bootstrap.
20 * - Auto-detects .calendar_loader_frame[data-wpbc-rid] blocks.
21 * - Waits a "grace" period per element before showing a helpful message
22 * if the real calendar hasn't replaced the loader.
23 * - Multiple calendars and duplicate RIDs are handled.
24 */
25 (function (w, d) {
26 'use strict';
27
28 /* ---------------------------------------------------------------------------
29 * Small utilities (snake_case)
30 * ------------------------------------------------------------------------ */
31
32 /** Track processed loader elements; fallback to data flag if WeakSet missing. */
33 var processed_set = typeof WeakSet === 'function' ? new WeakSet() : null;
34
35 /** Return first match inside optional root. */
36 function query_one(selector, root) {
37 return (root || d).querySelector( selector );
38 }
39
40 /** Return NodeList of matches inside optional root. */
41 function query_all(selector, root) {
42 return (root || d).querySelectorAll( selector );
43 }
44
45 /** Run a callback when DOM is ready. */
46 function on_ready(fn) {
47 if ( d.readyState === 'loading' ) {
48 d.addEventListener( 'DOMContentLoaded', fn );
49 } else {
50 fn();
51 }
52 }
53
54 /** Clear interval safely. */
55 function safe_clear(interval_id) {
56 try {
57 w.clearInterval( interval_id );
58 } catch ( e ) {
59 }
60 }
61
62 /** Mark element processed (WeakSet or data attribute). */
63 function mark_processed(el) {
64 if ( processed_set ) {
65 processed_set.add( el );
66 } else {
67 try {
68 el.dataset.wpbcProcessed = '1';
69 } catch ( e ) {
70 }
71 }
72 }
73
74 /** Check if element was processed. */
75 function is_processed(el) {
76 return processed_set ? processed_set.has( el ) : (el && el.dataset && el.dataset.wpbcProcessed === '1');
77 }
78
79 /* ---------------------------------------------------------------------------
80 * Messages (fixed English strings; no i18n)
81 * ------------------------------------------------------------------------ */
82
83 /**
84 * Build fixed English messages for a resource.
85 * @param {string|number} rid
86 * @return {{duplicate:string,support:string,lib_jq:string,lib_dp:string,lib_wpbc:string}}
87 */
88 function get_messages(rid) {
89 var rid_int = parseInt( rid, 10 );
90 return {
91 duplicate :
92 'You have added the same calendar (ID = ' + rid_int + ') more than once on this page. ' +
93 'Please keep only one calendar with the same ID on a page to avoid conflicts.',
94 init_failed:
95 'The calendar could not be initialized on this page.' + '\n' +
96 'Please check your browser console for JavaScript errors and conflicts with other scripts/plugins.',
97 support : '', /* 'Contact support@wpbookingcalendar.com if you have any questions.', */
98 lib_jq :
99 'It appears that the "jQuery" library is not loading correctly.' + '\n' +
100 'For more information, please refer to this page: https://wpbookingcalendar.com/faq/',
101 lib_dp :
102 'It appears that the "jQuery.datepick" library is not loading correctly.' + '\n' +
103 'For more information, please refer to this page: https://wpbookingcalendar.com/faq/',
104 lib_wpbc :
105 'It appears that the "_wpbc" library is not loading correctly.' + '\n' +
106 'Please enable the loading of JS/CSS files for this page on the "WP Booking Calendar" - "Settings General" - "Advanced" page' + '\n' +
107 'For more information, please refer to this page: https://wpbookingcalendar.com/faq/'
108 };
109 }
110
111 /**
112 * Wrap plain text (with newlines) in a small HTML container.
113 * @param {string} msg
114 * @return {string}
115 */
116 function wrap_html(msg) {
117 return '<div style="font-size:13px;margin:10px;">' + String( msg || '' ).replace( /\n/g, '<br>' ) + '</div>';
118 }
119
120 /** Library presence checks (fast & cheap). */
121 function has_jq() {
122 return !!(w.jQuery && jQuery.fn && typeof jQuery.fn.on === 'function');
123 }
124
125 function has_dp() {
126 return !!(w.jQuery && jQuery.datepick);
127 }
128
129 function has_wpbc() {
130 return !!(w._wpbc && typeof w._wpbc.set_other_param === 'function');
131 }
132
133 function normalize_rid(rid) {
134 var n = parseInt( rid, 10 );
135 return (n > 0) ? String( n ) : '';
136 }
137
138 function get_rid_counts(rid) {
139 var r = normalize_rid( rid );
140 return {
141 rid : r,
142 loaders : r ? query_all( '.calendar_loader_frame[data-wpbc-rid="' + r + '"]' ).length : 0,
143 containers: r ? query_all( '#calendar_booking' + r ).length : 0
144 };
145 }
146
147 function is_duplicate_rid(rid) {
148 var c = get_rid_counts( rid );
149 return (c.loaders > 1) || (c.containers > 1);
150 }
151
152 /**
153 * Determine if the loader has been replaced by the real calendar.
154 *
155 * @param {Element} el Loader element
156 * @param {string} rid Resource ID
157 * @param {Element|null} container Optional #calendar_booking{rid} element
158 * @return {boolean}
159 */
160 function is_replaced(el, rid, container) {
161 var loader_still_in_dom = d.body.contains( el );
162 var calendar_exists = !!query_one( '.wpbc_calendar_id_' + rid, container || d );
163 return (!loader_still_in_dom) || calendar_exists;
164 }
165
166 /**
167 * Start watcher for a single loader element.
168 * - Polls and observes the calendar container.
169 * - After grace, injects a suitable message if not replaced.
170 *
171 * @param {Element} el
172 */
173 function start_for(el) {
174 if ( ! el || is_processed( el ) ) {
175 return;
176 }
177 mark_processed( el );
178
179 var rid = el.dataset.wpbcRid;
180 if ( ! rid ) {
181 return;
182 }
183
184 var grace_ms = parseInt( el.dataset.wpbcGrace || '8000', 10 );
185 if ( ! (grace_ms > 0) ) {
186 grace_ms = 8000;
187 }
188
189 var container_id = 'calendar_booking' + rid;
190 var container = d.getElementById( container_id );
191 var text_el = query_one( '.calendar_loader_text', el );
192
193 function replaced_now() {
194 return is_replaced( el, rid, container );
195 }
196
197 // Already replaced -> nothing to do.
198 if ( replaced_now() ) {
199 return;
200 }
201
202 // 1) Cheap polling.
203 var poll_id = w.setInterval( function () {
204 if ( replaced_now() ) {
205 safe_clear( poll_id );
206 if ( observer ) {
207 try {
208 observer.disconnect();
209 } catch ( e ) {
210 }
211 }
212 }
213 }, 250 );
214
215 // 2) MutationObserver for faster reaction.
216 var observer = null;
217 if ( container && 'MutationObserver' in w ) {
218 try {
219 observer = new MutationObserver( function () {
220 if ( replaced_now() ) {
221 safe_clear( poll_id );
222 try {
223 observer.disconnect();
224 } catch ( e ) {
225 }
226 }
227 } );
228 observer.observe( container, { childList: true, subtree: true } );
229 } catch ( e ) {
230 }
231 }
232
233 // 3) Final decision after grace period.
234 w.setTimeout( function finalize_after_grace() {
235 if ( replaced_now() ) {
236 safe_clear( poll_id );
237 if ( observer ) {
238 try {
239 observer.disconnect();
240 } catch ( e ) {
241 }
242 }
243 return;
244 }
245
246 var M = get_messages( rid );
247 var msg;
248 if ( ! has_jq() ) {
249 msg = M.lib_jq;
250 } else if ( ! has_wpbc() ) {
251 msg = M.lib_wpbc;
252 } else if ( ! has_dp() ) {
253 msg = M.lib_dp;
254 } else {
255 // Libraries are present, but loader wasn't replaced -> decide what is most likely.
256 if ( is_duplicate_rid( rid ) ) {
257 msg = M.duplicate + '\n\n' + M.support;
258 } else {
259 msg = M.init_failed + '\n\n' + M.support;
260 }
261 }
262
263 try {
264 if ( text_el ) {
265 text_el.innerHTML = wrap_html( msg );
266 }
267 } catch ( e ) {
268 }
269
270 safe_clear( poll_id );
271 if ( observer ) {
272 try {
273 observer.disconnect();
274 } catch ( e ) {
275 }
276 }
277 }, grace_ms );
278 }
279
280 /**
281 * Initialize watchers for loader elements already in the DOM.
282 */
283 function bootstrap_existing() {
284 query_all( '.calendar_loader_frame[data-wpbc-rid]' ).forEach( start_for );
285 }
286
287 /**
288 * Observe the document for any new loader elements inserted later (AJAX, block render).
289 */
290 function observe_new_loaders() {
291 if ( ! ('MutationObserver' in w) ) {
292 return;
293 }
294 try {
295 var doc_observer = new MutationObserver( function (mutations) {
296 for ( var i = 0; i < mutations.length; i++ ) {
297 var nodes = mutations[i].addedNodes || [];
298 for ( var j = 0; j < nodes.length; j++ ) {
299 var node = nodes[j];
300 if ( ! node || node.nodeType !== 1 ) {
301 continue;
302 }
303 if ( node.matches && node.matches( '.calendar_loader_frame[data-wpbc-rid]' ) ) {
304 start_for( node );
305 }
306 if ( node.querySelectorAll ) {
307 var inner = node.querySelectorAll( '.calendar_loader_frame[data-wpbc-rid]' );
308 if ( inner && inner.length ) {
309 inner.forEach( start_for );
310 }
311 }
312 }
313 }
314 } );
315 doc_observer.observe( d.documentElement, { childList: true, subtree: true } );
316 } catch ( e ) {
317 }
318 }
319
320 /* ---------------------------------------------------------------------------
321 * Boot
322 * ------------------------------------------------------------------------ */
323 on_ready( function () {
324 bootstrap_existing();
325 observe_new_loaders();
326 } );
327
328 })( window, document );
329