HTMX fragments

Render the same partial inside a page and on its own, for HTMX swaps.

HTMX swaps a piece of the page with HTML the server returns. Write that piece as a partial, and you can render it in two places: inside the full page on the first load, and on its own when HTMX asks for it.

The fragment, todos/list.pt:

<ul id="todos">
  <li tal:repeat="todo todos">${todo}</li>
</ul>

The page, todos/index.pt, includes it:

<h1>Todos</h1>
<form hx-post="/todos" hx-target="#todos" hx-swap="outerHTML">
  <input name="title"><button>Add</button>
</form>
${render_partial('todos/list.pt', todos=todos)}

The views render the page on GET and only the fragment on POST:

import flask

import chameleon_rs
import chameleon_rs.flask as chameleon_flask

app = flask.Flask(__name__)
chameleon_flask.configure(template_folder=template_folder)
todos = ['Write docs']


@app.get('/todos')
@chameleon_flask.template('todos/index.pt')
def index():
    return {'todos': todos}


@app.post('/todos')
def add():
    todos.append(flask.request.form['title'])
    return chameleon_rs.render_partial('todos/list.pt', todos=todos).html_text


client = app.test_client()
print(client.post('/todos', data={'title': 'Ship it'}).text)
<ul id="todos">
  <li>Write docs</li>
  <li>Ship it</li>
</ul>

chameleon_rs.render_partial() returns an HTML object. Its html_text is the markup. Because the fragment is one template file, the page and the HTMX response can’t drift apart.

chameleon_flask.response('todos/list.pt', todos=todos) does the same and returns a flask.Response, if you’d rather set a content type or status code.