Skip to content

Instantly share code, notes, and snippets.

@xuxucode
Last active November 20, 2016 14:05
Show Gist options
  • Save xuxucode/507cea3635af86e2eb30352c0d3e300e to your computer and use it in GitHub Desktop.
Save xuxucode/507cea3635af86e2eb30352c0d3e300e to your computer and use it in GitHub Desktop.
Todo elm learning
port module MyTodo exposing (..)
{-| TodoMVC implemented in Elm, using plain HTML and CSS for rendering.
This application is broken up into three key parts:
1. Model - a full definition of the application's state
2. Update - a way to step the application state forward
3. View - a way to visualize our application state with HTML
This clean division of concerns is a core part of Elm. You can read more about
this in <http://guide.elm-lang.org/architecture/index.html>
-}
import Dom
import Html exposing (Html, Attribute, h1, div, section, header, footer, span, input, ul, li, label, button, p, a, strong, text)
import Html.Attributes exposing (id, type_, class, classList, name, value, placeholder, checked, autofocus, style, for, href, hidden)
import Html.Events exposing (on, onInput, keyCode, onClick, onDoubleClick, onBlur)
import Html.Keyed as Keyed
import Html.Lazy exposing (lazy, lazy2)
import Json.Decode as Json
-- import String
import Task
main : Program (Maybe Model) Model Msg
main =
Html.programWithFlags
{ init = init
, update = updateWithStorage
, subscriptions = \_ -> Sub.none
, view = view
}
port setStorage : Model -> Cmd msg
{- | We want to `setStorage` every update, This function adds the setStorage
command for every step of the update function.
-}
updateWithStorage : Msg -> Model -> (Model, Cmd Msg)
updateWithStorage msg model =
let
(newModel, cmd) =
update msg model
in
(newModel
, Cmd.batch [setStorage newModel, cmd]
)
-- MODEL
-- The full application state of our todo app.
type alias Model =
{ entries : List Entry
, visibility : String
, field : String
, uid : Int
}
type alias Entry =
{ description : String
, completed : Bool
, editing : Bool
, id : Int
}
emptyModel : Model
emptyModel =
{ entries = []
, visibility = "All"
, field = ""
, uid = 0
}
newEntry : String -> Int -> Entry
newEntry desc id =
{ description = desc
, completed = False
, editing = False
, id = id
}
init : Maybe Model -> (Model, Cmd msg)
init savedModel =
Maybe.withDefault emptyModel savedModel ! []
-- UPDATE
{-| Users of our app can trigger messages by clicking and typing. These
messages are fed into the `update` function as they occur, letting use react
to them.
-}
type Msg
= NoOp
| UpdateField String
| EditingEntry Int Bool
| UpdateEntry Int String
| Add
| Delete Int
| DeleteComplete
| Check Int Bool
| CheckAll Bool
| ChangeVisibility String
-- How we update our Model on a given Msg?
update : Msg -> Model -> (Model, Cmd Msg)
update msg model =
case msg of
NoOp ->
model ! []
Add ->
{ model
| uid = model.uid + 1
, field = ""
, entries =
if String.isEmpty model.field then -- ***** if / else can be used for inline field
model.entries
else
model.entries ++ [ newEntry model.field model.uid ]
}
! []
UpdateField value ->
{ model | field = value }
! []
EditingEntry id isEditing ->
let
updateEntry entry =
if id == entry.id then
{ entry | editing = isEditing }
else
entry
focus =
Dom.focus ("todo-" ++ toString id)
in
{ model | entries = List.map updateEntry model.entries }
! [ Task.attempt (\_ -> NoOp) focus ] -- ***** Task.attempt
UpdateEntry id value ->
let
updateEntry entry =
if id == entry.id then
{ entry | description = value }
else
entry
in
{ model | entries = List.map updateEntry model.entries }
! []
Delete id ->
{ model | entries = List.filter (\t -> t.id /= id) model.entries } -- ***** anonymous function
! []
DeleteComplete ->
{ model | entries = List.filter (not << .completed) model.entries } -- ***** function composition
! []
Check id isCompleted ->
let
updateEntry entry =
if id == entry.id then
{ entry | completed = isCompleted }
else
entry
in
{ model | entries = List.map updateEntry model.entries }
! []
CheckAll isCompleted->
let
updateEntry entry =
{ entry | completed = isCompleted }
in
{ model | entries = List.map updateEntry model.entries }
! []
ChangeVisibility visibility ->
{ model | visibility = visibility }
! []
-- VIEW
view : Model -> Html Msg
view model =
div
[ class "todomvc-wrapper"
-- , style [ ("visibility", "hidden") ]
]
[ section
[ class "todoapp" ]
[ lazy viewInput model.field -- ***** lazy
, lazy2 viewEntries model.visibility model.entries -- ***** lazy2
, lazy2 viewControls model.visibility model.entries
]
, infoFooter
]
viewInput : String -> Html Msg
viewInput field =
header
[ class "header" ]
[ h1 [] [ text "todos" ]
, input
[ type_ "text"
, class "new-todo"
, name "newTodo"
, autofocus True
, value field
, placeholder "What need to be done?"
, onInput UpdateField
, onEnter Add
]
[]
]
onEnter : Msg -> Attribute Msg -- ***** custom Event
onEnter msg =
let
isEnter code =
if code == 13 then
Json.succeed msg -- ***** Json.succeed
else
Json.fail "not Enter"
in
on "keydown" (Json.andThen isEnter keyCode) -- ***** andThen and keyCode
-- VIEW ALL ENTRIES
viewEntries : String -> List Entry -> Html Msg
viewEntries visibility entries =
let
isVisible entry =
case visibility of
"Completed" ->
entry.completed
"Active" ->
not entry.completed
_ ->
True
allCompleted =
List.all .completed entries
cssVisibility =
if List.isEmpty entries then
"hidden"
else
"visible"
in
section
[ class "main"
, style [("visibility", cssVisibility)]
]
[ input
[ type_ "checkbox"
, class "toggle-all"
, name "toggle"
, checked allCompleted
, onClick (CheckAll (not allCompleted))
]
[]
, label
[ for "toggle-all" ]
[ text "Mark all as complete" ]
, Keyed.ul
[ class "todo-list" ] <|
List.map viewKeyedEntry (List.filter isVisible entries) -- ***** backward function application
]
-- VIEW INDIVIDUAL ENTRIES
viewKeyedEntry : Entry -> (String, Html Msg)
viewKeyedEntry entry =
(toString entry.id, lazy viewEntry entry) -- TODO: What doese key used for
viewEntry : Entry -> Html Msg
viewEntry entry =
li
[ classList [("completed", entry.completed), ("editing", entry.editing)] ]
[ div
[ class "view" ]
[ input
[ type_ "checkbox"
, class "toggle"
, checked entry.completed
, onClick (Check entry.id (not entry.completed))
]
[]
, label
[ onDoubleClick (EditingEntry entry.id True) ]
[ text entry.description ]
, button
[ class "destroy"
, onClick (Delete entry.id)
]
[]
]
, input
[ type_ "text"
, class "edit"
, id ("todo-" ++ toString entry.id)
, value entry.description
, onInput (UpdateEntry entry.id)
, onBlur (EditingEntry entry.id False)
, onEnter (EditingEntry entry.id False)
]
[]
]
-- VIEW CONTROLS AND FOOTER
viewControls : String -> List Entry -> Html Msg
viewControls visibility entries =
let
entriesCompleted =
entries |> List.filter .completed |> List.length
entriesLeft =
List.length entries - entriesCompleted
in
footer
[ class "footer"
, hidden (List.isEmpty entries)
]
[ viewControlsCount entriesLeft
, viewControlsFilters visibility
, viewControlsClear entriesCompleted
]
viewControlsCount : Int -> Html Msg
viewControlsCount entriesLeft =
let
item_ =
if entriesLeft == 1 then
" item"
else
" items"
in
span
[ class "todo-count" ]
[ strong [] [ text (toString entriesLeft) ]
, text (item_ ++ " left")
]
viewControlsFilters : String -> Html Msg
viewControlsFilters visibility =
ul
[ class "filters" ]
[ visibilitySwap "#/" "All" visibility
-- , text " "
, visibilitySwap "#/active" "Active" visibility
-- , text " "
, visibilitySwap "#/completed" "Completed" visibility
]
visibilitySwap : String -> String -> String -> Html Msg
visibilitySwap uri visibility actualVisibility =
li
[ onClick (ChangeVisibility visibility) ]
[ a [ href uri, classList [ ("selected", visibility == actualVisibility) ] ]
[ text visibility ]
]
viewControlsClear : Int -> Html Msg
viewControlsClear entriesCompleted =
button
[ class "clear-completed"
, hidden (entriesCompleted == 0)
, onClick DeleteComplete
]
[ text ("Clear completed (" ++ toString entriesCompleted ++ ")")
]
infoFooter : Html Msg
infoFooter =
footer [ class "info" ]
[ p [] [ text "Double-click to edit a todo" ]
, p []
[ text "Written by "
, a [ href "https://github.com/evancz" ] [ text "Evan Czaplicki" ]
]
, p []
[ text "Part of "
, a [ href "http://todomvc.com" ] [ text "TodoMVC" ]
]
]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment