@capillarytech/creatives-library 8.0.340-beta.0 → 8.0.340-beta.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/styles/containers/layout/_layoutPage.scss +1 -1
- package/v2Components/CapTagList/index.js +18 -6
- package/v2Components/CommonTestAndPreview/CustomValuesEditor.js +7 -8
- package/v2Components/CommonTestAndPreview/UnifiedPreview/PreviewHeader.js +1 -1
- package/v2Components/CommonTestAndPreview/UnifiedPreview/SmsPreviewContent.js +7 -5
- package/v2Components/CommonTestAndPreview/UnifiedPreview/_unifiedPreview.scss +7 -3
- package/v2Components/CommonTestAndPreview/_commonTestAndPreview.scss +27 -1
- package/v2Components/CommonTestAndPreview/index.js +2 -2
- package/v2Components/TestAndPreviewSlidebox/CustomValuesEditor.js +1 -1
- package/v2Containers/MobilePushNew/index.scss +3 -3
- package/v2Containers/Sms/Create/_smsCreate.scss +9 -2
- package/v2Containers/Sms/SCHEMA_FORMBUILDER_MAP.md +922 -0
- package/v2Containers/Sms/initialSchema.js +7 -1
|
@@ -0,0 +1,922 @@
|
|
|
1
|
+
# SMS `initialSchema.js` ↔ `FormBuilder/index.js` Mapping
|
|
2
|
+
|
|
3
|
+
This document is a one-stop graph of how every node in
|
|
4
|
+
`app/v2Containers/Sms/initialSchema.js` flows into
|
|
5
|
+
`app/components/FormBuilder/index.js`. Use it to predict — for any change
|
|
6
|
+
you make in the schema — exactly which branch of FormBuilder will be hit.
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## 1. How the schema reaches FormBuilder
|
|
11
|
+
|
|
12
|
+
```
|
|
13
|
+
app/services/getSchema.js
|
|
14
|
+
└─ case SMS: import('../v2Containers/Sms/initialSchema') ← exports `response`
|
|
15
|
+
{ metaEntities: [ { definition: {...} } ] }
|
|
16
|
+
│
|
|
17
|
+
▼
|
|
18
|
+
container code (Sms/Edit etc.) extracts
|
|
19
|
+
schema = response.metaEntities[0].definition
|
|
20
|
+
│
|
|
21
|
+
▼
|
|
22
|
+
<FormBuilder schema={schema} ... />
|
|
23
|
+
│
|
|
24
|
+
┌───────────── componentWillMount (101) ─────────────┐
|
|
25
|
+
│ initialiseForm(schema) │
|
|
26
|
+
│ ├─ initializeStandAloneSections(schema, …) │
|
|
27
|
+
│ │ walks parent/multicols/col-label, │
|
|
28
|
+
│ │ seeds formData[col.id] = '' or col.value│
|
|
29
|
+
│ │ and errorData[col.id] = false │
|
|
30
|
+
│ │ pushes col.id into this.formElements │
|
|
31
|
+
│ ├─ initializeContainers(schema, …) │
|
|
32
|
+
│ │ only if schema.containers present (no-op │
|
|
33
|
+
│ │ for SMS) │
|
|
34
|
+
│ └─ prunes formData keys NOT in formElements │
|
|
35
|
+
│ (1302–1314) — schema is the source of truth│
|
|
36
|
+
└────────────────────────────────────────────────────┘
|
|
37
|
+
│
|
|
38
|
+
▼
|
|
39
|
+
FormBuilder.render() → renderForm() (index.js:3164)
|
|
40
|
+
└─ schema.standalone → schema.standalone.sections.map(renderSection)
|
|
41
|
+
└─ schema.containers → renderContainer(...) ← unused for SMS
|
|
42
|
+
└─ schema.channel → IS read by validateForm (437) — gate that
|
|
43
|
+
triggers the SMS-specific validation block
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
The SMS schema only uses the `standalone` branch. `channel: "SMS"` at the
|
|
47
|
+
top of `definition` IS load-bearing: `validateForm` (line 437) checks
|
|
48
|
+
`this.props.schema.channel === 'SMS'` to enter the per-tab content/unicode
|
|
49
|
+
validation loop. Changing this string disables SMS validation entirely.
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
## 1b. Initialization phase (runs BEFORE first render)
|
|
54
|
+
|
|
55
|
+
Before any field is rendered, FormBuilder walks the schema twice — once to
|
|
56
|
+
build `formData` (default values) and once to build `errorData` (defaults to
|
|
57
|
+
`false` for every field). Both walks recurse the same shape that `render`
|
|
58
|
+
will later traverse.
|
|
59
|
+
|
|
60
|
+
Entry: `initialiseForm(schema)` (index.js:1273)
|
|
61
|
+
├─ `initializeStandAloneSections(schema, …)` (1201) — only called for SMS
|
|
62
|
+
└─ `initializeContainers(schema, …)` (1219) — no-op for SMS (no `containers`)
|
|
63
|
+
|
|
64
|
+
```
|
|
65
|
+
initializeStandAloneSections
|
|
66
|
+
└─ for each schema.standalone.sections[]
|
|
67
|
+
├─ "multicols" → initializeMultiColSection (1089)
|
|
68
|
+
├─ "col-label" → initializeColLabelSection (1162)
|
|
69
|
+
└─ "parent" → initializeParentSection (1046) ← recurses
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
`initializeMultiColSection` (the one that touches every SMS leaf field):
|
|
73
|
+
|
|
74
|
+
| Schema key on a `col` | What happens during init |
|
|
75
|
+
|---|---|
|
|
76
|
+
| `col.id` | Becomes a key in `formData` (and in `errorData` on the error pass) |
|
|
77
|
+
| `col.id` starts with `tagList-` | NOT pushed into `this.formElements` (1116) — survives the prune |
|
|
78
|
+
| `col.onlyDisplay: true` | Skips creating `formData[col.id]` / `errorData[col.id]` (1115) — so `tagList`, `sms-count`, `sms-preview`, both buttons don't get state slots |
|
|
79
|
+
| `col.value` present + container path | Used as the seeded default (`formData[index][col.id] = col.value`, 1124–1125, 1138–1139). For non-container/SMS standalone path, default is `''`. So `value: ""` on `sms-count` and `value: "Save"` on the buttons are seed values for container flows only — not used in SMS standalone. |
|
|
80
|
+
| `col.type === 'checkbox'` | Forces `formData[col.id] = false` (1150) regardless of onlyDisplay |
|
|
81
|
+
| any field NOT in `formElements` | Pruned from final `formData` in `initialiseForm` (1303–1314) — schema is the single source of truth for which keys live in state |
|
|
82
|
+
|
|
83
|
+
So the practical contract is:
|
|
84
|
+
- An editable field (`onlyDisplay: false`) in the schema → gets a
|
|
85
|
+
`formData[id]` slot with default `''` (or `false` for checkbox) and a
|
|
86
|
+
`errorData[id]` slot with default `false`.
|
|
87
|
+
- A display-only field (`onlyDisplay: true`) → gets NO state slot;
|
|
88
|
+
reads from formData fall back to `undefined`.
|
|
89
|
+
|
|
90
|
+
This also explains why renaming `id: "template-name"` would not just break
|
|
91
|
+
the validator at 472 — it would also strip the field from `formElements`
|
|
92
|
+
and prune any existing edit-mode value from `formData`.
|
|
93
|
+
|
|
94
|
+
---
|
|
95
|
+
|
|
96
|
+
## 2. The dispatcher: `renderSection` (index.js:2493)
|
|
97
|
+
|
|
98
|
+
Every `section.type` lands here:
|
|
99
|
+
|
|
100
|
+
```
|
|
101
|
+
section.type
|
|
102
|
+
├─ "col-label" → renderColLabelSection()
|
|
103
|
+
├─ "multicols" → renderMultiColSection() ← ALL SMS leaf fields go here
|
|
104
|
+
├─ "parent" → renderParentSection() ← recurses on childSections
|
|
105
|
+
└─ default → null (so an unknown `type` silently disappears)
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### Recursion graph for the SMS schema
|
|
109
|
+
|
|
110
|
+
```
|
|
111
|
+
standalone.sections[0] type: "parent" rowStyle:{flexDirection:'column'}
|
|
112
|
+
└─ renderParentSection (index.js:2473)
|
|
113
|
+
├─ childSections[0] type: "multicols" width: 16
|
|
114
|
+
│ └─ renderMultiColSection inputFields[0].cols = [] ← empty placeholder
|
|
115
|
+
│
|
|
116
|
+
├─ childSections[1] type: "parent"
|
|
117
|
+
│ └─ renderParentSection
|
|
118
|
+
│ ├─ childSections[0] type: "parent" id: "kiwi" width: 14
|
|
119
|
+
│ │ └─ renderParentSection
|
|
120
|
+
│ │ ├─ multicols → cols[template-name] (input)
|
|
121
|
+
│ │ ├─ multicols → cols[tagList] (tag-list)
|
|
122
|
+
│ │ └─ multicols → cols[sms-editor, sms-count, unicode-validity]
|
|
123
|
+
│ │
|
|
124
|
+
│ └─ childSections[1] type: "multicols" width: 8
|
|
125
|
+
│ └─ renderMultiColSection
|
|
126
|
+
│ └─ cols[sms-preview] (sms-preview)
|
|
127
|
+
│
|
|
128
|
+
└─ childSections[2] type: "multicols"
|
|
129
|
+
└─ renderMultiColSection
|
|
130
|
+
└─ cols[save-button, test-and-preview-button] (button)
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
**Key takeaway:** changing `type` on any `parent`/`multicols` node will
|
|
134
|
+
re-route the rendering through a different branch of `renderSection`; the
|
|
135
|
+
default branch returns `null`, so a typo (e.g. `"multicol"` instead of
|
|
136
|
+
`"multicols"`) silently removes the whole subtree.
|
|
137
|
+
|
|
138
|
+
---
|
|
139
|
+
|
|
140
|
+
## 3. Layout-key wiring (parent / multicols / row-col)
|
|
141
|
+
|
|
142
|
+
These keys do not reach a leaf field renderer — they are consumed by the
|
|
143
|
+
parent/multicols renderers:
|
|
144
|
+
|
|
145
|
+
| Schema key (on parent / multicols / row) | Consumer in FormBuilder | Effect |
|
|
146
|
+
|---|---|---|
|
|
147
|
+
| `section.type: "parent"` | renderParentSection (2473) | Wraps `childSections` in a `<CapRow>` |
|
|
148
|
+
| `section.type: "multicols"` | renderMultiColSection (2530) | Iterates `inputFields[].cols` and `actionFields[]` |
|
|
149
|
+
| `section.rowStyle` | renderParentSection (2480) | Becomes `<CapRow style={rowStyle}>` (root row uses `flexDirection:'column'`) |
|
|
150
|
+
| `section.rowStyle` (on inputField row) | NOT read directly | `sms-count-container.rowStyle` is **dead config** in current code |
|
|
151
|
+
| `section.colStyle` (on childSection) | renderParentSection (2477) | Becomes `<CapColumn style={colStyle}>` wrapper |
|
|
152
|
+
| `section.offset` (on childSection) | renderParentSection (2477) | Becomes `<CapColumn offset={offset}>` |
|
|
153
|
+
| `section.width` (on childSection) | renderParentSection (2477) | Becomes `<CapColumn span={width}>` — controls left-pane (14) vs right-pane (8) split |
|
|
154
|
+
| `inputFields[].rowClassName` | NOT read (current code) | `'tag-container'`, `'sms-message-content'`, `'sms-count-container'`, `'creatives-sms-footer'` are **dead className config** |
|
|
155
|
+
| `inputFields[].cols[]` | renderMultiColSection (2537) | Each `val` here is a leaf field → switch(type) |
|
|
156
|
+
| `actionFields[]` | renderMultiColSection (2446) | Switch only handles `case "button"` — anything else is dropped |
|
|
157
|
+
|
|
158
|
+
---
|
|
159
|
+
|
|
160
|
+
## 4. Per-field mapping (every leaf in the SMS schema)
|
|
161
|
+
|
|
162
|
+
For each leaf `val` in `cols[]`, FormBuilder runs the switch at index.js:2576.
|
|
163
|
+
The case is selected by `val.type`. Below: schema field → FormBuilder branch
|
|
164
|
+
→ rendered component → which schema keys are consumed.
|
|
165
|
+
|
|
166
|
+
### 4.1 `template-name` (Creative name input)
|
|
167
|
+
|
|
168
|
+
```
|
|
169
|
+
val.type = "input" → case "input" index.js:2577
|
|
170
|
+
AND val.styling === 'semantic'
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
| Schema key | Used as |
|
|
174
|
+
|---|---|
|
|
175
|
+
| `id: "template-name"` | `<CapInput id>`, formData[id], errorData[id] lookup |
|
|
176
|
+
| `placeholder` | `<CapInput placeholder>` |
|
|
177
|
+
| `label` | NOT rendered (no label wrapper for input case) |
|
|
178
|
+
| `metaType: "text"` | NOT used by input branch |
|
|
179
|
+
| `datatype` | NOT used |
|
|
180
|
+
| `required: true` | Drives `validateForm` requirement check |
|
|
181
|
+
| `fluid: true` | Commented out (`// fluid={val.fluid}`) — dead |
|
|
182
|
+
| `styling: "semantic"` | Required for the `if (styling==='semantic')` gate |
|
|
183
|
+
| `order` | NOT used (cols are rendered in array order) |
|
|
184
|
+
| `width: 18` | `<CapColumn span={18}>` |
|
|
185
|
+
| `customComponent` | NOT used |
|
|
186
|
+
| `standalone: true` | Bypasses version-tab guards (`!val.standalone`) |
|
|
187
|
+
| `onlyDisplay: false` | Field is editable; participates in error checks |
|
|
188
|
+
| `errorMessage` | `<CapInput errorMessage>` shown when ifError is true |
|
|
189
|
+
| `supportedEvents` | Recognised by `callChildEvent` (onChange) |
|
|
190
|
+
|
|
191
|
+
Wraps in `<CapColumn span={width}>`. `style` (none here) would also flow.
|
|
192
|
+
|
|
193
|
+
### 4.2 `tagList` (Add label tag chooser)
|
|
194
|
+
|
|
195
|
+
```
|
|
196
|
+
val.type = "tag-list" → case "tag-list" index.js:2719
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
| Schema key | Used as |
|
|
200
|
+
|---|---|
|
|
201
|
+
| `id: "tagList"` | `<TagList id>` |
|
|
202
|
+
| `label: "Add label"` | `<TagList label>` |
|
|
203
|
+
| `className: 'add-label'` | `<TagList className>` |
|
|
204
|
+
| `style` | `<CapColumn style>` (defaults to `{marginBottom:'16px'}`) |
|
|
205
|
+
| `width: 18` | `<CapColumn span={width}>` |
|
|
206
|
+
| `metaType, datatype, fluid, styling, order, customComponent` | NOT read by tag-list branch |
|
|
207
|
+
| `required: true` | Validation hook (validateForm) — but `onlyDisplay:true` makes it bypass |
|
|
208
|
+
| `onlyDisplay: true` | Skips formData[]/errorData[] guard (2545–2557) |
|
|
209
|
+
| `supportedEvents: ["onTagSelect"]` | Wired via `onTagSelect={(d)=>callChildEvent(d, val,'onTagSelect')}` |
|
|
210
|
+
|
|
211
|
+
`rowClassName: 'tag-container'` on the parent row is **not read** — class
|
|
212
|
+
must be added differently (e.g. via wrapper) to take effect.
|
|
213
|
+
|
|
214
|
+
### 4.3 `sms-editor` (Message textarea)
|
|
215
|
+
|
|
216
|
+
```
|
|
217
|
+
val.type = "textarea" → case "textarea" index.js:2613
|
|
218
|
+
→ renderTextAreaContent(...)
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
| Schema key | Used as |
|
|
222
|
+
|---|---|
|
|
223
|
+
| `id: "sms-editor"` | formData[id] / errorData[id]; key for SMS counter & preview |
|
|
224
|
+
| `label: 'Message'` | Passed into `renderTextAreaContent` (label rendered inside) |
|
|
225
|
+
| `metaType, datatype, styling` | Branching inside `renderTextAreaContent` |
|
|
226
|
+
| `style: {marginBottom:'16px'}` | `<CapColumn style>` / inner element style |
|
|
227
|
+
| `required: true` | Validation |
|
|
228
|
+
| `errorMessage` | Error display in textarea wrapper |
|
|
229
|
+
| `placeholder` | Textarea placeholder |
|
|
230
|
+
| `fluid, onlyDisplay` | Behaviour flags |
|
|
231
|
+
| `width: 18` | `<CapColumn span={width}>` |
|
|
232
|
+
| `autosize: true` | Enables auto-resizing textarea |
|
|
233
|
+
| `autosizeParams` | `{maxRows:18, minRows:3}` passed to textarea |
|
|
234
|
+
| `order` | NOT used |
|
|
235
|
+
| `customComponent: true` | NOT used in this branch |
|
|
236
|
+
| `supportedEvents: ["onChange"]` | onChange wired to `updateFormData(value, val)` |
|
|
237
|
+
|
|
238
|
+
⚠ **Critical convention**: the literal id `"sms-editor"` is hard-coded as a
|
|
239
|
+
lookup key in:
|
|
240
|
+
- `case "contentPreview"` (2276) reads `formData['sms-editor']`
|
|
241
|
+
- `case "sms-preview"` (2332,2759) reads `formData[\`sms-editor${tab>1?tab:''}\`]`
|
|
242
|
+
- `validateForm` (447) reads `formData[count]['sms-editor' + suffix]`
|
|
243
|
+
|
|
244
|
+
Renaming `id: "sms-editor"` will break the preview, char counter, and
|
|
245
|
+
validation. The version-aware suffix logic (`sms-editor2`, `sms-editor3` …)
|
|
246
|
+
is also tied to this exact prefix.
|
|
247
|
+
|
|
248
|
+
⚠ **Error-message translation chain** (renderTextAreaContent, 2155–2168):
|
|
249
|
+
the textarea reads `errorData[id]` and translates these tokens
|
|
250
|
+
(written by `validateForm`) into i18n strings:
|
|
251
|
+
|
|
252
|
+
| `errorData[id]` value | Rendered errorMessage |
|
|
253
|
+
|---|---|
|
|
254
|
+
| `'missingTagsError'` | `messages.missingTagsValidationError` (with the missing tag names) |
|
|
255
|
+
| `'unsupportedTagsError'` | `messages.unsupportedTagsValidationError` |
|
|
256
|
+
| `true` | `messages.genericTagsValidationError` |
|
|
257
|
+
| `false` / falsy | no error shown |
|
|
258
|
+
|
|
259
|
+
So `errorMessage: "Template content has unsupported/missing tags!"` from
|
|
260
|
+
the schema is **never displayed** — i18n messages override it. This
|
|
261
|
+
schema field is effectively dead for `sms-editor`. (The check uses
|
|
262
|
+
`val.errorMessage` for input/checkbox via different branches but the
|
|
263
|
+
textarea branch overrides it.)
|
|
264
|
+
|
|
265
|
+
### 4.4 `sms-count` (character counter div)
|
|
266
|
+
|
|
267
|
+
```
|
|
268
|
+
val.type = "div" val.primitive = true → renderPrimitiveElement (2507)
|
|
269
|
+
→ case "div" (2514)
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
| Schema key | Used as |
|
|
273
|
+
|---|---|
|
|
274
|
+
| `id: "sms-count"` | `<div id>`; formData[id] read for value if non-empty |
|
|
275
|
+
| `type: 'div'` | Selects the `div` branch in `renderPrimitiveElement` |
|
|
276
|
+
| `props: {type:'label3'}` | NOT read by current code |
|
|
277
|
+
| `onlyDisplay: true` | Skips error-data guard |
|
|
278
|
+
| `metaType: "label"` | NOT read |
|
|
279
|
+
| `value: ""` | Default text rendered inside the div |
|
|
280
|
+
| `primitive: true` | Required so renderMultiColSection routes to `renderPrimitiveElement` (2561) |
|
|
281
|
+
| `width: 18` | `<CapColumn span={width}>` |
|
|
282
|
+
| `style: {float:'right'}` | Inline div style |
|
|
283
|
+
|
|
284
|
+
### 4.5 `unicode-validity` (Allow Unicode checkbox)
|
|
285
|
+
|
|
286
|
+
```
|
|
287
|
+
val.type = "checkbox" → case "checkbox" index.js:2618
|
|
288
|
+
AND val.styling === 'semantic'
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
| Schema key | Used as |
|
|
292
|
+
|---|---|
|
|
293
|
+
| `id: "unicode-validity"` | formData[id]/errorData[id]; also read by `case "sms-preview"` to set `unicodeEnabled` |
|
|
294
|
+
| `label` | `<CapCheckbox>{label}</CapCheckbox>` |
|
|
295
|
+
| `supportedEvents` | onChange wired |
|
|
296
|
+
| `labelStyle` | NOT read by checkbox branch (dead key) |
|
|
297
|
+
| `metaType, datatype, fluid, styling, order, width, customComponent` | width → CapColumn span; rest unused |
|
|
298
|
+
| `required: true` | Validation |
|
|
299
|
+
| `onlyDisplay: false` | Requires entries in formData/errorData |
|
|
300
|
+
| `errorMessage` | `<CapCheckbox errorMessage>` |
|
|
301
|
+
|
|
302
|
+
⚠ **Critical convention**: id `"unicode-validity"` is read as a
|
|
303
|
+
hard-coded key by `validateForm` (448) and `sms-preview` (2333, 2760).
|
|
304
|
+
Renaming breaks unicode toggling.
|
|
305
|
+
|
|
306
|
+
### 4.6 `sms-preview` (right-pane phone preview)
|
|
307
|
+
|
|
308
|
+
```
|
|
309
|
+
val.type = "sms-preview" → case "sms-preview" index.js:2754
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
| Schema key | Used as |
|
|
313
|
+
|---|---|
|
|
314
|
+
| `id: "sms-preview"` | Component key; formData[id] fallback when not version-tabbed |
|
|
315
|
+
| `type: "sms-preview"` | Selects branch |
|
|
316
|
+
| `customComponent` | NOT used |
|
|
317
|
+
| `customStyling: {padding:"0"}` | `<TemplatePreview style={customStyling}>` |
|
|
318
|
+
| `metaType: "preview"` | NOT used |
|
|
319
|
+
| `required, fluid, styling, order` | NOT used |
|
|
320
|
+
| `onlyDisplay: true` | Skips formData guard |
|
|
321
|
+
| `offset: 7` | NOT used in this branch (CapColumn offset is hardcoded `1`, span hardcoded `23`) — dead key |
|
|
322
|
+
| `channel: "SMS"` | `<TemplatePreview channel={val.channel}>` |
|
|
323
|
+
| `charCounterEnabled: true` | `<TemplatePreview charCounterEnabled>` |
|
|
324
|
+
|
|
325
|
+
The preview pulls its `content` from `formData['sms-editor' + suffix]` —
|
|
326
|
+
not from any field on `sms-preview` itself. So changing `id: "sms-preview"`
|
|
327
|
+
is mostly cosmetic, but changing `id: "sms-editor"` breaks the preview.
|
|
328
|
+
|
|
329
|
+
### 4.7 `save-button` and `test-and-preview-button`
|
|
330
|
+
|
|
331
|
+
These live under a `multicols` whose `cols[]` is on `inputFields` (not
|
|
332
|
+
`actionFields`). FormBuilder's switch handles `case "button"` in BOTH
|
|
333
|
+
`inputFields` (2738) and `actionFields` (2452) paths.
|
|
334
|
+
|
|
335
|
+
```
|
|
336
|
+
val.type = "button" → case "button" index.js:2738
|
|
337
|
+
AND val.styling==='semantic'
|
|
338
|
+
AND val.metaType==='submit-button'
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
| Schema key | Used as |
|
|
342
|
+
|---|---|
|
|
343
|
+
| `id` | Special-cased: `'save-button'` triggers `this.saveForm()`; otherwise `callChildEvent(data, val, val.submitAction)` |
|
|
344
|
+
| `metaType: "submit-button"` | Required for the gate to render the button |
|
|
345
|
+
| `colStyle` | `<CapColumn style={colStyle}>` |
|
|
346
|
+
| `type: "button"` | Selects the branch |
|
|
347
|
+
| `buttonType` | `<CapButton type={buttonType}>` ('primary' / 'secondary') |
|
|
348
|
+
| `value` | Button label text (children of CapButton) |
|
|
349
|
+
| `customComponent` | NOT used |
|
|
350
|
+
| `styling: "semantic"` | Required for the gate |
|
|
351
|
+
| `order` | NOT used |
|
|
352
|
+
| `fluid` | NOT used (CapButton has no `fluid` prop here) |
|
|
353
|
+
| `standalone: true` | Bypasses version-tab guards |
|
|
354
|
+
| `onlyDisplay: true` | Skips formData/errorData guard |
|
|
355
|
+
| `submitAction` | For non-save: passed as event name to `callChildEvent` ('onClick' on the test button) |
|
|
356
|
+
| `supportedEvents` | Recognised by `callChildEvent` |
|
|
357
|
+
|
|
358
|
+
`width`/`offset`/`icon`/`disabled` would also wire to CapButton if added
|
|
359
|
+
(see 2741–2746).
|
|
360
|
+
|
|
361
|
+
⚠ **save-button special case**: `id === 'save-button'` (2457, 2746) bypasses
|
|
362
|
+
`callChildEvent` entirely and calls `this.saveForm()` directly, so
|
|
363
|
+
`submitAction: "saveFormData"` and `supportedEvents: ["saveFormData"]` on the
|
|
364
|
+
schema are **never invoked** — they're dead config kept for symmetry with
|
|
365
|
+
other buttons. Only `id: "save-button"` matters for save behaviour.
|
|
366
|
+
|
|
367
|
+
For `test-and-preview-button`: `submitAction: "onClick"` IS used — it's
|
|
368
|
+
passed as the event name into `callChildEvent`, which then dispatches via
|
|
369
|
+
`val.injectedEvents.onClick(...)` (1977). The container component at
|
|
370
|
+
`v2Containers/Sms/...` must register an `onClick` handler in the
|
|
371
|
+
`injectedEvents` map for this id, or the click is a no-op.
|
|
372
|
+
|
|
373
|
+
---
|
|
374
|
+
|
|
375
|
+
## 5. Field-type → render-branch quick reference
|
|
376
|
+
|
|
377
|
+
This is the table to consult when you change `val.type` in the schema. All
|
|
378
|
+
SMS-relevant cases live in the switch at index.js:2576 (and a near-duplicate
|
|
379
|
+
at 2233 used by `renderTextAreaContent` recursion).
|
|
380
|
+
|
|
381
|
+
| `val.type` | Renders | File:line |
|
|
382
|
+
|---|---|---|
|
|
383
|
+
| `input` | `<CapInput>` inside `<CapColumn>` | 2577 |
|
|
384
|
+
| `SmsEditor` | `<SmsEditor>` (legacy) | 2602 |
|
|
385
|
+
| `textarea` | via `renderTextAreaContent` | 2613 |
|
|
386
|
+
| `contentPreview` | char-list `<CapColumn>` | 2275 |
|
|
387
|
+
| `checkbox` | `<CapCheckbox>` | 2618 |
|
|
388
|
+
| `radio` | `<CapRadio>` | 2638 |
|
|
389
|
+
| `radioGroup` | `<CapRadioGroup options={val.options}>` | 2657 |
|
|
390
|
+
| `image` | `<img>` with placeholder/error | 2683 |
|
|
391
|
+
| `upload` | hidden file input + `<CapButton>` | 2700 |
|
|
392
|
+
| `table` | `<CapTable>` (uses `val.columns`, `val.pagination`) | 2712 |
|
|
393
|
+
| `tag-list` | `<TagList>` | 2719 |
|
|
394
|
+
| `tabs` | `<TagList>` (legacy) | 2319 |
|
|
395
|
+
| `button` | `<CapButton>` | 2738 |
|
|
396
|
+
| `sms-preview` | `<TemplatePreview channel="SMS">` | 2754 |
|
|
397
|
+
| `line-preview` | `<TemplatePreview channel="LINE">` | 2776 |
|
|
398
|
+
| `mobile-push-preview`| `<TemplatePreview device=...>` | 2798 |
|
|
399
|
+
| `popover` | `<CapPopover>` with recursive sections | 2824 |
|
|
400
|
+
| `icon` | icon component | 2862 |
|
|
401
|
+
| `select` | `<CapSelect options={val.options}>` | 2883 |
|
|
402
|
+
| `ckeditor` | `<CKEditor>` | 2904 |
|
|
403
|
+
| `edmeditor` | `<EDMEditor>` | 2400 |
|
|
404
|
+
| `span` / `div` | via `renderPrimitiveElement` when `val.primitive=true` | 2507 |
|
|
405
|
+
|
|
406
|
+
Anything else falls through `default: break;` and renders nothing.
|
|
407
|
+
|
|
408
|
+
---
|
|
409
|
+
|
|
410
|
+
## 6. Hard-coded id contracts (DO NOT rename without updating code)
|
|
411
|
+
|
|
412
|
+
These ids from `initialSchema.js` are read as string literals in
|
|
413
|
+
FormBuilder/index.js. They are not generic schema lookups — renaming the
|
|
414
|
+
`id` will cause runtime breakage that the schema cannot express.
|
|
415
|
+
|
|
416
|
+
| Schema id | Hard-coded reads in FormBuilder |
|
|
417
|
+
|---|---|
|
|
418
|
+
| `sms-editor` | 447 (`validateForm`), 2276 (`contentPreview`), 2332/2759 (`sms-preview`), tab-suffix logic for versions |
|
|
419
|
+
| `unicode-validity` | 448 (`validateForm`), 2333/2760 (`sms-preview` unicodeEnabled) |
|
|
420
|
+
| `template-name` | 472–477 (`validateForm` non-embedded creative-name required check) |
|
|
421
|
+
| `save-button` | 2457, 2746 (special-cased to `this.saveForm()`) |
|
|
422
|
+
| `tab-header` | 1934 (`callChildEvent` onChange routes to `changeVersionName`) — not in SMS schema today, but reserved |
|
|
423
|
+
| `tagList` / `tags` | NOT hard-coded — `tags` come from `this.props.tags` |
|
|
424
|
+
|
|
425
|
+
Schema-level hard-coded values:
|
|
426
|
+
|
|
427
|
+
| Schema location | Code reading it |
|
|
428
|
+
|---|---|
|
|
429
|
+
| `definition.channel === "SMS"` | `validateForm` (437) — gate for SMS validation block |
|
|
430
|
+
| `definition.channel === "LINE"` / `"EMAIL"` | other channel branches in `validateForm` (481, 1882, 1893, 1913) |
|
|
431
|
+
|
|
432
|
+
Other ids (`sms-count`, `sms-preview`, `test-and-preview-button`) are
|
|
433
|
+
referenced only via `val.id` and are safe to rename — but the values of
|
|
434
|
+
`submitAction: "saveFormData"` / `"onClick"` must remain in sync with the
|
|
435
|
+
parent container's `injectedEvents` keys (see `callChildEvent`,
|
|
436
|
+
`val.injectedEvents`).
|
|
437
|
+
|
|
438
|
+
---
|
|
439
|
+
|
|
440
|
+
## 7. Keys that look meaningful but are NOT consumed (in the SMS path)
|
|
441
|
+
|
|
442
|
+
Searching FormBuilder confirms these schema keys are **dead config** for
|
|
443
|
+
SMS rendering today. Setting/removing them changes nothing visible. (Some
|
|
444
|
+
are read by other channels' code paths — but not by anything reachable
|
|
445
|
+
from the SMS schema.)
|
|
446
|
+
|
|
447
|
+
- Top-level: `_id`, `type:"LAYOUT"`, `isActive`, `createdBy`, `updatedBy`,
|
|
448
|
+
`createdAt`, `updatedAt` (FormBuilder consumes `metaEntities[0].definition`
|
|
449
|
+
via the container; the wrapper fields are used only by backend/analytics).
|
|
450
|
+
NOTE: `definition.channel` is NOT dead — see §6.
|
|
451
|
+
- On rows: `rowClassName` ('tag-container', 'sms-message-content',
|
|
452
|
+
'sms-count-container', 'creatives-sms-footer') — read only by
|
|
453
|
+
`renderContainer` (3008), unreachable from SMS standalone.
|
|
454
|
+
- On rows: `rowStyle` on `inputFields[]` rows — also only used at 3008.
|
|
455
|
+
The section-level `rowStyle` on the outer `parent` IS read (2480).
|
|
456
|
+
- On parents: `id: "kiwi"` on the inner-left `parent` section — never
|
|
457
|
+
read; pure label/comment.
|
|
458
|
+
- On fields: `metaType` (except `'submit-button'` gate on buttons),
|
|
459
|
+
`datatype`, `fluid` (commented out), `order`, `customComponent`,
|
|
460
|
+
`props`, `labelStyle` (on checkbox), `offset` (on `sms-preview`).
|
|
461
|
+
|
|
462
|
+
If you intend any of these to take effect, they need new code in
|
|
463
|
+
FormBuilder; today they're documentation-only.
|
|
464
|
+
|
|
465
|
+
---
|
|
466
|
+
|
|
467
|
+
## 8. Change-impact cheat-sheet
|
|
468
|
+
|
|
469
|
+
| If you change in `initialSchema.js`… | …it will affect in `FormBuilder/index.js` |
|
|
470
|
+
|---|---|
|
|
471
|
+
| any `section.type` | `renderSection` dispatch (2493) — wrong value silently renders nothing |
|
|
472
|
+
| `parent.childSections[].width` / `offset` / `colStyle` | `renderParentSection` CapColumn props (2477) — column layout |
|
|
473
|
+
| `parent.rowStyle` | `renderParentSection` CapRow style (2480) — row direction/spacing |
|
|
474
|
+
| any `cols[].type` | `renderMultiColSection` switch (2576) — picks the renderer |
|
|
475
|
+
| `cols[].id` (when id is `sms-editor`/`unicode-validity`/`save-button`) | Hard-coded reads (see §6) — breaks preview / counter / save |
|
|
476
|
+
| `cols[].id` (any other) | formData[id] / errorData[id] keys; safe to rename if you also update upstream consumers |
|
|
477
|
+
| `cols[].width` | `<CapColumn span>` for the field |
|
|
478
|
+
| `cols[].style` | `<CapColumn style>` (input/textarea/image/upload) |
|
|
479
|
+
| `cols[].colStyle` | `<CapColumn style>` (button/select/popover branches) |
|
|
480
|
+
| `cols[].offset` | `<CapColumn offset>` for tag-list/checkbox/radio/image/upload/button (NOT read by sms-preview) |
|
|
481
|
+
| `cols[].placeholder` | `<CapInput>` / textarea placeholder |
|
|
482
|
+
| `cols[].errorMessage` | Inline error string when ifError |
|
|
483
|
+
| `cols[].label` | tag-list / checkbox / radio / upload label |
|
|
484
|
+
| `cols[].required` | `validateForm` requirement check |
|
|
485
|
+
| `cols[].onlyDisplay` | Bypass guards in `renderMultiColSection` (2545–2557) |
|
|
486
|
+
| `cols[].standalone` | Bypass version-tab guards; sets `isVersionEnable=false` |
|
|
487
|
+
| `cols[].styling === 'semantic'` | Required gate for input/checkbox/radio/radioGroup/button branches to render |
|
|
488
|
+
| `cols[].supportedEvents` | Recognised by `callChildEvent` (parent container hooks) |
|
|
489
|
+
| `cols[].submitAction` | Event name used by `callChildEvent` for buttons |
|
|
490
|
+
| `cols[].buttonType` | `<CapButton type>` ('primary'/'secondary') |
|
|
491
|
+
| `cols[].channel` | `<TemplatePreview channel>` for sms/line/mobile-push previews |
|
|
492
|
+
| `cols[].charCounterEnabled` | `<TemplatePreview charCounterEnabled>` |
|
|
493
|
+
| `cols[].autosize` / `autosizeParams` | textarea auto-resize behaviour |
|
|
494
|
+
| `cols[].primitive: true` | Routes to `renderPrimitiveElement` (2561) |
|
|
495
|
+
| `cols[].value` (on primitive `div`/`span`) | Default text content (overridden by formData[id]) |
|
|
496
|
+
|
|
497
|
+
---
|
|
498
|
+
|
|
499
|
+
## 9. Schema-line → FormBuilder-line map
|
|
500
|
+
|
|
501
|
+
For every line in `app/v2Containers/Sms/initialSchema.js`, this is the
|
|
502
|
+
exact set of lines in `app/components/FormBuilder/index.js` it controls.
|
|
503
|
+
Format: `schema L<n>: <key>=<value>` → `FormBuilder L<n>: <what happens>`.
|
|
504
|
+
|
|
505
|
+
(Layout-only branches that are not consumed by anything are marked DEAD.)
|
|
506
|
+
|
|
507
|
+
---
|
|
508
|
+
|
|
509
|
+
### Schema L1 — `export const response = {`
|
|
510
|
+
- **Consumed by**: `app/services/getSchema.js` (line 6) — `import('../v2Containers/Sms/initialSchema')` then `.response`. The container reads `response.metaEntities[0].definition` and passes it to `<FormBuilder schema=...>`.
|
|
511
|
+
- FormBuilder lines: 3165 (`const schema = this.props.schema`), 3210 (`this.props.schema ? this.renderForm() : ''`).
|
|
512
|
+
|
|
513
|
+
### Schema L2–L4 — `metaEntities[0]: { _id, type:"LAYOUT" }`
|
|
514
|
+
- **DEAD** in FormBuilder. Wrapper fields `_id`, `type:"LAYOUT"`, `isActive`, `createdBy`, `updatedBy`, `createdAt`, `updatedAt` (L4, L295–299) are not read by any FormBuilder line. The container peels `definition` off and discards them.
|
|
515
|
+
|
|
516
|
+
### Schema L6 — `definition: {`
|
|
517
|
+
- The object passed in as `props.schema`. Read at FormBuilder L1280–1283 (init), L3165 (render), L437/481/1882/1893/1913 (channel gate).
|
|
518
|
+
|
|
519
|
+
### Schema L7 — `standalone: {`
|
|
520
|
+
- Read at FormBuilder L1203 (`if (schema.standalone)` in init), L3169 (render).
|
|
521
|
+
|
|
522
|
+
### Schema L8 — `sections: [`
|
|
523
|
+
- Iterated at FormBuilder L1204 (`_.forEach(schema.standalone.sections, ...)`) and L3170–3172 (`schema.standalone.sections.map(renderSection)`).
|
|
524
|
+
|
|
525
|
+
### Schema L10 — `type: 'parent'` (outer section)
|
|
526
|
+
- Init: dispatches to `initializeParentSection` at L1210–1211.
|
|
527
|
+
- Render: dispatches to `renderParentSection` at L2499–2500 inside `renderSection`.
|
|
528
|
+
|
|
529
|
+
### Schema L11–L13 — `rowStyle: { flexDirection: 'column' }`
|
|
530
|
+
- Read at FormBuilder L2480: `<CapRow useLegacy style={section.rowStyle ? section.rowStyle : {}}>`. Sets the outer row to vertical stacking.
|
|
531
|
+
|
|
532
|
+
### Schema L14 — `childSections: [`
|
|
533
|
+
- Iterated at FormBuilder L1048 (init) and L2475 (`section.childSections.map(...)` in `renderParentSection`).
|
|
534
|
+
|
|
535
|
+
### Schema L16 — `type: "multicols"` (childSections[0], the empty placeholder)
|
|
536
|
+
- Init: L1206/1051 → `initializeMultiColSection` (no-op because cols is empty).
|
|
537
|
+
- Render: L2497–2498 → `renderMultiColSection`. Since `inputFields[0].cols = []`, the inner `_.forEach(field.cols, ...)` at L2537 produces zero columns. **Effectively renders an empty `<CapRow>`.**
|
|
538
|
+
|
|
539
|
+
### Schema L17 — `width: 16`
|
|
540
|
+
- Read at FormBuilder L2477: `<CapColumn ... span={childSection.width}>` — wraps this empty multicols in a 16-span column.
|
|
541
|
+
|
|
542
|
+
### Schema L18 — `inputFields: [`
|
|
543
|
+
- Iterated at FormBuilder L1093 (init) and L2535 (`_.forEach(section.inputFields, ...)` in `renderMultiColSection`).
|
|
544
|
+
|
|
545
|
+
### Schema L20–L23 — `cols: []`
|
|
546
|
+
- Iterated at FormBuilder L1094 (init) and L2537 (render). Empty array → no fields produced.
|
|
547
|
+
|
|
548
|
+
### Schema L26–L28 — `actionFields: []`
|
|
549
|
+
- Iterated at FormBuilder L2446 (`_.forEach(section.actionFields, ...)`). Empty → no buttons produced.
|
|
550
|
+
|
|
551
|
+
### Schema L31 — `type: "parent"` (childSections[1])
|
|
552
|
+
- Same dispatchers as L10 above (L1210/L2499).
|
|
553
|
+
|
|
554
|
+
### Schema L33 — `childSections: [`
|
|
555
|
+
- L1048 / L2475.
|
|
556
|
+
|
|
557
|
+
### Schema L36 — `width: 14`
|
|
558
|
+
- L2477 → `<CapColumn span={14}>` — defines the LEFT (form) pane.
|
|
559
|
+
|
|
560
|
+
### Schema L37 — `type: "parent"`
|
|
561
|
+
- Same as L10 → L1210/L2499 (`renderParentSection` recurses).
|
|
562
|
+
|
|
563
|
+
### Schema L38 — `id: "kiwi"`
|
|
564
|
+
- **DEAD**. No grep hits in FormBuilder. Pure label.
|
|
565
|
+
|
|
566
|
+
### Schema L39 — `childSections: [`
|
|
567
|
+
- L1048 / L2475.
|
|
568
|
+
|
|
569
|
+
---
|
|
570
|
+
|
|
571
|
+
#### Field 1 — `template-name` (L46–L64)
|
|
572
|
+
|
|
573
|
+
**Branch chosen**: schema L41 `type: "multicols"` → render at L2497 → leaf switch at L2576 → schema L48 `type: "input"` → `case "input"` at L2577.
|
|
574
|
+
|
|
575
|
+
| Schema line | Schema content | FormBuilder line(s) — what happens |
|
|
576
|
+
|---|---|---|
|
|
577
|
+
| L41 | `type: "multicols"` | L2497–2498 `renderMultiColSection`; L1206 init |
|
|
578
|
+
| L42 | `inputFields: [` | L2535 iterate; L1093 init |
|
|
579
|
+
| L44 | `cols: [` | L2537 iterate; L1094 init |
|
|
580
|
+
| L46 | `id: "template-name"` | **L1117** `formElements.push(col.id)`; **L1132** `formData['template-name'] = ''`; **L1144** `errorData['template-name'] = false` (init pass); render: **L2241** `<CapInput id={val.id}>`; state reads at **L2251–2252** (`defaultValue`/`value`). Hard-coded reads in validateForm: **L472, L473, L476** (and L515/L519/L749/L750/L755 for LINE/EMAIL channel branches — not hit for SMS). updateFormData write: **L1739** `formData[val.id] = data` |
|
|
581
|
+
| L47 | `placeholder: "Enter template name"` | **L2249** `placeholder={val.placeholder}` |
|
|
582
|
+
| L48 | `type: "input"` | **L2577** `case "input":` selects this branch |
|
|
583
|
+
| L49 | `label: "Creative name"` | **DEAD** — input branch never renders a label |
|
|
584
|
+
| L50 | `metaType: "text"` | **DEAD** — only `metaType==='submit-button'` is checked (L2453, L2739) |
|
|
585
|
+
| L51 | `datatype: "string"` | **DEAD** — never grepped |
|
|
586
|
+
| L52 | `required: true` | implicit via L472 (validateForm only fires if formData empty) |
|
|
587
|
+
| L53 | `fluid: true` | **DEAD** — commented out at **L2244** (`// fluid={val.fluid}`) and L2587 |
|
|
588
|
+
| L54 | `styling: "semantic"` | **L2235** `if (styling === 'semantic')` gate; **L2580** in inputFields branch |
|
|
589
|
+
| L55 | `order: 1` | **DEAD** |
|
|
590
|
+
| L56 | `width: 18` | **L2239** `<CapColumn span={val.width}>` (also L2582 in inputFields path) |
|
|
591
|
+
| L57 | `customComponent: false` | **DEAD** |
|
|
592
|
+
| L58 | `standalone: true` | **L2540** `isVersionEnable = (usingTabContainer && !val.standalone)`; also gates L2542, L2545, L2553 (skipping version-tab guards) |
|
|
593
|
+
| L59 | `onlyDisplay: false` | **L1115** init: editable → state slot created; **L2545–L2557** render: required for guards |
|
|
594
|
+
| L60 | `errorMessage: "Template name cannot be empty."` | **L2242** `errorMessage={val.errorMessage && ifError ? val.errorMessage : ''}` (also L2585) |
|
|
595
|
+
| L61–L63 | `supportedEvents: ["onChange"]` | **DEAD as a list** — FormBuilder doesn't read `supportedEvents`. The actual onChange wiring is **L2250** `onChange={(e) => this.updateFormData(e.target.value, val)}` (also L2593). The string `"onChange"` matters only as a key in the parent container's `injectedEvents` map, dispatched at **L1778–1779** (`val.injectedEvents.onChange.call(...)`) |
|
|
596
|
+
|
|
597
|
+
---
|
|
598
|
+
|
|
599
|
+
#### Field 2 — `tagList` (L75–L94)
|
|
600
|
+
|
|
601
|
+
**Branch chosen**: schema L70 `type: "multicols"` → leaf switch → schema L81 `type: "tag-list"` → `case "tag-list"` at **L2719** (inputFields path inside renderMultiColSection).
|
|
602
|
+
|
|
603
|
+
| Schema line | Schema content | FormBuilder line(s) — what happens |
|
|
604
|
+
|---|---|---|
|
|
605
|
+
| L70 | `type: "multicols"` | L2497, L1206 |
|
|
606
|
+
| L71 | `inputFields: [` | L2535, L1093 |
|
|
607
|
+
| L73 | `rowClassName: 'tag-container'` | **DEAD** for SMS — only read at **L3008** (in `renderContainer`, not reachable from `standalone`) |
|
|
608
|
+
| L74 | `cols: [` | L2537, L1094 |
|
|
609
|
+
| L76 | `style: {marginBottom:'unset', height:'24px'}` | **L2721** `<CapColumn ... style={val.style ? val.style : {marginBottom:'16px'}}>` |
|
|
610
|
+
| L77 | `width: 18` | **L2721** `span={val.width ? val.width : ''}` |
|
|
611
|
+
| L78 | `id: "tagList"` | Not pushed to formElements (`onlyDisplay:true` at L1115 short-circuits AND L1116 also filters `tagList-` prefix); **L2732** `<TagList id={val.id}>` |
|
|
612
|
+
| L79 | `label: "Add label"` | **L2725** `label={val.label ? val.label : ''}` |
|
|
613
|
+
| L80 | `className: 'add-label'` | **L2731** `className={val.className ? val.className : ''}` |
|
|
614
|
+
| L81 | `type: "tag-list"` | **L2719** `case "tag-list":` |
|
|
615
|
+
| L82 | `metaType: "List"` | **DEAD** |
|
|
616
|
+
| L83 | `datatype: "select"` | **DEAD** |
|
|
617
|
+
| L84 | `required: true` | **DEAD** (no validation hits this id) |
|
|
618
|
+
| L85 | `fluid: true` | **DEAD** |
|
|
619
|
+
| L86 | `onlyDisplay: true` | **L1115** init: skips creating state slot; **L2545–L2557** render: bypasses formData/errorData guards so the field renders even with no state entry |
|
|
620
|
+
| L87 | `styling: "semantic"`| **DEAD** for tag-list (no styling gate in this branch) |
|
|
621
|
+
| L88 | `order: 1` | **DEAD** |
|
|
622
|
+
| L89 | `customComponent: true` | **DEAD** |
|
|
623
|
+
| L90–L92 | `supportedEvents: ["onTagSelect"]` | **DEAD as a list**; the actual wiring is **L2726** `onTagSelect={(data) => this.callChildEvent(data, val, 'onTagSelect')}`, dispatched at **L1877–1879** |
|
|
624
|
+
|
|
625
|
+
---
|
|
626
|
+
|
|
627
|
+
#### Field 3 — `sms-editor` (L105–L129)
|
|
628
|
+
|
|
629
|
+
**Branch chosen**: schema L99 `type: "multicols"` → leaf switch → schema L108 `type: "textarea"` → `case "textarea"` at **L2613** → `renderTextAreaContent` at **L2118** (call site L2616).
|
|
630
|
+
|
|
631
|
+
| Schema line | Schema content | FormBuilder line(s) — what happens |
|
|
632
|
+
|---|---|---|
|
|
633
|
+
| L99 | `type: "multicols"` | L2497, L1206 |
|
|
634
|
+
| L101 | `inputFields: [` | L2535, L1093 |
|
|
635
|
+
| L103 | `rowClassName: 'sms-message-content'` | **DEAD** — only L3008 (renderContainer) |
|
|
636
|
+
| L104 | `cols: [` | L2537, L1094 |
|
|
637
|
+
| L106 | `id: "sms-editor"` | **L1117** push to formElements; **L1132** init seed; **L1144** errorData seed. Render: **L2174** `<CapTextArea id={val.id}>`. Hard-coded literal reads: **L447, L454, L458, L466** (validateForm), **L2276** (`contentPreview` branch), **L2332, L2759** (sms-preview branch). The version-suffix string `\`sms-editor${index>1?index:''}\`` is concatenated at L447, L2332, L2759. updateFormData write: **L1739** (and L1723 in version-tab path) |
|
|
638
|
+
| L107 | `label: 'Message'` | **DEAD in textarea branch** — `renderTextAreaContent` never renders a label |
|
|
639
|
+
| L108 | `type: "textarea"` | **L2613** `case "textarea":` selects branch |
|
|
640
|
+
| L109 | `metaType: "text"` | **DEAD** |
|
|
641
|
+
| L110 | `datatype: "string"`| **DEAD** |
|
|
642
|
+
| L111 | `style: {marginBottom:'16px'}` | **L2180** `style={val.style ? val.style : {}}` |
|
|
643
|
+
| L112 | `required: true` | implicit via L466 (empty content = error) |
|
|
644
|
+
| L113 | `errorMessage: "Template content has unsupported/missing tags!"` | **DEAD** — overridden by i18n at **L2155–2168** (token translation: `MISSING_TAG_ERROR`/`UNSUPPORTED_TAG_ERROR`/`true` → `messages.*ValidationError`) |
|
|
645
|
+
| L114 | `placeholder: "Please input sms template content."` | **L2176** `placeholder={val.placeholder ? val.placeholder : ''}` |
|
|
646
|
+
| L115 | `fluid: true` | **DEAD** |
|
|
647
|
+
| L116 | `styling: "semantic"` | **L2170** `if (styling === 'semantic')` gate inside renderTextAreaContent |
|
|
648
|
+
| L117 | `onlyDisplay: false` | **L1115** init creates state slot; **L2545–L2557** render guards |
|
|
649
|
+
| L118 | `width: 18` | **L2172** `<CapColumn ... span={val.width}>` |
|
|
650
|
+
| L119 | `autosize: true` | **L2178** `autosize={val.autosize ? val.autosizeParams : false}` |
|
|
651
|
+
| L120–L123 | `autosizeParams: {maxRows:18, minRows:3}` | **L2178** value passed when `autosize` is truthy |
|
|
652
|
+
| L124 | `order: 2` | **DEAD** |
|
|
653
|
+
| L125 | `customComponent: true` | **DEAD** |
|
|
654
|
+
| L126–L128 | `supportedEvents: ["onChange"]` | onChange wired at **L2179** `onChange={(e) => this.updateFormData(e.target.value, val)}` (the `event` arg is omitted, so `updateFormData` falls through to L1738 / L1739 / L1722–1726 depending on tabContainer state). Container injectedEvents.onChange dispatched at **L1778–1779** |
|
|
655
|
+
|
|
656
|
+
---
|
|
657
|
+
|
|
658
|
+
#### Field 4 — `sms-count` (L138–L150)
|
|
659
|
+
|
|
660
|
+
**Branch chosen**: `val.primitive === true` at **L2561** → `renderPrimitiveElement` at **L2507** → schema L140 `type: 'div'` → `case "div"` at **L2514**.
|
|
661
|
+
|
|
662
|
+
| Schema line | Schema content | FormBuilder line(s) — what happens |
|
|
663
|
+
|---|---|---|
|
|
664
|
+
| L131 | `rowClassName: 'sms-count-container'` | **DEAD** (L3008 only) |
|
|
665
|
+
| L133–L136 | `rowStyle: { bottom:'8px', fontSize:'12px' }` | **DEAD** for SMS — `inputFields[].rowStyle` only read at L3008 |
|
|
666
|
+
| L137 | `cols: [` | L2537, L1094 |
|
|
667
|
+
| L139 | `id: 'sms-count'` | **NOT** pushed to formElements (L1115 skips because `onlyDisplay:true`); render: **L2520** `<div id={val.id}>`; state read at **L2515** `formData[currentTab-1][val.id]` if non-empty trims |
|
|
668
|
+
| L140 | `type: 'div'` | **L2514** `case "div":` in renderPrimitiveElement |
|
|
669
|
+
| L141 | `props: {type:'label3'}` | **DEAD** |
|
|
670
|
+
| L142 | `onlyDisplay: true` | **L1115** init skip; **L2545–L2557** render bypass |
|
|
671
|
+
| L143 | `metaType: "label"` | **DEAD** |
|
|
672
|
+
| L144 | `value: ""` | **L2510** `let value = val.dynamicTab ? tabName : val.value;` — used as default text, then overridden at **L2515–2516** if formData has a non-empty value. Also in init at **L1124–1125** seed for container path (not the SMS standalone path) |
|
|
673
|
+
| L145 | `primitive: true` | **L2561** `if (val.primitive)` — REQUIRED to route through `renderPrimitiveElement` instead of the type switch |
|
|
674
|
+
| L146 | `width: 18` | **L2569** `<CapColumn span={val.width}>` |
|
|
675
|
+
| L147–L149 | `style: { float:'right' }` | **L2522** `style={val.style ? val.style : {}}` |
|
|
676
|
+
|
|
677
|
+
Note: `colStyle`/`offset` would also flow at L2569 if added.
|
|
678
|
+
|
|
679
|
+
---
|
|
680
|
+
|
|
681
|
+
#### Field 5 — `unicode-validity` (L155–L177)
|
|
682
|
+
|
|
683
|
+
**Branch chosen**: schema L99 multicols → leaf switch → schema L166 `type: "checkbox"` → `case "checkbox"` at **L2618** (inputFields path).
|
|
684
|
+
|
|
685
|
+
| Schema line | Schema content | FormBuilder line(s) — what happens |
|
|
686
|
+
|---|---|---|
|
|
687
|
+
| L154 | `cols: [` | L2537 |
|
|
688
|
+
| L156 | `id: "unicode-validity"` | **L1117** push; **L1132**/**L1144** init seed; checkbox-init override at **L1153–1155** sets `formData[id]=false`; **L1192**/**L1194** also force false in initializeColLabelSection. Render: **L2622** wraps; **L2627** `checked={formData[id]}`; **L2628** `defaultChecked`. Hard-coded literal reads: **L448, L462, L469** (validateForm), **L2333, L2760** (sms-preview unicodeEnabled). Write: **L2626** `onChange={(e) => this.updateFormData(e.target.checked, val)}` → L1739 |
|
|
689
|
+
| L157 | `label: "Allow Unicode characters"` | **L2632** `<CapCheckbox>{val.label}</CapCheckbox>` |
|
|
690
|
+
| L158–L160 | `supportedEvents: ["onChange"]` | onChange wired at **L2626** (see above) |
|
|
691
|
+
| L161–L163 | `labelStyle: { backgroundColor:"white" }` | **DEAD** for SMS — only `renderColLabelSection` uses it at **L2212** |
|
|
692
|
+
| L166 | `type: "checkbox"` | **L2618** `case "checkbox":` |
|
|
693
|
+
| L167 | `metaType: "text"` | **DEAD** |
|
|
694
|
+
| L168 | `datatype: "boolean"` | **DEAD** |
|
|
695
|
+
| L169 | `required: true` | implicit (validateForm at L461–470 forces `unicodeCheck` based on content) |
|
|
696
|
+
| L170 | `fluid: true` | **DEAD** |
|
|
697
|
+
| L171 | `styling: "semantic"` | **L2620** `if (styling === 'semantic')` gate |
|
|
698
|
+
| L172 | `order: 1` | **DEAD** |
|
|
699
|
+
| L173 | `width: 18` | **L2622** `<CapColumn span={val.width}>` |
|
|
700
|
+
| L174 | `onlyDisplay: false` | **L1115** init creates slot; **L2545–L2557** render guards |
|
|
701
|
+
| L175 | `errorMessage: "Unicode characters were detected in your content."` | **L2625** `errorMessage={val.errorMessage ? val.errorMessage : ''}` |
|
|
702
|
+
| L176 | `customComponent: false` | **DEAD** |
|
|
703
|
+
|
|
704
|
+
Notes: `style`/`offset`/`disabled` (none here) would flow at **L2629–L2630** and **L2622** if added.
|
|
705
|
+
|
|
706
|
+
---
|
|
707
|
+
|
|
708
|
+
#### Field 6 — `sms-preview` (L194–L210)
|
|
709
|
+
|
|
710
|
+
**Branch chosen**: schema L189 `type: "multicols"` (under childSections[1] `width: 8`) → leaf switch → schema L196 `type: "sms-preview"` → `case "sms-preview"` at **L2754**.
|
|
711
|
+
|
|
712
|
+
| Schema line | Schema content | FormBuilder line(s) — what happens |
|
|
713
|
+
|---|---|---|
|
|
714
|
+
| L188 | `width: 8` (on enclosing childSection) | **L2477** `<CapColumn span={8}>` — defines the RIGHT (preview) pane |
|
|
715
|
+
| L189 | `type: "multicols"` | L2497 |
|
|
716
|
+
| L191 | `inputFields: [` | L2535 |
|
|
717
|
+
| L193 | `cols: [` | L2537 |
|
|
718
|
+
| L195 | `id: "sms-preview"` | NOT pushed to formElements (`onlyDisplay:true`). Render: **L2764** `key={val.id}`; **L2759** fallback read `formData[val.id]` when `isVersionEnable` is false |
|
|
719
|
+
| L196 | `type: "sms-preview"` | **L2754** `case "sms-preview":` selects branch |
|
|
720
|
+
| L197 | `customComponent: true` | **DEAD** |
|
|
721
|
+
| L198–L200 | `customStyling: {padding:"0"}` | **L2765** `style={val.customStyling ? val.customStyling : {}}` |
|
|
722
|
+
| L201 | `metaType: "preview"` | **DEAD** |
|
|
723
|
+
| L202 | `required: true` | **DEAD** (no validateForm hit) |
|
|
724
|
+
| L203 | `fluid: true` | **DEAD** |
|
|
725
|
+
| L204 | `styling: "semantic"` | **DEAD** for this branch (no gate) |
|
|
726
|
+
| L205 | `order: 1` | **DEAD** |
|
|
727
|
+
| L206 | `onlyDisplay: true`| **L1115** skip; **L2545–L2557** bypass |
|
|
728
|
+
| L207 | `offset: 7` | **DEAD** — **L2762** hardcodes `offset={1}` and `span={23}` (overrides any schema value) |
|
|
729
|
+
| L208 | `channel: "SMS"` | **L2766** `channel={val.channel ? val.channel : 'SMS'}` |
|
|
730
|
+
| L209 | `charCounterEnabled: true` | **L2768** `charCounterEnabled={val.charCounterEnabled}` |
|
|
731
|
+
|
|
732
|
+
Content shown by the preview pulls from `sms-editor`/`unicode-validity` at **L2759, L2760** — not from any `sms-preview` field.
|
|
733
|
+
|
|
734
|
+
---
|
|
735
|
+
|
|
736
|
+
#### Field 7 — `save-button` (L223–L240)
|
|
737
|
+
|
|
738
|
+
**Branch chosen**: schema L218 `type: "multicols"` → leaf switch (cols on `inputFields`, NOT actionFields) → schema L227 `type: "button"` → `case "button"` at **L2738**.
|
|
739
|
+
|
|
740
|
+
| Schema line | Schema content | FormBuilder line(s) — what happens |
|
|
741
|
+
|---|---|---|
|
|
742
|
+
| L218 | `type: "multicols"` | L2497 |
|
|
743
|
+
| L219 | `inputFields: [` | L2535 |
|
|
744
|
+
| L221 | `rowClassName: 'creatives-sms-footer'` | **DEAD** (L3008 only) |
|
|
745
|
+
| L222 | `cols: [` | L2537 |
|
|
746
|
+
| L224 | `id: "save-button"` | NOT pushed to formElements (`onlyDisplay:true`). **Hard-coded special case** at **L2746** `val.id === 'save-button' ? this.saveForm() : ...` (also L2457 in renderColLabelSection branch and L3036 in renderContainer). `this.saveForm()` is defined at **L919** |
|
|
747
|
+
| L225 | `metaType: "submit-button"` | **L2739** `if (styling==='semantic' && val.metaType==='submit-button')` — REQUIRED gate or button doesn't render |
|
|
748
|
+
| L226 | `colStyle: {marginRight:'16px'}` | **L2741** `<CapColumn style={val.colStyle ? val.colStyle : {}}>` |
|
|
749
|
+
| L227 | `type: "button"` | **L2738** `case "button":` |
|
|
750
|
+
| L228 | `buttonType: "primary"` | **L2743** `<CapButton type={val.buttonType}>` |
|
|
751
|
+
| L229 | `value: "Save"` | **L2747** `{val.value}` (button label) |
|
|
752
|
+
| L230 | `customComponent: false` | **DEAD** |
|
|
753
|
+
| L231 | `styling: "semantic"` | **L2739** required for the gate |
|
|
754
|
+
| L232 | `order: 3` | **DEAD** |
|
|
755
|
+
| L233 | `fluid: true` | **DEAD** |
|
|
756
|
+
| L234 | `standalone: true` | **L2540** sets `isVersionEnable=false`; bypasses guards at L2542, L2545, L2553 |
|
|
757
|
+
| L235 | `onlyDisplay: true`| **L1115** init skip; **L2545–L2557** bypass |
|
|
758
|
+
| L236 | `submitAction: "saveFormData"` | **DEAD** — L2746 special-cases save-button to call `saveForm()` directly, never uses submitAction |
|
|
759
|
+
| L237–L239 | `supportedEvents: ["saveFormData"]` | **DEAD** — same reason |
|
|
760
|
+
|
|
761
|
+
---
|
|
762
|
+
|
|
763
|
+
#### Field 8 — `test-and-preview-button` (L241–L258)
|
|
764
|
+
|
|
765
|
+
**Branch chosen**: same multicols → `case "button"` at **L2738**.
|
|
766
|
+
|
|
767
|
+
| Schema line | Schema content | FormBuilder line(s) — what happens |
|
|
768
|
+
|---|---|---|
|
|
769
|
+
| L242 | `id: "test-and-preview-button"` | NOT pushed to formElements. **L2746** falls into the else branch: `this.callChildEvent(data, val, val.submitAction)` |
|
|
770
|
+
| L243 | `metaType: "submit-button"` | **L2739** required gate |
|
|
771
|
+
| L244 | `colStyle: {marginRight:'16px'}` | **L2741** `<CapColumn style={val.colStyle}>` |
|
|
772
|
+
| L245 | `type: "button"` | **L2738** `case "button":` |
|
|
773
|
+
| L246 | `buttonType: "secondary"` | **L2743** `<CapButton type='secondary'>` |
|
|
774
|
+
| L247 | `value: "Preview and Test"` | **L2747** label |
|
|
775
|
+
| L248 | `customComponent: false` | **DEAD** |
|
|
776
|
+
| L249 | `styling: "semantic"` | **L2739** gate |
|
|
777
|
+
| L250 | `order: 4` | **DEAD** |
|
|
778
|
+
| L251 | `fluid: true` | **DEAD** |
|
|
779
|
+
| L252 | `standalone: true` | **L2540** isVersionEnable=false |
|
|
780
|
+
| L253 | `onlyDisplay: true`| **L1115** skip; **L2545–L2557** bypass |
|
|
781
|
+
| L254 | `submitAction: "onClick"` | **L2746** `this.callChildEvent(data, val, val.submitAction)` — passes `'onClick'` as the event name; falls through to **L1977** `val.injectedEvents['onClick'].call(this.props.parent, data, val.id)`. Container MUST register `injectedEvents.onClick` for this id, else click is a no-op |
|
|
782
|
+
| L255–L257 | `supportedEvents: ["onClick"]` | **DEAD as a list**; the dispatched event name comes from `submitAction` (L254), not `supportedEvents` |
|
|
783
|
+
|
|
784
|
+
---
|
|
785
|
+
|
|
786
|
+
#### Schema L259–L276 — commented-out `discard-button`
|
|
787
|
+
- **DEAD** — entirely commented out. If uncommented, would route to `case "button"` at L2738 with `submitAction: "discardValues"` → L1925 `else if (event === 'discardValues')` branch (special-cased to `resetState`).
|
|
788
|
+
|
|
789
|
+
### Schema L282–L284 — `actionFields: []` (footer multicols)
|
|
790
|
+
- L2446 `_.forEach(section.actionFields, ...)` — empty.
|
|
791
|
+
|
|
792
|
+
### Schema L293 — `channel: "SMS"` (on definition)
|
|
793
|
+
- **L437** `if (this.props.schema.channel === 'SMS')` — gates the entire SMS validation block (L437–L478). Without this exact string, validateForm becomes a no-op for the form. Also referenced at:
|
|
794
|
+
- L1882 `this.props.schema.channel.toUpperCase() === 'EMAIL'` (deleteVersion branch — not hit)
|
|
795
|
+
- L1893 `this.props.schema.channel.toUpperCase() === 'EMAIL'` (duplicateVersion — not hit)
|
|
796
|
+
- L1913 `this.props.schema.channel.toUpperCase() === 'EMAIL'` (deleteVersion — not hit)
|
|
797
|
+
|
|
798
|
+
### Schema L295–L299 — `isActive`, `createdBy`, `updatedBy`, `createdAt`, `updatedAt`
|
|
799
|
+
- **DEAD** in FormBuilder.
|
|
800
|
+
|
|
801
|
+
---
|
|
802
|
+
|
|
803
|
+
### Reverse index — every FormBuilder line that touches the SMS schema
|
|
804
|
+
|
|
805
|
+
| FormBuilder line | What it does | Schema line(s) it depends on |
|
|
806
|
+
|---|---|---|
|
|
807
|
+
| 437 | `schema.channel === 'SMS'` gate | L293 |
|
|
808
|
+
| 447 | `formData[count]['sms-editor'+suffix]` | L106 (id) |
|
|
809
|
+
| 448 | `formData[count]['unicode-validity'+suffix]` | L156 (id) |
|
|
810
|
+
| 454, 458, 466 | write `errorData['sms-editor'+suffix]` | L106 |
|
|
811
|
+
| 462, 469 | write `errorData['unicode-validity'+suffix]` | L156 |
|
|
812
|
+
| 472–476 | `template-name` required check | L46 (id) |
|
|
813
|
+
| 919–930 | `saveForm()` | L224 (special-cased id) |
|
|
814
|
+
| 1115 | `if (!col.onlyDisplay && !ifError)` init filter | L59, L86, L117, L142, L174, L206, L235, L253 (onlyDisplay) |
|
|
815
|
+
| 1116 | filter `tagList-` prefix from formElements | L78 |
|
|
816
|
+
| 1117 | `formElements.push(col.id)` | L46, L106, L156 (the editable ids) |
|
|
817
|
+
| 1132 / 1144 | seed `formData[col.id]=''` / `errorData[col.id]=false` | L46, L106, L156 |
|
|
818
|
+
| 1150–1155 | force checkbox formData = false | L156 (type:checkbox) |
|
|
819
|
+
| 1203, 1204 | enter standalone walk | L7, L8 |
|
|
820
|
+
| 1210–1211 | parent dispatch (init) | L10, L31, L37 |
|
|
821
|
+
| 1206–1208 | multicols/col-label dispatch (init) | L16, L41, L70, L99, L189, L218 |
|
|
822
|
+
| 1739 | `formData[val.id] = data` on every update | all editable ids |
|
|
823
|
+
| 1977 | `val.injectedEvents[event].call(parent, ...)` | L254 (submitAction:"onClick") |
|
|
824
|
+
| 2174 | `<CapTextArea id>` | L106 (id) |
|
|
825
|
+
| 2176 | textarea placeholder | L114 |
|
|
826
|
+
| 2178 | autosize/autosizeParams | L119, L120 |
|
|
827
|
+
| 2180 | textarea style | L111 |
|
|
828
|
+
| 2241 | `<CapInput id>` | L46 |
|
|
829
|
+
| 2249 | input placeholder | L47 |
|
|
830
|
+
| 2241–2256 | full input render | L46–L60 |
|
|
831
|
+
| 2241+L2245-2247 | dvs-module style override | L46+`style` (none here) |
|
|
832
|
+
| 2475 | `childSections.map` | L14, L33, L39 |
|
|
833
|
+
| 2477 | `<CapColumn span={width}>` for childSection | L36 (width:14), L188 (width:8) (and L17 width:16 for empty multicols) |
|
|
834
|
+
| 2480 | `<CapRow style={section.rowStyle}>` | L11–L13 |
|
|
835
|
+
| 2497–2498 | multicols dispatch (render) | L16, L41, L70, L99, L189, L218 |
|
|
836
|
+
| 2499–2500 | parent dispatch (render) | L10, L31, L37 |
|
|
837
|
+
| 2514–2524 | renderPrimitiveElement `case "div"` | L140 |
|
|
838
|
+
| 2515–2516 | div content from formData | L139 (id) |
|
|
839
|
+
| 2520, 2522 | div id, style | L139, L147 |
|
|
840
|
+
| 2540 | `isVersionEnable = (usingTabContainer && !standalone)` | L58, L234, L252 |
|
|
841
|
+
| 2545–2557 | onlyDisplay/standalone guard skip | L59, L86, L117, L142, L174, L206, L235, L253 |
|
|
842
|
+
| 2561 | `if (val.primitive)` → renderPrimitiveElement | L145 |
|
|
843
|
+
| 2569 | primitive `<CapColumn span={width}>` | L146 |
|
|
844
|
+
| 2576 | leaf type switch | L48, L81, L108, L140, L166, L196, L227, L245 |
|
|
845
|
+
| 2577–2601 | `case "input"` | L46–L60 |
|
|
846
|
+
| 2613–2617 | `case "textarea"` → renderTextAreaContent | L106–L128 |
|
|
847
|
+
| 2618–2637 | `case "checkbox"` | L155–L176 |
|
|
848
|
+
| 2719–2737 | `case "tag-list"` | L75–L93 |
|
|
849
|
+
| 2738–2752 | `case "button"` | L223–L257 |
|
|
850
|
+
| 2741 | button colStyle | L226, L244 |
|
|
851
|
+
| 2743 | `<CapButton type={buttonType}>` | L228, L246 |
|
|
852
|
+
| 2746 | save-button special-case OR callChildEvent | L224, L242, L254 |
|
|
853
|
+
| 2747 | button label (`val.value`) | L229, L247 |
|
|
854
|
+
| 2754–2774 | `case "sms-preview"` | L194–L210 |
|
|
855
|
+
| 2759 | preview content from `sms-editor` | L106 (id literal) |
|
|
856
|
+
| 2760 | preview unicodeEnabled from `unicode-validity` | L156 (id literal) |
|
|
857
|
+
| 2762 | hardcoded `span={23} offset={1}` | overrides L207 |
|
|
858
|
+
| 2765 | preview customStyling | L198–L200 |
|
|
859
|
+
| 2766 | preview channel | L208 |
|
|
860
|
+
| 2768 | preview charCounterEnabled | L209 |
|
|
861
|
+
| 3165 | `const schema = this.props.schema` | L6 (definition) |
|
|
862
|
+
| 3169–3174 | renderForm walks `standalone.sections` | L7, L8 |
|
|
863
|
+
|
|
864
|
+
---
|
|
865
|
+
|
|
866
|
+
## 10. Visual graph
|
|
867
|
+
|
|
868
|
+
```
|
|
869
|
+
┌─────────────────────────────────── initialSchema.js (response) ────────────────────────────────────┐
|
|
870
|
+
│ │
|
|
871
|
+
│ metaEntities[0].definition │
|
|
872
|
+
│ ├── channel: "SMS" ────────────────────────────────────────► validateForm gate (line 437) │
|
|
873
|
+
│ └── standalone.sections │
|
|
874
|
+
│ └── [0] type:"parent" rowStyle:{flexDirection:'column'} │
|
|
875
|
+
│ ├── childSections[0] type:"multicols" width:16 (empty cols) │
|
|
876
|
+
│ │ │
|
|
877
|
+
│ ├── childSections[1] type:"parent" │
|
|
878
|
+
│ │ ├── childSections[0] type:"parent" id:"kiwi" width:14 │
|
|
879
|
+
│ │ │ ├── multicols ─► cols[ template-name (input) ] │
|
|
880
|
+
│ │ │ ├── multicols ─► cols[ tagList (tag-list) ] │
|
|
881
|
+
│ │ │ └── multicols ─► cols[ sms-editor (textarea) , │
|
|
882
|
+
│ │ │ sms-count (div primitive), │
|
|
883
|
+
│ │ │ unicode-validity (checkbox) ] │
|
|
884
|
+
│ │ │ │
|
|
885
|
+
│ │ └── childSections[1] type:"multicols" width:8 │
|
|
886
|
+
│ │ └── cols[ sms-preview (sms-preview) ] │
|
|
887
|
+
│ │ │
|
|
888
|
+
│ └── childSections[2] type:"multicols" │
|
|
889
|
+
│ └── cols[ save-button (button id-special-cased), │
|
|
890
|
+
│ test-and-preview-button (button -> callChildEvent('onClick')) ] │
|
|
891
|
+
└────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
|
892
|
+
│
|
|
893
|
+
▼
|
|
894
|
+
┌─────────────────────────────────── FormBuilder/index.js ───────────────────────────────────────────┐
|
|
895
|
+
│ │
|
|
896
|
+
│ render() (3193) ─► renderForm() (3164) ─► schema.standalone.sections.map(renderSection) │
|
|
897
|
+
│ │
|
|
898
|
+
│ renderSection (2493) ──┬── "parent" ► renderParentSection (2473) ── recurses ──► renderSection│
|
|
899
|
+
│ ├── "multicols"► renderMultiColSection (2530) ── per-col switch (2576) │
|
|
900
|
+
│ └── "col-label"► renderColLabelSection │
|
|
901
|
+
│ │
|
|
902
|
+
│ renderMultiColSection per-col switch: │
|
|
903
|
+
│ val.primitive=true ─► renderPrimitiveElement (2507) ─► "div"/"span" (sms-count) │
|
|
904
|
+
│ val.type "input" ─► CapColumn>CapInput (template-name) │
|
|
905
|
+
│ val.type "textarea" ─► renderTextAreaContent (sms-editor) │
|
|
906
|
+
│ val.type "checkbox" ─► CapColumn>CapCheckbox (unicode-validity) │
|
|
907
|
+
│ val.type "tag-list" ─► CapColumn>TagList (tagList) │
|
|
908
|
+
│ val.type "sms-preview" ─► CapColumn>TemplatePreview (sms-preview) │
|
|
909
|
+
│ val.type "button" ─► CapColumn>CapButton (save-button, test-and-preview-button) │
|
|
910
|
+
│ │
|
|
911
|
+
│ Init phase (componentWillMount → initialiseForm, 1273): │
|
|
912
|
+
│ initializeStandAloneSections walks parent/multicols/col-label and seeds │
|
|
913
|
+
│ formData[col.id]='' (or col.value), errorData[col.id]=false, formElements.push(col.id) │
|
|
914
|
+
│ then prunes formData keys not in formElements (1303–1314) │
|
|
915
|
+
│ │
|
|
916
|
+
│ Validation/state side channel (uses formData/errorData keyed by val.id): │
|
|
917
|
+
│ validateForm (430) channel-gates on schema.channel==='SMS' (437) │
|
|
918
|
+
│ validateForm hard-codes: 'sms-editor', 'unicode-validity', 'template-name' │
|
|
919
|
+
│ textarea errorMessage translates errorData tokens via i18n messages (2155–2168) │
|
|
920
|
+
│ saveForm/callChildEvent special-case: id==='save-button' │
|
|
921
|
+
└────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
|
922
|
+
```
|