GitHub

Deleting & Bulk Actions (reactive)

Goal: Finish the CRUD circle—remove single Todos and clear all completed items. You'll meet #delete, revisit multi‑row #update, and learn how PageQL wraps all mutations of one request in a single transaction. The browser reflects changes instantly via hx-* actions.

Estimated time: 10 minutes.

« Part 3: Updating State

1 New concepts

ConceptPurpose
#deletePermanently removes rows that match a WHERE clause.
Bulk write queries#update or #delete without WHERE id = … affect many rows at once.
Implicit transactionPageQL opens a database transaction at the first data‑modifying tag and commits on success; if any tag fails, everything rolls back automatically.

2 Make "delete" reachable in the UI

2.1 Destroy button inside each list item

Add the italicised hx-* attributes to the view version of the <li> (the one rendered when not editing):


<li {{#if completed}}class="completed"{{/if}}>
  <input hx-post="/todos/{{id}}/toggle" class="toggle" type="checkbox" {{#if completed}}checked{{/if}}>
  <label hx-get="/?edit_id={{id}}">{{text}}</label>
  <button hx-delete="/todos/{{id}}" class="destroy" style="cursor:pointer; background:none; border:none; color:#ac4a1a;">✕</button>
</li>
<li {{#if completed}}class="completed"{{/if}}> <input hx-post="/todos/{{id}}/toggle" class="toggle" type="checkbox" {{#if completed}}checked{{/if}}> <label hx-get="/?edit_id={{id}}">{{text}}</label> <button hx-delete="/todos/{{id}}" class="destroy" style="cursor:pointer; background:none; border:none; color:#ac4a1a;">✕</button> </li>

(The destroy class is defined by TodoMVC CSS and draws an × icon.)

2.2 Clear‑completed button in the footer

Replace or add this inside the existing <footer class="footer"> block after the filter links:


{{#if :completed_count > 0}}
  <button class="clear-completed" hx-post="/todos/clear_completed">Clear completed</button>
{{/if}}
{{#if :completed_count > 0}} <button class="clear-completed" hx-post="/todos/clear_completed">Clear completed</button> {{/if}}

3 New public partials

Append to the bottom of your actions file (or inline):


{{#partial delete :id}}
  {{#delete from todos WHERE id = :id}}
{{/partial}}

{{#partial post clear_completed}}
  {{#delete from todos WHERE completed = 1}}
{{/partial}}
{{#partial delete :id}} {{#delete from todos WHERE id = :id}} {{/partial}} {{#partial post clear_completed}} {{#delete from todos WHERE completed = 1}} {{/partial}}

Notice: there is no explicit BEGIN/COMMIT. If an error occurs during the partial, PageQL aborts the whole request and rolls back the delete.

This highlights one of PageQL's key features - all data-modifying operations within a single request are automatically wrapped in a transaction. This ensures that your database remains in a consistent state even if an error occurs during processing.

4 Try it out

  1. Complete a couple of todos, then click Clear completed—the list updates instantly showing only active items.
  2. Click the × next to a todo—item vanishes.
  3. Deliberately break the SQL inside clear_completed (e.g., change the table name to todoz) and test again ➜ all rows survive; the error page shows the failed query, proving rollback.

5 Under the hood: request transaction timeline

┌─ HTTP POST /todos/clear_completed ─┐
│  BEGIN TRANSACTION                │  (implicit)
│  DELETE FROM todos WHERE …        │  ← #delete
│  COMMIT                           │  (on success)
└─ UI updates automatically ────────┘

Multiple #insert, #update, and #delete tags in one partial still share one transaction boundary—handy for complex wizard‑style forms.

This automatic transaction handling is particularly valuable for operations that need to maintain data integrity, such as forms that update multiple tables at once or operations that have multiple steps that must either all succeed or all fail.

6 Recap & next step

« Back: Part 3: Updating State Next: Part 5: Adding Filters »