phihung commited on
Commit
0760113
·
1 Parent(s): 9d8008e

feat: Display server calls

Browse files
public/script.js ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ function init_sub_page(slug) {
2
+ console.log(`init_sub_page ${slug}`);
3
+ document.addEventListener("htmx:configRequest", (event) => {
4
+ if (!event.detail.path.startsWith(`/${slug}`)) {
5
+ event.detail.path = `/${slug}${event.detail.path}`;
6
+ }
7
+ });
8
+
9
+ document.addEventListener("htmx:afterRequest", (event) => {
10
+ let req = event.detail.requestConfig;
11
+ let detail = {
12
+ verb: req.verb,
13
+ path: req.path.slice(slug.length + 1),
14
+ parameters: Object.fromEntries(req.formData),
15
+ headers: req.headers,
16
+ response: event.detail.xhr.response,
17
+ };
18
+ window.parent.document.dispatchEvent(
19
+ new CustomEvent("SubappAfterRequest", { detail })
20
+ );
21
+ });
22
+ }
23
+
24
+ function init_main_page() {
25
+ console.log("init_main_page");
26
+ window.document.addEventListener(
27
+ "SubappAfterRequest",
28
+ (e) => {
29
+ // console.log(e);
30
+ htmx.ajax("PUT", "/requests", {
31
+ target: "#request-list",
32
+ values: e.detail,
33
+ swap: "afterbegin",
34
+ });
35
+ },
36
+ false
37
+ );
38
+ }
src/tutorial/__init__.py CHANGED
@@ -1,8 +1,9 @@
1
  import importlib
 
2
  from pathlib import Path
3
 
4
  import fasthtml.common as fh
5
- from fasthtml.common import A, Div, Table, Tbody, Td, Th, Thead, Tr
6
 
7
  from tutorial import utils
8
  from tutorial.example import Example
@@ -10,27 +11,23 @@ from tutorial.example import Example
10
  hdrs = (
11
  fh.MarkdownJS(),
12
  utils.HighlightJS(langs=["python", "javascript", "html", "css"]),
13
- *fh.Socials(
14
- title="HTMX examples with FastHTML",
15
- description="Reproduction of HTMX official examples with Python FastHTML",
16
- site_name="phihung-htmx-examples.hf.space",
17
- twitter_site="@hunglp",
18
- image="/social.png",
19
- url="https://phihung-htmx-examples.hf.space",
20
- ),
21
  utils.alpine(),
 
 
22
  )
23
  html_kv = {
24
- "x-data": "{darkMode: localStorage.getItem('darkMode') || (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light')}",
25
- "x-init": "$watch('darkMode', val => localStorage.setItem('darkMode', val))",
 
 
 
26
  "x-bind:data-theme": "darkMode !== 'system'? darkMode : (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light')",
27
  }
28
 
29
  app, rt = fh.fast_app(hdrs=hdrs, static_path="public", htmlkw=html_kv, surreal=False)
30
 
31
  htmx_examples = sorted([f.stem for f in Path(__file__).parent.glob("htmx/*.py") if f.stem not in ["__init__"]])
32
-
33
-
34
  INTRO = """
35
  # HTMX examples with FastHTML
36
 
@@ -62,6 +59,26 @@ def homepage():
62
  )
63
 
64
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
65
  def get_app():
66
  for name in htmx_examples:
67
  get_example(name).create_routes(app)
 
1
  import importlib
2
+ from dataclasses import dataclass
3
  from pathlib import Path
4
 
5
  import fasthtml.common as fh
6
+ from fasthtml.common import H4, A, Div, Pre, Table, Tbody, Td, Th, Thead, Tr
7
 
8
  from tutorial import utils
9
  from tutorial.example import Example
 
11
  hdrs = (
12
  fh.MarkdownJS(),
13
  utils.HighlightJS(langs=["python", "javascript", "html", "css"]),
14
+ utils.social_card(),
 
 
 
 
 
 
 
15
  utils.alpine(),
16
+ fh.Script(src="/script.js"),
17
+ fh.Script("init_main_page()"),
18
  )
19
  html_kv = {
20
+ "x-data": """{
21
+ showRequests: localStorage.getItem('showRequests') == 'true',
22
+ darkMode: localStorage.getItem('darkMode') || (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light')
23
+ }""",
24
+ "x-init": "$watch('darkMode', val => localStorage.setItem('darkMode', val));$watch('showRequests', val => localStorage.setItem('showRequests', val))",
25
  "x-bind:data-theme": "darkMode !== 'system'? darkMode : (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light')",
26
  }
27
 
28
  app, rt = fh.fast_app(hdrs=hdrs, static_path="public", htmlkw=html_kv, surreal=False)
29
 
30
  htmx_examples = sorted([f.stem for f in Path(__file__).parent.glob("htmx/*.py") if f.stem not in ["__init__"]])
 
 
31
  INTRO = """
32
  # HTMX examples with FastHTML
33
 
 
59
  )
60
 
61
 
62
+ @dataclass
63
+ class RequestInfo:
64
+ verb: str
65
+ path: str
66
+ parameters: str
67
+ headers: str
68
+ response: str
69
+
70
+
71
+ @app.put("/requests")
72
+ def requests(r: RequestInfo):
73
+ return Div(**{"x-data": "{show: false}", "@click": "show = !show"})(
74
+ H4(x_text="(show?'▽':'▶') + ' " + r.verb.upper() + " " + r.path + "'"),
75
+ Div(**{"x-show": "show"})(
76
+ Div(Pre("Input: " + r.parameters)),
77
+ Div(Pre(r.response or "(empty response)"), style="max-height:150px;overflow:scroll;"),
78
+ ),
79
+ )
80
+
81
+
82
  def get_app():
83
  for name in htmx_examples:
84
  get_example(name).create_routes(app)
src/tutorial/example.py CHANGED
@@ -6,7 +6,7 @@ from pathlib import Path
6
  from types import ModuleType
7
 
8
  import fasthtml.common as fh
9
- from fasthtml.common import H1, A, Code, Div, Hgroup, P, Pre
10
 
11
  from tutorial import utils
12
 
@@ -28,6 +28,10 @@ class Example:
28
  def doc(self):
29
  return self.module.DOC
30
 
 
 
 
 
31
  @cached_property
32
  def slug(self):
33
  return self.name.replace("_", "-")
@@ -44,9 +48,8 @@ class Example:
44
 
45
  def create_routes(self, main_app: fh.FastHTML):
46
  sub_app, slug = self.module.app, self.slug
47
- self._fix_url()
48
  sub_app.htmlkw = main_app.htmlkw
49
- sub_app.hdrs.append(utils.alpine())
50
  main_app.mount(f"/{slug}", sub_app)
51
  main_app.get(f"/{slug}")(self.main_page)
52
 
@@ -60,7 +63,7 @@ class Example:
60
  doc = _replace_code_blocks(module, self.doc)
61
  content = Div(doc, cls="marked")
62
 
63
- return fh.Main(cls="container")(
64
  Hgroup(H1(self.title), P(self.desc)),
65
  Div(
66
  *utils.concat(
@@ -72,24 +75,20 @@ class Example:
72
  )
73
  ),
74
  Div(cls="grid")(
75
- Div(content, style="height:80vh;overflow:scroll"),
76
  Div(
77
- fh.Iframe(src=self.start_url, height="500px", width="100%"),
 
 
 
 
 
 
 
78
  ),
79
  ),
80
  )
81
 
82
- def _fix_url(self):
83
- sub_app, slug = self.module.app, self.slug
84
- code = f"""
85
- document.addEventListener('htmx:configRequest', (event) => {{
86
- if (!event.detail.path.startsWith('/{slug}')) {{
87
- event.detail.path = `/{slug}${{event.detail.path}}`
88
- }}
89
- }})
90
- """
91
- sub_app.hdrs.append(fh.Script(code))
92
-
93
 
94
  def _replace_code_blocks(module, doc):
95
  """Replace placeholders by real implementations"""
 
6
  from types import ModuleType
7
 
8
  import fasthtml.common as fh
9
+ from fasthtml.common import H1, H3, A, Code, Div, Hgroup, P, Pre
10
 
11
  from tutorial import utils
12
 
 
28
  def doc(self):
29
  return self.module.DOC
30
 
31
+ @cached_property
32
+ def height(self):
33
+ return getattr(self.module, "HEIGHT", "500px")
34
+
35
  @cached_property
36
  def slug(self):
37
  return self.name.replace("_", "-")
 
48
 
49
  def create_routes(self, main_app: fh.FastHTML):
50
  sub_app, slug = self.module.app, self.slug
 
51
  sub_app.htmlkw = main_app.htmlkw
52
+ sub_app.hdrs += (utils.alpine(), fh.Script(src="/script.js"), fh.Script(f"init_sub_page('{slug}')"))
53
  main_app.mount(f"/{slug}", sub_app)
54
  main_app.get(f"/{slug}")(self.main_page)
55
 
 
63
  doc = _replace_code_blocks(module, self.doc)
64
  content = Div(doc, cls="marked")
65
 
66
+ return fh.Main(cls="container", x_cloak=True)(
67
  Hgroup(H1(self.title), P(self.desc)),
68
  Div(
69
  *utils.concat(
 
75
  )
76
  ),
77
  Div(cls="grid")(
78
+ Div(content, style="max-height:80vh;overflow:scroll"),
79
  Div(
80
+ Div(cls="grid")(
81
+ fh.Label(fh.Input(type="checkbox", role="switch", x_model="showRequests"), "Show requests"),
82
+ ),
83
+ Div(fh.Iframe(src=self.start_url, height=self.height, width="100%")),
84
+ ),
85
+ Div(**{"x-show": "showRequests"})(
86
+ H3("Server Calls"),
87
+ Div(Div(id="request-list"), style="height:80vh;overflow:scroll"),
88
  ),
89
  ),
90
  )
91
 
 
 
 
 
 
 
 
 
 
 
 
92
 
93
  def _replace_code_blocks(module, doc):
94
  """Replace placeholders by real implementations"""
src/tutorial/htmx/_01_click_to_edit.py CHANGED
@@ -18,16 +18,16 @@ def get_contact():
18
  return Div(hx_target="this", hx_swap="outerHTML", cls="container")(
19
  Div(P(f"Name : {current.name}")),
20
  Div(P(f"Email : {current.email}")),
21
- Button("Click To Edit", hx_get=contact_edit.rt(), cls="btn primary"),
22
  )
23
 
24
 
25
  @app.get("/contact/edit")
26
  def contact_edit():
27
- return Form(hx_put=put_contact.rt(), hx_target="this", hx_swap="outerHTML", cls="container")(
28
  Div(Label("Name"), Input(type="text", name="name", value=current.name)),
29
  Div(Label("Email"), Input(type="email", name="email", value=current.email)),
30
- Div(Button("Submit"), Button("Cancel", hx_get=get_contact.rt()), cls="grid"),
31
  )
32
 
33
 
@@ -54,3 +54,4 @@ The form issues a PUT back to /contact, following the usual REST-ful pattern.
54
 
55
  ::put_contact::
56
  """
 
 
18
  return Div(hx_target="this", hx_swap="outerHTML", cls="container")(
19
  Div(P(f"Name : {current.name}")),
20
  Div(P(f"Email : {current.email}")),
21
+ Button("Click To Edit", hx_get=contact_edit, cls="btn primary"),
22
  )
23
 
24
 
25
  @app.get("/contact/edit")
26
  def contact_edit():
27
+ return Form(hx_put=put_contact, hx_target="this", hx_swap="outerHTML", cls="container")(
28
  Div(Label("Name"), Input(type="text", name="name", value=current.name)),
29
  Div(Label("Email"), Input(type="email", name="email", value=current.email)),
30
+ Div(Button("Submit"), Button("Cancel", hx_get=get_contact), cls="grid"),
31
  )
32
 
33
 
 
54
 
55
  ::put_contact::
56
  """
57
+ HEIGHT = "300px"
src/tutorial/htmx/_02_bulk_update.py CHANGED
@@ -18,8 +18,7 @@ app, rt = fast_app(hdrs=[Style(css)])
18
 
19
  @app.get
20
  def page():
21
- # Bug with default enctype: multipart/form-data
22
- return Form(enctype="", hx_post=update.rt(), hx_swap="outerHTML settle:3s", hx_target="#toast", cls="container")(
23
  Table(
24
  Thead(Tr(Th("Name"), Th("Email"), Th("Active"))),
25
  Tbody(
@@ -51,3 +50,4 @@ The cool thing is that, because HTML form inputs already manage their own state,
51
  You can see a working example of this code below.
52
  ::page update::
53
  """
 
 
18
 
19
  @app.get
20
  def page():
21
+ return Form(hx_post=update, hx_swap="outerHTML settle:3s", hx_target="#toast", cls="container")(
 
22
  Table(
23
  Thead(Tr(Th("Name"), Th("Email"), Th("Active"))),
24
  Tbody(
 
50
  You can see a working example of this code below.
51
  ::page update::
52
  """
53
+ HEIGHT = "300px"
src/tutorial/htmx/_04_delete_row.py CHANGED
@@ -74,3 +74,4 @@ Tr(
74
  )
75
  ```
76
  """
 
 
74
  )
75
  ```
76
  """
77
+ HEIGHT = "350px"
src/tutorial/htmx/_05_edit_row.py CHANGED
@@ -38,7 +38,7 @@ def edit_view(idx: int):
38
  return Tr(hx_trigger="cancel", hx_get=f"/contact/{idx}", cls="editing")(
39
  Td(Input(name="name", value=name)),
40
  Td(Input(name="email", value=email)),
41
- Td(
42
  Button("Cancel", hx_get=f"/contact/{idx}", cls="btn secondary"),
43
  Button("Save", hx_put=f"/contact/{idx}", hx_include="closest tr", cls="btn primary"),
44
  ),
 
38
  return Tr(hx_trigger="cancel", hx_get=f"/contact/{idx}", cls="editing")(
39
  Td(Input(name="name", value=name)),
40
  Td(Input(name="email", value=email)),
41
+ Td(cls="grid")(
42
  Button("Cancel", hx_get=f"/contact/{idx}", cls="btn secondary"),
43
  Button("Save", hx_put=f"/contact/{idx}", hx_include="closest tr", cls="btn primary"),
44
  ),
src/tutorial/htmx/_06_lazy_loading.py CHANGED
@@ -16,17 +16,17 @@ app, rt = fast_app(hdrs=[Style(css)])
16
 
17
  @app.get
18
  def page():
19
- return Div(hx_get=get_content.rt(), hx_trigger="load", cls="container")(
20
  Img(src="/img/bars.svg", alt="Result loading...", cls="htmx-indicator", width="150"),
21
  )
22
 
23
 
24
  @app.get
25
  def get_content():
26
- time.sleep(3)
27
  return Div(
28
- NotStr("<ins>This simple text takes 3s to load!</ins>"),
29
- Button("Reload", hx_target="body", hx_get="/page"),
30
  )
31
 
32
 
@@ -38,3 +38,4 @@ This example shows how to lazily load an element on a page. We start with an ini
38
  Which shows a progress indicator as we are loading the graph. The graph is then loaded and faded gently into view via a settling CSS transition:
39
  ::css::
40
  """
 
 
16
 
17
  @app.get
18
  def page():
19
+ return Div(hx_get=get_content, hx_trigger="load", cls="container")(
20
  Img(src="/img/bars.svg", alt="Result loading...", cls="htmx-indicator", width="150"),
21
  )
22
 
23
 
24
  @app.get
25
  def get_content():
26
+ time.sleep(2)
27
  return Div(
28
+ NotStr("<ins>This simple text takes 2s to load!</ins>"),
29
+ Button("Reload", hx_target="body", hx_get=page),
30
  )
31
 
32
 
 
38
  Which shows a progress indicator as we are loading the graph. The graph is then loaded and faded gently into view via a settling CSS transition:
39
  ::css::
40
  """
41
+ HEIGHT = "200px"
src/tutorial/htmx/_07_inline_validation.py CHANGED
@@ -29,7 +29,7 @@ def page():
29
 
30
  @app.post("/contact/email")
31
  def validate_email(email: str):
32
- time.sleep(2)
33
  return make_email_field(email, email != "[email protected]", True)
34
 
35
 
@@ -37,7 +37,7 @@ def make_email_field(value: str, error: bool, touched: bool):
37
  cls = "" if not touched else "error" if error else "valid"
38
  return Div(hx_target="this", hx_swap="outerHTML", cls=cls)(
39
  Label("Email"),
40
- Input(name="email", hx_post=validate_email.rt(), hx_indicator="#ind", value=value),
41
  Img(id="ind", src="/img/bars.svg", cls="htmx-indicator"),
42
  Span("Please enter a valid email address", style="color:red;") if error else None,
43
  )
@@ -57,3 +57,4 @@ When a request occurs, it will return a partial to replace the outer div. It mig
57
  This form can be lightly styled with this CSS to give better visual feedback.
58
  ::css::
59
  """
 
 
29
 
30
  @app.post("/contact/email")
31
  def validate_email(email: str):
32
+ time.sleep(1)
33
  return make_email_field(email, email != "[email protected]", True)
34
 
35
 
 
37
  cls = "" if not touched else "error" if error else "valid"
38
  return Div(hx_target="this", hx_swap="outerHTML", cls=cls)(
39
  Label("Email"),
40
+ Input(name="email", hx_post=validate_email, hx_indicator="#ind", value=value),
41
  Img(id="ind", src="/img/bars.svg", cls="htmx-indicator"),
42
  Span("Please enter a valid email address", style="color:red;") if error else None,
43
  )
 
57
  This form can be lightly styled with this CSS to give better visual feedback.
58
  ::css::
59
  """
60
+ HEIGHT = "400px"
src/tutorial/htmx/_09_active_search.py CHANGED
@@ -12,7 +12,7 @@ def page():
12
  return Div(
13
  H3("Search Contacts"),
14
  Input(
15
- hx_post=search.rt(), hx_target="#results",
16
  hx_trigger="input changed delay:500ms, search", hx_indicator=".htmx-indicator",
17
  type="search", name="query", placeholder="Begin Typing To Search Users...",
18
  ),
 
12
  return Div(
13
  H3("Search Contacts"),
14
  Input(
15
+ hx_post=search, hx_target="#results",
16
  hx_trigger="input changed delay:500ms, search", hx_indicator=".htmx-indicator",
17
  type="search", name="query", placeholder="Begin Typing To Search Users...",
18
  ),
src/tutorial/htmx/_10_progress_bar.py CHANGED
@@ -35,7 +35,7 @@ current = 1
35
  def page():
36
  return Div(hx_target="this", hx_swap="outerHTML")(
37
  H3("Start Progress"),
38
- Button("Start Job", hx_post=start.rt(), cls="btn primary"),
39
  )
40
 
41
 
@@ -43,9 +43,9 @@ def page():
43
  def start():
44
  global current
45
  current = 1
46
- return Div(hx_trigger="done", hx_get=job_finished.rt(), hx_swap="outerHTML", hx_target="this")(
47
  H3("Running", role="status", id="pblabel", tabindex="-1", autofocus=""),
48
- Div(hx_get=progress_bar.rt(), hx_trigger="every 600ms", hx_target="this", hx_swap="innerHTML")(
49
  progress_bar(),
50
  ),
51
  )
@@ -67,7 +67,7 @@ def job_finished():
67
  return Div(hx_swap="outerHTML", hx_target="this")(
68
  H3("Complete", role="status", id="pblabel", tabindex="-1", autofocus=""),
69
  Div(Div(style="width:100%", id="THIS_ID_IS_INDISPENSIBLE", cls="progressbar"), cls="progress"),
70
- Button("Restart Job", hx_post=start.rt(), cls="btn primary show"),
71
  )
72
 
73
 
@@ -82,3 +82,4 @@ This progress bar is updated every 600 milliseconds, with the “width” style
82
  Finally, when the process is complete, a server returns HX-Trigger: done header, which triggers an update of the UI to “Complete” state with a restart button added to the UI:
83
  ::job_finished::
84
  """
 
 
35
  def page():
36
  return Div(hx_target="this", hx_swap="outerHTML")(
37
  H3("Start Progress"),
38
+ Button("Start Job", hx_post=start, cls="btn primary"),
39
  )
40
 
41
 
 
43
  def start():
44
  global current
45
  current = 1
46
+ return Div(hx_trigger="done", hx_get=job_finished, hx_swap="outerHTML", hx_target="this")(
47
  H3("Running", role="status", id="pblabel", tabindex="-1", autofocus=""),
48
+ Div(hx_get=progress_bar, hx_trigger="every 600ms", hx_target="this", hx_swap="innerHTML")(
49
  progress_bar(),
50
  ),
51
  )
 
67
  return Div(hx_swap="outerHTML", hx_target="this")(
68
  H3("Complete", role="status", id="pblabel", tabindex="-1", autofocus=""),
69
  Div(Div(style="width:100%", id="THIS_ID_IS_INDISPENSIBLE", cls="progressbar"), cls="progress"),
70
+ Button("Restart Job", hx_post=start, cls="btn primary show"),
71
  )
72
 
73
 
 
82
  Finally, when the process is complete, a server returns HX-Trigger: done header, which triggers an update of the UI to “Complete” state with a restart button added to the UI:
83
  ::job_finished::
84
  """
85
+ HEIGHT = "200px"
src/tutorial/htmx/_11_cascading_select.py CHANGED
@@ -53,3 +53,4 @@ When a request is made to the /models end point, we return the models for that m
53
 
54
  And they become available in the model select.
55
  """
 
 
53
 
54
  And they become available in the model select.
55
  """
56
+ HEIGHT = "300px"
src/tutorial/htmx/_12_animations.py CHANGED
@@ -80,7 +80,7 @@ def demo1(idx: int = 0):
80
 
81
  @app.get
82
  def demo2():
83
- return Button("Fade Me Out", cls="fade-me-out", hx_delete=demo2_delete.rt(), hx_swap="outerHTML swap:2s")
84
 
85
 
86
  @app.delete
@@ -90,12 +90,12 @@ def demo2_delete():
90
 
91
  @app.get
92
  def demo3():
93
- return Button("Fade Me In", hx_get=demo3.rt(), hx_swap="outerHTML settle:1s", id="demo3")
94
 
95
 
96
  @app.get
97
  def demo4():
98
- return Form(hx_post=demo4_form.rt(), hx_swap="outerHTML")(
99
  Input(name="name", placeholder="Your name here..."), Button("Submit", cls="btn primary")
100
  )
101
 
@@ -164,3 +164,4 @@ TODO
164
  ### Conclusion
165
  You can use the techniques above to create quite a few interesting and pleasing effects with plain old HTML while using htmx.
166
  """
 
 
80
 
81
  @app.get
82
  def demo2():
83
+ return Button("Fade Me Out", cls="fade-me-out", hx_delete=demo2_delete, hx_swap="outerHTML swap:2s")
84
 
85
 
86
  @app.delete
 
90
 
91
  @app.get
92
  def demo3():
93
+ return Button("Fade Me In", hx_get=demo3, hx_swap="outerHTML settle:1s", id="demo3")
94
 
95
 
96
  @app.get
97
  def demo4():
98
+ return Form(hx_post=demo4_form, hx_swap="outerHTML")(
99
  Input(name="name", placeholder="Your name here..."), Button("Submit", cls="btn primary")
100
  )
101
 
 
164
  ### Conclusion
165
  You can use the techniques above to create quite a few interesting and pleasing effects with plain old HTML while using htmx.
166
  """
167
+ HEIGHT = "300px"
src/tutorial/htmx/_13_file_upload.py CHANGED
@@ -21,10 +21,10 @@ def method1():
21
  htmx.find('#progress').setAttribute('value', evt.detail.loaded/evt.detail.total * 100)
22
  });
23
  """
24
- form = Form(hx_target="#output", hx_post=upload.rt(), id="form")(
25
  Input(type="file", name="file"),
26
  Button("Upload"),
27
- Progress(id="progress", value="0", max="100"),
28
  Div(id="output"),
29
  )
30
  return form, Script(js)
@@ -33,10 +33,10 @@ def method1():
33
  @app.get
34
  def method2():
35
  script = "on htmx:xhr:progress(loaded, total) set #progress2.value to (loaded/total)*100"
36
- return Form(_=script, hx_target="#output2", hx_post=upload.rt())(
37
  Input(type="file", name="file"),
38
  Button("Upload"),
39
- Progress(id="progress2", value="0", max="100"),
40
  Div(id="output2"),
41
  )
42
 
 
21
  htmx.find('#progress').setAttribute('value', evt.detail.loaded/evt.detail.total * 100)
22
  });
23
  """
24
+ form = Form(hx_target="#output", hx_post=upload, id="form")(
25
  Input(type="file", name="file"),
26
  Button("Upload"),
27
+ Progress(id="progress", value="0", max="100", style="margin-top:20px"),
28
  Div(id="output"),
29
  )
30
  return form, Script(js)
 
33
  @app.get
34
  def method2():
35
  script = "on htmx:xhr:progress(loaded, total) set #progress2.value to (loaded/total)*100"
36
+ return Form(_=script, hx_target="#output2", hx_post=upload)(
37
  Input(type="file", name="file"),
38
  Button("Upload"),
39
+ Progress(id="progress2", value="0", max="100", style="margin-top:20px"),
40
  Div(id="output2"),
41
  )
42
 
src/tutorial/utils.py CHANGED
@@ -1,5 +1,5 @@
1
  import fasthtml.common as fh
2
- from fasthtml.js import Script, jsd, light_media
3
 
4
 
5
  def HighlightJS(
@@ -11,13 +11,13 @@ def HighlightJS(
11
  "Implements browser-based syntax highlighting. Usage example [here](/tutorials/quickstart_for_web_devs.html#code-highlighting)."
12
  src = (
13
  """
14
- hljs.addPlugin(new CopyButtonPlugin());
15
  hljs.configure({'cssSelector': '%s'});
16
  htmx.onLoad(hljs.highlightAll);"""
17
  % sel
18
  )
19
  hjs = "highlightjs", "cdn-release", "build"
20
- hjc = "arronhunt", "highlightjs-copy", "dist"
21
  if isinstance(langs, str):
22
  langs = [langs]
23
  langjs = [jsd(*hjs, f"languages/{lang}.min.js") for lang in langs]
@@ -25,16 +25,30 @@ htmx.onLoad(hljs.highlightAll);"""
25
  jsd(*hjs, f"styles/{dark}.css", typ="css", **{"x-bind:disabled": "darkMode !== 'dark'"}),
26
  jsd(*hjs, f"styles/{light}.css", typ="css", **{"x-bind:disabled": "darkMode !== 'light'"}),
27
  jsd(*hjs, "highlight.min.js"),
28
- jsd(*hjc, "highlightjs-copy.min.js"),
29
- jsd(*hjc, "highlightjs-copy.min.css", typ="css"),
30
- light_media(".hljs-copy-button {background-color: #2d2b57;}"),
31
  *langjs,
32
  Script(src, type="module"),
33
  ]
34
 
35
 
36
  def alpine():
37
- return fh.Script(src="https://cdn.jsdelivr.net/npm/[email protected]/dist/cdn.min.js", defer=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
38
 
39
 
40
  def concat(*elts, sep=" | "):
 
1
  import fasthtml.common as fh
2
+ from fasthtml.js import Script, jsd
3
 
4
 
5
  def HighlightJS(
 
11
  "Implements browser-based syntax highlighting. Usage example [here](/tutorials/quickstart_for_web_devs.html#code-highlighting)."
12
  src = (
13
  """
14
+ // hljs.addPlugin(new CopyButtonPlugin());
15
  hljs.configure({'cssSelector': '%s'});
16
  htmx.onLoad(hljs.highlightAll);"""
17
  % sel
18
  )
19
  hjs = "highlightjs", "cdn-release", "build"
20
+ # hjc = "arronhunt", "highlightjs-copy", "dist"
21
  if isinstance(langs, str):
22
  langs = [langs]
23
  langjs = [jsd(*hjs, f"languages/{lang}.min.js") for lang in langs]
 
25
  jsd(*hjs, f"styles/{dark}.css", typ="css", **{"x-bind:disabled": "darkMode !== 'dark'"}),
26
  jsd(*hjs, f"styles/{light}.css", typ="css", **{"x-bind:disabled": "darkMode !== 'light'"}),
27
  jsd(*hjs, "highlight.min.js"),
28
+ # jsd(*hjc, "highlightjs-copy.min.js"),
29
+ # jsd(*hjc, "highlightjs-copy.min.css", typ="css"),
30
+ # fh.Style(".hljs-copy-button {background-color: #2d2b57;}", **{"x-bind:disabled": "darkMode !== 'light'"}),
31
  *langjs,
32
  Script(src, type="module"),
33
  ]
34
 
35
 
36
  def alpine():
37
+ return (
38
+ fh.Script(src="https://cdn.jsdelivr.net/npm/[email protected]/dist/cdn.min.js", defer=True),
39
+ fh.Style("[x-cloak] {\n display: none !important; \n}"),
40
+ )
41
+
42
+
43
+ def social_card():
44
+ return fh.Socials(
45
+ title="HTMX examples with FastHTML",
46
+ description="Reproduction of HTMX official examples with Python FastHTML",
47
+ site_name="phihung-htmx-examples.hf.space",
48
+ twitter_site="@hunglp",
49
+ image="/social.png",
50
+ url="https://phihung-htmx-examples.hf.space",
51
+ )
52
 
53
 
54
  def concat(*elts, sep=" | "):