maipatana.me

Pagination กับ Flatpages

pythonflaskpaginationflatpages

สวัสดีครับ ตอนที่ผมเขียนเว็บ 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 ก็ให้มันกดแล้วอยู่ที่เดิม รูปร่างหน้าตาก็จะเป็น

{% highlight 'html+jinja' %} {% raw %} {% 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 %} {% endraw %} {% endhighlight %}

ข้อสาม

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

{% highlight 'html+jinja' %} {% raw %} {% if page != 1 %} {# ถ้าหน้าไม่ใช่หน้าแรก #} <a href="{{ url_for('posts', page=page-1) }}" aria-label="Previous"> {# ให้มี tag <a> #} {% endif %} <span aria-hidden="true">&laquo;</span> </a> {# จริงๆผมทำแบบนี้ก็ไม่ค่อยดีเท่าไหร่ เพราะถ้าเป็นหน้าแรกก็จะเหลือ </a> อันนี้เอาไว้ ไม่เรียบร้อย #} {% if page != pages[-1] %} {# ถ้าไม่ใช่หน้าสุดท้าย #} <a href="{{ url_for('posts', page=page+1) }}" aria-label="Next"> {% endif %} <span aria-hidden="true">&raquo;</span> </a> {% endraw %} {% endhighlight %}

รวมกัน

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

{% highlight 'html+jinja' %} {% raw %} {% 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">&laquo;</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">&raquo;</span> </a> </li> </ul> </nav> </div> {% endif %} {% endraw %} {% endhighlight %}

สรุป

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