forked from tastejs/todomvc
/
components.dart
204 lines (170 loc) · 6.22 KB
/
components.dart
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
194
195
196
197
198
199
200
201
202
203
204
library todomvc.components;
import 'dart:html';
import 'package:react/react.dart';
import 'package:persistent/persistent.dart';
import 'dispatcher.dart';
// React components are defined here. They can assign events to the elements, but these
// events only should send some payload about the event happened to Dispatcher, and should not
// make any changes in the global data by themselves
// We are going to subclass from this class for all our React components.
// We are going to not use React's 'props' directly, but put everything to 'props['value']' instead.
// Then, we can check if that new props['value'] is actually changed, and if it is not - we'll just
// skip updating this component.
//
// This allows to speed up React even more - it even won't try to apply its diff algorithms to the
// components where the input wasn't changed.
abstract class _Component extends Component {
get value => props['value'];
bool shouldComponentUpdate(nextProps, nextState) {
return props['value'] != nextProps['value'];
}
}
class TodoApp extends _Component {
render() {
return
div({'id': 'todo-app', 'className': value['filter']}, [
headerComponent({'value': value["new-input"]}),
mainComponent({'value': persist({
'list': value["list"],
'edit': value["edit"]})}),
footerComponent({'value': persist({
'count': value["list"].length,
'filter': value["filter"],
'countCompleted': value["list"].where((i) => i["isCompleted"]).length})})]);
}
}
var todoAppComponent = registerComponent(() => new TodoApp());
class Header extends _Component {
Map getInitialState() {
return {'value': value};
}
void _inputChange(event) {
dispatch({'action': 'new-input', 'value': event.target.value});
setState({'value': event.target.value});
}
void _onKeyDown(event) {
if (event.keyCode == KeyCode.ENTER) {
dispatch({'action': 'create', 'value': event.target.value});
}
}
render() {
return
header({'id': 'header'}, [
h1({}, 'todos'),
input({
'id': 'new-todo', 'placeholder': 'What needs to be done?', 'autofocus': 'autofocus',
'value': value, 'onChange': _inputChange, 'onKeyDown': _onKeyDown})]);
}
}
var headerComponent = registerComponent(() => new Header());
class Main extends _Component {
void _toggleAll(_) {
dispatch({'action': 'toggle-all'});
}
render() {
return
section({'id': 'main'}, [
input({
'id': 'toggle-all', 'type': 'checkbox', 'onClick': _toggleAll,
'checked': value['list'].every((i) => i['isCompleted'])}),
label({'htmlFor': 'toggle-all'}, "Mark all as complete"),
todoListComponent({'value': value})]);
}
}
var mainComponent = registerComponent(() => new Main());
class TodoList extends _Component {
render() {
return
ul({'id': 'todo-list'}, (value['list'] as PersistentVector).toList().reversed.map((PersistentMap item) =>
todoListItemComponent({'value': persist({
'item': item,
'isEditing': value['edit'] == item['id']})})));
}
}
var todoListComponent = registerComponent(() => new TodoList());
class TodoListItem extends _Component {
PersistentMap get item => value['item'];
Map getInitialState() {
return {'value': item['text']};
}
void _updateText(event) {
dispatch({'action': 'update-text', 'text': event.target.value, 'id': item['id']});
setState({'value': event.target.value});
}
String get _itemClassName {
var classes = [];
classes.add(item['isCompleted'] ? 'completed' : 'active');
if (value['isEditing']) {
classes.add("editing");
}
return classes.join(" ");
}
void _toggle(_) {
dispatch({'action': 'toggle', 'id': item['id']});
}
void _destroy(_) {
dispatch({'action': 'destroy', 'id': item['id']});
}
void _edit(_) {
dispatch({'action': 'edit', 'id': item['id']});
}
void _commit(_) {
dispatch({'action': 'commit'});
}
componentDidUpdate(prevProps, prevState, rootNode) {
if (value['isEditing'] && !prevProps['value']['isEditing']) {
InputElement input = rootNode.querySelector(".edit");
input.focus();
input.setSelectionRange(input.value.length, input.value.length);
}
}
render() {
return
li({'className': _itemClassName}, [
div({'className': 'view'}, [
input({'className': 'toggle', 'type': 'checkbox', 'onClick': _toggle, 'checked': item['isCompleted']}),
label({'onDoubleClick': _edit}, item['text']),
button({'className': 'destroy', 'onClick': _destroy})]),
input({'className': 'edit', 'value': item['text'], 'onChange': _updateText, 'onBlur': _commit, 'ref': 'edit'})]);
}
}
var todoListItemComponent = registerComponent(() => new TodoListItem());
class Footer extends _Component {
String get filter => value['filter'];
int get count => value['count'];
int get countCompleted => value['countCompleted'];
String _filterClassName(String filterType) {
return filterType == filter ? 'selected' : '';
}
String get _buttonClassName {
return countCompleted == 0 ? 'hidden' : '';
}
String get _footerClassName {
return count == 0 ? 'hidden' : '';
}
void _clearCompleted(_) {
dispatch({'action': 'clear-completed'});
}
void _filter(SyntheticEvent e, String filterType) {
e.preventDefault();
dispatch({'action': 'filter', 'filter': filterType});
}
render() {
return
footer({'id': 'footer', 'className': _footerClassName}, [
span({'id': 'todo-count'}, [
strong({}, count),
" items left"]),
ul({'id': 'filters'}, [
li({},
a({'className': _filterClassName('all'), 'href': '#', 'onClick': (e) => _filter(e, 'all')}, "All")),
li({},
a({'className': _filterClassName('active'), 'href': '#', 'onClick': (e) => _filter(e, 'active')}, "Active")),
li({},
a({'className': _filterClassName('completed'), 'href': '#', 'onClick': (e) => _filter(e, 'completed')}, "Completed"))]),
button(
{'id': 'clear-completed', 'className': _buttonClassName, 'onClick': _clearCompleted},
"Clear Completed ($countCompleted)")]);
}
}
var footerComponent = registerComponent(() => new Footer());