11import isEqual from 'lodash/isEqual'
22import dom from './common/dom'
33import { 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
68const 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+
3623const 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-
7336export 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