I'm working on a presence-like system in firebase with following layout:
firebase {
user1 {
isOnline: true
}
user 2 {
isOnline: true
}
user3 {
isOnline: false
}
}
The isOnline booleans are what I am going to use later to output the names of the users that are online to the console
So for example, in the case above it would say:
user1 is online.
user2 is online.
Here is my code:
var gameRef = new Firebase("https://xxx.firebaseio.com/");
var userOnline = new Firebase('https://xxx/.info/connected');
userOnline.on('value', function (snapshot) {
if (snapshot.val()) {
gameRef.child(user).update({
isOnline : true
});
}
else {
gameRef.child(user).update({
isOnline : false
});
}
});
// for each user that is online, output to the console
gameRef.forEach(function (snapshot) {
var obj = snapshot.val();
if(obj.isOnline == true) {
console.log(obj.name + " is online.");
}
});
There seems to be a problem with my forEach, how can I fix this?
Thanks.
You cannot forEach over a ref, but only over a snapshot.
// for each user that is online, output to the console
gameRef.on('value', function(function(gamesSnapshot) {
gamesSnapshot.forEach(function (snapshot) {
var obj = snapshot.val();
if(obj.isOnline == true) {
console.log(obj.name + " is online.");
}
}
});
This code has two snapshot variables:
gameSnapshot is the data in the parent node
snapshot is the data of a specific player
Alternative
The approach above will download all players, even though you are only looking to deal with players that are online. It is more efficient in this case, to query Firebase so that it only returns players that are online.
// for each user that is online, output to the console
var onlinePlayers = gameRef.orderByChild('isOnline').equalTo(true);
onlinePlayers.on('child_added', function(function(snapshot) {
var obj = snapshot.val();
if(obj.isOnline == true) {
console.log(obj.name + " is online.");
}
});
The code now listens for the child_added event, since Firebase spoon-feeds us the players one at a time. You will probably also have to handle child_changed and child_removed, once you map the players to HTML elements.
Even though this will result in a bit more code, I would normally recommend using querying and the child_* events, since they limit the data that Firebase sends you both initially and when e.g. a player goes offline.
Related
Is there a way (maybe using rules) to duplicate data on add/push to firebase?
What I want to archive is when I do an add to a firebase array I want to duplicate the data to another array.
So this is my firebase structure:
my-firebase: {
items: [ ... ],
queue: [ ... ]
}
And this is how I have my services defined:
.factory('Service1',['$firebaseArray', function($firebaseArray) {
var items = new Firebase('my-firebase.firebaseio.com/items');
return $firebaseArray(items);
}])
.factory('Service2',['$firebaseArray', function($firebaseArray) {
var queue = new Firebase('my-firebase.firebaseio.com/queue');
return $firebaseArray(queue);
}])
And here is how I use them:
.controller('controller', function($scope, Service1, Service2) {
$scope.save = function() {
Service1.$add({name: "test1"});
Service2.$add({name: "test1"});
}
};
And want I to have a single call not a duplicate call/code but having the result in both arrays (items and queue).
Thanks so much!
Always remember that AngularFire is a relatively thin wrapper around Firebase's JavaScript SDK that helps in binding data into your AngularJS views. If you're not trying to bind and something is not immediately obvious, you'll often find more/better information in the documentation of Firebase's JavaScript SDK.
The API documentation for $firebaseArray.$add() is helpful for this. From there:
var list = $firebaseArray(ref);
list.$add({ foo: "bar" }).then(function(ref) {
var id = ref.key();
console.log("added record with id " + id);
list.$indexFor(id); // returns location in the array
});
So $add() returns a promise that is fulfilled when the item has been added to Firebase. With that knowledge you can add a same-named child to the other list:
var queue = new Firebase('my-firebase.firebaseio.com/queue');
$scope.save = function() {
Service1.$add({name: "test1"}).then(function(ref) {
queue.child(ref.key().set({name: "test1"});
});
}
This last snippet uses a regular Firebase reference. Since AngularFire builds on top of the Firebase JavaScript SDK, they work perfectly together. In fact: unless you're binding these $firebaseArrays to the $scope, you're better off not using AngularFire for them:
var items = new Firebase('my-firebase.firebaseio.com/items');
var queue = new Firebase('my-firebase.firebaseio.com/queue');
$scope.save = function() {
var ref = queue.push();
ref.set({name: "test1"})
queue.child(ref.key().set({name: "test1"});
}
To my eyes this is much easier to read, because we're skipping a layer that wasn't being used. Even if somewhere else in your code, you're binding a $firebaseArray() or $firebaseObject() to the same data, they'll update in real-time there too.
Frank's answer is authoritative. One additional thought here is that AngularFire is extremely extensible.
If you want data pushed to two paths, you could simply override the $add method and apply the update to the second path at the same time:
app.factory('DoubleTap', function($firebaseArray, $q) {
var theOtherPath = new Firebase(...);
return $firebaseArray.$extend({
$add: function(recordOrItem) {
var self = this;
return $firebaseArray.prototype.$add.apply(this, arguments).then(function(ref) {
var rec = self.$getRecord(ref.key());
var otherData = ...do something with record here...;
return $q(function(resolve, reject) {
theOtherPath.push(rec.$id).set(otherData);
});
});
}
});
});
I'm completely new to javascript testing and I am trying to get a grasp on how to approach testing methods that touch the database
For example, I have this method that returns true if there are any documents in the db matching the query
Payments = new Mongo.Collection('payments');
_.extend(Payments, {
hasAnyPayments: function(userId) {
var payments = Payments.find({ userId: userId });
return payments.count() > 0;
}
});
So far I have only written the structure that I think is correct, but I am pretty lost
describe('Payments', function() {
describe('#hasAnyPayments', function() {
it('should return true when user has any payments', function() {
});
});
});
Are such tests even supposed to touch the database? Any advice is much appreciated
Unless you are manually inputting data into Mongo manually (or outside of Meteor) then you don't need to test the database.
What you should be testing, are the execution paths in your code.
So for the case above, hasAnyPayments is a unit that finds all user payments and returns true if there are more than 0. So your test would look something like this:
describe('Payments', function() {
describe('#hasAnyPayments', function() {
it('should return true when user has any payments', function() {
// SETUP
Payments.find = function() { return 1; } // stub to return a positive value
// EXECUTE
var actualValue = Payments.hasAnyPayments(); // you don't really care about the suer
// VERIFY
expect(actualValue).toBe(true);
});
});
});
I just got done with the rough draft of my app, and thought it was time to remove autopublish and insecure mode. I started transfering all the stray update and insert methods I had been calling on the client to methods. But now I'm having trouble returning a username from an ID.
My function before: (that worked, until I removed autopublish)
challenger: function() {
var postId = Session.get('activePost');
var post = Posts.findOne(postId);
if (post.challenger !== null) {
var challenger = Meteor.users.findOne(post.challenger);
return challenger.username;
}
return false;
}
Now what I'm trying:
Template.lobby.helpers({
challenger: function() {
var postId = Session.get('activePost');
var post = Posts.findOne(postId);
if (post.challenger !== null) {
var userId = post.challenger;
Meteor.call('getUsername', userId, function (err, result) {
if (err) {
console.log(err);
}
return result;
});
}
return false;
},
Using:
Meteor.methods({
getUsername: function(userId) {
var user = Meteor.users.findOne({_id: userId});
var username = user.username;
return username;
},
...
})
I have tried blocking the code, returning values only once they're defined, and console.logging in the call-callback (which returned the correct username to console, but the view remained unchanged)
Hoping someone can find the obvious mistake I'm making, because I've tried for 3 hours now and I can't figure out why the value would be returned in console but not returned to the template.
Helpers need to run synchronously and should not have any side effects. Instead of calling a method to retrieve the user, you should ensure the user(s) you need for that route/template are published. For example your router could wait on subscriptions for both the active post and the post's challenger. Once the client has the necessary documents, you can revert to your original code.
I am working on a real time application and i am using firebase with pure html and javascript (not angularJS).
I am having a problem where i saved user's data to firebase with the given code by firebase :
var isNewUser = true;
ref.onAuth(function(authData) {
if (authData && isNewUser) {
authData['status'] = 'active';
authData['role'] = 'member';
ref.child("users").child(authData.uid).set(authData);
}
});
This will add the authData to the /users/ node. As you can see that i also appended some custom fields to the authData, status and role.
Now i am using this code to get the user's data from firebase and display them.
ref4.on("value", function(snapshot) {
var snapshotData = snapshot.val();
console.log('username: '+snapshotData.status);
});
If i use on('value'), the status get printed out on the console but if i do it this way,
ref4.on("child_added", function(snapshot) {
var snapshotData = snapshot.val();
console.log('status: '+snapshotData.status);
});
It is showing undefined for the status. May i know what's wrong and how to fix this problem. Thank you.
Since value is returning the path provided by ref4, and child_added is returning each child of that path, it's unlikely both are going to have a key status.
Consider this data structure:
{
"users": {
"brucelee": {
"status": "awesome"
},
"chucknorris": {
"status": "awesomerest"
}
}
}
If I now query for this according to your incomplete example:
var ref = new Firebase('https://<instance>firebaseio.com/users/brucelee');
ref.on('value', function(snap) {
// requests the brucelee record
console.log(snap.name(), ':', snap.val().status); // "brucelee: awesome"
});
ref.on('child_added', function(snap) {
// iterates children of the brucelee path (i.e. status)
console.log(snap.name(), ':', snap.val().status); // THROWS AN ERROR, because status is a string
});
So to do this on child_added with a data structure like this (and presumably somewhat like yours), it would look as follows:
ref.on('child_added', function(snap) {
// iterates children of the brucelee path (i.e. status)
console.log(snap.name(), ':', snap.val()); // "status: awesome"
});
Hi I'm new to firebase and was trying out the presence example on firebase everything is working normal. My issue is how do I display the username of others ONLY because everything I cant seem to find the solution for this because
I tried googling for an answer but none of the results are what I'm looking for.
I'm new to Firebase and non-mysql database so I dont know how to do a WHERE Statement on firebase
here is my code:
<body>
<div id="presenceDiv" class="l-demo-container example-base">
</div>
<script>
var name = "<?php echo $uname;?>";
var currentStatus = "★ online";
// Get a reference to the presence data in Firebase.
var userListRef = new Firebase("https://<URL>.firebaseio.com/");
// Generate a reference to a new location for my user with push.
var myUserRef = userListRef.push();
// Get a reference to my own presence status.
var connectedRef = new Firebase("https://<URL>.firebaseio.com//.info/connected");
connectedRef.on("value", function(isOnline) {
if (isOnline.val()) {
// If we lose our internet connection, we want ourselves removed from the list.
myUserRef.onDisconnect().remove();
// Set our initial online status.
setUserStatus("★ online");
}
else {
// We need to catch anytime we are marked as offline and then set the correct status. We
// could be marked as offline 1) on page load or 2) when we lose our internet connection
// temporarily.
setUserStatus(currentStatus);
}
});
// A helper function to let us set our own state.
function setUserStatus(status) {
// Set our status in the list of online users.
currentStatus = status;
myUserRef.set({ name: name, status: status });
}
function getMessageId(snapshot) {
return snapshot.name().replace(/[^a-z0-9\-\_]/gi,'');
}
// Update our GUI to show someone"s online status.
userListRef.on("child_added", function(snapshot) {
var user = snapshot.val();
$("<div/>")
.attr("id", getMessageId(snapshot))
.text(user.name + " is currently " + user.status)
.appendTo("#presenceDiv");
});
// Update our GUI to remove the status of a user who has left.
userListRef.on("child_removed", function(snapshot) {
$("#presenceDiv").children("#" + getMessageId(snapshot))
.remove();
});
// Update our GUI to change a user"s status.
userListRef.on("child_changed", function(snapshot) {
var user = snapshot.val();
$("#presenceDiv").children("#" + getMessageId(snapshot))
.text(user.name + " is currently " + user.status);
});
// Use idle/away/back events created by idle.js to update our status information.
document.onIdle = function () {
setUserStatus("☆ idle");
}
document.onAway = function () {
setUserStatus("☄ away");
}
document.onBack = function (isIdle, isAway) {
setUserStatus("★ online");
}
setIdleTimeout(5000);
setAwayTimeout(10000);
</script>
</body>
</html>
This script keeps on loading my 1st dummy username along the other dummy users that i tried logging on with. The same goes for the other dummy accounts the browser loads their username along with the others.. Whats causing this and how do I solve it? Please help
I'd simply identify and exclude the current user in you on(child_ handlers.
So for example:
// Update our GUI to show someone"s online status.
userListRef.on("child_added", function(snapshot) {
var user = snapshot.val();
if (user.name != name) {
$("<div/>")
.attr("id", getMessageId(snapshot))
.text(user.name + " is currently " + user.status)
.appendTo("#presenceDiv");
}
});