rakutentech/r10/5.1.1

NOTE: the latest version is 7.0.0

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 =
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 (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.

Form
-> MsgMapper msg
-> Theme
-> List (Element (ContextInternal z) msg)

Use this version if you have R10.Theme.Theme.

Form
-> MsgMapper msg
-> Palette
-> List (Element (ContextInternal z) msg)

Use this version if you have a specific palette that you want to use.

Form
-> MsgMapper msg
-> Options z
-> List (Element (ContextInternal z) msg)

Use this version for full control.

type alias Options z =
{ maker :
Maybe
( { key : Key
, formState : State
, translator : Translator
, style : Style
, palette : Palette
}
-> Conf
-> List (Element (ContextInternal z) Msg)
)
, translator : Maybe Translator
, style : Style
, palette : Maybe Palette
}

These are the options that you can use with viewWithOptions.

Messages

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

Return true if the message can potentially change some value

isFormSubmittable : Form -> Bool

This function return True in one of these two cases

  • The form has never been submitted. This because we want to show an active "Submit" button to the user the first time they load the form.
  • The has been an attempt to submit the form that failed because some data didn't pass the validation. Then the data was fixed so the form can now be submitted.

This function is normally used to disble or not the submit button.

isFormSubmittableAndSubmitted : Form -> Msg -> 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 =
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 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 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, Cmd Msg )

Configuration and Entities

Configuration

type alias 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 Error Conf
changeFieldConf : (FieldConf -> FieldConf) -> Entity -> Entity

Entities

type alias Entity =
Entity

A form is made of multiple entities. An example of entity is an input field, a title, a subtitle, etc.

{ 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 =
EntityId
String
type alias TextConf =
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 =
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 =
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 =
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 =
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 Bool
getActiveTab : KeyAsString -> State -> Maybe String
getMultiActiveKeys : Key -> State -> List Key

Set

setFieldValue : KeyAsString -> String -> State -> State
setActiveTab : KeyAsString -> String -> State -> State
setMultiplicableQuantities : KeyAsString -> Int -> State -> State
setFieldDisabled : KeyAsString -> Bool -> State -> State

Others

stringToBool : String -> Bool
boolToString : Bool -> String

Form Components

List (Attribute (ContextInternal z) msg)
-> ArgsIconButton z msg
-> Element (ContextInternal z) msg
type alias ArgsIconButton z msg =
{ icon : Element (ContextInternal z) msg
, msgOnClick : Maybe msg
, palette : Palette
, size : Int
}
List (Attribute (ContextInternal z) msg)
-> ArgsButton z msg
-> Element (ContextInternal z) msg
type alias ArgsButton z msg =
Args z msg
List (Attribute (ContextInternal z) msg)
-> List (Attribute (ContextInternal z) msg)
-> ArgsText z msg
-> Element (ContextInternal z) msg
List (Attribute (ContextInternal z) msg)
-> List (Attribute (ContextInternal z) msg)
-> Args z msg
-> Element (ContextInternal z) msg
{ pattern : String
, previousValue : String
, value : String
}
-> String
type alias TextArgs z msg =
Args z msg
type alias ArgsText z msg =
Args z msg
List (Attribute (ContextInternal z) msg)
-> ArgsBinary msg
-> Element (ContextInternal z) msg
type alias ArgsBinary msg =
Args msg

Style

type alias Style =
Style
{ filled : Style
, outlined : Style
}
defaultStyle : Style
styleToString : Style -> String
stringToStyle : String -> Style

Button

type alias Button =
Button
{ contained : Button
, outlined : Button
, text : Button
, icon : Button
}

CSS, Colors Palette

themeToPalette : Theme -> Palette
label : Palette -> Color
extraCssComponents : Palette -> String
extraCss : Maybe Palette -> String
colorToCssString : Color -> String

Events

onClickWithStopPropagation : msg -> Attribute (ContextInternal z) msg
onFocusOut : String -> msg -> 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 (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 =
Translator
defaultTranslator : Translator
{ 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
, 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 =
Model
type alias SingleMsg =
Msg
type alias SingleFieldOption =
FieldOption
defaultSearchFn : String -> SingleFieldOption -> Bool
initSingle : SingleModel
normalizeString : String -> String
insertBold : List Int -> String -> String
{ a | msgOnSelect : String -> msg, search : String }
-> SingleFieldOption
-> Element (ContextInternal z) msg
{ a | opened : Bool, palette : Palette }
-> Element (ContextInternal z) msg
singleMsg : { onOptionSelect : String -> SingleMsg }
updateSingle : SingleMsg -> SingleModel -> ( SingleModel, Cmd SingleMsg )

Views

List (Attribute (ContextInternal z) msg)
-> SingleModel
-> ArgsSingle msg
-> Element (ContextInternal z) msg
type alias ArgsSingle msg =
Args msg
List (Attribute (ContextInternal z) msg)
-> SingleModel
-> ArgsSingleCustom z msg
-> Element (ContextInternal z) msg
type alias ArgsSingleCustom z msg =
ArgsCustom z msg

Validation

(String -> String -> String)
-> Bool
-> Key
-> Maybe ValidationSpecs
-> State
-> FieldState
-> FieldState
{ allOf : List Validation -> Validation
, dependant : KeyAsString -> Validation -> Validation
, equal : KeyAsString -> Validation
, maxLength : Int -> Validation
, minLength : Int -> Validation
, noValidation : Validation
, oneOf : List Validation -> Validation
, regex : String -> Validation
, required : Validation
, withMsg : ValidationMessage -> Validation -> Validation
, empty : Validation
, not : Validation -> Validation
}
type alias Validation =
Validation
type alias ValidationCode =
ValidationCode

Just a String

type alias ValidationSpecs =
ValidationSpecs
type alias ValidationForView =
ValidationForView
type alias ValidationMessage =
ValidationMessage
validateDirtyFormFields : (String -> String -> String) -> Form -> State
validateEntireForm : (String -> String -> String) -> Form -> State
{ error : String -> ValidationMessage
, ok : String -> ValidationMessage
}
validationToString : ValidationForView -> String
allValidationKeysMaker : Form -> List ( Key, FieldType, Maybe ValidationSpecs )
(String -> String -> String)
-> List ( Key, FieldType, Maybe ValidationSpecs )
-> State
-> Dict KeyAsString FieldState
-> Dict KeyAsString FieldState
{ alphaNumericDash : Validation
, alphaNumericDashSpace : Validation
, email : Validation
, hexColor : Validation
, numeric : Validation
, password : Validation
, phoneNumber : Validation
, url : Validation
}
clearFieldValidation : KeyAsString -> State -> State
{ pretendIsNotYetValidated : ValidationForView
, validated : List ValidationMessage -> ValidationForView
}
initValidationSpecs : ValidationSpecs
setFieldValidationError : KeyAsString -> String -> State -> State
Form
-> Maybe Translator
-> List ( Key, FieldType, ValidationForView )
isRegexValidation : Validation -> Bool
entitiesWithErrors : Form -> List ( Key, 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