{%
param filter default='all' pattern="^(all|active|completed)$";
-- Ensure the table exists (harmless if already created)
create table if not exists todos (
id INTEGER PRIMARY KEY AUTOINCREMENT,
text TEXT NOT NULL,
completed INTEGER DEFAULT 0 CHECK(completed IN (0,1))
);
let active_count = COUNT(*) from todos WHERE completed = 0;
let completed_count = COUNT(*) from todos WHERE completed = 1;
let total_count = COUNT(*) from todos;
let all_complete = (:active_count == 0 AND :total_count > 0)
%}
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>TODOMVC</title>
<style>
body { font-family: Arial, sans-serif; margin: 2rem; }
ul { list-style: none; padding: 0; }
li { margin-bottom: 0.5rem; }
li.completed label { text-decoration: line-through; color: #777; }
.filters { margin-top: 1rem; }
.filters a { text-decoration: none; margin: 0 0.5rem; }
.filters a.selected { font-weight: bold; }
.back-link {
margin-bottom: 2rem;
padding-bottom: 1rem;
border-bottom: 1px solid #ccc;
font-size: 1.8rem;
font-weight: bold;
}
.back-link a {
color: #be5028;
text-decoration: none;
}
.code-column {
overflow-x: auto;
}
/* Mobile styles */
@media (max-width: 768px) {
body {
margin: 0;
padding: 0;
}
.back-link {
margin-bottom: 1rem;
}
}
</style>
</head>
<body>
<div class="back-link">
<a href="/">← PageQL</a>
<span
style="font-size: 1.8rem; font-weight: bold; color:rgb(40, 170, 190);"
>
TODOMVC
</span>
</div>
<div>
{%if :total_count < 20%}
<input name="text" placeholder="What needs to be done?" maxlength="100" autofocus autocomplete="off"
hx-post="/todos/add" hx-trigger="keyup[key=='Enter']" hx-on:htmx:after-on-load="this.value=''">
{%end if%}
<ul>
{%from todos
WHERE (:filter == 'all')
OR (:filter == 'active' AND completed = 0)
OR (:filter == 'completed' AND completed = 1)
%}
<li {%if completed%}class="completed"{%end if%}>
<input hx-post="/todos/{{id}}/toggle" class="toggle" type="checkbox" {%if completed%}checked{%end if%}>
<label
contenteditable="false"
onclick="this.contentEditable=true;this.focus();"
onblur="this.contentEditable=false;"
onkeydown="if(event.key==='Enter'){event.preventDefault();this.blur();}"
oninput="if(this.innerText.length>100){this.innerText=this.innerText.slice(0,100);}"
hx-patch="/todos/{{id}}"
hx-trigger="blur"
hx-vals='js:{text: event.target.innerText.slice(0, 100)}'
hx-swap="none"
>{{text}}</label>
<button
hx-delete="/todos/{{id}}"
class="destroy"
style="cursor:pointer; background:none; border:none; color:#ac4a1a;"
>✕</button>
</li>
{%end from%}
</ul>
<input
id="toggle-all"
class="toggle-all"
type="checkbox"
{%if all_complete%}checked{%end if%}
hx-post="/todos/toggle_all"
>
<label for="toggle-all">Mark all as complete</label>
<span class="todo-count">
<strong>{{active_count}}</strong>
item{%if :active_count != 1%}s{%end if%} left
</span>
{%if :completed_count > 0%}
<button class="clear-completed" hx-post="/todos/clear_completed">Clear completed</button>
{%end if%}
<div class="filters">
<a {%if :filter == 'all'%}class="selected"{%end if%} href="/todos?filter=all">All</a> |
<a {%if :filter == 'active'%}class="selected"{%end if%} href="/todos?filter=active">Active</a> |
<a {%if :filter == 'completed'%}class="selected"{%end if%} href="/todos?filter=completed">Completed</a>
</div>
</div>
<h2 style="color:rgb(50, 56, 86); margin-top: 2rem; margin-bottom: 0.5rem;">Source code</h2>
{%showsource%}
</body>
</html>
{%
partial post add;
param text maxlength=100;
let current_total = COUNT(*) from todos;
if :current_total < 20;
insert into todos(text) values (:text);
end if;
end partial;
partial post :id/toggle;
update todos set completed = 1 - completed WHERE id = :id;
end partial;
partial patch :id;
param text maxlength=100;
-- Update todo text
update todos set text = :text WHERE id = :id;
end partial;
partial post toggle_all;
let active_count = COUNT(*) from todos WHERE completed = 0;
-- Set all todos completed state based on active count
update todos set completed = IIF(:active_count = 0, 0, 1);
end partial;
partial delete :id;
delete from todos WHERE id = :id;
end partial;
partial post clear_completed;
delete from todos WHERE completed = 1;
end partial
%}