Skip to content

Instantly share code, notes, and snippets.

@CaiJimmy
Last active May 4, 2020 09:16
Show Gist options
  • Save CaiJimmy/400fe6a76745483e58f28113538d4c5b to your computer and use it in GitHub Desktop.
Save CaiJimmy/400fe6a76745483e58f28113538d4c5b to your computer and use it in GitHub Desktop.
Firebase pagination
/* HTTP Function to initialize / reset question counter manually */
const express = require('express');
const cors = require('cors')({
origin: true
});
const app = express();
app.use(cors);
app.get('/', (req, res) => {
const topicID = req.query.topicID,
topicRef = db.collection('topics').doc(topicID);
if (!topicID) {
res.send("Topic ID is required");
return;
};
const allQuestions = db.collection('questions').where('topic', '==', topicID);
allQuestions.get().then(snap => {
const totalCount = snap.size
/*
Set counter
*/
topicRef.set({
'count': {
total: totalCount
}
}, {
merge: true
}).then(() => {
/*
Send response
*/
res.json({
total: totalCount
});
});
})
});
exports.reCount = functions.https.onRequest(app);
'use strict';
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();
const db = admin.firestore();
exports.questionChange = functions.firestore.document('questions/{questionID}').onWrite((change) => {
const oldQuestion = change.before.data(),
newQuestion = change.after.data();
let oldTopic,
oldTopicRef,
newTopic,
newTopicRef;
/* Load variables */
if (change.before.exists) {
oldTopic = oldQuestion.topic;
oldTopicRef = db.collection('topics').doc(oldTopic);
};
if (change.after.exists) {
newTopic = newQuestion.topic;
newTopicRef = db.collection('topics').doc(newTopic);
};
if (!change.before.exists) {
/* New document Created : plus one to count.total */
return newTopicRef.get().then(snap => {
return newTopicRef.set({
count: {
total: snap.data().count.total + 1
}
}, {
merge: true
});
});
};
if (!change.after.exists) {
/* Question deleted : subtract one from count.total */
return oldTopicRef.get().then(snap => {
return oldTopicRef.set({
count: {
total: snap.data().count.total - 1
}
}, {
merge: true
});
});
};
});
import * as firebase from "firebase/app";
import "firebase/firestore";
export default {
name: 'QuestionList',
data: () => ({
questions: [],
paging: {
question_per_page: 20,
end: false,
loading: false
},
ref: {
questions: null,
questionsNext: null
}
}),
created() {
/* Set common Firestore reference */
this.ref.questions = firebase.firestore().collection('questions').where('topic', '==', this.ref.topic.id).orderBy("date", 'desc');
/* Load first page */
const firstPage = this.ref.questions.limit(this.paging.question_per_page);
this.handleQuestions(firstPage);
},
methods: {
loadMore() {
if (this.paging.end) {
return;
};
this.paging.loading = true;
this.handleQuestions(this.ref.questionsNext).then((documentSnapshots) => {
this.paging.loading = false;
if (documentSnapshots.empty) {
/* If there is no more questions to load, set paging.end to true */
this.paging.end = true;
}
})
},
handleQuestions(ref) {
/*
Fetch questions of given reference
*/
return new Promise((resolve, reject) => {
ref.get().then((documentSnapshots) => {
/* If documentSnapshots is empty, then we have loaded all of pages */
if (documentSnapshots.empty) {
this.paging.end = true;
resolve(documentSnapshots);
};
documentSnapshots.forEach((doc) => {
let questionData = doc.data();
questionData.id = doc.id;
this.questions.push(questionData);
});
/* Build reference for next page */
const lastVisible = documentSnapshots.docs[documentSnapshots.size - 1];
if (!lastVisible) {
return;
};
this.ref.questionsNext = this.ref.questions
.startAfter(lastVisible)
.limit(this.paging.question_per_page);
resolve(documentSnapshots);
});
});
}
}
}
import * as firebase from "firebase/app";
import "firebase/firestore";
export default {
name: 'QuestionList',
props: {
topicData: Object
},
data: () => ({
questions: [],
paging: {
question_per_page: 20, /* Number of questions per page */
loading: false,
loaded: [] /* Loaded pages, to avoid querying data again */
},
ref: {
questions: null, /* Old questions Firestore Reference */
}
}),
created () {
this.init();
},
methods: {
init () {
const count = this.topicData.count;
/* Fill array with placeholders, to build pagination */
this.questions = new Array(count.total).fill({
loading: true
});
/* Set common Firestore reference */
this.ref.questions = firebase.firestore().collection('questions')
.where('topic', '==', this.topicData.id)
.orderBy("date", 'desc');
/*
Load questions of first page
*/
this.onPageChange(0);
},
handleQuestions (ref, index = 0) {
/*
Fetch questions of given reference
*/
return new Promise((resolve, reject) => {
console.log('Questions reference: ', ref);
ref.get().then((documentSnapshots) => {
let _questions = [],
_index = index || 0;
console.log('Questions document snapshots: ', documentSnapshots);
documentSnapshots.forEach((doc) => {
let questionData = doc.data();
questionData.id = doc.id;
_questions.push(questionData);
this.$set(this.questions, _index, {
loading: false,
...questionData
});
_index++;
});
resolve(documentSnapshots);
});
});
},
async onPageChange (toPage, fromPage) {
const currentPage = toPage,
per_page = this.paging.question_per_page;
let startAfter = null,
limit = per_page,
index = per_page * (currentPage - 1), /* Start after the question before that page */
startAfterAvailable;
if (this.paging.loaded.includes(currentPage)) {
return;
};
/* Display progress spinner */
this.paging.loading = true;
const questionBefore = this.questions[index - 1];
if (questionBefore && !questionBefore.loading) {
/*
If the question before that page is loaded,
we can build it's documentSnapshot to query the following page only
*/
const questionBeforeRef = firebase.firestore().collection('questions').doc(questionBefore.id),
questionBeforeSnapshot = await questionBeforeRef.get();
startAfter = this.ref.questions.startAfter(questionBeforeSnapshot).limit(per_page);
if (index < 0) {
index = 0;
};
startAfterAvailable = true;
}
else {
/*
But if it's not loaded, we'll have to request all questions between first page and current page
*/
limit = currentPage * per_page; /* Load all question from first page to current page */
if (limit <= 0) {
/*
Limit can not be less or equal to zero
This happend when currentPage == 0
*/
limit = per_page;
};
startAfter = this.ref.questions.limit(limit);
index = 0; /* Start loop from first page */
startAfterAvailable = false;
};
this.handleQuestions(startAfter, index).then((documentSnapshots) => {
this.paging.loading = false;
this.loading.questions = false;
if (startAfterAvailable) {
/* startAfterAvailable = true
=> Only requested current page
Add current page to paging.loaded to avoid requesting the data again
*/
this.paging.loaded.push(currentPage);
}
else {
/*
Request from 0 to current page
Add those pages to paging.loaded
*/
this.paging.loaded = Array.from(Array(currentPage).keys());
};
})
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment