Vuejs rendering one item less - javascript

I am new in Vuejs and I get an unexpected output in my app. What my app does is to search on YouTube API for channels, and then adding those channels in a list.
Then I like to render the list of the subscribed channels, but always I get one item less. Alway the last inserted item is missing from the rendered list, while the item exists in my data.
This is the rendered output:
If you see the right column, under the text box, has only one item rendered, while in my Vue console I have two items under the channels_info key:
Then if I try to append yet another one item in the list, the console will display 3 items while the HTML render will display 2, and so on.
My code is the following:
var setup = function () {
app = new Vue(
{
el : '#sml_app',
data : {
channel_name : '',
errors : [],
channels_found : {},
no_channels_found : true,
next_page_token : '',
prev_page_token : '',
page_token : '',
subscriptions : [],
channels_info : {},
subscriptions_counter: 1
},
methods: {
fetch_channel_info : function ($channel_id) {
var self = this;
var base_api_url = 'https://www.googleapis.com/youtube/v3/channels';
var query_params = {
'part' : 'snippet,contentDetails',
'key' : 'My-ApiKey',
'maxResults': 1,
'id' : $channel_id
};
var get_params = '';
for (var key in query_params) {
if (get_params != '') {
get_params += '&';
}
get_params += key + '=' + encodeURIComponent(query_params[key]);
}
get_params = '?' + get_params;
axios.get(base_api_url + get_params).then(
function (data) {
data = 'data' in data ? data.data : {};
if (
typeof undefined !== typeof data.items &&
typeof undefined !== typeof data.items[0] &&
typeof undefined === typeof self.channels_info[$channel_id]
) {
var snippet = data.items[0].snippet;
var $key = self.subscriptions_counter + '-' + $channel_id;
self.channels_info[$key] = snippet;
self.subscriptions_counter += 1;
}
}
).catch(
function () {
self.errors.push(
'No channel found matching this channel id.');
}
);
},
// ...
append_to_subscriptions: function ($channel_id) {
if (-1 === this.subscriptions.indexOf($channel_id)) {
this.subscriptions.push($channel_id);
this.fetch_channel_info($channel_id);
// Todo-merianos: Create an AJAX request to set the options in
// database
}
}
}
}
);
};
While my HTML side is like that:
<div class="subscription" v-for="subscription in channels_info">
<span v-text="subscription.title"></span>
</div>
Do you see anything in wrong ? I don't understand why I have that strange output :/
Any sugestion please?

You're appending a new property to an object. I recommend reading this relevant section of the Vue.js documentation regarding object change detection caveats. Specifically, you can use Vue.set(object, key, value) to ensure that your new object key is detected and becomes reactive.
So, instead of self.channels_info[$key] = snippet; you might instead do something like Vue.set(this.channels_info, $key, snippet);.
Definitely read through some more of the documentation. I'm certain that you'll find a lot of value in the rest of the information on this topic.

Related

Retrieve and assign gameId to multiple players in Cloud Function transaction

I've been following this tutorial and trying to add a third player to the game. Here is the source code that works for two players. Below is my data structure:
"matchmaking" : {
"32idNK8OndcujN8CFNe3VBTxsXL2" : {
"gameId" : "placeholder"
},
"LnMnOtdLJYbsRuTeUpeHfaAhpX32" : {
"gameId" : "placeholder"
},
"plpISHWWtuNJvhSlFwfik6DOHR53" : {
"gameId" : "placeholder"
}
}
I have a cloud function that is called every time a user joins the matchmaking room. Depending on whichever two users are available, the function generates a unique game room for all three users. I am not well-versed in Javascript so I've been struggling on this for a while.
How can I retrieve and assign the gameId value to three players during a transaction?
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase);
var database = admin.database();
exports.matchmaker = functions.database.ref('matchmaking/{playerId}')
.onCreate((snap, context) => {
var gameId = generateGameId();
database.ref('matchmaking').once('value').then(snapshot => {
var secondPlayer = null;
var thirdPlayer = null;
// add two players into the queue
snapshot.forEach(child => { // check that its not the playerId who just joined
var playerId = child.key
var gameId = child.val().gameId
if (gameId === "placeholder" && playerId !== context.params.playerId) {
secondPlayer = child;
}
});
snapshot.forEach(child => { // check that its not the playerId who just joined
var playerId = child.key
var gameId = child.val().gameId
if (gameId === "placeholder" && playerId !== secondPlayer.key && playerId !== context.params.playerId) {
thirdPlayer = child;
}
});
if (secondPlayer === null || thirdPlayer === null) return null; // one room needs three players
database.ref("matchmaking").transaction(function (matchmaking) {
console.log(matchmaking)
// ================================================
// PROBLEM STARTS HERE
// ================================================
console.log("playerId gameId:",context.params.playerId.gameId) // returns undefined
// If any of the players gets into another game during the transaction, abort the operation
if (matchmaking === null ||
context.params.playerId.gameId !== "placeholder" ||
matchmaking[secondPlayer.key].gameId !== "placeholder" ||
matchmaking[thirdPlayer.key].gameId !== "placeholder") return matchmaking;
// matchmaking.forEach(player => { // check that its not the playerId that just joined
// var playerId = player.key
// var gameId = player.val().gameId
// if (gameId !== "placeholder") {
// player["gameId"] = gameId;
// }
// });
context.params.playerId.gameId = gameId; // assign game id to player
matchmaking[secondPlayer.key].gameId = gameId;
matchmaking[thirdPlayer.key].gameId = gameId;
return matchmaking;
}).then(result => {
if (result.snapshot.child(context.params.playerId).val() !== gameId) return;
// ENDS HERE
// ================================================
var game = {
gameInfo: {
gameId: gameId,
playersIds: [context.params.playerId, secondPlayer.key, thirdPlayer.key]
},
turn: context.params.playerId
}
database.ref("games/" + gameId).set(game).then(snapshot => {
console.log("Game created successfully!")
return null;
}).catch(error => {
console.log(error);
});
return null;
}).catch(error => {
console.log(error);
});
return null;
}).catch(error => {
console.log(error);
});
});
function generateGameId() {
var possibleChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
var gameId = "";
for (var j = 0; j < 20; j++) gameId += possibleChars.charAt(Math.floor(Math.random() * possibleChars.length));
return gameId;
}
I've been getting errors such as matchmaking.forEach is not a function or Cannot read property 'params' of undefined or Cannot read property 'val' of undefined
console.log(matchmaking) shows the following:
{ '32idNK8OndcujN8CFNe3VBTxsXL2': { gameId: 'placeholder' },
LnMnOtdLJYbsRuTeUpeHfaAhpX32: { gameId: 'placeholder' },
plpISHWWtuNJvhSlFwfik6DOHR53: { gameId: 'placeholder' } }
I would really appreciate any help.
Update:
Solution:
database.ref("matchmaking").transaction(function (matchmakingSnapshot) {
if (matchmakingSnapshot) {
matchmakingSnapshot[context.params.playerId].gameId = gameId; // assign game id to player
matchmakingSnapshot[secondPlayer.key].gameId = gameId;
matchmakingSnapshot[thirdPlayer.key].gameId = gameId;
return matchmakingSnapshot;
} else if ( // If any of the players gets into another game during the transaction, abort the operation
matchmakingSnapshot === null ||
context.params.playerId.gameId !== "placeholder" ||
matchmakingSnapshot[secondPlayer.key].gameId !== "placeholder" ||
matchmakingSnapshot[thirdPlayer.key].gameId !== "placeholder") {
return matchmakingSnapshot;
}
}).then(result => {
The problem was that when I was trying to update the gameId, the transaction returns a null data. I had to make sure matchmaingSnapshot is not null before accessing the context params playerId. I'm not sure why calling matchmakingSnapshot.val() returns function undefined, but yeah that's it!
You should not currently be getting the error `matchmaking.forEach is not a function`.
The ".forEach" function can work on an array or on a DataSnapshot (where it iterates over the children, providing a DataSnapshot of each in turn).
But if you have an Object, ".forEach" will give you the error message you describe. The code as it stands appears to be having "matchmaking" as a DataSnapshot, so it should be ok for ".forEach" at the moment.
Explanation for error: Cannot read property 'params' of undefined
I think you have accidentally inserted a variable name, instead of telling javascript to use the variable name's value
if (matchmaking === null ||
matchmaking.context.params.playerId.gameId !== "placeholder" ||
matchmaking.secondPlayer.key.gameId !== "placeholder" ||
matchmaking.thirdPlayer.key.gameId !== "placeholder"
) return matchmaking;
I think you don't mean matchmaking.secondPlayer.key.gameId. I think you mean matchmaking[secondPlayer].key.gameId, so that if secondPlayer is "ABC123", you would be referring to matchmaking.ABC123.key.gameId
Same for thirdPlayer.
Explanation for error: Cannot read property 'params' of undefined
Did you mean just context.params.playerId.gameId, not matchmaking.context.params.playerId.gameId? You might have added the "matchmaking" by mistake?
Exploring the error: TypeError: Cannot read property 'gameId' of undefined
If you get the above error with this:
matchmaking[context.params.playerId].key.gameId`
First, check that you need the step ".key". Looking at your object, it looks to me that ".gameId" comes straight after the matchmaking[playerId], with no ".key" needed. If you are sure you need the ".key", then I would debug as follows. Immediately before that statement, insert the following console logs.
console.log("playerId:",context.params.playerId)
console.log("matchmaking[context.params.playerId]:",
matchmaking[context.params.playerId])
Then check that the playerId is something sensible. If it isn't, console.log the entire context with
console.log("context:",JSON.stringify(context)) // This avoids getting something useless logged like `[Object object]`.
If the playerId is correct, you will then see if matchmaking has an entry for that player. This approach to debugging will eventually expose where the problem lies.
I think the final problem is the confusion between 'matchmaking' being a DataSnapshot versus a straightforward, writeable Object
This was hard for me to understand because matchmaking is currently a DataSnapshot, which is a somewhat confusing type of thing. It seems to get console.logged as though it is an object, but it isn't writeable.
I suggest we extract an actual object, as follows:
database.ref("matchmaking").transaction(function (matchmaking) {
let matchmakingObject = matchmaking.val();
// ... remainder of code as before, and then:
matchmakingObject[context.params.playerId.gameId] = gameId; // assign game id to player
matchmakingObject[secondPlayer.key].gameId = gameId;
matchmakingObject[thirdPlayer.key].gameId = gameId;
return matchmakingObject;
I hope this fixes things!
My way to avoid such problems is to ALWAYS call snapshots xxxxSnapshot. That way I never forget that while it may console.log as though it was an object, in reality it is some sort of mystery thing.
For example, if I was writing this code, I would call things as follows:
database.ref("matchmaking")
.transaction(function (matchmakingSnapshot) {
let matchmakingObject = matchmakingSnapshot.val();
// ... remainder of code as before, and then:
matchmakingObject[context.params.playerId.gameId] = gameId; // assign game id to player
matchmakingObject[secondPlayer.key].gameId = gameId;
matchmakingObject[thirdPlayer.key].gameId = gameId;
return matchmakingObject;
It's a bit ugly but less painful than trying to debug a name that has two powerfully different meanings that are easily conflated.

MongoDB Node JS - to delete an entry from an object inside a document object

I'm trying to create a command in Node JS using native mongodb driver, to remove the key value pair from an object which is inside the document object.
I have a mongoDB collection in the following format:
{
"name" : "PrakashPM"
"data" : {
"Jan-2017" : "2,3,1",
"Dec-2016" : "1,2,0",
"Nov-2016" : "9,9,9"
}
},
{
"name" : "Valavan"
"data" : {
"Jan-2017" : "1,1,1",
"Dec-2016" : "3,3,3",
"Nov-2016" : "9,9,9"
}
}
My target is to remove "Dec-2016" : "1,2,0" which is inside "name" :
"PrakashPM"
My Code:
var mongoName = 'PrakashPM';
var mongoDate = "'data'.'Dec-2016'";
// TRIALS
// var mongoDate = "data.'Dec-2016'";
// var mongoDate = "data.Dec-2016";
var mongoVal = "'1,2,0'";
// TRIALS
// var mongoVal = "1,2,0";
mycollection.update( { name: mongoName },
{ $unset: {mongoDate : mongoVal} }
);
NOTE: I'm doing the above operations inside a PUT request function.
I tried many possible ways (TRIALS) for the input values (mongoDate, mongoVal) but I'm not able to achieve the result below.
Also, is it possible to remove the key value pair entry, just by using the key? (i.e. in this case {$unset: {mongoDate}} or something like that)
EXPECTED RESULT:
{
"name" : "PrakashPM"
"data" : {
"Jan-2017" : "2,3,1",
"Nov-2016" : "9,9,9"
}
},
{
"name" : "Valavan"
"data" : {
"Jan-2017" : "1,1,1",
"Dec-2016" : "3,3,3",
"Nov-2016" : "9,9,9"
}
}
Assuming that req.body.timerDate has the month-date string value exactly as in MongoDB, this should work. (See documentation).
You have to use string as key. You cannot use variable names there.
// Assuming that req.body.timerDate
// has the month-date as stored in MongoDB (case-sensitive match)
var reqDate = "data." + req.body.timerDate;
var reqName = req.body.name;
var _unset = {};
_unset[reqDate] = "";
mycollection.update({ name: reqName }, { $unset: _unset })
Use the following example as a guide to updating your collection. You need to use the bracket notation to create your query and update documents i.e. you require an update operation which has the structure:
db.mycollection.update(
{ 'name': 'PrakashPM' },
{
'$unset': {
'data.Dec-2016': ''
}
}
)
So, using the variables to construct the objects to use in your operation
var mongoName = 'PrakashPM';
var timerDate = 'Dec-2016';
var query = {};
var update = {'$unset': {}};
query['name'] = mongoName;
update['$unset']['data.'+timerDate] = '';
db.mycollection.update(query, update)
You are trying to use variables as keys in a object.
mycollection.update( { timerName: mongoName },
{ $unset: {mongoDate : mongoVal} }
);
This does not work as you expect and is a general JavaScript concept (not mongodb problem).
You are sending a query to mongo to update a row where the key "timerName" equals the content of the variable "mongoName".
But the proper key is "name".
Try this:
mycollection.update( { name: mongoName },
{ $unset: {data : mongoVal} }
);

Firebase - Object.key returns undefined

I want to order the query based on multiple values. The problem is, that I can't select the objects key type because I get undefined when I do so.
var filterDataAccordingToDate = function(ref, startTime, endTime, travelType) {
ref.orderByChild('date')
.startAt(startTime).endAt(endTime)
.once('value', function(snapshot) {
var travel = snapshot.val();
console.log("TRAVEL OBJ: " + util.inspect(travel, false, null));
console.log("TRAVEL TYPE: " + travel.type);
if (travel.type == travelType) {
// DO STUFF
}
});
}
The first console.log() returns the correct object:
TRAVEL OBJ: {
"-KKiZKAVH0-QulKnThhF" : {
"date" : 1466439009,
"dest" : 1,
"fbKey" : "-KKiZKAVH0-QulKnThhF",
"type" : 1
}
}
The second one: TRAVEL TYPE: undefined
Any idea, where I made a mistake?
Use the .forEach() method on DataSnapshot
snapshot.forEach(function(snap) {
var key = snap.key;
if (key === travelType) {
// Do stuff
}
});
Since you will be retrieving multiple objects you need to iterate over them to get the values for each one.
for (var key in travel) {
console.log("TRAVEL OBJ: " + util.inspect(travel[key], false, null));
console.log("TRAVEL TYPE: " + travel[key].type);
}

pg-promise create custom filters for select query

The function that I am working on is getting an input object that has 7 different key-values and each of them could be undefined or not. I want to filter my database based on those key-values that exists in the input. For example if only input.userID exists I want to run this query:
db.query("...WHERE userID = ${userID}", {userID: input.userID});
else if both input.userID and input.startTime exist, I want to do this:
db.query("...WHERE userID = ${userID} and startTime= ${startTime}", {userID: input.userID, startTime: input.startTime});
What I have done is I created a params and keys object like this:
if(input.userID) {
keys.push('userID');
params.push(input.userID);
query = addFilterToTheQuery(query, 'userID', input.userID, filteredItemsCount);
filteredItemsCount = filteredItemsCount +1;
}
addFilterToTheQuery is a simple function I implemented myself. I basically make 7 if cases. Then I have to use those keys and param values to pass to the query function in a way that might need another huge switch case code.
Is this the only way to do this? Is there a better way to get rid of the redundancies in this code?
Custom Type Formatting is the most suitable here.
For example, if we want to convert an object with properties - filter values, we could do it like this:
var pgp = require('pg-promise')(/* initialization options */);
function FilterSet(filters) {
if (!filters || typeof filters !== 'object') {
throw new TypeError('Parameter \'filters\' must be an object.');
}
this._rawDBType = true; // property renamed later - see UPDATE below
this.formatDBType = function () {
var keys = Object.keys(filters);
var s = keys.map(function (k) {
return pgp.as.name(k) + ' = ${' + k + '}';
}).join(' AND ');
return pgp.as.format(s, filters);
};
}
TEST
var filter = new FilterSet({
first: 1,
second: 'two'
});
var test = pgp.as.format('WHERE $1', filter);
console.log(test);
This outputs:
WHERE "first" = 1 AND "second" = 'two'
If your filters are to be used as %value% with LIKE or ILIKE, then you would need to change your custom type accordingly.
See related questions:
42, 49, 89, 90,
UPDATE
Below is the same example re-written for the latest pg-promise (version 8.x or newer):
const pgp = require('pg-promise')(/* initialization options */);
class FilterSet {
constructor(filters) {
if (!filters || typeof filters !== 'object') {
throw new TypeError('Parameter \'filters\' must be an object.');
}
this.filters = filters;
this.rawType = true; // do not escape the result from toPostgres()
}
toPostgres(/*self*/) {
// self = this
const keys = Object.keys(this.filters);
const s = keys.map(k => pgp.as.name(k) + ' = ${' + k + '}').join(' AND ');
return pgp.as.format(s, this.filters);
}
}
See Custom Type Formatting.

How to make a Javascript class with methods and an array I can push to?

I want to define a Javascript object which manages messages. Within this object, I'll need an array that I can do a push() to:
MsgObjCollection.push(MsgObj)
Essentially I am trying to fill the MsgObjCollection object with a bunch of MsgObjs. Each MsgObj has the 3 variables messagesText, timeStamp, source (sent or received).
Also, I'll need to have some methods like:
MsgObjCollection.Sent.Count // Counts the number of sent messages
MsgObjCollection.Received.Count // Counts the number of received messages
MsgObjCollection.Count // Counts the total number of messages in the object
I'm not sure how to approach this in the simplest, cleanest manner.
NOTE: In case there's any confusion, these are not static methods. I'll be creating instances of these objects using the new operator. So I will need multiple instances.
Here's a tweak on bfavaretto's answer that should get you closer to what you want:
function MsgObjCollection() {
this.sent = [];
this.received = [];
this.total = [];
this.push = function(msg) {
// Assuming msg.source is either 'sent' or 'received',
// this will push to the appropriate array.
this[msg.source].push(msg);
// Always push to the 'total' array.
this.total.push(msg);
};
};
You would use this as follows:
var coll = new MsgObjCollection();
coll.push(/* whatever */);
var sent = coll.sent.length;
var received = coll.received.length;
If you wanted, you could wrap the sent and received arrays with objects that expose a Count function instead of a length property; but that strikes me as unnecessary.
You need push, count, you might want to have all arrays methods / accesssors / iterators.
What's more, you 'll get some speed boost if you let your collection be an array.
So best solution is to inherit from array, and to
have your objects be just real arrays : nothing should be
defined on the object, everything on its prototype.
-->> You'll get the speed and all features of arrays for free.
The function looks like :
function MsgObjCollection() { /* nothing */ };
var SO_pr = ( MsgObjCollection.prototype = [] ) ;
And then, to define count, sent and received on the prototype, use Object.defineProperty not to pollute enumeration, and also to have getters/setters :
Object.defineProperty(SO_pr, 'sent', { get : function() {
var cnt = 0;
this.forEach( function(x) { if (x.source == 'Sent') cnt++; });
return cnt; } } );
Object.defineProperty(SO_pr, 'received', { get : function() {
var cnt = 0;
this.forEach( function(x) { if (x.source == 'Received') cnt++; });
return cnt; } } );
Object.defineProperty(SO_pr, 'count', { get : function() { return this.length } ,
set : function (x) { this.length = x } });
Notice that since the Msg collection's prototype is a new array, you do not pollute array's prototype when changing MsgObjCollection's prototype.
The Sent and Received property you wish are more complex : they act as a view on the underlying object.
One thing you can do is to have them return a new array built out of the right items of the original array.
I prefer, though, to build a wrapper around the original array 1) to allow modification through this view and 2) to avoid garbage creation.
The fiddle is here : http://jsfiddle.net/cjQFj/1/
Object.defineProperty(SO_pr, 'Sent',
{ get : function() { return getWrapper('Sent', this); } } ) ;
Object.defineProperty(SO_pr, 'Received',
{ get : function() { return getWrapper('Received', this); } } ) ;
function getWrapper(wrappedProp, wrappedThis) {
var indx = 0, wp=null;
// try to find a cached wrapper
while (wp = getWrapper.wrappers[indx++] ) { 
if (wp.wthis === this && wp.wprop==wrappedProp) return wp.wrapper;
};
// no wrapper : now build, store, then return a new one
var newWrapper = {
get count() { return (wrappedProp=='Sent') ? wrappedThis.sent : wrappedThis.received },
unshift : function () { if (this.count == 0) return null;
var indx=0;
while (wrappedThis[indx].source != wrappedProp ) indx++;
var popped = wrappedThis[indx];
while (indx<wrappedThis.length-1) {wrappedThis[indx]=wrappedThis[indx+1]; indx++; }
wrappedThis.length--;
return popped;
}
};
getWrapper.wrappers.push({ wthis : wrappedThis, wprop : wrappedProp, wrapper : newWrapper });
return newWrapper;
};
getWrapper.wrappers = [];
Now just a little test :
var myColl = new MsgObjCollection();
myColl.push({ source : 'Sent', message : 'hello to Jhon' });
myColl.push({ source : 'Received' , message : 'hi from Kate' });
myColl.push({ source : 'Sent', message : 'hello to Kate' });
myColl.push({ source : 'Received' , message : 'Reply from Jhon' });
myColl.push({ source : 'Received' , message : 'Ho, i forgot from Jhon' });
console.log('total number of messages : ' + myColl.count);
console.log('sent : ' + myColl.sent + ' Sent.count ' + myColl.Sent.count);
console.log('received : ' + myColl.received + ' Received.count ' + myColl.Received.count);
console.log('removing oldest sent message ');
var myLastSent = myColl.Sent.unshift();
console.log ('oldest sent message content : ' + myLastSent.message);
console.log('total number of messages : ' + myColl.count);
console.log('sent : ' + myColl.sent + ' Sent.count ' + myColl.Sent.count);
console.log('received : ' + myColl.received + ' Received.count ' + myColl.Received.count);
Output : >>
total number of messages : 5
sent : 2 Sent.count 2
received : 3 Received.count 3
removing oldest sent message
oldest sent message content : hello to Jhon
total number of messages : 4
sent : 1 Sent.count 1
received : 3 Received.count 3
The annoying part is that those view properties are not arrays, but since you cannot overload [] operator, you cannot have a fully transparent view on the original array, (i.e. : myBox.Sent[i] that would be exactly the i-th sent message ) so at some point you might want to create arrays on the fly for some operations.
There are several ways to do that. One of the simplest, if you only need one instance, is an object literal:
var MsgObjCollection = {
_arr : [],
push : function(val) {
return this._arr.push(val);
},
Sent : {
Count : function() {
// ...
}
},
// etc.
};
If you need multiple instances, use a constructor, and add methods to its prototype property:
function MsgObjCollection() {
this._arr = [];
}
MsgObjCollection.prototype.push = function(val) {
return this._arr.push(val);
}
MsgObjCollection.prototype.get = function(index) {
return this._arr[index];
}
// and so on...
// USAGE:
var collection = new MsgObjCollection();
collection.push('foo');
console.log(collection.get(0));

Categories

Resources