-
Notifications
You must be signed in to change notification settings - Fork 0
/
main.js
105 lines (95 loc) · 3.79 KB
/
main.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
import _ from "./libs/atomic_/core.js"; //shadow modules (see how some names have trailing underscores) support partial application without a build step
import $ from "./libs/atomic_/shell.js";
import dom from "./libs/atomic_/dom.js"; //includes its own reactives
import {reg} from "./libs/cmd.js"
import * as t from "./todo.js"; // functional core, named "t" for the domain
//create what elements?
const li = dom.tag("li"),
label = dom.tag("label"),
input = dom.tag("input"),
div = dom.tag("div"),
button = dom.tag("button"),
checkbox = dom.tag("input", {class: "toggle", type: "checkbox"});
function getId(el){
const todo = _.closest(el, "li");
return parseInt(dom.attr(todo, "data-id"));
}
//view for newly created items where a view is just a function returning elements
function todoItem(item){
const cb = checkbox();
cb.checked = item.status === "completed"
return [div({class: "view"},
cb,
label(item.title),
button({class: "destroy"})),
input({class: "entry", value: item.title})
];
}
// locate potentially matching element
_.maybe(dom.sel1("#todoapp"), function(el){
dom.addClass(el, "todoapp");
//locate fixed, relative elements
const entry = dom.sel1(".new-todo", el),
list = dom.sel1(".todo-list"),
filters = dom.sel1(".filters"),
views = dom.sel("a", filters),
count = dom.sel1(".todo-count strong"),
all = dom.sel1("#toggle-all");
//setup necessary reactives
const $state = $.atom(t.init()),
$hash = dom.hash(window),
$todo = $.map(_.get(_, "todo"), $state), //note the regular use of _ as a partial application placeholder, no build step required
$shown = $.map(t.shown, $state),
$active = $.map(_.pipe(t.active, _.count), $todo),
$total = $.map(_.count, $todo),
$nothingDone = $.map(_.eq, $active, $total);
reg({t, $state, $todo, $shown, $active, $total, $nothingDone}); //trick for temporarily exposing choice objects for interactive, REPL-driven development
function doneEditing(e){
dom.removeClass(_.closest(e.target, "[data-id]"), "editing");
$.swap($state, t.updateTodo(getId(e.target), "title", dom.value(e.target)));
}
//subscribe to reactives
$.sub($hash, _.map(_.either(_, "#/")), function(hash){
$.each(dom.removeClass(_, "selected"), views);
dom.addClass(dom.sel1(`a[href='${hash}']`), "selected");
$.swap($state, t.selectView(hash.replace("#/", "") || "all"));
});
$.sub($shown, function(shown){
dom.html(list, _.map(function(item){
return $.doto(li({"data-id": item.id}, todoItem(item)), dom.toggleClass(_, "completed", item.status === "completed"));
}, shown));
});
$.sub($active, dom.html(count, _));
$.sub($nothingDone, dom.prop(all, "checked", _));
//subscribe to dom preferring delegate events
$.on(el, "dblclick", "[data-id]", function(e){
dom.addClass(this, "editing");
const tb = dom.sel1(".entry", this);
tb.selectionStart = tb.selectionEnd = tb.value.length;
tb.focus();
});
$.on(el, "focusout", "[data-id].editing input.entry", doneEditing);
$.on(el, "keydown", "[data-id].editing input.entry", function(e){
if (e.key === "Enter"){
doneEditing(e);
}
});
$.on(el, "change", "#toggle-all", function(e){
$.swap($state, t.toggle);
});
$.on(el, "click", ".clear-completed", function(e){
$.swap($state, t.clearCompleted);
});
$.on(entry, "keydown", function(e){
if (e.key === "Enter"){
$.swap($state, t.addTodo(this.value));
this.value = "";
}
});
$.on(el, "click", "button.destroy", function(e){
$.swap($state, t.removeTodo(getId(e.target)));
});
$.on(el, "change", "li[data-id] input[type='checkbox']", function(e){
$.swap($state, t.updateTodo(getId(e.target), "status", e.target.checked ? "completed" : "active"));
});
});