@@ -15,6 +15,23 @@ class Todo:
15
15
db .users = db .create (User , transform = True , pk = 'name' )
16
16
db .todos = db .create (Todo , transform = True )
17
17
18
+ @patch
19
+ def __ft__ (self :Todo ):
20
+ # Some FastHTML tags have an 'X' suffix, which means they're "extended" in some way.
21
+ # For instance, here `AX` is an extended `A` tag, which takes 3 positional arguments:
22
+ # `(text, hx_get, target_id)`.
23
+ # All underscores in FT attrs are replaced with hyphens, so this will create an `hx-get` attr,
24
+ # which HTMX uses to trigger a GET request.
25
+ # Generally, most of your route handlers in practice (as in this demo app) are likely to be HTMX handlers.
26
+ # For instance, for this demo, we only have two full-page handlers: the '/login' and '/' GET handlers.
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 ''
30
+ # FastHTML provides some shortcuts. For instance, `Hidden` is defined as simply:
31
+ # `return Input(type="hidden", value=value, **kwargs)`
32
+ cts = (dt , show , ' | ' , edit , Hidden (id = "id" , value = self .id ), Hidden (id = "priority" , value = "0" ))
33
+ # Any FT object can take a list of children as positional args, and a dict of attrs as keyword args.
34
+ return Li (* cts , id = f'todo-{ self .id } ' )
18
35
19
36
def user_auth_before (req , sess ):
20
37
auth = req .scope ['auth' ] = sess .get ('auth' , None )
@@ -25,26 +42,11 @@ def user_auth_before(req, sess):
25
42
user_auth_before ,
26
43
skip = [r'/favicon\.ico' , r'/static/.*' , r'.*\.css' , r'.*\.js' , '/login' ]
27
44
)
28
-
29
-
30
45
app , rt = fast_app (hdrs = Theme .blue .headers (),before = beforeware )
31
46
47
+ # Authentication
32
48
login_redir = Redirect ('/login' )
33
49
34
-
35
- @rt
36
- def index (auth ):
37
- top = Grid (Div (A ('logout' , href = logout ), style = 'text-align: right' ))
38
- new_inp = Input (id = "new-title" , name = "title" , placeholder = "New Todo" )
39
- add = Form (Group (new_inp , Button ("Add" )),
40
- hx_post = "/" , target_id = 'todo-list' , hx_swap = "afterbegin" )
41
- frm = Form (* db .todos (order_by = 'priority' ),
42
- id = 'todo-list' , cls = 'sortable' , hx_post = "/reorder" , hx_trigger = "end" )
43
-
44
- card = Card (Ul (frm ), header = add , footer = Div (id = 'current-todo' ))
45
- return Titled (f"{ auth } 's Todo list" , Container (top , card ))
46
-
47
-
48
50
@rt ('/login' )
49
51
def get ():
50
52
frm = Form (
@@ -71,4 +73,25 @@ def logout(sess):
71
73
del sess ['auth' ]
72
74
return login_redir
73
75
76
+ # Dashboard and control handlers
77
+ @rt
78
+ def index (auth ):
79
+ top = Grid (Div (A ('logout' , href = logout ), style = 'text-align: right' ))
80
+ new_inp = Input (id = "new-title" , name = "title" , placeholder = "New Todo" )
81
+ add = Form (Group (new_inp , Button ("Add" )),
82
+ hx_post = add_todo , target_id = 'todo-list' , hx_swap = "afterbegin" )
83
+ frm = Form (* db .todos (order_by = 'priority' ),
84
+ id = 'todo-list' , cls = 'sortable' , hx_post = "/reorder" , hx_trigger = "end" )
85
+
86
+ card = Card (Ul (frm ), header = add , footer = Div (id = 'current-todo' ))
87
+ return Titled (f"{ auth } 's Todo list" , Container (top , card ))
88
+
89
+ @rt
90
+ def add_todo (todo :Todo , auth ):
91
+ new_inp = LabelInput ('Title' , id = "new-title" , name = "title" , placeholder = "New Todo" , hx_swap_oob = 'true' )
92
+ # `insert` returns the inserted todo, which is appended to the start of the list, because we used
93
+ # `hx_swap='afterbegin'` when creating the todo list form.
94
+ return db .todos .insert (todo ), new_inp
95
+
96
+
74
97
serve ()
0 commit comments