| 1 |
/** |
| 2 |
* Booking Calendar — WP template helpers (BFB Admin) |
| 3 |
* |
| 4 |
* Provides a tiny wrapper around wp.template() and a safe "ensure in DOM" helper |
| 5 |
* for templates that produce modal markup or other admin UI. |
| 6 |
* |
| 7 |
* Exposes under: |
| 8 |
* WPBC_BFB_Core.UI.Templates: |
| 9 |
* - render_wp_template( template_id, data ) |
| 10 |
* - ensure_dom_from_wp_template( template_id, dom_id?, data? ) |
| 11 |
* - ensure_dom_ref_from_wp_template( template_id, dom_id?, data? ) |
| 12 |
* |
| 13 |
* Back-compat aliases (optional): |
| 14 |
* - render() |
| 15 |
* - ensure_dom() |
| 16 |
* - ensure_dom_ref() |
| 17 |
* @file ../includes/page-form-builder/admin-page-tpl/_out/bfb-templates.js |
| 18 |
*/ |
| 19 |
(function (w, d) { |
| 20 |
'use strict'; |
| 21 |
|
| 22 |
const Core = (w.WPBC_BFB_Core = w.WPBC_BFB_Core || {}); |
| 23 |
const UI = (Core.UI = Core.UI || {}); |
| 24 |
|
| 25 |
/** |
| 26 |
* Namespace for template helpers. |
| 27 |
*/ |
| 28 |
const Templates = (UI.Templates = UI.Templates || {}); |
| 29 |
|
| 30 |
/** |
| 31 |
* Escape an id for querySelector usage. |
| 32 |
* |
| 33 |
* @param {string} s |
| 34 |
* @returns {string} |
| 35 |
*/ |
| 36 |
function css_escape(s) { |
| 37 |
s = String( s || '' ); |
| 38 |
if ( w.CSS && typeof w.CSS.escape === 'function' ) { |
| 39 |
return w.CSS.escape( s ); |
| 40 |
} |
| 41 |
return s.replace( /([^\w-])/g, '\\$1' ); |
| 42 |
} |
| 43 |
|
| 44 |
/** |
| 45 |
* Try to obtain wp.template render function. |
| 46 |
* |
| 47 |
* @param {string} template_id |
| 48 |
* @returns {Function|null} |
| 49 |
*/ |
| 50 |
function get_wp_template_fn(template_id) { |
| 51 |
try { |
| 52 |
if ( ! w.wp || typeof w.wp.template !== 'function' ) { |
| 53 |
return null; |
| 54 |
} |
| 55 |
const fn = w.wp.template( String( template_id || '' ) ); |
| 56 |
return (typeof fn === 'function') ? fn : null; |
| 57 |
} catch ( _e ) { |
| 58 |
return null; |
| 59 |
} |
| 60 |
} |
| 61 |
|
| 62 |
/** |
| 63 |
* Render wp.template() safely. |
| 64 |
* |
| 65 |
* Note: template_id is the id WITHOUT the "tmpl-" prefix. |
| 66 |
* |
| 67 |
* @param {string} template_id |
| 68 |
* @param {Object} [data] |
| 69 |
* @returns {string} HTML string or empty string on failure |
| 70 |
*/ |
| 71 |
function render_wp_template(template_id, data) { |
| 72 |
const fn = get_wp_template_fn( template_id ); |
| 73 |
if ( ! fn ) { |
| 74 |
return ''; |
| 75 |
} |
| 76 |
try { |
| 77 |
return String( fn( data || {} ) ); |
| 78 |
} catch ( _e ) { |
| 79 |
return ''; |
| 80 |
} |
| 81 |
} |
| 82 |
|
| 83 |
/** |
| 84 |
* Parse arguments for ensure_dom_from_wp_template() supporting both signatures: |
| 85 |
* - (template_id, dom_id, data) |
| 86 |
* - (template_id, data) // dom_id omitted |
| 87 |
* |
| 88 |
* @param {string|Object} dom_id_or_data |
| 89 |
* @param {Object} data |
| 90 |
* @returns {{dom_id:string, tpl_data:Object}} |
| 91 |
*/ |
| 92 |
function normalize_dom_args(dom_id_or_data, data) { |
| 93 |
// If 2nd arg is plain object => treat as data, no explicit dom_id. |
| 94 |
if ( dom_id_or_data && typeof dom_id_or_data === 'object' && ! Array.isArray( dom_id_or_data ) ) { |
| 95 |
return { dom_id: '', tpl_data: dom_id_or_data || {} }; |
| 96 |
} |
| 97 |
return { dom_id: String( dom_id_or_data || '' ), tpl_data: data || {} }; |
| 98 |
} |
| 99 |
|
| 100 |
/** |
| 101 |
* Find an id on the root element or inside its subtree. |
| 102 |
* Used to avoid inserting duplicates when template HTML already has an id. |
| 103 |
* |
| 104 |
* @param {HTMLElement} root |
| 105 |
* @returns {string} |
| 106 |
*/ |
| 107 |
function detect_first_id(root) { |
| 108 |
try { |
| 109 |
const direct = String( root.id || root.getAttribute( 'id' ) || '' ); |
| 110 |
if ( direct ) { |
| 111 |
return direct; |
| 112 |
} |
| 113 |
|
| 114 |
const first_with_id = root.querySelector ? root.querySelector( '[id]' ) : null; |
| 115 |
return first_with_id ? String( first_with_id.id || '' ) : ''; |
| 116 |
} catch ( _e ) { |
| 117 |
return ''; |
| 118 |
} |
| 119 |
} |
| 120 |
|
| 121 |
/** |
| 122 |
* Ensure the returned node has a stable id (only if it doesn't have one). |
| 123 |
* |
| 124 |
* @param {HTMLElement} el |
| 125 |
* @param {string} preferred_id |
| 126 |
* @param {string} fallback_id |
| 127 |
* @returns {void} |
| 128 |
*/ |
| 129 |
function ensure_element_id(el, preferred_id, fallback_id) { |
| 130 |
if ( ! el || el.id ) { |
| 131 |
return; |
| 132 |
} |
| 133 |
|
| 134 |
const base = String( preferred_id || fallback_id || '' ); |
| 135 |
if ( ! base ) { |
| 136 |
return; |
| 137 |
} |
| 138 |
|
| 139 |
let candidate = base; |
| 140 |
let i = 2; |
| 141 |
|
| 142 |
while ( d.getElementById( candidate ) && d.getElementById( candidate ) !== el ) { |
| 143 |
candidate = base + '-' + (i++); |
| 144 |
} |
| 145 |
|
| 146 |
try { |
| 147 |
el.id = candidate; |
| 148 |
} catch ( _e ) { |
| 149 |
} |
| 150 |
} |
| 151 |
|
| 152 |
/** |
| 153 |
* Ensure a rendered template element exists in DOM (lazy insert). |
| 154 |
* |
| 155 |
* Behavior: |
| 156 |
* - If dom_id is provided and exists => returns that existing node. |
| 157 |
* - Otherwise renders the template and inserts its first root node into DOM. |
| 158 |
* - If the rendered root (or its subtree) already has an id that exists in DOM, |
| 159 |
* return the existing element instead of inserting a duplicate. |
| 160 |
* - Returns the requested dom_id element if possible, else the inserted root. |
| 161 |
* |
| 162 |
* @param {string} template_id |
| 163 |
* @param {string|Object} [dom_id_or_data] |
| 164 |
* @param {Object} [data] |
| 165 |
* @returns {HTMLElement|null} |
| 166 |
*/ |
| 167 |
function ensure_dom_from_wp_template(template_id, dom_id_or_data, data) { |
| 168 |
const args = normalize_dom_args( dom_id_or_data, data ); |
| 169 |
const dom_id = args.dom_id; |
| 170 |
const tpl_data = args.tpl_data; |
| 171 |
|
| 172 |
// 1) If caller asked for a specific dom_id and it's already in DOM, return it. |
| 173 |
if ( dom_id ) { |
| 174 |
const existing = d.getElementById( dom_id ); |
| 175 |
if ( existing ) { |
| 176 |
return existing; |
| 177 |
} |
| 178 |
} |
| 179 |
|
| 180 |
// 2) Render template HTML. |
| 181 |
const html = render_wp_template( template_id, tpl_data ); |
| 182 |
if ( ! html ) { |
| 183 |
return null; |
| 184 |
} |
| 185 |
|
| 186 |
// 3) Convert HTML -> element (first root node). |
| 187 |
const wrap = d.createElement( 'div' ); |
| 188 |
wrap.innerHTML = String( html ).trim(); |
| 189 |
|
| 190 |
const root = wrap.firstElementChild; |
| 191 |
if ( ! root ) { |
| 192 |
return null; |
| 193 |
} |
| 194 |
|
| 195 |
// 4) Avoid duplicate IDs: |
| 196 |
// If template contains an id (root or subtree) and that id already exists in DOM, |
| 197 |
// return the existing element instead of inserting a duplicate. |
| 198 |
const detected_id = detect_first_id( root ); |
| 199 |
if ( !dom_id && detected_id ) { |
| 200 |
const existing_by_detected = d.getElementById( detected_id ); |
| 201 |
if ( existing_by_detected ) return existing_by_detected; |
| 202 |
} |
| 203 |
|
| 204 |
// 5) Insert into DOM. |
| 205 |
(d.body || d.documentElement).appendChild( root ); |
| 206 |
|
| 207 |
// 6) Return requested target: |
| 208 |
// - If dom_id provided: try getElementById(dom_id), else try querySelector inside root, else root. |
| 209 |
let ret = root; |
| 210 |
|
| 211 |
if ( dom_id ) { |
| 212 |
ret = d.getElementById( dom_id ) || root; |
| 213 |
if ( ret === root ) { |
| 214 |
try { |
| 215 |
const inside = root.querySelector( '#' + css_escape( dom_id ) ); |
| 216 |
if ( inside ) ret = inside; |
| 217 |
} catch ( _e ) { |
| 218 |
} |
| 219 |
} |
| 220 |
} |
| 221 |
|
| 222 |
// 7) Make sure returned node has an id (useful for modal show/hide). |
| 223 |
ensure_element_id( ret, dom_id, detected_id || ('wpbc_tpl_' + String( template_id || 'tpl' )) ); |
| 224 |
|
| 225 |
// 8) Post-render normalizers (safe no-op if not defined yet). |
| 226 |
try { |
| 227 |
if ( UI.apply_post_render ) { |
| 228 |
UI.apply_post_render( ret ); |
| 229 |
} |
| 230 |
} catch ( _e ) { |
| 231 |
} |
| 232 |
|
| 233 |
return ret; |
| 234 |
} |
| 235 |
|
| 236 |
/** |
| 237 |
* Same as ensure_dom_from_wp_template(), but returns both element and resolved id. |
| 238 |
* |
| 239 |
* @param {string} template_id |
| 240 |
* @param {string|Object} [dom_id_or_data] |
| 241 |
* @param {Object} [data] |
| 242 |
* @returns {{id:string, el:HTMLElement}|null} |
| 243 |
*/ |
| 244 |
function ensure_dom_ref_from_wp_template(template_id, dom_id_or_data, data) { |
| 245 |
const el = ensure_dom_from_wp_template( template_id, dom_id_or_data, data ); |
| 246 |
if ( !el ) return null; |
| 247 |
return { id: String( el.id || '' ), el }; |
| 248 |
} |
| 249 |
|
| 250 |
// ------------------------------------------------------------------------- |
| 251 |
// Public API (clear names) |
| 252 |
// ------------------------------------------------------------------------- |
| 253 |
|
| 254 |
Templates.render_wp_template = render_wp_template; |
| 255 |
Templates.ensure_dom_from_wp_template = ensure_dom_from_wp_template; |
| 256 |
Templates.ensure_dom_ref_from_wp_template = ensure_dom_ref_from_wp_template; |
| 257 |
|
| 258 |
// Keep escape available (useful for callers that need selector-safe ids). |
| 259 |
Templates.css_escape = css_escape; |
| 260 |
|
| 261 |
// Also keep your existing UI.* aliases if other files already call these:. |
| 262 |
UI.render_wp_template = Templates.render_wp_template; |
| 263 |
UI.ensure_dom_from_wp_template = Templates.ensure_dom_from_wp_template; |
| 264 |
UI.ensure_dom_ref_from_wp_template = Templates.ensure_dom_ref_from_wp_template; |
| 265 |
|
| 266 |
|
| 267 |
// ------------------------------------------------------------------------- |
| 268 |
// UI.Modals (admin helper) |
| 269 |
// - Optional dependency: jQuery.fn.wpbc_my_modal |
| 270 |
// - Safe fallback: toggles display style |
| 271 |
// ------------------------------------------------------------------------- |
| 272 |
|
| 273 |
const Modals = (UI.Modals = UI.Modals || {}); |
| 274 |
|
| 275 |
// Reuse the same escape helper everywhere. |
| 276 |
Modals.css_escape = Modals.css_escape || Templates.css_escape || css_escape; |
| 277 |
|
| 278 |
Modals.show = Modals.show || function (modal_el_or_id) { |
| 279 |
|
| 280 |
const modal_id = (typeof modal_el_or_id === 'string') |
| 281 |
? String( modal_el_or_id || '' ) |
| 282 |
: String( (modal_el_or_id && modal_el_or_id.id) ? modal_el_or_id.id : '' ); |
| 283 |
|
| 284 |
const modal_el = (typeof modal_el_or_id === 'string') |
| 285 |
? d.getElementById( modal_id ) |
| 286 |
: modal_el_or_id; |
| 287 |
|
| 288 |
if ( !modal_el ) return; |
| 289 |
|
| 290 |
try { |
| 291 |
if ( w.jQuery && w.jQuery.fn && w.jQuery.fn.wpbc_my_modal ) { |
| 292 |
|
| 293 |
const $m = modal_id |
| 294 |
? w.jQuery( '#' + Modals.css_escape( modal_id ) ) |
| 295 |
: w.jQuery( modal_el ); |
| 296 |
|
| 297 |
if ( $m && $m.wpbc_my_modal ) { |
| 298 |
$m.wpbc_my_modal( 'show' ); |
| 299 |
return; |
| 300 |
} |
| 301 |
} |
| 302 |
} catch ( _e ) { |
| 303 |
} |
| 304 |
|
| 305 |
modal_el.style.display = 'block'; |
| 306 |
}; |
| 307 |
|
| 308 |
Modals.hide = Modals.hide || function (modal_el_or_id) { |
| 309 |
|
| 310 |
const modal_id = (typeof modal_el_or_id === 'string') |
| 311 |
? String( modal_el_or_id || '' ) |
| 312 |
: String( (modal_el_or_id && modal_el_or_id.id) ? modal_el_or_id.id : '' ); |
| 313 |
|
| 314 |
const modal_el = (typeof modal_el_or_id === 'string') |
| 315 |
? d.getElementById( modal_id ) |
| 316 |
: modal_el_or_id; |
| 317 |
|
| 318 |
if ( !modal_el ) return; |
| 319 |
|
| 320 |
try { |
| 321 |
if ( w.jQuery && w.jQuery.fn && w.jQuery.fn.wpbc_my_modal ) { |
| 322 |
|
| 323 |
const $m = modal_id |
| 324 |
? w.jQuery( '#' + Modals.css_escape( modal_id ) ) |
| 325 |
: w.jQuery( modal_el ); |
| 326 |
|
| 327 |
if ( $m && $m.wpbc_my_modal ) { |
| 328 |
$m.wpbc_my_modal( 'hide' ); |
| 329 |
return; |
| 330 |
} |
| 331 |
} |
| 332 |
} catch ( _e ) { |
| 333 |
} |
| 334 |
|
| 335 |
modal_el.style.display = 'none'; |
| 336 |
}; |
| 337 |
|
| 338 |
}( window, document )); |
| 339 |
|