| 1 |
/** |
| 2 |
* WPBC BFB: Field Renderer for "text" (Schema-driven Reference, template-literal render) |
| 3 |
* ============================================================================================== |
| 4 |
* Purpose: |
| 5 |
* - Uses template literals (no wp.template) |
| 6 |
* - Inspector is rendered by Factory (PHP schema) |
| 7 |
* - Uses WPBC_BFB_Sanitize (from core) with method names as in bfb-core.js |
| 8 |
* |
| 9 |
* Contracts: |
| 10 |
* - Registry: WPBC_BFB_Field_Renderer_Registry.register( 'text', Class ) |
| 11 |
* - Class API: static get_defaults(), static render(el, data, ctx), static on_field_drop(data, el, ctx?) [optional] |
| 12 |
* |
| 13 |
* Notes: |
| 14 |
* - Keep defaults aligned with PHP schema->props->default. |
| 15 |
* - Use Overlay.ensure(...) so field controls (handle, settings, etc.) appear. |
| 16 |
* |
| 17 |
* File: ../includes/page-form-builder/field-packs/text/_out/field-text.js |
| 18 |
* |
| 19 |
* @since 11.0.0 |
| 20 |
* @modified 2025-09-06 14:08 |
| 21 |
* @version 1.0.1 |
| 22 |
* |
| 23 |
*/ |
| 24 |
(function (w) { |
| 25 |
'use strict'; |
| 26 |
|
| 27 |
var Core = w.WPBC_BFB_Core || {}; |
| 28 |
var registry = Core.WPBC_BFB_Field_Renderer_Registry; |
| 29 |
var Base = Core.WPBC_BFB_Field_Base; |
| 30 |
|
| 31 |
if ( ! registry || typeof registry.register !== 'function' || ! Base ) { |
| 32 |
_wpbc?.dev?.error?.( 'WPBC_BFB_Field_Text', 'Core registry/base missing' ); |
| 33 |
return; |
| 34 |
} |
| 35 |
|
| 36 |
class WPBC_BFB_Field_Text extends Base { |
| 37 |
|
| 38 |
/** |
| 39 |
* Return default props for "text" field. |
| 40 |
* Must stay in sync with PHP schema defaults. |
| 41 |
* |
| 42 |
* @returns {Object} |
| 43 |
*/ |
| 44 |
static get_defaults() { |
| 45 |
return { |
| 46 |
type : 'text', |
| 47 |
label : 'Text', |
| 48 |
name : '', |
| 49 |
html_id : '', |
| 50 |
placeholder : '', |
| 51 |
required : false, |
| 52 |
minlength : null, |
| 53 |
maxlength : null, |
| 54 |
pattern : '', |
| 55 |
cssclass : '', |
| 56 |
help : '', |
| 57 |
default_value: '' |
| 58 |
}; |
| 59 |
} |
| 60 |
|
| 61 |
/** |
| 62 |
* Render the preview markup into the field element. |
| 63 |
* |
| 64 |
* @param {HTMLElement} el Field root element inside the canvas. |
| 65 |
* @param {Object} d Field props (already normalized by schema). |
| 66 |
* @param {Object} ctx Context: { builder, sanit, ... } |
| 67 |
*/ |
| 68 |
static render(el, data, ctx) { |
| 69 |
|
| 70 |
if ( ! el ) { |
| 71 |
return; |
| 72 |
} |
| 73 |
|
| 74 |
// Normalize against defaults first. |
| 75 |
const d = this.normalize_data( data ); |
| 76 |
|
| 77 |
// ----- Core sanitize helpers (static) ----- |
| 78 |
const eh = (v) => Core.WPBC_BFB_Sanitize.escape_html( v ); |
| 79 |
const sid = (v) => Core.WPBC_BFB_Sanitize.sanitize_html_id( v ); |
| 80 |
const sname = (v) => Core.WPBC_BFB_Sanitize.sanitize_html_name( v ); |
| 81 |
const sclass = (v) => Core.WPBC_BFB_Sanitize.sanitize_css_classlist( v ); |
| 82 |
const truthy = (v) => Core.WPBC_BFB_Sanitize.is_truthy( v ); |
| 83 |
|
| 84 |
// Sanitize public id/name for the control itself. |
| 85 |
const html_id = d.html_id ? sid( String( d.html_id ) ) : ''; |
| 86 |
const name_val = sname( String( d.name || d.id || 'field' ) ); |
| 87 |
const cssNext = sclass( String( d.cssclass || '' ) ); |
| 88 |
|
| 89 |
// Keep wrapper classes in sync with dataset.cssclass ONLY (don’t touch core classes). |
| 90 |
// if ( 'cssclass' in d ) { |
| 91 |
// const prev = el.dataset.cssclass || ''; |
| 92 |
// if ( prev !== cssNext ) { |
| 93 |
// prev.split( /\s+/ ).filter( Boolean ).forEach( (c) => el.classList.remove( c ) ); |
| 94 |
// cssNext.split( /\s+/ ).filter( Boolean ).forEach( (c) => el.classList.add( c ) ); |
| 95 |
// el.dataset.cssclass = cssNext; |
| 96 |
// } |
| 97 |
// } |
| 98 |
// NEW: store only; do NOT modify wrapper classes. |
| 99 |
if ( 'cssclass' in d ) { |
| 100 |
if ( el.dataset.cssclass !== cssNext ) { |
| 101 |
el.dataset.cssclass = cssNext; |
| 102 |
} |
| 103 |
} |
| 104 |
// Keep wrapper's stored html_id (dataset) updated. |
| 105 |
if ( 'html_id' in d ) { |
| 106 |
if ( el.dataset.html_id !== html_id ) { |
| 107 |
el.dataset.html_id = html_id; |
| 108 |
} |
| 109 |
} |
| 110 |
|
| 111 |
// Flags / numeric constraints. |
| 112 |
const is_required = truthy( d.required ); |
| 113 |
const has_minlength = (d.minlength != null && d.minlength !== '' && Number.isFinite( +d.minlength )); |
| 114 |
const has_maxlength = (d.maxlength != null && d.maxlength !== '' && Number.isFinite( +d.maxlength )); |
| 115 |
const has_pattern = !!d.pattern; |
| 116 |
|
| 117 |
const minlength_num = has_minlength ? Number( d.minlength ) : null; |
| 118 |
const maxlength_num = has_maxlength ? Number( d.maxlength ) : null; |
| 119 |
|
| 120 |
// Attribute fragments (using escape_html for safe innerHTML attribute context). |
| 121 |
const id_attr = html_id ? ` id="${eh( html_id )}"` : ''; |
| 122 |
const name_attr = ` name="${eh( name_val )}"`; |
| 123 |
// const cls_attr = ` class="wpbc_bfb__preview-input ${eh( cssNext )}"`; |
| 124 |
const cls_attr = ` class="wpbc_bfb__preview-input ${cssNext ? ' ' + eh(cssNext) : ''}"`; |
| 125 |
const ph_attr = d.placeholder ? ` placeholder="${eh( d.placeholder )}"` : ''; |
| 126 |
const req_attr = is_required ? ' required aria-required="true"' : ''; |
| 127 |
const minlength_attr = has_minlength ? ` minlength="${minlength_num}"` : ''; |
| 128 |
const maxlength_attr = has_maxlength ? ` maxlength="${maxlength_num}"` : ''; |
| 129 |
const pattern_attr = has_pattern ? ` pattern="${eh( d.pattern )}"` : ''; |
| 130 |
const value_attr = (d.default_value != null && d.default_value !== '') |
| 131 |
? ` value="${eh( String( d.default_value ) )}"` |
| 132 |
: ''; |
| 133 |
// Optional fragments. |
| 134 |
const label_html = (d.label != null && d.label !== '') |
| 135 |
? `<label class="wpbc_bfb__field-label"${html_id ? ` for="${eh( html_id )}"` : ''}>${eh( d.label )}${is_required ? '<span aria-hidden="true">*</span>' : ''}</label>` |
| 136 |
: ''; |
| 137 |
|
| 138 |
const help_html = d.help ? `<div class="wpbc_bfb__help">${eh( d.help )}</div>` : ''; |
| 139 |
|
| 140 |
// Render preview HTML. |
| 141 |
el.innerHTML = ` |
| 142 |
<span class="wpbc_bfb__noaction wpbc_bfb__no-drag-zone" inert=""> |
| 143 |
${label_html} |
| 144 |
<span class="wpbc_wrap_text wpdev-form-control-wrap"> |
| 145 |
<input type="text"${cls_attr}${ph_attr}${name_attr} autocomplete="off" tabindex="-1" aria-disabled="true"${id_attr}${value_attr}${req_attr}${minlength_attr}${maxlength_attr}${pattern_attr} /> |
| 146 |
|
| 147 |
</span> |
| 148 |
${help_html} |
| 149 |
</span> |
| 150 |
`; |
| 151 |
|
| 152 |
// Overlay (handles/toolbars). |
| 153 |
Core.UI?.WPBC_BFB_Overlay?.ensure?.( ctx?.builder, el ); |
| 154 |
} |
| 155 |
|
| 156 |
/** |
| 157 |
* Optional hook executed after field is dropped from the palette. |
| 158 |
* Example recipe placeholder for future: try { if ( !data.name ) { data.name = core.WPBC_BFB_IdService?.next_name?.( 'text' ) || 'text'; } } catch ( e ) { } |
| 159 |
* |
| 160 |
* @param {Object} data Palette/field data. |
| 161 |
* @param {HTMLElement} el Newly created field element. |
| 162 |
* @param {Object} ctx Context { builder, sanit, context: 'drop' | 'load' | 'preview' } |
| 163 |
* @returns {void} |
| 164 |
*/ |
| 165 |
static on_field_drop(data, el, ctx) { |
| 166 |
super.on_field_drop?.( data, el, ctx ); // Required for correctly auto-names from Labels ! |
| 167 |
// (your extra pack-specific logic if ever needed) |
| 168 |
} |
| 169 |
} |
| 170 |
|
| 171 |
try { |
| 172 |
registry.register( 'text', WPBC_BFB_Field_Text ); |
| 173 |
} catch ( e ) { |
| 174 |
_wpbc?.dev?.error?.( 'WPBC_BFB_Field_Text.register', e ); |
| 175 |
} |
| 176 |
|
| 177 |
// Optional global alias (debugging / dev tools). |
| 178 |
w.WPBC_BFB_Field_Text = w.WPBC_BFB_Field_Text || WPBC_BFB_Field_Text; |
| 179 |
|
| 180 |
|
| 181 |
// ----------------------------------------------------------------------------------------------------------------- |
| 182 |
// Export for "Booking Form" (Advanced Form shortcode) |
| 183 |
// ----------------------------------------------------------------------------------------------------------------- |
| 184 |
/** |
| 185 |
* Booking Form exporter callback (Advanced Form shortcode). |
| 186 |
* |
| 187 |
* This callback is registered per field type via: |
| 188 |
* WPBC_BFB_Exporter.register( 'text', callback ) |
| 189 |
* |
| 190 |
* Core call site (builder-exporter.js): |
| 191 |
* WPBC_BFB_Exporter.run_registered_exporter( field, io, cfg, once, ctx ) |
| 192 |
* -> callback( field, emit, { io, cfg, once, ctx, core } ); |
| 193 |
* |
| 194 |
* @callback WPBC_BFB_ExporterCallback |
| 195 |
* @param {Object} field |
| 196 |
* Normalized field data coming from the Builder structure. |
| 197 |
* - field.type {string} Field type, e.g. "text". |
| 198 |
* - field.name {string} Name as stored on the canvas (already validated). |
| 199 |
* - field.id / html_id {string} Optional HTML id / user-visible id. |
| 200 |
* - field.label {string} Visible label in the form (may be empty). |
| 201 |
* - field.placeholder {string} Placeholder text (may be empty). |
| 202 |
* - field.required {boolean|number|string} "truthy" if required. |
| 203 |
* - field.cssclass {string} Extra CSS classes entered in Inspector. |
| 204 |
* - field.default_value {string} Default text value. |
| 205 |
* - field.options {Array} Only for option-based fields (select, checkbox, etc.). |
| 206 |
* - ... (Any other pack-specific props are also present.) |
| 207 |
* |
| 208 |
* @param {function(string):void} emit |
| 209 |
* Emits one line/fragment into the export buffer. |
| 210 |
* - Each call corresponds to one `push()` in the core exporter. |
| 211 |
* - For multi-line output (e.g. label + shortcode), call `emit()` multiple times: |
| 212 |
* emit('<l>Label</l>'); |
| 213 |
* emit('<br>[text* name ...]'); |
| 214 |
* |
| 215 |
* @param {Object} [extras] |
| 216 |
* Extra context passed by the core exporter. |
| 217 |
* |
| 218 |
* @param {Object} [extras.io] |
| 219 |
* Low-level writer used internally by the core. |
| 220 |
* Normally you do NOT need it in packs — prefer `emit()`. |
| 221 |
* - extras.io.open(str) -> open a nested block (increments indentation). |
| 222 |
* - extras.io.close(str) -> close a block (decrements indentation). |
| 223 |
* - extras.io.push(str) -> push raw line (used by `emit()`). |
| 224 |
* - extras.io.blank() -> push an empty line. |
| 225 |
* |
| 226 |
* @param {Object} [extras.cfg] |
| 227 |
* Export configuration (same object passed to WPBC_BFB_Exporter.export_form()). |
| 228 |
* Useful flags for field packs: |
| 229 |
* - extras.cfg.addLabels {boolean} Default: true. |
| 230 |
* If false, packs should NOT emit <l>Label</l> lines. |
| 231 |
* - extras.cfg.newline {string} Newline separator (usually "\n"). |
| 232 |
* - extras.cfg.gapPercent{number} Layout gap (used only by section/column logic). |
| 233 |
* |
| 234 |
* @param {Object} [extras.once] |
| 235 |
* Shared "once-per-form" guards across all fields. |
| 236 |
* Counters are incremented by some field types (captcha, coupon, etc.). |
| 237 |
* Typical shape: |
| 238 |
* - extras.once.captcha {number} |
| 239 |
* - extras.once.country {number} |
| 240 |
* - extras.once.coupon {number} |
| 241 |
* - extras.once.cost_corrections {number} |
| 242 |
* - extras.once.submit {number} |
| 243 |
* |
| 244 |
* Text field usually does not touch this object, but other packs can use it |
| 245 |
* to skip duplicates (e.g. only the first [coupon] per form is exported). |
| 246 |
* |
| 247 |
* @param {Object} [extras.ctx] |
| 248 |
* Shared export context for the entire form. |
| 249 |
* Currently: |
| 250 |
* - extras.ctx.usedIds {Set<string>} |
| 251 |
* Set of HTML/shortcode IDs already used in this export. |
| 252 |
* Helpers like Exp.id_option(field, ctx) use it to ensure uniqueness. |
| 253 |
* |
| 254 |
* Packs normally just pass `ctx` into helpers (id_option, etc.) without |
| 255 |
* mutating it directly. |
| 256 |
* |
| 257 |
* @param {Object} [extras.core] |
| 258 |
* Reference to WPBC_BFB_Core passed from builder-exporter.js. |
| 259 |
* Primarily used to access sanitizers: |
| 260 |
* - extras.core.WPBC_BFB_Sanitize.escape_html(...) |
| 261 |
* - extras.core.WPBC_BFB_Sanitize.escape_for_shortcode(...) |
| 262 |
* - extras.core.WPBC_BFB_Sanitize.sanitize_html_name(...) |
| 263 |
* - etc. |
| 264 |
*/ |
| 265 |
function register_text_booking_form_exporter() { |
| 266 |
|
| 267 |
const Exp = w.WPBC_BFB_Exporter; |
| 268 |
if ( ! Exp || typeof Exp.register !== 'function' ) { return; } |
| 269 |
if ( typeof Exp.has_exporter === 'function' && Exp.has_exporter( 'text' ) ) { return; } |
| 270 |
|
| 271 |
const S = Core.WPBC_BFB_Sanitize; |
| 272 |
|
| 273 |
Exp.register( 'text', (field, emit, extras = {}) => { |
| 274 |
|
| 275 |
const cfg = extras.cfg || {}; |
| 276 |
const ctx = extras.ctx; // no local fallback needed. |
| 277 |
const addLabels = cfg.addLabels !== false; |
| 278 |
|
| 279 |
// Required marker logic (same as before). |
| 280 |
const is_req = Exp.is_required( field ); |
| 281 |
const req_mark = is_req ? '*' : ''; |
| 282 |
|
| 283 |
// Reuse helpers from WPBC_BFB_Exporter. |
| 284 |
const name = Exp.compute_name( 'text', field ); |
| 285 |
const id_opt = Exp.id_option( field, ctx ); |
| 286 |
const cls_opts = Exp.class_options( field ); |
| 287 |
const ph_attr = Exp.ph_attr( field.placeholder ); |
| 288 |
const size_max = Exp.size_max_token( field ); |
| 289 |
const def_value = Exp.default_text_suffix( field ); |
| 290 |
|
| 291 |
// Build body shortcode. |
| 292 |
const body = `[text${req_mark} ${name}${size_max}${id_opt}${cls_opts}${ph_attr}${def_value}]`; |
| 293 |
|
| 294 |
// Label behavior identical to legacy emit_label_then(). |
| 295 |
const label = (field.label ?? '').toString().trim(); |
| 296 |
if ( label && addLabels ) { |
| 297 |
emit( `<l>${S.escape_html( label )}${req_mark}</l>` ); |
| 298 |
emit( `<br>${body}` ); |
| 299 |
} else { |
| 300 |
emit( body ); |
| 301 |
} |
| 302 |
// Help text is appended centrally by WPBC_BFB_Exporter.render_field_node(). |
| 303 |
} ); |
| 304 |
} |
| 305 |
|
| 306 |
// Try immediate registration; if core isn’t ready, wait for the event. |
| 307 |
if ( w.WPBC_BFB_Exporter && typeof w.WPBC_BFB_Exporter.register === 'function' ) { |
| 308 |
register_text_booking_form_exporter(); |
| 309 |
} else { |
| 310 |
document.addEventListener( 'wpbc:bfb:exporter-ready', register_text_booking_form_exporter, { once: true } ); |
| 311 |
} |
| 312 |
|
| 313 |
|
| 314 |
// ----------------------------------------------------------------------------------------------------------------- |
| 315 |
// Export for "Booking Data" (Content of booking fields data) |
| 316 |
// ----------------------------------------------------------------------------------------------------------------- |
| 317 |
/** |
| 318 |
* Booking Data exporter callback ("Content of booking fields data"). Default output: <b>Label</b>: <f>[field_name]</f><br> |
| 319 |
* |
| 320 |
* Registered per field type via: |
| 321 |
* WPBC_BFB_ContentExporter.register( 'text', callback ) |
| 322 |
* |
| 323 |
* Core call site (builder-exporter.js): |
| 324 |
* WPBC_BFB_ContentExporter.run_registered_exporter( field, emit, { cfg, core } ); |
| 325 |
* |
| 326 |
* @callback WPBC_BFB_ContentExporterCallback |
| 327 |
* @param {Object} field |
| 328 |
* Normalized field data (same shape as in the main exporter). |
| 329 |
* Important properties for content templates: |
| 330 |
* - field.type {string} Field type, e.g. "text". |
| 331 |
* - field.name {string} Field name used as placeholder token. |
| 332 |
* - field.label {string} Human-readable label (may be empty). |
| 333 |
* - field.options {Array} For option-based fields (select, checkbox, radio, etc.). |
| 334 |
* - Other pack-specific props if needed. |
| 335 |
* |
| 336 |
* @param {function(string):void} emit |
| 337 |
* Emits a raw HTML fragment into the "Content of booking fields data" template. |
| 338 |
* Core will wrap everything once into: |
| 339 |
* <div class="standard-content-form"> |
| 340 |
* ... emitted fragments ... |
| 341 |
* </div> |
| 342 |
* |
| 343 |
* Typical usage pattern: |
| 344 |
* emit('<b>Label</b>: <f>[field_name]</f><br>'); |
| 345 |
* |
| 346 |
* In most cases, packs call the shared helper: |
| 347 |
* WPBC_BFB_ContentExporter.emit_line_bold_field(emit, label, token, cfg); |
| 348 |
* |
| 349 |
* @param {Object} [extras] |
| 350 |
* Additional context passed from run_registered_exporter(). |
| 351 |
* |
| 352 |
* @param {Object} [extras.cfg] |
| 353 |
* Content exporter configuration: |
| 354 |
* - extras.cfg.addLabels {boolean} Default: true. |
| 355 |
* If false, helper may omit the bold label part. |
| 356 |
* - extras.cfg.sep {string} Label separator, default ": ". |
| 357 |
* Example: "<b>Label</b>: " vs "<b>Label</b> – ". |
| 358 |
* - extras.cfg.newline {string} Newline separator when joining lines (usually "\n"). |
| 359 |
* |
| 360 |
* @param {Object} [extras.core] |
| 361 |
* Reference to WPBC_BFB_Core (same as in main exporter). |
| 362 |
* Usually not needed here, because: |
| 363 |
* - Sanitization and consistent rendering are already done via |
| 364 |
* WPBC_BFB_ContentExporter.emit_line_bold_field( ... ). |
| 365 |
*/ |
| 366 |
function register_text_booking_data_exporter() { |
| 367 |
|
| 368 |
const C = w.WPBC_BFB_ContentExporter; |
| 369 |
if ( ! C || typeof C.register !== 'function' ) { return; } |
| 370 |
if ( typeof C.has_exporter === 'function' && C.has_exporter( 'text' ) ) { return; } |
| 371 |
|
| 372 |
C.register( 'text', function (field, emit, extras) { |
| 373 |
|
| 374 |
extras = extras || {}; |
| 375 |
const cfg = extras.cfg || {}; |
| 376 |
|
| 377 |
const Exp = w.WPBC_BFB_Exporter; |
| 378 |
if ( ! Exp || typeof Exp.compute_name !== 'function' ) { return; } |
| 379 |
|
| 380 |
const name = Exp.compute_name( 'text', field ); |
| 381 |
if ( ! name ) { return; } |
| 382 |
|
| 383 |
const label = (typeof field.label === 'string' && field.label.trim()) ? field.label.trim() : name; |
| 384 |
|
| 385 |
// Shared formatter keeps all packs consistent:. |
| 386 |
C.emit_line_bold_field( emit, label, name, cfg ); |
| 387 |
} ); |
| 388 |
} |
| 389 |
|
| 390 |
if ( w.WPBC_BFB_ContentExporter && typeof w.WPBC_BFB_ContentExporter.register === 'function' ) { |
| 391 |
register_text_booking_data_exporter(); |
| 392 |
} else { |
| 393 |
document.addEventListener( 'wpbc:bfb:content-exporter-ready', register_text_booking_data_exporter, { once: true } ); |
| 394 |
} |
| 395 |
|
| 396 |
})( window ); |
| 397 |
|