/** * Booking Calendar — WP template helpers (BFB Admin) * * Provides a tiny wrapper around wp.template() and a safe "ensure in DOM" helper * for templates that produce modal markup or other admin UI. * * Exposes under: * WPBC_BFB_Core.UI.Templates: * - render_wp_template( template_id, data ) * - ensure_dom_from_wp_template( template_id, dom_id?, data? ) * - ensure_dom_ref_from_wp_template( template_id, dom_id?, data? ) * * Back-compat aliases (optional): * - render() * - ensure_dom() * - ensure_dom_ref() * @file ../includes/page-form-builder/admin-page-tpl/_out/bfb-templates.js */ (function (w, d) { 'use strict'; const Core = (w.WPBC_BFB_Core = w.WPBC_BFB_Core || {}); const UI = (Core.UI = Core.UI || {}); /** * Namespace for template helpers. */ const Templates = (UI.Templates = UI.Templates || {}); /** * Escape an id for querySelector usage. * * @param {string} s * @returns {string} */ function css_escape(s) { s = String( s || '' ); if ( w.CSS && typeof w.CSS.escape === 'function' ) { return w.CSS.escape( s ); } return s.replace( /([^\w-])/g, '\\$1' ); } /** * Try to obtain wp.template render function. * * @param {string} template_id * @returns {Function|null} */ function get_wp_template_fn(template_id) { try { if ( ! w.wp || typeof w.wp.template !== 'function' ) { return null; } const fn = w.wp.template( String( template_id || '' ) ); return (typeof fn === 'function') ? fn : null; } catch ( _e ) { return null; } } /** * Render wp.template() safely. * * Note: template_id is the id WITHOUT the "tmpl-" prefix. * * @param {string} template_id * @param {Object} [data] * @returns {string} HTML string or empty string on failure */ function render_wp_template(template_id, data) { const fn = get_wp_template_fn( template_id ); if ( ! fn ) { return ''; } try { return String( fn( data || {} ) ); } catch ( _e ) { return ''; } } /** * Parse arguments for ensure_dom_from_wp_template() supporting both signatures: * - (template_id, dom_id, data) * - (template_id, data) // dom_id omitted * * @param {string|Object} dom_id_or_data * @param {Object} data * @returns {{dom_id:string, tpl_data:Object}} */ function normalize_dom_args(dom_id_or_data, data) { // If 2nd arg is plain object => treat as data, no explicit dom_id. if ( dom_id_or_data && typeof dom_id_or_data === 'object' && ! Array.isArray( dom_id_or_data ) ) { return { dom_id: '', tpl_data: dom_id_or_data || {} }; } return { dom_id: String( dom_id_or_data || '' ), tpl_data: data || {} }; } /** * Find an id on the root element or inside its subtree. * Used to avoid inserting duplicates when template HTML already has an id. * * @param {HTMLElement} root * @returns {string} */ function detect_first_id(root) { try { const direct = String( root.id || root.getAttribute( 'id' ) || '' ); if ( direct ) { return direct; } const first_with_id = root.querySelector ? root.querySelector( '[id]' ) : null; return first_with_id ? String( first_with_id.id || '' ) : ''; } catch ( _e ) { return ''; } } /** * Ensure the returned node has a stable id (only if it doesn't have one). * * @param {HTMLElement} el * @param {string} preferred_id * @param {string} fallback_id * @returns {void} */ function ensure_element_id(el, preferred_id, fallback_id) { if ( ! el || el.id ) { return; } const base = String( preferred_id || fallback_id || '' ); if ( ! base ) { return; } let candidate = base; let i = 2; while ( d.getElementById( candidate ) && d.getElementById( candidate ) !== el ) { candidate = base + '-' + (i++); } try { el.id = candidate; } catch ( _e ) { } } /** * Ensure a rendered template element exists in DOM (lazy insert). * * Behavior: * - If dom_id is provided and exists => returns that existing node. * - Otherwise renders the template and inserts its first root node into DOM. * - If the rendered root (or its subtree) already has an id that exists in DOM, * return the existing element instead of inserting a duplicate. * - Returns the requested dom_id element if possible, else the inserted root. * * @param {string} template_id * @param {string|Object} [dom_id_or_data] * @param {Object} [data] * @returns {HTMLElement|null} */ function ensure_dom_from_wp_template(template_id, dom_id_or_data, data) { const args = normalize_dom_args( dom_id_or_data, data ); const dom_id = args.dom_id; const tpl_data = args.tpl_data; // 1) If caller asked for a specific dom_id and it's already in DOM, return it. if ( dom_id ) { const existing = d.getElementById( dom_id ); if ( existing ) { return existing; } } // 2) Render template HTML. const html = render_wp_template( template_id, tpl_data ); if ( ! html ) { return null; } // 3) Convert HTML -> element (first root node). const wrap = d.createElement( 'div' ); wrap.innerHTML = String( html ).trim(); const root = wrap.firstElementChild; if ( ! root ) { return null; } // 4) Avoid duplicate IDs: // If template contains an id (root or subtree) and that id already exists in DOM, // return the existing element instead of inserting a duplicate. const detected_id = detect_first_id( root ); if ( !dom_id && detected_id ) { const existing_by_detected = d.getElementById( detected_id ); if ( existing_by_detected ) return existing_by_detected; } // 5) Insert into DOM. (d.body || d.documentElement).appendChild( root ); // 6) Return requested target: // - If dom_id provided: try getElementById(dom_id), else try querySelector inside root, else root. let ret = root; if ( dom_id ) { ret = d.getElementById( dom_id ) || root; if ( ret === root ) { try { const inside = root.querySelector( '#' + css_escape( dom_id ) ); if ( inside ) ret = inside; } catch ( _e ) { } } } // 7) Make sure returned node has an id (useful for modal show/hide). ensure_element_id( ret, dom_id, detected_id || ('wpbc_tpl_' + String( template_id || 'tpl' )) ); // 8) Post-render normalizers (safe no-op if not defined yet). try { if ( UI.apply_post_render ) { UI.apply_post_render( ret ); } } catch ( _e ) { } return ret; } /** * Same as ensure_dom_from_wp_template(), but returns both element and resolved id. * * @param {string} template_id * @param {string|Object} [dom_id_or_data] * @param {Object} [data] * @returns {{id:string, el:HTMLElement}|null} */ function ensure_dom_ref_from_wp_template(template_id, dom_id_or_data, data) { const el = ensure_dom_from_wp_template( template_id, dom_id_or_data, data ); if ( !el ) return null; return { id: String( el.id || '' ), el }; } // ------------------------------------------------------------------------- // Public API (clear names) // ------------------------------------------------------------------------- Templates.render_wp_template = render_wp_template; Templates.ensure_dom_from_wp_template = ensure_dom_from_wp_template; Templates.ensure_dom_ref_from_wp_template = ensure_dom_ref_from_wp_template; // Keep escape available (useful for callers that need selector-safe ids). Templates.css_escape = css_escape; // Also keep your existing UI.* aliases if other files already call these:. UI.render_wp_template = Templates.render_wp_template; UI.ensure_dom_from_wp_template = Templates.ensure_dom_from_wp_template; UI.ensure_dom_ref_from_wp_template = Templates.ensure_dom_ref_from_wp_template; // ------------------------------------------------------------------------- // UI.Modals (admin helper) // - Optional dependency: jQuery.fn.wpbc_my_modal // - Safe fallback: toggles display style // ------------------------------------------------------------------------- const Modals = (UI.Modals = UI.Modals || {}); // Reuse the same escape helper everywhere. Modals.css_escape = Modals.css_escape || Templates.css_escape || css_escape; Modals.show = Modals.show || function (modal_el_or_id) { const modal_id = (typeof modal_el_or_id === 'string') ? String( modal_el_or_id || '' ) : String( (modal_el_or_id && modal_el_or_id.id) ? modal_el_or_id.id : '' ); const modal_el = (typeof modal_el_or_id === 'string') ? d.getElementById( modal_id ) : modal_el_or_id; if ( !modal_el ) return; try { if ( w.jQuery && w.jQuery.fn && w.jQuery.fn.wpbc_my_modal ) { const $m = modal_id ? w.jQuery( '#' + Modals.css_escape( modal_id ) ) : w.jQuery( modal_el ); if ( $m && $m.wpbc_my_modal ) { $m.wpbc_my_modal( 'show' ); return; } } } catch ( _e ) { } modal_el.style.display = 'block'; }; Modals.hide = Modals.hide || function (modal_el_or_id) { const modal_id = (typeof modal_el_or_id === 'string') ? String( modal_el_or_id || '' ) : String( (modal_el_or_id && modal_el_or_id.id) ? modal_el_or_id.id : '' ); const modal_el = (typeof modal_el_or_id === 'string') ? d.getElementById( modal_id ) : modal_el_or_id; if ( !modal_el ) return; try { if ( w.jQuery && w.jQuery.fn && w.jQuery.fn.wpbc_my_modal ) { const $m = modal_id ? w.jQuery( '#' + Modals.css_escape( modal_id ) ) : w.jQuery( modal_el ); if ( $m && $m.wpbc_my_modal ) { $m.wpbc_my_modal( 'hide' ); return; } } } catch ( _e ) { } modal_el.style.display = 'none'; }; }( window, document ));