7 items left
All | Active | Completed

Source code

{%
  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="/">&larr; 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
%}