Pagination กับ Flatpages


2016/11/28 17:55:03 |

สวัสดีครับ ตอนที่ผมเขียนเว็บ maipatana.me นี้ขึ้นมา ก็พยายามหา tutorial ว่าคนอื่นๆเค้าทำหน้า (pagination) กันยังไง ก็หาเจอแต่วิธีที่ใช้ SQLAlchemy แต่ไม่เจอที่ใช้ Flask-Flatpages ที่ดูจะใกล้เคียงที่สุดคือ อันนี้ แต่ผมดูแล้วมันซับซ้อน งงๆ ผมเลยเขียน function ที่จัดการตรงนี้ขึ้นมา

อธิบาย

หลักการก็คือ เราจะให้แต่ละหน้าแสดงจำหนวนโพสตามที่เรากำหนด เช่น หน้าละ 5 โพส หรือ 10 โพสก็ว่ากันไป โดยใช้วิธี slice ([ : ]) โพสจากทั้งหมดมา ยกตัวอย่างสำหรับหน้าละ 5 โพส หน้าแรกก็ [:5] หน้าสองก็ [5:10] หน้าสามก็ [10:15] เป็นต้น

จากข้างบน เราก็จะได้

posts = posts[(page*per_page)-per_page:page*per_page]

โดย posts คือโพสทั้งหมด page คือหน้าที่กำลังถูกเรียกใช้งาน per_page คือจำนวนโพสต่อหน้า แปลว่า ถ้า 5 โพสต่อหน้า (per_page) และเราอยู่หน้าที่ 2 ก็จะเป็น posts[(2*5)-5:2*5] = posts[5:10] แบบที่เราต้องการ

โอเค เราได้วิธีที่ให้แสดงโพสในแต่ละหน้าแล้ว เมื่อเอาไปใส่ใน __init__.py ก็จะมีรูปร่างหน้าตาแบบนี้

@app.route('/posts/', defaults={'page': 1})
@app.route("/posts/page/<int:page>/")
def posts(page):
    posts = [p for p in flatpages if p.path.startswith(POSTS_DIR)]
    posts.sort(key=lambda item:item['date'], reverse=True)
    posts = posts[(page*per_page)-per_page:page*per_page]
    return render_template('posts.html', posts=posts)
# POSTS_DIR คือ directory ที่ไฟล์ Markdown เก็บอยู่

จริงๆถ้าเราจะเอาแค่ให้มันแสดงผลตามหน้าที่เราใส่ไปใน url ละก็ เท่านี้ก็ได้แล้ว แต่ยังใช้งานจริงๆไม่ได้ เพราะเราต้องการปุ่มเลื่อนหน้าด้วย สิ่งที่เราต้องรู้คือ

  1. อย่างแรกเราก็ต้องรู้ก่อนว่าทั้งหมดจะมีกี่หน้า เราจะได้สามารถทำ < 1 2 3... > แบบนี้ได้
  2. เราก็ต้องรู้ด้วยว่าตอนนี้เราอยู่หน้าไหน เราจะได้ทำให้หน้าปัจจุบันกดไม่ได้ หรือทำให้ปุ่มของหน้าปัจจุบันดูแปลกตาไป
  3. ถ้าเป็นหน้าแรกหรือหน้าสุดท้าย ปุ่มลูกศรที่เลื่อหน้าไปจะใช้ไม่ได้ เช่น ถ้าเราอยู่หน้าแรก ลูกศรไปทางซ้ายก็จะต้องกดไม่ได้

เอาข้อแรกก่อน

ข้อแรกนี่เราจัดการในไฟล์ __init__.py วิธีที่ผมใช้หาจำนวนหน้าและสร้างเลขหน้าคือฟังชั่น โดยเราเอาจำนวนโพสทั้งหมด ค่อยๆลบออกไปด้วยจำนวนโพสต่อหนึ่งหน้า แล้วก็ .append หน้าที่เพิ่มขึ้นเข้าไปใน list ที่ชื่อ pages ก็จะทำให้เราได้ list เลขหน้าทั้งหมด

def pagenum(totalposts, postperpage):
    pages = [1]
    num = 2
    while totalposts > postperpage:
        pages.append(num)
        totalposts -= postperpage
        num += 1
    return pages
# totalposts คือจำนวนโพสทั้งหมด หรือ len(posts) นั่นเอง
# postperpage ก็คือ per_page นั่นเอง

ฉะนั้น เมื่อเราอัพเดท @app.route ของเราก็จะกลายเป็น

@app.route('/posts/', defaults={'page': 1})
@app.route("/posts/page/<int:page>/")
def posts(page):
    posts = [p for p in flatpages if p.path.startswith(POSTS_DIR)]
    posts.sort(key=lambda item:item['date'], reverse=True)
    total = len(posts)
    pages = pagenum(total, per_page)
    posts = posts[(page*per_page)-per_page:page*per_page]
    return render_template('posts.html', posts=posts, page=page, pages=pages)
# pages คือ list ของเลขหน้าทั้งหมด
# page คือหน้าปัจจุบัน


ข้อสอง

ข้อสองนี่เราไปทำใน template โดยใช้ Jinja อันนี้ง่ายมาก แค่บอกว่า ถ้า หน้าปัจจุบัน ตรงกับเลขหน้า ก็ให้ปุ่มมันกดไม่ได้ ก็คือว่า if หน้า (page) ปัจจุบัน == เลขหน้าใน list ที่ชื่อ pages ก็ให้มันกดแล้วอยู่ที่เดิม รูปร่างหน้าตาก็จะเป็น

{% for i in pages %}
    {% if i == page %}
        <li class="active"><a href="#">{{i}} <span class="sr-only">(current)</span></a></li>
    {% else %}
        <li><a href="{{ url_for('posts', page=i) }}">{{i}}</a></li>
    {% endif %}
{% endfor %}


ข้อสาม

จากนั้นก็บอกว่า ถ้า page ปัจจุบัน คือไม่ใช่หน้าแรก ให้ปุ่มลูกศรไปทางซ้ายกดได้ ถ้าเป็นหน้าแรก ให้มีปุ่มเฉยๆ แต่กดไม่ได้ และหน้าสุดท้ายก็เหมือนกัน ก็คือ if page != pages[-1] ก็ให้ปุ่มลูกศรไปทางขวากดไม่ได้

{% if page != 1 %} {# ถ้าหน้าไม่ใช่หน้าแรก #}
  <a href="{{ url_for('posts', page=page-1) }}" aria-label="Previous"> {# ให้มี tag <a> #}
{% endif %}
  <span aria-hidden="true">«</span>
  </a> {# จริงๆผมทำแบบนี้ก็ไม่ค่อยดีเท่าไหร่ เพราะถ้าเป็นหน้าแรกก็จะเหลือ </a> อันนี้เอาไว้ ไม่เรียบร้อย #}

{% if page != pages[-1] %} {# ถ้าไม่ใช่หน้าสุดท้าย #}
  <a href="{{ url_for('posts', page=page+1) }}" aria-label="Next">
{% endif %}
  <span aria-hidden="true">»</span>
  </a>


รวมกัน

เมื่อเอาในส่วนของ template ทั้งหมดมารวมกัน

{% if pages|length > 1 %} {# ถ้ามีมากกว่าหนึ่งหน้า ค่อยมีตัวให้เลือกหน้า #}
<div>
    <nav aria-label="Page navigation">
      <ul class="pagination pagination-justified">
        <li>
        {% if page != 1 %}
          <a href="{{ url_for('posts', page=page-1) }}" aria-label="Previous">
        {% endif %}
          <span aria-hidden="true">«</span>
          </a>
        </li>
          {% for i in pages %}
            {% if i == page %}
                <li class="active"><a href="#">{{i}} <span class="sr-only">(current)</span></a></li>
            {% else %}
                <li><a href="{{ url_for('posts', page=i) }}">{{i}}</a></li>
            {% endif %}
          {% endfor %}
        <li>
        {% if page != pages[-1] %}
          <a href="{{ url_for('posts', page=page+1) }}" aria-label="Next">
        {% endif %}
          <span aria-hidden="true">»</span>
          </a>
        </li>
      </ul>
    </nav>
</div>
{% endif %}

สรุป

จบแล้วครับ รูปร่างหน้าตาของมันก็ดูได้จากเว็บนี้ในหน้า blogs, tutorials, projects หรือพวก tags ได้ครับ เว็บนี้ก็ทำหน้าด้วย code นี้แหละครับ


| python | flask | pagination | flatpages |

โพสที่เกี่ยวข้อง