From 39e5d58fedc15d650a4f180e278ae8c461a58c13 Mon Sep 17 00:00:00 2001 From: Francesco Cattoglio Date: Wed, 19 Mar 2025 10:53:26 +0000 Subject: [PATCH 1/8] Implemented optional table footer rendering --- sqlpage/sqlpage.css | 10 +++++++ sqlpage/sqlpage.js | 2 +- sqlpage/templates/table.handlebars | 48 +++++++++++++++++++++++++----- 3 files changed, 51 insertions(+), 9 deletions(-) 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..d81d0697 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, we always start with the header }} {{#each this}} @@ -52,9 +54,17 @@ {{/each}} - {{#delay}}{{/delay}} + {{! For the first row we have two choices, depending on the first_row_is_footer variable }} + {{#if ../first_row_is_footer}} + {{! If first row is the footer we open the tag, while the will be added after this row is rendered }} + + {{#else}} + {{! If there is no footer we open the now, with a 'delay'ed closure }} + {{#delay}}{{/delay}} + {{/if}} {{~/if~}} + {{! Regardless of which row we are in, this part of the template renders the row data }} {{~#each this~}} {{~#if (not (starts_with @key '_sqlpage_'))~}} @@ -75,15 +85,37 @@ {{/if~}} {{~/each~}} + + {{! After this has been rendered, we need to check again if there is more work to do done }} + {{#if (eq @row_index 0)}} + {{#if ../first_row_is_footer}} + {{! If the first row was the footer, 2 things must happen: we need to close the tag }} + + {{! and since it was not done before, the tag needs to be added now, with a delayed }} + {{#delay}}{{/delay}} + {{/if}} + {{~/if~}} {{/each_row}} {{flush_delayed}} - {{#if (eq @row_index 0)}} - - - - - - {{/if}} + + {{! If not enough rows were rendered, we need to place a 'No data' cell. "Not enough rows" depends on the footer settings }} + {{#if ../first_row_is_footer}} + {{#if (lte @row_index 1)}} + + + + + + {{/if}} + {{else}} + {{#if (eq @row_index 0)}} + + + + + + {{/if}} + {{~/if~}}
{{default empty_description 'No data'}}
{{default empty_description 'No data'}}
{{default empty_description 'No data'}}
From 604f673133913282f74a62e492acc98f9f93db3a Mon Sep 17 00:00:00 2001 From: Francesco Cattoglio Date: Wed, 19 Mar 2025 11:33:02 +0000 Subject: [PATCH 2/8] Added an example of footer usage --- .../official-site/sqlpage/migrations/01_documentation.sql | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/examples/official-site/sqlpage/migrations/01_documentation.sql b/examples/official-site/sqlpage/migrations/01_documentation.sql index 7658d0db..c8d00ac2 100644 --- a/examples/official-site/sqlpage/migrations/01_documentation.sql +++ b/examples/official-site/sqlpage/migrations/01_documentation.sql @@ -807,6 +807,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 table with a footer showing the average value of its entries.', + json('[{"component":"table", "sort":true, "first_row_is_footer":true}, '|| + '{"Person": "Average", "Height": 180},' || + '{"Person": "Joe Public", "Height": 190},' || + '{"Person": "Jane Doe", "Height": 150},' || + '{"Person": "John Doe", "Height": 200}]')), ( 'table', 'A table with column sorting. Sorting sorts numbers in numeric order, and strings in alphabetical order. From 75c07dd56d0f52d0576844d90f0638afd5949fa6 Mon Sep 17 00:00:00 2001 From: Francesco Cattoglio Date: Wed, 19 Mar 2025 12:16:44 +0000 Subject: [PATCH 3/8] Fixed a minor mistake, improved the example --- .../official-site/sqlpage/migrations/01_documentation.sql | 6 +++--- sqlpage/templates/table.handlebars | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/official-site/sqlpage/migrations/01_documentation.sql b/examples/official-site/sqlpage/migrations/01_documentation.sql index c8d00ac2..7c983699 100644 --- a/examples/official-site/sqlpage/migrations/01_documentation.sql +++ b/examples/official-site/sqlpage/migrations/01_documentation.sql @@ -807,10 +807,10 @@ 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 table with a footer showing the average value of its entries.', + ('table', 'A sortable table with a colored footer showing the average value of its entries.', json('[{"component":"table", "sort":true, "first_row_is_footer":true}, '|| - '{"Person": "Average", "Height": 180},' || - '{"Person": "Joe Public", "Height": 190},' || + '{"_sqlpage_color": "green", "Person": "Average", "Height": 180},' || + '{"Person": "Rudolph Lingens", "Height": 190},' || '{"Person": "Jane Doe", "Height": 150},' || '{"Person": "John Doe", "Height": 200}]')), ( diff --git a/sqlpage/templates/table.handlebars b/sqlpage/templates/table.handlebars index d81d0697..6e00388e 100644 --- a/sqlpage/templates/table.handlebars +++ b/sqlpage/templates/table.handlebars @@ -58,7 +58,7 @@ {{#if ../first_row_is_footer}} {{! If first row is the footer we open the tag, while the will be added after this row is rendered }} - {{#else}} + {{else}} {{! If there is no footer we open the now, with a 'delay'ed closure }} {{#delay}}{{/delay}} {{/if}} From 84a59e1da711a610d1d2e489290e65b7c12395d3 Mon Sep 17 00:00:00 2001 From: Francesco Cattoglio Date: Wed, 19 Mar 2025 12:21:45 +0000 Subject: [PATCH 4/8] Updated changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6958e2df..6b87274e 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 `first_row_is_footer` property in table component. When enabled, the first row of table data will be put in the table footer instead of body. +- New `freeze-footers` property in table component. If the is footer enabled, it will always be visible while the user scrolls the table body. ## 0.33.1 (2025-02-25) From 895ac553d0111b5ccb6b58c9e373ec846984af7c Mon Sep 17 00:00:00 2001 From: Francesco Cattoglio Date: Wed, 19 Mar 2025 16:26:39 +0000 Subject: [PATCH 5/8] implemented requested change --- CHANGELOG.md | 4 +- .../sqlpage/migrations/01_documentation.sql | 8 ++-- sqlpage/templates/table.handlebars | 48 +++++-------------- 3 files changed, 20 insertions(+), 40 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6b87274e..d974f357 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,8 +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 `first_row_is_footer` property in table component. When enabled, the first row of table data will be put in the table footer instead of body. -- New `freeze-footers` property in table component. If the is footer enabled, it will always be visible while the user scrolls the table body. +- 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 7c983699..c3f11dba 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; @@ -808,11 +810,11 @@ INSERT INTO example(component, description, properties) VALUES {"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, "first_row_is_footer":true}, '|| - '{"_sqlpage_color": "green", "Person": "Average", "Height": 180},' || + json('[{"component":"table", "sort":true}, '|| '{"Person": "Rudolph Lingens", "Height": 190},' || '{"Person": "Jane Doe", "Height": 150},' || - '{"Person": "John Doe", "Height": 200}]')), + '{"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. diff --git a/sqlpage/templates/table.handlebars b/sqlpage/templates/table.handlebars index 6e00388e..83d33497 100644 --- a/sqlpage/templates/table.handlebars +++ b/sqlpage/templates/table.handlebars @@ -30,7 +30,7 @@ {{#if description}}{{description}}{{/if}} {{#each_row}} {{#if (eq @row_index 0)}} - {{! Since we are inside the first data row, we always start with the header }} + {{! Since we are inside the first data row, render the header }} {{#each this}} @@ -54,17 +54,11 @@ {{/each}} - {{! For the first row we have two choices, depending on the first_row_is_footer variable }} - {{#if ../first_row_is_footer}} - {{! If first row is the footer we open the tag, while the will be added after this row is rendered }} - - {{else}} - {{! If there is no footer we open the now, with a 'delay'ed closure }} - {{#delay}}{{/delay}} - {{/if}} + {{#delay}}{{/delay}} {{~/if~}} - {{! Regardless of which row we are in, this part of the template renders the row data }} + {{! 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_'))~}} @@ -86,35 +80,19 @@ {{~/each~}} - {{! After this has been rendered, we need to check again if there is more work to do done }} - {{#if (eq @row_index 0)}} - {{#if ../first_row_is_footer}} - {{! If the first row was the footer, 2 things must happen: we need to close the tag }} - - {{! and since it was not done before, the tag needs to be added now, with a delayed }} - {{#delay}}{{/delay}} - {{/if}} - {{~/if~}} + {{! 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 ../first_row_is_footer}} - {{#if (lte @row_index 1)}} - - - {{default empty_description 'No data'}} - - - {{/if}} - {{else}} - {{#if (eq @row_index 0)}} - - - {{default empty_description 'No data'}} - - - {{/if}} + {{#if (eq @row_index 0)}} + + + {{default empty_description 'No data'}} + + {{~/if~}} From 9e49e3090d4779f3deab48e32920139164120bea Mon Sep 17 00:00:00 2001 From: francesco-cattoglio Date: Wed, 19 Mar 2025 17:27:43 +0100 Subject: [PATCH 6/8] Update table.handlebars spacing --- sqlpage/templates/table.handlebars | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sqlpage/templates/table.handlebars b/sqlpage/templates/table.handlebars index 83d33497..366c428a 100644 --- a/sqlpage/templates/table.handlebars +++ b/sqlpage/templates/table.handlebars @@ -54,7 +54,7 @@ {{/each}} - {{#delay}}{{/delay}} + {{#delay}}{{/delay}} {{~/if~}} {{! If this data row should go into the footer, close the , open the }} From 3c857a5b2ea1d19c30aef6e23252f5a8da9c2d26 Mon Sep 17 00:00:00 2001 From: francesco-cattoglio Date: Wed, 19 Mar 2025 17:28:53 +0100 Subject: [PATCH 7/8] Update table.handlebars minor, removed a `~` --- sqlpage/templates/table.handlebars | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sqlpage/templates/table.handlebars b/sqlpage/templates/table.handlebars index 366c428a..1f98deed 100644 --- a/sqlpage/templates/table.handlebars +++ b/sqlpage/templates/table.handlebars @@ -93,7 +93,7 @@ {{default empty_description 'No data'}} - {{~/if~}} + {{/if}} From bab0bd4bac331c1e2d5eda2823e0fee97599c301 Mon Sep 17 00:00:00 2001 From: lovasoa Date: Wed, 19 Mar 2025 23:32:42 +0100 Subject: [PATCH 8/8] remove unused whitespace --- .../sqlpage/migrations/01_documentation.sql | 6 ++++++ sqlpage/templates/table.handlebars | 14 +++++++------- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/examples/official-site/sqlpage/migrations/01_documentation.sql b/examples/official-site/sqlpage/migrations/01_documentation.sql index c3f11dba..083a608f 100644 --- a/examples/official-site/sqlpage/migrations/01_documentation.sql +++ b/examples/official-site/sqlpage/migrations/01_documentation.sql @@ -908,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/templates/table.handlebars b/sqlpage/templates/table.handlebars index 1f98deed..26a520a5 100644 --- a/sqlpage/templates/table.handlebars +++ b/sqlpage/templates/table.handlebars @@ -56,9 +56,8 @@ {{#delay}}{{/delay}} {{~/if~}} - - {{! If this data row should go into the footer, close the , open the }} - {{#if _sqlpage_footer}} {{/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_'))~}} @@ -79,10 +78,11 @@ {{/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}} + {{!~ + 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}}