Merge the latest version of Speedometer 2.0 to browserbench.org against at r221119.
[WebKit-https.git] / Websites / browserbench.org / Speedometer2.0 / resources / todomvc / functional-prog-examples / elm / Todo.elm
1 port module Todo exposing (..)
2
3 {-| TodoMVC implemented in Elm, using plain HTML and CSS for rendering.
4
5 This application is broken up into four distinct parts:
6
7   1. Model  - a full description of the application as data
8   2. Update - a way to update the model based on user actions
9   3. View   - a way to visualize our model with HTML
10
11 This program is not particularly large, so definitely see the following
12 document for notes on structuring more complex GUIs with Elm:
13 http://guide.elm-lang.org/architecture/
14 -}
15
16 import Dom
17 import Task
18 import Html exposing (..)
19 import Html.Attributes exposing (..)
20 import Html.Events exposing (..)
21 import Html.Lazy exposing (lazy, lazy2)
22 import Html.App
23 import Navigation exposing (Parser)
24 import String
25 import String.Extra
26 import Todo.Task
27
28
29 -- MODEL
30 -- The full application state of our todo app.
31
32
33 type alias Model =
34     { tasks : List Todo.Task.Model
35     , field : String
36     , uid : Int
37     , visibility : String
38     }
39
40
41 type alias Flags =
42     Maybe Model
43
44
45 emptyModel : Model
46 emptyModel =
47     { tasks = []
48     , visibility = "All"
49     , field = ""
50     , uid = 0
51     }
52
53
54
55 -- UPDATE
56 -- A description of the kinds of actions that can be performed on the model of
57 -- our application. See the following post for more info on this pattern and
58 -- some alternatives: http://guide.elm-lang.org/architecture/
59
60
61 type Msg
62     = NoOp
63     | UpdateField String
64     | Add
65     | UpdateTask ( Int, Todo.Task.Msg )
66     | DeleteComplete
67     | CheckAll Bool
68     | ChangeVisibility String
69
70
71
72 -- How we update our Model on any given Message
73
74
75 update : Msg -> Model -> ( Model, Cmd Msg )
76 update msg model =
77     case Debug.log "MESSAGE: " msg of
78         NoOp ->
79             ( model, Cmd.none )
80
81         UpdateField str ->
82             let
83                 newModel =
84                     { model | field = str }
85             in
86                 ( newModel, save model )
87
88         Add ->
89             let
90                 description =
91                     String.trim model.field
92
93                 newModel =
94                     if String.isEmpty description then
95                         model
96                     else
97                         { model
98                             | uid = model.uid + 1
99                             , field = ""
100                             , tasks = model.tasks ++ [ Todo.Task.init description model.uid ]
101                         }
102             in
103                 ( newModel, save newModel )
104
105         UpdateTask ( id, taskMsg ) ->
106             let
107                 updateTask t =
108                     if t.id == id then
109                         Todo.Task.update taskMsg t
110                     else
111                         Just t
112
113                 newModel =
114                     { model | tasks = List.filterMap updateTask model.tasks }
115             in
116                 case taskMsg of
117                     Todo.Task.Focus elementId ->
118                         newModel ! [ save newModel, focusTask elementId ]
119
120                     _ ->
121                         ( newModel, save newModel )
122
123         DeleteComplete ->
124             let
125                 newModel =
126                     { model
127                         | tasks = List.filter (not << .completed) model.tasks
128                     }
129             in
130                 ( newModel, save newModel )
131
132         CheckAll bool ->
133             let
134                 updateTask t =
135                     { t | completed = bool }
136
137                 newModel =
138                     { model | tasks = List.map updateTask model.tasks }
139             in
140                 ( newModel, save newModel )
141
142         ChangeVisibility visibility ->
143             let
144                 newModel =
145                     { model | visibility = visibility }
146             in
147                 ( newModel, save model )
148
149
150 focusTask : String -> Cmd Msg
151 focusTask elementId =
152     Task.perform (\_ -> NoOp) (\_ -> NoOp) (Dom.focus elementId)
153
154
155
156 -- VIEW
157
158
159 view : Model -> Html Msg
160 view model =
161     div
162         [ class "todomvc-wrapper"
163         , style [ ( "visibility", "hidden" ) ]
164         ]
165         [ section
166             [ class "todoapp" ]
167             [ lazy taskEntry model.field
168             , lazy2 taskList model.visibility model.tasks
169             , lazy2 controls model.visibility model.tasks
170             ]
171         , infoFooter
172         ]
173
174
175 taskEntry : String -> Html Msg
176 taskEntry task =
177     header
178         [ class "header" ]
179         [ h1 [] [ text "todos" ]
180         , input
181             [ class "new-todo"
182             , placeholder "What needs to be done?"
183             , autofocus True
184             , value task
185             , name "newTodo"
186             , onInput UpdateField
187             , Todo.Task.onFinish Add NoOp
188             ]
189             []
190         ]
191
192
193 taskList : String -> List Todo.Task.Model -> Html Msg
194 taskList visibility tasks =
195     let
196         isVisible todo =
197             case visibility of
198                 "Completed" ->
199                     todo.completed
200
201                 "Active" ->
202                     not todo.completed
203
204                 -- "All"
205                 _ ->
206                     True
207
208         allCompleted =
209             List.all .completed tasks
210
211         cssVisibility =
212             if List.isEmpty tasks then
213                 "hidden"
214             else
215                 "visible"
216     in
217         section
218             [ class "main"
219             , style [ ( "visibility", cssVisibility ) ]
220             ]
221             [ input
222                 [ class "toggle-all"
223                 , type' "checkbox"
224                 , name "toggle"
225                 , checked allCompleted
226                 , onClick (CheckAll (not allCompleted))
227                 ]
228                 []
229             , label
230                 [ for "toggle-all" ]
231                 [ text "Mark all as complete" ]
232             , ul
233                 [ class "todo-list" ]
234                 (List.map
235                     (\task ->
236                         let
237                             id =
238                                 task.id
239
240                             taskView =
241                                 Todo.Task.view task
242                         in
243                             Html.App.map (\msg -> UpdateTask ( id, msg )) taskView
244                     )
245                     (List.filter isVisible tasks)
246                 )
247             ]
248
249
250 controls : String -> List Todo.Task.Model -> Html Msg
251 controls visibility tasks =
252     let
253         tasksCompleted =
254             List.length (List.filter .completed tasks)
255
256         tasksLeft =
257             List.length tasks - tasksCompleted
258
259         item_ =
260             if tasksLeft == 1 then
261                 " item"
262             else
263                 " items"
264     in
265         footer
266             [ class "footer"
267             , hidden (List.isEmpty tasks)
268             ]
269             [ span
270                 [ class "todo-count" ]
271                 [ strong [] [ text (toString tasksLeft) ]
272                 , text (item_ ++ " left")
273                 ]
274             , ul
275                 [ class "filters" ]
276                 [ visibilitySwap "#/" "All" visibility
277                 , text " "
278                 , visibilitySwap "#/active" "Active" visibility
279                 , text " "
280                 , visibilitySwap "#/completed" "Completed" visibility
281                 ]
282             , button
283                 [ class "clear-completed"
284                 , hidden (tasksCompleted == 0)
285                 , onClick DeleteComplete
286                 ]
287                 [ text ("Clear completed (" ++ toString tasksCompleted ++ ")") ]
288             ]
289
290
291 visibilitySwap : String -> String -> String -> Html Msg
292 visibilitySwap uri visibility actualVisibility =
293     let
294         className =
295             if visibility == actualVisibility then
296                 "selected"
297             else
298                 ""
299     in
300         li
301             [ onClick (ChangeVisibility visibility) ]
302             [ a [ class className, href uri ] [ text visibility ] ]
303
304
305 infoFooter : Html msg
306 infoFooter =
307     footer
308         [ class "info" ]
309         [ p [] [ text "Double-click to edit a todo" ]
310         , p []
311             [ text "Written by "
312             , a [ href "https://github.com/evancz" ] [ text "Evan Czaplicki" ]
313             ]
314         , p []
315             [ text "Part of "
316             , a [ href "http://todomvc.com" ] [ text "TodoMVC" ]
317             ]
318         ]
319
320
321
322 -- wire the entire application together
323
324
325 main : Program Flags
326 main =
327     Navigation.programWithFlags urlParser
328         { urlUpdate = urlUpdate
329         , view = view
330         , init = init
331         , update = update
332         , subscriptions = subscriptions
333         }
334
335
336
337 -- URL PARSERS - check out evancz/url-parser for fancier URL parsing
338
339
340 toUrl : String -> String
341 toUrl visibility =
342     "#/" ++ String.toLower visibility
343
344
345 fromUrl : String -> Maybe String
346 fromUrl hash =
347     let
348         cleanHash =
349             String.dropLeft 2 hash
350     in
351         if (List.member cleanHash [ "all", "active", "completed" ]) == True then
352             Just cleanHash
353         else
354             Nothing
355
356
357 urlParser : Parser (Maybe String)
358 urlParser =
359     Navigation.makeParser (fromUrl << .hash)
360
361
362 {-| The URL is turned into a Maybe value. If the URL is valid, we just update
363 our model with the new visibility settings. If it is not a valid URL,
364 we set the visibility filter to show all tasks.
365 -}
366 urlUpdate : Maybe String -> Model -> ( Model, Cmd Msg )
367 urlUpdate result model =
368     case result of
369         Just visibility ->
370             update (ChangeVisibility (String.Extra.toSentenceCase visibility)) model
371
372         Nothing ->
373             update (ChangeVisibility "All") model
374
375
376 init : Flags -> Maybe String -> ( Model, Cmd Msg )
377 init flags url =
378     urlUpdate url (Maybe.withDefault emptyModel flags)
379
380
381
382 -- interactions with localStorage
383
384
385 port save : Model -> Cmd msg
386
387
388 subscriptions : Model -> Sub Msg
389 subscriptions model =
390     Sub.none