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

This commit is contained in:
wehub-resource-sync
2026-07-13 12:31:57 +08:00
commit d718c5a372
986 changed files with 74597 additions and 0 deletions
@@ -0,0 +1,64 @@
# Forms with multiple steps
Multi-steps forms are forms where the user has to go through multiple pages
to fill in all the information.
They are a good practice to improve the user experience
on complex forms by removing the cognitive load of filling in a long form at once.
Additionally, they allow you to validate the input at each step,
and create dynamic forms, where the next step depends on the user's input.
There are multiple ways to create forms with multiple steps in SQLPage,
which vary in the way the state of the partially filled form
is persisted between steps.
This example illustrates the main ones.
All the examples will implement the same simple form:
a form that asks for a person's name, email, and age.
## [Storing the state in hidden fields](./hidden/)
![schema](./hidden/illustration.png)
You can store the state of the partially filled form in hidden fields,
using `'hidden' as type` in the [form component](https://sql-page.com/component.sql?component=form#component).
- **advantages**
- simple to implement
- the form state is not sent to the server when the user navigates to other pages
- **disadvantages**
- the entire state is re-sent to the server on each step
- you need to reference all the previous answers in each step
- no *backwards navigation*: the user has to fill in the steps in order. If they go back to a previous step, you cannot prefill the form with the previous answers, or save the data they have already entered.
## [Storing the state in the database](./database/)
You can store the state of the partially filled form in the database,
either in the final table where you want to store the data,
or in a dedicated table that will be used to store only partial data,
allowing you to have more relaxed column constraints in the partially filled data.
- **advantages**
- the website administrator can access user inputs before they submit the final form
- the user can start filling the form on one device, and continue on another one.
- the user can have multiple partially filled forms in flight at the same time.
- **disadvantages**
- the website administrator needs to manage a dedicated table for the form state
- old partially filled forms may pile up in the database
## [Storing the state in cookies](./cookies/)
You can store each answer of the user in a cookie,
using the
[`cookie` component](https://sql-page.com/component.sql?component=cookie#component).
and retrieve it on the next step using the
[`sqlpage.cookie` function](https://sql-page.com/functions.sql?function=cookie#function).
- **advantages**
- simple to implement
- if the user leaves the form before submitting it, and returns to it later,
the state will be persisted.
- works even if some of the steps do not use the form component.
- **disadvantages**
- the entire state is re-sent to the server on each step
- the user needs to have cookies enabled to fill in the form
- if the user leaves the form before submitting it, the form state will keep being sent to all the pages he visits until he submits the form.
@@ -0,0 +1,20 @@
insert into users (
name, email, age
) values (
sqlpage.cookie('name'),
sqlpage.cookie('email'),
:age -- This is the age that was submitted from the form in step_3.sql
);
-- remove cookies
with t(name) as (values ('name'), ('email'), ('age'))
select 'cookie' as component, name, '/cookies/' as path, true as remove from t;
select
'alert' as component,
'Welcome, ' || name || '!' as title,
'You are user #' || id || '. [Create a new user](step_1.sql)' as description_md
from users where id = last_insert_rowid();
select 'list' as component, 'Existing users' as title, 'users' as value;
select name as title, email as description from users;
@@ -0,0 +1 @@
select 'redirect' as component, 'step_1.sql' as link;
@@ -0,0 +1,8 @@
select
'form' as component,
'step_2.sql' as action;
select
'name' as name,
true as required,
sqlpage.cookie ('name') as value;
@@ -0,0 +1,17 @@
select
'cookie' as component,
'name' as name,
:name as value,
'/cookies/' as path; -- Only send the cookie for pages in the /cookies/ directory
select
'form' as component,
'step_3.sql' as action;
select
'email' as name,
'email' as type,
true as required,
sqlpage.cookie ('email') as value,
'you@example.com' as placeholder,
'Hey ' || coalesce(:name, sqlpage.cookie('name')) || '! what is your email?' as description;
@@ -0,0 +1,15 @@
select
'cookie' as component,
'email' as name,
:email as value,
'/cookies/' as path;
select
'form' as component,
'finish.sql' as action;
select
'age' as name,
'number' as type,
true as required,
'How old are you, ' || sqlpage.cookie('name') || '?' as description;
@@ -0,0 +1,16 @@
update partially_filled_users set age = :age
where :age is not null and id = $id;
insert into users (name, email, age)
select name, email, age from partially_filled_users where id = $id;
delete from partially_filled_users where id = $id;
select
'alert' as component,
'Welcome, ' || name || '!' as title,
'You are user #' || id || '. [Create a new user](index.sql)' as description_md
from users where id = last_insert_rowid();
select 'list' as component, 'Existing users' as title, 'users' as value;
select name as title, email as description from users;
@@ -0,0 +1,5 @@
-- create a new empty partially_filled_users row, returning its id
insert into partially_filled_users default values
returning
'redirect' as component,
'step_1.sql?id=' || id as link;
@@ -0,0 +1,4 @@
select 'form' as component, 'step_2.sql?id=' || $id as action;
select 'name' as name, true as required, name as value
from partially_filled_users where id = $id;
@@ -0,0 +1,9 @@
update partially_filled_users set name = :name
where :name is not null and id = $id;
select 'form' as component, 'step_3.sql?id=' || $id as action;
select 'email' as name, 'email' as type, true as required, email as value,
'you@example.com' as placeholder,
'Hey ' || name || '! what is your email?' as description
from partially_filled_users where id = $id;
@@ -0,0 +1,8 @@
update partially_filled_users set email = :email
where :email is not null and id = $id;
select 'form' as component, 'finish.sql?id=' || $id as action;
select 'age' as name, 'number' as type, true as required, age as value,
'How old are you, ' || name || '?' as description
from partially_filled_users where id = $id;
@@ -0,0 +1,8 @@
services:
web:
image: lovasoa/sqlpage:main
ports:
- "8080:8080"
volumes:
- .:/var/www
- ./sqlpage:/etc/sqlpage
@@ -0,0 +1,8 @@
insert into users (name, email, age) values (:name, :email, :age)
returning
'alert' as component,
'Welcome, ' || name || '!' as title,
'You are user #' || id || '. [Create a new user](step_1.sql)' as description_md;
select 'list' as component, 'Existing users' as title, 'users' as value;
select name as title, email as description from users;
Binary file not shown.

After

Width:  |  Height:  |  Size: 486 KiB

@@ -0,0 +1 @@
select 'redirect' as component, 'step_1.sql' as link;
@@ -0,0 +1,7 @@
select
'form' as component,
'step_2.sql' as action;
select
'name' as name,
true as required;
@@ -0,0 +1,13 @@
select
'form' as component,
'step_3.sql' as action;
select
'email' as name,
'email' as type,
true as required,
'you@example.com' as placeholder,
'Hey ' || :name || '! what is your email?' as description;
with previous_answers(name, value) as (values ('name', :name))
select 'hidden' as type, name, value from previous_answers;
@@ -0,0 +1,12 @@
select
'form' as component,
'finish.sql' as action;
select
'age' as name,
'number' as type,
true as required,
'How old are you, ' || :name || '?' as description;
with previous_answers(name, value) as (values ('name', :name), ('email', :email))
select 'hidden' as type, name, value from previous_answers;
@@ -0,0 +1,7 @@
select 'list' as component, 'Forms with multiple steps' as title;
select 'Database persistence' as title, 'database' as link;
select 'Cookies' as title, 'cookies' as link;
select 'Hidden fields' as title, 'hidden' as link;
select 'text' as component, sqlpage.read_file_as_text('README.md') as contents_md;
@@ -0,0 +1,7 @@
-- Simple SQLite users table
create table users (
id integer primary key autoincrement,
name text not null,
email text not null,
age integer not null check(age > 0)
);
@@ -0,0 +1,7 @@
-- this table will store partially filled user forms
create table partially_filled_users (
id integer primary key autoincrement,
name text null, -- all fields are nullable, because the user may not have filled them yet
email text null,
age integer null check(age > 0)
);
@@ -0,0 +1,33 @@
GET http://localhost:8080/hidden/step_1.sql
HTTP 200
[Asserts]
body contains "name"
body contains "step_2.sql"
POST http://localhost:8080/hidden/step_2.sql
[FormParams]
name: Hurl User
HTTP 200
[Asserts]
body contains "Hey Hurl User! what is your email?"
body contains "step_3.sql"
POST http://localhost:8080/hidden/step_3.sql
[FormParams]
name: Hurl User
email: hurl@example.com
HTTP 200
[Asserts]
body contains "How old are you, Hurl User?"
body contains "finish.sql"
POST http://localhost:8080/hidden/finish.sql
[FormParams]
name: Hurl User
email: hurl@example.com
age: 42
HTTP 200
[Asserts]
body contains "Welcome, Hurl User!"
body contains "Existing users"
body contains "hurl@example.com"