forked from TomBers/Eve-Experiments
-
Notifications
You must be signed in to change notification settings - Fork 0
/
todomvc.eve
193 lines (141 loc) · 9.29 KB
/
todomvc.eve
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
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
# TodoMVC
[TodoMVC][1] is a specification for a todo list web application. Its original purpose was to help developers evaluate the plethora of Javascript frameworks implementing the Model-View-Controller (MVC) design pattern. Today, it has evolved into a benchmark for programming languages and frameworks targeting the web, so TodoMVC apps don’t necessarily reflect the MVC design pattern.
TodoMVC is a great example to demonstrate Eve's capabilities, as our semantics naturally enable a concise implementation; without optimizing for line count, the program you're reading only contains 63 lines of Eve code.
## Todos
Each todo is tagged `#todo` and has a `body`, which is the text of the todo entered by the user. Additionally, each todo has two flags. The first flag is the `completed` flag affects how the todo is displayed, and allows the user to filter todos based on completed status; we can look at "completed" todos, "active" todos, or "all" todos. The second flag is the `editing` flag, used to toggle an editing mode on the todo. This is used later to allow the user to update the body text of the todo.
These todos exist in the context of an `#app`, which we use to hold global state information. We use it to place a filter on the todos. The filter can be one of "completed", "active", or "all".
### The Application View
We draw Todo MVC here. All styling is handled in a separate CSS file. The app consists of three parts:
1. __Header__ - Contains the `#toggle-all` button as well as `#new-todo`, which is an input box for entering new todos.
2. __Body__ - Contains `#todo-list`, the list of todos. The work here is handled in the second block.
3. __Footer__ - Contains the count of todos, as well as control buttons for filtering, and clearing completed todos.
In this block, we do a little work to determine todo-count, all-checked, and none-checked. Other than that, this block simply lays out the major control elements of TodoMVC. A key aspect of this block is the `bind` keyword. This denotes the beginning of the action phase of the block, and tells Eve that to update records as data changes. This is the key component that enables Eve to react to user interaction and update the display.
~~~
search
[#app filter]
all-checked = if not([#todo completed: false]) then true
else false
none-checked = if [#todo completed: true] then false
else true
todo-count = if c = count[given: [#todo completed: false]] then c
else 0
bind @browser
// Links to an external stylesheet
[#link rel: "stylesheet" href: "examples/css/todomvc.css"]
[#div class: "todoapp" children:
[#header children:
[#h1 text: "todos"]
[#input #new-todo, class: "new-todo", autofocus: true,
placeholder: "What needs to be done?"]
[#input #toggle-all class: "toggle-all", type: "checkbox",
checked: all-checked]]
[#div class: "main" children:
[#ul #todo-list, class: "todo-list"]]
[#footer children:
[#span #todo-count, class: "todo-count", children:
[#strong text: todo-count]
[#span text: " items left"]]
[#ul #filters, class: "filters", children:
[#li children: [#a href: "#/examples/todomvc.eve/#/all" text: "all"
class: [selected: is(filter = "all")]]]
[#li children: [#a href: "#/examples/todomvc.eve/#/active" text: "active"
class: [selected: is(filter = "active")]]]
[#li children: [#a href: "#/examples/todomvc.eve/#/completed" text: "completed"
class: [selected: is(filter = "completed")]]]]
[#span #clear-completed text: "Clear completed"
class: [clear-completed: true,
hidden: none-checked]]]]
~~~
### Drawing the Todo List
Now we look at how the todos are actually displayed in the application. We attach it to `#todo-list` using its `children` attribute. Each todo display element consists of:
- a **list item**, with a checkbox for toggling the completed status of the todo
- a **label** displaying the text of the todo
- an **input textbox** for editing the text of the todo
- a **button** for deleting the todo
~~~
search @session @browser
[#app filter]
parent = [#todo-list]
(todo, body, completed, editing) =
if filter = "completed"
then ([#todo, body, completed: true, editing], body, true, editing)
else if filter = "active"
then ([#todo, body, completed: false, editing], body, false, editing)
else if filter = "all"
then ([#todo, body, completed, editing], body, completed, editing)
bind @browser
parent.children +=
[#li, class: [todo: true, completed, editing], todo, children:
[#input #todo-checkbox todo type: "checkbox" checked: completed
class: [toggle: true, hidden: editing]]
[#label #todo-item, class: [hidden: editing], todo, text: body]
[#input #todo-editor, todo, value: body, autofocus: true
class: [edit: true,
hidden: toggle[value: editing]]]
[#button #remove-todo, class: [destroy: true, hidden: editing], todo]]
~~~
Thanks to Eve's set semantics, we don't need any loops here; for every unique `#todo` in the database, Eve will do the work of adding another `#li` as a child of `#todo-list`.
## Responding to User Events
### Creating a New Todo
A user can interact with TodoMVC in several ways. First and foremost, the user can create new todos. When the `@new-todo` input box is focused and the user presses enter, the value of the input is captured and a new todo is created.
~~~
search @event @session @browser
element = [#new-todo value]
kd = [#keydown element, key: "enter"]
commit
[#todo body: value, editing: false, completed: false, kd]
commit @browser
element.value := ""
~~~
Of note here is the record `[#todo body: value, editing: false, completed: false, kd]`. The inclusion of the `kd` attribute might seem strange, but its purpose is to guarantee the uniqueness of the todo. Let’s say we want to add two todos with the same body. If `kd` were not an attribute, then the two todos would be exactly the same and Eve’s set semantics would collapse them into a single todo. Therefore, we need some way to distinguish todos with identical bodies. Adding `kd` allows for this.
### Editing Todos
Here we handle all the ways we edit a todo. Editing includes changing the body as well as toggling the status of between complete and active.
- click `#todo-checkbox` - toggles the completed status of the checkbox.
- click `#toggle-all` - marks all todos as complete or incomplete, depending on the initial value. If all todos are marked incomplete, clicking `#toggle-all` will mark them complete. If only some are marked complete, then clicking `#toggle-all` will mark the rest complete. If all todos are marked as complete, then clicking `#toggle-all` will mark them all as incomplete.
- blur `#todo-editor` - blurring the `#todo-editor` will cancel the edit
- escape `#todo-editor` - this has the same effect as blurring
- enter `#todo-editor` - commits the new text in `#todo-editor`, replacing the original body
~~~
search @event @session @browser
(todo, body, editing, completed) =
if [#click element: [#todo-checkbox todo]]
then (todo, todo.body, todo.editing, toggle[value: todo.completed])
else if [#click element: [#toggle-all checked]]
then ([#todo body], body, todo.editing, toggle[value: checked])
else if [#double-click element: [#todo-item todo]]
then (todo, todo.body, true, todo.completed)
else if [#blur element: [#todo-editor todo value]]
then (todo, value, false, todo.completed)
else if [#keydown element: [#todo-editor todo] key: "escape"]
then (todo, todo.body, false, todo.completed)
else if [#keydown element: [#todo-editor todo value] key: "enter"]
then (todo, value, false, todo.completed)
commit
todo <- [body, completed, editing]
~~~
### Deleting Todos
We remove a todo from the list by setting the todo's record to special `none` value. Doing so completely erases that todo from the database.
~~~
search @event @session @browser
todo = if [#click element: [#remove-todo todo]]
then todo
else if [#click element: [#clear-completed]]
then [#todo completed: true]
commit
todo := none
~~~
### Filtering Todos (Routing)
The TodoMVC specification requires filtering via the URL. This is actually how the filter buttons work; if you look at their href attributes, they modify the URL with certain tags:
- all - displays all todos
- active - displays active todos only
- completed - displays completed todos only
We can extract this value using `#url`, which has a hash-segment attribute that automatically parses the URL for us, returning the `value` (expected to be any one of the above). Any other value will fail to show any todos, but the application will not break.
~~~
search @browser
value = if [#url hash-segment: [index: 1, value]]
then value
else "all"
bind
[#app filter: value]
~~~
[1]: http://todomvc.com/