PluginProbe
Booking Calendar / 11.5
Booking Calendar v11.5
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 10.11 All 204 releases
booking / includes / page-form-builder / field-packs / text / _src / field-text.js

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

397 lines 16.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
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