Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit e82a7d4

Browse files
authoredApr 5, 2025··
Merge pull request #704 from AnswerDotAI/fix-toast-insertion
inserts toasts at top of the page
2 parents 760fc6c + 642ff5c commit e82a7d4

File tree

3 files changed

+16
-11
lines changed

3 files changed

+16
-11
lines changed
 

‎fasthtml/toaster.py

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -35,24 +35,22 @@
3535

3636
def Toast(message: str, typ: str = "info", dismiss: bool = False, duration:int=5000):
3737
x_btn = Button('x', cls="fh-toast-dismiss", onclick="htmx.remove(this?.parentElement);") if dismiss else None
38-
return Div(Div(Span(message), x_btn, cls=f"fh-toast fh-toast-{typ}", hx_on_transitionend="setTimeout(() => this?.remove(), %d);" % duration), hx_swap_oob=f"afterbegin:#{tcid}")
38+
return Div(Span(message), x_btn, cls=f"fh-toast fh-toast-{typ}", hx_on_transitionend="setTimeout(() => this?.remove(), %d);" % duration)
3939

4040
def add_toast(sess, message: str, typ: str = "info", dismiss: bool = False):
4141
assert typ in ("info", "success", "warning", "error"), '`typ` not in ("info", "success", "warning", "error")'
4242
sess.setdefault(sk, []).append((message, typ, dismiss))
4343

4444
def render_toasts(sess):
45-
toasts = [Toast(msg, typ, dismiss, sess['toast_duration'])
46-
for msg, typ, dismiss in sess.pop(sk, [])]
47-
return Div(*toasts)
45+
toasts = [Toast(msg, typ, dismiss, sess['toast_duration']) for msg, typ, dismiss in sess.pop(sk, [])]
46+
return Div(*toasts, id=tcid, hx_swap_oob='afterbegin')
4847

4948
def toast_after(resp, req, sess):
5049
if sk in sess and (not resp or isinstance(resp, (tuple,FT,FtResponse))):
5150
sess['toast_duration'] = req.app.state.toast_duration
5251
req.injects.append(render_toasts(sess))
5352

54-
js = Script("htmx.onLoad(() => {if (!document.querySelector('#%s')){const elm = document.createElement('div'); elm.id='%s'; document.body.prepend(elm);}})" % (tcid, tcid))
5553
def setup_toasts(app, duration=5000):
5654
app.state.toast_duration = duration
57-
app.hdrs += [Style(toast_css), js]
58-
app.after.append(toast_after)
55+
app.hdrs += [Style(toast_css)]
56+
app.after.append(toast_after)

‎nbs/tutorials/quickstart_for_web_devs.ipynb

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1282,7 +1282,9 @@
12821282
"2. Toasts require sessions\n",
12831283
"3. Views with Toasts must return FT or FtResponse components.\n",
12841284
"\n",
1285-
"💡 `setup_toasts` takes a `duration` input that allows you to specify how long a toast will be visible before disappearing. For example `setup_toasts(duration=5)` sets the toasts duration to 5 seconds. By default toasts disappear after 10 seconds."
1285+
"💡 `setup_toasts` takes a `duration` input that allows you to specify how long a toast will be visible before disappearing. For example `setup_toasts(duration=5)` sets the toasts duration to 5 seconds. By default toasts disappear after 10 seconds.\n",
1286+
"\n",
1287+
"⚠️ Toasts don't work with SPA like navigation that replaces the entire body such as this navigation trigger `A('About', hx_get=\"/about\", hx_swap=\"outerHTML\", hx_push_url=\"true\", hx_target=\"body\")`. As an alternative, wrap the content of your route in an element containing an id and set this id as the target for your navigation trigger (i.e. `hx_target='#container_id'`). "
12861288
]
12871289
},
12881290
{
@@ -1633,9 +1635,9 @@
16331635
],
16341636
"metadata": {
16351637
"kernelspec": {
1636-
"display_name": "python3",
1638+
"display_name": "python",
16371639
"language": "python",
1638-
"name": "python3"
1640+
"name": "python"
16391641
}
16401642
},
16411643
"nbformat": 4,

‎tests/test_toaster.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,12 @@ def test_get_toaster_with_typehint():
5252
res = cli.get('/see-toast-with-typehint', follow_redirects=True)
5353
assert 'Toast get' in res.text
5454

55+
def test_toast_container_in_response():
56+
# toasts will not render correctly if the toast container isn't rendered.
57+
res = cli.get('/see-toast-ft-response')
58+
assert 'id="fh-toast-container"' in res.text
59+
5560
test_get_toaster()
5661
test_post_toaster()
5762
test_ft_response()
58-
63+
test_toast_container_in_response()

0 commit comments

Comments
 (0)