Delete Row
This example shows how to implement a delete button that removes a table row upon completion. For this example, we use have a sample database of people which we display in a table. Each row in the table can be deleted by clicking a button and a confirmation prompt.
Demo
#Implementation
#Since we want to display people as rows in a table, we can create a dedicated class-based endpoint which can render itself like a component. Before we do that, we also need to declare what kind of data this endpoint handles:
1 from ludic.attrs import Attrs 2 3 class PersonAttrs(Attrs): 4 id: str 5 name: str 6 email: str 7 active: bool 8 9 class PeopleAttrs(Attrs): 10 people: list[PersonAttrs]
Now we can use these attributes in our class-based endpoint:
1 from typing import override 2 3 from ludic.catalog.tables import Table, TableRow 4 from ludic.catalog.buttons import ButtonDanger 5 from ludic.components import Component 6 from ludic.web import Endpoint, LudicApp 7 8 from your_app.attrs import PersonAttrs 9 from your_app.database import db 10 11 app = LudicApp() 12 13 @app.endpoint("/people/{id}") 14 class PersonRow(Endpoint[PersonAttrs]): 15 @classmethod 16 def delete(cls, id: str) -> None: 17 try: 18 db.people.pop(id) 19 except KeyError: 20 raise NotFoundError("Person not found") 21 22 @override 23 def render(self) -> TableRow: 24 return TableRow( 25 self.attrs["name"], 26 self.attrs["email"], 27 "Active" if self.attrs["active"] else "Inactive", 28 ButtonDanger( 29 "Delete", 30 hx_delete=self.url_for(PersonRow), 31 classes=["small"] 32 ), 33 )
We created a class-based endpoint with the following methods:
delete
– handles the DELETE request which removes a person from the database. We don't need to return anything in this case.render
– handles rendering of the table. Apart from the textual columns, we also render an action button to delete a row which is hooked to thePersonRow.delete()
method.
The last remaining part is the table with people itself. We can create another class-based endpoint for this task:
1 from typing import override 2 3 from ludic.catalog.tables import Table, TableRow 4 from ludic.catalog.buttons import ButtonDanger 5 from ludic.components import Component 6 from ludic.web import Endpoint, LudicApp 7 8 from your_app.attrs import PeopleAttrs 9 from your_app.database import db 10 11 # ... the PersonRow class is omitted here 12 13 @app.endpoint("/people/") 14 class PeopleTable(Endpoint[PeopleAttrs]): 15 styles = { 16 "tr.htmx-swapping td": { 17 "opacity": "0", 18 "transition": "opacity 1s ease-out", 19 } 20 } 21 22 @classmethod 23 def get(cls) -> Self: 24 return cls(people=[person.dict() for person in db.people.values()]) 25 26 @override 27 def render(self) -> Table[TableHead, PersonRow]: 28 return Table( 29 TableHead("Name", "Email", "Active", ""), 30 *(PersonRow(**person) for person in self.attrs["people"]), 31 body_attrs=HtmxAttrs( 32 hx_confirm="Are you sure?", 33 hx_target="closest tr", 34 hx_swap="outerHTML swap:1s", 35 ), 36 classes=["text-align-center"], 37 )
We created a class-based endpoint with the following methods:
get
– handles the GET request which returns an instance of thePeopleTable
filled with a list of people fetched from database.render
– handles rendering of the table of people. We use a specialbody_attrs
attribute to configure HTMX operations on thetbody
element.