GitHub

Deleting & Bulk Actions (completing CRUD)

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.

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 <form> to the view version of the <li> (the one rendered when not editing):


<li {{#if completed}}class="completed"{{/if}}>
  <form method="POST" action="/todos/toggle" style="display:inline">
    <input type="hidden" name="id" value="{{id}}">
    <input class="toggle" type="checkbox" {{#if completed}}checked{{/if}} onchange="this.form.submit();">
  </form>
  <label ondblclick="window.location='/todos?edit_id={{id}}'">{{text}}</label>
  <form method="POST" action="/todos/delete" style="display:inline">
    <input type="hidden" name="id" value="{{id}}">
    <button class="destroy" style="cursor:pointer; background:none; border:none; color:#ac4a1a;">✕</button>
  </form>
</li>
<li {{#if completed}}class="completed"{{/if}}> <form method="POST" action="/todos/toggle" style="display:inline"> <input type="hidden" name="id" value="{{id}}"> <input class="toggle" type="checkbox" {{#if completed}}checked{{/if}} onchange="this.form.submit();"> </form> <label ondblclick="window.location='/todos?edit_id={{id}}'">{{text}}</label> <form method="POST" action="/todos/delete" style="display:inline"> <input type="hidden" name="id" value="{{id}}"> <button class="destroy" style="cursor:pointer; background:none; border:none; color:#ac4a1a;">✕</button> </form> </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}}
  <form method="POST" action="/todos/clear_completed" style="display:inline">
    <button class="clear-completed">Clear completed</button>
  </form>
{{/if}}
{{#if :completed_count > 0}} <form method="POST" action="/todos/clear_completed" style="display:inline"> <button class="clear-completed">Clear completed</button> </form> {{/if}}

3 New public partials

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


{{#partial public delete}}
  {{#param id required type=integer min=1}}
  {{#delete from todos WHERE id = :id}}
  {{#redirect '/todos'}}
{{/partial}}

{{#partial public clear_completed}}
  {{#delete from todos WHERE completed = 1}}
  {{#redirect '/todos'}}
{{/partial}}
{{#partial public delete}} {{#param id required type=integer min=1}} {{#delete from todos WHERE id = :id}} {{#redirect '/todos'}} {{/partial}} {{#partial public clear_completed}} {{#delete from todos WHERE completed = 1}} {{#redirect '/todos'}} {{/partial}}

Notice: there is no explicit BEGIN/COMMIT. If an error occurs before the #redirect, 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—page reloads with 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)
└─ 302 Redirect /todos ─────────────┘

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 »