Skip to content

Commit

Permalink
fix: field ids for input groups
Browse files Browse the repository at this point in the history
  • Loading branch information
kevinchappell committed Jun 1, 2019
1 parent 1dacddc commit fd27c65
Show file tree
Hide file tree
Showing 6 changed files with 153 additions and 144 deletions.
1 change: 0 additions & 1 deletion src/js/common/dom.js
Original file line number Diff line number Diff line change
Expand Up @@ -439,7 +439,6 @@ class DOM {
}

if (isPreview) {
input.attrs.name = `prev-${input.attrs.name}`
optionLabel.attrs.contenteditable = true
}

Expand Down
2 changes: 1 addition & 1 deletion src/js/components/component.js
Original file line number Diff line number Diff line change
Expand Up @@ -433,7 +433,6 @@ export default class Component extends Data {
from.classList.remove('column-editing-field')
}


// make this configurable
if (this.name !== 'stage' && !this.children.length) {
return this.remove()
Expand Down Expand Up @@ -584,6 +583,7 @@ export default class Component extends Data {
if (this.name === 'column') {
parent.autoColumnWidths()
}
return newClone
}

cloneChildren = toParent => {
Expand Down
2 changes: 2 additions & 0 deletions src/js/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -175,3 +175,5 @@ export const CONDITION_TEMPLATE = () => ({
},
],
})

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
235 changes: 112 additions & 123 deletions src/js/renderer.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import isEqual from 'lodash/isEqual'
import dom from './common/dom'
import { uuid, isAddress, isExternalAddress } from './common/utils'
import { STAGE_CLASSNAME } from './constants'
import { STAGE_CLASSNAME, UUID_REGEXP } from './constants'

const RENDER_PREFIX = 'f-'

const processOptions = ({ container, ...opts }) => {
const processedOptions = {
Expand All @@ -11,28 +13,13 @@ const processOptions = ({ container, ...opts }) => {
return Object.assign({}, opts, processedOptions)
}

const baseId = id => id.replace(/^f-/, '')

const recursiveNewIds = (elem, level = 0) => {
if (!level) {
elem.setAttribute('id', `f-${uuid()}`)
}
const elems = elem.querySelectorAll('*')
const elemsLength = elems.length
for (let i = 0; i < elemsLength; i++) {
const element = elems[i]
if (element.id) {
const label = element.parentElement.querySelector(`[for=${element.id}]`)
const newElementId = `f-${uuid()}`
element.setAttribute('id', newElementId)
if (label) {
label.setAttribute('for', newElementId)
}
}
recursiveNewIds(element, level + 1)
}
const baseId = id => {
const match = id.match(UUID_REGEXP)
return (match && match[0]) || id
}

const newUUID = id => id.replace(UUID_REGEXP, uuid())

const createRemoveButton = () =>
dom.render(
dom.btnTemplate({
Expand All @@ -46,30 +33,6 @@ const createRemoveButton = () =>
})
)

const addButton = () =>
dom.render({
tag: 'button',
attrs: {
className: 'add-input-group btn pull-right',
type: 'button',
},
children: 'Add +',
action: {
click: e => {
const fInputGroup = e.target.parentElement
const elem = e.target.previousSibling.cloneNode(true)
recursiveNewIds(elem)
const existingRemoveButton = elem.querySelector('.remove-input-group')
if (existingRemoveButton) {
dom.remove(existingRemoveButton)
}

fInputGroup.insertBefore(elem, fInputGroup.lastChild)
elem.appendChild(createRemoveButton())
},
},
})

export default class FormeoRenderer {
constructor(opts, formData) {
const { renderContainer, external } = processOptions(opts)
Expand All @@ -96,80 +59,107 @@ export default class FormeoRenderer {
this.renderedForm = dom.render(config)
dom.empty(this.container)

this.applyConditions()

this.container.appendChild(this.renderedForm)
}

orderChildren = (type, order) =>
order.reduce((acc, cur) => {
acc.push(this.form[type][cur])
return acc
}, [])
orderChildren = (type, order) => order.reduce((acc, cur) => [...acc, this.form[type][cur]], [])

prefixId = id => RENDER_PREFIX + id

/**
* Convert sizes, apply styles for render
* @param {Object} columnData
* @return {Object} processed column data
*/
processColumnConfig = columnData => {
if (!columnData) {
return
}
const colWidth = columnData.config.width || '100%'
columnData.style = `width: ${colWidth}`
columnData.children = this.processFields(columnData.children)
return dom.render(columnData)
}
processColumn = ({ id, ...columnData }) =>
Object.assign({}, columnData, {
id: this.prefixId(id),
children: this.processFields(columnData.children),
style: `width: ${columnData.config.width || '100%'}`,
})

processRows = stageId =>
this.orderChildren('rows', this.form.stages[stageId].children).map(row => {
if (!row) {
return
}
this.orderChildren('rows', this.form.stages[stageId].children).reduce(
(acc, row) => (row ? [...acc, this.processRow(row)] : acc),
[]
)

cacheComponent = data => {
this.components[baseId(data.id)] = data
return data
}

/**
* Applies a row's config
* @param {Object} row data
* @return {Object} row config object
*/
processRow = (data, type = 'row') => {
const { config, id } = data
const className = [`formeo-${type}-wrap`]
const rowData = Object.assign({}, data, { children: this.processColumns(data.id), id: this.prefixId(id) })
this.cacheComponent(rowData)

const configConditions = [
{ condition: config.legend, result: () => ({ tag: config.fieldset ? 'legend' : 'h3', children: config.legend }) },
{ condition: true, result: () => rowData },
{ condition: config.inputGroup, result: () => this.addButton(id) },
]

const children = configConditions.reduce((acc, { condition, result }) => (condition ? [...acc, result()] : acc), [])

if (config.inputGroup) {
className.push(RENDER_PREFIX + 'input-group-wrap')
}

row.children = this.processColumns(row.id)
return {
tag: config.fieldset ? 'fieldset' : 'div',
id: uuid(),
className,
children,
}
}

if (row.config.inputGroup) {
return this.makeInputGroup(row)
}
cloneComponentData = componentId => {
const { children = [], id, ...rest } = this.components[componentId]
return Object.assign({}, rest, {
id: newUUID(id),
children: children.length && children.map(({ id }) => this.cloneComponentData(baseId(id))),
})
}

return row
addButton = id =>
dom.render({
tag: 'button',
attrs: {
className: 'add-input-group btn pull-right',
type: 'button',
},
children: 'Add +',
action: {
click: e => {
const fInputGroup = e.target.parentElement
const elem = dom.render(this.cloneComponentData(id))
fInputGroup.insertBefore(elem, fInputGroup.lastChild)
elem.appendChild(createRemoveButton())
},
},
})

processColumns = rowId => {
return this.orderChildren('columns', this.form.rows[rowId].children).map(columnConfig => {
if (columnConfig) {
const column = this.processColumnConfig(columnConfig)
this.components[baseId(columnConfig.id)] = column

return column
}
})
return this.orderChildren('columns', this.form.rows[rowId].children).map(column =>
this.cacheComponent(this.processColumn(column))
)
}

processFields = fieldIds => {
return this.orderChildren('fields', fieldIds).map(child => {
if (child) {
const { conditions } = child
const field = dom.render(child)
this.components[baseId(child.id)] = field
this.processConditions(conditions)
return field
}
})
return this.orderChildren('fields', fieldIds).map(({ id, ...field }) =>
this.cacheComponent(Object.assign({}, field, { id: this.prefixId(id) }))
)
}

/**
* Converts a row to an cloneable input group
* @todo make all columns and fields input groups
* @param {Object} componentData
* @return {NodeElement} inputGroup-ified component
*/
makeInputGroup = data => ({
id: uuid(),
className: 'f-input-group-wrap',
children: [data, addButton()],
})

get processedData() {
return Object.values(this.form.stages).map(stage => {
stage.children = this.processRows(stage.id)
Expand All @@ -180,33 +170,32 @@ export default class FormeoRenderer {

/**
* Evaulate and execute conditions for fields by creating listeners for input and changes
* @param {Array} conditions array of arrays of condition definitions
* @return {Array} flattened array of conditions
*/
processConditions = conditions => {
if (!conditions) {
return null
}

conditions.forEach((condition, i) => {
const { if: ifConditions, then: thenConditions } = condition

ifConditions.forEach(ifCondition => {
const { source, ...ifRest } = ifCondition
if (isAddress(source)) {
const component = this.getComponent(source)
const listenerEvent = LISTEN_TYPE_MAP(component)
if (listenerEvent) {
component.addEventListener(
listenerEvent,
evt =>
this.evaluateCondition(ifRest, evt) &&
thenConditions.forEach(thenCondition => this.execResult(thenCondition, evt)),
false
)
}
}
})
applyConditions = () => {
Object.values(this.components).forEach(({ conditions }) => {
if (conditions) {
conditions.forEach((condition, i) => {
const { if: ifConditions, then: thenConditions } = condition

ifConditions.forEach(ifCondition => {
const { source, ...ifRest } = ifCondition
if (isAddress(source)) {
const component = this.getComponent(source)
const listenerEvent = LISTEN_TYPE_MAP(component)
if (listenerEvent) {
component.addEventListener(
listenerEvent,
evt =>
this.evaluateCondition(ifRest, evt) &&
thenConditions.forEach(thenCondition => this.execResult(thenCondition, evt)),
false
)
}
}
})
})
}
})
}

Expand Down Expand Up @@ -248,7 +237,7 @@ export default class FormeoRenderer {
const componentId = address.slice(address.indexOf('.') + 1)
const component = isExternalAddress(address)
? this.external[componentId]
: this.components[componentId].querySelector(`#f-${componentId}`)
: this.renderedForm.querySelector(`#f-${componentId}`)
return component
}
}
Expand Down
Loading

0 comments on commit fd27c65

Please sign in to comment.