/** * WPBC BFB: Field Renderer for "text" (Schema-driven Reference, template-literal render) * ============================================================================================== * Purpose: * - Uses template literals (no wp.template) * - Inspector is rendered by Factory (PHP schema) * - Uses WPBC_BFB_Sanitize (from core) with method names as in bfb-core.js * * Contracts: * - Registry: WPBC_BFB_Field_Renderer_Registry.register( 'text', Class ) * - Class API: static get_defaults(), static render(el, data, ctx), static on_field_drop(data, el, ctx?) [optional] * * Notes: * - Keep defaults aligned with PHP schema->props->default. * - Use Overlay.ensure(...) so field controls (handle, settings, etc.) appear. * * File: ../includes/page-form-builder/field-packs/text/_out/field-text.js * * @since 11.0.0 * @modified 2025-09-06 14:08 * @version 1.0.1 * */ (function (w) { 'use strict'; var Core = w.WPBC_BFB_Core || {}; var registry = Core.WPBC_BFB_Field_Renderer_Registry; var Base = Core.WPBC_BFB_Field_Base; if ( ! registry || typeof registry.register !== 'function' || ! Base ) { _wpbc?.dev?.error?.( 'WPBC_BFB_Field_Text', 'Core registry/base missing' ); return; } class WPBC_BFB_Field_Text extends Base { /** * Return default props for "text" field. * Must stay in sync with PHP schema defaults. * * @returns {Object} */ static get_defaults() { return { type : 'text', label : 'Text', name : '', html_id : '', placeholder : '', required : false, minlength : null, maxlength : null, pattern : '', cssclass : '', help : '', default_value: '' }; } /** * Render the preview markup into the field element. * * @param {HTMLElement} el Field root element inside the canvas. * @param {Object} d Field props (already normalized by schema). * @param {Object} ctx Context: { builder, sanit, ... } */ static render(el, data, ctx) { if ( ! el ) { return; } // Normalize against defaults first. const d = this.normalize_data( data ); // ----- Core sanitize helpers (static) ----- const eh = (v) => Core.WPBC_BFB_Sanitize.escape_html( v ); const sid = (v) => Core.WPBC_BFB_Sanitize.sanitize_html_id( v ); const sname = (v) => Core.WPBC_BFB_Sanitize.sanitize_html_name( v ); const sclass = (v) => Core.WPBC_BFB_Sanitize.sanitize_css_classlist( v ); const truthy = (v) => Core.WPBC_BFB_Sanitize.is_truthy( v ); // Sanitize public id/name for the control itself. const html_id = d.html_id ? sid( String( d.html_id ) ) : ''; const name_val = sname( String( d.name || d.id || 'field' ) ); const cssNext = sclass( String( d.cssclass || '' ) ); // Keep wrapper classes in sync with dataset.cssclass ONLY (don’t touch core classes). // if ( 'cssclass' in d ) { // const prev = el.dataset.cssclass || ''; // if ( prev !== cssNext ) { // prev.split( /\s+/ ).filter( Boolean ).forEach( (c) => el.classList.remove( c ) ); // cssNext.split( /\s+/ ).filter( Boolean ).forEach( (c) => el.classList.add( c ) ); // el.dataset.cssclass = cssNext; // } // } // NEW: store only; do NOT modify wrapper classes. if ( 'cssclass' in d ) { if ( el.dataset.cssclass !== cssNext ) { el.dataset.cssclass = cssNext; } } // Keep wrapper's stored html_id (dataset) updated. if ( 'html_id' in d ) { if ( el.dataset.html_id !== html_id ) { el.dataset.html_id = html_id; } } // Flags / numeric constraints. const is_required = truthy( d.required ); const has_minlength = (d.minlength != null && d.minlength !== '' && Number.isFinite( +d.minlength )); const has_maxlength = (d.maxlength != null && d.maxlength !== '' && Number.isFinite( +d.maxlength )); const has_pattern = !!d.pattern; const minlength_num = has_minlength ? Number( d.minlength ) : null; const maxlength_num = has_maxlength ? Number( d.maxlength ) : null; // Attribute fragments (using escape_html for safe innerHTML attribute context). const id_attr = html_id ? ` id="${eh( html_id )}"` : ''; const name_attr = ` name="${eh( name_val )}"`; // const cls_attr = ` class="wpbc_bfb__preview-input ${eh( cssNext )}"`; const cls_attr = ` class="wpbc_bfb__preview-input ${cssNext ? ' ' + eh(cssNext) : ''}"`; const ph_attr = d.placeholder ? ` placeholder="${eh( d.placeholder )}"` : ''; const req_attr = is_required ? ' required aria-required="true"' : ''; const minlength_attr = has_minlength ? ` minlength="${minlength_num}"` : ''; const maxlength_attr = has_maxlength ? ` maxlength="${maxlength_num}"` : ''; const pattern_attr = has_pattern ? ` pattern="${eh( d.pattern )}"` : ''; const value_attr = (d.default_value != null && d.default_value !== '') ? ` value="${eh( String( d.default_value ) )}"` : ''; // Optional fragments. const label_html = (d.label != null && d.label !== '') ? `` : ''; const help_html = d.help ? `
${eh( d.help )}
` : ''; // Render preview HTML. el.innerHTML = ` ${label_html} ${help_html} `; // Overlay (handles/toolbars). Core.UI?.WPBC_BFB_Overlay?.ensure?.( ctx?.builder, el ); } /** * Optional hook executed after field is dropped from the palette. * Example recipe placeholder for future: try { if ( !data.name ) { data.name = core.WPBC_BFB_IdService?.next_name?.( 'text' ) || 'text'; } } catch ( e ) { } * * @param {Object} data Palette/field data. * @param {HTMLElement} el Newly created field element. * @param {Object} ctx Context { builder, sanit, context: 'drop' | 'load' | 'preview' } * @returns {void} */ static on_field_drop(data, el, ctx) { super.on_field_drop?.( data, el, ctx ); // Required for correctly auto-names from Labels ! // (your extra pack-specific logic if ever needed) } } try { registry.register( 'text', WPBC_BFB_Field_Text ); } catch ( e ) { _wpbc?.dev?.error?.( 'WPBC_BFB_Field_Text.register', e ); } // Optional global alias (debugging / dev tools). w.WPBC_BFB_Field_Text = w.WPBC_BFB_Field_Text || WPBC_BFB_Field_Text; // ----------------------------------------------------------------------------------------------------------------- // Export for "Booking Form" (Advanced Form shortcode) // ----------------------------------------------------------------------------------------------------------------- /** * Booking Form exporter callback (Advanced Form shortcode). * * This callback is registered per field type via: * WPBC_BFB_Exporter.register( 'text', callback ) * * Core call site (builder-exporter.js): * WPBC_BFB_Exporter.run_registered_exporter( field, io, cfg, once, ctx ) * -> callback( field, emit, { io, cfg, once, ctx, core } ); * * @callback WPBC_BFB_ExporterCallback * @param {Object} field * Normalized field data coming from the Builder structure. * - field.type {string} Field type, e.g. "text". * - field.name {string} Name as stored on the canvas (already validated). * - field.id / html_id {string} Optional HTML id / user-visible id. * - field.label {string} Visible label in the form (may be empty). * - field.placeholder {string} Placeholder text (may be empty). * - field.required {boolean|number|string} "truthy" if required. * - field.cssclass {string} Extra CSS classes entered in Inspector. * - field.default_value {string} Default text value. * - field.options {Array} Only for option-based fields (select, checkbox, etc.). * - ... (Any other pack-specific props are also present.) * * @param {function(string):void} emit * Emits one line/fragment into the export buffer. * - Each call corresponds to one `push()` in the core exporter. * - For multi-line output (e.g. label + shortcode), call `emit()` multiple times: * emit('Label'); * emit('
[text* name ...]'); * * @param {Object} [extras] * Extra context passed by the core exporter. * * @param {Object} [extras.io] * Low-level writer used internally by the core. * Normally you do NOT need it in packs — prefer `emit()`. * - extras.io.open(str) -> open a nested block (increments indentation). * - extras.io.close(str) -> close a block (decrements indentation). * - extras.io.push(str) -> push raw line (used by `emit()`). * - extras.io.blank() -> push an empty line. * * @param {Object} [extras.cfg] * Export configuration (same object passed to WPBC_BFB_Exporter.export_form()). * Useful flags for field packs: * - extras.cfg.addLabels {boolean} Default: true. * If false, packs should NOT emit Label lines. * - extras.cfg.newline {string} Newline separator (usually "\n"). * - extras.cfg.gapPercent{number} Layout gap (used only by section/column logic). * * @param {Object} [extras.once] * Shared "once-per-form" guards across all fields. * Counters are incremented by some field types (captcha, coupon, etc.). * Typical shape: * - extras.once.captcha {number} * - extras.once.country {number} * - extras.once.coupon {number} * - extras.once.cost_corrections {number} * - extras.once.submit {number} * * Text field usually does not touch this object, but other packs can use it * to skip duplicates (e.g. only the first [coupon] per form is exported). * * @param {Object} [extras.ctx] * Shared export context for the entire form. * Currently: * - extras.ctx.usedIds {Set} * Set of HTML/shortcode IDs already used in this export. * Helpers like Exp.id_option(field, ctx) use it to ensure uniqueness. * * Packs normally just pass `ctx` into helpers (id_option, etc.) without * mutating it directly. * * @param {Object} [extras.core] * Reference to WPBC_BFB_Core passed from builder-exporter.js. * Primarily used to access sanitizers: * - extras.core.WPBC_BFB_Sanitize.escape_html(...) * - extras.core.WPBC_BFB_Sanitize.escape_for_shortcode(...) * - extras.core.WPBC_BFB_Sanitize.sanitize_html_name(...) * - etc. */ function register_text_booking_form_exporter() { const Exp = w.WPBC_BFB_Exporter; if ( ! Exp || typeof Exp.register !== 'function' ) { return; } if ( typeof Exp.has_exporter === 'function' && Exp.has_exporter( 'text' ) ) { return; } const S = Core.WPBC_BFB_Sanitize; Exp.register( 'text', (field, emit, extras = {}) => { const cfg = extras.cfg || {}; const ctx = extras.ctx; // no local fallback needed. const addLabels = cfg.addLabels !== false; // Required marker logic (same as before). const is_req = Exp.is_required( field ); const req_mark = is_req ? '*' : ''; // Reuse helpers from WPBC_BFB_Exporter. const name = Exp.compute_name( 'text', field ); const id_opt = Exp.id_option( field, ctx ); const cls_opts = Exp.class_options( field ); const ph_attr = Exp.ph_attr( field.placeholder ); const size_max = Exp.size_max_token( field ); const def_value = Exp.default_text_suffix( field ); // Build body shortcode. const body = `[text${req_mark} ${name}${size_max}${id_opt}${cls_opts}${ph_attr}${def_value}]`; // Label behavior identical to legacy emit_label_then(). const label = (field.label ?? '').toString().trim(); if ( label && addLabels ) { emit( `${S.escape_html( label )}${req_mark}` ); emit( `
${body}` ); } else { emit( body ); } // Help text is appended centrally by WPBC_BFB_Exporter.render_field_node(). } ); } // Try immediate registration; if core isn’t ready, wait for the event. if ( w.WPBC_BFB_Exporter && typeof w.WPBC_BFB_Exporter.register === 'function' ) { register_text_booking_form_exporter(); } else { document.addEventListener( 'wpbc:bfb:exporter-ready', register_text_booking_form_exporter, { once: true } ); } // ----------------------------------------------------------------------------------------------------------------- // Export for "Booking Data" (Content of booking fields data) // ----------------------------------------------------------------------------------------------------------------- /** * Booking Data exporter callback ("Content of booking fields data"). Default output: Label: [field_name]
* * Registered per field type via: * WPBC_BFB_ContentExporter.register( 'text', callback ) * * Core call site (builder-exporter.js): * WPBC_BFB_ContentExporter.run_registered_exporter( field, emit, { cfg, core } ); * * @callback WPBC_BFB_ContentExporterCallback * @param {Object} field * Normalized field data (same shape as in the main exporter). * Important properties for content templates: * - field.type {string} Field type, e.g. "text". * - field.name {string} Field name used as placeholder token. * - field.label {string} Human-readable label (may be empty). * - field.options {Array} For option-based fields (select, checkbox, radio, etc.). * - Other pack-specific props if needed. * * @param {function(string):void} emit * Emits a raw HTML fragment into the "Content of booking fields data" template. * Core will wrap everything once into: *
* ... emitted fragments ... *
* * Typical usage pattern: * emit('Label: [field_name]
'); * * In most cases, packs call the shared helper: * WPBC_BFB_ContentExporter.emit_line_bold_field(emit, label, token, cfg); * * @param {Object} [extras] * Additional context passed from run_registered_exporter(). * * @param {Object} [extras.cfg] * Content exporter configuration: * - extras.cfg.addLabels {boolean} Default: true. * If false, helper may omit the bold label part. * - extras.cfg.sep {string} Label separator, default ": ". * Example: "Label: " vs "Label – ". * - extras.cfg.newline {string} Newline separator when joining lines (usually "\n"). * * @param {Object} [extras.core] * Reference to WPBC_BFB_Core (same as in main exporter). * Usually not needed here, because: * - Sanitization and consistent rendering are already done via * WPBC_BFB_ContentExporter.emit_line_bold_field( ... ). */ function register_text_booking_data_exporter() { const C = w.WPBC_BFB_ContentExporter; if ( ! C || typeof C.register !== 'function' ) { return; } if ( typeof C.has_exporter === 'function' && C.has_exporter( 'text' ) ) { return; } C.register( 'text', function (field, emit, extras) { extras = extras || {}; const cfg = extras.cfg || {}; const Exp = w.WPBC_BFB_Exporter; if ( ! Exp || typeof Exp.compute_name !== 'function' ) { return; } const name = Exp.compute_name( 'text', field ); if ( ! name ) { return; } const label = (typeof field.label === 'string' && field.label.trim()) ? field.label.trim() : name; // Shared formatter keeps all packs consistent:. C.emit_line_bold_field( emit, label, name, cfg ); } ); } if ( w.WPBC_BFB_ContentExporter && typeof w.WPBC_BFB_ContentExporter.register === 'function' ) { register_text_booking_data_exporter(); } else { document.addEventListener( 'wpbc:bfb:content-exporter-ready', register_text_booking_data_exporter, { once: true } ); } })( window );