@@ -3,6 +3,8 @@ module Lumi.Components.Form
3
3
, module Internal
4
4
, module Validation
5
5
, build
6
+ , build'
7
+ , defaultRenderForm
6
8
, static
7
9
, section
8
10
, inputBox
@@ -36,7 +38,10 @@ module Lumi.Components.Form
36
38
, withProps
37
39
, withValue
38
40
, mapProps
41
+ , mapUI
42
+ , mapUI_
39
43
, indent
44
+ , wrap
40
45
, filterWithProps
41
46
, withKey
42
47
, styles
@@ -66,8 +71,8 @@ import Lumi.Components.Color (colors)
66
71
import Lumi.Components.Column (column )
67
72
import Lumi.Components.FetchCache as FetchCache
68
73
import 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
71
76
import Lumi.Components.Form.Validation (Validated (..), Validator , _Validated , fromValidated , mustBe , mustEqual , nonEmpty , nonEmptyArray , nonNull , validNumber , validInt , validDate , optional , setFresh , setModified , validated , warn ) as Validation
72
77
import Lumi.Components.Input (alignToInput )
73
78
import Lumi.Components.Input as Input
@@ -94,68 +99,117 @@ import Unsafe.Coerce (unsafeCoerce)
94
99
-- |
95
100
-- | _Note_: this function should be fully applied, to avoid remounting
96
101
-- | the component on each render.
102
+
97
103
build
98
104
:: 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
100
118
-> { value :: unvalidated
101
119
, onChange :: (unvalidated -> unvalidated ) -> Effect Unit
102
- , inlineTable :: Boolean
103
120
, forceTopLabels :: Boolean
121
+ , inlineTable :: Boolean
104
122
, readonly :: Boolean
105
123
| props
106
124
}
107
125
-> 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
151
127
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
+ }
159
213
160
214
-- | Create an always-valid `FormBuilder` that renders the supplied `JSX`.
161
215
static :: forall props value . JSX -> FormBuilder props value Unit
@@ -518,7 +572,7 @@ array
518
572
-> FormBuilder { readonly :: Boolean | props } (Array u ) (Array a )
519
573
array { label, addLabel, defaultValue, editor } = FormBuilder \props@{ readonly } xs ->
520
574
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 }
522
576
in { edit: \onChange ->
523
577
wrapper $ xs # Array .mapWithIndex (\i x ->
524
578
Node
@@ -620,7 +674,7 @@ arrayModal
620
674
-> FormBuilder { readonly :: Boolean | props } (Array a ) (Array a )
621
675
arrayModal { label, addLabel, defaultValue, summary, component, componentProps } = FormBuilder \props@{ readonly } xs ->
622
676
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 }
624
678
in { edit : \onChange ->
625
679
wrapper $ xs # Array .mapWithIndex (\i x ->
626
680
Node
@@ -766,10 +820,10 @@ initializer loader aff =
766
820
-- | Caveat emptor, you get what you pay for if you pass in a dodgy
767
821
-- | `Iso` here.
768
822
via
769
- :: forall props s a result
823
+ :: forall ui props s a result
770
824
. 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
773
827
via i e = FormBuilder \props s ->
774
828
let { edit, validate } = un FormBuilder e props (view i s)
775
829
-- TODO: make this point-free
@@ -779,10 +833,10 @@ via i e = FormBuilder \props s ->
779
833
780
834
-- | Focus a `FormBuilder` on a smaller piece of state, using a `Lens`.
781
835
focus
782
- :: forall props s a result
836
+ :: forall ui props s a result
783
837
. 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
786
840
focus l e = FormBuilder \props s ->
787
841
let { edit, validate } = un FormBuilder e props (view l s)
788
842
in { edit: \k -> edit (k <<< l)
@@ -792,22 +846,24 @@ focus l e = FormBuilder \props s ->
792
846
-- | Focus a `FormBuilder` on a possible type of state, using a `Prism`,
793
847
-- | ignoring validation.
794
848
match_
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
799
854
match_ p = match p p
800
855
801
856
-- | Focus a `FormBuilder` on a possible type of state, using a `Prism`.
802
857
-- |
803
858
-- | We need two `Prism`s in order to change the result type for
804
859
-- | validation purposes.
805
860
match
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
808
864
-> 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
811
867
match p1 p2 e = FormBuilder \props s ->
812
868
case matching p2 s of
813
869
Left t -> { edit: mempty, validate: pure t }
@@ -819,24 +875,48 @@ match p1 p2 e = FormBuilder \props s ->
819
875
820
876
-- | Change the props type.
821
877
mapProps
822
- :: forall p q u a
878
+ :: forall ui p q u a
823
879
. (q -> p )
824
- -> FormBuilder p u a
825
- -> FormBuilder q u a
880
+ -> FormBuilder' ui p u a
881
+ -> FormBuilder' ui q u a
826
882
mapProps f form = FormBuilder (un FormBuilder form <<< f)
827
883
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
+
828
908
-- | Make the props available, for convenience.
829
909
withProps
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
833
913
withProps f = FormBuilder \props value -> un FormBuilder (f props) props value
834
914
835
915
-- | Make the value available, for convenience.
836
916
withValue
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
840
920
withValue f = FormBuilder \props value -> un FormBuilder (f value) props value
841
921
842
922
-- | Indent a `Forest` of editors by one level, providing a label.
@@ -859,18 +939,38 @@ indent label required editor = FormBuilder \props val ->
859
939
, validate
860
940
}
861
941
862
- -- | Filter parts of the form based on the current value (and the props).
863
- filterWithProps
942
+ wrap
864
943
:: forall props u a
865
- . (props -> u -> Boolean )
944
+ . (Array JSX -> JSX )
866
945
-> FormBuilder props u a
867
946
-> 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
868
968
filterWithProps p editor = FormBuilder \props value ->
869
969
let { edit, validate } = un FormBuilder editor props value
870
970
in { edit: \onChange ->
871
971
if p props value
872
972
then edit onChange
873
- else []
973
+ else mempty
874
974
, validate
875
975
}
876
976
0 commit comments