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// TodoMVC behaviour on top of the server-rendered page. Every change is
2// still one of the page's own form POSTs; this script sends it with
3// fetch and an `X-Fragment: 1` header, and the server answers with the
4// fresh section.main and footer.footer, which replace the old ones.
5// Without this script the same forms submit normally.
6
7const app = document.querySelector('.todoapp');
8const header = app.querySelector('.header');
9
10let queue = Promise.resolve();
11
12function render(html) {
13 for (const el of app.querySelectorAll(':scope > .main, :scope > .footer')) el.remove();
14 header.insertAdjacentHTML('afterend', html);
15}
16
17// POSTs run one after another, in the order they were made.
18function send(form) {
19 const body = new URLSearchParams(new FormData(form));
20 queue = queue.then(async () => {
21 try {
22 const res = await fetch(form.action, {
23 method: 'POST',
24 body,
25 headers: { 'X-Fragment': '1' },
26 credentials: 'same-origin',
27 });
28 if (!res.ok) throw new Error(`${res.status}`);
29 render(await res.text());
30 } catch {
31 location.reload();
32 }
33 });
34 return queue;
35}
36
37app.addEventListener('submit', (e) => {
38 const form = e.target;
39 e.preventDefault();
40 if (form.classList.contains('edit-form')) {
41 const li = form.closest('li');
42 if (!li.classList.contains('editing')) return;
43 li.classList.remove('editing');
44 }
45 send(form);
46 const input = form.querySelector('.new-todo');
47 if (input) input.value = '';
48});
49
50// The checkboxes belong to their item's toggle form (and the toggle-all
51// form) through the `form` attribute; a change submits that form.
52app.addEventListener('change', (e) => {
53 const box = e.target;
54 if (box.matches('.toggle, .toggle-all') && box.form) box.form.requestSubmit();
55});
56
57app.addEventListener('dblclick', (e) => {
58 const label = e.target.closest('.todo-list li label');
59 if (!label) return;
60 const li = label.closest('li');
61 const input = li.querySelector('.edit');
62 input.value = label.textContent;
63 li.classList.add('editing');
64 input.focus();
65 input.setSelectionRange(input.value.length, input.value.length);
66});
67
68app.addEventListener('keydown', (e) => {
69 if (e.key !== 'Escape' || !e.target.matches('.edit')) return;
70 const li = e.target.closest('li');
71 e.target.value = li.querySelector('label').textContent;
72 li.classList.remove('editing');
73});
74
75// Leaving the editor saves, as in TodoMVC. focusout bubbles; blur doesn't.
76app.addEventListener('focusout', (e) => {
77 if (!e.target.matches('.edit')) return;
78 const li = e.target.closest('li');
79 if (li && li.classList.contains('editing')) e.target.form.requestSubmit();
80});
81