Vue firebase query: how to show the next results using infinite loading? - javascript

I am creating the food order system. I am building the order history page and try to achieve the infinite loading.
I want to show the next five results each time when I press the "next" button.
The each five results must be returned in descending order. The column name is created_at.
The issue now is, if I press the next button once, I can see the sixth to tenth results.
But if I press the button again, I see the sixth to tenth results.
And the same thing is happening again and again, each time I press the next button.
I think I need to use the for loop in my method. But I could not see how to solve this issue.
I am using firebase query in my methods.
My code:
<template>
<div>
<NavbarMobile />
<CartSmartphone class="mb-5" />
<b-container class="history-mobile">
<b-row class="mb-2 orders">
<span class="ml-2 orders">Your orders</span>
</b-row>
<div class="user-section d-flex " v-for="(item, index) in history" :key="index">
<div v-if="item.status == 'processing'" class="card-area">
<div class=" mb-3" v-for="(sample, index) in item.sample" :key="index">
<router-link :to="{name:'OrderSmartphone',params:{id: item.code}}">
<b-card :img-src="sample" img-alt="Card image" img-left class="mb-3">
<b-card-text class="text">
<!-- Some quick example text to build on the card and make up the bulk of the card's content. -->
<ul class="list-unstyled">
<b-row tag="li">
<span class="shop">{{ item.business }}</span>
</b-row>
<div cols="12" class="d-flex my-3">
<span class="date">{{ item.day }}/{{ item.month }}/{{ item.year}}</span>
<span class="status">{{ item.status }}</span>
</div>
<b-row class="my-3">
<span class="price">{{ item.sale }}</span>
</b-row>
</ul>
</b-card-text>
</b-card>
</router-link>
</div>
</div>
<div v-else class="card-area">
<div class="card-area mb-3" v-for="(sample, index) in item.sample" :key="index">
<b-card :img-src="sample" img-alt="Card image" img-left class="mb-3">
<b-card-text class="text">
<!-- Some quick example text to build on the card and make up the bulk of the card's content. -->
<ul class="list-unstyled">
<b-row tag="li">
<span class="shop">{{ item.business }}</span>
</b-row>
<div cols="12" class="d-flex my-3">
<span class="date">{{ item.day }}/{{ item.month }}/{{ item.year}}</span>
<span class="status">{{ item.status }}</span>
</div>
<b-row class="my-3">
<span class="price">{{ item.sale }}</span>
</b-row>
</ul>
</b-card-text>
</b-card>
</div>
</div>
<div class="arrow-area">
<div class="svg">
<svg xmlns="http://www.w3.org/2000/svg" width="5.126" height="9.313" viewBox="0 0 5.126 9.313">
<g id="download" transform="translate(-116.995 9.036) rotate(-90)">
<path id="パス_390" data-name="パス 390" d="M8.452,117.33,4.4,121.385.344,117.33a.19.19,0,0,0-.269.269l4.189,4.189a.19.19,0,0,0,.269,0L8.722,117.6a.19.19,0,0,0-.265-.274l0,0Z" transform="translate(-0.021 0)" fill="#fff" stroke="#fff" stroke-width="0.5"/>
<path id="パス_391" data-name="パス 391" d="M4.377,121.845a.19.19,0,0,1-.135-.056L.053,117.6a.19.19,0,0,1,.269-.269l4.055,4.054,4.054-4.055a.19.19,0,0,1,.274.265l0,0-4.189,4.189A.19.19,0,0,1,4.377,121.845Z" transform="translate(0 -0.001)" fill="#fff" stroke="#fff" stroke-width="0.5"/>
</g>
</svg>
</div>
</div>
</div>
</b-container>
<b-button #click.prevent="nextPage" class="next">next</b-button>
<FooterMobile />
</div>
</template>
<script>
import CartSmartphone from "#/mobile/CartSmartphone.vue";
import NavbarMobile from "#/components/NavbarMobile.vue";
import FooterMobile from "#/sections/FooterMobile.vue";
import fireApp from '#/plugins/firebase'
const firebase = require("firebase");
require("firebase/firestore");
const db = firebase.firestore();
export default {
name: 'UserSmartphone',
components: {
CartSmartphone,
NavbarMobile,
FooterMobile
},
data() {
return {
customer: this.$route.params.id,
history: [],
sample: ""
}
},
mounted() {
// const customerId = this.$route.params.id
// this.customer = customerId
const dbOrdersRef = db.collection('OrdersUser').doc(this.$route.params.id).collection('Orders').orderBy("created_at", "desc").limit(5)
dbOrdersRef.onSnapshot((querySnapshot) => {
querySnapshot.forEach((doc) => {
const historyData = doc.data()
this.history.push(historyData)
this.sample = doc.data().sample
})
})
},
methods: {
nextPage() {
fireApp.auth().onAuthStateChanged((user) => {
if (user) {
const userId = fireApp.auth().currentUser.uid;
const first = db.collection('OrdersUser').doc(userId).collection('Orders').orderBy("created_at", "desc").limit(5)
return first.onSnapshot((querySnapshot) => {
// Construct a new query starting at this document,
// get the next 5 results.
var lastVisible = querySnapshot.docs[querySnapshot.docs.length - 1];
console.log("last", lastVisible);
var next = db.collection('OrdersUser').doc(userId).collection('Orders').orderBy("created_at", "desc")
.startAfter(lastVisible)
.limit(5);
next.onSnapshot((nextQuery) => {
nextQuery.forEach((doc) => {
const nextData = doc.data()
console.log(nextData)
this.history.push(nextData)
})
})
});
}
})
},
}
}
</script>
The collection name is OrdersUser and I am pushing the data to the array named history.
And then, I'm showing the results in the template section using the for loop.

You are not setting the last visible document outside your nextPage function, so that function is always starting the query after the 5 initial documents, what you need to do is:
Add a variable that stores the last visible document while you are first populating your list, as below:
const dbOrdersRef = db.collection('OrdersUser').doc(this.$route.params.id)
.collection('Orders').orderBy("created_at", "desc").limit(5);
dbOrdersRef.onSnapshot((querySnapshot) => {
querySnapshot.forEach((doc) => {
const historyData = doc.data()
this.history.push(historyData)
this.sample = doc.data().sample
})
this.lastVisible = querySnapshot.docs[querySnapshot.docs.length-1];
})
Use the lastVisible variable set at the initial load to start you subsequent query, like the following:
nextPage() {
fireApp.auth().onAuthStateChanged((user) => {
if (user) {
const userId = fireApp.auth().currentUser.uid;
const next = db.collection('OrdersUser')
.doc(userId)
.collection('Orders')
.orderBy("created_at", "desc")
.startAfter(this.lastVisible)
.limit(5);
return next.onSnapshot((nextQuery) => {
nextQuery.forEach((doc) => {
const nextData = doc.data()
console.log(nextData)
this.history.push(nextData)
})
this.lastVisible = nextQuery.docs[nextQuery.docs.length-1];
})
};
})
}
Declare the lastVisible variable to your data(), as this will make it accessible both in nextPage() and in mounted() by using this., so like the following:
data() {
return {
customer: this.$route.params.id,
history: [],
sample: "",
lastVisible: ""
}
},

Related

Vue.js list rendering just when IDE updates

I created a page to show all events that I created, the problem is that when entering the page, no event is shown, only the add events button. And as soon as I hit enter in the IDE and add an empty line anywhere in the code, the list magically appears. Can anyone explain to me the reason for this?
I'm using Quasar and Firebase, I'll let my HTML and Javascript code here if anyone has any idea why this is happening
HTML
<template>
<q-page class="flex flex-center bg-primary column">
<q-btn
to="/adicionar-evento"
color="grey-9"
padding="16px 50px"
class="q-mt-lg"
>
<q-icon left size="2.5em" name="add_circle" />
Adicionar um novo evento
</q-btn>
<div v-for="evento in eventos" :key="evento.id" class="partidaDiv row">
<div class="column colunaPartida">
<h1 class="dataHorario">{{ evento.data.slice(0, 5) }}</h1>
<h1 class="dataHorario">{{ evento.horaInicio }}</h1>
</div>
<div class="column colunaPartida2">
<h1 class="dataHorario">{{ evento.quadra.label }}</h1>
<h1 class="textoLocal">{{ evento.quadra.value }}</h1>
<h1 class="textoLocal">Cachoeirinha, Rio Grande do Sul</h1>
</div>
<div class="column colunaPartida3">
<q-icon name="person" color="white" size="2rem" />
<h1 class="dataHorario">
{{ evento.numeroVagasPreenchidas }}/{{ evento.numeroVagasJogadores }}
</h1>
</div>
</div>
<q-item>
<h1></h1>
</q-item>
</q-page>
</template>
Javascript
<script>
import db from "src/boot/firebase";
import {
collection,
getDocs,
orderBy,
query,
addDoc,
onSnapshot,
deleteDoc,
doc,
getDoc,
updateDoc,
} from "firebase/firestore";
export default {
setup() {
return {
name: "IndexPage",
eventos: [],
};
},
async mounted() {
const ordersRef = collection(db, "eventos");
const q = query(ordersRef, orderBy("data"));
const unsubscribe = onSnapshot(q, (snapshot) => {
snapshot.docChanges().forEach((change) => {
let eventoChange = change.doc.data();
eventoChange.id = change.doc.id;
if (change.type === "added") {
this.eventos.unshift(eventoChange);
}
});
});
},
};
</script>
Figured out, I was using setup() instead of data().

Vue not reacting to a change as it should - only reacts after another action performed

I'm trying to programatically toggle a class on a vuetify card.
I am toggling it based on a value on the user that is in a for loop.
I have this code which tells the card to apply a class if member.toggle is true. A users background card should turn orange, but it doesn't.
When a user is clicked, it adds toggle:true to the user object.
After I click on the user, I check the vue console in chrome, and yep, the user has the value of true:
Yet the users background remains blank....
However, the confusing part - when I click something else (which appears irrelevant, a different toggle to show and hide the whole panel) the user then gets the class applied.
What am I missing here? This seems strange, even for me!
Here is the full code:
<template>
<div>
<v-sheet
max-width="100%"
>
<v-card-title class="text-h7 grey-bg pt-0 pb-0">
Line Managers
<v-spacer></v-spacer>
<v-switch
class="mt-2"
:input-value="showLM"
#click="showLM = !showLM"
></v-switch>
</v-card-title>
</v-sheet>
<v-slide-y-transition>
<v-sheet
class="mx-auto"
elevation="1"
max-width="100%"
v-show="showLM"
>
<div class="text-center pa-10" v-if="lineManagerLoading">
<v-progress-circular
:size="100"
:width="6"
color="primary"
indeterminate
></v-progress-circular>
</div>
<v-slide-group
class="pa-2"
show-arrows
center-active
v-if="!lineManagerLoading"
>
<v-slide-item
v-for="(member, i) in staff"
:key="member.id"
>
<v-card
class="text-center m-2"
:class="member.toggle ? 'primary' : 'grey lighten'"
width="100px"
#click="staffSelected(member)"
>
{{member.toggle}}
<v-avatar
color="#33304f"
size="94"
tile
class="profile-image m-1"
style="cursor:pointer;transition-duration:0.7s"
:style="member.toggle ? 'opacity:1.0' : 'opacity:0.5'"
>
<v-img
:src="'/img/profile-pictures/'+member.profile_photo"
>
</v-img>
</v-avatar>
<v-spacer class="m-0 p-0"></v-spacer>
<div class="pb-1">
<small class="text-truncate d-block pl-1 pr-1" :style="member.toggle ? 'color:white' : 'grey'">
{{member.first_name}}
</small>
<small class="text-truncate d-block pl-1 pr-1" :style="member.toggle ? 'color:white' : 'grey'">
{{member.last_name}}
</small>
</div>
</v-card>
</v-slide-item>
</v-slide-group>
</v-sheet>
</v-slide-y-transition>
</div>
</template>
<script>
import {mapGetters} from "vuex";
export default {
components: {
},
data () {
return {
staff: [],
lineManagerLoading: true,
showLM: true,
}
},
computed:{
...mapGetters({
workingLineManaged: 'performanceManagement/getWorkingLineManaged',
staffLoading: 'performanceManagement/getStaffLoading',
authUser: 'global/getAuthUser'
})
},
methods:{
async staffSelected(member){
member.toggle = true;
await axios
.get('/app/performance-management/getLineManagedByUser?id=' + member.id)
.then(response => {
this.$store.commit('performanceManagement/updateWorkingLineManaged', response.data);
})
.catch(error => {
console.log(error)
})
.finally( () => this.$store.commit('performanceManagement/updateStaffLoading', false))
},
async populateLineManagers(){
await axios
.get('/app/performance-management/getLineManagers')
.then(response => {
this.staff = response.data
})
.catch(error => {
console.log(error)
})
.finally(() => this.lineManagerLoading = false)
},
},
watch: {
},
mounted(){
this.populateLineManagers();
},
created(){
}
}
</script>
<style scoped>
::v-deep .v-slide-group__content{
justify-content:center;
}
</style>
It turns out I needed to use Vue.set(member, 'toggle', true); Vue wasn't being notified about the change, but this did the trick!

Vuejs emit not working form child to parent

I'm working on this app and the idea is to show details of the cars in a sidebar on click. There are several issues like the sidebar is showing four times and I resolve it somehow but I don't know why is it showing four times. now I don't getting any response on emit call help me out please, I try $parent.$emit, $root.$emit but not seems working!!!
<template>
<div class="home">
<!-- warehouse details -->
<div
v-for="(detail, detailindex) in details"
:key="detailindex"
class="container mt-5 mb-5"
>
<h1>
{{ detail.name }}
<span class="location">{{ detail.cars.location }}</span>
</h1>
<!-- vehicle details -->
<SingleGarage :detail="detail"> </SingleGarage>
</div>
<b-sidebar
id="my-sidebar"
title="Sidebar with backdrop"
backdrop-variant="dark"
ref="mySidebar"
backdrop
shadow
#emitData="testingEmit()"
>
<div class="px-3 py-2">
<h1>{{currentCar}}</h1>
</div>
</b-sidebar>
</div>
</template>
<script>
// # is an alias to /src
import axios from "axios";
import SingleGarage from "../components/SingleGarage";
export default {
components: { SingleGarage },
name: "Home",
data: () => ({
details: String,
currentCar: 'String',
}),
methods:{
testingEmit(data){
this.currentCar = data
console.log('data from emit',data)
}
},
mounted() {
axios
.get("https://api.jsonbin.io/b/5ebe673947a2266b1478d892")
.then((response) => {
var results;
response.data.forEach((element) => {
element.cars.vehicles.sort((a, b) => {
a = new Date(a.date_added);
b = new Date(b.date_added);
results = a > b ? -1 : a < b ? 1 : 0;
return results * -1;
});
});
this.details = response.data;
});
},
};
</script>
<template>
<div class="vGrid mt-4">
<div
class="gridItem border vehicle singleCar"
v-for="(vehicle, vehicleIndex) in detail.cars.vehicles"
:class="'griditem' + vehicleIndex"
:key="vehicle._id"
>
<SingleCar
:vehicle="vehicle"
#click.native="testingTef(vehicleIndex)"
></SingleCar>
</div>
</div>
</template>
<script>
import SingleCar from "#/components/SingleCar";
export default {
name: "SingleGarage",
components: { SingleCar },
props: ["detail"],
data: () => ({
dummyImg: require("#/assets/img/dummycar.png"),
currentCar : 1
}),
methods: {
testingTef(vehicleIndex) {
this.$parent.$emit('emitData',this.detail.cars.vehicles[vehicleIndex].make)
this.$root.$emit('bv::toggle::collapse', 'my-sidebar')
console.log(this.detail.cars.vehicles[vehicleIndex].make)
console.log(this.detail.cars.vehicles[vehicleIndex].date_added)
this.currentCar = this.detail.cars.vehicles[vehicleIndex].make;
},
},
};
</script>
<template>
<div class="singleCar">
<!-- conditionally show image -->
<img
class="carImg"
:src="vehicle.img"
v-if="vehicle.img"
alt="No Preview"
/>
<img class="carImg" :src="dummyImg" v-else alt="No Preview" />
<div class="p-3">
<h3 class="make">{{ vehicle.make }}</h3>
<div class="modelDetails">
<div class="model d-flex ">
<p class="bold">Model:</p>
<p class="price ml-auto ">{{ vehicle.model }}</p>
</div>
<div class="price d-flex ">
<p class="bold">Price:</p>
<p class="price ml-auto ">€{{ vehicle.price }}</p>
</div>
</div>
<p class="dateAdded ml-auto ">{{ vehicle.date_added }}</p>
</div>
</div>
</template>
<script>
export default {
name: "SingleCar",
props: ["vehicle"],
data: () => ({
dummyImg: require("#/assets/img/dummycar.png"),
}),
methods:{
working(){
console.log('working');
console.log(this.vehicle.make)
}
}
};
</script>
Thanks for your help.
So a few things you can try to fix this
in your Home.vue you can change
#emitData="testingEmit()"
to
#emitData="testingEmit"
// or
#emitData="testingEmit($event)"
You are telling to the function testingEmit that is not params to parse. So you need to take out the () and Vue will parse everything that comes from the $event or you cant say put the $event as a param in your testingEmit (second option).
For your SingleGarage.vue you can take the $parent.$emit and replace it with
this.$emit('emitData',this.detail.cars.vehicles[vehicleIndex].make)

Vue: Retrieve firestore data to show user's business name v-for loop

In my project, I am working on the result page of the e-commerce.
I want to retrieve the data from two collections.
One is called "Product", and the other one is called "ProUser".
Now, I could retrieve and show the product info from "Product" collection.
I used v-for loop to show the each item's information.
I also want to retrieve shop name in "ProUser" collection of each user at the same time.
I set the shop name in "business" field.
Product collection is corresponding to uid.
In my dev console, I could confirm I could retrieve all the data from "ProUser" collection.
Following is my vue.js code.
Now in my browser, I cannot show the shop name of each user.
<template>
<div class="main">
<section class="cards">
<div class="card-list" v-for="(item, index) in getMenuItems" :key="index">
<div class="card" >
<div class="product-list" v-if="item.quantity > 0">
<div class="quantity">
<span>{{ item.quantity }}</span>
</div>
<div class="card__image-container" v-for="(sample, index) in item.sample" :key="index">
<router-link to="/product">
<img
:src="sample"
alt="Detailed image description would go here."
class="image"
/>
</router-link>
<!-- Retrieve business name from the ProUser collection. -->
<div class="card__content" v-for="(item, index) in ProUsers" :key="index">
<div class="card__info">
<span class="text--medium">{{ item.business }}</span>
<span class="card__distance text--medium"> product.quantity -- orders </span>
</div>
</div>
</div>
<div class="icons">
<div class="time">
<span>{{ item.limitObject }}</span>
</div>
<div class="fav">
<span>Heart</span>
</div>
<div class="price">
<span>{{ item.sale }}</span>
</div>
</div>
</div>
</div>
</div>
</section>
</div>
</div>
</div>
</template>
<script>
import axios from 'axios';
import fireApp from '#/plugins/firebase'
const firebase = require("firebase");
require("firebase/firestore");
const db = firebase.firestore();
import googleMapMixin from '#/mixins/googleMapMixin'
import { mapGetters } from 'vuex'
export default {
name: "UserLocation",
data() {
return {
address: "",
error: "",
spinner: false,
ProUsers: []
}
},
mixins: [googleMapMixin],
created() {
//vuexfire
const dbMenuRef = db.collection('Product')
this.$store.dispatch('setMenuRef', dbMenuRef)
//firestore
db.collection("ProUser")
.get()
.then((querySnapshot)=> {
querySnapshot.forEach((doc) => {
console.log(doc.id, " => ", doc.data());
this.ProUsers.push(doc.data())
});
})
.catch((error) => {
console.log("Error getting documents: ", error);
});
},
computed: {
...mapGetters([
'getMenuItems'
])
},
methods: {
}
}
</script>
The problem now is, I could see all the shop name in <div class="card__info> tag.
What I want to achieve is to show one shop name of each user in here.
<!-- Retrieve business name from the ProUser collection. -->
<div class="card__content" v-for="(item, index) in ProUsers" :key="index">
<div class="card__info">
<span class="text--medium">{{ item.business }}</span>
<span class="card__distance text--medium"> product.quantity -- orders </span>
</div>
</div>
</div>
Not showing all the shop name.
My question is In other words, I want to fetch the product and the product owner.
Now in one tag, I could show one product with all the product owners in "ProUser" collection.
Hope someone helps me out.
If you need more information, please feel free to ask.
Here's how you can fetch data user from prouser collection and after then fetch the product related to the prouser and create a new object from data from both collection data.
let data = [];
let db = firebase.firestore();
db.collection("ProUser")
.get()
.then((userSnapshot) => {
db.collection("Product")
.get()
.then((productSnapshot) => {
userSnapshot.forEach((user) => {
productSnapshot.forEach((product) => {
if (user.id === product.id) {
data.push({
uid: user.id,
pId: product.id,
...user.data(),
...product.data(),
});
}
});
});
})
.catch((e) => {
console.log(e);
});
})
.catch((e) => {
console.log(e);
});

Change attribute of this button inside v-for loop in Vue.js

I'm using a Vue.js v-for loop to output a table of information, each with their own action buttons like so using Element UI library.
<template>
<div class="card">
<div class="card-header">
<tool-bar></tool-bar>
</div>
<div class="card-body">
<el-table :data="orders" v-loading="loading" current-row-key="index" empty-text="No products found">
<el-table-column type="expand">
<template slot-scope="props">
<el-tabs>
<el-tab-pane label="Order Items">
<ul>
<li v-for="(product, index) in orders[props.$index].products" :key="index">{{ product.name }}</li>
</ul>
</el-tab-pane>
<el-tab-pane label="Customer Details">Customer Details
<!-- <p v-for="(customer, index) in orders[props.$index].order.billing_address" :key="index">{{ customer.index }}</p> -->
</el-tab-pane>
</el-tabs>
</template>
</el-table-column>
<el-table-column label="Order ID" prop="order.id"></el-table-column>
<el-table-column label="Customer" prop="order.billing_address.first_name"></el-table-column>
<el-table-column label="Due Time" prop="order.due_time"></el-table-column>
<el-table-column
align="right">
<template slot="header" slot-scope="scope">
<el-input
v-model="search"
placeholder="Type to search"/>
</template>
<template slot-scope="scope">
<el-button size="mini" type="success" :disabled="orders[scope.$index].checked_in" :loading="false" :ref="'btn-' + scope.$index" #click="checkInOrder(scope.$index, scope.row)">{{ (orders[scope.$index].checked_in) ? 'Checked in' : 'Check in' }}</el-button>
</template>
</el-table-column>
</el-table>
</div>
</div>
</template>
<script>
import Toolbar from '../components/Toolbar.vue';
export default {
components: {
'tool-bar': Toolbar
},
mounted() {
this.fetchOrders();
},
data () {
return {
search: '',
}
},
computed: {
loading() {
return this.$store.getters.loading;
},
orders() {
return this.$store.getters.orders;
}
},
methods: {
fetchOrders() {
this.$store.dispatch('fetchOrders')
.then(res => {
})
.catch(err => {
})
},
checkInOrder(index, row) {
this.$refs['btn-' + index].loading = true;
axios.post('/order/update', {
id: row.order.id,
status: 'bakery',
products: row.products
})
.then(res => {
this.$refs['btn-' + index].loading = false;
})
.catch(err => {
this.$refs['btn-' + index].loading = false;
})
}
}
}
</script>
When I click one of the buttons, I want to be able to set the :loading attribute of the clicked button to true as well as change the button label to Loading... until a given Ajax request is completed.
I used the :ref attribute on the button and, when the button is clicked, I alter the attribute as follows:
checkInOrder(index, row) {
this.$refs['btn-' + index].loading = true;
}
This seems to work fine, but the console is throwing a warning, so I want to find out the way to achieve this.
The warning I get is this:
I believe it's prompting you to link :loading to a property, which you can set, rather than mutating the element prop directly. Thus, when you call checkInOrder() you could just update the boolean property that :loading is linked to.
I believe this question is relevant and will help you fix this issue.

Categories