PluginProbe
Booking Calendar / 11.9
Booking Calendar v11.9
11.9 11.8.4 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 All 205 releases
booking / includes / __js / client / cal / wpbc_cal_loader.js

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

353 lines 10.5 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
81 * ------------------------------------------------------------------------ */
82
83 /**
84 * Build visitor messages for a resource, using localized Form Messages when available.
85 * @param {string|number} rid
86 * @return {{duplicate:string,init_failed: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 var duplicate_message =
91 'More than one calendar or booking form for Booking Resource ID {resource_id} is present on this page. ' +
92 'A hidden copy still conflicts with the visible form and can prevent booking submission. ' +
93 'The site administrator must keep only one calendar or form for this Booking Resource. ' +
94 'After the page is corrected, reload it and try again.';
95
96 if ( w._wpbc && typeof w._wpbc.get_message === 'function' ) {
97 duplicate_message = w._wpbc.get_message( 'message_duplicate_booking_resource_on_page' ) || duplicate_message;
98 }
99 duplicate_message = String( duplicate_message ).replace( /\{resource_id\}/g, String( rid_int ) );
100
101 return {
102 duplicate : duplicate_message,
103 init_failed:
104 'The calendar could not be initialized on this page.' + '\n' +
105 'Please check your browser console for JavaScript errors and conflicts with other scripts/plugins.',
106 support : '', /* 'Contact [email protected] if you have any questions.', */
107 lib_jq :
108 'It appears that the "jQuery" library is not loading correctly.' + '\n' +
109 'For more information, please refer to this page: https://wpbookingcalendar.com/faq/',
110 lib_dp :
111 'It appears that the "jQuery.datepick" library is not loading correctly.' + '\n' +
112 'For more information, please refer to this page: https://wpbookingcalendar.com/faq/',
113 lib_wpbc :
114 'It appears that the "_wpbc" library is not loading correctly.' + '\n' +
115 'Please enable the loading of JS/CSS files for this page on the "WP Booking Calendar" - "Settings General" - "Advanced" page' + '\n' +
116 'For more information, please refer to this page: https://wpbookingcalendar.com/faq/'
117 };
118 }
119
120 /**
121 * Escape a plain-text loader message before inserting its wrapper markup.
122 *
123 * @param {string} plain_text Visitor-facing plain text.
124 * @return {string} HTML-safe text.
125 */
126 function escape_html(plain_text) {
127 return String( plain_text || '' )
128 .replace( /&/g, '&' )
129 .replace( /</g, '&lt;' )
130 .replace( />/g, '&gt;' )
131 .replace( /"/g, '&quot;' )
132 .replace( /'/g, '&#039;' );
133 }
134
135 /**
136 * Wrap plain text (with newlines) in a small HTML container.
137 * @param {string} msg
138 * @return {string}
139 */
140 function wrap_html(msg) {
141 return '<div style="font-size:13px;margin:10px;">' + escape_html( msg ).replace( /\n/g, '<br>' ) + '</div>';
142 }
143
144 /** Library presence checks (fast & cheap). */
145 function has_jq() {
146 return !!(w.jQuery && jQuery.fn && typeof jQuery.fn.on === 'function');
147 }
148
149 function has_dp() {
150 return !!(w.jQuery && jQuery.datepick);
151 }
152
153 function has_wpbc() {
154 return !!(w._wpbc && typeof w._wpbc.set_other_param === 'function');
155 }
156
157 function normalize_rid(rid) {
158 var n = parseInt( rid, 10 );
159 return (n > 0) ? String( n ) : '';
160 }
161
162 function get_rid_counts(rid) {
163 var r = normalize_rid( rid );
164 return {
165 rid : r,
166 loaders : r ? query_all( '.calendar_loader_frame[data-wpbc-rid="' + r + '"]' ).length : 0,
167 containers: r ? query_all( '#calendar_booking' + r ).length : 0
168 };
169 }
170
171 function is_duplicate_rid(rid) {
172 var c = get_rid_counts( rid );
173 return (c.loaders > 1) || (c.containers > 1);
174 }
175
176 /**
177 * Determine if the loader has been replaced by the real calendar.
178 *
179 * @param {Element} el Loader element
180 * @param {string} rid Resource ID
181 * @param {Element|null} container Optional #calendar_booking{rid} element
182 * @return {boolean}
183 */
184 function is_replaced(el, rid, container) {
185 var loader_still_in_dom = d.body.contains( el );
186 var calendar_exists = !!query_one( '.wpbc_calendar_id_' + rid, container || d );
187 return (!loader_still_in_dom) || calendar_exists;
188 }
189
190 /**
191 * Start watcher for a single loader element.
192 * - Polls and observes the calendar container.
193 * - After grace, injects a suitable message if not replaced.
194 *
195 * @param {Element} el
196 */
197 function start_for(el) {
198 if ( ! el || is_processed( el ) ) {
199 return;
200 }
201 mark_processed( el );
202
203 var rid = el.dataset.wpbcRid;
204 if ( ! rid ) {
205 return;
206 }
207
208 var grace_ms = parseInt( el.dataset.wpbcGrace || '8000', 10 );
209 if ( ! (grace_ms > 0) ) {
210 grace_ms = 8000;
211 }
212
213 var container_id = 'calendar_booking' + rid;
214 var container = d.getElementById( container_id );
215 var text_el = query_one( '.calendar_loader_text', el );
216
217 function replaced_now() {
218 return is_replaced( el, rid, container );
219 }
220
221 // Already replaced -> nothing to do.
222 if ( replaced_now() ) {
223 return;
224 }
225
226 // 1) Cheap polling.
227 var poll_id = w.setInterval( function () {
228 if ( replaced_now() ) {
229 safe_clear( poll_id );
230 if ( observer ) {
231 try {
232 observer.disconnect();
233 } catch ( e ) {
234 }
235 }
236 }
237 }, 250 );
238
239 // 2) MutationObserver for faster reaction.
240 var observer = null;
241 if ( container && 'MutationObserver' in w ) {
242 try {
243 observer = new MutationObserver( function () {
244 if ( replaced_now() ) {
245 safe_clear( poll_id );
246 try {
247 observer.disconnect();
248 } catch ( e ) {
249 }
250 }
251 } );
252 observer.observe( container, { childList: true, subtree: true } );
253 } catch ( e ) {
254 }
255 }
256
257 // 3) Final decision after grace period.
258 w.setTimeout( function finalize_after_grace() {
259 if ( replaced_now() ) {
260 safe_clear( poll_id );
261 if ( observer ) {
262 try {
263 observer.disconnect();
264 } catch ( e ) {
265 }
266 }
267 return;
268 }
269
270 var M = get_messages( rid );
271 var msg;
272 if ( ! has_jq() ) {
273 msg = M.lib_jq;
274 } else if ( ! has_wpbc() ) {
275 msg = M.lib_wpbc;
276 } else if ( ! has_dp() ) {
277 msg = M.lib_dp;
278 } else {
279 // Libraries are present, but loader wasn't replaced -> decide what is most likely.
280 if ( is_duplicate_rid( rid ) ) {
281 msg = M.duplicate + '\n\n' + M.support;
282 } else {
283 msg = M.init_failed + '\n\n' + M.support;
284 }
285 }
286
287 try {
288 if ( text_el ) {
289 text_el.innerHTML = wrap_html( msg );
290 }
291 } catch ( e ) {
292 }
293
294 safe_clear( poll_id );
295 if ( observer ) {
296 try {
297 observer.disconnect();
298 } catch ( e ) {
299 }
300 }
301 }, grace_ms );
302 }
303
304 /**
305 * Initialize watchers for loader elements already in the DOM.
306 */
307 function bootstrap_existing() {
308 query_all( '.calendar_loader_frame[data-wpbc-rid]' ).forEach( start_for );
309 }
310
311 /**
312 * Observe the document for any new loader elements inserted later (AJAX, block render).
313 */
314 function observe_new_loaders() {
315 if ( ! ('MutationObserver' in w) ) {
316 return;
317 }
318 try {
319 var doc_observer = new MutationObserver( function (mutations) {
320 for ( var i = 0; i < mutations.length; i++ ) {
321 var nodes = mutations[i].addedNodes || [];
322 for ( var j = 0; j < nodes.length; j++ ) {
323 var node = nodes[j];
324 if ( ! node || node.nodeType !== 1 ) {
325 continue;
326 }
327 if ( node.matches && node.matches( '.calendar_loader_frame[data-wpbc-rid]' ) ) {
328 start_for( node );
329 }
330 if ( node.querySelectorAll ) {
331 var inner = node.querySelectorAll( '.calendar_loader_frame[data-wpbc-rid]' );
332 if ( inner && inner.length ) {
333 inner.forEach( start_for );
334 }
335 }
336 }
337 }
338 } );
339 doc_observer.observe( d.documentElement, { childList: true, subtree: true } );
340 } catch ( e ) {
341 }
342 }
343
344 /* ---------------------------------------------------------------------------
345 * Boot
346 * ------------------------------------------------------------------------ */
347 on_ready( function () {
348 bootstrap_existing();
349 observe_new_loaders();
350 } );
351
352 })( window, document );
353