From the Material Design Lite documentation:
The Material Design Lite (MDL) text field component is an enhanced version of the standard HTML
<input type="text">
and<input type="textarea">
elements. A text field consists of a horizontal line indicating where keyboard input can occur and, typically, text that clearly communicates the intended contents of the text field. The MDL text field component provides various types of text fields, and allows you to add both display and click effects.Text fields are a common feature of most user interfaces, regardless of a site's content or function. Their design and use is therefore an important factor in the overall user experience. See the text field component's Material Design specifications page for details.
The enhanced text field component has a more vivid visual look than a standard text field, and may be initially or programmatically disabled. There are three main types of text fields in the text field component, each with its own basic coding requirements. The types are single-line, multi-line, and expandable.
Refer to this site for a live demo.
This implementation provides only single-line and password.
Component render. Below is an example, assuming boilerplate setup as indicated
in Material
, and a user message ChangeAgeMsg Int
.
Textfield.render Mdl [0] model.mdl
[ Textfield.label "Your age?"
, Textfield.floatingLabel
, Textfield.value model.age
, Textfield.onInput (String.toInt >> ChangeAgeMsg)
]
TODO
TODO
TODO
TODO
TODO
TODO
TODO
TODO
Sets the type of input to 'password'.
Creates a multiline textarea using 'textarea' element
TODO
Component actions. Input
carries the new value of the field.
Model. The textfield is in its error-container if error
is not Nothing
.
The contents of the field is value
.
Default model. No label, error, or value.
Component update.
module Material.Textfield exposing
( Property, label, floatingLabel, error, value, disabled, password
, onInput
, Msg, Model, defaultModel, update, view
, render
, text', textarea, rows, cols
)
{-| From the [Material Design Lite documentation](http://www.getmdl.io/components/#textfields-section):
> The Material Design Lite (MDL) text field component is an enhanced version of
> the standard HTML `<input type="text">` and `<input type="textarea">` elements.
> A text field consists of a horizontal line indicating where keyboard input
> can occur and, typically, text that clearly communicates the intended
> contents of the text field. The MDL text field component provides various
> types of text fields, and allows you to add both display and click effects.
>
> Text fields are a common feature of most user interfaces, regardless of a
> site's content or function. Their design and use is therefore an important
> factor in the overall user experience. See the text field component's
> [Material Design specifications page](https://www.google.com/design/spec/components/text-fields.html)
> for details.
>
> The enhanced text field component has a more vivid visual look than a standard
> text field, and may be initially or programmatically disabled. There are three
> main types of text fields in the text field component, each with its own basic
> coding requirements. The types are single-line, multi-line, and expandable.
Refer to
[this site](https://debois.github.io/elm-mdl/#/textfields)
for a live demo.
This implementation provides only single-line and password.
# Component render
@docs render
# Options
@docs Property, value
# Appearance
@docs label, floatingLabel, error, disabled, rows, cols
# Type
@docs password, textarea, text', onInput
# Elm Architecture
@docs Msg, Model, defaultModel, update, view
-}
import Html exposing (div, span, Html, text)
import Html.Attributes exposing (class, type', style)
import Html.Events exposing (onFocus, onBlur, targetValue)
import Json.Decode as Decoder
import Platform.Cmd
import Parts exposing (Indexed)
import Material.Options as Options exposing (cs, nop)
-- OPTIONS
type Kind
= Text
| Textarea
| Password
type alias Config m =
{ labelText : Maybe String
, labelFloat : Bool
, error : Maybe String
, value : Maybe String
, disabled : Bool
, onInput : Maybe (Html.Attribute m)
, kind : Kind
, rows : Maybe Int
, cols : Maybe Int
}
defaultConfig : Config m
defaultConfig =
{ labelText = Nothing
, labelFloat = False
, error = Nothing
, value = Nothing
, disabled = False
, kind = Text
, onInput = Nothing
, rows = Nothing
, cols = Nothing
}
{-|
TODO
-}
type alias Property m =
Options.Property (Config m) m
{-|
TODO
-}
label : String -> Property m
label str =
Options.set
(\config -> { config | labelText = Just str })
{-|
TODO
-}
floatingLabel : Property m
floatingLabel =
Options.set
(\config -> { config | labelFloat = True })
{-|
TODO
-}
error : String -> Property m
error str =
Options.set
(\config -> { config | error = Just str })
{-|
TODO
-}
value : String -> Property m
value str =
Options.set
(\config -> { config | value = Just str })
{-|
TODO
-}
disabled : Property m
disabled =
Options.set
(\config -> { config | disabled = True })
{-|
TODO
-}
onInput : (String -> m) -> Property m
onInput f =
Options.set
(\config -> { config | onInput =
Just (Html.Events.on "input" (Decoder.map f targetValue)) })
{-| Sets the type of input to 'password'.
-}
password : Property m
password =
Options.set
(\config -> { config | kind = Password })
{-| Creates a multiline textarea using 'textarea' element
-}
textarea : Property m
textarea =
Options.set
(\config -> { config | kind = Textarea })
{-| Sets the type of input to 'text'. (Name chosen to avoid clashing with Html.text)
-}
text' : Property m
text' =
Options.set
(\config -> { config | kind = Text })
{-|
TODO
-}
rows : Int -> Property m
rows rows =
Options.set
(\config -> { config | rows = Just rows })
{-|
TODO
-}
cols : Int -> Property m
cols cols =
Options.set
(\config -> { config | cols = Just cols })
-- MODEL
{- Kind of textfield. Currently supports only single-line input or password
inputs.
| MultiLine (Maybe Int) -- Max no. of rows or no limit
-- TODO. Should prevent key event for ENTER
-- when number of rows exceeds maxrows argument to constructor:
MaterialTextfield.prototype.onKeyDown_ = function(event) {
var currentRowCount = event.target.value.split('\n').length;
if (event.keyCode === 13) {
if (currentRowCount >= this.maxRows) {
event.preventDefault();
}
}
};
-}
{-| Model. The textfield is in its error-container if `error` is not `Nothing`.
The contents of the field is `value`.
-}
type alias Model =
{ isFocused : Bool
, value : String
}
{-| Default model. No label, error, or value.
-}
defaultModel : Model
defaultModel =
{ isFocused = False
, value = ""
}
-- ACTIONS, UPDATE
{-| Component actions. `Input` carries the new value of the field.
-}
type Msg
= Blur
| Focus
| Input String
{-| Component update.
-}
update : Msg -> Model -> Model
update action model =
case action of
Input str ->
{ model | value = str }
Blur ->
{ model | isFocused = False }
Focus ->
{ model | isFocused = True }
-- VIEW
{-|
-}
view : Model -> List (Property Msg) -> Html Msg
view = view' (\x -> x)
view' : (Msg -> m) -> Model -> List (Property m) -> Html m
view' lift model options =
let
({ config } as summary) =
Options.collect defaultConfig options
val =
config.value |> Maybe.withDefault model.value
isTextarea = config.kind == Textarea
elementFunction =
if isTextarea then
Html.textarea
else
Html.input
-- Applying the type attribute only if we are not a textarea
-- However, if we are a textarea and rows and/or cols have been defined, add them instead
typeAttributes =
case config.kind of
Text -> [type' "text"]
Password -> [type' "password"]
Textarea ->
[] ++ (case config.rows of
Just r -> [Html.Attributes.rows r]
Nothing -> [])
++ (case config.cols of
Just c -> [Html.Attributes.cols c]
Nothing -> [])
in
Options.apply summary div
[ cs "mdl-textfield"
, cs "mdl-js-textfield"
, cs "is-upgraded"
, if config.labelFloat then cs "mdl-textfield--floating-label" else nop
, if config.error /= Nothing then cs "is-invalid" else nop
, if val /= "" then cs "is-dirty" else nop
, if model.isFocused && not config.disabled then cs "is-focused" else nop
, if config.disabled then cs "is-disabled" else nop
]
[ config.onInput
]
([ Just <| elementFunction
([ class "mdl-textfield__input"
, style [ ("outline", "none") ]
, Html.Attributes.disabled config.disabled
, onBlur (lift Blur)
, onFocus (lift Focus)
, case config.value of
Just str ->
Html.Attributes.value str
Nothing ->
Html.Events.on "input" (Decoder.map (Input >> lift) targetValue)
] ++ typeAttributes)
[]
, Just <| Html.label
[class "mdl-textfield__label"]
(case config.labelText of
Just str -> [ text str ]
Nothing -> [])
, config.error |> Maybe.map (\e ->
span [class "mdl-textfield__error"] [text e])
]
|> List.filterMap (\x -> x)
)
-- PART
type alias Container c =
{ c | textfield : Indexed Model }
{-| Component render. Below is an example, assuming boilerplate setup as indicated
in `Material`, and a user message `ChangeAgeMsg Int`.
Textfield.render Mdl [0] model.mdl
[ Textfield.label "Your age?"
, Textfield.floatingLabel
, Textfield.value model.age
, Textfield.onInput (String.toInt >> ChangeAgeMsg)
]
-}
render
: (Parts.Msg (Container c) -> m)
-> Parts.Index
-> (Container c)
-> List (Property m)
-> Html m
render =
Parts.create
view' (\action model -> (update action model, Cmd.none))
.textfield (\x c -> { c | textfield = x })
defaultModel