Skip to content

Commit 8addb16

Browse files
authored
Merge pull request #1053 from Phoenix79-spec/feature/table-action-buttons
Feature/table action buttons
2 parents f8a47cd + b485028 commit 8addb16

File tree

8 files changed

+276
-25
lines changed

8 files changed

+276
-25
lines changed

CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
# CHANGELOG.md
22

3-
## v0.38.1
3+
## v0.39.0
44
- Ability to execute sql for URL paths with another extension. If you create sitemap.xml.sql, it will be executed for example.com/sitemap.xml
55
- Display source line info in errors even when the database does not return a precise error position. In this case, the entire problematic SQL statement is referenced.
66
- The shell with a vertical sidebar can now have "active" elements, just like the horizontal header bar.
7+
- New `edit_url`, `delete_url`, and `custom_actions` properties in the [table](https://sql-page.com/component.sql?component=table) component to easily add nice icon buttons to a table.
78

89
## v0.38.0
910
- Added support for the Open Database Connectivity (ODBC) standard.

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "sqlpage"
3-
version = "0.38.1"
3+
version = "0.39.0"
44
edition = "2021"
55
description = "Build data user interfaces entirely in SQL. A web server that takes .sql files and formats the query result using pre-made configurable professional-looking components."
66
keywords = ["web", "sql", "framework"]

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

Lines changed: 123 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -810,11 +810,15 @@ INSERT INTO parameter(component, name, description, type, top_level, optional) S
810810
('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),
811811
('currency', 'The ISO 4217 currency code (e.g., USD, EUR, GBP, etc.) to use when formatting monetary values.', 'TEXT', TRUE, TRUE),
812812
('number_format_digits', 'Maximum number of decimal digits to display for numeric values.', 'INTEGER', TRUE, TRUE),
813+
('edit_url', 'If set, an edit button will be added to each row. The value of this property should be a URL, possibly containing the `{id}` placeholder that will be replaced by the value of the `_sqlpage_id` property for that row. Clicking the edit button will take the user to that URL. Added in v0.39.0', 'TEXT', TRUE, TRUE),
814+
('delete_url', 'If set, a delete button will be added to each row. The value of this property should be a URL, possibly containing the `{id}` placeholder that will be replaced by the value of the `_sqlpage_id` property for that row. Clicking the delete button will take the user to that URL. Added in v0.39.0', 'TEXT', TRUE, TRUE),
815+
('custom_actions', 'If set, a column of custom action buttons will be added to each row. The value of this property should be a JSON array of objects, each object defining a button with the following properties: `name` (the text to display on the button), `icon` (the tabler icon name or image link to display on the button), `link` (the URL to navigate to when the button is clicked, possibly containing the `{id}` placeholder that will be replaced by the value of the `_sqlpage_id` property for that row), and `tooltip` (optional text to display when hovering over the button). Added in v0.39.0', 'JSON', TRUE, TRUE),
813816
-- row level
814817
('_sqlpage_css_class', 'For advanced users. Sets a css class on the table row. Added in v0.8.0.', 'TEXT', FALSE, TRUE),
815818
('_sqlpage_color', 'Sets the background color of the row. Added in v0.8.0.', 'COLOR', FALSE, TRUE),
816819
('_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),
817-
('_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)
820+
('_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),
821+
('_sqlpage_actions', 'Sets custom action buttons for this specific row in addition to any defined at the table level, The value of this property should be a JSON array of objects, each object defining a button with the following properties: `name` (the text to display on the button), `icon` (the tabler icon name or image link to display on the button), `link` (the URL to navigate to when the button is clicked, possibly containing the `{id}` placeholder that will be replaced by the value of the `_sqlpage_id` property for that row), and `tooltip` (optional text to display when hovering over the button). Added in v0.39.0', 'JSON', FALSE, TRUE)
818822
) x;
819823

820824
INSERT INTO example(component, description, properties) VALUES
@@ -994,7 +998,124 @@ GROUP BY
994998
This will generate a table with the stores in the first column, and the items in the following columns, with the quantity sold in each store for each item.
995999
9961000
', NULL
997-
);
1001+
),
1002+
(
1003+
'table',
1004+
'## Using Action Buttons in a table.
1005+
1006+
### Preset Actions: `edit_url` & `delete_url`
1007+
Since edit and delete are common actions, the `table` component has dedicated `edit_url` and `delete_url` properties to add buttons for these actions.
1008+
The value of these properties should be a URL, containing the `{id}` placeholder that will be replaced by the value of the `_sqlpage_id` property for that row.
1009+
1010+
### Column with fixed action buttons
1011+
1012+
You may want to add custom action buttons to your table rows, for instance to view details, download a file, or perform a custom operation.
1013+
For this, the `table` component has a `custom_actions` top-level property that lets you define a column of buttons, each button defined by a name, an icon, a link, and an optional tooltip.
1014+
1015+
### Column with variable action buttons
1016+
1017+
The `table` component also supports the row level `_sqlpage_actions` column in your data table.
1018+
This is helpful if you want a more complex logic, for instance to disable a button on some rows, or to change the link or icon based on the row data.
1019+
1020+
> WARNING!
1021+
> If the number of array items in `_sqlpage_actions` is not consistent across all rows, the table may not render correctly.
1022+
> You can leave blank spaces by including an object with only the `name` property.
1023+
1024+
The table has a column of buttons, each button defined by the `_sqlpage_actions` column at the table level, and by the `_sqlpage_actions` property at the row level.
1025+
### `custom_actions` & `_sqlpage_actions` JSON properties.
1026+
Each button is defined by the following properties:
1027+
* `name`: sets the column header and the tooltip if no tooltip is provided,
1028+
* `tooltip`: text to display when hovering over the button,
1029+
* `link`: the URL to navigate to when the button is clicked, possibly containing the `{id}` placeholder that will be replaced by the value of the `_sqlpage_id` property for that row,
1030+
* `icon`: the tabler icon name or image link to display on the button
1031+
1032+
### Example using all of the above
1033+
'
1034+
,
1035+
json('[
1036+
{
1037+
"component": "table",
1038+
"edit_url": "/examples/show_variables.sql?action=edit&update_id={id}",
1039+
"delete_url": "/examples/show_variables.sql?action=delete&delete_id={id}",
1040+
"custom_actions": [
1041+
{
1042+
"name": "history",
1043+
"tooltip": "View Standard History",
1044+
"link": "/examples/show_variables.sql?action=history&standard_id={id}",
1045+
"icon": "history"
1046+
}
1047+
]
1048+
},
1049+
{
1050+
"name": "CalStd",
1051+
"vendor": "PharmaCo",
1052+
"Product": "P1234",
1053+
"lot number": "T23523",
1054+
"status": "Available",
1055+
"expires on": "2026-10-13",
1056+
"_sqlpage_id": 32,
1057+
"_sqlpage_actions": [
1058+
{
1059+
"name": "View PDF",
1060+
"tooltip": "View Presentation",
1061+
"link": "https://sql-page.com/pgconf/2024-sqlpage-badass.pdf",
1062+
"icon": "file-type-pdf"
1063+
},
1064+
{
1065+
"name": "Action",
1066+
"tooltip": "Set In Use",
1067+
"link": "/examples/show_variables.sql?action=set_in_use&standard_id=32",
1068+
"icon": "caret-right"
1069+
}
1070+
]
1071+
},
1072+
{
1073+
"name": "CalStd",
1074+
"vendor": "PharmaCo",
1075+
"Product": "P1234",
1076+
"lot number": "T2352",
1077+
"status": "In Use",
1078+
"expires on": "2026-10-14",
1079+
"_sqlpage_id": 33,
1080+
"_sqlpage_actions": [
1081+
{
1082+
"name": "View PDF",
1083+
"tooltip": "View Presentation",
1084+
"link": "https://sql-page.com/pgconf/2024-sqlpage-badass.pdf",
1085+
"icon": "file-type-pdf"
1086+
},
1087+
{
1088+
"name": "Action",
1089+
"tooltip": "Retire Standard",
1090+
"link": "/examples/show_variables.sql?action=retire&standard_id=33",
1091+
"icon": "test-pipe-off"
1092+
}
1093+
]
1094+
},
1095+
{
1096+
"name": "CalStd",
1097+
"vendor": "PharmaCo",
1098+
"Product": "P1234",
1099+
"lot number": "A123",
1100+
"status": "Discarded",
1101+
"expires on": "2026-09-30",
1102+
"_sqlpage_id": 31,
1103+
"_sqlpage_actions": [
1104+
{
1105+
"name": "View PDF",
1106+
"tooltip": "View Presentation",
1107+
"link": "https://sql-page.com/pgconf/2024-sqlpage-badass.pdf",
1108+
"icon": "file-type-pdf"
1109+
},
1110+
{
1111+
"name": "Action"
1112+
}
1113+
]
1114+
}
1115+
]'
1116+
)
1117+
);
1118+
9981119

9991120

10001121
INSERT INTO component(name, icon, description) VALUES

sqlpage/templates/table.handlebars

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
{{#if (or search initial_search_value)}}
44
<div class="p-3">
55
<input
6+
id="{{component_index}}-search"
67
type="search"
78
class="form-control form-control-rounded fs-6 search"
89
placeholder="{{default search_placeholder 'Search…'}}"
@@ -52,6 +53,18 @@
5253
</th>
5354
{{/if}}
5455
{{/each}}
56+
{{#if ../edit_url}}<th class="_col_edit text-center">Edit</th>{{/if}}
57+
{{#if ../delete_url}}<th class="_col_delete text-center">Delete</th>{{/if}}
58+
{{#if ../custom_actions}}
59+
{{#each ../custom_actions}}
60+
<th class="_col_custom text-center">{{this.name}}</th>
61+
{{/each}}
62+
{{/if}}
63+
{{#if _sqlpage_actions}}
64+
{{#each _sqlpage_actions}}
65+
<th class="_col_action text-center">{{this.name}}</th>
66+
{{/each}}
67+
{{/if}}
5568
</tr>
5669
</thead>
5770
<tbody class="table-tbody list">{{#delay}}</tbody>{{/delay}}
@@ -78,6 +91,38 @@
7891
</td>
7992
{{/if~}}
8093
{{~/each~}}
94+
{{#if ../edit_url}}
95+
<td class="align-middle _col_edit">
96+
<a href="{{replace ../edit_url '{id}' _sqlpage_id}}" class="align-middle link-secondary _col_edit" data-action="edit" title="Edit">
97+
{{~icon_img 'edit'~}}
98+
</a>
99+
</td>
100+
{{/if}}
101+
{{#if ../delete_url}}
102+
<td class="align-middle _col_delete text-center">
103+
<a href="{{replace ../delete_url '{id}' _sqlpage_id}}" class="align-middle link-secondary _col_delete" data-action="delete" title="Delete">
104+
{{~icon_img 'trash'~}}
105+
</a>
106+
</td>
107+
{{/if}}
108+
{{#if ../custom_actions}}
109+
{{#each ../custom_actions}}
110+
<td class="align-middle _col_{{this.name}} text-center">
111+
<a href="{{replace this.link '{id}' ../_sqlpage_id}}" class="align-middle link-secondary _col_{{this.name}}" data-action="{{this.name}}" title="{{default this.tooltip this.name}}">{{!Title property sets the tooltip text}}
112+
{{~icon_img this.icon~}}
113+
</a>
114+
</td>
115+
{{/each}}
116+
{{/if}}
117+
{{#if _sqlpage_actions}}
118+
{{#each _sqlpage_actions}}
119+
<td class="align-middle _col_{{this.name}} text-center">
120+
<a href="{{replace this.link '{id}' ../_sqlpage_id}}" class="align-middle link-secondary _col_{{this.name}}" data-action="{{this.name}}" title="{{default this.tooltip this.name}}">
121+
{{~icon_img this.icon~}}
122+
</a>
123+
</td>
124+
{{/each}}
125+
{{/if}}
81126
</tr>
82127
{{!~
83128
After this <tr> has been rendered, if this was a footer, we need to reopen a new <tbody>

tests/end-to-end/official-site.spec.ts

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,3 +213,87 @@ test("modal", async ({ page }) => {
213213
await modal.getByRole("button", { label: "Close" }).first().click();
214214
await expect(modal).not.toBeVisible();
215215
});
216+
217+
test("table action buttons - edit_url and delete_url", async ({ page }) => {
218+
await page.goto(`${BASE}/documentation.sql?component=table`);
219+
const tableSection = page.locator(".table-responsive", {
220+
has: page.getByRole("cell", { name: "PharmaCo" }),
221+
});
222+
223+
const editButton = tableSection.getByTitle("Edit").first();
224+
await expect(editButton).toBeVisible();
225+
await expect(editButton).toHaveAttribute("href", /action=edit&update_id=\d+/);
226+
227+
const deleteButton = tableSection.getByTitle("Delete").first();
228+
await expect(deleteButton).toBeVisible();
229+
await expect(deleteButton).toHaveAttribute(
230+
"href",
231+
/action=delete&delete_id=\d+/,
232+
);
233+
});
234+
235+
test("table action buttons - custom_actions", async ({ page }) => {
236+
await page.goto(`${BASE}/documentation.sql?component=table`);
237+
const tableSection = page.locator(".table-responsive", {
238+
has: page.getByRole("cell", { name: "PharmaCo" }),
239+
});
240+
241+
const historyButton = tableSection
242+
.getByTitle("View Standard History")
243+
.first();
244+
await expect(historyButton).toBeVisible();
245+
await expect(historyButton).toHaveAttribute(
246+
"href",
247+
/action=history&standard_id=\d+/,
248+
);
249+
});
250+
251+
test("table action buttons - _sqlpage_actions", async ({ page }) => {
252+
await page.goto(`${BASE}/documentation.sql?component=table`);
253+
const tableSection = page.locator(".table-responsive", {
254+
has: page.getByRole("cell", { name: "PharmaCo" }),
255+
});
256+
257+
const pdfButtons = tableSection.getByTitle("View Presentation");
258+
await expect(pdfButtons.first()).toBeVisible();
259+
await expect(pdfButtons).toHaveCount(3);
260+
261+
const firstPdfButton = pdfButtons.first();
262+
await expect(firstPdfButton).toHaveAttribute(
263+
"href",
264+
"https://sql-page.com/pgconf/2024-sqlpage-badass.pdf",
265+
);
266+
267+
const setInUseButton = tableSection.getByTitle("Set In Use");
268+
await expect(setInUseButton).toBeVisible();
269+
await expect(setInUseButton).toHaveAttribute(
270+
"href",
271+
/action=set_in_use&standard_id=32/,
272+
);
273+
274+
const retireButton = tableSection.getByTitle("Retire Standard");
275+
await expect(retireButton).toBeVisible();
276+
await expect(retireButton).toHaveAttribute(
277+
"href",
278+
/action=retire&standard_id=33/,
279+
);
280+
});
281+
282+
test("table action buttons - disabled action", async ({ page }) => {
283+
await page.goto(`${BASE}/documentation.sql?component=table`);
284+
const tableSection = page.locator(".table-responsive", {
285+
has: page.getByRole("cell", { name: "PharmaCo" }),
286+
});
287+
288+
const viewPresentationButtons = tableSection.getByTitle("View Presentation");
289+
await expect(viewPresentationButtons).toHaveCount(3);
290+
291+
const actionColumnButtons = tableSection.locator(
292+
"td._col_Action a[data-action='Action']",
293+
);
294+
await expect(actionColumnButtons).toHaveCount(3);
295+
296+
const emptyActionButton = actionColumnButtons.last();
297+
await expect(emptyActionButton).toHaveAttribute("href", "null");
298+
await expect(emptyActionButton).toHaveAttribute("title", "Action");
299+
});

0 commit comments

Comments
 (0)