Skip to content

Commit fd27c65

Browse files
committed
fix: field ids for input groups
1 parent 1dacddc commit fd27c65

File tree

6 files changed

+153
-144
lines changed

6 files changed

+153
-144
lines changed

src/js/common/dom.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -439,7 +439,6 @@ class DOM {
439439
}
440440

441441
if (isPreview) {
442-
input.attrs.name = `prev-${input.attrs.name}`
443442
optionLabel.attrs.contenteditable = true
444443
}
445444

src/js/components/component.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -433,7 +433,6 @@ export default class Component extends Data {
433433
from.classList.remove('column-editing-field')
434434
}
435435

436-
437436
// make this configurable
438437
if (this.name !== 'stage' && !this.children.length) {
439438
return this.remove()
@@ -584,6 +583,7 @@ export default class Component extends Data {
584583
if (this.name === 'column') {
585584
parent.autoColumnWidths()
586585
}
586+
return newClone
587587
}
588588

589589
cloneChildren = toParent => {

src/js/constants.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,3 +175,5 @@ export const CONDITION_TEMPLATE = () => ({
175175
},
176176
],
177177
})
178+
179+
export const UUID_REGEXP = /(\b[0-9a-f]{8}\b-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-\b[0-9a-f]{12}\b)/gi

src/js/renderer.js

Lines changed: 112 additions & 123 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
import isEqual from 'lodash/isEqual'
22
import dom from './common/dom'
33
import { uuid, isAddress, isExternalAddress } from './common/utils'
4-
import { STAGE_CLASSNAME } from './constants'
4+
import { STAGE_CLASSNAME, UUID_REGEXP } from './constants'
5+
6+
const RENDER_PREFIX = 'f-'
57

68
const processOptions = ({ container, ...opts }) => {
79
const processedOptions = {
@@ -11,28 +13,13 @@ const processOptions = ({ container, ...opts }) => {
1113
return Object.assign({}, opts, processedOptions)
1214
}
1315

14-
const baseId = id => id.replace(/^f-/, '')
15-
16-
const recursiveNewIds = (elem, level = 0) => {
17-
if (!level) {
18-
elem.setAttribute('id', `f-${uuid()}`)
19-
}
20-
const elems = elem.querySelectorAll('*')
21-
const elemsLength = elems.length
22-
for (let i = 0; i < elemsLength; i++) {
23-
const element = elems[i]
24-
if (element.id) {
25-
const label = element.parentElement.querySelector(`[for=${element.id}]`)
26-
const newElementId = `f-${uuid()}`
27-
element.setAttribute('id', newElementId)
28-
if (label) {
29-
label.setAttribute('for', newElementId)
30-
}
31-
}
32-
recursiveNewIds(element, level + 1)
33-
}
16+
const baseId = id => {
17+
const match = id.match(UUID_REGEXP)
18+
return (match && match[0]) || id
3419
}
3520

21+
const newUUID = id => id.replace(UUID_REGEXP, uuid())
22+
3623
const createRemoveButton = () =>
3724
dom.render(
3825
dom.btnTemplate({
@@ -46,30 +33,6 @@ const createRemoveButton = () =>
4633
})
4734
)
4835

49-
const addButton = () =>
50-
dom.render({
51-
tag: 'button',
52-
attrs: {
53-
className: 'add-input-group btn pull-right',
54-
type: 'button',
55-
},
56-
children: 'Add +',
57-
action: {
58-
click: e => {
59-
const fInputGroup = e.target.parentElement
60-
const elem = e.target.previousSibling.cloneNode(true)
61-
recursiveNewIds(elem)
62-
const existingRemoveButton = elem.querySelector('.remove-input-group')
63-
if (existingRemoveButton) {
64-
dom.remove(existingRemoveButton)
65-
}
66-
67-
fInputGroup.insertBefore(elem, fInputGroup.lastChild)
68-
elem.appendChild(createRemoveButton())
69-
},
70-
},
71-
})
72-
7336
export default class FormeoRenderer {
7437
constructor(opts, formData) {
7538
const { renderContainer, external } = processOptions(opts)
@@ -96,80 +59,107 @@ export default class FormeoRenderer {
9659
this.renderedForm = dom.render(config)
9760
dom.empty(this.container)
9861

62+
this.applyConditions()
63+
9964
this.container.appendChild(this.renderedForm)
10065
}
10166

102-
orderChildren = (type, order) =>
103-
order.reduce((acc, cur) => {
104-
acc.push(this.form[type][cur])
105-
return acc
106-
}, [])
67+
orderChildren = (type, order) => order.reduce((acc, cur) => [...acc, this.form[type][cur]], [])
68+
69+
prefixId = id => RENDER_PREFIX + id
10770

10871
/**
10972
* Convert sizes, apply styles for render
11073
* @param {Object} columnData
11174
* @return {Object} processed column data
11275
*/
113-
processColumnConfig = columnData => {
114-
if (!columnData) {
115-
return
116-
}
117-
const colWidth = columnData.config.width || '100%'
118-
columnData.style = `width: ${colWidth}`
119-
columnData.children = this.processFields(columnData.children)
120-
return dom.render(columnData)
121-
}
76+
processColumn = ({ id, ...columnData }) =>
77+
Object.assign({}, columnData, {
78+
id: this.prefixId(id),
79+
children: this.processFields(columnData.children),
80+
style: `width: ${columnData.config.width || '100%'}`,
81+
})
12282

12383
processRows = stageId =>
124-
this.orderChildren('rows', this.form.stages[stageId].children).map(row => {
125-
if (!row) {
126-
return
127-
}
84+
this.orderChildren('rows', this.form.stages[stageId].children).reduce(
85+
(acc, row) => (row ? [...acc, this.processRow(row)] : acc),
86+
[]
87+
)
88+
89+
cacheComponent = data => {
90+
this.components[baseId(data.id)] = data
91+
return data
92+
}
93+
94+
/**
95+
* Applies a row's config
96+
* @param {Object} row data
97+
* @return {Object} row config object
98+
*/
99+
processRow = (data, type = 'row') => {
100+
const { config, id } = data
101+
const className = [`formeo-${type}-wrap`]
102+
const rowData = Object.assign({}, data, { children: this.processColumns(data.id), id: this.prefixId(id) })
103+
this.cacheComponent(rowData)
104+
105+
const configConditions = [
106+
{ condition: config.legend, result: () => ({ tag: config.fieldset ? 'legend' : 'h3', children: config.legend }) },
107+
{ condition: true, result: () => rowData },
108+
{ condition: config.inputGroup, result: () => this.addButton(id) },
109+
]
110+
111+
const children = configConditions.reduce((acc, { condition, result }) => (condition ? [...acc, result()] : acc), [])
112+
113+
if (config.inputGroup) {
114+
className.push(RENDER_PREFIX + 'input-group-wrap')
115+
}
128116

129-
row.children = this.processColumns(row.id)
117+
return {
118+
tag: config.fieldset ? 'fieldset' : 'div',
119+
id: uuid(),
120+
className,
121+
children,
122+
}
123+
}
130124

131-
if (row.config.inputGroup) {
132-
return this.makeInputGroup(row)
133-
}
125+
cloneComponentData = componentId => {
126+
const { children = [], id, ...rest } = this.components[componentId]
127+
return Object.assign({}, rest, {
128+
id: newUUID(id),
129+
children: children.length && children.map(({ id }) => this.cloneComponentData(baseId(id))),
130+
})
131+
}
134132

135-
return row
133+
addButton = id =>
134+
dom.render({
135+
tag: 'button',
136+
attrs: {
137+
className: 'add-input-group btn pull-right',
138+
type: 'button',
139+
},
140+
children: 'Add +',
141+
action: {
142+
click: e => {
143+
const fInputGroup = e.target.parentElement
144+
const elem = dom.render(this.cloneComponentData(id))
145+
fInputGroup.insertBefore(elem, fInputGroup.lastChild)
146+
elem.appendChild(createRemoveButton())
147+
},
148+
},
136149
})
137150

138151
processColumns = rowId => {
139-
return this.orderChildren('columns', this.form.rows[rowId].children).map(columnConfig => {
140-
if (columnConfig) {
141-
const column = this.processColumnConfig(columnConfig)
142-
this.components[baseId(columnConfig.id)] = column
143-
144-
return column
145-
}
146-
})
152+
return this.orderChildren('columns', this.form.rows[rowId].children).map(column =>
153+
this.cacheComponent(this.processColumn(column))
154+
)
147155
}
148156

149157
processFields = fieldIds => {
150-
return this.orderChildren('fields', fieldIds).map(child => {
151-
if (child) {
152-
const { conditions } = child
153-
const field = dom.render(child)
154-
this.components[baseId(child.id)] = field
155-
this.processConditions(conditions)
156-
return field
157-
}
158-
})
158+
return this.orderChildren('fields', fieldIds).map(({ id, ...field }) =>
159+
this.cacheComponent(Object.assign({}, field, { id: this.prefixId(id) }))
160+
)
159161
}
160162

161-
/**
162-
* Converts a row to an cloneable input group
163-
* @todo make all columns and fields input groups
164-
* @param {Object} componentData
165-
* @return {NodeElement} inputGroup-ified component
166-
*/
167-
makeInputGroup = data => ({
168-
id: uuid(),
169-
className: 'f-input-group-wrap',
170-
children: [data, addButton()],
171-
})
172-
173163
get processedData() {
174164
return Object.values(this.form.stages).map(stage => {
175165
stage.children = this.processRows(stage.id)
@@ -180,33 +170,32 @@ export default class FormeoRenderer {
180170

181171
/**
182172
* Evaulate and execute conditions for fields by creating listeners for input and changes
183-
* @param {Array} conditions array of arrays of condition definitions
184173
* @return {Array} flattened array of conditions
185174
*/
186-
processConditions = conditions => {
187-
if (!conditions) {
188-
return null
189-
}
190-
191-
conditions.forEach((condition, i) => {
192-
const { if: ifConditions, then: thenConditions } = condition
193-
194-
ifConditions.forEach(ifCondition => {
195-
const { source, ...ifRest } = ifCondition
196-
if (isAddress(source)) {
197-
const component = this.getComponent(source)
198-
const listenerEvent = LISTEN_TYPE_MAP(component)
199-
if (listenerEvent) {
200-
component.addEventListener(
201-
listenerEvent,
202-
evt =>
203-
this.evaluateCondition(ifRest, evt) &&
204-
thenConditions.forEach(thenCondition => this.execResult(thenCondition, evt)),
205-
false
206-
)
207-
}
208-
}
209-
})
175+
applyConditions = () => {
176+
Object.values(this.components).forEach(({ conditions }) => {
177+
if (conditions) {
178+
conditions.forEach((condition, i) => {
179+
const { if: ifConditions, then: thenConditions } = condition
180+
181+
ifConditions.forEach(ifCondition => {
182+
const { source, ...ifRest } = ifCondition
183+
if (isAddress(source)) {
184+
const component = this.getComponent(source)
185+
const listenerEvent = LISTEN_TYPE_MAP(component)
186+
if (listenerEvent) {
187+
component.addEventListener(
188+
listenerEvent,
189+
evt =>
190+
this.evaluateCondition(ifRest, evt) &&
191+
thenConditions.forEach(thenCondition => this.execResult(thenCondition, evt)),
192+
false
193+
)
194+
}
195+
}
196+
})
197+
})
198+
}
210199
})
211200
}
212201

@@ -248,7 +237,7 @@ export default class FormeoRenderer {
248237
const componentId = address.slice(address.indexOf('.') + 1)
249238
const component = isExternalAddress(address)
250239
? this.external[componentId]
251-
: this.components[componentId].querySelector(`#f-${componentId}`)
240+
: this.renderedForm.querySelector(`#f-${componentId}`)
252241
return component
253242
}
254243
}

0 commit comments

Comments
 (0)