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

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

Tools

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

Step

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 ของผมประกอบด้วย

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

https://docs.opendota.com/

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

https://vuetifyjs.com/en/

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

ทิ้งคำตอบไว้

Please enter your comment!
Please enter your name here

3 × 2 =