Skip to content

Commit b074dd1

Browse files
Added an option to create footers in the table component (#862)
* Implemented optional table footer rendering * Added an example of footer usage * Fixed a minor mistake, improved the example * Updated changelog * implemented requested change * Update table.handlebars spacing * Update table.handlebars minor, removed a `~` * remove unused whitespace --------- Co-authored-by: lovasoa <[email protected]>
1 parent dd138f7 commit b074dd1

File tree

5 files changed

+38
-2
lines changed

5 files changed

+38
-2
lines changed

CHANGELOG.md

+2
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@
2222
- Columns without buttons
2323
- In the columns component, when no button text is specified, no button is displayed (instead of an empty button)
2424
- New `unsafe_contents_md` property in the text component to allow rendering markdown with embedded HTML tags.
25+
- 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.
26+
- New `freeze_footers` property in table component. If the footer is enabled, this will make it always visible. Works similarly to `freeze_headers`.
2527

2628
## 0.33.1 (2025-02-25)
2729

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

+14
Original file line numberDiff line numberDiff line change
@@ -785,13 +785,15 @@ INSERT INTO parameter(component, name, description, type, top_level, optional) S
785785
('empty_description', 'Text to display if the table does not contain any row. Defaults to "no data".', 'TEXT', TRUE, TRUE),
786786
('freeze_columns', 'Whether to freeze the leftmost column of the table.', 'BOOLEAN', TRUE, TRUE),
787787
('freeze_headers', 'Whether to freeze the top row of the table.', 'BOOLEAN', TRUE, TRUE),
788+
('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),
788789
('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),
789790
('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),
790791
('currency', 'The ISO 4217 currency code (e.g., USD, EUR, GBP, etc.) to use when formatting monetary values.', 'TEXT', TRUE, TRUE),
791792
('number_format_digits', 'Maximum number of decimal digits to display for numeric values.', 'INTEGER', TRUE, TRUE),
792793
-- row level
793794
('_sqlpage_css_class', 'For advanced users. Sets a css class on the table row. Added in v0.8.0.', 'TEXT', FALSE, TRUE),
794795
('_sqlpage_color', 'Sets the background color of the row. Added in v0.8.0.', 'COLOR', FALSE, TRUE),
796+
('_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),
795797
('_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)
796798
) x;
797799

@@ -807,6 +809,12 @@ INSERT INTO example(component, description, properties) VALUES
807809
'{"icon": "table", "name": "[Table](?component=table)", "description": "Displays SQL results as a searchable table.", "_sqlpage_color": "red"},
808810
{"icon": "timeline", "name": "[Chart](?component=chart)", "description": "Show graphs based on numeric data."}
809811
]')),
812+
('table', 'A sortable table with a colored footer showing the average value of its entries.',
813+
json('[{"component":"table", "sort":true}, '||
814+
'{"Person": "Rudolph Lingens", "Height": 190},' ||
815+
'{"Person": "Jane Doe", "Height": 150},' ||
816+
'{"Person": "John Doe", "Height": 200},' ||
817+
'{"_sqlpage_footer":true, "_sqlpage_color": "green", "Person": "Average", "Height": 180}]')),
810818
(
811819
'table',
812820
'A table with column sorting. Sorting sorts numbers in numeric order, and strings in alphabetical order.
@@ -900,6 +908,12 @@ Numbers can be displayed
900908
"feature": "Performance",
901909
"description": "Designed for performance, with a focus on efficient data processing and minimal overhead.",
902910
"benefits": "Quickly processes large datasets, handles high volumes of requests, and minimizes server load."
911+
},
912+
{
913+
"_sqlpage_footer": true,
914+
"feature": "Summary",
915+
"description": "Summarizes the features of the product.",
916+
"benefits": "Provides a quick overview of the product''s features and benefits."
903917
}
904918
]')
905919
),

sqlpage/sqlpage.css

+10
Original file line numberDiff line numberDiff line change
@@ -76,10 +76,20 @@ code {
7676
z-index: 2;
7777
}
7878

79+
.table-freeze-footers tfoot {
80+
position: sticky;
81+
bottom: 0;
82+
z-index: 2;
83+
}
84+
7985
.table-freeze-headers {
8086
max-height: 50vh;
8187
}
8288

89+
.table-freeze-footers {
90+
max-height: 50vh;
91+
}
92+
8393
.table-freeze-columns th:first-child {
8494
position: sticky;
8595
left: 0;

sqlpage/sqlpage.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ function apply_number_formatting(table_el) {
7474
const number_format_digits = table_el.dataset.number_format_digits;
7575
const currency = table_el.dataset.currency;
7676

77-
for (const tr_el of table_el.querySelectorAll("tbody tr")) {
77+
for (const tr_el of table_el.querySelectorAll("tbody tr, tfoot tr")) {
7878
const cells = tr_el.getElementsByTagName("td");
7979
for (let idx = 0; idx < cells.length; idx++) {
8080
const column_type = col_types[idx];

sqlpage/templates/table.handlebars

+11-1
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
<div class="table-responsive
1515
{{~#if freeze_columns}} table-freeze-columns text-nowrap {{/if~}}
1616
{{~#if freeze_headers}} table-freeze-headers text-nowrap {{/if~}}
17+
{{~#if freeze_footers}} table-freeze-footers text-nowrap {{/if~}}
1718
">
1819
<table class="table
1920
{{~#if striped_rows}} table-striped {{/if~}}
@@ -29,6 +30,7 @@
2930
{{#if description}}<caption class="text-center text-muted">{{description}}</caption>{{/if}}
3031
{{#each_row}}
3132
{{#if (eq @row_index 0)}}
33+
{{! Since we are inside the first data row, render the header }}
3234
<thead>
3335
<tr>
3436
{{#each this}}
@@ -54,7 +56,8 @@
5456
</thead>
5557
<tbody class="table-tbody list">{{#delay}}</tbody>{{/delay}}
5658
{{~/if~}}
57-
59+
{{!~ If this data row should go into the footer, close the <tbody>, open the <tfoot> ~}}
60+
{{~#if _sqlpage_footer~}} </tbody><tfoot> {{/if~}}
5861
<tr class="{{_sqlpage_css_class}} {{#if _sqlpage_color}}bg-{{_sqlpage_color}}-lt{{/if}}" {{#if _sqlpage_id}}id="{{_sqlpage_id}}"{{/if}}>
5962
{{~#each this~}}
6063
{{~#if (not (starts_with @key '_sqlpage_'))~}}
@@ -75,8 +78,15 @@
7578
{{/if~}}
7679
{{~/each~}}
7780
</tr>
81+
{{!~
82+
After this <tr> has been rendered, if this was a footer, we need to reopen a new <tbody>
83+
No need for another delayed closure since the previous one still applies
84+
~}}
85+
{{~#if _sqlpage_footer}} </tfoot><tbody class="table-tbody list"> {{/if~}}
7886
{{/each_row}}
7987
{{flush_delayed}}
88+
89+
{{! If not enough rows were rendered, we need to place a 'No data' cell. "Not enough rows" depends on the footer settings }}
8090
{{#if (eq @row_index 0)}}
8191
<tbody class="table-tbody list">
8292
<tr>

0 commit comments

Comments
 (0)