rakutentech / r10 / R10.Form

Useful things to build a form.

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
    }


type alias Form =
Internal.Shared.Form

Views

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.


type alias Options z =
{ 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.

Messages


type alias Msg =
Internal.Msg.Msg


type alias MsgMapper 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.

Messages helpers

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

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.


type alias State =
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:

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

Update

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 )

Configuration and Entities

Configuration


type alias Conf =
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

Entities


type alias 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


type alias EntityId =
Internal.Conf.EntityId



String


type alias TextConf =
Internal.Conf.TextConf



type alias TextConf =
    { title : String
    , helperText : Maybe String
    , validationSpecs : Maybe ValidationSpecs
    }

Fields Configuration and State

Singular fields, similarly to forms, have their own Configuration and State.


type alias FieldConf =
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


type alias FieldState =
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

Key


type alias Key =
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.


type alias KeyAsString =
Internal.Key.KeyAsString

keyToString : Key -> KeyAsString

stringToKey : KeyAsString -> Key

composeKey : Key -> String -> Key

listToKey : List String -> Key

headId : Key -> String

emptyKey : Key

Helpers

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

Form Components

viewIconButton : List (Element.WithContext.Attribute (R10.Context.ContextInternal z) msg) -> ArgsIconButton z msg -> Element.WithContext.Element (R10.Context.ContextInternal z) msg


type alias ArgsIconButton 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


type alias ArgsButton 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


type alias TextArgs z msg =
R10.FormComponents.Internal.Text.Args z msg


type alias ArgsText 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


type alias ArgsBinary msg =
R10.FormComponents.Internal.Binary.Args msg

Style


type alias Style =
R10.FormComponents.Internal.Style.Style

style : { fixedLabels : Style, floatingLabels : Style }

defaultStyle : Style

styleToString : Style -> String

stringToStyle : String -> Style

Button


type alias Button =
R10.FormComponents.Internal.Button.Button

button : { contained : Button, outlined : Button, text : Button, icon : Button }

CSS, Colors Palette

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

Events

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

Markdown

elementMarkdown : String -> List (Element.WithContext.Element (R10.Context.ContextInternal z) msg)

Translator

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


type alias Translator =
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 }

Single Field


type alias SingleModel =
R10.FormComponents.Internal.Single.Common.Model


type alias SingleMsg =
R10.FormComponents.Internal.Single.Common.Msg


type alias SingleFieldOption =
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 )

Views

viewSingle : List (Element.WithContext.Attribute (R10.Context.ContextInternal z) msg) -> SingleModel -> ArgsSingle msg -> Element.WithContext.Element (R10.Context.ContextInternal z) msg


type alias ArgsSingle 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


type alias ArgsSingleCustom z msg =
R10.FormComponents.Internal.Single.ArgsCustom z msg

Validation

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 }


type alias Validation =
Internal.FieldConf.Validation


type alias ValidationCode =
Internal.FieldConf.ValidationCode

Just a String


type alias ValidationSpecs =
Internal.FieldConf.ValidationSpecs


type alias ValidationForView =
R10.FormComponents.Internal.Validations.ValidationForView


type alias ValidationMessage =
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 )

Advanced usage

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

Others

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