diff --git a/CHANGELOG.md b/CHANGELOG.md index 6958e2df..d974f357 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,8 @@ - Columns without buttons - In the columns component, when no button text is specified, no button is displayed (instead of an empty button) - New `unsafe_contents_md` property in the text component to allow rendering markdown with embedded HTML tags. +- New `_sqlpage_footer` property for table rows. When applied, that row will be rendered as the table footer. It is recommended to use this on the last data row. +- New `freeze_footers` property in table component. If the footer is enabled, this will make it always visible. Works similarly to `freeze_headers`. ## 0.33.1 (2025-02-25) diff --git a/examples/official-site/sqlpage/migrations/01_documentation.sql b/examples/official-site/sqlpage/migrations/01_documentation.sql index 7658d0db..083a608f 100644 --- a/examples/official-site/sqlpage/migrations/01_documentation.sql +++ b/examples/official-site/sqlpage/migrations/01_documentation.sql @@ -785,6 +785,7 @@ INSERT INTO parameter(component, name, description, type, top_level, optional) S ('empty_description', 'Text to display if the table does not contain any row. Defaults to "no data".', 'TEXT', TRUE, TRUE), ('freeze_columns', 'Whether to freeze the leftmost column of the table.', 'BOOLEAN', TRUE, TRUE), ('freeze_headers', 'Whether to freeze the top row of the table.', 'BOOLEAN', TRUE, TRUE), + ('freeze_footers', 'Whether to freeze the footer (bottom row) of the table, only works if that row has the `_sqlpage_footer` property applied to it.', 'BOOLEAN', TRUE, TRUE), ('raw_numbers', 'Name of a column whose values are numeric, but should be displayed as raw numbers without any formatting (no thousands separators, decimal separator is always a dot). This argument can be repeated multiple times.', 'TEXT', TRUE, TRUE), ('money', 'Name of a numeric column whose values should be displayed as currency amounts, in the currency defined by the `currency` property. This argument can be repeated multiple times.', 'TEXT', TRUE, TRUE), ('currency', 'The ISO 4217 currency code (e.g., USD, EUR, GBP, etc.) to use when formatting monetary values.', 'TEXT', TRUE, TRUE), @@ -792,6 +793,7 @@ INSERT INTO parameter(component, name, description, type, top_level, optional) S -- row level ('_sqlpage_css_class', 'For advanced users. Sets a css class on the table row. Added in v0.8.0.', 'TEXT', FALSE, TRUE), ('_sqlpage_color', 'Sets the background color of the row. Added in v0.8.0.', 'COLOR', FALSE, TRUE), + ('_sqlpage_footer', 'Sets this row as the table footer. It is recommended that this parameter is applied to the last row. Added in v0.34.0.', 'BOOLEAN', FALSE, TRUE), ('_sqlpage_id', 'Sets the id of the html tabler row element. Allows you to make links targeting a specific row in a table.', 'TEXT', FALSE, TRUE) ) x; @@ -807,6 +809,12 @@ INSERT INTO example(component, description, properties) VALUES '{"icon": "table", "name": "[Table](?component=table)", "description": "Displays SQL results as a searchable table.", "_sqlpage_color": "red"}, {"icon": "timeline", "name": "[Chart](?component=chart)", "description": "Show graphs based on numeric data."} ]')), + ('table', 'A sortable table with a colored footer showing the average value of its entries.', + json('[{"component":"table", "sort":true}, '|| + '{"Person": "Rudolph Lingens", "Height": 190},' || + '{"Person": "Jane Doe", "Height": 150},' || + '{"Person": "John Doe", "Height": 200},' || + '{"_sqlpage_footer":true, "_sqlpage_color": "green", "Person": "Average", "Height": 180}]')), ( 'table', 'A table with column sorting. Sorting sorts numbers in numeric order, and strings in alphabetical order. @@ -900,6 +908,12 @@ Numbers can be displayed "feature": "Performance", "description": "Designed for performance, with a focus on efficient data processing and minimal overhead.", "benefits": "Quickly processes large datasets, handles high volumes of requests, and minimizes server load." + }, + { + "_sqlpage_footer": true, + "feature": "Summary", + "description": "Summarizes the features of the product.", + "benefits": "Provides a quick overview of the product''s features and benefits." } ]') ), diff --git a/sqlpage/sqlpage.css b/sqlpage/sqlpage.css index 0a0e49d0..bc12ff06 100644 --- a/sqlpage/sqlpage.css +++ b/sqlpage/sqlpage.css @@ -76,10 +76,20 @@ code { z-index: 2; } +.table-freeze-footers tfoot { + position: sticky; + bottom: 0; + z-index: 2; +} + .table-freeze-headers { max-height: 50vh; } +.table-freeze-footers { + max-height: 50vh; +} + .table-freeze-columns th:first-child { position: sticky; left: 0; diff --git a/sqlpage/sqlpage.js b/sqlpage/sqlpage.js index b26d57ab..1cc70006 100644 --- a/sqlpage/sqlpage.js +++ b/sqlpage/sqlpage.js @@ -74,7 +74,7 @@ function apply_number_formatting(table_el) { const number_format_digits = table_el.dataset.number_format_digits; const currency = table_el.dataset.currency; - for (const tr_el of table_el.querySelectorAll("tbody tr")) { + for (const tr_el of table_el.querySelectorAll("tbody tr, tfoot tr")) { const cells = tr_el.getElementsByTagName("td"); for (let idx = 0; idx < cells.length; idx++) { const column_type = col_types[idx]; diff --git a/sqlpage/templates/table.handlebars b/sqlpage/templates/table.handlebars index 0d502f2b..26a520a5 100644 --- a/sqlpage/templates/table.handlebars +++ b/sqlpage/templates/table.handlebars @@ -14,6 +14,7 @@
{{description}}{{/if}} {{#each_row}} {{#if (eq @row_index 0)}} + {{! Since we are inside the first data row, render the header }} {{#each this}} @@ -54,7 +56,8 @@ {{#delay}}{{/delay}} {{~/if~}} - + {{!~ If this data row should go into the footer, close the , open the ~}} + {{~#if _sqlpage_footer~}} {{/if~}} {{~#each this~}} {{~#if (not (starts_with @key '_sqlpage_'))~}} @@ -75,8 +78,15 @@ {{/if~}} {{~/each~}} + {{!~ + After this has been rendered, if this was a footer, we need to reopen a new + No need for another delayed closure since the previous one still applies + ~}} + {{~#if _sqlpage_footer}} {{/if~}} {{/each_row}} {{flush_delayed}} + + {{! If not enough rows were rendered, we need to place a 'No data' cell. "Not enough rows" depends on the footer settings }} {{#if (eq @row_index 0)}}