GitHub

Adding Filters (All • Active • Completed)

Goal: Finish our TodoMVC by letting users slice the list between All, Active, and Completed items. You'll declare one more query‑parameter, adjust the #from query, and add a footer navigation that highlights the selected filter.

Estimated time: 10 minutes.

« Part 4: Deleting & Bulk Actions

1 New concepts

ConceptPurpose
#param … pattern="…" defaultDeclare and validate a query parameter (filter) in GET requests.
Context‑aware #ifDynamically add the selected CSS class to the active link.
SQL condition using bound paramsShow only the rows that match the chosen filter.

2 Declare the filter parameter

Near the top of templates/todos.pageql, right after any existing #set or #create tags, insert:


{{#param filter default='all' pattern="^(all|active|completed)$" optional}}
{{#param filter default='all' pattern="^(all|active|completed)$" optional}}

optional means the page also works without the query string.

3 Update the #from loop

Replace the simple query with a conditional one:


{{#from todos
  WHERE (:filter == 'all')
        OR (:filter == 'active'     AND completed = 0)
        OR (:filter == 'completed'  AND completed = 1)
  ORDER BY id}}
  … list‑item markup …
{{/from}}
{{#from todos WHERE (:filter == 'all') OR (:filter == 'active' AND completed = 0) OR (:filter == 'completed' AND completed = 1) ORDER BY id}} … list‑item markup … {{/from}}

The entire boolean expression is sent to SQLite; :filter is safely bound.

4 Add the filter links in the footer

Inside the <footer class="footer"> block (just after the item‑count), add:


<div class="filters">
  <a {{#if :filter == 'all'}}class="selected"{{/if}} href="/todos?filter=all">All</a> |
  <a {{#if :filter == 'active'}}class="selected"{{/if}} href="/todos?filter=active">Active</a> |
  <a {{#if :filter == 'completed'}}class="selected"{{/if}} href="/todos?filter=completed">Completed</a>
</div>
<div class="filters"> <a {{#if :filter == 'all'}}class="selected"{{/if}} href="/todos?filter=all">All</a> | <a {{#if :filter == 'active'}}class="selected"{{/if}} href="/todos?filter=active">Active</a> | <a {{#if :filter == 'completed'}}class="selected"{{/if}} href="/todos?filter=completed">Completed</a> </div>

The CSS from TodoMVC highlights the link that carries class="selected".

5 Try it out

  1. Open http://localhost:8000/todosAll is selected by default.
  2. Click Active — only incomplete items remain.
  3. Tick a few checkboxes so they disappear from the Active view, then click Completed to confirm they moved.
  4. Manually tamper with the URL (e.g. ?filter=hack) → PageQL blocks the request because it fails the regex pattern.

6 How it works

7 Wrap‑up

Congratulations 🎉 You have:

PageQL let us ship a reactive, database‑driven TodoMVC in ≈120 lines of HTML+SQL. From here you can explore:

Enjoy building with PageQL, and thanks for following the tutorial! 🎊

« Back: Part 4: Deleting & Bulk Clear Next: Part 6: Enhancing with HTMX »