PluginProbe
Booking Calendar / 10.15.6
Booking Calendar v10.15.6
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 / page-form-builder / field-packs / calendar / _src / calendar.js

calendar.js in Booking Calendar 10.15.6, at includes/page-form-builder/field-packs/calendar/_src/calendar.js

816 lines 28.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 // ---------------------------------------------------------------------------------------------------------------------
2 // == File /includes/page-form-builder/field-packs/calendar/_out/calendar.js
3 // == Pack Calendar (WP-template–driven) — minimal, modern, and Builder-focused renderer
4 // == Compatible with PHP pack: ../calendar.php (version 1.2.2)
5 // ---------------------------------------------------------------------------------------------------------------------
6 (function (w) {
7 'use strict';
8
9 // Direct dev logger alias (snake format); no local wrappers.
10 const dev = (w._wpbc && w._wpbc.dev) ? w._wpbc.dev : { log(){}, error(){} };
11
12 // Core singletons from the Builder.
13 var Core = w.WPBC_BFB_Core || {};
14 var Registry = Core.WPBC_BFB_Field_Renderer_Registry;
15 var Field_Base = Core.WPBC_BFB_Field_Base || null;
16
17 if ( !Registry || !Registry.register ) {
18 dev.error( 'WPBC_BFB_Field_Calendar', 'Registry missing — load bfb-core first.' );
19 return;
20 }
21
22 // Localized boot payload from PHP (calendar.php::enqueue_js()).
23 var Boot = w.WPBC_BFB_CalendarBoot || {};
24
25 // Remember which resource IDs already have their data loaded this session.
26 var rid_loaded_cache = Object.create( null );
27
28 // -----------------------------------------------------------------------------------------------------------------
29 // Resource ID Helpers
30 // -----------------------------------------------------------------------------------------------------------------
31 /**
32 * Get configured preview booking resource ID from localized boot data.
33 *
34 * @returns {number}
35 */
36 function get_configured_preview_resource_id() {
37 var rid = Number( Boot.default_preview_resource_id || 1 );
38 rid = isFinite( rid ) ? Math.max( 1, Math.floor( rid ) ) : 1;
39 return rid;
40 }
41
42 /**
43 * Get first existing booking resource ID from localized boot data.
44 *
45 * @returns {number}
46 */
47 function get_first_existing_resource_id() {
48
49 var resources = Array.isArray( Boot.booking_resources ) ? Boot.booking_resources : [];
50
51 for ( var i = 0; i < resources.length; i++ ) {
52 var id = Number( resources[i] && resources[i].booking_type_id );
53 if ( isFinite( id ) && id > 0 ) {
54 return Math.floor( id );
55 }
56 }
57
58 return 1;
59 }
60
61 /**
62 * Resolve effective preview booking resource ID.
63 * Priority:
64 * 1. Configured default preview resource, if it exists
65 * 2. Provided rid, if it exists
66 * 3. First existing booking resource
67 * 4. Fallback to 1
68 *
69 * @param {number} rid_candidate
70 * @returns {number}
71 */
72 function resolve_effective_resource_id(rid_candidate) {
73
74 var resources = Array.isArray( Boot.booking_resources ) ? Boot.booking_resources : [];
75 var configured_rid = get_configured_preview_resource_id();
76 var candidate = Number( rid_candidate || 1 );
77
78 function resource_exists(id) {
79 id = Number( id || 1 );
80 if ( ! isFinite( id ) || id <= 0 ) {
81 return false;
82 }
83
84 for ( var i = 0; i < resources.length; i++ ) {
85 if ( Number( resources[i] && resources[i].booking_type_id ) === id ) {
86 return true;
87 }
88 }
89 return false;
90 }
91
92 if ( resource_exists( configured_rid ) ) {
93 return configured_rid;
94 }
95
96 if ( resource_exists( candidate ) ) {
97 return candidate;
98 }
99
100 return get_first_existing_resource_id();
101 }
102
103 // -----------------------------------------------------------------------------------------------------------------
104 // Small utilities
105 // -----------------------------------------------------------------------------------------------------------------
106
107 /**
108 * Debounce wrapper.
109 *
110 * @param {Function} fn
111 * @param {number} ms
112 * @returns {Function}
113 */
114 function debounce(fn, ms) {
115 var t;
116 return function () {
117 var a = arguments;
118 clearTimeout( t );
119 t = setTimeout( function () {
120 fn.apply( null, a );
121 }, ms );
122 };
123 }
124
125 /**
126 * Wait until calendar API is present in the window (e.g., wpbc_calendar_show).
127 *
128 * @param {Function} cb Callback when ready.
129 * @param {number} max_tries Maximum retry attempts.
130 * @param {number} delay_ms Delay between attempts.
131 */
132 function wait_until_api_ready(cb, max_tries, delay_ms) {
133 var tries = 0;
134 (function tick() {
135 if ( typeof w.wpbc_calendar_show === 'function' ) {
136 try {
137 cb();
138 } catch ( e ) {
139 dev.error( 'api_ready_cb', e );
140 }
141 return;
142 }
143 if ( tries++ >= (max_tries || 40) ) {
144 dev.log( 'calendar_api_not_ready_after_retries' );
145 return;
146 }
147 w.setTimeout( tick, delay_ms || 100 );
148 })();
149 }
150
151 /**
152 * Apply "months in row" class to container element.
153 *
154 * @param {Element} field_el Field root element (wrap).
155 * @param {number} months 1..12
156 */
157 function apply_months_class(field_el, months) {
158 var cont = field_el ? field_el.querySelector( '.wpbc_cal_container' ) : null;
159 if ( ! cont ) {
160 return;
161 }
162 // Remove existing cal_month_num_* safely via classList.
163 Array.from( cont.classList ).forEach( function (c) {
164 if ( /^cal_month_num_\d+$/.test( c ) ) {
165 cont.classList.remove( c );
166 }
167 } );
168 cont.classList.add( 'cal_month_num_' + months );
169 }
170
171 /**
172 * Set secure parameters (nonce, user_id, locale) before any AJAX calls.
173 */
174 function set_secure_params() {
175 try {
176 if ( !(w._wpbc && typeof w._wpbc.set_secure_param === 'function') ) {
177 return;
178 }
179 if ( Boot.nonce ) {
180 w._wpbc.set_secure_param( 'nonce', String( Boot.nonce ) );
181 }
182 if ( Boot.user_id != null ) {
183 w._wpbc.set_secure_param( 'user_id', String( Boot.user_id ) );
184 }
185 if ( Boot.locale ) {
186 w._wpbc.set_secure_param( 'locale', String( Boot.locale ) );
187 }
188 } catch ( e ) {
189 dev.log( 'secure_params_skip', e );
190 }
191 }
192
193 /**
194 * Push calendar environment parameters for a specific resource and months count.
195 * We *directly* set parameters (no global polyfill) to keep this file self-contained.
196 *
197 * @param {number} rid
198 * @param {number} months
199 */
200 function set_calendar_params(rid, months) {
201 var L = Boot || {};
202 try {
203 if ( w._wpbc && typeof w._wpbc.balancer__set_max_threads === 'function' ) {
204 w._wpbc.balancer__set_max_threads( Number( L.balancer_max_threads || 1 ) );
205 }
206 } catch ( e ) {
207 }
208
209 function set_param(k, v) {
210 try {
211 if ( w._wpbc && typeof w._wpbc.calendar__set_param_value === 'function' ) {
212 w._wpbc.calendar__set_param_value( rid, k, v );
213 }
214 } catch ( e ) {
215 }
216 }
217
218 if ( L.booking_max_monthes_in_calendar != null ) {
219 set_param( 'booking_max_monthes_in_calendar', String( L.booking_max_monthes_in_calendar ) );
220 }
221 if ( L.booking_start_day_weeek != null ) {
222 set_param( 'booking_start_day_weeek', String( L.booking_start_day_weeek ) );
223 }
224 set_param( 'calendar_number_of_months', String( months ) );
225 set_param( 'calendar_scroll_to', false );
226
227 if ( L.booking_date_format ) {
228 set_param( 'booking_date_format', String( L.booking_date_format ) );
229 }
230 if ( L.booking_time_format ) {
231 set_param( 'booking_time_format', String( L.booking_time_format ) );
232 }
233
234 var ds = L.days_selection || {};
235 set_param( 'days_select_mode', String( ds.days_select_mode || 'multiple' ) );
236 set_param( 'fixed__days_num', Number( ds.fixed__days_num || 0 ) );
237 if ( ds.fixed__week_days__start != null ) {
238 set_param( 'fixed__week_days__start', [ String( ds.fixed__week_days__start ) ] );
239 }
240 set_param( 'dynamic__days_min', Number( ds.dynamic__days_min || 0 ) );
241 set_param( 'dynamic__days_max', Number( ds.dynamic__days_max || 0 ) );
242 if ( ds.dynamic__days_specific != null ) {
243 var arr = String( ds.dynamic__days_specific || '' ).split( /\s*,\s*/ ).filter( Boolean ).map( Number );
244 set_param( 'dynamic__days_specific', arr );
245 }
246 if ( ds.dynamic__week_days__start != null ) {
247 set_param( 'dynamic__week_days__start', [ String( ds.dynamic__week_days__start ) ] );
248 }
249
250 try {
251 if ( typeof w.wpbc__conditions__SAVE_INITIAL__days_selection_params__bm === 'function' ) {
252 w.wpbc__conditions__SAVE_INITIAL__days_selection_params__bm( rid );
253 }
254 } catch ( e ) {
255 }
256 }
257
258 /**
259 * Extract sanitized rid and months from data or DOM.
260 *
261 * @param {Element} field_el
262 * @param {object} data
263 * @returns {{rid:number, months:number}}
264 */
265 function get_rid_and_months(field_el, data) {
266 var rid = 1;
267 var months = 1;
268
269 // NEW: prefer dataset on the wrap (Inspector edits land here first)
270 var wrap = field_el ? (field_el.closest && field_el.closest('.wpbc_calendar_wraper')) || field_el : null;
271 if (wrap && wrap.dataset) {
272 if (wrap.dataset.resource_id != null && wrap.dataset.resource_id !== '') {
273 rid = Number(wrap.dataset.resource_id);
274 }
275 if (wrap.dataset.months != null && wrap.dataset.months !== '') {
276 months = Number(wrap.dataset.months);
277 }
278 }
279
280 if ( data && data.resource_id != null ) {
281 rid = Number( data.resource_id );
282 }
283 if ( data && data.months != null ) {
284 months = Number( data.months );
285 }
286
287 if ( !data ) {
288 // Fallbacks from DOM when data object is not provided.
289 var n = field_el ? field_el.querySelector( '[id^="calendar_booking"]' ) : null;
290 if ( n && n.id ) {
291 var m1 = n.id.match( /calendar_booking(\d+)/ );
292 if ( m1 ) {
293 rid = Number( m1[1] );
294 }
295 }
296 var cont = field_el ? field_el.querySelector( '.wpbc_cal_container' ) : null;
297 if ( cont && cont.className ) {
298 var m2 = cont.className.match( /cal_month_num_(\d+)/ );
299 if ( m2 ) {
300 months = Number( m2[1] );
301 }
302 }
303 }
304
305 // FixIn: 2026-03-07 Override to always get the default booking resource, for specific user in MU or for existed resourecs !
306 rid = resolve_effective_resource_id( rid );
307
308 months = isFinite( months ) ? Math.max( 1, Math.min( 12, Math.floor( months ) ) ) : 1;
309
310 return { rid: rid, months: months };
311 }
312
313 // -----------------------------------------------------------------------------------------------------------------
314 // Minimal preview bootstrap — load/refresh a calendar for a field
315 // -----------------------------------------------------------------------------------------------------------------
316
317
318 /**
319 * Initialize (or update) a calendar preview for a given field element.
320 *
321 * @param {Element} field_el - calendar field element (wrap)
322 * @param {object} data - builder field data (optional)
323 * @param {boolean} should_reload_data - force AJAX data reload
324 */
325 function init_field(field_el, data, should_reload_data = true) {
326 if ( !field_el ) {
327 return;
328 }
329
330 var pair = get_rid_and_months( field_el, data );
331 var rid = pair.rid;
332
333
334 wait_until_api_ready( function () {
335
336 // 1) Always (re)apply local UI — needed after DOM moves.
337 apply_months_class( field_el, pair.months );
338 set_calendar_params( rid, pair.months );
339
340 try {
341 w.wpbc_calendar_show( String( rid ) );
342 } catch ( e1 ) {
343 dev.error( 'wpbc_calendar_show', e1 );
344 }
345 set_secure_params();
346
347 // 2) Decide on AJAX strictly by RID state + explicit request.
348 var first_time_for_rid = !rid_loaded_cache[rid];
349 // Respect caller intent. Reload only if explicitly asked OR first time for this RID.
350 var do_reload = !!should_reload_data || first_time_for_rid;
351
352 try {
353 if ( typeof w.wpbc_calendar__load_data__ajx === 'function' ) {
354 if ( do_reload ) {
355 w.wpbc_calendar__load_data__ajx( {
356 'resource_id' : rid,
357 'booking_hash' : '',
358 'request_uri' : Boot.request_uri || (w.location ? String( w.location.pathname + w.location.search ) : ''),
359 'custom_form' : 'standard',
360 'aggregate_resource_id_str': '',
361 'aggregate_type' : 'all'
362 } );
363 // Mark this rid as loaded so subsequent DOM churn uses look-only refresh.
364 rid_loaded_cache[rid] = true;
365 } else if ( typeof w.wpbc_calendar__update_look === 'function' ) {
366 w.wpbc_calendar__update_look( rid );
367 }
368 }
369 } catch ( e2 ) {
370 dev.log( 'calendar_data_load_skip', e2 );
371 }
372
373 // Track last config for soft dedupe (UI churn); keep per-element.
374 try { field_el.setAttribute('data-wpbc-cal-init', '1'); field_el.setAttribute('data-wpbc-cal-loaded-rid', String(rid)); } catch (e3) {}
375
376 }, 40, 100 );
377 }
378
379
380 // -----------------------------------------------------------------------------------------------------------------
381 // FOCS: tiny bus & DOM helpers (snake format)
382 // -----------------------------------------------------------------------------------------------------------------
383 const defer_fn = (fn) => (typeof w.requestAnimationFrame === 'function' ? w.requestAnimationFrame( fn ) : setTimeout( fn, 0 ));
384
385 function find_calendar_wrap(el) {
386 if ( ! el ) {
387 return null;
388 }
389 return (el.closest && el.closest( '.wpbc_calendar_wraper' )) || el;
390 }
391
392 function is_calendar_wrap(el) {
393 var wrap = find_calendar_wrap( el );
394 return !!(wrap && (
395 (wrap.dataset && wrap.dataset.type === 'calendar') ||
396 (wrap.querySelector && wrap.querySelector( '[id^="calendar_booking"]' ))
397 ));
398 }
399
400 function look_refresh(target, opts) {
401 var o = opts || {};
402 var wrap = find_calendar_wrap( target );
403 if ( ! wrap || ! document.contains( wrap ) ) {
404 return;
405 }
406 defer_fn( function () {
407 init_field( wrap, null, !!o.reload );
408 } );
409 }
410
411 function on_event(type, handler) {
412 document.addEventListener( type, handler );
413 }
414
415 /**
416 * Initialize all calendars present in the current Builder preview panel.
417 */
418 function init_all_on_page(should_reload_data = true) {
419 var scope = w.document.querySelector( '#wpbc_bfb__pages_panel' ) || w.document;
420 var nodes = scope.querySelectorAll( '[id^="calendar_booking"]' );
421 for ( var i = 0; i < nodes.length; i++ ) {
422 var node = nodes[i];
423 var field_el = node.closest( '.wpbc_calendar_wraper' ) || node.parentElement || node;
424 init_field( field_el, null, should_reload_data );
425 }
426 }
427
428
429 /**
430 * Listen to builder’s bus (events bubble on document via Core.WPBC_BFB_EventBus)
431 */
432 function bind_builder_bus_events() {
433
434 // Alias events once.
435 var EV = Core.WPBC_BFB_Events || {};
436
437 // 1) First structure ready -> look-only render.
438 on_event( EV.STRUCTURE_LOADED, function () {
439 init_all_on_page( false );
440 } );
441
442 // 2) Field/section added -> initialize that node if it’s a calendar (force reload).
443 on_event( EV.FIELD_ADD, function (e) {
444 var el = e && e.detail && e.detail.el;
445 if ( ! el ) {
446 // Defensive fallback: if emitter didn’t provide the element, refresh all calendars (look-only).
447 return defer_fn(function () { init_all_on_page(false); });
448 }
449 if ( is_calendar_wrap( el ) ) {
450 look_refresh( el, { reload: true } ); // builder just inserted; So do Ajax fetch data.
451 }
452 } );
453
454
455 // 3) Generic structure changes.
456 on_event( EV.STRUCTURE_CHANGE, function (e) {
457 var d = (e && e.detail) || {};
458 var reason = d.reason || '';
459
460 // Heavy operations -> cheap look refresh for all calendars, no data reload.
461 if ( reason === 'sort-update' || reason === 'section-move' || reason === 'delete' ) {
462 return defer_fn( function () {
463 init_all_on_page( false );
464 } );
465 }
466
467 // Only care about calendar targets.
468 if ( ! is_calendar_wrap( d.el ) ) return;
469
470 // Only reload data on committed resource_id changes.
471 var k = d.key || '';
472 var phase = (d.phase || '').toLowerCase(); // set by the emitter above.
473 if ( k === 'resource_id' && phase !== 'change' ) {
474 return; // Skip on second input handler: ins.addEventListener( 'input', handler, true ); in ../includes/page-form-builder/__js/core/bfb-ui.js //.
475 }
476 // Replace to TRUE, if needs to FORCE ajax reload of calendar data of resource ID change.
477 var must_reload = (k === 'resource_id' && phase === 'change')
478 ? false
479 : false;
480 look_refresh( d.el, { reload: must_reload } );
481 } );
482 }
483
484 // -----------------------------------------------------------------------------------------------------------------
485 // Field Renderer (class-like, extendable)
486 // -----------------------------------------------------------------------------------------------------------------
487 class WPBC_BFB_Field_Calendar extends (Field_Base || class {}) {
488
489 static template_id = 'wpbc-bfb-field-calendar'; // Underscore template id from PHP printer.
490 static kind = 'calendar';
491
492 /**
493 * Default props — keep in sync with PHP schema defaults.
494 */
495 static get_defaults() {
496 return {
497 type : 'calendar',
498 label : 'Select Date',
499 resource_id: resolve_effective_resource_id( 1 ),
500 months : 1,
501 name : '',
502 html_id : '',
503 cssclass : '',
504 help : '',
505 min_width : '250px'
506 };
507 }
508
509 /**
510 * Called by the Builder after the field is dropped/loaded/previewed.
511 * We (re)initialize the preview for the specific element.
512 *
513 * @param {object} data
514 * @param {Element} field_el
515 * @param {{context:string}} ctx
516 */
517 static on_field_drop(data, field_el, ctx) {
518 try {
519 init_field( field_el, data, false );
520 } catch ( e ) {
521 dev.error( 'WPBC_BFB_Field_Calendar.on_field_drop', e );
522 }
523 }
524
525 /**
526 * Hydrate after preview render (no rebuild). Called by builder.render_preview().
527 */
528 static hydrate(field_el, data, ctx) {
529 try {
530 init_field( field_el, data, false );
531 } catch ( e ) {
532 dev.error( 'WPBC_BFB_Field_Calendar.hydrate', e );
533 }
534 }
535
536 }
537
538 // Register pack renderer with the central registry.
539 try {
540 Registry.register( 'calendar', WPBC_BFB_Field_Calendar );
541 } catch ( e ) {
542 dev.error( 'WPBC_BFB_Field_Calendar.register', e );
543 }
544
545 // Bootstrap: on DOM ready, run a first scan and wire light reactivity.
546 function on_ready(fn) {
547 if ( w.document.readyState === 'interactive' || w.document.readyState === 'complete' ) {
548 try {
549 fn();
550 } catch ( e ) {
551 }
552 } else {
553 w.document.addEventListener( 'DOMContentLoaded', function () {
554 try {
555 fn();
556 } catch ( e ) {
557 }
558 } );
559 }
560 }
561
562 on_ready( function () {
563 setTimeout( function () {
564 init_all_on_page( false );
565 bind_builder_bus_events();
566 }, 0 );
567 } );
568
569 // Optional export (handy for debugging).
570 w.WPBC_BFB_Field_Calendar = WPBC_BFB_Field_Calendar;
571
572
573 // -- Export for "Booking Form" ------------------------------------------------------------------------------------
574
575 /**
576 * Register the "calendar" exporter (lazy: tries now, or waits for exporter-ready).
577 * Output:
578 * • [calendar] only (no rid/months/class/id tokens inside)
579 * • If html_id / cssclass set → wrap shortcode in <span ... style="flex:1;">…</span>
580 * • Label above (when addLabels !== false).
581 * Help text is appended by WPBC_BFB_Exporter.render_field_node().
582 *
583 * Booking Form exporter callback (Advanced Form shortcode).
584 *
585 * This callback is registered per field type via:
586 * WPBC_BFB_Exporter.register( 'shortcode_name', callback )
587 *
588 * Core call site (builder-exporter.js):
589 * WPBC_BFB_Exporter.run_registered_exporter( field, io, cfg, once, ctx )
590 * → callback( field, emit, { io, cfg, once, ctx, core } );
591 *
592 * @callback WPBC_BFB_ExporterCallback
593 * @param {Object} field
594 * Normalized field data coming from the Builder structure.
595 * - field.type {string} Field type, e.g. "text".
596 * - field.name {string} Name as stored on the canvas (already validated).
597 * - field.id / html_id {string} Optional HTML id / user-visible id.
598 * - field.label {string} Visible label in the form (may be empty).
599 * - field.placeholder {string} Placeholder text (may be empty).
600 * - field.required {boolean|number|string} "truthy" if required.
601 * - field.cssclass {string} Extra CSS classes entered in Inspector.
602 * - field.default_value {string} Default text value.
603 * - field.options {Array} Only for option-based fields (select, checkbox, etc.).
604 * - ... (Any other pack-specific props are also present.)
605 *
606 * @param {function(string):void} emit
607 * Emits one line/fragment into the export buffer.
608 * - Each call corresponds to one `push()` in the core exporter.
609 * - For multi-line output (e.g. label + shortcode), call `emit()` multiple times:
610 * emit('<l>Label</l>');
611 * emit('<br>[text* name ...]');
612 *
613 * @param {Object} [extras]
614 * Extra context passed by the core exporter.
615 *
616 * @param {Object} [extras.io]
617 * Low-level writer used internally by the core.
618 * Normally you do NOT need it in packs — prefer `emit()`.
619 * - extras.io.open(str) → open a nested block (increments indentation).
620 * - extras.io.close(str) → close a block (decrements indentation).
621 * - extras.io.push(str) → push raw line (used by `emit()`).
622 * - extras.io.blank() → push an empty line.
623 *
624 * @param {Object} [extras.cfg]
625 * Export configuration (same object passed to WPBC_BFB_Exporter.export_form()).
626 * Useful flags for field packs:
627 * - extras.cfg.addLabels {boolean} Default: true.
628 * If false, packs should NOT emit <l>Label</l> lines.
629 * - extras.cfg.newline {string} Newline separator (usually "\n").
630 * - extras.cfg.gapPercent{number} Layout gap (used only by section/column logic).
631 *
632 * @param {Object} [extras.once]
633 * Shared "once-per-form" guards across all fields.
634 * Counters are incremented by some field types (captcha, coupon, etc.).
635 * Typical shape:
636 * - extras.once.captcha {number}
637 * - extras.once.country {number}
638 * - extras.once.coupon {number}
639 * - extras.once.cost_corrections {number}
640 * - extras.once.submit {number}
641 *
642 * Text field usually does not touch this object, but other packs can use it
643 * to skip duplicates (e.g. only the first [coupon] per form is exported).
644 *
645 * @param {Object} [extras.ctx]
646 * Shared export context for the entire form.
647 * Currently:
648 * - extras.ctx.usedIds {Set<string>}
649 * Set of HTML/shortcode IDs already used in this export.
650 * Helpers like Exp.id_option(field, ctx) use it to ensure uniqueness.
651 *
652 * Packs normally just pass `ctx` into helpers (id_option, etc.) without
653 * mutating it directly.
654 *
655 * @param {Object} [extras.core]
656 * Reference to WPBC_BFB_Core passed from builder-exporter.js.
657 * Primarily used to access sanitizers:
658 * - extras.core.WPBC_BFB_Sanitize.escape_html(...)
659 * - extras.core.WPBC_BFB_Sanitize.escape_for_shortcode(...)
660 * - extras.core.WPBC_BFB_Sanitize.sanitize_html_name(...)
661 * - etc.
662 */
663 function export_shortcode_in_booking_form() {
664
665 const Exp = w.WPBC_BFB_Exporter;
666 if ( ! Exp || typeof Exp.register !== 'function' ) { return false; }
667 if ( typeof Exp.has_exporter === 'function' && Exp.has_exporter( 'calendar' ) ) { return true; }
668
669 // Use sanitize helpers from core (already loaded).
670 const S = Core.WPBC_BFB_Sanitize || (w.WPBC_BFB_Core && w.WPBC_BFB_Core.WPBC_BFB_Sanitize) || {};
671 const esc = S.escape_html || (v => String( v ));
672 const sid = S.sanitize_html_id || (v => String( v ));
673 const scls = S.sanitize_css_classlist || (v => String( v ));
674
675 /**
676 * Per-field exporter for "calendar" in Advanced Form.
677 * @type {WPBC_BFB_ExporterCallback}
678 */
679 Exp.register( 'calendar', (field, emit, extras = {}) => {
680
681 const cfg = extras.cfg || {};
682 const ctx = extras.ctx;
683 const usedIds = (ctx && ctx.usedIds instanceof Set) ? ctx.usedIds : null;
684 const addLabels = cfg.addLabels !== false;
685
686 // Optional wrapper attrs (id/class on outer span, not inside [calendar]).
687 let html_id = field && field.html_id ? sid( String( field.html_id ) ) : '';
688 if ( html_id && usedIds ) {
689 let u = html_id, i = 2;
690 while ( usedIds.has( u ) ) {
691 u = `${html_id}_${i++}`;
692 }
693 usedIds.add( u );
694 html_id = u;
695 }
696
697 const cls_raw = field && (field.cssclass_extra || field.cssclass || field.class) || '';
698 const cls = scls( String( cls_raw ) );
699
700 const hasWrap = !!( html_id || cls );
701 const wrapOpen = hasWrap
702 ? `<span${html_id ? ` id="${esc( html_id )}"` : ''}${cls ? ` class="${esc( cls )}"` : ''} style="flex:1;">`
703 : '';
704 const wrapClose = hasWrap ? '</span>' : '';
705
706 // Calendar body is intentionally minimal; no rid/months/id/class tokens inside shortcode.
707 const body = '[calendar]';
708 const label = (typeof field?.label === 'string') ? field.label.trim() : '';
709
710 if ( label && addLabels ) {
711 emit( `<l>${esc( label )}</l>` );
712 emit( `<br>${wrapOpen}${body}${wrapClose}` );
713 } else {
714 emit( `${wrapOpen}${body}${wrapClose}` );
715 }
716 } );
717
718 return true;
719 }
720
721 // Try now; if exporter isn't ready yet, wait for one-shot event from builder-exporter.
722 if ( ! export_shortcode_in_booking_form() ) {
723 document.addEventListener( 'wpbc:bfb:exporter-ready', export_shortcode_in_booking_form, { once: true } );
724 }
725
726 // -- Export for "Booking Data" ------------------------------------------------------------------------------------
727
728 /**
729 * Register the "calendar" exporter for "Content of booking fields data". Produces e.g.: "<b>Dates</b>:
730 * <f>[dates]</f><br>"
731 *
732 * Booking Data exporter callback ("Content of booking fields data"). Default output: <b>Label</b>:
733 * <f>[field_name]</f><br>
734 *
735 * Registered per field type via:
736 * WPBC_BFB_ContentExporter.register( 'shortcode_name', callback )
737 *
738 * Core call site (builder-exporter.js):
739 * WPBC_BFB_ContentExporter.run_registered_exporter( field, emit, { cfg, core } );
740 *
741 * @callback WPBC_BFB_ContentExporterCallback
742 * @param {Object} field
743 * Normalized field data (same shape as in the main exporter).
744 * Important properties for content templates:
745 * - field.type {string} Field type, e.g. "text".
746 * - field.name {string} Field name used as placeholder token.
747 * - field.label {string} Human-readable label (may be empty).
748 * - field.options {Array} For option-based fields (select, checkbox, radio, etc.).
749 * - Other pack-specific props if needed.
750 *
751 * @param {function(string):void} emit
752 * Emits a raw HTML fragment into the "Content of booking fields data" template.
753 * Core will wrap everything once into:
754 * <div class="standard-content-form">
755 * ... emitted fragments ...
756 * </div>
757 *
758 * Typical usage pattern:
759 * emit('<b>Label</b>: <f>[field_name]</f><br>');
760 *
761 * In most cases, packs call the shared helper:
762 * WPBC_BFB_ContentExporter.emit_line_bold_field(emit, label, token, cfg);
763 *
764 * @param {Object} [extras]
765 * Additional context passed from run_registered_exporter().
766 *
767 * @param {Object} [extras.cfg]
768 * Content exporter configuration:
769 * - extras.cfg.addLabels {boolean} Default: true.
770 * If false, helper may omit the bold label part.
771 * - extras.cfg.sep {string} Label separator, default ": ".
772 * Example: "<b>Label</b>: " vs "<b>Label</b> – ".
773 * - extras.cfg.newline {string} Newline separator when joining lines (usually "\n").
774 *
775 * @param {Object} [extras.core]
776 * Reference to WPBC_BFB_Core (same as in main exporter).
777 * Usually not needed here, because:
778 * - Sanitization and consistent rendering are already done via
779 * WPBC_BFB_ContentExporter.emit_line_bold_field( ... ).
780 */
781 function export_shortcode_in_booking_data() {
782
783 var C = w.WPBC_BFB_ContentExporter;
784 if ( ! C || typeof C.register !== 'function' ) { return false; }
785 if ( typeof C.has_exporter === 'function' && C.has_exporter( 'calendar' ) ) { return true; }
786
787 C.register( 'calendar', function ( field, emit, extras ) {
788
789 extras = extras || {};
790 var cfg = extras.cfg || {};
791 var label = (typeof field.label === 'string' && field.label.trim()) ? field.label.trim() : 'Dates';
792
793 // Reuse shared formatter from builder-exporter - e.g.: emit_line_bold_field(emit, label, token, cfg) -> emit(`<b>${S.escape_html(label)}</b>${sep}<f>[${token}]</f><br>`); .
794 // C.emit_line_bold_field( emit, label, 'dates', cfg );
795
796 if(0) {
797 // Defensive fallback: keep a simple, backward-compatible output. Just for help in using in other field packs.
798 var core_local = extras.core || Core || {};
799 var S_local = core_local.WPBC_BFB_Sanitize || {};
800 var esc = S_local.escape_html || function (s) { return String( s ); };
801
802 var sep = (cfg && typeof cfg.sep === 'string') ? cfg.sep : ': ';
803 var title = label ? '<b>' + esc( label ) + '</b>' + sep : '';
804 emit( title + '<f>[dates]</f><br>' );
805 }
806 } );
807
808 return true;
809 }
810
811 if ( ! export_shortcode_in_booking_data() ) {
812 document.addEventListener( 'wpbc:bfb:content-exporter-ready', export_shortcode_in_booking_data, { once: true } );
813 }
814
815 })( window );
816