Skip to content

Commit dbac26c

Browse files
committed
Finish working functionality
1 parent f081595 commit dbac26c

File tree

1 file changed

+74
-12
lines changed

1 file changed

+74
-12
lines changed

examples/adv_app2.py

+74-12
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,9 @@
33
from functools import partial
44
from dataclasses import dataclass
55
from hmac import compare_digest
6-
from monsterui.all import *
7-
8-
db = database(':memory:')
96

107

8+
db = database(':memory:')
119

1210
class User: name:str; pwd:str
1311

@@ -26,12 +24,17 @@ def __ft__(self:Todo):
2624
# which HTMX uses to trigger a GET request.
2725
# Generally, most of your route handlers in practice (as in this demo app) are likely to be HTMX handlers.
2826
# For instance, for this demo, we only have two full-page handlers: the '/login' and '/' GET handlers.
29-
show = AX(self.title, f'/todos/{self.id}', 'current-todo')
30-
edit = AX('edit', f'/edit/{self.id}' , 'current-todo')
31-
dt = '✅ ' if self.done else ''
27+
### show = AX(self.title, f'/todos/{self.id}', 'current-todo')
28+
### edit = AX('edit', f'/edit/{self.id}' , 'current-todo')
29+
### dt = '✅ ' if self.done else ''
3230
# FastHTML provides some shortcuts. For instance, `Hidden` is defined as simply:
3331
# `return Input(type="hidden", value=value, **kwargs)`
34-
cts = (dt, show, ' | ', edit, Hidden(id="id", value=self.id), Hidden(id="priority", value="0"))
32+
cts = ('✅ ' if self.done else '',
33+
AX(self.title, todo_detail.to(id=self.id), 'current-todo'),
34+
' | ',
35+
AX('edit', todo_edit.to(id=self.id) , 'current-todo'),
36+
Hidden(id="id", value=self.id),
37+
Hidden(id="priority", value="0"))
3538
# Any FT object can take a list of children as positional args, and a dict of attrs as keyword args.
3639
return Li(*cts, id=f'todo-{self.id}')
3740

@@ -44,19 +47,30 @@ def user_auth_before(req, sess):
4447
user_auth_before,
4548
skip=[r'/favicon\.ico', r'/static/.*', r'.*\.css', r'.*\.js', '/login']
4649
)
47-
app, rt = fast_app(hdrs=Theme.blue.headers()+[SortableJS('.sortable'),],before=beforeware)
50+
app, rt = fast_app(hdrs=[SortableJS('.sortable'),],before=beforeware)
4851

4952
# Authentication
5053
login_redir = Redirect('/login')
5154

5255
@rt('/login')
5356
def get():
57+
# This creates a form with two input fields, and a submit button.
58+
# All of these components are `FT` objects. All HTML tags are provided in this form by FastHTML.
59+
# If you want other custom tags (e.g. `MyTag`), they can be auto-generated by e.g
60+
# `from fasthtml.components import MyTag`.
61+
# Alternatively, manually call e.g `ft(tag_name, *children, **attrs)`.
5462
frm = Form(
55-
LabelInput("Name", name='name'),
56-
LabelInput("Password", name='pwd', type='password'),
63+
# Tags with a `name` attr will have `name` auto-set to the same as `id` if not provided
64+
Input(id='name', placeholder='Name'),
65+
Input(id='pwd', type='password', placeholder='Password'),
5766
Button('login'),
5867
action='/login', method='post')
59-
return Titled("Login", frm, cls=ContainerT.sm)
68+
# If a user visits the URL directly, FastHTML auto-generates a full HTML page.
69+
# However, if the URL is accessed by HTMX, then one HTML partial is created for each element of the tuple.
70+
# To avoid this auto-generation of a full page, return a `HTML` object, or a Starlette `Response`.
71+
# `Titled` returns a tuple of a `Title` with the first arg and a `Container` with the rest.
72+
# See the comments for `Title` later for details.
73+
return Titled("Login", frm)
6074

6175
@dataclass
6276
class Login: name:str; pwd:str
@@ -90,7 +104,7 @@ def index(auth):
90104

91105
@rt
92106
def add_todo(todo:Todo, auth):
93-
new_inp = LabelInput('Title', id="new-title", name="title", placeholder="New Todo", hx_swap_oob='true')
107+
new_inp = Input(id="new-title", name="title", placeholder="New Todo", hx_swap_oob='true')
94108
# `insert` returns the inserted todo, which is appended to the start of the list, because we used
95109
# `hx_swap='afterbegin'` when creating the todo list form.
96110
return db.todos.insert(todo), new_inp
@@ -105,5 +119,53 @@ def reorder(id:list[int]):
105119
# and the server.
106120
return tuple(db.todos(order_by='priority'))
107121

122+
@rt
123+
def todo_detail(id:int):
124+
todo = db.todos[id]
125+
# `hx_swap` determines how the update should occur. We use "outerHTML" to replace the entire todo `Li` element.
126+
btn = Button('delete', hx_delete=todos_delete.to(id=id),
127+
target_id=f'todo-{todo.id}', hx_swap="outerHTML")
128+
# The "markdown" class is used here because that's the CSS selector we used in the JS earlier.
129+
# Therefore this will trigger the JS to parse the markdown in the details field.
130+
# Because `class` is a reserved keyword in Python, we use `cls` instead, which FastHTML auto-converts.
131+
return Div(H2(todo.title), Div(todo.details, cls="markdown"), btn)
132+
133+
@rt
134+
def todo_edit(id:int):
135+
# The `hx_put` attribute tells HTMX to send a PUT request when the form is submitted.
136+
# `target_id` specifies which element will be updated with the server's response.
137+
res = Form(Group(Input(id="title"), Button("Save")),
138+
Hidden(id="id"), CheckboxX(id="done", label='Done'),
139+
Textarea(id="details", name="details", rows=10),
140+
hx_put="/", target_id=f'todo-{id}', id="edit")
141+
# `fill_form` populates the form with existing todo data, and returns the result.
142+
# Indexing into a table (`todos`) queries by primary key, which is `id` here. It also includes
143+
# `xtra`, so this will only return the id if it belongs to the current user.
144+
return fill_form(res, db.todos[id])
145+
146+
# Refactoring components in FastHTML is as simple as creating Python functions.
147+
# The `clr_details` function creates a Div with specific HTMX attributes.
148+
# `hx_swap_oob='innerHTML'` tells HTMX to swap the inner HTML of the target element out-of-band,
149+
# meaning it will update this element regardless of where the HTMX request originated from.
150+
def clr_details(): return Div(hx_swap_oob='innerHTML', id='current-todo')
151+
152+
@rt("/")
153+
def put(todo: Todo):
154+
# `update` is part of the MiniDataAPI spec.
155+
# Note that the updated todo is returned. By returning the updated todo, we can update the list directly.
156+
# Because we return a tuple with `clr_details()`, the details view is also cleared.
157+
return db.todos.update(todo), clr_details()
158+
159+
160+
# This route handler uses a path parameter `{id}` which is automatically parsed and passed as an int.
161+
@rt(methods=['DELETE'])
162+
def todos_delete(id:int):
163+
# The `delete` method is part of the MiniDataAPI spec, removing the item with the given primary key.
164+
db.todos.delete(id)
165+
# Returning `clr_details()` ensures the details view is cleared after deletion,
166+
# leveraging HTMX's out-of-band swap feature.
167+
# Note that we are not returning *any* FT component that doesn't have an "OOB" swap, so the target element
168+
# inner HTML is simply deleted. That's why the deleted todo is removed from the list.
169+
return clr_details()
108170

109171
serve()

0 commit comments

Comments
 (0)