carlos / todomvc-bakeoff Public

Clone
git clone https://amadan.net/carlos/todomvc-bakeoff

Plain git — no account needed to clone.

Download

Download this file

1(* The TodoMVC page, rendered from shared/reference.html. The no-JS controls
2 carry the class "nojs"; app.css hides them once app.js has loaded. *)
3
4type filter = All | Active | Completed
5
6let filter_of_string = function "active" -> Active | "completed" -> Completed | _ -> All
7let filter_name = function All -> "all" | Active -> "active" | Completed -> "completed"
8let filter_path = function All -> "/" | Active -> "/active" | Completed -> "/completed"
9
10let shows filter (t : Store.todo) =
11 match filter with All -> true | Active -> not t.completed | Completed -> t.completed
12
13let escape s =
14 let b = Buffer.create (String.length s + 16) in
15 String.iter
16 (function
17 | '&' -> Buffer.add_string b "&"
18 | '<' -> Buffer.add_string b "&lt;"
19 | '>' -> Buffer.add_string b "&gt;"
20 | '"' -> Buffer.add_string b "&quot;"
21 | '\'' -> Buffer.add_string b "&#39;"
22 | c -> Buffer.add_char b c)
23 s;
24 Buffer.contents b
25
26(* A hidden form that a control elsewhere submits with form="<id>". *)
27let action_form b ~id ~action filter =
28 Printf.bprintf b
29 {|<form id="%s" method="post" action="%s" hidden><input type="hidden" name="filter" value="%s"></form>|}
30 id action (filter_name filter)
31
32let item b filter ~editing (t : Store.todo) =
33 let title = escape t.title in
34 let classes = List.filter_map Fun.id
35 [ (if t.completed then Some "completed" else None); (if editing then Some "editing" else None) ] in
36 Printf.bprintf b {|<li data-id="%d"%s><div class="view">|} t.id
37 (if classes = [] then "" else Printf.sprintf {| class="%s"|} (String.concat " " classes));
38 Printf.bprintf b {|<input class="toggle" type="checkbox" form="toggle-%d"%s><label>%s</label>|}
39 t.id (if t.completed then " checked" else "") title;
40 Printf.bprintf b {|<button class="destroy" form="destroy-%d" aria-label="Delete"></button>|} t.id;
41 Printf.bprintf b
42 {|<button class="nojs toggle-button" data-action="toggle" form="toggle-%d">%s</button>|}
43 t.id (if t.completed then "Mark as active" else "Mark as completed");
44 Printf.bprintf b {|<a class="nojs edit-link" data-action="edit" href="%s?edit=%d">Edit</a></div>|}
45 (filter_path filter) t.id;
46 Printf.bprintf b
47 {|<form class="edit-form" method="post" action="/todos/%d/edit"><input type="hidden" name="filter" value="%s"><input class="edit" name="title" value="%s" autocomplete="off"%s></form>|}
48 t.id (filter_name filter) title (if editing then " autofocus" else "");
49 action_form b ~id:(Printf.sprintf "toggle-%d" t.id) ~action:(Printf.sprintf "/todos/%d/toggle" t.id) filter;
50 action_form b ~id:(Printf.sprintf "destroy-%d" t.id) ~action:(Printf.sprintf "/todos/%d/destroy" t.id) filter;
51 Buffer.add_string b "</li>"
52
53(* section.todoapp: the part app.js swaps after each action. *)
54let todoapp b ~filter ?editing (todos : Store.todo list) =
55 let left = List.length (List.filter (fun (t : Store.todo) -> not t.completed) todos) in
56 let done_ = List.length todos - left in
57 Printf.bprintf b
58 {|<section class="todoapp"><header class="header"><h1>todos</h1><form method="post" action="/todos"><input type="hidden" name="filter" value="%s"><input class="new-todo" name="title" placeholder="What needs to be done?" autocomplete="off"%s></form></header>|}
59 (filter_name filter) (if editing = None then " autofocus" else "");
60 if todos <> [] then begin
61 Buffer.add_string b {|<section class="main">|};
62 action_form b ~id:"toggle-all-form" ~action:"/todos/toggle-all" filter;
63 Printf.bprintf b
64 {|<input id="toggle-all" class="toggle-all" type="checkbox" form="toggle-all-form"%s><label for="toggle-all">Mark all as complete</label>|}
65 (if left = 0 then " checked" else "");
66 Buffer.add_string b
67 {|<button class="nojs toggle-all-button" data-action="toggle-all" form="toggle-all-form">Mark all as complete</button><ul class="todo-list">|};
68 List.iter
69 (fun (t : Store.todo) -> if shows filter t then item b filter ~editing:(editing = Some t.id) t)
70 todos;
71 Buffer.add_string b {|</ul></section><footer class="footer">|};
72 Printf.bprintf b {|<span class="todo-count"><strong>%d</strong> %s left</span><ul class="filters">|}
73 left (if left = 1 then "item" else "items");
74 List.iter
75 (fun (f, label) ->
76 Printf.bprintf b {|<li><a%s href="%s">%s</a></li>|}
77 (if f = filter then {| class="selected"|} else "") (filter_path f) label)
78 [ (All, "All"); (Active, "Active"); (Completed, "Completed") ];
79 Buffer.add_string b "</ul>";
80 if done_ > 0 then begin
81 action_form b ~id:"clear-completed-form" ~action:"/todos/clear-completed" filter;
82 Buffer.add_string b
83 {|<button class="clear-completed" form="clear-completed-form">Clear completed</button>|}
84 end;
85 Buffer.add_string b "</footer>"
86 end;
87 Buffer.add_string b "</section>"
88
89let fragment ~filter todos =
90 let b = Buffer.create 4096 in
91 todoapp b ~filter todos;
92 Buffer.contents b
93
94let page ~version ~filter ?editing todos =
95 let b = Buffer.create 8192 in
96 Printf.bprintf b
97 {|<!doctype html>
98<html lang="en">
99<head>
100<meta charset="utf-8">
101<meta name="viewport" content="width=device-width, initial-scale=1">
102<title>TodoMVC</title>
103<script>document.documentElement.classList.add("js")</script>
104<link rel="stylesheet" href="/static/base.css?v=%s">
105<link rel="stylesheet" href="/static/index.css?v=%s">
106<link rel="stylesheet" href="/static/app.css?v=%s">
107<script type="module" src="/static/app.js?v=%s"></script>
108</head>
109<body>
110|}
111 version version version version;
112 todoapp b ~filter ?editing todos;
113 Buffer.add_string b
114 {|
115<footer class="info">
116<p>Double-click to edit a todo</p>
117<p>Created by <a href="https://bakeoff.bfj.oncarlos.com">the CARLOS bakeoff</a> · OCaml</p>
118<p>Part of <a href="http://todomvc.com">TodoMVC</a></p>
119</footer>
120</body>
121</html>
122|};
123 Buffer.contents b
124