Skip to content

Instantly share code, notes, and snippets.

@mdgriffith
Last active July 6, 2017 14:39
Show Gist options
  • Save mdgriffith/b2a59e77cb6e3467053a757366651cdf to your computer and use it in GitHub Desktop.
Save mdgriffith/b2a59e77cb6e3467053a757366651cdf to your computer and use it in GitHub Desktop.
{-
Form elements could use some love as far as standardizing how they work.
Every form element has the following in common:
- a value (except button)
- a change event
- a label
- style
- label content
We can have every form input generate an `Input` type that can only be converted to an `Element` through labeling.
As far as I can see, this is what Tessa did for the A11y library: http://package.elm-lang.org/packages/tesk9/elm-html-a11y/2.1.0/Html-A11y
and it's awesome.
The standard pattern for form elements could be the following
formElement ChangeMsg style [] value
Here's what that might look like
-}
checkbox Change style [] True
-- For more involved checkbox styling
checkboxWith ChangeMsg True
{ checked = el CheckedStyle [] (text "✓")
, unchecked el UnChecked [] empty
}
-- Do we need the distinction between single line and multiline text?
textarea Change style [] "My Text Area!"
inputText Change style [] "Some text"
password Change style [] "superpass"
button Change style [] (text "Button text!")
-- Button's child can be any `el`
-- We can disable the form submit on the button as the default
-- Do we need to be able to re-enable it?
-- Radio Buttons
type Lunch = Burrito | Taco
radio ChangeRadio RadioStyle []
[ option Burrito (text "A Burrito!")
, option Taco (text "A Taco!")
]
currentlySelected -- : Lunch
-- More involved styling
-- Communicate which element is selected
-- Be able to style the selected statement
radio ChangeRadio RadioStyle []
[ optionWith Burrito
{ selected = text "Burrito!"
, unselected = text "Unselected burrito :("
}
, option Taco (text "A Taco!")
]
currentlySelected -- : Lunch
-- The `<select>` element.
-- Do we even need a `<select>`? It's a weird, non-stylable subclass of a dropdown menu. Is there any advantage?
type Animal = Manatee | Pangolin | Bee
optionList ChangeSelection SelectStyle []
[ option Manatee (text "Manatees are pretty cool")
, option Pangolin (text "But so are pangolins")
, option Bee (text "Bees")
]
currentlySelected -- : Animal
-- Here's what a normal dropdown looks like
type Animal = Manatee | Pangolin | Bee
el DropDown [] (text "My Animal Menu")
|> below
[ when menu.open <|
radio ChangeSelection Style []
[ option Manatee (text "Manatees are pretty cool")
, option Pangolin (text "But so are pangolins")
, option Bee (text "Bees")
]
currentlySelected -- : Animal
]
{- Labels! -}
{-| We can use the labeler that's currently in the lib.
It'll just have a different signature of label : style -> List Attr -> Element -> Input -> Element
There should be versions for labelBelow, labelLeft, labelRight
The label element itself is a layout element that corresponds to row or column depending on the directionality
-}
label style [] (text "First Name") <|
inputText change style [] "LazerCat"
{- Error States! -}
{-| We want to be able to model error states easily, because that's very common with forms.
Here's an example.
We want a text input that changes style when there is an error.
We want to show that error message if it's present.
Labels for inputs are always required.
firstname.error -- Maybe (List String), a list of validation errors
-}
let
inputStyle =
if firstname.error == Nothing then
TextInputStyle
else
TextError
in
(inputText ChangeFirstName inputStyle [] "LazerCat")
|> label style [] (text "First Name")
|> below
[ whenJust firstname.error
(text << String.join ", ")
]
{-| With inline styles
-}
test =
inputText ChangeFirstName
[ style
[ Color.text blue
, Color.background lightGrey
, Color.mixIf firstname.error
[ Color.text blue
, Color.background lightGrey
]
]
]
"LazerCat"
|> label style [] (text "First Name")
|> below
[ whenJust firstname.error
(text << String.join ", ")
]
{- This exploration focuses on records instead of positional arguments.
It also explores what things would look like if styles were defined inline isntead of having a style identifier.
-}
checkbox [ style ]
{ value = True
, onChange = Just ChangeMsg
}
-- For more involved checkbox styling
checkboxWith
{ value = True
, onChange = Just ChangeMsg
, checked = el CheckedStyle [] (text "✓")
, unchecked el UnChecked [] empty
}
-- Do we need the distinction between single line and multiline text?
textarea [ style, attrs ]
{ value = "My Text Area!"
, change = Just Change
}
inputText [ style, attrs ]
{ value = "Some Text"
, change = Just Change
}
password [ style, attrs ]
{ value = "Some Text"
, change = Just Change
}
button [ style ]
{ value = text "Button text!"
, change = Just Change
}
-- Button's child can be any `el`
-- We can disable the form submit on the button as the default
-- Do we need to be able to re-enable it?
-- Radio Buttons
type Lunch = Burrito | Taco
radio [ radioStyle, attrs ]
{ change = Just ChangeRadio
, options =
[ option Burrito (text "A Burrito!")
, option Taco (text "A Taco!")
]
, value = currentlySelected -- : Lunch
}
-- More involved styling
-- Communicate which element is selected
-- Be able to style the selected statement
radio [ radioStyle, attrs ]
{ change = Just ChangeRadio
, options =
[ optionWith Burrito
{ selected = text "Burrito!"
, unselected = text "Unselected burrito :("
}
, option Taco (text "A Taco!")
]
, value = currentlySelected -- : Lunch
}
-- The `<select>` element.
-- Do we even need a `<select>`? It's a weird, non-stylable subclass of a dropdown menu. Is there any advantage?
type Animal = Manatee | Pangolin | Bee
optionList [ radioStyle, attrs ]
{ change = Just ChangeRadio
, options =
[ optionWith Burrito
{ selected = text "Burrito!"
, unselected = text "Unselected burrito :("
}
, option Taco (text "A Taco!")
]
, value = currentlySelected -- : Lunch
}
-- Here's what a normal dropdown looks like
type Animal = Manatee | Pangolin | Bee
el DropDown [] (text "My Animal Menu")
|> below
[ when menu.open <|
radio [ radioStyle, attrs ]
{ change = Just ChangeRadio
, options =
[ optionWith Burrito
{ selected = text "Burrito!"
, unselected = text "Unselected burrito :("
}
, option Taco (text "A Taco!")
]
, value = currentlySelected -- : Lunch
}
]
{- Labels! -}
{-| We can use the labeler that's currently in the lib.
It'll just have a different signature of label : style -> List Attr -> Element -> Input -> Element
There should be versions for labelBelow, labelLeft, labelRight
The label element itself is a layout element that corresponds to row or column depending on the directionality
-}
label [ style ] (text "First Name") <|
inputText [ style, attrs ]
{ value = "LazerCat"
, change = Just Change
}
{- Error States! -}
{-| We want to be able to model error states easily, because that's very common with forms.
Here's an example.
We want a text input that changes style when there is an error.
We want to show that error message if it's present.
Labels for inputs are always required.
firstname.error -- Maybe (List String), a list of validation errors
-}
let
style =
if firstname.error == Nothing then
TextInputStyle
else
TextError
in
inputText [ style, attrs ]
{ value = "LazerCat"
, change = Just ChangeFirstName
}
|> label style [] (text "First Name")
|> below
[ whenJust firstname.error
(politeError << String.join ", ")
]
{-| With inline styles
-}
test =
inputText
[ style
[ Color.text blue
, Color.background lightGrey
, Color.mixIf firstname.error
[ Color.text blue
, Color.background lightGrey
]
]
]
{ value = "LazerCat"
, change = Just ChangeFirstName
}
|> label style [] (text "First Name")
|> below
[ whenJust firstname.error
(politeError << String.join ", ")
]
@gampleman
Copy link

What about other events? Blur, Focus?

@mdgriffith
Copy link
Author

My thought was that those would be handled the normal way, just by adding onFocus Msg as an attribute.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment