choonkeat / nativeform / NativeForm

decoder : String -> Json.Decode.Decoder (List ( String, Value String ))

Given the id attribute of a <form> tag, we can decode the current form values from the browser document.forms and into a List of key values.

view model =
    -- form with `id` attribute
    form [ id "edituserform123" ]
        -- fields with `name` attribute
        []

update msg model =
    ( { model
        | formFields =
            -- decode model.document.forms to obtain a list of
            -- form field values anytime
            model.documentForms
                |> Json.Decode.decodeValue (NativeForm.decoder "edituserform123")
                |> Result.withDefault []
      }
    , Cmd.none
    )

We are returning a List instead of Dict because on form submit, duplicate names are preserved. So we are preserving them here too.

If a Dict is desired, pipe to the valuesDict helper function

    update msg model =
    ( { model
        | formFields =
            -- decode model.document.forms to obtain a list of
            -- form field values anytime
            model.documentForms
                |> Json.Decode.decodeValue (NativeForm.decoder "edituserform123")
+               |> Result.map NativeForm.valuesDict
                |> Result.withDefault []
      }
    , Cmd.none
    )


type Value a
    = OneValue a
    | ManyValues (List a)

Values from form fields are either String or List String

e.g. values for input [ type_ "number" ] [] will still be a String since and is entirely up to your application to convert and validate it with String.toInt or String.toFloat

valuesDict : List ( comparable, Value a ) -> Dict comparable (Value a)

Given a list of key values, combine the values of duplicate keys

import Dict exposing (Dict)

valuesDict
    [ (1, OneValue "1")
    , (2, OneValue "a")
    , (2, ManyValues ["b","c"])
    , (3, ManyValues ["yes","no"])
    ]
--> Dict.fromList
-->     [ (1, OneValue "1")
-->     , (2, ManyValues ["a","b","c"])
-->     , (3, ManyValues ["yes","no"])
-->     ]

valuesAppend : Value a -> Value a -> Value a

Given 2 Value, return a ManyValues

valuesAppend (OneValue "1") (OneValue "a")
--> ManyValues ["1","a"]

valuesAppend (OneValue "1") (ManyValues ["a","b"])
--> ManyValues ["1","a","b"]

valuesAppend (ManyValues ["1","2"]) (ManyValues ["a","b"])
--> ManyValues ["1","2","a","b"]

valuesAppend (ManyValues ["1","2"]) (OneValue "a")
--> ManyValues ["1","2","a"]

Helpers for OneValue

Example usage

toColor : Maybe (NativeForm.Value String) -> Result String Color
toColor maybeV =
    maybeV
        |> Maybe.map (NativeForm.oneMap colorFromString)
        |> Maybe.andThen (NativeForm.oneWithDefault Nothing)
        |> Result.fromMaybe "invalid color"

oneMap : (a -> b) -> Value a -> Value b

OneValue 3
|> oneMap ((+) 2)
--> OneValue 5

ManyValues [ 3 ]
|> oneMap ((+) 2)
--> ManyValues []

oneWithDefault : a -> Value a -> a

OneValue 42
|> oneWithDefault 3
--> 42

ManyValues [42]
|> oneWithDefault 3
--> 3

Helpers for ManyValues

Example usage

toHobbies : Maybe (NativeForm.Value String) -> Result String (List Hobby)
toHobbies maybeV =
    maybeV
        |> Maybe.map (NativeForm.manyMap (List.filterMap hobbyFromString))
        |> Maybe.map (NativeForm.manyWithDefault [])
        |> Result.fromMaybe "invalid hobby"

manyMap : (List a -> List b) -> Value a -> Value b

If there's only 1 occurrence of the field, we'd have decoded it as OneValue. But semantically, we want to treat it as ManyValues of 1 item when we manyMap

OneValue 3
|> manyMap ((++) [ 2 ])
--> ManyValues [ 2, 3 ]

ManyValues [ 3 ]
|> manyMap ((++) [ 2 ])
--> ManyValues [ 2, 3 ]

manyWithDefault : List a -> Value a -> List a

OneValue 42
|> manyWithDefault [3]
--> [3]

ManyValues [42]
|> manyWithDefault [3]
--> [42]

Parsing form values into desired types

field : comparable -> Result err a -> Result (Dict comparable err) (a -> b) -> Result (Dict comparable err) b

Pipe friendly builder of values that accumulates errors. Useful for writing your parseDontValidate functions

parseDontValidate : Time.Zone -> List ( String, NativeForm.Value String ) -> Result Errors ParsedInfo
parseDontValidate tz list =
    let
        dict =
            NativeForm.valuesDict list
    in
    Ok ParsedInfo
        |> field "myselect" (toRating (Dict.get "myselect" dict))
        |> field "myselectmulti" (toCharacteristics (Dict.get "myselectmulti" dict))
        |> field "mycheckbox" (toHobbies (Dict.get "mycheckbox" dict))
        |> field "mytext" (toNonEmptyString (Dict.get "mytext" dict))
        |> field "mynumber" (toInt (Dict.get "mynumber" dict))
        |> field "myurl" (toUrl (Dict.get "myurl" dict))
        |> field "mycolor" (toColor (Dict.get "mycolor" dict))
        |> field "mydate" (toTimePosix TypeDate tz (Dict.get "mydate" dict))
        |> field "mydatetime-local" (toTimePosix TypeDateTimeLocal tz (Dict.get "mydatetime-local" dict))