GithubHelp home page GithubHelp logo

jmpavlick / elm-html-form Goto Github PK

View Code? Open in Web Editor NEW
11.0 2.0 1.0 108 KB

A flexible and extensible form package in Elm that uses the platform.

License: MIT License

HTML 1.85% Elm 96.82% JavaScript 1.33%

elm-html-form's Introduction

elm-html-form

A typesafe form package for Elm that lets you really use the platform.

⚠️ This is alpha, severely under-documented, and has zero tests - so please proceed with caution, for now. ⚠️

But with all that said - this is the form package that you want. The form package that you didn't realize could exist.

Features

The Html.Form.Module instance gives you access to a record with named fields that allows you to access:

  • The form input element
  • The form input element's attributes (i.e., events and value attribute) as a record with named fields
  • The form input element's attributes as a function `List (Html.Attribute msg) -> List (Html.Attribute msg)
  • The form input element's errors

You can implement this in an existing application by applying the element's attributes to whatever inputs you have already - or you can build up elements yourself and hoist the form events into them - or you can simply take the element outputs and style them however you want. It's up to you, really.

It also gives you access to a list of your whole form's errors, tupled with their "editor" value - which you can easily restructure into a record with named properties, if you'd like.

Oh, and none of this is using functions in the Model, or any black magic. At time of writing, the only dependencies are on packages from the elm Github org.

Sample Implementation

More documentation to come soon, but here's a taste of the API - this is all copied from this repo's demo/src/Main.elm and demo/src/Signup.elm modules.

If you clone the repo, you can run the sample with pnpm start.

Callsites for form fields

Forgive this; I'm not very good at HTML. But at least you can get an idea of what your callsites could look like! (I'm going to clean this up later.)

view : Model -> Html Msg
view model =
    let
        fieldset : Signup.Fieldset Msg
        fieldset =
            signupModule.fieldset model

        margin : Int -> Html.Attribute msg
        margin i =
            String.fromInt i
                ++ "px"
                |> Html.Attributes.style "margin"

        withLabel l field =
            Html.div [ margin 12 ]
                [ Html.label [ margin 4 ]
                    [ Html.text l
                    , field
                    ]
                ]

        withCallback field =
            Html.div [ Html.Events.onInput GotPropagatedEvent ] [ field ]

        withErrors field attrs =
            let
                showErrors =
                    not <| List.isEmpty field.errors
            in
            Html.div []
                [ field.element <|
                    if showErrors then
                        Html.Attributes.style "text-decoration-style" "wavy"
                            :: Html.Attributes.style "text-decoration-line" "underline"
                            :: Html.Attributes.style "text-decoration-color" "red"
                            :: attrs

                    else
                        attrs
                , if showErrors then
                    Html.div [] [ Html.ul [] <| Html.h4 [] [ Html.text "Errors" ] :: List.map (\err -> Html.li [] [ Html.text err ]) field.errors ]

                  else
                    Html.text ""
                ]
    in
    Html.div []
        [ Html.div []
            [ Html.div []
                [ withErrors fieldset.name [ margin 12 ] |> withLabel "Name"
                , Html.input (fieldset.age.toAttrs [ margin 12 ]) [] |> withLabel "Age"
                , withErrors fieldset.emailAddress [ margin 12 ] |> withLabel "Email Address"
                , fieldset.subscribe.element [ Html.Attributes.style "margin" "4px" ] |> withLabel "Subscribe"
                ]
            , Html.button [ Html.Events.onClick signupModule.submitMsg ] [ Html.text "Submit" ]
            , Html.hr [] []
            , Debug.toString model |> Html.text
            , Html.hr [] []
            , Debug.toString (signupModule.errors model) |> Html.text
            , Html.hr [] []
            , Debug.toString (signupModule.errors model |> Signup.fromErrors) |> Html.text
            ]
        ]

Form creation

This is how you initialize a Html.Form.Module.

import Html
import Html.Form
import Html.Form.Validation

{-| First, you create a type to represent a single field's editing state.
-}
type Editor
    = Name (Maybe String)
    | Age (Maybe Int)
    | EmailAddress (Maybe String)
    | Subscribe (Maybe Bool)


{-| Then, you create a type to represent a validated, submitted value.
-}
type alias Record =
    { name : String
    , age : Int
    , emailAddress : String
    , subscribe : Bool
    }

{-| You can use custom types for errors, if you want to!
-}
type alias Error =
    String

{-| Then, you create a type to represent your output, from the form module.
-}
type alias Fieldset msg =
    { name : Html.Form.Field Error msg
    , age : Html.Form.Field Error msg
    , emailAddress : Html.Form.Field Error msg
    , subscribe : Html.Form.Field Error msg
    }

{-| Now, you provide a map from a `List` of your `Editor` type, to a `Maybe Record`.

(Note: Don't put your validation in here - that's not where it goes!)
-}
toRecord : List Editor -> Maybe Record
toRecord editors =
    List.foldl
        (\step acc ->
            case step of
                Name name ->
                    { acc | name = name }

                Age age ->
                    { acc | age = age }

                EmailAddress emailAddress ->
                    { acc | emailAddress = emailAddress }

                Subscribe subscribe ->
                    { acc | subscribe = subscribe }
        )
        { name = Nothing
        , age = Nothing
        , emailAddress = Nothing
        , subscribe = Nothing
        }
        editors
        |> (\e -> Maybe.map4 Record e.name e.age e.emailAddress e.subscribe)

{-| Now, you can initialize the module. (In this sample, I'm defining my form in one module and actually
hosting it in another, so `toMsg` and `onSubmit` are parameterized.)
-}
form :
    { toMsg : Html.Form.Msg Editor -> msg, onSubmit : Record -> msg }
    -> Html.Form.Module String Editor { model | signupForm : Html.Form.Model Editor } (Fieldset msg) msg
form { toMsg, onSubmit } =
    {-
      Mapping the fields to the `Fieldset msg` works similiarly to `Json.Decode.Pipeline`, where you use a constructor function
      (usually the type alias constructor from a record type alias) to kick off an applicative. 
    -}
    Html.Form.init Fieldset
        { toModel = \m formModel -> { m | signupForm = formModel }
        , fromModel = .signupForm
        , toMsg = toMsg
        , toRecord = toRecord
        , onSubmit = onSubmit
        }
        -- You can then add as many fields as you have parameters to your constructor function.
        |> Html.Form.withField Name
            (Html.Form.input
                {-
                  You can allow propagation on inputs so that the event bubbles; you can use this
                  to fire events in a host module when something happens in your form - per field!
                -}
                |> Html.Form.withStopPropagation False
                {-
                  You can add validation!
                -}
                |> Html.Form.withValidation
                    (Html.Form.Validation.when.blurredAfterEdit
                        (\args ->
                            if args.self == Name (Just "John") then
                                Err "I'm sorry, John; you can't do that."

                            else
                                Ok args.self
                        )
                    )
            )
        |> Html.Form.withField (Maybe.andThen String.toInt >> Age) Html.Form.input
        |> Html.Form.withField EmailAddress
            (Html.Form.input
                |> Html.Form.withInitialValue (Just "[email protected]")
                |> Html.Form.withValidation
                    (Html.Form.Validation.when.editingOrBlurred
                        (\args ->
                            case args.self of
                                EmailAddress (Just v) ->
                                    if v == "" then
                                        Err "Email address must not be empty"

                                    else
                                        Ok args.self

                                _ ->
                                    Err "Email address must not be empty"
                        )
                        |> Html.Form.Validation.andThen
                            (\args ->
                                case args.self of
                                    EmailAddress (Just v) ->
                                        if not <| String.contains "@" v then
                                            Err "Email addresses must contain an @ symbol"

                                        else
                                            Ok args.self

                                    _ ->
                                        Ok args.self
                            )
                    )
            )
        |> Html.Form.withField Subscribe (Html.Form.checkbox |> Html.Form.withStopPropagation False)
        |> Html.Form.build

elm-html-form's People

Contributors

arrow7000 avatar jmpavlick avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar

Forkers

arrow7000

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.