33from functools import partial
44from dataclasses import dataclass
55from hmac import compare_digest
6- from monsterui .all import *
7-
8- db = database (':memory:' )
96
107
8+ db = database (':memory:' )
119
1210class 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
5053login_redir = Redirect ('/login' )
5154
5255@rt ('/login' )
5356def 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
6276class Login : name :str ; pwd :str
@@ -90,7 +104,7 @@ def index(auth):
90104
91105@rt
92106def 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
109171serve ()
0 commit comments