Useful things to build a form.
A form is defined by the configuration (R10.Form.Conf
) that contain data about the input fields, radio buttons, etc. and the state (R10.Form.State
), containing instead all the values and other parameters that change during the life of the form.
type alias Form =
{ conf : R10.Form.Conf
, state : R10.Form.State
}
Internal.Shared.Form
There are four functions to render a Form
, each with a different degree of customization. They are order from the simplest (less customizable) to the most complex (more customizable).
view : Form -> MsgMapper msg -> List (Element.WithContext.Element (R10.Context.ContextInternal z) msg)
This is the simplest way to render a form, as you can see in this minimalistic example:
module Main exposing (main)
import Element.WithContext exposing (..)
import Html
import R10.Form
import R10.FormTypes
formModel : R10.Form.Form
formModel =
{ conf =
[ R10.Form.entity.field
{ id = "email"
, idDom = Nothing
, type_ = R10.FormTypes.TypeText R10.FormTypes.TextEmail
, label = "Email"
, helperText = Just "My first form"
, requiredLabel = Nothing
, validationSpecs = Nothing
}
]
, state = R10.Form.initState
}
formMsgMapper : R10.Form.MsgMapper ()
formMsgMapper =
\_ -> ()
main : Html.Html ()
main =
layout [] <|
column [ centerX, centerY ] <|
R10.Form.view formModel formMsgMapper
Note that the form of this example is not active because it is just rendering the view but it is not wired to The Elm Architecture. If interested, look at the code of an active form example.
viewWithTheme : Form -> MsgMapper msg -> R10.Theme.Theme -> List (Element.WithContext.Element (R10.Context.ContextInternal z) msg)
Use this version if you have R10.Theme.Theme
.
viewWithPalette : Form -> MsgMapper msg -> R10.Palette.Palette -> List (Element.WithContext.Element (R10.Context.ContextInternal z) msg)
Use this version if you have a specific palette that you want to use.
viewWithOptions : Form -> MsgMapper msg -> Options z -> List (Element.WithContext.Element (R10.Context.ContextInternal z) msg)
Use this version for full control.
{ maker : Maybe ({ key : Key
, formState : State
, translator : Translator
, style : Style
, palette : R10.Palette.Palette } -> Conf -> List (Element.WithContext.Element (R10.Context.ContextInternal z) Msg))
, translator : Maybe Translator
, style : Style
, palette : Maybe R10.Palette.Palette
}
These are the options that you can use with viewWithOptions
.
Internal.Msg.Msg
Msg -> msg
This function required by all views is used to convert specific form messages (Msg
) into generic messages (msg
). Tipically you would define this function like this in your application:
type Msg
= MsgForm R10.Form.Internal.Msg
MsgForm
can now be used as MsgMapper
that convert R10.Form.Internal.Msg
into Msg
.
For a code example have a look at this simple form.
isChangingValues : Msg -> Basics.Bool
Return true if the message can potentially change some value
isFormSubmittable : Form -> Basics.Bool
This function return True
in one of these two cases
This function is normally used to disble or not the submit button.
isFormSubmittableAndSubmitted : Form -> Msg -> Basics.Bool
This function can be used to do some processing before the form get submitted.
It returns True
when the form is submittable and the message is Submit
. To be used in the update
function.
msg : { submit : Conf -> Msg, getFocus : Key -> FieldConf -> Msg }
State
is the equivalent of the Model
in a TEA structure.
It contains the values and the other things that ususally change while the user is interacting with the form.
Internal.State.State
The state is defined as
type alias State =
{ fieldsState : Dict KeyAsString FieldState
, multiplicableQuantities : Dict KeyAsString Int
, activeTabs : Dict KeyAsString String
, focused : Maybe KeyAsString
, active : Maybe KeyAsString
, removed : Set KeyAsString
, qtySubmitAttempted : QtySubmitAttempted
, changesSinceLastSubmissions : Bool
}
Where:
fieldState
is a dictionary containing the states of all fields.multiplicableQuantities
is a dictionary containin the quantity of a multiplicable field. A multiplicable field is a field that the user can clone to add extra information.activeTabs
describe which tab is active in case tabs are used to group objects of the form.focused
is the focused element of the form at the present moment.active
... what is active
?removed
contains multiplicable input fields that have been removed by the user.qtySubmitAttempted
is the number of times that the user tried to submit the form.changesSinceLastSubmissions
is True
if the user changed something after the last for submission.initState : State
initStateWithDefaults : Conf -> Maybe R10.Country.Country -> State
stateToString : State -> String
This can be useful to store the state of a form to the Local Storage, for example. Then, using stringToState
is possible to restore all the values of a form or keep the form sync'ed on different tabs.
stringToState : String -> Result Json.Decode.Error State
This is the update
function to wire the form in your existing TEA
structure.
It consider the State
of the form as the Model
in a standard TEA
structure, as usually is the State
that change while the user interact with the form.
But, differently from the standard TEA
, the view function of this form library also require the configuration of the form.
This means that if you want to go crazy and also change the configuration, you can do it. For example you could change the validations on the flight while user is changing a value. Or you can add new input fields, etc. See fieldConfigConcatMap
for this type of usage.
update : (String -> String -> String) -> Msg -> State -> ( State, Platform.Cmd.Cmd Msg )
Internal.Conf.Conf
The configuration of a form (Conf
) is simply defined as a list of entities (List Entity
).
initConf : Conf
confToString : Conf -> String
stringToConf : String -> Result Json.Decode.Error Conf
changeFieldConf : (FieldConf -> FieldConf) -> Entity -> Entity
Internal.Conf.Entity
A form is made of multiple entities. An example of entity is an input field, a title, a subtitle, etc.
entity : { field : FieldConf -> Entity, multi : EntityId -> List Entity -> Entity, normal : EntityId -> List Entity -> Entity, subTitle : EntityId -> TextConf -> Entity, title : EntityId -> TextConf -> Entity, withBorder : EntityId -> List Entity -> Entity, withTabs : EntityId -> List ( String, Entity ) -> Entity, wrappable : EntityId -> List Entity -> Entity }
These are entities constructors
Internal.Conf.EntityId
String
Internal.Conf.TextConf
type alias TextConf =
{ title : String
, helperText : Maybe String
, validationSpecs : Maybe ValidationSpecs
}
Singular fields, similarly to forms, have their own Configuration and State.
Internal.FieldConf.FieldConf
type alias FieldConf =
{ id : FieldId
, idDom : Maybe String
, type_ : R10.FormTypes.FieldType
, label : String
, helperText : Maybe String
, requiredLabel : Maybe String
, validationSpecs : Maybe ValidationSpecs
}
initFieldConf : FieldConf
Internal.FieldState.FieldState
type alias FieldState =
{ lostFocusOneOrMoreTime : Bool
, value : String
, search : String
, select : String
, scroll : Float
, dirty : Bool
, disabled : Bool
, validation : Validation
, showPassword : Bool -- Used only for passwords
}
initFieldState : FieldState
Internal.Key.Key
Key
is internally defined as a list of strings.
The form is rapresented internally as a tree and the Key
is the path to reach a node.
Internal.Key.KeyAsString
keyToString : Key -> KeyAsString
stringToKey : KeyAsString -> Key
composeKey : Key -> String -> Key
listToKey : List String -> Key
headId : Key -> String
emptyKey : Key
Get
updateField : KeyAsString -> (FieldState -> FieldState) -> State -> State
getField : KeyAsString -> State -> Maybe FieldState
getFieldValue : KeyAsString -> State -> Maybe String
getFieldValueIgnoringPath : KeyAsString -> State -> Maybe String
getFieldValueAsBool : KeyAsString -> State -> Maybe Basics.Bool
getActiveTab : KeyAsString -> State -> Maybe String
setFieldValue : KeyAsString -> String -> State -> State
setActiveTab : KeyAsString -> String -> State -> State
setMultiplicableQuantities : KeyAsString -> Basics.Int -> State -> State
stringToBool : String -> Basics.Bool
boolToString : Basics.Bool -> String
commonRegularExpression : { alpha : String, alphaNumeric : String, alphaNumericDash : String, alphaNumericDashSpace : String, phoneNumber : String, email : String, numeric : String, integer : String, decimal : String, url : String, hexColor : String }
msgToValue : Msg -> Maybe String
stateToValue : State -> Json.Encode.Value
viewIconButton : List (Element.WithContext.Attribute (R10.Context.ContextInternal z) msg) -> ArgsIconButton z msg -> Element.WithContext.Element (R10.Context.ContextInternal z) msg
{ icon : Element.WithContext.Element (R10.Context.ContextInternal z) msg
, msgOnClick : Maybe msg
, palette : R10.Palette.Palette
, size : Basics.Int
}
viewButton : List (Element.WithContext.Attribute (R10.Context.ContextInternal z) msg) -> ArgsButton z msg -> Element.WithContext.Element (R10.Context.ContextInternal z) msg
R10.FormComponents.Internal.Button.Args z msg
viewText : List (Element.WithContext.Attribute (R10.Context.ContextInternal z) msg) -> List (Element.WithContext.Attribute (R10.Context.ContextInternal z) msg) -> ArgsText z msg -> Element.WithContext.Element (R10.Context.ContextInternal z) msg
processValue : { pattern : String, previousValue : String, value : String } -> String
R10.FormComponents.Internal.Text.Args z msg
R10.FormComponents.Internal.Text.Args z msg
viewBinary : List (Element.WithContext.Attribute (R10.Context.ContextInternal z) msg) -> ArgsBinary msg -> Element.WithContext.Element (R10.Context.ContextInternal z) msg
R10.FormComponents.Internal.Binary.Args msg
R10.FormComponents.Internal.Style.Style
style : { fixedLabels : Style, floatingLabels : Style }
defaultStyle : Style
styleToString : Style -> String
stringToStyle : String -> Style
R10.FormComponents.Internal.Button.Button
button : { contained : Button, outlined : Button, text : Button, icon : Button }
themeToPalette : R10.Theme.Theme -> R10.Palette.Palette
label : R10.Palette.Palette -> Element.WithContext.Color
extraCssComponents : R10.Palette.Palette -> String
extraCss : Maybe R10.Palette.Palette -> String
colorToCssString : Element.WithContext.Color -> String
onClickWithStopPropagation : msg -> Element.WithContext.Attribute (R10.Context.ContextInternal z) msg
onFocusOut : String -> msg -> Json.Decode.Decoder msg
onLoseFocus : (Key -> FieldConf -> a) -> Msg -> Maybe a
onValueChange : (Key -> FieldConf -> Conf -> String -> a) -> Msg -> Maybe a
onOptionSelect : (Key -> FieldConf -> Conf -> String -> a) -> Msg -> Maybe a
elementMarkdown : String -> List (Element.WithContext.Element (R10.Context.ContextInternal z) msg)
If you want to personalise the translations or you want to translate them in different languages, you can do so defining a function like
translator : Key -> ValidationCode -> String
translator key validationCode =
Dict.fromList
[ ( validationCodes.emailFormatInvalid
, "Invalid email format"
)
, ( validationCodes.emailFormatValid
, "Valid email format"
)
...
, ( validationCodes.oneOf
, "All of the validations have failed"
)
]
|> Dict.get validationCode
|> Maybe.withDefault validationCode
Internal.Translator.Translator
defaultTranslator : Translator
validationCodes : { emailFormatInvalid : ValidationCode, emailFormatValid : ValidationCode, equalInvalid : ValidationCode, formatInvalid : ValidationCode, formatInvalidCharactersInvalid : ValidationCode, formatNoNumberInvalid : ValidationCode, formatNoSpecialCharactersInvalid : ValidationCode, formatNoUppercaseInvalid : ValidationCode, formatValid : ValidationCode, hexColorFormatInvalid : ValidationCode, jsonFormatInvalid : ValidationCode, lengthTooLargeInvalid : ValidationCode, lengthTooSmallInvalid : ValidationCode, lengthExactInvalid : ValidationCode, lengthValid : ValidationCode, required : ValidationCode, empty : ValidationCode, requiredField : ValidationCode, somethingWrong : ValidationCode, valueInvalid : ValidationCode, allOf : ValidationCode, oneOf : ValidationCode, mobileEmailFormatInvalid : ValidationCode, mobileEmailDomainInvalid : ValidationCode }
R10.FormComponents.Internal.Single.Common.Model
R10.FormComponents.Internal.Single.Common.Msg
R10.FormComponents.Internal.Single.Common.FieldOption
defaultSearchFn : String -> SingleFieldOption -> Basics.Bool
initSingle : SingleModel
normalizeString : String -> String
insertBold : List Basics.Int -> String -> String
defaultToOptionEl : { a | msgOnSelect : String -> msg, search : String } -> SingleFieldOption -> Element.WithContext.Element (R10.Context.ContextInternal z) msg
defaultTrailingIcon : { a | opened : Basics.Bool, palette : R10.Palette.Palette } -> Element.WithContext.Element (R10.Context.ContextInternal z) msg
singleMsg : { onOptionSelect : String -> SingleMsg }
updateSingle : SingleMsg -> SingleModel -> ( SingleModel, Platform.Cmd.Cmd SingleMsg )
viewSingle : List (Element.WithContext.Attribute (R10.Context.ContextInternal z) msg) -> SingleModel -> ArgsSingle msg -> Element.WithContext.Element (R10.Context.ContextInternal z) msg
R10.FormComponents.Internal.Single.Args msg
viewSingleCustom : List (Element.WithContext.Attribute (R10.Context.ContextInternal z) msg) -> SingleModel -> ArgsSingleCustom z msg -> Element.WithContext.Element (R10.Context.ContextInternal z) msg
R10.FormComponents.Internal.Single.ArgsCustom z msg
validate : { formStateBeforeValidationFixer : String -> String -> String, showAlsoPassedValidation : Basics.Bool, key : Internal.Key.Key, fieldType : R10.FormTypes.FieldType, maybeValidationSpec : Maybe Internal.FieldConf.ValidationSpecs, formState : Internal.State.State } -> FieldState -> FieldState
validation : { allOf : List Validation -> Validation, dependant : KeyAsString -> Validation -> Validation, equal : KeyAsString -> Validation, maxLength : Basics.Int -> Validation, minLength : Basics.Int -> Validation, noValidation : Validation, oneOf : List Validation -> Validation, regex : String -> Validation, required : Validation, withMsg : ValidationMessage -> Validation -> Validation, empty : Validation, not : Validation -> Validation, dateRange : Range -> Validation }
Internal.FieldConf.Validation
Internal.FieldConf.ValidationCode
Just a String
Internal.FieldConf.ValidationSpecs
R10.FormComponents.Internal.Validations.ValidationForView
Internal.FieldConf.ValidationMessage
validateDirtyFormFields : (String -> String -> String) -> Form -> State
validateEntireForm : (String -> String -> String) -> Form -> State
validationMessage : { error : String -> R10.FormComponents.Internal.Validations.ValidationMessage, ok : String -> R10.FormComponents.Internal.Validations.ValidationMessage }
validationToString : ValidationForView -> String
shouldShowTheValidationOverview : State -> Basics.Bool
allValidationKeysMaker : Form -> List ( Key, R10.FormTypes.FieldType, Maybe ValidationSpecs )
runOnlyExistingValidations : (String -> String -> String) -> List ( Key, R10.FormTypes.FieldType, Maybe ValidationSpecs ) -> State -> Dict KeyAsString FieldState -> Dict KeyAsString FieldState
commonValidation : { alphaNumericDash : Validation, alphaNumericDashSpace : Validation, email : Validation, hexColor : Validation, numeric : Validation, password : Validation, phoneNumber : Validation, url : Validation }
clearFieldValidation : KeyAsString -> State -> State
componentValidation : { pretendIsNotYetValidated : ValidationForView, validated : List R10.FormComponents.Internal.Validations.ValidationMessage -> ValidationForView }
initValidationSpecs : ValidationSpecs
isExistingFormFieldsValid : Form -> Basics.Bool
setFieldValidationError : KeyAsString -> String -> State -> State
entitiesValidationOutcomes : Form -> Maybe Translator -> List ( Key, R10.FormTypes.FieldType, ValidationForView )
isRegexValidation : Validation -> Basics.Bool
entitiesWithErrors : Form -> List ( Key, R10.FormTypes.FieldType, Maybe ValidationSpecs )
fieldConfigConcatMap : (FieldConf -> List FieldConf) -> Conf -> Conf
Utility that can be used to change the configuration of a form on the fly. It can be useful if certain state value of the form can influence some of the validation of the form itself.
For example a Japanese address field can require to have at least one digit in it (called "Banchi", a building number). But some address may be missing this number so we give the user to opt-out from a building number requirement if the click on a check box.
fieldConfigMap : (FieldConf -> a) -> Conf -> List a
Utility that can be used to iterate through the entire form configuration and returns a list of results
handleWithPatternChange : { pattern : String, oldValue : String, newValue : String } -> String
errorsFromApiToValidationForView : Translator -> List { a | field : String, reasonCode : String } -> List ( Key, ValidationForView )
validationForViewToErrorMessages : List ( Key, ValidationForView ) -> List String