Skip to content

Commit a76acd7

Browse files
committed
feat: modals have their own URL
- Allow opening modals with a simple link, enabling triggering from other components. - Persist modal state across page refreshes using URL hash.
1 parent 0ddcc35 commit a76acd7

File tree

4 files changed

+52
-25
lines changed

4 files changed

+52
-25
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@
66
- Additionally, `base64` can be specified to decode binary data as base64 (compatible with [data URI](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URIs))
77
- By default, the old behavior of the `fetch_with_meta` function is preserved: the response body is decoded as `utf-8` if possible, otherwise the response is encoded in `base64`.
88
- Added a specific warning when a URL parameter and a form field have the same name. The previous general warning about referencing form fields with the `$var` syntax was confusing in that case.
9+
- [modal](https://sql-page.com/component.sql?component=modal) component: allow opening modals with a simple link.
10+
- This allows you to trigger modals from any other component, including tables, maps, forms, lists and more.
11+
- Since modals have their own url inside the page, you can now link to a modal from another page, and if you refresh a page while the modal is open, the modal will stay open.
12+
- modals now have an `open` parameter to open the modal automatically when the page is loaded.
913

1014
## v0.36.1
1115
- Fix regression introduced in v0.36.0: PostgreSQL money values showed as 0.0

examples/official-site/sqlpage/migrations/63_modal.sql

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
INSERT INTO component(name, icon, description, introduced_in_version) VALUES
2-
('modal', 'app-window', 'Defines the content of a modal box. Useful for displaying additional information or help.', '0.36.0');
2+
('modal', 'app-window', '
3+
Defines the a temporary popup box displayed on top of a webpage’s content.
4+
Useful for displaying additional information, help, or collect data from users.
5+
6+
Modals are closed by default, and can be opened by clicking on a button or link targeting their ID.', '0.36.0');
37

48
INSERT INTO parameter(component, name, description, type, top_level, optional) SELECT 'modal', * FROM (VALUES
59
('title','Description of the modal box.','TEXT',TRUE,FALSE),
@@ -26,7 +30,7 @@ INSERT INTO example(component, description, properties) VALUES
2630
{"component": "modal","id": "my_modal","title": "A modal box","close": "Close"},
2731
{"contents":"I''m a modal window, and I allow you to display additional information or help for the user."},
2832
{"component": "button"},
29-
{"title":"Open a simple modal","modal":"my_modal"}
33+
{"title":"Open a simple modal","link":"#my_modal"}
3034
]')
3135
),
3236
('modal',
@@ -40,11 +44,11 @@ INSERT INTO example(component, description, properties) VALUES
4044
"embed":"/examples/form.sql?_sqlpage_embed"
4145
},
4246
{"component": "button"},
43-
{"title":"Open a modal with a form","modal":"my_embed_form_modal"}
47+
{"title":"Open a modal with a form","link":"#my_embed_form_modal"}
4448
]')
4549
),
4650
('modal',
47-
'Example of modal chart content',
51+
'A popup modal that contains a chart generated by a separate SQL file. The modal is triggered by links inside a datagrid.',
4852
json('[
4953
{
5054
"component":"modal",
@@ -53,8 +57,9 @@ INSERT INTO example(component, description, properties) VALUES
5357
"close":"Close",
5458
"embed":"/examples/chart.sql?_sqlpage_embed"
5559
},
56-
{"component": "button"},
57-
{"title":"Open a modal with a chart","modal":"my_embed_chart_modal"}
60+
{"component": "datagrid"},
61+
{"title":"Chart", "color":"blue", "description":"Revenue", "link":"#my_embed_chart_modal"},
62+
{"title":"Form", "color":"green", "description":"Fill info", "link":"#my_embed_form_modal"},
5863
]')
5964
),
6065
('modal',
@@ -70,11 +75,10 @@ INSERT INTO example(component, description, properties) VALUES
7075
"embed_mode":"iframe",
7176
"height":"350"
7277
},
73-
{"component": "button"},
74-
{"title":"Open a modal with a video","modal":"my_embed_video_modal"}
78+
{"component": "text", "contents_md": "Open a [modal with a video](#my_embed_video_modal)"}
7579
]')
7680
);
7781

7882
INSERT INTO parameter(component, name, description, type, top_level, optional) SELECT 'button', * FROM (VALUES
7983
('modal','Display the modal window corresponding to the specified ID.','TEXT',FALSE,TRUE)
80-
) x;
84+
) x;

sqlpage/sqlpage.js

Lines changed: 30 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -332,21 +332,37 @@ add_init_fn(sqlpage_form);
332332
add_init_fn(load_scripts);
333333

334334
function init_bootstrap_components(event) {
335-
if (window.bootstrap) {
336-
const fragment = event.target;
337-
for (const el of fragment.querySelectorAll('[data-bs-toggle="tooltip"]')) {
338-
new bootstrap.Tooltip(el);
339-
}
340-
for (const el of fragment.querySelectorAll('[data-bs-toggle="popover"]')) {
341-
new bootstrap.Popover(el);
342-
}
343-
for (const el of fragment.querySelectorAll('[data-bs-toggle="dropdown"]')) {
344-
new bootstrap.Dropdown(el);
345-
}
346-
for (const el of fragment.querySelectorAll('[data-bs-ride="carousel"]')) {
347-
new bootstrap.Carousel(el);
348-
}
335+
const bootstrap = window.bootstrap || window.tabler.bootstrap;
336+
const fragment = event.target;
337+
for (const el of fragment.querySelectorAll('[data-bs-toggle="tooltip"]')) {
338+
new bootstrap.Tooltip(el);
339+
}
340+
for (const el of fragment.querySelectorAll('[data-bs-toggle="popover"]')) {
341+
new bootstrap.Popover(el);
342+
}
343+
for (const el of fragment.querySelectorAll('[data-bs-toggle="dropdown"]')) {
344+
new bootstrap.Dropdown(el);
345+
}
346+
for (const el of fragment.querySelectorAll('[data-bs-ride="carousel"]')) {
347+
new bootstrap.Carousel(el);
349348
}
350349
}
351350

352351
document.addEventListener("fragment-loaded", init_bootstrap_components);
352+
353+
function open_modal_for_hash() {
354+
const hash = window.location.hash.substring(1);
355+
if (!hash) return;
356+
const modal = document.getElementById(hash);
357+
const classes = modal.classList;
358+
if (!modal || !classes.contains("modal")) return;
359+
const bootstrap_modal =
360+
window.tabler.bootstrap.Modal.getOrCreateInstance(modal);
361+
bootstrap_modal.show();
362+
modal.addEventListener("hidden.bs.modal", () => {
363+
window.history.replaceState(null, "", "#");
364+
});
365+
}
366+
367+
window.addEventListener("hashchange", open_modal_for_hash);
368+
window.addEventListener("DOMContentLoaded", open_modal_for_hash);

sqlpage/templates/modal.handlebars

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
1-
<div class="modal{{~#if class}} {{class}}{{/if~}}" id="{{id}}" tabindex="-1" aria-hidden="false" aria-labelledby="{{title}}">
1+
<div class="modal {{~#if class}} {{class}}{{/if~}}"
2+
id="{{id}}"
3+
tabindex="-1"
4+
aria-hidden="false"
5+
aria-labelledby="{{title}}">
26
<div role="document" class="modal-dialog {{#if small}} modal-sm{{/if}}{{#if large}} modal-lg{{/if}}{{#if scrollable}} modal-dialog-scrollable{{/if}}">
37
<div class="modal-content">
48
<div class="modal-header">
@@ -47,4 +51,3 @@
4751
</div>
4852
</div>
4953
</div>
50-

0 commit comments

Comments
 (0)