diff --git a/CHANGELOG.md b/CHANGELOG.md index 555cee9a..ff948e62 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, 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 new file mode 100644 index 00000000..f64b0cc7 --- /dev/null +++ b/examples/official-site/sqlpage/migrations/77_facet.sql @@ -0,0 +1,126 @@ +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. 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. + +```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 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), + ('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, + "dropdown_title": "Constitution" + }, + { + "title": "Constitution", + "link": "?component=facet&class=Constitution", + "active": true + }, + { + "title": "Galaxy", + "link": "?component=facet&class=Galaxy" + } + ]' + ) + ); 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..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 @@ -39,7 +39,8 @@ FROM (VALUES ('text', TRUE), ('carousel', TRUE), ('login', TRUE), - ('pagination', TRUE) + ('pagination', TRUE), + ('facet', TRUE) ); INSERT INTO parameter(component, top_level, name, description, type, optional) @@ -78,6 +79,6 @@ FROM (VALUES ('tracking', TRUE), ('carousel', TRUE), ('login', TRUE), - ('pagination', TRUE) + ('pagination', TRUE), + ('facet', TRUE) ); - diff --git a/sqlpage/templates/facet.handlebars b/sqlpage/templates/facet.handlebars new file mode 100644 index 00000000..b4502209 --- /dev/null +++ b/sqlpage/templates/facet.handlebars @@ -0,0 +1,25 @@ + 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..413dd90d --- /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, 'It works !' AS dropdown_title, 1 AS compact, '/all' AS all_link, 1 AS all_active; +SELECT 'It works !' AS title, '/active' AS link, 0 AS active;