Internal.Field.Field error parsed input initial kind constraints
A Field
represents the base information of how to turn a raw input into a parsed value, and how to display that value
as an HTML form field element. Note that you can also perform more advanced validations and mapping using the
Form.Validation
API in your combine
function.
For example, if you want to display a check-in and check-out date field, you would use date
Fields. Using date
does two things:
Sets display options for the browser to display the field with <input type="date">
.
Parses into an Elm value of type Date
(or a validation error if the format isn't invalid).
Why would the date format be invalid if it is managed by the Browser's date picker element? In the happy path on the Browser this will never happen.
However, you can't make any assumptions about the client (non-standard clients could be used), or about the data format that is sent to servers. Often validation logic is duplicated to guard against this on both the client (display useful validation feedback for the user) and the server (validate untrusted input). If you use full-stack Elm, you can use your Form definition on your server to run the same code that you use to present validation errors on the client, allowing you to keep the validations in sync.
Here is an example that showcases different types of Fields (text
, date
, time
, and checkbox
), as well as how to use the combine
function to
transform the parsed values and perform additional validations between the parsed fields.
import Date exposing (Date)
import Form
import Form.Field as Field
import Form.Validation as Validation
type alias Stay =
{ name : String
, checkIn : Checkin
, emailUpdates : Bool
}
type alias Checkin =
{ date : Date
, nights : Int
, time : TimeOfDay
}
example : Form.HtmlForm String Stay input Msg
example =
(\name checkIn checkOut checkInTime emailUpdates ->
{ combine =
Validation.succeed Stay
|> Validation.andMap name
|> Validation.andMap
(Validation.succeed
(\checkinValue checkoutValue checkInTimeValue ->
Validation.succeed
{ date = checkinValue
, nights = Date.toRataDie checkoutValue - Date.toRataDie checkinValue
, time = checkInTimeValue
}
|> Validation.withErrorIf (Date.toRataDie checkinValue >= Date.toRataDie checkoutValue) checkIn "Must be before checkout"
)
|> Validation.andMap checkIn
|> Validation.andMap checkOut
|> Validation.andMap checkInTime
|> Validation.andThen identity
)
|> Validation.andMap emailUpdates
, view =
\formState ->
let
fieldView label field =
Html.div []
[ Html.label []
[ Html.text (label ++ " ")
, FieldView.input [] field
, errorsView formState field
]
]
in
[ fieldView "Name" name
, fieldView "Check-In" checkIn
, fieldView "Check-Out" checkOut
, fieldView "Check-In Time" checkInTime
, fieldView "Sign Up For Email Updates" emailUpdates
, Html.button [] [ Html.text "Submit" ]
]
}
)
|> Form.form
|> Form.field "name"
(Field.text
|> Field.required "Required"
)
|> Form.field "checkin"
(Field.date
{ invalid = \_ -> "Invalid" }
|> Field.required "Required"
|> Field.withMin today ("Must be after " ++ Date.toIsoString today)
)
|> Form.field "checkout"
(Field.date
{ invalid = \_ -> "Invalid" }
|> Field.required "Required"
)
|> Form.field "checkinTime"
(Field.time
{ invalid = \_ -> "Invalid" }
|> Field.required "Required"
|> Field.withMin { hours = 10, minutes = 0 } "Must be after today"
)
|> Form.field "emailUpdates"
Field.checkbox
today : Date
today =
Date.fromRataDie 738624
The key concepts to understand here are where the view and validation logic for fields lives.
The Form
's view
function is responsible for combining the rendered Fields, but the Field
contains the information for how to display
the form field itself (details like like <input type="date">
, <input type="checkbox">
, <textarea>
etc.). Since form fields contain both
display logic that changes how we parse/validate the field, all of the parsing and validation logic related to displaying the
Field is also defined with Field
type. For example, Field.withMin
contains information that is used both for displaying the form field and for
validating it. When we set Field.withMin
, it gives the Browser information on how to restrict the date picker UI from showing
invalid dates, but setting that minimum value also runs that validation when we run the Form parser, even if we run it
on a server through code sharing.
Note that the validations in a Field
s definition (such as Field.withMin
, or Field.date
, etc.) are run
regardless of whether you use that field in the Form's combine
function.
text : Field error (Maybe String) input String Form.FieldView.Input { required : (), plainText : (), wasMapped : No, minlength : (), maxlength : () }
The base for a text field. You can add display modifiers to text fields, including displaying them as a textarea
.
See Text Field Display Options.
By default, text fields are not required. If the field is not touched or has been deleted, the value will be Nothing
(not empty string Just ""
). See required
.
import Form.Field as Field
type alias Profile =
{ status : Maybe String
}
example =
(\username ->
{ combine =
Validation.succeed Status
|> Validation.andMap username
, view = []
}
)
|> Form.form
|> Form.field "status" Field.text
checkbox : Field error Basics.Bool input Basics.Bool Form.FieldView.Input { required : () }
Renders a checkbox input (<input type="checkbox">
), see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/checkbox.
import Form.Field as Field
example =
Field.checkbox
int : { invalid : String -> error } -> Field error (Maybe Basics.Int) input Basics.Int Form.FieldView.Input { min : Basics.Int, max : Basics.Int, required : (), wasMapped : No, step : Basics.Int }
Renders a number input (<input type="number">
), see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/number.
Floating point numbers will give a validation error, using the error value passed in through the invalid
function.
import Form.Field as Field
example =
Field.number
{ invalid =
\value -> "Must be an integer"
}
float : { invalid : String -> error } -> Field error (Maybe Basics.Float) input Basics.Float Form.FieldView.Input { min : Basics.Float, max : Basics.Float, required : (), wasMapped : No }
A number input (<input type="number">
), see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/number.
Unlike int
, this field allows floating point numbers.
It will give a validation error if the input is not a number, using the error value passed in through the invalid
function.
import Form.Field as Field
example =
Field.number
{ invalid =
\value -> "Must be a number"
}
select : List ( String, option ) -> (String -> error) -> Field error (Maybe option) input option (Internal.Input.Options option) { required : (), wasMapped : No }
An input for a set of possible options. Can be rendered in two ways
<select>
)Form.FieldView.select
<input type="radio">
)Form.FieldView.radio
.import Form
import Form.Field as Field
import Form.FieldView as FieldView
import Form.Validation as Validation
sizeForm : Form.HtmlForm String Size input msg
sizeForm =
(\size ->
{ combine =
Validation.succeed identity
|> Validation.andMap size
, view =
\formState ->
[ Html.div []
[ FieldView.select []
(\entry -> ( [], sizeToString entry ))
size
]
, Html.button [] [ Html.text "Submit" ]
]
}
)
|> Form.form
|> Form.field "size"
(Field.select
[ ( "small", Small )
, ( "medium", Medium )
, ( "large", Large )
]
(\_ -> "Invalid")
|> Field.required "Required"
|> Field.withInitialValue (\_ -> Small)
)
sizeToString : Size -> String
sizeToString size =
case size of
Small ->
"Small"
Medium ->
"Medium"
Large ->
"Large"
type Size
= Small
| Medium
| Large
Used for errors from a range
Field.
date : { invalid : String -> error } -> Field error (Maybe Date) input Date Form.FieldView.Input { min : Date, max : Date, required : (), wasMapped : No, step : Basics.Int }
A date field. Parses into a value of type Date
.
example =
Field.date
{ invalid = \_ -> "Invalid date" }
|> Field.required "Required"
|> Field.withMin (Date.fromRataDie 738624) "Must be after today"
-- date picker will show dates on the same day of the week starting from the start date
|> Field.withStep 7
time : { invalid : String -> error } -> Field error (Maybe TimeOfDay) input TimeOfDay Form.FieldView.Input { min : TimeOfDay, max : TimeOfDay, required : (), wasMapped : No, step : Basics.Int }
https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/time
{ hours : Basics.Int
, minutes : Basics.Int
, seconds : Maybe Basics.Int
}
A time of day in 24-hour time.
The hours must be between 0 and 23, and the minutes must be between 0 and 59.
This is the type that a time
field parses into, and is also used to set initial values and minimum/maximum values for time
.
See https://developer.mozilla.org/en-US/docs/Web/HTML/Date_and_time_formats#time_strings
withInitialValue : (input -> initial) -> Field error value input initial kind constraints -> Field error value input initial kind constraints
Set an initial value for the Field
given the Form
's input
(see Form.withInput
).
This allows you to pass in dynamic state like values from your Model
.
The initial value will be used until the field is modified by the user, and from there it is controlled by user input. If you
need to programmatically set a field's value for more advanced use cases, you can also modify the Form.Model
.
The type you use to set the initial value depends on the Field. For example, you can set a checkbox
Field's initial value
with a Bool
example =
Form.checkbox |> Form.withInitialValue .autoplay
formOptions : { autoplay : Bool } -> Form.Options String parsed { autoplay : Bool } msg
formOptions currentSettings =
Form.options "settings"
|> Form.withInput currentSettings
Note that the type used to set the initial value is independent of types you might map
a field into.
withOptionalInitialValue : (input -> Maybe initial) -> Field error value input initial kind constraints -> Field error value input initial kind constraints
Similar to withInitialValue
, but takes in a Maybe
value. If the Maybe
is Nothing
then it's
the same as if no initial value were set.
example =
Form.text |> Form.withOptionalInitialValue .nickname
formOptions : { nickname : Maybe String } -> Form.Options String parsed { nickname : Maybe String } msg
formOptions currentProfile =
Form.options "profile"
|> Form.withInput currentProfile
exactValue : String -> error -> Field error String input Basics.Never Form.FieldView.Input { required : (), plainText : (), wasMapped : No }
Render a field with a hardcoded value.
required : error -> Field error (Maybe parsed) kind input initial { constraints | required : (), wasMapped : No } -> Field error parsed kind input initial { constraints | wasMapped : No }
Gives a validation error for fields that haven't been set, and removes the Maybe
around the parsed value.
example =
Field.int { invalid = \_ -> "Invalid" }
-- parses into `Maybe Int` before we call `required`
-- after `required`, it parses into an `Int`
|> Field.required "Required"
validateMap : (parsed -> Result error mapped) -> Field error parsed input initial kind constraints -> Field error mapped input initial kind { constraints | wasMapped : Yes }
Add a custom validation and/or transformation of the value to the field.
import Form.Field as Field
example =
Field.text
|> Field.required "Required"
|> Field.validateMap Username.fromString
-- in Username.elm
fromString : String -> Result String Username
fromString string =
if string |> String.contains "@" then
Err "Must not contain @"
else
Username string |> Ok
map : (parsed -> mapped) -> Field error parsed input initial kind constraints -> Field error mapped input initial kind { constraints | wasMapped : Yes }
Map the parsed value of a Field without adding or modifying its validations or rendering.
import Form.Field as Field
example =
Field.text
|> Field.required "Required"
|> Field.map String.toUpper
email : Field error parsed input initial Form.FieldView.Input { constraints | plainText : () } -> Field error parsed input initial Form.FieldView.Input constraints
Modifier for text
Field. This does not perform any additional validations on the Field, it only provides a hint to the browser
that the Field should be displayed as an email input (<input type="email">
). This is especially useful for mobile devices to make sure
the correct keyboard is displayed.
See https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/email.
example =
Field.text
|> Field.email
|> Field.required "Email is required"
password : Field error parsed input initial Form.FieldView.Input { constraints | plainText : () } -> Field error parsed input initial Form.FieldView.Input constraints
Modifier for text
Field. This is only a display hint to the browser that the Field should be displayed as a password input (<input type="password">
).
See https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/password.
example =
Field.text
|> Field.password
|> Field.required "Password is required"
search : Field error parsed input initial Form.FieldView.Input { constraints | plainText : () } -> Field error parsed input initial Form.FieldView.Input constraints
Modifier for text
Field. This changes the display of the Field to a password input (<input type="search">
).
On mobile devices, this will display a keyboard with a search button.
See https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/search.
example =
Field.text
|> Field.search
telephone : Field error parsed input initial Form.FieldView.Input { constraints | plainText : () } -> Field error parsed input initial Form.FieldView.Input constraints
Modifier for text
Field. This is only a display hint to the browser (<input type="tel">
).
This is especially important on mobile devices for ensuring that the correct keyboard is displayed for inputting a phone number.
See https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/tel.
example =
Field.text
|> Field.telephone
url : Field error parsed input initial Form.FieldView.Input { constraints | plainText : () } -> Field error parsed input initial Form.FieldView.Input constraints
Modifier for text
Field. This does not perform any additional validations on the Field, it only provides a hint to the browser
that the Field should be displayed as a URL input (<input type="url">
).
See https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/url.
example =
Field.text
|> Field.url
|> Field.required "URL is required"
textarea : { rows : Maybe Basics.Int, cols : Maybe Basics.Int } -> Field error parsed input initial Form.FieldView.Input { constraints | plainText : () } -> Field error parsed input initial Form.FieldView.Input constraints
Modifier for text
Field to display it as a textarea
.
textarea
are for multi-line text input. For example, you might use a regular text
Field for a username, and a textarea
Field for a biography.
import Form.Field as Field
type alias Profile =
{ username : String
, bio : String
}
example =
(\username bio ->
{ combine =
Validation.succeed Profile
|> Validation.andMap username
|> Validation.andMap bio
, view = []
}
)
|> Form.form
|> Form.field "username"
(Field.text
|> Field.required "Required"
)
|> Form.field "bio"
(Field.text
|> Field.textarea
{ rows = Just 20
, cols = Just 50
}
|> Field.required "Required"
)
range : { min : numberInitial, max : numberInitial, missing : error, invalid : OutsideRange -> error } -> Field error (Maybe valueType) input numberInitial kind { constraints | required : (), min : numberInitial, max : numberInitial, wasMapped : No } -> Field error valueType input numberInitial Form.FieldView.Input { constraints | wasMapped : No }
Display a range input (<input type="range">
). See https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/range.
import Form.Field as Field
type alias Settings =
{ brightness : Int
}
example =
(\brightness ->
{ combine =
Validation.succeed Settings
|> Validation.andMap brightness
, view = []
}
)
|> Form.form
|> Form.field "brightness"
(Field.range
{ min = 0
, max = 100
, missing = "Required"
, invalid =
\outsideRange ->
case outsideRange of
Field.AboveRange ->
"Must be below 100"
Field.BelowRange ->
"Must be above 0"
}
(Field.int { invalid = \_ -> "Invalid" })
)
Can be used with either int
or float
.
withMin : initial -> error -> Field error parsed input initial kind { constraints | min : initial } -> Field error parsed input initial kind constraints
Set the min value for the Field. This results in both a validation (run on the server as well as the client) as well as a display hint to the browser
(<input type="date" min="2023-04-14">
). The Browser will prevent the user from entering a value below the min value in some cases but not all.
See https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/min.
If the value is invalid (below the minimum), the error will be whichever error is passed in as the second argument.
import Date exposing (Date)
import Form.Field as Field
example =
Field.date
{ invalid = \_ -> "Must be valid date" }
|> Field.required "Required"
|> Field.withMin today ("Must be after " ++ Date.toIsoString today)
today : Date
today =
Date.fromRataDie 738624
withMax : initial -> error -> Field error parsed input initial kind { constraints | max : initial } -> Field error parsed input initial kind constraints
Same as withMin
but for a maximum value. See https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/max.
import Date exposing (Date)
import Form.Field as Field
example =
Field.date
{ invalid = \_ -> "Must be valid date" }
|> Field.required "Required"
|> Field.withMax today "Cannot schedule more than 7 days in advance"
inAWeek : Date
inAWeek =
Date.fromRataDie (today + 7)
today : Int
today =
738624
withMinLength : Basics.Int -> error -> Field error parsed input initial kind { constraints | minlength : () } -> Field error parsed input initial kind constraints
Set a minimum length for the string. See https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/minlength.
withMaxLength : Basics.Int -> error -> Field error parsed input initial kind { constraints | maxlength : () } -> Field error parsed input initial kind constraints
Set a maximum length for the string. See https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/maxlength.
withStep : Basics.Int -> Field error value input initial view { constraints | step : Basics.Int } -> Field error value input initial view constraints
Sets the step
attribute on the form field for Field's with Int
steps. See https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/step.
For int
fields, the step will change the up/down buttons in the field's UI to increment/decrement by the given step.
Int
step values have different meanings for different kinds of Fields.
Used in the constraints for a Field. These can't be built or used outside of the API, they are only used as guardrails
to ensure sure that Fields are configured correctly.
Used in the constraints for a Field. These can't be built or used outside of the API, they are only used as guardrails to ensure sure that Fields are configured correctly.