forked from Github-Mirrors/canaille
Dynamic tables with htmx
- Search is triggered with user inputs - Page changes are triggered with clicks
This commit is contained in:
parent
2d0c58c3e3
commit
cf9b5c11a3
14 changed files with 122 additions and 69 deletions
|
@ -29,6 +29,7 @@ from .apputils import obj_to_b64
|
|||
from .apputils import profile_hash
|
||||
from .flaskutils import current_user
|
||||
from .flaskutils import permissions_needed
|
||||
from .flaskutils import render_htmx_template
|
||||
from .flaskutils import smtp_needed
|
||||
from .flaskutils import user_needed
|
||||
from .forms import FirstLoginForm
|
||||
|
@ -187,7 +188,7 @@ def users(user):
|
|||
if request.form and not table_form.validate():
|
||||
abort(404)
|
||||
|
||||
return render_template(
|
||||
return render_htmx_template(
|
||||
"users.html",
|
||||
menuitem="users",
|
||||
table_form=table_form,
|
||||
|
|
|
@ -7,6 +7,7 @@ from canaille.models import User
|
|||
from flask import abort
|
||||
from flask import current_app
|
||||
from flask import render_template
|
||||
from flask import request
|
||||
from flask import session
|
||||
from flask_babel import gettext as _
|
||||
|
||||
|
@ -84,3 +85,12 @@ def set_parameter_in_url_query(url, **kwargs):
|
|||
parameters = {**parameters, **kwargs}
|
||||
split[3] = "&".join(f"{key}={value}" for key, value in parameters.items())
|
||||
return urlunsplit(split)
|
||||
|
||||
|
||||
def render_htmx_template(template, htmx_template=None, **kwargs):
|
||||
template = (
|
||||
(htmx_template or f"partial/{template}")
|
||||
if request.headers.get("HX-Request")
|
||||
else template
|
||||
)
|
||||
return render_template(template, **kwargs)
|
||||
|
|
|
@ -8,6 +8,7 @@ from flask_babel import gettext as _
|
|||
from flask_themer import render_template
|
||||
|
||||
from .flaskutils import permissions_needed
|
||||
from .flaskutils import render_htmx_template
|
||||
from .forms import CreateGroupForm
|
||||
from .forms import EditGroupForm
|
||||
from .forms import TableForm
|
||||
|
@ -24,7 +25,7 @@ def groups(user):
|
|||
if request.form and request.form.get("page") and not table_form.validate():
|
||||
abort(404)
|
||||
|
||||
return render_template("groups.html", menuitem="groups", table_form=table_form)
|
||||
return render_htmx_template("groups.html", menuitem="groups", table_form=table_form)
|
||||
|
||||
|
||||
@bp.route("/add", methods=("GET", "POST"))
|
||||
|
@ -96,8 +97,9 @@ def edit_group(group):
|
|||
else:
|
||||
flash(_("Group edition failed."), "error")
|
||||
|
||||
return render_template(
|
||||
return render_htmx_template(
|
||||
"group.html",
|
||||
"partial/users.html",
|
||||
form=form,
|
||||
edited_group=group,
|
||||
table_form=table_form,
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
from canaille.flaskutils import permissions_needed
|
||||
from canaille.flaskutils import render_htmx_template
|
||||
from canaille.forms import TableForm
|
||||
from canaille.oidc.models import AuthorizationCode
|
||||
from flask import abort
|
||||
|
@ -17,7 +18,7 @@ def index(user):
|
|||
if request.form and request.form.get("page") and not table_form.validate():
|
||||
abort(404)
|
||||
|
||||
return render_template(
|
||||
return render_htmx_template(
|
||||
"oidc/admin/authorization_list.html",
|
||||
menuitem="admin",
|
||||
table_form=table_form,
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import datetime
|
||||
|
||||
from canaille.flaskutils import permissions_needed
|
||||
from canaille.flaskutils import render_htmx_template
|
||||
from canaille.forms import TableForm
|
||||
from canaille.oidc.forms import ClientAdd
|
||||
from canaille.oidc.models import Client
|
||||
|
@ -25,7 +26,7 @@ def index(user):
|
|||
if request.form and request.form.get("page") and not table_form.validate():
|
||||
abort(404)
|
||||
|
||||
return render_template(
|
||||
return render_htmx_template(
|
||||
"oidc/admin/client_list.html", menuitem="admin", table_form=table_form
|
||||
)
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import datetime
|
||||
|
||||
from canaille.flaskutils import permissions_needed
|
||||
from canaille.flaskutils import render_htmx_template
|
||||
from canaille.forms import TableForm
|
||||
from canaille.models import User
|
||||
from canaille.oidc.models import Client
|
||||
|
@ -24,7 +25,7 @@ def index(user):
|
|||
if request.form and request.form.get("page") and not table_form.validate():
|
||||
abort(404)
|
||||
|
||||
return render_template(
|
||||
return render_htmx_template(
|
||||
"oidc/admin/token_list.html", menuitem="admin", table_form=table_form
|
||||
)
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
{% extends theme('base.html') %}
|
||||
{% import 'macro/fomanticui.html' as sui %}
|
||||
{% import "macro/table.html" as table %}
|
||||
|
||||
{% block script %}
|
||||
<script src="/static/js/confirm.js"></script>
|
||||
|
@ -87,6 +88,9 @@
|
|||
</div>
|
||||
</h2>
|
||||
|
||||
<div class="ui attached segment">
|
||||
{{ table.search(table_form, "table.users") }}
|
||||
</div>
|
||||
{% include "partial/users.html" %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
</div>
|
||||
</h2>
|
||||
<div class="ui attached segment">
|
||||
{{ table.search(table_form) }}
|
||||
{{ table.search(table_form, "table.groups") }}
|
||||
</div>
|
||||
{% include "partial/groups.html" %}
|
||||
</div>
|
||||
|
|
|
@ -1,70 +1,100 @@
|
|||
{% macro search(form) %}
|
||||
{% macro search(form, target) %}
|
||||
<form id="search" action="{{ url_for(request.url_rule.endpoint, **request.view_args) }}" method="POST" class="ui form">
|
||||
{{ form.hidden_tag() if form.hidden_tag }}
|
||||
<input type="hidden" name="page" value="{{ form.page.data }}">
|
||||
<div class="ui fluid action input">
|
||||
<input type="search" placeholder="{{ _("Search…") }}" name="{{ form.query.name }}" value="{{ form.query.data }}">
|
||||
<button type="submit" class="ui icon button" title="{{ _("Search") }}">
|
||||
<div class="ui fluid action input icon">
|
||||
<input
|
||||
type="search"
|
||||
placeholder="{{ _("Search…") }}"
|
||||
name="{{ form.query.name }}"
|
||||
value="{{ form.query.data }}"
|
||||
hx-post="{{ url_for(request.url_rule.endpoint, **request.view_args) }}"
|
||||
hx-trigger="keyup changed delay:500ms, search"
|
||||
hx-target="{{ target }}"
|
||||
hx-swap="outerHTML"
|
||||
hx-indicator=".search-button"
|
||||
/>
|
||||
<button type="submit" class="ui icon button search-button" title="{{ _("Search") }}">
|
||||
<i class="search icon"></i>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
{% endmacro %}
|
||||
|
||||
{% macro pagination(form) %}
|
||||
<form id="pagination" action="{{ url_for(request.url_rule.endpoint, **request.view_args) }}" method="POST" class="ui form">
|
||||
{# At the moment we need to build one form per button
|
||||
# https://github.com/bigskysoftware/htmx/issues/1120
|
||||
# when this is fixed we will be able to set the page
|
||||
# value directly in the submit button and get rid
|
||||
# of the radius reset
|
||||
#}
|
||||
{% macro buttonform(form, page) %}
|
||||
<form id="pagination" action="{{ url_for(request.url_rule.endpoint, **request.view_args) }}" method="POST">
|
||||
{{ form.hidden_tag() if form.hidden_tag }}
|
||||
<input type="hidden" name="page" value="{{ page }}">
|
||||
<input type="hidden" name="query" value="{{ form.query.data }}">
|
||||
<div class="ui right floated stackable buttons">
|
||||
<span class="icon disabled ui button">
|
||||
{% trans %}Page{% endtrans %}
|
||||
</span>
|
||||
{% if form.page.data > 1 %}
|
||||
<button name="page" type="submit" class="icon ui button" value="{{ form.page.data - 1 }}">
|
||||
<i class="left chevron icon"></i>
|
||||
</button>
|
||||
{% else %}
|
||||
<span class="icon disabled ui button">
|
||||
<i class="left chevron icon"></i>
|
||||
</span>
|
||||
{% endif %}
|
||||
{% if form.page.data > 1 %}
|
||||
<button name="page" type="submit" class="ui button" value="1">
|
||||
1
|
||||
</button>
|
||||
{% endif %}
|
||||
{% if form.page.data > 2 %}
|
||||
<span class="disabled ui button">
|
||||
…
|
||||
</span>
|
||||
{% endif %}
|
||||
<span class="ui button active">
|
||||
{{ form.page.data }}
|
||||
</span>
|
||||
{% if form.page.data < form.page_max - 1 %}
|
||||
<span class="disabled ui button">
|
||||
…
|
||||
</span>
|
||||
{% endif %}
|
||||
{% if form.page.data < form.page_max %}
|
||||
<button name="page" type="submit" class="ui button" value="{{ form.page_max }}">
|
||||
{{ form.page_max }}
|
||||
</button>
|
||||
{% endif %}
|
||||
{% if form.page.data < form.page_max %}
|
||||
<button name="page" type="submit" class="icon ui button" value="{{ form.page.data + 1 }}">
|
||||
<i class="right chevron icon"></i>
|
||||
</button>
|
||||
{% else %}
|
||||
<span class="icon disabled ui button">
|
||||
<i class="right chevron icon"></i>
|
||||
</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="ui left floated">
|
||||
<span class="disabled ui button">
|
||||
{{ _("%(nb_items)s items", nb_items=form.nb_items) }}
|
||||
</span>
|
||||
</div>
|
||||
<button
|
||||
type="submit"
|
||||
style="border-radius: 0"
|
||||
class="icon ui button"
|
||||
hx-post="{{ url_for(request.url_rule.endpoint, **request.view_args) }}"
|
||||
hx-target="closest table"
|
||||
hx-swap="outerHTML"
|
||||
>
|
||||
{{ caller() }}
|
||||
</button>
|
||||
</form>
|
||||
{% endmacro %}
|
||||
|
||||
{% macro pagination(form) %}
|
||||
<div class="ui right floated buttons">
|
||||
<span class="icon disabled ui button" style="border-radius: 0">
|
||||
{% trans %}Page{% endtrans %}
|
||||
</span>
|
||||
{% if form.page.data > 1 %}
|
||||
{% call buttonform(form, form.page.data - 1) %}
|
||||
<i class="left chevron icon"></i>
|
||||
{% endcall %}
|
||||
{% else %}
|
||||
<span class="icon disabled ui button" style="border-radius: 0">
|
||||
<i class="left chevron icon"></i>
|
||||
</span>
|
||||
{% endif %}
|
||||
{% if form.page.data > 1 %}
|
||||
{% call buttonform(form, 1) %}
|
||||
1
|
||||
{% endcall %}
|
||||
{% endif %}
|
||||
{% if form.page.data > 2 %}
|
||||
<span class="disabled ui button" style="border-radius: 0">
|
||||
…
|
||||
</span>
|
||||
{% endif %}
|
||||
<span class="ui button active">
|
||||
{{ form.page.data }}
|
||||
</span>
|
||||
{% if form.page.data < form.page_max - 1 %}
|
||||
<span class="disabled ui button" style="border-radius: 0">
|
||||
…
|
||||
</span>
|
||||
{% endif %}
|
||||
{% if form.page.data < form.page_max %}
|
||||
{% call buttonform(form, form.page_max) %}
|
||||
{{ form.page_max }}
|
||||
{% endcall %}
|
||||
{% endif %}
|
||||
{% if form.page.data < form.page_max %}
|
||||
{% call buttonform(form, form.page.data + 1) %}
|
||||
<i class="right chevron icon"></i>
|
||||
{% endcall %}
|
||||
{% else %}
|
||||
<span class="icon disabled ui button" style="border-radius: 0">
|
||||
<i class="right chevron icon"></i>
|
||||
</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="ui left floated">
|
||||
<span class="disabled ui button">
|
||||
{{ _("%(nb_items)s items", nb_items=form.nb_items) }}
|
||||
</span>
|
||||
</div>
|
||||
{% endmacro %}
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
|
||||
{% block content %}
|
||||
<div class="ui attached segment">
|
||||
{{ table.search(table_form) }}
|
||||
{{ table.search(table_form, "table.codes") }}
|
||||
</div>
|
||||
{% include "partial/oidc/admin/authorization_list.html" %}
|
||||
{% endblock %}
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
</div>
|
||||
|
||||
<div class="ui attached segment">
|
||||
{{ table.search(table_form) }}
|
||||
{{ table.search(table_form, "table.clients") }}
|
||||
</div>
|
||||
{% include "partial/oidc/admin/client_list.html" %}
|
||||
{% endblock %}
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
|
||||
{% block content %}
|
||||
<div class="ui attached segment">
|
||||
{{ table.search(table_form) }}
|
||||
{{ table.search(table_form, "table.tokens") }}
|
||||
</div>
|
||||
{% include "partial/oidc/admin/token_list.html" %}
|
||||
{% endblock %}
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class="ui attached segment">
|
||||
{{ table.search(table_form) }}
|
||||
{{ table.search(table_form, "table.users") }}
|
||||
</div>
|
||||
{% include "partial/users.html" %}
|
||||
{% endblock %}
|
||||
|
|
|
@ -105,10 +105,13 @@
|
|||
<footer>
|
||||
<a href="{{ url_for('account.about') }}">{{ _("About Canaille") }}</a>
|
||||
</footer>
|
||||
<script src="/static/htmx/htmx.min.js"></script>
|
||||
<script src="/static/jquery/jquery.min.js"></script>
|
||||
<script src="/static/fomanticui/semantic.min.js"></script>
|
||||
<script src="/static/htmx/htmx.min.js"></script>
|
||||
<script src="/static/js/base.js"></script>
|
||||
<script>
|
||||
htmx.config.requestClass = "loading"
|
||||
</script>
|
||||
{% block script %}{% endblock %}
|
||||
</body>
|
||||
</html>
|
||||
|
|
Loading…
Reference in a new issue