Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 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},' ||
'{"Person": "Rudolph Lingens", "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.
Expand Down
10 changes: 10 additions & 0 deletions sqlpage/sqlpage.css
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
2 changes: 1 addition & 1 deletion sqlpage/sqlpage.js
Original file line number Diff line number Diff line change
Expand Up @@ -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];
Expand Down
48 changes: 40 additions & 8 deletions sqlpage/templates/table.handlebars
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
<div class="table-responsive
{{~#if freeze_columns}} table-freeze-columns text-nowrap {{/if~}}
{{~#if freeze_headers}} table-freeze-headers text-nowrap {{/if~}}
{{~#if freeze_footers}} table-freeze-footers text-nowrap {{/if~}}
">
<table class="table
{{~#if striped_rows}} table-striped {{/if~}}
Expand All @@ -29,6 +30,7 @@
{{#if description}}<caption class="text-center text-muted">{{description}}</caption>{{/if}}
{{#each_row}}
{{#if (eq @row_index 0)}}
{{! Since we are inside the first data row, we always start with the header }}
<thead>
<tr>
{{#each this}}
Expand All @@ -52,9 +54,17 @@
{{/each}}
</tr>
</thead>
<tbody class="table-tbody list">{{#delay}}</tbody>{{/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 <tfoot> tag, while the <tbody> will be added after this row is rendered }}
<tfoot>
{{else}}
{{! If there is no footer we open the <tbody> now, with a 'delay'ed closure }}
<tbody class="table-tbody list">{{#delay}}</tbody>{{/delay}}
{{/if}}
{{~/if~}}

{{! Regardless of which row we are in, this part of the template renders the row data }}
<tr class="{{_sqlpage_css_class}} {{#if _sqlpage_color}}bg-{{_sqlpage_color}}-lt{{/if}}" {{#if _sqlpage_id}}id="{{_sqlpage_id}}"{{/if}}>
{{~#each this~}}
{{~#if (not (starts_with @key '_sqlpage_'))~}}
Expand All @@ -75,15 +85,37 @@
{{/if~}}
{{~/each~}}
</tr>

{{! After this <tr> 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 <tfoot> tag }}
</tfoot>
{{! and since it was not done before, the <tbody> tag needs to be added now, with a delayed </tbody> }}
<tbody class="table-tbody list">{{#delay}}</tbody>{{/delay}}
{{/if}}
{{~/if~}}
{{/each_row}}
{{flush_delayed}}
{{#if (eq @row_index 0)}}
<tbody class="table-tbody list">
<tr>
<td class="text-center">{{default empty_description 'No data'}}</td>
</tr>
</tbody>
{{/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)}}
<tbody class="table-tbody list">
<tr>
<td class="text-center">{{default empty_description 'No data'}}</td>
</tr>
</tbody>
{{/if}}
{{else}}
{{#if (eq @row_index 0)}}
<tbody class="table-tbody list">
<tr>
<td class="text-center">{{default empty_description 'No data'}}</td>
</tr>
</tbody>
{{/if}}
{{~/if~}}
</table>
</div>
</div>
Expand Down