ทำ Pagination จัดเรียงหน้าด้วย Firebase Cloud Firestore + Vue CLI06 October 2020Programing

ทำ Pagination จัดเรียงหน้าด้วย Firebase Cloud Firestore + Vue CLI

เนื่องจากได้มีโอกาสได้เล่น feature ต่าง ๆ ของ Firebase มาสักระยะหนึ่งก็เลยอยากมานำเสนอหนึ่งใน feature ที่หน้าสนใจและเจาะเป็นเรื่องลงไป นั้นคือ Cloud Firestore กับการทำ Paginations ซึ่งบอกก่อนว่าจริง ๆ แล้ว Firebase มีเครื่องมือให้ลองเล่นเยอะมาก ๆ เช่น Functions, Storage, Hosting, Read time Database เป็นต้น

แต่วันนี้อยากจะมาลองทำ Paginations หรือการจัดหน้า เรียงหน้าสำหรับข้อมูลเยอะ ๆ เพื่อเป็นประโยชน์สำหรับเว็บไซต์หรือคลังข้อมูลใด ๆ ก็ตามที่ต้องการความเป็นระเบียบเรียบร้อย แน่นอนว่าการคลิ๊กไปหน้า 1, 2, 3 ของข้อมูลแต่ละชุด นั่นแหละ คือการทำ Paginations ล่ะครับ

แต่หากทำไมต้อง Vue เป็นตัวเสริม ขอบอกซ้ำอีกรอบ หลัก ๆ คืออยากให้เห็นภาพนั้นแค่นั้นและ แล้วผมเองก็อยากลองเล่น Vue ด้วยถ้ามันผิดพลาดตรงไหนก็กราบขอโทษด้วยนะครับ มาถ้าหากเราไปเรียกข้อมูลมากจาก Cloud Firestore แล้วเรามาทำ paginations มันดีนะเพราะมันทำให้ฝั่งที่เรียกข้อมูลจาก Cloud Firestore ไม่ว่าจะเป็นส่วน Client หรือ Server มันเร็วขึ้นด้วย เราสามารถนำ Cloud Firestore ไปใช้ได้ทั้ง Client ดังในตัวอย่างนี้นำไปใช้กับ Vue หรือจะไปใช้ร่วม Nodejs, Go, Php ก็ได้เช่นกัน

เดี๋ยววันนี้จะลองเอาข้อมูลตัวละครจากเกมส์ Esports ที่คนเล่นกันทั่วโลกอย่าง DOTA2 ซึ่งแน่นอนว่าผมก็เล่นด้วย มาจัดเรียงให้มันเป็นระเบียบให้ได้ชมกันครับ

Tools ที่ใช้

  1. Vue Cli
  2. Package Vuetify
  3. Firebase ( Cloud Firestore )
  4. Open Dota Api

วิธีทำ

1. เข้าไป https://console.firebase.google.com/ เพื่อไปสร้าง Project บน firebase

2. หลังจากที่ผมสร้างเสร็จแล้วก็เข้าไป coppy config ของ Firebase เพื่อมาในใช้ในงานของเรา ส่วนในรูปคือผมแอบไปใส่ข้อมูลมาไว้ก่อน แต่ว่าไม่ต้องเพิ่มข้อมูลแบบกดเอาน่ะนานไปฮ่าๆ เดียวผมจะอธิบายเพิ่มข้อมูลทีเดียว 100 ตัว

const firebaseConfig = {
  apiKey: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
  authDomain:"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
  databaseURL: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
  projectId: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
  storageBucket: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
  messagingSenderId: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
  appId:"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
  measurementId: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
};

หน้าตาของ firebaseConfig

3. สร้างโปรเจ็คด้วย Vue Cli

vue create hello-world
vue add vuetify // เพิ่ม vuetify เพื่อมาช่วยการทำ pagination ซึ่งผมจะนำเอา data table และก็ pagination มาใช้งานครับ
yarn add firebase
โครงสร้าง Vue หลังจากที่เราติดตั้งเสร็จ

4. import firebase มาใช้งาน

import firebase from "firebase";
firebase.initializeApp({
    apiKey: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
    authDomain: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
    databaseURL: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
    projectId: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
    storageBucket: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
    messagingSenderId: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
    appId: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
    measurementId: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
})
const db = firebase.firestore();
export {
    db
}

ถ้าใครงงกับผมแนะนำเปิด https://github.com/najaroen/pagination-firestore ดูไปด้วยนะครับ

สิ่งที่ผมเพิ่มมาในงานคือ index.js ซึ่งเป็นการ initialize firebase กับ exports firestore มาใช้งาน และก็ component DotaHero.vue ซึ่งผมทำ pagination ใน component นี้ครับ

5. ดึงข้อมูล Dota2 Hero เพื่อเพิ่มเข้าไปใน firestore

fetch('https://api.opendota.com/api/heroes')
                .then(response => response)
                .then(async result => {
                    let list = await result.json();
                    this.desserts = list;
                })
createCollections: function () {
                let batch = db.db.batch()
  // batch ของ firestore คือการทำ transaction ครับเราสามารถทำ action ต่าง ๆ ไม่ว่าเป็น set, update, delete ได้ถึง 500 ครั้งก่อน commit
  // ครั้งนี้ผมใช้ set
                this.desserts.forEach((v) => {
                    let doc = db.db.collection('hero').doc(v.id.toString())
 
                    batch.set(doc, {
  // ผม set object ที่ละตัวที่ได้มาจากการ GET Api มาจาก opendotaapi
                        id:v.id.toString(),
                        name:v.localized_name,
                        type:v.primary_attr,
                        attack_type:v.attack_type
                    })
                })
                batch.commit()
  // commit object ของเรา
                    .then(() => console.log('done'))
                    .catch((err) => console.error(err))
                return null
            }
<template>
    <v-container class="lighten-5">
        <v-row >
            <v-col>
                <v-data-table
                        :headers="headers"
                  // headers ของ data table
                        :items="desserts"
                  // items data table
                        :items-per-page='perPage'
                  // item ต่อหนึ่งหน้าตาราง
                        :hide-default-footer=true
                        :loading=isLoad
                        class="elevation-1"
                ></v-data-table>
                <div class="text-center">
                    <v-pagination
                            v-on:next="nextData"
                  // function กดปุ่ม next 
                            v-on:previous="previousData"
                  // function กดปุ่ม previous
                            v-on:input="inputData"
                  // function กดปุ่ม เลือกตามหน้าที่เราต้องการ
                            class="mt-2"
                            v-model="page"
                   // หน้าปัจจุบัน
                            :length="totalPage"
                   // จำนวนหน้าทั้งหมด
                    ></v-pagination>
                </div>
            </v-col>
        </v-row>
        <p>totalPage: {{totalPage}}</p>
        <p>totalItems: {{totalItems}}</p>
        <p>limit: {{limit}}</p>
    </v-container>
</template>

<script>
    const db  = require('../firebase/index')
    export default {
        name: "DotaHero",
        data() {
            return {
                perPage:20, // จำนวนข้อมูลต่อหน้า
                totalPage:0, // จำนวนหน้า
                limit:20, //
                page:1,
                totalItems:0, // จำนวนข้อมูลทั้งหมด
                isLoad:true,
                headers:[
                    { text: 'Id', value: 'id' },
                    { text: 'Name', value: 'name' },
                    { text: 'Type', value: 'type' },
                    { text: 'AttackType', value: 'attack_type' }
                    ],
                desserts:[],
            }
        },
        created() {
            this.initData() // เรียกให้โหลดข้อมูลครั้งแรก
        },
            // ประกาศฟังก์ชั่นที่ใช้ใน methods 
        methods:{
            previousData: function () { 
           // ฟังก์ชั่นกดปุ่มย้อนกลับ 
                this.fetchHero({page:this.page-1, limit:20})
            // ใน firestore ผมให้เริ่มที่หน้า 0 ทำให้ทุกครั้งที่กดเปลี่ยนผมต้องลบออก 1
            },
            nextData: function () {
            // ฟังก์ชั่นกดปุ่มถัดไป
                this.fetchHero({page:this.page-1, limit:20})
            // ใน firestore ผมให้เริ่มที่หน้า 0 ทำให้ทุกครั้งที่กดเปลี่ยนผมต้องลบออก 1
            },
            inputData: function (value) {
                this.page=value;
                this.fetchHero({page:this.page-1, limit:20})
            // ใน firestore ผมให้เริ่มที่หน้า 0 ทำให้ทุกครั้งที่กดเปลี่ยนผมต้องลบออก 1
            },
            initData: async function() {
            // ฟังก์ชั่นแรกที่เข้ามาใน component ครับ
              let totalHero  = await db.db.collection('hero').get()
                  .then((document) => document.docs.length)
              this.totalPage = Math.ceil(totalHero/this.limit)
            // จำนวนหน้าผมดึงจำนวนข้อมูลทั้งหมด หารด้วย จำนวนข้อมูลต่อหน้าแล้วปัดขึ้นจะได้จำนวนหน้าทั้งหมด
              this.totalItems = totalHero;
            // ข้อมูลทั้งหมด

                let indexOf = 0
            // index สำหรับที่่เราต้องการให้เริ่ม ผมใส่ 0 เพราะเข้ามาครั้งแรกให้เริ่มข้อมูล index 0
                let get = db.db.collection('hero')
                    .orderBy('id', 'asc')
                return get.get()
                    .then((document) => {
                        let last = document.docs[indexOf]
                        db.db.collection('hero')
                            .orderBy('id', "asc")
                            .startAt(last)
                            .limit(this.limit)
             // จำนวนต่อหน้า
                            .get()
                            .then((data) => {
                                let List = [];
                                data.forEach((v) => {
                                    List.push({
                                        id:+v.id,
                                        attack_type:v.data().attack_type,
                                        name:v.data().name,
                                        type:v.data().type
                                    })
                                })
                                this.desserts = List;
                                this.isLoad = false
                            })
                    }).catch((err) => {
                        console.log('err', err)
                    })

            },
            fetchHero: async function({page=0, limit=20}) {
                this.isLoad = true
                let indexOf = this.totalPage * page
                   // เอาจำนวนต่อหน้า * หน้าที่ต้องการเริ่มเราจะได้ index ที่จะ start ออกมา เช่น หน้า 1 ต่อ 20 ข้อมูล จะได้ 1*20 =20 
                // start index 20
                let get = db.db.collection('hero')
                    .orderBy('id', 'asc')
                return get.get()
                    .then((document) => {
                        let last = document.docs[indexOf]
                        db.db.collection('hero')
                            .orderBy('id', "asc")
                            .startAt(last)
                            .limit(limit)
                            .get()
                            .then((data) => {
                                let List = [];
                                data.forEach((v) => {
                                    List.push({
                                        id:+v.id,
                                        attack_type:v.data().attack_type,
                                        name:v.data().name,
                                        type:v.data().type
                                    })
                                })
                                this.desserts = List;
                                this.isLoad = false
                            })
                    }).catch((err) => {
                        console.log('err', err)
                    })
            },
            createCollections: function () {
                let batch = db.db.batch()
                this.desserts.forEach((v) => {
                    let doc = db.db.collection('hero').doc(v.id.toString())
                    batch.set(doc, {
                        id:v.id.toString(),
                        name:v.localized_name,
                        type:v.primary_attr,
                        attack_type:v.attack_type
                    })
                })
                batch.commit()
                    .then(() => console.log('done'))
                    .catch((err) => console.error(err))
                return null
            }
        },
        mounted:function mounted () {
            fetch('https://api.opendota.com/api/heroes')
                .then(response => response)
                .then(async result => {
                    let list = await result.json();
                    this.desserts = list;
                })
        },
    }
</script>

<style scoped>

</style>
ข้อมูล data table

เท่านี้ก็เป็นอันเสร็จแล้วครับไม่ยากเลย ซึ่งใน database ผมมีทั้งหมด 119 ข้อมูลเรียกมาหน้าละ 20 เท่านี้เราก็จะได้ pagination อย่างง่าย ๆ แล้วครับ หวังว่าจะเป็นประโยชน์สำหรับคนนำไปใช้งานนะครับ การทำ pagination สำคัญเหมือนกันนะครับเพราะถ้าหากเรามีข้อมูลหลัก 1000 หลัก 10000 แน่นอนอาจจะทำให้หน้าโหลดช้ามากแน่ ๆ สำหรับใครที่ยังอ่านแล้วงง หรือสงสัยผมต้องกราบขออภัยด้วย ผมมือใหม่จริง ๆ แนะนำให้ดู https://github.com/najaroen/pagination-firestore ของผมประกอบด้วย หรือจะโทรมาปรึกษาที่บริษัทก็ได้ รับรองว่าหายงง และมีตัวเทพรอให้คำปรึกษาทุกคนอยู่เพียบ!

📌สนใจติดต่อ📌
💻 twinsynergy.co.th
📱 063-789-9059

ตัวอย่างและข้อมูลเพิ่มเติม

https://docs.opendota.com/

https://firebase.google.com/docs/firestore/query-data/query-cursors

https://vuetifyjs.com/en/

https://vuejs.org/v2/guide/

ก่อนหน้า
ถัดไป