Skip to content

Interactive apps

Hyperflask can be used to create interactive apps thanks to HTMX.

HTMX introduction

HTMX has comprehensive documentation but this section will cover the essential.

TODO

Interactive components

Components can define their own custom backend logic. Similar to pages, files should use the jpy extension and provide a frontmatter with the python code.

Functions named after HTTP methods will be registered as routes. For example, if a get function exists, Hyperflask will make the component accessible through GET requests.
These functions can return a dict with component props to render the component or any other valid Flask response value.

Use HTMX to call your component logic and retrieve only the necessery HTML.

Let's create a todo app:

app/pages/index.jpy:

---
from app.models import Todo
page.todos = Todo.find_all()
---
<table>
    {% for todo in todos %}
        <{TodoItem todo=todo }/>
    {% endfor %}
</table>
<{button hx-get=url_for('TodoItemForm') hx-target="previous" hx-swap="beforeend"}>Add todo</{button}>

app/components/TodoItem.jpy:

---
from app.models import db, Todo

def get():
    todo = Todo.get_or_404(request.args['id'])
    return {"todo": todo}

def delete():
    with db:
        todo = Todo.get_or_404(request.args['id'])
        todo.delete()
    return ""
---
<tr>
    <td>{{props.todo.title}}</td>
    <td>
        <{button hx-get=url_for('TodoItemForm', id=props.todo.id) hx-target="closest tr" hx-swap="outerHTML"}>Edit</{button}>
        <{button hx-delete=url_for('TodoItem', id=props.todo.id) hx-target="closest tr" hx-swap="delete"}>Delete</{button}>
    </td>
</tr>

app/components/TodoItemForm.jpy:

---
from hyperflask import request, current_app
from app.models import db, Todo

def get():
    todo = Todo.get_or_404(request.args['id']) if "id" in request.args else None
    return {"todo": todo}

def post():
    with db:
        if "id" in request.values:
            todo = Todo.get_or_404(request.args['id'])
        else:
            todo = Todo()
        todo.title = request.form["title"]
        todo.save()
    return current_app.components.TodoItem(todo=todo) # return another component
---
<tr>
    <td>
        <input type="text" name="title" value="{{props.todo.title if props.todo else ''}}" required>
    </td>
    <td>
        <{button hx-get=url_for('TodoItemForm', id=props.todo.id if props.todo else None) hx-include="closest tr" hx-target="closest tr" hx-swap="outerHTML"}>Save</{button}>
    </td>
</tr>

Warning

Unlike pages, all python imports are mandatory in components

Warning

When using Alpine.js in components that will be loaded via htmx, Alpine may not be included automatically. To ensure it is included, use current_app.assets.include('@hyperflask/alpine') in your page python code.

Tip

The request object in Hyperflask uses htmx-Flask subclass that provides easy access to htmx headers.

Pure frontend interactions

For simple frontend interactions, we recommend using Alpine.js with html components.

Sometines, you need a truly interactive component. In this case you can use Web Components, React or any other frontend framework. Creating frontend components is fully integrated in Hyperflask component system.

Highligting active URLs

Use the built-in hx-active-url htmx extension. Apply the attribute to anchor elements or their parents. Its value is a class name that will be applied to the anchor element when its url matches window.location.

The url is resolved in the following order: hx-push-url, hx-replace-url, href, hx-get.

<nav hx-active-url="active">
    <a hx-get="/page1" hx-push-url="true">Page 1</a>
    <a href="/page2">Page 2</a>
</nav>

When a link is fetching a component, the url may not be the right one. Use hx-href to setup the href attribute properly and activate hx-push-url:

<nav hx-active-url="active">
    <a hx-get={{url_for('Component')}} hx-href={{url_for('index', id=1)}}>Page 1</a>
</nav>

HTMX utilities

Perform an htmx redirection using htmx_redirect():

from hyperflask import htmx_redirect
def post():
    # ...
    return htmx_redirect("/")

Perform an Out-Of-Band (oob) swap using htmx_oob():

from hyperflask import htmx_oob
def post():
    # ...
    return htmx_oob(current_app.components.Sidebar()) # replaces the sidebar element with a new version of itself

You can also add out of band elements in your template using the <{HtmxOob}> component:

<{HtmxOob}><{Sidebar}/></{HtmxOob}>

Realtime apps

Add realtime updates to your app using push messages.