Setting the file. One moment.
Wix Form Schema Utils · Wix Vibe Headless · wix/skills · Skills Docs
ContentsBack to the top of the page 22.10
Post Detail
export const isNumber
— line 127
This file
Number 22.57
Position 57 of 145
Type JavaScript
Size 9 KB
Lines 187 references/forms/app/lib/ wix-form-schema-utils.js
JavaScript · 187 lines · 9 KB
// ├─ componentType / validation ← the control, and its rules (`validationOf`)
15 // └─ dropdownOptions | textInputOptions | … ← named after `componentType` (`componentOf`)
16 // └─ label / placeholder / options / default
17 //
18 // ⚠️ Those two block names are the whole reason this file exists. They are looked up in the tables
19 // below rather than hand-written at the call site, because `inputOptions.stringOptions.dropdownOptions`
20 // spelled out in a component is one typo away from silently reading back no label at all.
21
22 /**
23 * `inputType` → the block holding that type's `componentType`, `validation`, and component block.
24 * Every input type the Form Schemas API defines
25 * (https://dev.wix.com/docs/api-reference/crm/forms/form-schemas/about-form-fields.md).
26 */
27 const INPUT_BLOCK = {
28 STRING: "stringOptions" ,
29 NUMBER: "numberOptions" ,
30 BOOLEAN: "booleanOptions" ,
31 ARRAY: "arrayOptions" ,
32 ADDRESS: "addressOptions" ,
33 WIX_FILE: "wixFileOptions" ,
34 PAYMENT: "paymentOptions" ,
35 SCHEDULING: "schedulingOptions" ,
36 };
37
38 /**
39 * `componentType` → the block holding that control's `label`, `placeholder`, `options`, `default`.
40 * Note several fields share one component — short AND long answer are both `TEXT_INPUT`, image
41 * choice and multi choice are both `CHECKBOX_GROUP` — which is why the render branches on
42 * `identifier` too.
43 */
44 const COMPONENT_BLOCK = {
45 TEXT_INPUT: "textInputOptions" ,
46 NUMBER_INPUT: "numberInputOptions" ,
47 RATING_INPUT: "ratingInputOptions" ,
48 PHONE_INPUT: "phoneInputOptions" ,
49 DATE_INPUT: "dateInputOptions" ,
50 DATE_PICKER: "datePickerOptions" ,
51 DATE_TIME: "dateTimeOptions" ,
52 TIME_INPUT: "timeInputOptions" ,
53 CHECKBOX: "checkboxOptions" ,
54 CHECKBOX_GROUP: "checkboxGroupOptions" ,
55 RADIO_GROUP: "radioGroupOptions" ,
56 DROPDOWN: "dropdownOptions" ,
57 TAGS: "tagsOptions" ,
58 MULTILINE_ADDRESS: "multilineAddressOptions" ,
59 FILE_UPLOAD: "fileUploadOptions" ,
60 SIGNATURE: "signatureOptions" ,
61 FIXED_PAYMENT: "fixedPaymentOptions" ,
62 PAYMENT_INPUT: "paymentInputOptions" ,
63 DONATION_INPUT: "donationInputOptions" ,
64 APPOINTMENT: "appointmentOptions" ,
65 SERVICES_DROPDOWN: "servicesDropdownOptions" ,
66 SERVICES_CHECKBOX_GROUP: "servicesCheckboxGroupOptions" ,
67 };
68
69 // An enum missing from a table means Wix added a type — say so ONCE (these run per field per
70 // render) instead of quietly handing back an empty block and a field labelled by its target.
71 const warned = new Set ();
72 function blockName ( table , key , what ) {
73 const name = table[key];
74 if ( ! name && key && ! warned. has (key)) {
75 warned. add (key);
76 console. warn (
77 `wix-forms: no ${ what } block mapped for "${ key }" — that field will render without its label ` +
78 `or options. Add it to ${ what === "inputType" ? "INPUT_BLOCK" : "COMPONENT_BLOCK"} in lib/wix-form-schema-utils.js.` ,
79 );
80 }
81 return name;
82 }
83
84 /** The field's immutable storage key — the input's `name`, and THE submission key. */
85 export const targetOf = ( field ) => field?.inputOptions?.target;
86
87 /** STRING | NUMBER | ARRAY | ADDRESS | WIX_FILE | BOOLEAN | PAYMENT | SCHEDULING. */
88 export const inputTypeOf = ( field ) => field?.inputOptions?.inputType;
89
90 /** ⚠️ From `inputOptions`, not from `validation`. */
91 export const isRequired = ( field ) => field?.inputOptions?.required ?? false ;
92
93 /** The input-type block: `componentType`, `validation`, and the component's own sub-block. */
94 export const optionsOf = ( field ) =>
95 field?.inputOptions?.[ blockName ( INPUT_BLOCK , inputTypeOf (field), "inputType" )] ?? {};
96
97 /** DROPDOWN | RADIO_GROUP | CHECKBOX_GROUP | TEXT_INPUT | NUMBER_INPUT | FILE_UPLOAD | … */
98 export const componentTypeOf = ( field ) => optionsOf (field).componentType;
99
100 /** The component sub-block: `label`, `placeholder`, `options`, `default`, … */
101 export const componentOf = ( field ) =>
102 optionsOf (field)[ blockName ( COMPONENT_BLOCK , componentTypeOf (field), "componentType" )] ?? {};
103
104 /** `format`, `minLength`, `maxLength`, `pattern`, `fields` (address), `fileLimit`, `phoneOptions`, … */
105 export const validationOf = ( field ) => optionsOf (field).validation ?? {};
106
107 /**
108 * The owner's label, falling back to the target so a control is never nameless.
109 *
110 * ⚠️ The fallback also covers a label that ISN'T A STRING: a BOOLEAN field (consent checkbox) labels
111 * itself with a Ricos rich-content object, which would otherwise reach the page — and any error copy
112 * built from it — as the text `[object Object]`. Render that field's label from
113 * `componentOf(field).label` yourself if you write a branch for it.
114 */
115 export const labelOf = ( field ) => {
116 const label = componentOf (field).label;
117 return typeof label === "string" && label ? label : targetOf (field);
118 };
119
120 /** Multi-choice: submits an ARRAY of the checked option values. */
121 export const isMulti = ( field ) => inputTypeOf (field) === "ARRAY" ;
122
123 /** File upload or signature: holds `File` objects until submit swaps them for their upload URLs. */
124 export const isFile = ( field ) => inputTypeOf (field) === "WIX_FILE" ;
125
126 /** NUMBER covers both the number input and a rating — the value goes on the wire as a number. */
127 export const isNumber = ( field ) => inputTypeOf (field) === "NUMBER" ;
128
129 /** An ADDRESS submits a nested OBJECT, so its subfields are their own controls. */
130 export const isAddress = ( field ) => inputTypeOf (field) === "ADDRESS" ;
131
132 /** Choice options as `{ value, label }` — a label is optional in the schema, the value never is. */
133 export const choicesOf = ( field ) =>
134 componentOf (field).options?. map (( o ) => ({ value: o.value, label: o.label ?? o.value })) ?? [];
135
136 /**
137 * An ADDRESS field's subfields as `{ sub, required }`, in schema order. `validation.fields` lists
138 * them; only `addressLine2` carries a visibility setting, so this hides just that one when the owner
139 * turned it off.
140 */
141 export const addressPartsOf = ( field ) =>
142 Object. entries ( validationOf (field).fields ?? {})
143 . filter (([ sub ]) => componentOf (field).fieldSettings?.[sub]?.show !== false )
144 . map (([ sub , cfg ]) => ({ sub, required: cfg?.required ?? false }));
145
146 /** The country a PHONE field's example should use — the FIELD's own before the site's. */
147 export const phoneCountryOf = ( field ) =>
148 componentOf (field).defaultCountryCode ?? validationOf (field).phoneOptions?.allowedCountryCodes?.[ 0 ];
149
150 /**
151 * Every visible INPUT field, in the order the owner laid out.
152 *
153 * ⚠️ Display order comes from `steps[].layout`, NOT from `formFields[]` array order. Take the order
154 * only — the geometry (`row`/`column`/`width`) is Wix's editor layout, which your own design
155 * replaces. A field the owner never placed sorts LAST; it still stores values.
156 *
157 * The submit button is `fieldType: "DISPLAY"`, so filtering to INPUT drops it automatically.
158 *
159 * @param {object} form A Form from `getForm` / `listForms`.
160 * @returns {object[]} Raw field objects — read them with the accessors above.
161 */
162 export function orderedInputs ( form ) {
163 const order = new Map (
164 (form?.steps ?? [])
165 // Sort WITHIN each step, then concatenate in step order: `row` restarts at 0 in every step,
166 // so one sort across the flattened list interleaves them — step 1's second field would land
167 // after step 2's first.
168 . flatMap (( s ) =>
169 (s.layout?.large?.items ?? s.layout?.medium?.items ?? s.layout?.small?.items ?? [])
170 . slice ()
171 . sort (( a , b ) => a.row - b.row || a.column - b.column),
172 )
173 . map (( item , i ) => [item.fieldId, i]),
174 );
175 return (form?.formFields ?? [])
176 . filter (( f ) => f.fieldType === "INPUT" && ! f.hidden)
177 . sort (( a , b ) => (order. get (a.id) ?? Infinity ) - (order. get (b.id) ?? Infinity ));
178 }
179
180 /**
181 * The owner's submit-button wording, or "" when they didn't set one. It lives on the SUBMIT_BUTTON —
182 * a DISPLAY field, so it never appears in `orderedInputs` — and nests under `pageNavigationOptions`
183 * because one control drives both multi-page navigation and the final submit.
184 */
185 export const submitTextOf = ( form ) =>
186 (form?.formFields ?? []). find (( f ) => f.identifier === "SUBMIT_BUTTON" )
187 ?.displayOptions?.pageNavigationOptions?.submitText ?? "" ;