I'm using VueJS 2 and Firestore for this project.
I have an infinite loading method, where I need the next item to load, when the user hits the bottom of the page on scroll.
Everything is in the same method called getStraps()
The idea is to have a first set of items to load, and when the user hits the bottom, then it should load the next batch of items.
Main problem: The first two items are loading as usual, and then another one is loading. But then when I scroll another time, the 4th item repeats to be the same as the 3rd as so on. The variable "lastVisible" doesn't seem to update, so it won't load the following items with "startAfter"
Video: http://recordit.co/TwqEb4SeWe
getStraps() {
var strapper = db.collection("straps");
var first = strapper.limit(2);
return first.get().then(documentSnapshots => {
var lastVisible =
documentSnapshots.docs[documentSnapshots.docs.length - 1];
console.log("first last visible", lastVisible);
const straps = [];
documentSnapshots.forEach(doc => {
const data = {
id: doc.id,
title: doc.data().title,
price: doc.data().price,
skin: doc.data().skin,
type: doc.data().type,
imgs: doc.data().imgs[0].url,
colors: doc.data().colors,
desc: doc.data().desc,
date: doc
.data()
.date.toString()
.slice(0, 15)
};
straps.push(data);
});
this.straps = straps;
var next = strapper.startAfter(lastVisible).limit(1);
window.onscroll = () => {
let bottomOfWindow =
document.documentElement.scrollTop + window.innerHeight ===
document.documentElement.offsetHeight;
if (bottomOfWindow) {
this.fetchingData = true;
console.log("fetch", this.fetchingData);
return next.get().then(documentSnapshots => {
var lastVisible =
documentSnapshots.docs[documentSnapshots.docs.length - 1];
console.log("last last", lastVisible);
if (documentSnapshots.empty) {
this.fetchingData = false;
this.noMoreStraps = true;
} else {
documentSnapshots.forEach(doc => {
const straps = this.straps;
const data = {
id: doc.id,
title: doc.data().title,
price: doc.data().price,
skin: doc.data().skin,
type: doc.data().type,
imgs: doc.data().imgs[0].url,
colors: doc.data().colors,
desc: doc.data().desc,
date: doc
.data()
.date.toString()
.slice(0, 15)
};
console.log("more data", data);
straps.push(data);
this.fetchingData = false;
});
this.straps = straps;
}
});
}
};
});
},
Related
I use vue-infinitegrid and I have realized in a browser that a backend API is called three times. Some code first (git):
<GridLayout
ref="ig"
:options="options"
:layoutOptions="layoutOptions"
#append="onAppend"
#layout-complete="onLayoutComplete"
#image-error="onImageError"
>
<div slot="loading">Loading...</div>
<div class="item" v-for="(item) in list" :key="item.key">
<ViewItem :item="item"/>
</div>
</GridLayout>
data() {
return {
start: 0,
loading: false,
list: [],
isEnded: false,
options: {
isOverflowScroll: false,
useFit: true,
useRecycle: true,
horizontal: false,
align: 'center',
transitionDuration: 0.2,
},
layoutOptions: {
margin: 15,
align: 'center',
},
pageSize: 3,
};
},
methods: {
async onAppend({ groupKey, startLoading }) {
this.$log.debug(`onAppend group key = ${groupKey}`);
const { list } = this;
if (this.isEnded) return;
const items = await this.loadItems();
startLoading();
this.list = list.concat(items);
},
async loadItems() {
const start = this.start || 0, size = parseFloat(this.pageSize), { tag } = this;
this.$log.debug(`loadItems start = ${start}, size = ${size}`);
let res = await this.$store.dispatch('GET_ITEM_STREAM', { start, size, tag });
if (res.length === 0) { // todo or smaller than requested
this.$log.debug('loadItems no data');
this.isEnded = true;
this.$refs.ig.endLoading();
return res;
}
if (this.exceptItem) {
res = res.filter(item => item._id !== this.exceptItem._id);
}
this.start = start + res.length;
this.$log.debug('loadItems finished');
return res;
},
onLayoutComplete({ isLayout, endLoading }) {
this.$log.debug(`onLayoutComplete isLayout = ${isLayout}`);
if (!isLayout) {
endLoading();
}
},
And some logs:
onAppend group key =
ItemList.vue:71 loadItems start = 0, size = 3
items.js:132 GET_ITEM_STREAM {"start":0,"size":3}
See more tips at https://vuejs.org/guide/deployment.html
ItemList.vue:83 loadItems finished
ItemList.vue:87 onLayoutComplete isLayout = false
ItemList.vue:62 onAppend group key =
ItemList.vue:71 loadItems start = 3, size = 3
items.js:132 GET_ITEM_STREAM {"start":3,"size":3}
ItemList.vue:62 onAppend group key =
ItemList.vue:71 loadItems start = 3, size = 3
items.js:132 GET_ITEM_STREAM {"start":3,"size":3}
2 ItemList.vue:83 loadItems finished
ItemList.vue:87 onLayoutComplete isLayout = false
ItemList.vue:62 onAppend group key =
ItemList.vue:71 loadItems start = 6, size = 3
items.js:132 GET_ITEM_STREAM {"start":6,"size":3}
ItemList.vue:62 onAppend group key =
ItemList.vue:71 loadItems start = 6, size = 3
items.js:132 GET_ITEM_STREAM {"start":6,"size":3}
2 ItemList.vue:83 loadItems finished
ItemList.vue:87 onLayoutComplete isLayout = false
I can see that start is incremented after onAppend is called. It looks like some concurrency issue, that the infinitegrid component does not wait until the REST call is finished and fires new event. Has anybody any experience with this component and knows how to handle this situation when I need to wait for a backend response?
Update
I have replaced async call with fixed data and it started to work correctly. So the trouble is with async.
// let items = await this.$store.dispatch('GET_ITEM_STREAM', { start, size, tag });
let items = [{ ...
Update:
Code sandbox with minimum reproducible scenerio: https://w56p2.csb.app/
The symptoms are different now, probably exhibiting the root cause - the event is emitted before the previous is processed.
https://github.com/naver/egjs-infinitegrid/issues/365
https://naver.github.io/egjs-infinitegrid/storybook/?path=/story/loading-bar-with-data-delay--grid-layout
In startLoading and endLoading, the loading bar appears and disappears, and some functions are temporarily disabled (moveTo, useFit).
The append and prepend work and must be prevented through the isProcessing method.
onAppend({ groupKey, startLoading, currentTarget }) {
if (currentTarget.isProcessing()) {
return;
}
}
Hi I have a row of players,
and from this row of players I get a player that matches two conditions
const condition = (5 / 100) * playerOne.mmr + playerOne.mmr
const condition2 = playerOne.mmr - ((5 / 100) * playerOne.mmr);
find another player who is between 5% less and 5% more than my player's mmr value
and then I did it
searching(playerOne) {
const condition = (5 / 100) * playerOne.mmr + playerOne.mmr
const condition2 = playerOne.mmr - ((5 / 100) * playerOne.mmr);
const player = playerOne;
const playerTwo = this.players.find((playerTwo) => playerTwo.mmr < condition && playerTwo.mmr > condition2 && playerTwo.id != playerOne.id);
while(!this.players.find((playerTwo) => playerTwo.mmr < condition && playerTwo.mmr > condition2 && playerTwo.id != playerOne.id)){
const playerTwo = this.players.find((playerTwo) => playerTwo.mmr < condition && playerTwo.mmr > condition2 && playerTwo.id != playerOne.id);
console.log(this.players);
}
const matchedPlayers = [
player,
playerTwo
]
// remove matched players from this.players
this.removePlayers(matchedPlayers);
// return new Match with matched players
return matchedPlayers;
}
}
Good if I can't find a compatible mmr I go into the while forever
And even on my server I adding a player to the queue after
inside my while my queue continues with the same players and the new player I added in the queue does not appear
queue.addPlayer(new Player(1,'spt',970));
queue.addPlayer(new Player(2,'test2',1000));
queue.addPlayer(new Player(3,'test3',1050));
queue.addPlayer(new Player(4,'test4',70));
const playerOne = queue.players.find((playerOne) => playerOne.mmr === 70);
const players = queue.searching(playerOne);
queue.addPlayer(new Player(5,'test6',75));
I added my 5 player after I called my search function
and inside my whilei I put to give console.log in my queue, but only 4 players appear and not my 5 player so it is in the infinite loop and I don't know where I went wrong or how I can fix or if my logic is very failed
[
Player { id: 1, name: 'spt', mmr: 970 },
Player { id: 2, name: 'test2', mmr: 1000 },
Player { id: 3, name: 'test3', mmr: 1050 },
Player { id: 4, name: 'test4', mmr: 70 }
]
You need to make the searching happen in an asynchronous manner. There are a bunch of ways to accomplish it. Personally I am a fan of promises. Below is showing a basic way of creating a way to wait until a player is added in the range. I also added the ability to cancel the search.
const players = [
{ id: 1, name: 'spt', mmr: 970 },
{ id: 2, name: 'test2', mmr: 1000 },
{ id: 3, name: 'test3', mmr: 1050 },
{ id: 4, name: 'test4', mmr: 70 }
]
const makeMatch = (id) => {
let timer
let promiseResolve
let promiseReject
const promise = new Promise((resolve, reject) => {
promiseResolve = resolve
promiseReject = reject
});
const playerDetails = players.find(p => p.id == id)
const { mmr } = playerDetails
const findMatchup = () => {
const secondPlayer = players.find(p =>
p.id !== id &&
Math.abs(p.mmr - mmr) <= 10)
if (secondPlayer) {
console.log("match!", id)
promiseResolve({ player1: playerDetails, player2: secondPlayer })
} else {
console.log("no matches", id)
timer = window.setTimeout(findMatchup, 1000)
}
}
findMatchup()
return {
kill: () => {
timer && window.clearTimeout(timer)
promiseReject('cancelled')
},
promise
}
}
// Make up a match with player 4
// There are no matches so it will loop for awhile
var matchMaking = makeMatch(4)
matchMaking.promise.then( ({ player1, player2 }) => {
console.log(player1, player2)
})
// Act like a new player signed on, push in the player
// We should be a match after this happens
window.setTimeout(() => {
console.log("Player is added");
players.push({ id: 5, name: 'test5', mmr: 75 })
}, 5500)
//Example showing killing off
// Make up a match with player 1
// There are no matches so it will loop for awhile
var matchMaking2 = makeMatch(1)
matchMaking2.promise.then( ({ player1, player2 }) => {
console.log(player1, player2)
}).catch((details) => {
console.log(details);
})
// Acting like user clicked cancel button to stop playing
window.setTimeout(() => {
console.log("Player has cancelled");
matchMaking2.kill()
}, 10000)
How to get the next item, previous item? For example, an active item 3-th, when I clicking to the next button, the function returns 5-th item.
My JSON data:
const DataJ = [
{ date: '2018-05-17T15:11:23.351Z'},
{ date: '2018-01-17T15:11:23.351Z'},
{ date: '2018-10-17T15:11:23.351Z'},
{ date: '2018-10-17T15:11:23.351Z'},
{ date: '2018-12-17T15:11:23.351Z'}
];
Prev and next functions.
const nextItem = () => {
// Next json item
console.log();
}
const prevItem = () => {
// Prev json item
console.log();
}
<div>
<button type="button" className="control-btn" onClick="prevItem"> Prev </button>
<button type="button" className="control-btn" onClick="nextItem"> Next </button>
</div>
I tried to use this method:
const prevItem = () => {
const { DataJ } = this.props;
const curDate = new Date(Date.now());
console.log(DataJ[0]);
return DataJ.reduce(
(a, b) =>
new Date(b.date) <= curDate
? b : a,
DataJ[0]
);
}
This is how you can iterate your items:
var myApp = window.MyApp || {};
const DataJ = [
{ date: '2018-05-17T15:11:23.351Z'},
{ date: '2018-01-17T15:11:23.351Z'},
{ date: '2018-10-17T15:11:23.351Z'},
{ date: '2018-10-17T15:11:23.351Z'},
{ date: '2018-12-17T15:11:23.351Z'}
];
myApp.currentIndex = 0;
myApp.currentItem = function () {
return DataJ[myApp.currentIndex];
};
myApp.prevItem = function () {
myApp.currentIndex--;
return myApp.currentItem();
};
myApp.nextItem = function () {
myApp.currentIndex++;
return myApp.currentItem();
};
console.log(myApp.currentItem());
console.log(myApp.nextItem());
console.log(myApp.nextItem());
console.log(myApp.prevItem());
As per your request, 3rd to 5th, you need to add a few checks before switching.
I would guess (since the question is not clear), that you want the NEXT DATE item, but the code i provided should allow you to get very close to that by yourself.
P.S.
You should also think about what happens when FIRST/LAST item is reached.
I am using mirage for creating fake data.
scenario/default.js
export default function(server) {
server.createList('product', 48);
server.loadFixtures();
}
Above I am creating 48 products and from controller I am calling
this.store.query('product', {
filter: {
limit: 10,
offset: 0
}
}).then((result) => {
console.log(result);
});
and in mirage/config.js
this.get('/products', function(db) {
let products = db.products;
return {
data: products.map(attrs => ({
type: 'product',
id: attrs.id,
attributes: attrs
}))
};
});
now my question is, how to load 10 products per page? I am sending in filter 10 as page size and offset means page number.
what changes should be done to config.js to load only limited products?
In your handler in mirage/config.js:
this.get('/products', function(db) {
let images = db.images;
return {
data: images.map(attrs => ({
type: 'product',
id: attrs.id,
attributes: attrs
}))
};
});
You are able to access the request object like so:
this.get('/products', function(db, request) {
let images = db.images;
//use request to limit images here
return {
data: images.map(attrs => ({
type: 'product',
id: attrs.id,
attributes: attrs
}))
};
});
Have a look at this twiddle for a full example.
Where the this twiddle has the following:
this.get('tasks',function(schema, request){
let qp = request.queryParams
let page = parseInt(qp.page)
let limit = parseInt(qp.limit)
let start = page * limit
let end = start + limit
let filtered = tasks.slice(start,end)
return {
data: filtered
}
})
You'll just adapt it for your use like this:
this.get('products',function(db, request){
let qp = request.queryParams
let offset = parseInt(qp.offset)
let limit = parseInt(qp.limit)
let start = offset * limit
let end = start + limit
let images = db.images.slice(start,end)
return {
data: images.map(attrs => ({
type: 'product',
id: attrs.id,
attributes: attrs
}))
}
})
An example with todos, you can adapt it to your own use case.
// Fetch all todos
this.get("/todos", (schema, request) => {
const {queryParams: { pageOffset, pageSize }} = request
const todos = schema.db.todos;
if (Number(pageSize)) {
const start = Number(pageSize) * Number(pageOffset)
const end = start + Number(pageSize)
const page = todos.slice(start, end)
return {
items: page,
nextPage: todos.length > end ? Number(pageOffset) + 1 : undefined,
}
}
return todos
});
I have an Angular application that collects values of items for an invoice, I want to make sure only unique items are being added to this collection but am having no luck.
I am pushing 3 pieces of information to this collection: id, price, and type. I want to make sure there is nothing in the collection currently matching those 3 points.
// My container
$scope.invoice = {
items: [{
}]
}
$scope.addPhoto = function() {
console.log('Withdrawing Photo: '+ $scope.item.id);
if ($scope.invoice.items.indexOf(item.id) != $scope.item.id)
{
$scope.invoice.items.push({
id: $scope.item.id,
price: $scope.item.price,
type: 'photo'
});
}
}
// Trying to avoid collections like this
invoice: {
items:
[ { } , {
id: 25
price: 0
type: photo
} , {
id: 25
price: 0
type: photo
} ]
}
.filter is pretty much what you need.
$scope.addPhoto = function() {
console.log('Withdrawing Photo: '+ $scope.item.id);
var matches = $scope.invoice.items.filter(function(datum) {
return datum.id === $scope.item.id &&
datum.price === $scope.item.price &&
datum.type === $scope.item.type;
});
if (!matches.length)
{
$scope.invoice.items.push({
id: $scope.item.id,
price: $scope.item.price,
type: 'photo'
});
}
}
Semi-contrived JSFiddle
This is the solution I came up with to solve my problem, hopefully it helps someone else.
$scope.addPhoto = function () {
console.log('Withdrawing Photo: ' + $scope.item.id);
var newItemId = $scope.item.id;
var newItemPrice = $scope.item.price;
var newItemType = 'photo';
var matches = true;
// Make sure user hasnt already added this item
angular.forEach($scope.invoice.items, function(item) {
if (newItemId === item.id && newItemPrice === item.price && newItemType === item.type) {
matches = false;
$scope.message = 'You have already selected to withdraw this item!';
}
});
// add item to collection
if (matches != false) {
$scope.invoice.items.push({
id: $scope.item.id,
price: $scope.item.price,
type: 'photo'
});
$scope.total += $scope.item.price;
$scope.message = 'Total Amount Selected';
}
};
YOu can simple pop opposite of push
array.splice(array.pop(item));