htmlflaskflask-sqlalchemyflask-restfulhtmx

Infinite scroll with htmx in flask is not working for me


This is my first time using htmx and I'm trying to use the infinite scroll but it's not working. This is my code below

@app.route('/home', methods = ['GET'])



def home():
    # pagination
    page = request.args.get('page', 1, type=int)
    all_data = Data.query.order_by(desc(Data.timestamp)).paginate(page = page, per_page = 20)

    if "hx_request"  in request.headers:
        return render_template("table.html",datas = all_data)
    return render_template("home.html", datas = all_data)

This is the html code i am trying to add the infinite scroll to

{% for data in datas.items %}
{% if loop.last and data.has_next %}
<tr hx-get="{{url_for('home', page = data.next_num)}}" hx-trigger="revealed" hx-swap="afterend">
{% else %}
    <tr>
  {% endif %} 
     
        <td scope="row">{{data.uuid}}</td>
        <td scope="row">{{data.timestamp}}</td>
        <td scope="row">{{data.decibel}}</td>
        
    </tr>
    
    {% endfor %}

And this is my home.html that contains the table

{% extends 'layout.html' %}
{% block head %}
<title>home</title>
{% endblock %}
{% block body %}
<center>
<table class="table table-primary table-striped" >
    <tr> 
        <th scope="col">UUID</th>
        <th scope="col">Timestamp</th>
        <th scope="col">Decibel</th>
    </tr>
   <tbody>
    {% include 'table.html' %}


</tbody>


</table>
</center>
{% endblock %}

I updated some things here and there but it's still not working


Solution

  • The following example shows you a not very nice way of using htmx infinite scroll. It's pretty similar to your approach.

    from flask import (
        Flask,
        render_template,
        request
    )
    from flask_sqlalchemy import SQLAlchemy
    
    app = Flask(__name__)
    db = SQLAlchemy(app)
    
    class Record(db.Model):
        id = db.Column(db.Integer, primary_key=True)
        name = db.Column(db.String, unique=True, nullable=False)
    
    with app.app_context():
        db.drop_all()
        db.create_all()
        items = [Record(name=f'Record-{i}') for i in range(100)]
        db.session.add_all(items)
        db.session.commit()
    
    @app.route('/')
    def index():
        page = request.args.get('page', 1, type=int)
        records = Record.query.paginate(page=page, per_page=20)
        return render_template('index.html', **locals())
    

    A macro is defined which uses the paginate object to add the htmx pattern for each last row of the loaded chunk as long as there is another page.

    The macro iterates over the elements of the loaded page, returning a table row for each entry. The attributes necessary for the infinite scroll pattern are added to the last row, provided there is another page.
    In order to make the macro usable for other data records as well, the current object is sent back to the call within the iteration using caller(). This makes it usable as an argument in the nested block of the call. See also the call section in the documentation for a more detailed explanation.

    {% macro htmx_infinite_table_rows(paginate, endpoint) -%}
      {% for item in paginate.items -%}
        {% if loop.last and paginate.has_next -%}
        <tr
          hx-get="{{ url_for(endpoint, page=paginate.next_num) }}"
          hx-trigger="revealed"
          hx-swap="afterend"
        >
        {% else -%}
        <tr>
        {% endif -%}
          {{ caller(item) }}
        </tr>
      {% endfor -%}
    {% endmacro -%}
    
    {% if 'hx_request' in request.headers -%}
      {% call(record) htmx_infinite_table_rows(records, 'index') -%}
        <td>{{ record.name }}</td>
      {% endcall -%}
    {% else -%}
      <!DOCTYPE html>
      <html>
        <head>
          <meta charset="utf-8">
          <title></title>
          <style>
            .htmx-indicator {
              display:none;
            }
            .htmx-indicator.htmx-request {
              display:inline;
            }
          </style>
        </head>
        <body>
          <table>
            <thead>
              <tr>
                <th>Name</th>
              </tr>
            </thead>
            <tbody>
              {% call(record) htmx_infinite_table_rows(records, 'index') -%}
                <td>{{ record.name }}</td>
              {% endcall -%}
            </tbody>
          </table>
          <center><img class="htmx-indicator" width="60" src="https://htmx.org/img/bars.svg"></center>
          
          <script 
            src="https://unpkg.com/htmx.org@1.7.0" 
            integrity="sha384-EzBXYPt0/T6gxNp0nuPtLkmRpmDBbjg6WmCUZRLXBBwYYmwAUxzlSGej0ARHX0Bo" 
            crossorigin="anonymous" 
            defer></script>
        </body>
      </html>
    {% endif -%}
    

    Update:

    You made a mistake when getting the attributes has_next and next_num. The code should be as follows.

    {% for data in datas.items %}
      {% if loop.last and datas.has_next %}
      <tr hx-get="{{url_for('home', page=datas.next_num)}}" hx-trigger="revealed" hx-swap="afterend">
      {% else %}
      <tr>
      {% endif %}
        <td scope="row">{{data.uuid}}</td>
        <td scope="row">{{data.timestamp}}</td>
        <td scope="row">{{data.decibel}}</td>
      </tr>
    {% endfor %}