From ef8c14813c7fb23d40a711822793f3a7c55ecfab Mon Sep 17 00:00:00 2001 From: Olivier Auverlot Date: Wed, 5 Aug 2026 18:48:08 +0200 Subject: [PATCH 1/3] New partition component --- .../sqlpage/migrations/77_partition.sql | 137 ++++++++++++++++++ .../99_shared_id_class_attributes.sql | 6 +- sqlpage/templates/partition.handlebars | 31 ++++ 3 files changed, 172 insertions(+), 2 deletions(-) create mode 100644 examples/official-site/sqlpage/migrations/77_partition.sql create mode 100644 sqlpage/templates/partition.handlebars diff --git a/examples/official-site/sqlpage/migrations/77_partition.sql b/examples/official-site/sqlpage/migrations/77_partition.sql new file mode 100644 index 00000000..f84b563a --- /dev/null +++ b/examples/official-site/sqlpage/migrations/77_partition.sql @@ -0,0 +1,137 @@ +INSERT INTO component(name, icon, description, introduced_in_version) VALUES + ('partition', 'stack-front', ' +Provides a mechanism to define custom pagination rules. +It allows splitting records on the generated page based on a user-specified partitioning criterion, +ensuring controlled data distribution across pages. + +The `partition` component is typically used alongside the [table](?component=table) component to define which rows are displayed. +* The component uses the GET method to send the active partition selection parameters to the server. +* `compact` overrides the default link-based display when enabled. +* `all_link` and `all_title` work together to provide a fallback for viewing all data in a single view. +* `id` and `class` are standard HTML attributes for styling and DOM manipulation. + +The SQL query below uses the [Chinook](https://www.sqlitetutorial.net/sqlite-sample-database) SQLite database. In this example, we use the partition component +to display the music albums of an artist. The `$id` parameter passed in the URL corresponds to the primary key +of the selected artist. + +```sql +select ''table'' as component; +select title +from + albums alb, + artists art +where + alb.ArtistId = art.artistid +and (($id IS NULL) or ($id IS NOT NULL and art.artistid = $id)) + +select + ''partition'' as component, + ''Artists'' as description, + ''?component=partition'' as all_link, + ($id IS NULL) as all_active; +select distinct + art.Name as title, + concat(''?component=partition&id='',art.artistid) as link, + (art.artistid = $id) as active +from + artists art, + albums alb +where + alb.ArtistId = art.artistid; +``` +', '0.46.0'); + +INSERT INTO parameter(component, name, description, type, top_level, optional) SELECT 'partition', * FROM (VALUES + -- Top-level parameters + ('description','Specifies the type of partitions. In compact mode, if none, the placeholder text "Choose partition" is displayed.','TEXT',TRUE,TRUE), + ('compact','Allows selecting the partition display mode. If the compact attribute is set to TRUE, partitions are displayed in a dropdown list. By default, partitions are shown as links.','BOOLEAN',TRUE,TRUE), + ('all_link','Add a link to display all data. If none, the link is not displayed.','URL',TRUE,TRUE), + ('all_title','Text used for the link to display all data. If none, the placeholder "ALL" is displayed.','TEXT',TRUE,TRUE), + ('all_active','Whether the link to display all data is active or not. Defaults to false.','TEXT',TRUE,TRUE), + -- Item-level parameters (for each page) + ('title','Partition title.','TEXT',FALSE,FALSE), + ('link','A target URL to which the user should be redirected to view the requested partition.','URL',FALSE,FALSE), + ('active','Whether the link is active or not. Defaults to false.','BOOLEAN',FALSE,TRUE) +) x; + + +-- Insert example(s) for the component +INSERT INTO example(component, description, properties) +VALUES ( + 'partition', + 'Classification of starships in Star Trek. Class selection is performed using links.', + JSON( + '[ + { + "component": "table" + }, + { + "name": "USS Enterprise (NCC-1701)", + "class": "Constitution" + }, + { + "name": "USS Exeter (NCC-1672)", + "class": "Galaxy" + }, + { + "name": "USS Exeter (NCC-1672)", + "class": "Constitution" + }, + { + "component": "partition", + "description": "Classes", + "all_link": "?component=partition", + "all_title": "All", + "all_active": true + }, + { + "title": "Constitution", + "link": "?component=partition&class=1", + }, + { + "title": "Galaxy", + "link": "?component=partition&class=2" + } + ]' + ) + ), + ( + 'partition', + 'The second example uses the compact display mode for partition names. This mode is suitable when many partitions are available and for display on mobile devices.', + JSON( + '[ + { + "component": "table" + }, + { + "name": "USS Enterprise (NCC-1701)", + "class": "Constitution" + }, + { + "name": "USS Exeter (NCC-1672)", + "class": "Galaxy" + }, + { + "name": "USS Exeter (NCC-1672)", + "class": "Constitution" + }, + { + "component": "partition", + "description": "Classes", + "all_link": "?component=partition", + "all_title": "All", + "all_active": true, + "compact": true + }, + { + "title": "Constitution", + "link": "?component=partition&class=1", + }, + { + "title": "Galaxy", + "link": "?component=partition&class=2" + } + ]' + ) + ); + diff --git a/examples/official-site/sqlpage/migrations/99_shared_id_class_attributes.sql b/examples/official-site/sqlpage/migrations/99_shared_id_class_attributes.sql index 6c2a9bf1..274f6e36 100644 --- a/examples/official-site/sqlpage/migrations/99_shared_id_class_attributes.sql +++ b/examples/official-site/sqlpage/migrations/99_shared_id_class_attributes.sql @@ -39,7 +39,8 @@ FROM (VALUES ('text', TRUE), ('carousel', TRUE), ('login', TRUE), - ('pagination', TRUE) + ('pagination', TRUE), + ('partition', TRUE) ); INSERT INTO parameter(component, top_level, name, description, type, optional) @@ -78,6 +79,7 @@ FROM (VALUES ('tracking', TRUE), ('carousel', TRUE), ('login', TRUE), - ('pagination', TRUE) + ('pagination', TRUE), + ('partition', TRUE) ); diff --git a/sqlpage/templates/partition.handlebars b/sqlpage/templates/partition.handlebars new file mode 100644 index 00000000..d38b083e --- /dev/null +++ b/sqlpage/templates/partition.handlebars @@ -0,0 +1,31 @@ + + + + + + + From dcebb1f9a98236ff1e362fbe900a9d655647260e Mon Sep 17 00:00:00 2001 From: Ophir Lojkine Date: Wed, 26 Aug 2026 15:55:31 +0000 Subject: [PATCH 2/3] feat(facet): add filter navigation component --- CHANGELOG.md | 1 + .../sqlpage/migrations/77_facet.sql | 124 ++++++++++++++++ .../sqlpage/migrations/77_partition.sql | 137 ------------------ .../99_shared_id_class_attributes.sql | 5 +- sqlpage/templates/facet.handlebars | 25 ++++ sqlpage/templates/partition.handlebars | 31 ---- .../component_rendering/facet.sql | 6 + 7 files changed, 158 insertions(+), 171 deletions(-) create mode 100644 examples/official-site/sqlpage/migrations/77_facet.sql delete mode 100644 examples/official-site/sqlpage/migrations/77_partition.sql create mode 100644 sqlpage/templates/facet.handlebars delete mode 100644 sqlpage/templates/partition.handlebars create mode 100644 tests/sql_test_files/component_rendering/facet.sql diff --git a/CHANGELOG.md b/CHANGELOG.md index 555cee9a..8c059f45 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ - The cast is retained on SQLite and on ODBC connections to PostgreSQL, SQLite, Oracle, Snowflake and other databases where it is needed for correct comparisons. - AWS Lambda builds and documentation now use the supported Amazon Linux 2023 custom runtime instead of the end-of-life Amazon Linux 2 runtime. Release artifacts include the configuration directory required on Lambda's read-only filesystem. - Added a `toast` component with plain-text or Markdown content, icons, colors, six screen placements, configurable auto-dismiss timing, optional manual dismissal, URL-fragment triggers, and automatic stacking of queued notifications. + - Added a `facet` component for SQL-generated category filters, with inline links or a compact dropdown and an optional link to clear the filter. - `sqlpage.send_mail` now supports rich email bodies. Use `body_html` for a caller-provided HTML alternative, or `body_md` to render Markdown as HTML. Messages retain a plain-text alternative; `body` may be omitted when `body_md` is used, and `body_md` and `body_html` cannot be combined. - Form `options_source` URLs now preserve existing query parameters when adding the dynamic `search` parameter. - Searchable single-select form fields now close their dropdown after an option is selected. diff --git a/examples/official-site/sqlpage/migrations/77_facet.sql b/examples/official-site/sqlpage/migrations/77_facet.sql new file mode 100644 index 00000000..c9764b1e --- /dev/null +++ b/examples/official-site/sqlpage/migrations/77_facet.sql @@ -0,0 +1,124 @@ +INSERT INTO component(name, icon, description, introduced_in_version) VALUES + ('facet', 'filter', ' +Navigation links for filtering a dataset by a category, status, owner, or other attribute. + +This component only renders the filter navigation. **Your SQL query is responsible for filtering the data** based on the URL parameter selected by the user. + +Use it alongside a [table](?component=table), [list](?component=list), or [card](?component=card). The links use GET parameters, so the selected filter can be bookmarked and shared. + +Set `compact` to display the facets in a dropdown, which is useful for a moderate number of choices or on narrow screens. `all_link` and `all_title` add a link that clears the filter. + +The following portable pattern filters a table by category. `sqlpage.link` preserves the current page path while safely generating the URL. + +```sql +select ''table'' as component; +select title, category +from my_table +where $category is null or category = $category; + +select ''facet'' as component, + ''Category'' as description, + sqlpage.link(sqlpage.path(), json_object(''category'', null)) as all_link, + $category is null as all_active; +select distinct category as title, + sqlpage.link(sqlpage.path(), json_object(''category'', category)) as link, + category = $category as active +from my_table +order by category; +``` +', '0.46.0'); + +INSERT INTO parameter(component, name, description, type, top_level, optional) SELECT 'facet', * FROM (VALUES + -- Top-level parameters + ('description','The facet category label. In compact mode, it is shown on the dropdown button. If omitted, the button displays "Choose facet".','TEXT',TRUE,TRUE), + ('compact','Displays the facets in a dropdown instead of links.','BOOLEAN',TRUE,TRUE), + ('all_link','URL that clears the filter. If omitted, no All link is displayed.','URL',TRUE,TRUE), + ('all_title','Text for the link that clears the filter. Defaults to "ALL".','TEXT',TRUE,TRUE), + ('all_active','Whether the link that clears the filter is active. Defaults to false.','BOOLEAN',TRUE,TRUE), + -- Item-level parameters (for each facet) + ('title','Facet title.','TEXT',FALSE,FALSE), + ('link','URL for the facet.','URL',FALSE,FALSE), + ('active','Whether the link is active or not. Defaults to false.','BOOLEAN',FALSE,TRUE) +) x; + +-- Insert example(s) for the component +INSERT INTO example(component, description, properties) +VALUES ( + 'facet', + 'A category selector with an All link. In an application, the SQL pattern above uses the chosen URL parameter to filter the displayed data.', + JSON( + '[ + { + "component": "table" + }, + { + "name": "USS Enterprise (NCC-1701)", + "class": "Constitution" + }, + { + "name": "USS Exeter (NCC-1672)", + "class": "Galaxy" + }, + { + "name": "USS Exeter (NCC-1672)", + "class": "Constitution" + }, + { + "component": "facet", + "description": "Classes", + "all_link": "?component=facet", + "all_title": "All", + "all_active": false + }, + { + "title": "Constitution", + "link": "?component=facet&class=Constitution", + "active": true + }, + { + "title": "Galaxy", + "link": "?component=facet&class=Galaxy" + } + ]' + ) + ), + ( + 'facet', + 'Compact mode displays the facet choices in a dropdown.', + JSON( + '[ + { + "component": "table" + }, + { + "name": "USS Enterprise (NCC-1701)", + "class": "Constitution" + }, + { + "name": "USS Exeter (NCC-1672)", + "class": "Galaxy" + }, + { + "name": "USS Exeter (NCC-1672)", + "class": "Constitution" + }, + { + "component": "facet", + "description": "Classes", + "all_link": "?component=facet", + "all_title": "All", + "all_active": false, + "compact": true + }, + { + "title": "Constitution", + "link": "?component=facet&class=Constitution", + "active": true + }, + { + "title": "Galaxy", + "link": "?component=facet&class=Galaxy" + } + ]' + ) + ); diff --git a/examples/official-site/sqlpage/migrations/77_partition.sql b/examples/official-site/sqlpage/migrations/77_partition.sql deleted file mode 100644 index f84b563a..00000000 --- a/examples/official-site/sqlpage/migrations/77_partition.sql +++ /dev/null @@ -1,137 +0,0 @@ -INSERT INTO component(name, icon, description, introduced_in_version) VALUES - ('partition', 'stack-front', ' -Provides a mechanism to define custom pagination rules. -It allows splitting records on the generated page based on a user-specified partitioning criterion, -ensuring controlled data distribution across pages. - -The `partition` component is typically used alongside the [table](?component=table) component to define which rows are displayed. -* The component uses the GET method to send the active partition selection parameters to the server. -* `compact` overrides the default link-based display when enabled. -* `all_link` and `all_title` work together to provide a fallback for viewing all data in a single view. -* `id` and `class` are standard HTML attributes for styling and DOM manipulation. - -The SQL query below uses the [Chinook](https://www.sqlitetutorial.net/sqlite-sample-database) SQLite database. In this example, we use the partition component -to display the music albums of an artist. The `$id` parameter passed in the URL corresponds to the primary key -of the selected artist. - -```sql -select ''table'' as component; -select title -from - albums alb, - artists art -where - alb.ArtistId = art.artistid -and (($id IS NULL) or ($id IS NOT NULL and art.artistid = $id)) - -select - ''partition'' as component, - ''Artists'' as description, - ''?component=partition'' as all_link, - ($id IS NULL) as all_active; -select distinct - art.Name as title, - concat(''?component=partition&id='',art.artistid) as link, - (art.artistid = $id) as active -from - artists art, - albums alb -where - alb.ArtistId = art.artistid; -``` -', '0.46.0'); - -INSERT INTO parameter(component, name, description, type, top_level, optional) SELECT 'partition', * FROM (VALUES - -- Top-level parameters - ('description','Specifies the type of partitions. In compact mode, if none, the placeholder text "Choose partition" is displayed.','TEXT',TRUE,TRUE), - ('compact','Allows selecting the partition display mode. If the compact attribute is set to TRUE, partitions are displayed in a dropdown list. By default, partitions are shown as links.','BOOLEAN',TRUE,TRUE), - ('all_link','Add a link to display all data. If none, the link is not displayed.','URL',TRUE,TRUE), - ('all_title','Text used for the link to display all data. If none, the placeholder "ALL" is displayed.','TEXT',TRUE,TRUE), - ('all_active','Whether the link to display all data is active or not. Defaults to false.','TEXT',TRUE,TRUE), - -- Item-level parameters (for each page) - ('title','Partition title.','TEXT',FALSE,FALSE), - ('link','A target URL to which the user should be redirected to view the requested partition.','URL',FALSE,FALSE), - ('active','Whether the link is active or not. Defaults to false.','BOOLEAN',FALSE,TRUE) -) x; - - --- Insert example(s) for the component -INSERT INTO example(component, description, properties) -VALUES ( - 'partition', - 'Classification of starships in Star Trek. Class selection is performed using links.', - JSON( - '[ - { - "component": "table" - }, - { - "name": "USS Enterprise (NCC-1701)", - "class": "Constitution" - }, - { - "name": "USS Exeter (NCC-1672)", - "class": "Galaxy" - }, - { - "name": "USS Exeter (NCC-1672)", - "class": "Constitution" - }, - { - "component": "partition", - "description": "Classes", - "all_link": "?component=partition", - "all_title": "All", - "all_active": true - }, - { - "title": "Constitution", - "link": "?component=partition&class=1", - }, - { - "title": "Galaxy", - "link": "?component=partition&class=2" - } - ]' - ) - ), - ( - 'partition', - 'The second example uses the compact display mode for partition names. This mode is suitable when many partitions are available and for display on mobile devices.', - JSON( - '[ - { - "component": "table" - }, - { - "name": "USS Enterprise (NCC-1701)", - "class": "Constitution" - }, - { - "name": "USS Exeter (NCC-1672)", - "class": "Galaxy" - }, - { - "name": "USS Exeter (NCC-1672)", - "class": "Constitution" - }, - { - "component": "partition", - "description": "Classes", - "all_link": "?component=partition", - "all_title": "All", - "all_active": true, - "compact": true - }, - { - "title": "Constitution", - "link": "?component=partition&class=1", - }, - { - "title": "Galaxy", - "link": "?component=partition&class=2" - } - ]' - ) - ); - diff --git a/examples/official-site/sqlpage/migrations/99_shared_id_class_attributes.sql b/examples/official-site/sqlpage/migrations/99_shared_id_class_attributes.sql index 274f6e36..46d1c041 100644 --- a/examples/official-site/sqlpage/migrations/99_shared_id_class_attributes.sql +++ b/examples/official-site/sqlpage/migrations/99_shared_id_class_attributes.sql @@ -40,7 +40,7 @@ FROM (VALUES ('carousel', TRUE), ('login', TRUE), ('pagination', TRUE), - ('partition', TRUE) + ('facet', TRUE) ); INSERT INTO parameter(component, top_level, name, description, type, optional) @@ -80,6 +80,5 @@ FROM (VALUES ('carousel', TRUE), ('login', TRUE), ('pagination', TRUE), - ('partition', TRUE) + ('facet', TRUE) ); - diff --git a/sqlpage/templates/facet.handlebars b/sqlpage/templates/facet.handlebars new file mode 100644 index 00000000..43f24bd4 --- /dev/null +++ b/sqlpage/templates/facet.handlebars @@ -0,0 +1,25 @@ + diff --git a/sqlpage/templates/partition.handlebars b/sqlpage/templates/partition.handlebars deleted file mode 100644 index d38b083e..00000000 --- a/sqlpage/templates/partition.handlebars +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - - diff --git a/tests/sql_test_files/component_rendering/facet.sql b/tests/sql_test_files/component_rendering/facet.sql new file mode 100644 index 00000000..dac1e0e8 --- /dev/null +++ b/tests/sql_test_files/component_rendering/facet.sql @@ -0,0 +1,6 @@ +SELECT 'facet' AS component, 'Categories' AS description, '/all' AS all_link, 'All categories' AS all_title, 0 AS all_active; +SELECT 'It works !' AS title, '/active' AS link, 1 AS active; +SELECT 'Other category' AS title, '/other' AS link, 0 AS active; + +SELECT 'facet' AS component, 'Categories' AS description, 1 AS compact, '/all' AS all_link, 1 AS all_active; +SELECT 'It works !' AS title, '/active' AS link, 0 AS active; From 0d3647ce8bb851be7cb05346c882f79f76aa091d Mon Sep 17 00:00:00 2001 From: Ophir Lojkine Date: Wed, 26 Aug 2026 20:11:05 +0000 Subject: [PATCH 3/3] feat(facet): show active compact selection --- CHANGELOG.md | 2 +- examples/official-site/sqlpage/migrations/77_facet.sql | 8 +++++--- sqlpage/templates/facet.handlebars | 2 +- tests/sql_test_files/component_rendering/facet.sql | 2 +- 4 files changed, 8 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8c059f45..ff948e62 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,7 +9,7 @@ - The cast is retained on SQLite and on ODBC connections to PostgreSQL, SQLite, Oracle, Snowflake and other databases where it is needed for correct comparisons. - AWS Lambda builds and documentation now use the supported Amazon Linux 2023 custom runtime instead of the end-of-life Amazon Linux 2 runtime. Release artifacts include the configuration directory required on Lambda's read-only filesystem. - Added a `toast` component with plain-text or Markdown content, icons, colors, six screen placements, configurable auto-dismiss timing, optional manual dismissal, URL-fragment triggers, and automatic stacking of queued notifications. - - Added a `facet` component for SQL-generated category filters, with inline links or a compact dropdown and an optional link to clear the filter. + - Added a `facet` component for SQL-generated category filters, with inline links or a compact dropdown, an optional link to clear the filter, and a configurable compact-mode title. - `sqlpage.send_mail` now supports rich email bodies. Use `body_html` for a caller-provided HTML alternative, or `body_md` to render Markdown as HTML. Messages retain a plain-text alternative; `body` may be omitted when `body_md` is used, and `body_md` and `body_html` cannot be combined. - Form `options_source` URLs now preserve existing query parameters when adding the dynamic `search` parameter. - Searchable single-select form fields now close their dropdown after an option is selected. diff --git a/examples/official-site/sqlpage/migrations/77_facet.sql b/examples/official-site/sqlpage/migrations/77_facet.sql index c9764b1e..f64b0cc7 100644 --- a/examples/official-site/sqlpage/migrations/77_facet.sql +++ b/examples/official-site/sqlpage/migrations/77_facet.sql @@ -6,7 +6,7 @@ This component only renders the filter navigation. **Your SQL query is responsib Use it alongside a [table](?component=table), [list](?component=list), or [card](?component=card). The links use GET parameters, so the selected filter can be bookmarked and shared. -Set `compact` to display the facets in a dropdown, which is useful for a moderate number of choices or on narrow screens. `all_link` and `all_title` add a link that clears the filter. +Set `compact` to display the facets in a dropdown, which is useful for a moderate number of choices or on narrow screens. Use `dropdown_title` to show the active facet in the closed dropdown. `all_link` and `all_title` add a link that clears the filter. The following portable pattern filters a table by category. `sqlpage.link` preserves the current page path while safely generating the URL. @@ -30,7 +30,8 @@ order by category; INSERT INTO parameter(component, name, description, type, top_level, optional) SELECT 'facet', * FROM (VALUES -- Top-level parameters - ('description','The facet category label. In compact mode, it is shown on the dropdown button. If omitted, the button displays "Choose facet".','TEXT',TRUE,TRUE), + ('description','The facet category label. In compact mode, it is shown on the dropdown button when `dropdown_title` is omitted. If omitted, the button displays "Choose facet".','TEXT',TRUE,TRUE), + ('dropdown_title','Text shown on the compact-mode dropdown button. Use it to show the active facet. Defaults to `description`.','TEXT',TRUE,TRUE), ('compact','Displays the facets in a dropdown instead of links.','BOOLEAN',TRUE,TRUE), ('all_link','URL that clears the filter. If omitted, no All link is displayed.','URL',TRUE,TRUE), ('all_title','Text for the link that clears the filter. Defaults to "ALL".','TEXT',TRUE,TRUE), @@ -108,7 +109,8 @@ VALUES ( "all_link": "?component=facet", "all_title": "All", "all_active": false, - "compact": true + "compact": true, + "dropdown_title": "Constitution" }, { "title": "Constitution", diff --git a/sqlpage/templates/facet.handlebars b/sqlpage/templates/facet.handlebars index 43f24bd4..b4502209 100644 --- a/sqlpage/templates/facet.handlebars +++ b/sqlpage/templates/facet.handlebars @@ -6,7 +6,7 @@ {{else}}