-
Notifications
You must be signed in to change notification settings - Fork 11
/
Copy pathTask.purs
130 lines (113 loc) · 3.87 KB
/
Task.purs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
module Todo.Task where
import Prelude
import Data.Maybe (Maybe(..), fromMaybe, isJust)
import Data.Monoid (guard)
import Data.String as String
import Effect (Effect)
import React.Basic (JSX, StateUpdate(..))
import React.Basic as React
import React.Basic.DOM as DOM
import React.Basic.DOM.Events as DOM.Events
import React.Basic.Events as Events
import Todo.View (classy)
-- | Type of our single Todo item
type Task =
{ description :: String
, id :: Int
, completed :: Boolean
}
-- | Every component keeps track of the fact that it's being edited,
-- and what's the new value
type State = { edits :: Maybe String }
-- | Callbacks that we pass into the component to update the main list
-- in the parent's state when things happen.
-- Note: the `key` here is needed so that React can disambiguate our items on render
type Props =
{ key :: Int
, task :: Task
, onCheck :: Effect Unit
, onDelete :: Effect Unit
, onCommit :: String -> Effect Unit
}
data Action
= Focus
| Change (Maybe String)
| KeyDown (Maybe String)
| Commit
type SetState = (State -> State) -> Effect Unit
-- | We start in a "non editing" state
initialState :: State
initialState = { edits: Nothing }
taskComponent :: React.Component Props
taskComponent = React.createComponent "Task"
component :: Props -> JSX
component props = React.make taskComponent
{ render
, initialState
} props
render :: React.Self Props State -> JSX
render self@{state, props} =
DOM.li
{ className: classNames
, children:
[ classy DOM.div "view"
[ DOM.input
{ className: "toggle"
, "type": "checkbox"
, checked: props.task.completed
, onChange: Events.handler_ props.onCheck
}
, DOM.label
-- Set the field in edit mode when focused
{ onDoubleClick: DOM.Events.capture_ (send self Focus)
, children: [ DOM.text description ]
}
, DOM.button
{ className: "destroy"
, onClick: Events.handler_ props.onDelete
}
]
, DOM.input
{ className: "edit"
, value: description
, name: "title"
-- Update the input field
, onChange: DOM.Events.capture DOM.Events.targetValue (send self <<< Change)
-- Commit our changes to the parent component once we're done editing
, onBlur: DOM.Events.capture_ (send self Commit)
-- Special case some keys that might be inserted: on Enter commit the changes,
-- on Esc discard them - otherwise, type normally
, onKeyDown: Events.handler DOM.Events.key (send self <<< KeyDown)
}
]
}
where
send = React.runUpdate \_self ->
case _ of
Focus ->
Update $ self.state { edits = Just self.props.task.description }
Change value ->
Update (self.state { edits = value })
KeyDown key ->
case key of
Just "Escape" -> Update $ self.state { edits = Nothing }
Just "Enter" -> commitAction
_ -> NoUpdate
Commit -> commitAction
commitAction =
let newDescription = String.trim $ fromMaybe "" self.state.edits
in case newDescription of
"" ->
NoUpdate
_ ->
let
state' = self.state { edits = Nothing }
in
UpdateAndSideEffects state' (const $ self.props.onCommit newDescription)
classNames = String.joinWith " "
[ guard (isJust state.edits) "editing"
, guard props.task.completed "completed"
]
-- | The description of the task is either the edited one if present,
-- or the original description
description = fromMaybe props.task.description state.edits