chore: import upstream snapshot with attribution
CI / compile_and_lint (push) Failing after 0s
CI / docker_build (linux/amd64, -linux-amd64-duckdb, duckdb) (push) Failing after 0s
CI / docker_build (linux/arm64, -linux-arm64, minimal) (push) Failing after 2s
CI / docker_build (linux/arm64, -linux-arm64-duckdb, duckdb) (push) Failing after 1s
CI / docker_build (linux/amd64, minimal) (push) Failing after 1s
CI / test (, sqlite, sqlite::memory:) (push) Has been skipped
CI / test (mssql, mssql, mssql://root:Password123!@127.0.0.1/sqlpage) (push) Has been skipped
CI / test (mysql, mysql, mysql://root:Password123!@127.0.0.1/sqlpage) (push) Has been skipped
CI / test (oracle, oracle, Driver=Oracle 21 ODBC driver;Dbq=//127.0.0.1:1521/FREEPDB1;Uid=root;Pwd=Password123!) (push) Has been skipped
CI / test (postgres, odbc, Driver=PostgreSQL Unicode;Server=127.0.0.1;Port=5432;Database=sqlpage;UID=root;PWD=Password123!, true) (push) Has been skipped
CI / test (postgres, postgres, postgres://root:Password123!@127.0.0.1/sqlpage) (push) Has been skipped
CI / playwright (push) Has been skipped
CI / docker_build (linux/arm/v7, -linux-arm-v7, minimal) (push) Failing after 0s
CI / hurl_examples (push) Failing after 8s
deploy website / deploy_official_site (push) Failing after 1s
CI / hurl (${{ matrix.example }}) (push) Has been skipped
CI / docker_push (duckdb) (push) Has been cancelled
CI / docker_push (minimal) (push) Has been cancelled
CI / windows_test (push) Has been cancelled
CI / compile_and_lint (push) Failing after 0s
CI / docker_build (linux/amd64, -linux-amd64-duckdb, duckdb) (push) Failing after 0s
CI / docker_build (linux/arm64, -linux-arm64, minimal) (push) Failing after 2s
CI / docker_build (linux/arm64, -linux-arm64-duckdb, duckdb) (push) Failing after 1s
CI / docker_build (linux/amd64, minimal) (push) Failing after 1s
CI / test (, sqlite, sqlite::memory:) (push) Has been skipped
CI / test (mssql, mssql, mssql://root:Password123!@127.0.0.1/sqlpage) (push) Has been skipped
CI / test (mysql, mysql, mysql://root:Password123!@127.0.0.1/sqlpage) (push) Has been skipped
CI / test (oracle, oracle, Driver=Oracle 21 ODBC driver;Dbq=//127.0.0.1:1521/FREEPDB1;Uid=root;Pwd=Password123!) (push) Has been skipped
CI / test (postgres, odbc, Driver=PostgreSQL Unicode;Server=127.0.0.1;Port=5432;Database=sqlpage;UID=root;PWD=Password123!, true) (push) Has been skipped
CI / test (postgres, postgres, postgres://root:Password123!@127.0.0.1/sqlpage) (push) Has been skipped
CI / playwright (push) Has been skipped
CI / docker_build (linux/arm/v7, -linux-arm-v7, minimal) (push) Failing after 0s
CI / hurl_examples (push) Failing after 8s
deploy website / deploy_official_site (push) Failing after 1s
CI / hurl (${{ matrix.example }}) (push) Has been skipped
CI / docker_push (duckdb) (push) Has been cancelled
CI / docker_push (minimal) (push) Has been cancelled
CI / windows_test (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,108 @@
|
||||
# Todo app with SQLPage
|
||||
|
||||
This is a simple todo app implemented with SQLPage. It uses a SQLite database to store the todo items.
|
||||
(See [the PostgreSQL version](<../todo%20application%20(PostgreSQL)/README.md>))
|
||||
|
||||

|
||||
|
||||
It is meant as an illustrative example of how to use SQLPage to create a simple CRUD application.
|
||||
|
||||
## Structure
|
||||
|
||||
### [`index.sql`](./index.sql)
|
||||
|
||||
This is the main file of the application.
|
||||
It will be loaded when the user visits the root of the application
|
||||
(`http://localhost:8080/` when running this example locally).
|
||||
|
||||
In order, it uses:
|
||||
|
||||
- the [`dynamic`](https://sql-page.com/documentation.sql?component=dynamic#component) component to load the [`shell.sql`](#shellsql) file that will be used at the top of every page
|
||||
in the application to create a consistent layout and top bar.
|
||||
- the [`list`](https://sql-page.com/documentation.sql?component=list#component) component to display the list of todo items.
|
||||
- the [`button`](https://sql-page.com/documentation.sql?component=button#component) component to create a button that will redirect the user to the [`todo_form.sql`](#todo_formsql) page to create a new todo item when clicked.
|
||||
|
||||
### [`todo_form.sql`](./todo_form.sql)
|
||||
|
||||
This file is used to create a new todo item or edit an existing one.
|
||||
|
||||
It uses:
|
||||
|
||||
1. the [`redirect`](https://sql-page.com/documentation.sql?component=redirect#component) component to redirect the user back to the [`index.sql`](#indexsql) page after the form is submitted.
|
||||
1. the [`dynamic`](https://sql-page.com/documentation.sql?component=dynamic#component) component to load [`shell.sql`](#shellsql) to create a consistent layout and top bar.
|
||||
1. the [`form`](https://sql-page.com/documentation.sql?component=form#component) component to create a form with fields for the title and description of the todo item.
|
||||
|
||||
The order of the components is important, as the `redirect` component cannot be used after the page has been displayed. It is called first to ensure that the user is redirected immediately after submitting the form. It is guarded by a `WHERE :todo_id IS NOT NULL` clause to ensure that it only redirects when
|
||||
the form was submitted, not when the page is
|
||||
initially loaded by the user in their browser.
|
||||
|
||||

|
||||
|
||||
### [`delete.sql`](./delete.sql)
|
||||
|
||||
This file is used to delete a todo item.
|
||||
|
||||
It contains a delete statement guarded by a
|
||||
`WHERE $confirm = 'yes'` clause.
|
||||
So, the delete is not executed when the page
|
||||
is initially loaded, but only when the user
|
||||
clicks the "Yes" button, which contains a link
|
||||
pointing to the same page with the `confirm=yes` query parameter.
|
||||
|
||||
The detailed step by step explanation of the delete process is as follows:
|
||||
|
||||
- From the `index.sql` page, the user clicks the 'Delete' button on a todo item
|
||||
- It loads the page `/delete.sql?todo_id=7` (without the `confirm=yes` parameter)
|
||||
- the delete statement **is** sent to the database and executed. SQLPage has bound the values to URL query parameters, so we have
|
||||
- `$todo_id` bound to `'7'`, and
|
||||
- `$confirm` bound to `NULL` (since there was no `confirm` parameter in the url)
|
||||
- the database evaluates the `where id = $todo_id and $confirm = 'yes'` condition to FALSE
|
||||
- so it deletes nothing, and returns nothing
|
||||
- SQLPage receives no row back from the database, it continues processing normally
|
||||
- it executes the `select 'dynamic' ...` query, which itself requires executing the `shell.sql` file. The result of this is a row that contains `component=dynamic` and `properties={"component": "shell", "title": "My Todo App", ... }`
|
||||
- it renders the page header with the application header and the top bar following the results of the query
|
||||
- it sends to the database the last query: `select 'alert' as component, ... from todos where id = $todo_id` it binds the parameters like before
|
||||
- `$todo_id` bound to `'7'`
|
||||
- the database returns a single row, containing `component=alert`, `description_md=Are you sure [...] [the title of the todo item with id 7]`, ...
|
||||
- SQLPage returns the the `alert` component with its contents to the browser
|
||||
- The user sees the confirmation alert and clicks the 'Delete' button
|
||||
- The page is reloaded, this time with the URL `/delete.sql?todo_id=7&confirm=yes`
|
||||
- the delete statement is sent to the database and executed like last time. But this time SQLPage has bound the values to the new URL query parameters,
|
||||
- `$todo_id` bound to `'7'`, (like before)
|
||||
- `$confirm` bound to `'yes'` (since there is now a `confirm` parameter in the url)
|
||||
- the database evaluates the `where id = $todo_id and $confirm = 'yes'` condition to TRUE
|
||||
- so it deletes the todo item with id 7 and, as instructed by the `returning` clause, returns a single row containing `component=redirect`, `link=/`
|
||||
- SQLPage receives the row back from the database, and immediately returns sends a 302 redirect response to the browser, redirecting the user to the `/` page.
|
||||
- The following queries are not executed, as the page is redirected before they are processed.
|
||||
|
||||
### [`shell.sql`](./shell.sql)
|
||||
|
||||
This file is not meant to be accessed directly by the user (it would display an empty page with only the top bar).
|
||||
|
||||
But it is included from all the other pages to
|
||||
call the [`shell`](https://sql-page.com/documentation.sql?component=shell#component) component with the exact same parameters on every page.
|
||||
|
||||
It is included everywhere using the [`dynamic`](https://sql-page.com/documentation.sql?component=dynamic#component) component and the [`sqlpage.run_sql`](https://sql-page.com/functions.sql?function=run_sql#function) function.
|
||||
|
||||
## Running the example
|
||||
|
||||
To run the example, simply [download the latest SQLPage release](https://github.com/sqlpage/SQLPage/releases) and run it from the root folder of the example.
|
||||
|
||||
## SQLPage features used
|
||||
|
||||
This example is meant to illustrate many
|
||||
of the common features of SQLPage.
|
||||
|
||||
### Components
|
||||
|
||||
- [list](https://sql-page.com/documentation.sql?component=list#component)
|
||||
- [button](https://sql-page.com/documentation.sql?component=button#component)
|
||||
- [form](https://sql-page.com/documentation.sql?component=form#component)
|
||||
- [redirect](https://sql-page.com/documentation.sql?component=redirect#component)
|
||||
- [shell](https://sql-page.com/documentation.sql?component=shell#component)
|
||||
- [timeline](https://sql-page.com/documentation.sql?component=timeline#component)
|
||||
- [dynamic](https://sql-page.com/documentation.sql?component=timeline#component)
|
||||
|
||||
### Functions
|
||||
|
||||
- [sqlpage.run_sql](https://sql-page.com/functions.sql?function=run_sql#function)
|
||||
Reference in New Issue
Block a user