-
Notifications
You must be signed in to change notification settings - Fork 86
/
Copy pathtask-list.js
147 lines (129 loc) · 4.7 KB
/
task-list.js
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
/* jshint esnext: true */
const R = require('ramda');
const flyd = require('flyd');
const stream = flyd.stream;
const forwardTo = require('flyd-forwardto');
const Type = require('union-type');
const Router = require('../../../router');
const patch = require('snabbdom').init([
require('snabbdom/modules/class'),
require('snabbdom/modules/style'),
require('snabbdom/modules/props'),
require('snabbdom/modules/eventlisteners'),
]);
const treis = require('treis');
const h = require('snabbdom/h')
const targetValue = require('../../../helpers/targetvalue');
const ifEnter = require('../../../helpers/ifenter');
const Todo = require('./task')
// Model
const init = () => ({
todos: [],
newTitle: '',
view: 'all',
nextId: 0,
});
// Actions
const Action = Type({
ChangeNewTitle: [String],
Create: [],
Remove: [Object],
Modify: [Object, Todo.Action],
ToggleAll: [],
ClearDone: [],
ChangePage: [R.T],
});
// Update
// action -> model -> newModel
const update = Action.caseOn({
ChangeNewTitle: R.assoc('newTitle'),
Create: (model) => R.evolve({todos: R.append(Todo.init(model.nextId, model.newTitle)),
nextId: R.inc,
newTitle: R.always('')}, model),
Remove: (todo, model) => R.evolve({todos: R.reject(R.eq(todo))}, model),
Modify: (todo, action, model) => {
const idx = R.indexOf(todo, model.todos)
return R.evolve({todos: R.adjust(Todo.update(action), idx)}, model)
},
ToggleAll: (model) => {
const left = R.length(R.reject(R.prop('done'), model.todos)),
todoAction = left === 0 ? Todo.Action.UnsetDone() : Todo.Action.SetDone();
return R.evolve({todos: R.map(Todo.update(todoAction))}, model);
},
ClearDone: R.evolve({todos: R.reject(R.prop('done'))}),
ChangePage: MyRouter.Action.caseOn({
ViewAll: (_, model) => R.assoc('view', 'all', model),
ViewActive: (_, model) => treis(R.assoc)('view', 'active', model),
ViewCompleted: (_, model) => treis(R.assoc)('view', 'complete', model),
})
})
// View
const viewTodo = R.curry((action$, todo) => {
return Todo.view({
action$: forwardTo(action$, Action.Modify(todo)),
remove$: forwardTo(action$, R.always(Action.Remove(todo))),
}, todo)
})
const view = R.curry((action$, model) => {
const hasTodos = model.todos.length > 0,
left = R.length(R.reject(R.prop('done'), model.todos)),
filteredTodos = model.view === 'all' ? model.todos
: model.view === 'active' ? R.reject(R.prop('done'), model.todos)
: R.filter(R.prop('done'), model.todos)
return h('section.todoapp', [
h('header.header', [
h('h1', 'todos'),
h('input.new-todo', {
props: {placeholder: 'What needs to be done?',
value: model.newTitle},
on: {input: R.compose(action$, Action.ChangeNewTitle, targetValue),
keydown: ifEnter(action$, Action.Create())},
}),
]),
h('section.main', {
style: {display: hasTodos ? 'block' : 'none'}
}, [
h('input.toggle-all', {props: {type: 'checkbox'}, on: {click: [action$, Action.ToggleAll()]}}),
h('ul.todo-list', R.map(viewTodo(action$), filteredTodos)),
]),
h('footer.footer', {
style: {display: hasTodos ? 'block' : 'none'}
}, [
h('span.todo-count', [h('strong', left), ` item${left === 1 ? '' : 's'} left`]),
h('ul.filters', [
h('li', [h('a', {class: {selected: model.view === 'all'}, props: {href: '#/'}}, 'All')]),
h('li', [h('a', {class: {selected: model.view === 'active'}, props: {href: '#/active'}}, 'Active')]),
h('li', [h('a', {class: {selected: model.view === 'completed'}, props: {href: '#/completed'}}, 'Completed')]),
]),
h('button.clear-completed', {on: {click: [action$, Action.ClearDone()]}}, 'Clear completed'),
])
])
})
// Router
const MyRouter = Router.init({
history: false,
constr: Action.ChangePage,
routes: {
'/': 'ViewAll',
'/active': 'ViewActive',
'/completed': 'ViewCompleted',
},
});
// Persistence
const restoreState = () => {
const restored = JSON.parse(localStorage.getItem('state'));
return restored === null ? init() : restored;
};
const saveState = (model) => {
localStorage.setItem('state', JSON.stringify(model));
};
// Streams
const action$ = flyd.merge(MyRouter.stream, flyd.stream());
const model$ = flyd.scan(R.flip(update), restoreState(), action$)
const vnode$ = flyd.map(view(action$), model$)
flyd.map(saveState, model$);
// flyd.map((model) => console.log(model), model$); // Uncomment to log state on every update
window.addEventListener('DOMContentLoaded', function() {
const container = document.querySelector('.todoapp')
flyd.scan(patch, container, vnode$)
})