@@ -3,6 +3,8 @@ module Lumi.Components.Form
33 , module Internal
44 , module Validation
55 , build
6+ , build'
7+ , defaultRenderForm
68 , static
79 , section
810 , inputBox
@@ -36,7 +38,10 @@ module Lumi.Components.Form
3638 , withProps
3739 , withValue
3840 , mapProps
41+ , mapUI
42+ , mapUI_
3943 , indent
44+ , wrap
4045 , filterWithProps
4146 , withKey
4247 , styles
@@ -66,8 +71,8 @@ import Lumi.Components.Color (colors)
6671import Lumi.Components.Column (column )
6772import Lumi.Components.FetchCache as FetchCache
6873import Lumi.Components.Form.Defaults (formDefaults ) as Defaults
69- import Lumi.Components.Form.Internal (FormBuilder (..), SeqFormBuilder , Tree (..), formBuilder , formBuilder_ , invalidate , pruneTree , sequential )
70- import Lumi.Components.Form.Internal (FormBuilder , SeqFormBuilder , formBuilder , formBuilder_ , invalidate , listen , parallel , revalidate , sequential ) as Internal
74+ import Lumi.Components.Form.Internal (Forest , FormBuilder' (..), FormBuilder , SeqFormBuilder , Tree (..), formBuilder , formBuilder_ , invalidate , pruneTree , sequential )
75+ import Lumi.Components.Form.Internal (Forest , FormBuilder' , FormBuilder , SeqFormBuilder' , SeqFormBuilder , formBuilder , formBuilder_ , invalidate , listen , parallel , revalidate , sequential ) as Internal
7176import Lumi.Components.Form.Validation (Validated (..), Validator , _Validated , fromValidated , mustBe , mustEqual , nonEmpty , nonEmptyArray , nonNull , validNumber , validInt , validDate , optional , setFresh , setModified , validated , warn ) as Validation
7277import Lumi.Components.Input (alignToInput )
7378import Lumi.Components.Input as Input
@@ -94,68 +99,117 @@ import Unsafe.Coerce (unsafeCoerce)
9499-- |
95100-- | _Note_: this function should be fully applied, to avoid remounting
96101-- | the component on each render.
102+
97103build
98104 :: forall props unvalidated result
99- . FormBuilder { readonly :: Boolean | props } unvalidated result
105+ . Union
106+ ( forceTopLabels :: Boolean
107+ , inlineTable :: Boolean
108+ )
109+ ( readonly :: Boolean
110+ | props
111+ )
112+ ( forceTopLabels :: Boolean
113+ , inlineTable :: Boolean
114+ , readonly :: Boolean
115+ | props
116+ )
117+ => FormBuilder { readonly :: Boolean | props } unvalidated result
100118 -> { value :: unvalidated
101119 , onChange :: (unvalidated -> unvalidated ) -> Effect Unit
102- , inlineTable :: Boolean
103120 , forceTopLabels :: Boolean
121+ , inlineTable :: Boolean
104122 , readonly :: Boolean
105123 | props
106124 }
107125 -> JSX
108- build editor = makeStateless (createComponent " Form" ) render where
109- render props@{ value, onChange, inlineTable, forceTopLabels, readonly } =
110-
111- let forest = Array .mapMaybe pruneTree $ edit onChange
112- where
113- props' = contractProps props
114- { edit } = un FormBuilder editor props' value
115-
116- contractProps
117- :: { value :: unvalidated
118- , onChange :: (unvalidated -> unvalidated ) -> Effect Unit
119- , inlineTable :: Boolean
120- , forceTopLabels :: Boolean
121- , readonly :: Boolean
122- | props
123- }
124- -> { readonly :: Boolean
125- | props
126- }
127- contractProps = unsafeCoerce
128-
129- fieldDivider = R .hr { className: " lumi field-divider" }
130-
131- toRow = case _ of
132- Child { key, child } ->
133- maybe identity keyed key $ child
134- Wrapper { key, children } ->
135- R .div
136- { key: fromMaybe " " key
137- , children: [ intercalate fieldDivider (map toRow children) ]
138- }
139- Node { label, key, required, validationError, children } ->
140- maybe identity keyed key $ labeledField
141- { label: text body
142- { children = [ label ]
143- , className = toNullable (pure " field-label" )
144- }
145- , value: intercalate fieldDivider (map toRow children)
146- , validationError: validationError
147- , required: required
148- , forceTopLabel: forceTopLabels
149- , style: R .css {}
150- }
126+ build = build' defaultRenderForm
151127
152- in element (R .unsafeCreateDOMComponent " lumi-form" )
153- { " class" : String .joinWith " " $ fold
154- [ guard inlineTable [" inline-table" ]
155- , guard readonly [" readonly" ]
156- ]
157- , children: surround fieldDivider (map toRow forest)
158- }
128+ -- | Create a React component for a form from a `FormBuilder'` and a custom
129+ -- | rendering function.
130+ -- |
131+ -- | _Note_: this function should be fully applied, to avoid remounting
132+ -- | the component on each render.
133+ build'
134+ :: forall ui renderProps formProps props unvalidated result
135+ . Union renderProps formProps props
136+ => ({ | props } -> ui -> JSX )
137+ -> FormBuilder' ui { | formProps } unvalidated result
138+ -> { value :: unvalidated
139+ , onChange :: (unvalidated -> unvalidated ) -> Effect Unit
140+ | props
141+ }
142+ -> JSX
143+ build' render editor =
144+ makeStateless (createComponent " Form" ) \props@{ value, onChange } ->
145+ let
146+ { edit } = un FormBuilder editor (contractFormProps props) value
147+ in
148+ render (contractProps props) (edit onChange)
149+ where
150+ contractFormProps
151+ :: { value :: unvalidated
152+ , onChange :: (unvalidated -> unvalidated ) -> Effect Unit
153+ | props
154+ }
155+ -> { | formProps }
156+ contractFormProps = unsafeCoerce
157+
158+ contractProps
159+ :: { value :: unvalidated
160+ , onChange :: (unvalidated -> unvalidated ) -> Effect Unit
161+ | props
162+ }
163+ -> { | props }
164+ contractProps = unsafeCoerce
165+
166+ -- | The default Lumi implementation for rendering a forest of JSX
167+ -- | form fields.
168+ defaultRenderForm
169+ :: forall props
170+ . { forceTopLabels :: Boolean
171+ , inlineTable :: Boolean
172+ , readonly :: Boolean
173+ | props
174+ }
175+ -> Forest
176+ -> JSX
177+ defaultRenderForm { inlineTable, forceTopLabels, readonly } forest =
178+ element (R .unsafeCreateDOMComponent " lumi-form" )
179+ { class:
180+ String .joinWith " " $ fold
181+ [ guard inlineTable [" inline-table" ]
182+ , guard readonly [" readonly" ]
183+ ]
184+ , children:
185+ surround fieldDivider (map toRow (Array .mapMaybe pruneTree forest))
186+ }
187+ where
188+ fieldDivider = R .hr { className: " lumi field-divider" }
189+
190+ toRow :: Tree -> JSX
191+ toRow = case _ of
192+ Child { key, child } ->
193+ maybe identity keyed key $ child
194+ Wrapper { key, wrap: f, children } ->
195+ maybe identity keyed key
196+ $ f
197+ $ intercalate [fieldDivider]
198+ $ map (pure <<< toRow)
199+ $ children
200+ Node { label, key, required, validationError, children } ->
201+ maybe identity keyed key $
202+ labeledField
203+ { label: text body
204+ { children = [ label ]
205+ , className = toNullable (pure " field-label" )
206+ }
207+ , value: intercalate fieldDivider (map toRow children)
208+ , validationError
209+ , required
210+ , forceTopLabel: forceTopLabels
211+ , style: R .css {}
212+ }
159213
160214-- | Create an always-valid `FormBuilder` that renders the supplied `JSX`.
161215static :: forall props value . JSX -> FormBuilder props value Unit
@@ -518,7 +572,7 @@ array
518572 -> FormBuilder { readonly :: Boolean | props } (Array u ) (Array a )
519573array { label, addLabel, defaultValue, editor } = FormBuilder \props@{ readonly } xs ->
520574 let editAt i f xs' = fromMaybe xs' (Array .modifyAt i f xs')
521- wrapper children = Array .singleton $ Wrapper { key: Nothing , children }
575+ wrapper children = Array .singleton $ Wrapper { key: Nothing , wrap: R .div_, children }
522576 in { edit: \onChange ->
523577 wrapper $ xs # Array .mapWithIndex (\i x ->
524578 Node
@@ -620,7 +674,7 @@ arrayModal
620674 -> FormBuilder { readonly :: Boolean | props } (Array a ) (Array a )
621675arrayModal { label, addLabel, defaultValue, summary, component, componentProps } = FormBuilder \props@{ readonly } xs ->
622676 let editAt i f xs' = fromMaybe xs' (Array .modifyAt i f xs')
623- wrapper children = Array .singleton $ Wrapper { key: Nothing , children }
677+ wrapper children = Array .singleton $ Wrapper { key: Nothing , wrap: R .div_, children }
624678 in { edit : \onChange ->
625679 wrapper $ xs # Array .mapWithIndex (\i x ->
626680 Node
@@ -766,10 +820,10 @@ initializer loader aff =
766820-- | Caveat emptor, you get what you pay for if you pass in a dodgy
767821-- | `Iso` here.
768822via
769- :: forall props s a result
823+ :: forall ui props s a result
770824 . Iso' s a
771- -> FormBuilder props a result
772- -> FormBuilder props s result
825+ -> FormBuilder' ui props a result
826+ -> FormBuilder' ui props s result
773827via i e = FormBuilder \props s ->
774828 let { edit, validate } = un FormBuilder e props (view i s)
775829 -- TODO: make this point-free
@@ -779,10 +833,10 @@ via i e = FormBuilder \props s ->
779833
780834-- | Focus a `FormBuilder` on a smaller piece of state, using a `Lens`.
781835focus
782- :: forall props s a result
836+ :: forall ui props s a result
783837 . Lens' s a
784- -> FormBuilder props a result
785- -> FormBuilder props s result
838+ -> FormBuilder' ui props a result
839+ -> FormBuilder' ui props s result
786840focus l e = FormBuilder \props s ->
787841 let { edit, validate } = un FormBuilder e props (view l s)
788842 in { edit: \k -> edit (k <<< l)
@@ -792,22 +846,24 @@ focus l e = FormBuilder \props s ->
792846-- | Focus a `FormBuilder` on a possible type of state, using a `Prism`,
793847-- | ignoring validation.
794848match_
795- :: forall props s a
796- . Prism' s a
797- -> FormBuilder props a a
798- -> FormBuilder props s s
849+ :: forall ui props s a
850+ . Monoid ui
851+ => Prism' s a
852+ -> FormBuilder' ui props a a
853+ -> FormBuilder' ui props s s
799854match_ p = match p p
800855
801856-- | Focus a `FormBuilder` on a possible type of state, using a `Prism`.
802857-- |
803858-- | We need two `Prism`s in order to change the result type for
804859-- | validation purposes.
805860match
806- :: forall props result s t a
807- . Prism s s a a
861+ :: forall ui props result s t a
862+ . Monoid ui
863+ => Prism s s a a
808864 -> Prism s t a result
809- -> FormBuilder props a result
810- -> FormBuilder props s t
865+ -> FormBuilder' ui props a result
866+ -> FormBuilder' ui props s t
811867match p1 p2 e = FormBuilder \props s ->
812868 case matching p2 s of
813869 Left t -> { edit: mempty, validate: pure t }
@@ -819,24 +875,48 @@ match p1 p2 e = FormBuilder \props s ->
819875
820876-- | Change the props type.
821877mapProps
822- :: forall p q u a
878+ :: forall ui p q u a
823879 . (q -> p )
824- -> FormBuilder p u a
825- -> FormBuilder q u a
880+ -> FormBuilder' ui p u a
881+ -> FormBuilder' ui q u a
826882mapProps f form = FormBuilder (un FormBuilder form <<< f)
827883
884+ -- | Change the UI type of a form.
885+ mapUI_
886+ :: forall ui ui' props value result
887+ . (ui -> ui' )
888+ -> FormBuilder' ui props value result
889+ -> FormBuilder' ui' props value result
890+ mapUI_ f = mapUI \_ _ _ -> f
891+
892+ -- | Change the UI type of a form based on the props, the current value and the
893+ -- | validated result.
894+ mapUI
895+ :: forall ui ui' props value result
896+ . (props -> value -> Maybe result -> ui -> ui' )
897+ -> FormBuilder' ui props value result
898+ -> FormBuilder' ui' props value result
899+ mapUI f form =
900+ FormBuilder \props value ->
901+ let
902+ { edit, validate } = un FormBuilder form props value
903+ in
904+ { edit: f props value validate <<< edit
905+ , validate
906+ }
907+
828908-- | Make the props available, for convenience.
829909withProps
830- :: forall props unvalidated result
831- . (props -> FormBuilder props unvalidated result )
832- -> FormBuilder props unvalidated result
910+ :: forall ui props unvalidated result
911+ . (props -> FormBuilder' ui props unvalidated result )
912+ -> FormBuilder' ui props unvalidated result
833913withProps f = FormBuilder \props value -> un FormBuilder (f props) props value
834914
835915-- | Make the value available, for convenience.
836916withValue
837- :: forall props unvalidated result
838- . (unvalidated -> FormBuilder props unvalidated result )
839- -> FormBuilder props unvalidated result
917+ :: forall ui props unvalidated result
918+ . (unvalidated -> FormBuilder' ui props unvalidated result )
919+ -> FormBuilder' ui props unvalidated result
840920withValue f = FormBuilder \props value -> un FormBuilder (f value) props value
841921
842922-- | Indent a `Forest` of editors by one level, providing a label.
@@ -859,18 +939,38 @@ indent label required editor = FormBuilder \props val ->
859939 , validate
860940 }
861941
862- -- | Filter parts of the form based on the current value (and the props).
863- filterWithProps
942+ wrap
864943 :: forall props u a
865- . (props -> u -> Boolean )
944+ . (Array JSX -> JSX )
866945 -> FormBuilder props u a
867946 -> FormBuilder props u a
947+ wrap f form =
948+ FormBuilder \props value ->
949+ let
950+ { edit, validate } = un FormBuilder form props value
951+ in
952+ { edit: \k ->
953+ pure $ Wrapper
954+ { key: Nothing
955+ , wrap: f
956+ , children: edit k
957+ }
958+ , validate
959+ }
960+
961+ -- | Filter parts of the form based on the current value (and the props).
962+ filterWithProps
963+ :: forall ui props u a
964+ . Monoid ui
965+ => (props -> u -> Boolean )
966+ -> FormBuilder' ui props u a
967+ -> FormBuilder' ui props u a
868968filterWithProps p editor = FormBuilder \props value ->
869969 let { edit, validate } = un FormBuilder editor props value
870970 in { edit: \onChange ->
871971 if p props value
872972 then edit onChange
873- else []
973+ else mempty
874974 , validate
875975 }
876976
0 commit comments