A Form is an interface containing functions that you can use in your
program to initialise, view, update and submit your form.
Convert a Control into a Form by providing:
Msg variant for updating the formMsg variant for submitting the formControl itselftype alias Msg
= FormUpdated (Delta Int)
| FormSubmitted
myForm =
toForm "My Form" FormUpdated FormSubmitted intA control that produces a Boolean value. Renders as an HTML checkbox???
A control that produces an integer. Renders as an HTML text input.
A control that produces a floating-point number. Renders as an HTML text input.
A control that produces a string. Renders as an HTML text input.
A control that produces a custom type where none of the variants have any payload. Renders as an HTML radio input.
type Colour
= Red
| Green
| Blue
colourControl : Control Colour Colour Colour
colourControl =
enum
( "Red", Red )
[ ( "Green", Green )
, ( "Blue", Blue )
]A combinator that produces a wrapper type around a given type.
type Id
= Id Int
idControl =
wrapper Id (\(Id i) -> i) intA combinator that produces a tuple of two controls of given types.
myTupleControl =
tuple "First" string "Second" intA combinator that produces a Maybe of a control of a given type.
myMaybeControl =
maybe intA combinator that produces a List of controls of a given type.
myListControl =
list intA combinator that produces a Dict from controls of given key and value
types. The key type must be comparable.
myDictControl =
dict "Key" int "Value" stringConfiguration for a custom control. You need to supply the following fields:
initEmpty: a default initial "empty" value for the state of the control. For a control whose state is of type String, this might be "", for a number it might be 0.initWith: a function that initialises the control's state from a value of the control's output type. For a control that outputs an Int, but whose state is a String, this might be String.fromInt.update: a function that updates the state of the control based on its existing state and a delta type that it receives. This is exactly analogous to an Elm program's update function, where state = Model and delta = Msg.view: a function that outputs Html delta based on the state of the control. This is exactly analogous to an Elm program's view function.parse: a function that attempts to parse the control's state into its output type, producing a result. If parsing fails, it can provide a list of errors.Create a simple control, with any arbitrary state and delta types, and
producing any arbitrary output type.
Here's how we could create a control like the famous counter example from the Elm Guide
type CounterDelta
= Increment
| Decrement
counterControl : Control Int CounterDelta Int
counterControl =
create
{ initEmpty = 0
, initWith = identity
, parse = Ok
, update =
\delta state ->
case delta of
Increment ->
state + 1
Decrement ->
state - 1
, view =
\state ->
Html.div []
[ Html.button
[ Html.Attributes.type_ "button"
, Html.Events.onClick Increment
]
[ Html.text "+" ]
, Html.div [] [ Html.text <| String.fromInt state ]
, Html.button
[ Html.Attributes.type_ "button"
, Html.Events.onClick Decrement
]
[ Html.text "-" ]
]
}Display an error on a Control if its output fails to meet the predicate.
int
|> failIf
(\int_ -> int_ < 1)
"Integer must be greater than zero!"You may want display errors on one or more controls based on the validation of another control higher up the tree.
Take the example of a password creation form. You want to validate that the "Enter password" and "Confirm password" fields both contain the same string, and show helpful error messages on both fields if they don't. You can achieve this as follows:
passwordControl =
record
(\password confirmation ->
{ password = password
, confirmation = confirmation
}
)
|> field "Enter password"
(string
|> catchFlag
"flag-passwords-do-not-match"
"Must match 'Confirm password' field"
)
|> field "Confirm password"
(string
|> catchFlag
"flag-passwords-do-not-match"
"Must match 'Enter password' field"
)
|> end
|> throwFlagIf
(\{ password, confirmation } -> password /= confirmation)
"flag-passwords-do-not-match"Throw a "flag" from a Control if its output fails to meet the predicate.
This is meant to be used in combination with catchFlag, which catches the "flag"
and displays an error.
Catch a "flag" thrown by another Control and display an error on this
control.
This is meant to be used in combination with throwFlagIf, which throws the "flag".
Conditionally display an error on one or more items in a list control,
based on the output of the list control.
The following example will display errors on the first two items in a list of strings, if and only if those first two items are "hello" and "world":
myString =
string
|> catchFlag
"flag-hello-world"
"The first two items in the list must not be \"hello\" and \"world\".")
list myString
|> throwFlagsAt
(\list_ ->
case list of
"hello" :: "world" :: _ -> [ 0, 1 ]
_ -> []
)
"flag-hello-world"Initialise a Control using a value of the type that the Control outputs.
tuple "Int" int "String" string
|> initWith ( 1, "hello" )Debounce a Control for a number of milliseconds before it will display
any parsing or validation errors. This can be used to allow the user to finish
typing into a text input before we show them feedback.
string
|> debounce 500A combinator that produces a record type, or any 'product' type that needs to
be constructed from multiple controls. (For example, the tuple combinator in
this library is built using the record combinator).
type alias MyRecord =
{ name : String
, age : Int
}
myRecordControl =
record
(\name age ->
{ name = name
, age = age
}
)
|> field "Name" string
|> field "Age" int
|> endAdd a field to a record combinator.
type alias Hello =
{ hello : String }
helloControl =
record Hello
|> field "Hello" string
|> endAdd a read-only field to a record combinator.
type alias Hello =
{ hello : String }
helloControl =
record Hello
|> readOnlyfield "Hello" string
|> endFinalise the construction of a record or customType combinator.
type alias Hello =
{ hello : String }
helloControl =
record Hello
|> field "Hello" string
|> endChange the layout of the fields of a record control.
type alias MyRecord =
{ name : String
, age : Int
}
myRecordControl =
record
(\name age ->
{ name = name
, age = age
}
)
|> field "Name" string
|> field "Age" int
|> end
|> layout (\fieldViews config -> Html.div [] fieldViews)A combinator that produces a custom type.
type MyCustomType
= NoArgs
| OneArg String
| TwoArgs Int Float
myCustomTypeControl =
customType
(\noArgs oneArg twoArgs tag ->
case tag of
NoArgs ->
noArgs
OneArg arg1 ->
oneArg arg1
TwoArgs arg1 arg2 ->
TwoArgs arg1 arg2
)
|> tag0 "NoArgs" NoArgs
|> tag1 "OneArg" OneArg string
|> tag2 "TwoArgs" TwoArgs int float
|> endAdd a tag with no arguments to a custom type.
type Unit
= Unit
unitControl =
customType
(\unit tag ->
case tag of
Unit ->
unit
)
|> tag0 "Unit"
|> endAdd a tag with one argument to a custom type.
type alias MyResult =
Result String Int
myResultControl =
customType
(\ok err tag ->
case tag of
Ok value ->
ok value
Err error ->
err error
)
|> tag1 "Ok" Ok int
|> tag1 "Err" Err string
|> endAdd a tag with two arguments to a custom type.
type Point
= Point Float Float
pointControl =
customType
(\point tag ->
Point x y ->
point x y
)
|> tag2 Point ("X", float) ("Y", float)Add a tag with three arguments to a custom type.
type Point3D
= Point3D Float Float Float
point3DControl =
customType
(\point3D tag ->
Point3D x y z ->
point3D x y z
)
|> tag3 Point3D ("X", float) ("Y", float) ("Z", float)Add a tag with four arguments to a custom type.
Add a tag with five arguments to a custom type.
These are types that you will see in your form's State and Delta type signatures.
State is the form's equivalent of an Elm program's Modeltype. It is
used to manage the form's internal state.
A Delta is the form's equivalent of an Elm program's Msg type. It is
used internally within a form to update its state.
Special Delta type used only by list controls
Under the hood, this library encodes complex Elm types (such as records and
custom types) as nested tuples, which are used in both the State and Delta
types for each Control. The End type is used to mark the end of a nested
tuple.
For example, the State for this Elm type:
type alias User =
{ name : String, age : Int }Could be encoded as:
( String, ( Int, End ) )A user of this package shouldn't need to know about any of these types - they are only exposed to make it possible to write type signatures.
An internal type used to represent whether a record field is editable, read-only, or hidden
A slightly more flexible version of Control
An internal type used to differentiate between builders for record and custom type combinators
Some internal stuff needed to build up record and custom type controls
Some internal stuff needed to handle validations
Some internal stuff needed to build up a record control
an internal type needed to represent the validation state of a control
Some internal stuff needed to view controls
A
Controlis the basic unit from which forms are composed. This library provides various example controls and combinators that you can use to build a form for any arbitrarily complex Elm type.