bulk_update - Code
from fasthtml.common import *
from collections import defaultdict

app, rt = fast_app()

default_data = [
    {'id': 1, 'name': 'Alice', 'age': 25},
    {'id': 2, 'name': 'Bob', 'age': 30},
    {'id': 3, 'name': 'Charlie', 'age': 28},
]

data = defaultdict(lambda: [dict(d) for d in default_data])

@rt 
def index(session):
    # if no id, create one so diff users changes don't conflict
    if not session.get('id'): session['id'] = unqid()
    
    # Create a table based on the current users data
    rows = []
    for item in data[session['id']]:
        rows.append(
            Tr(Td(str(item['id'])),
               Td(Input(value=item['name'],    name=f"name{item['id']}", _id=f"name{item['id']}")),
               Td(Input(value=str(item['age']), name=f"age{item['id']}", _id=f"age{item['id']}"))))
    
    return Div(
        Form(
            Table(
                Thead(Tr(map(Th, ('ID', 'Name', 'Age')))),
                Tbody(*rows)),
            # Bulk update button that submits all inputs from the table because it's inside fot form.
            Button('Bulk Update', hx_post="update", hx_target='#response', hx_indicator="#loading", _type="button", hx_vals={'id': session['id']})),
        
        # Response div that will be updated with the result of the bulk update
        Div(id='response'),
        # Loading indicator that will be shown when the bulk update is happening
        Div(id="loading", style="display:none;", _class="loader"))

@rt
async def update(request, id:str):
    changes = []
    form_data = await request.form()

    # Iterate over the items in the users data
    for item in data[id]:
        # Get the new name and age from the form data
        new_name = form_data.get(f"name{item['id']}")
        new_age  = form_data.get(f"age{item['id']}")

        # Check if the item has changed and if so add it to the changes list
        if new_name != item['name'] or new_age != str(item['age']):
            changes.append(f"Row {item['id']} changed: Name {item['name']} → {new_name}, Age {item['age']} → {new_age}")
            item['name'] = new_name
            item['age'] = int(new_age)
    
    # Return the changes or a message if there are no changes
    return Div(*[Div(change) for change in changes]) if changes else Div("No changes detected")

serve()