pelme / htpy

Generate HTML in Python
http://htpy.dev
MIT License
261 stars 11 forks source link

Request for Blank or Empty Element #22

Closed zulqasar closed 4 months ago

zulqasar commented 6 months ago

I was trying to implement htmx infinite scroll with a table in FastAPI (Async) and wanted to return a list of tr.

import htpy as h

# the fix 
# The request is for integrating this into htpy 
class NoNameElement(h.Element):
    def __iter__(self):
        yield from h._iter_children(self._children)

blank = NoNameElement("blank_", {}, None)

def get_table_rows(data: list[dict[str, Any]], page: int | str = 0):
    table_rows = []
    *data, last_record = data 
    for record in data:
        td = []
        for k, v in record.items():
            if k != "id":
                td.append(h.td[v])
        table_rows.append(h.tr[*td])

    # last row with htmx
    td = []
    for k, v in last_record.items():
        if k != "id":
            td.append(h.td[v])

    url = f"/my_api_url?page={page + 1}"            
    table_rows.append(
        h.tr(
            hx_get=url,
            hx_trigger="revealed",
            hx_swap="afterend",
            hx_target="this",
        )[*td]
    )
    return blank[*table_rows]
pelme commented 6 months ago

Interesting. lists are generally used for fragments and works fine as long as you don't need to output a list of elements instead of a root element. Am a bit hesistant to add this since i thinkn that lists/tuples are more intuitive fragments.

Would using something return ''.join(str(e) for e in table_rows) work in your case?

Would be happy to document it!

pelme commented 6 months ago

html tables are always problematic in these cases... in you particular case with htmx, another option is to wrap all your rows in a <table> (return table[table_rows]). You could then use hx-select="table" to have htmx pull the children out of the wrapping table.

zulqasar commented 6 months ago

Thanks for your time. ''.join(str(e) for e in table_rows) works when the return is rendered to the browser. However trying to wrap the return in h.body when returning the table on first call does not work because the str is escaped (the default behavior in htpy).

As regards to your second suggestion, HTMX is a new thing for me. As of now as a beginner I was following the examples code on HTMX and checking how to handle it using htpy and FastAPI.

pelme commented 6 months ago

In that case, return table_rows from get_table_rows() directly. Then get_table_rows() can be used both in the initial page load by just putting it in a table[] On the htmx load FastAPI endpoint, use ''.join(str(e) for e in get_table_rows(...).

zulqasar commented 6 months ago

That works. Had to break down the code into smaller functions.

Thanks A LOT.

pelme commented 6 months ago

I like fragments as lists and think it may be confusing to add a blank/empty/fragment since it would serve the same purpose most of the time. See #23 for too.

I am instead thinking that it could be useful to add a render function that could render any Node, so something like: from htpy.util import render / render(get_table_rows()) in this case.

What do you think about that?

The implementation would probably be:

def render(node: Node) -> Markup:
    return Markup("".join(str(x) for x in _iter_children(node)))
pelme commented 4 months ago

htpy 24.7.2 has the render_node function that can be used to render a list of elements without a parent element:

https://htpy.dev/usage/#render-elements-without-a-parent-orphans