Skip to content

Commit 63e8d1e

Browse files
committed
new multi-step form example
1 parent c298d7c commit 63e8d1e

File tree

5 files changed

+141
-1
lines changed

5 files changed

+141
-1
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
- The `dynamic` component now properly displays error messages when its properties are invalid. There used to be a bug where errors would be silently ignored, making it hard to debug invalid dynamic components.
1515
- New [`sqlpage.request_method`](https://sql.ophir.dev/functions.sql?function=request_method#function) function to get the HTTP method used to access the current page. This is useful to create pages that behave differently depending on whether they are accessed with a GET request (to display a form, for instance) or a POST request (to process the form).
1616
- include the trailing semicolon as a part of the SQL statement sent to the database. This doesn't change anything in most databases, but Microsoft SQL Server requires a trailing semicolon after certain statements, such as `MERGE`. Fixes [issue #318](https://github.com/lovasoa/SQLpage/issues/318)
17+
- New `readonly` and `disabled` attributes in the [form](https://sql.ophir.dev/documentation.sql?component=form#component) component to make form fields read-only or disabled. This is useful to prevent the user from changing some fields.
1718

1819
## 0.20.5 (2024-05-07)
1920

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
-- This demonstrates how to build multi-step forms using the `form` component, and hidden inputs.
2+
select 'dynamic' as component, properties FROM example WHERE component = 'shell' LIMIT 1;
3+
4+
select 'text' as component, '
5+
# SQLPage Multi-Step Form Example
6+
7+
The example below demonstrates how to build complex multi-step forms using the [`form`](/documentation.sql?component=form#component) component.
8+
In this example, the list of cities is taken from a dynamic database table,
9+
and each step of the form is shown conditionally based on the previous step.
10+
The form has a variable number of fields: after the number of adults and children are selected,
11+
a field is shown for each passenger to enter their name.
12+
13+
See [the SQL source on GitHub](https://github.com/lovasoa/SQLpage/blob/main/examples/official-site/examples/multistep-form) for the full code.
14+
' as contents_md;
15+
16+
create temporary table if not exists cities as
17+
select 1 as id, 'Cairo' as city union all
18+
select 2, 'Delhi' union all
19+
select 3, 'Dhaka' union all
20+
select 4, 'Istanbul' union all
21+
select 5, 'Karachi' union all
22+
select 6, 'Kinshasa' union all
23+
select 7, 'Lagos' union all
24+
select 8, 'Mexico' union all
25+
select 9, 'New York City' union all
26+
select 10, 'Paris';
27+
28+
select 'form' as component, 'Book a flight' as title,
29+
case when :adults is null
30+
then 'Next'
31+
else 'Book the flight !'
32+
end as validate,
33+
case when :adults is null
34+
then ''
35+
else 'result.sql'
36+
end as action;
37+
38+
-- First step: Select origin city
39+
select 'select' as type, 'origin' as name, 'From' as label, 'Select your origin city' as placeholder, true as searchable,
40+
case when :origin is null then 12 else 6 end as width, -- The origin field takes the entire width of the form, unless it's already selected
41+
CAST(:origin AS INTEGER) as value, -- We keep the value of the origin city in the form. All form fields are submitted as text
42+
json_group_array(json_object('value', id, 'label', city)) as options
43+
from cities;
44+
45+
-- Second step: Select destination city, but only show destinations once origin is selected, and not the same as origin
46+
select 'select' as type, 'destination' as name, 'To' as label, 'Select your destination city' as placeholder, true as searchable,
47+
6 as width, -- The destination field always takes half the width of the form
48+
CAST(:destination AS INTEGER) as value, -- We keep the value of the destination city in the form
49+
json_group_array(json_object('value', id, 'label', city)) as options
50+
from cities
51+
where id != CAST(:origin AS INTEGER) -- We can't fly to the same city we're flying from
52+
having :origin is not null; -- Only show destinations once origin is selected
53+
54+
-- Third step: Select departure date and number of passengers
55+
select 'date' as type, 'departure_date' as name, 'Departure date' as label, 'When do you want to depart?' as placeholder,
56+
date('now') as min, date('now', '+6 months') as max,
57+
4 as width, -- The departure date field takes a third of the width of the form
58+
coalesce(:departure_date, date('now', '+7 days')) as value -- Default to a week from now
59+
where :destination is not null; -- Only show departure date once destination is selected
60+
61+
select 'number' as type, 'adults' as name, 'Number of Adults' as label, 'How many adults are flying?' as placeholder, 1 as min, 10 as max,
62+
coalesce(:adults, 2) as value, -- Default to 1 adult
63+
:adults is not null as readonly, -- The number of adults field is readonly once it's selected
64+
4 as width -- The number of adults field takes a third of the width of the form
65+
where :destination is not null; -- Only show number of adults once destination is selected
66+
67+
select 'number' as type, 'children' as name, 'Number of Children' as label, 'How many children are flying?' as placeholder,
68+
coalesce(:children, '0') as value, -- Default to 0 children
69+
:children is not null as readonly, -- The number of adults field is readonly once it's selected
70+
4 as width -- The number of children field takes a third of the width of the form
71+
where :destination is not null; -- Only show number of children once destination is selected
72+
73+
-- Fourth step: Enter passenger details
74+
with recursive passenger_ids as (
75+
select 0 as id, 0 as passenger_type union all
76+
select id + 1 as id,
77+
case when id < CAST(:adults AS INTEGER)
78+
then 'adult'
79+
else 'child'
80+
end as passenger_type
81+
from passenger_ids
82+
where id < CAST(:adults AS INTEGER) + CAST(:children AS INTEGER)
83+
)
84+
select 'text' as type,
85+
printf('%s_names[]', passenger_type) as name,
86+
true as required,
87+
printf('Passenger %d (%s)', id, passenger_type) as label,
88+
printf('Enter %s passenger name', passenger_type) as placeholder
89+
from passenger_ids
90+
where id>0 and :adults is not null -- Only show passenger details once number of adults and children are selected
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
select 'dynamic' as component, properties FROM example WHERE component = 'shell' LIMIT 1;
2+
3+
select 'hero' as component,
4+
'Booking confirmation' as title,
5+
'Your flight is confirmed ! We wish you a pleasant journey with *SQLPage Airlines*.' as description_md,
6+
'https://upload.wikimedia.org/wikipedia/commons/9/9a/Bl%C3%A9riot_XI_Thulin_A_Mikael_Carlson_OTT_2013_08b.jpg' as image;
7+
8+
9+
select 'datagrid' as component, 'plane-departure' as icon, 'Flight SQL-966' as title, 'Gate closes: ' || :departure_date || ' at 8AM' as description;
10+
select
11+
'Payment' as title,
12+
'processed' as description,
13+
'https://upload.wikimedia.org/wikipedia/commons/2/2a/Mastercard-logo.svg' as image_url;
14+
select
15+
'Status' as title,
16+
'Confirmed' as description,
17+
'green' as color;
18+
select
19+
'Receipt' as title,
20+
'Email sent' as description,
21+
'check' as icon,
22+
TRUE as active;
23+
select
24+
'Check-In' as title,
25+
'Online Check-In' as description,
26+
'https://ophir.dev/' as link;
27+
28+
select 'list' as component, 'Passengers' as title, 'users' as icon;
29+
select value as title, 'Adult' as description, 'user' as icon from json_each(:adult_names);
30+
select value as title, 'Child' as description, 'baby-carriage' as icon from json_each(:child_names);
31+
32+
select 'divider' as component, 'Technical details' as contents;
33+
34+
select 'text' as component, '
35+
# How is this even possible ?
36+
37+
Not a single line of *HTML*, *CSS*, *JavaScript*, *Python*, *PHP* or *Ruby* was written to implement
38+
SQLPage Airlines'' multi-step, conditional booking process. How is this even possible ?
39+
40+
The entire form and result page are dynamically generated by a few simple SQL queries, that
41+
select graphical components from [SQLPage''s component library](/documentation.sql).
42+
43+
You can find the [**full source code** of this example on GitHub](https://github.com/lovasoa/SQLpage/blob/main/examples/official-site/examples/multistep-form).
44+
' as contents_md;

examples/official-site/sqlpage/migrations/01_documentation.sql

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,8 @@ INSERT INTO parameter(component, name, description, type, top_level, optional) S
241241
('prefix_icon','Icon to display on the left side of the input field, on the same line.','ICON',FALSE,TRUE),
242242
('prefix','Text to display on the left side of the input field, on the same line.','TEXT',FALSE,TRUE),
243243
('suffix','Short text to display after th input, on the same line. Useful to add units or a currency symbol to an input.','TEXT',FALSE,TRUE),
244+
('readonly','Set to true to prevent the user from modifying the value of the input field.','BOOL',FALSE,TRUE),
245+
('disabled','Set to true to prevent the user from interacting with the input field.','BOOL',FALSE,TRUE),
244246
('id','A unique identifier for the input, which can then be used to select and manage the field with Javascript code. Usefull for advanced using as setting client side event listeners, interactive control of input field (disabled, visibility, read only, e.g.) and AJAX requests.','TEXT',FALSE,TRUE)
245247
) x;
246248
INSERT INTO example(component, description, properties) VALUES
@@ -792,6 +794,7 @@ You see the [page layouts demo](./examples/layouts.sql) for a live example of th
792794
{"title": "Examples", "submenu": [
793795
{"link": "/examples/tabs.sql", "title": "Tabs"},
794796
{"link": "/examples/layouts.sql", "title": "Layouts"},
797+
{"link": "/examples/multistep-form", "title": "Forms"},
795798
{"link": "/examples/handle_picture_upload.sql", "title": "File uploads"},
796799
{"link": "/examples/hash_password.sql", "title": "Password protection"},
797800
{"link": "//github.com/lovasoa/SQLpage/blob/main/examples/", "title": "All examples & demos"}

sqlpage/templates/form.handlebars

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,9 @@
101101
{{~#if list}}list="{{list}}" {{/if~}}
102102
{{~#if multiple}}multiple="{{multiple}}" {{/if~}}
103103
{{~#if accept}}accept="{{accept}}" {{/if~}}
104-
{{~#if autofocus}}autofocus {{/if~}}
104+
{{~#if autofocus}}autofocus {{/if~}}
105+
{{~#if disabled}}disabled {{/if~}}
106+
{{~#if readonly}}readonly {{/if~}}
105107
/>
106108
{{#if suffix}}<span class="input-group-text">{{suffix}}</span>{{/if}}
107109
</div>

0 commit comments

Comments
 (0)