I'm new to Backbone and Firebase. I'm using Backfire, have a collection:
var UsersCollection = Backbone.Firebase.Collection.extend({
model: UserModel,
firebase: new Firebase( "https://xxxxxx.firebaseio.com/users" ),
});
The model itself is not tied to Firebase (was getting "Invalid Firebase reference created" error):
var UserModel = Backbone.Model.extend({
defaults: function() {
return {
email: "example#example.com"
};
}
});
In my View, I instantiate the collection, and I get the data okay, and I can add new models to the collection, as follows:
this.allUsers = new UsersCollection();
...
this.allUsers.add( userData );
Works great, new user records appear on Firebase. However, let's say I now want to grab a given user's model and update its data:
var userRecord = this.allUsers.findWhere( {email: email} );
userRecord.set( {age: age} );
This updates the model locally but the changed model is NOT getting synced to Firebase. I tried userRecord.save(); afterwards but it triggers a "circular reference" error. Per the docs, set() should do it bur clearly something is off :(
The userRecord variable comes back as undefined in this case because the allUsers collection hasn't been populated with data yet. The data is still being downloaded to Firebase when you are calling .findWhere().
To avoid this, you can listen to the sync event and do all of your actions from there.
JSBin example.
allUsers.on('sync', function(collection) {
// collection is allUsers
var userRecord = collection.findWhere( {email: 'david#email.com'} );
userRecord.on('change', function(model) {
console.log('changed', model);
});
userRecord.set( {age:4} );
});
You'll want to ensure your collections are populated before you process any actions on them. The sync event is the recommended way to do this.
Related
Basically, I am trying to get a wss feed going from Poloniex, and update a collection with it so that I can have 'latest' prices in a collection (I will update and overwrite existing entries) and show it on a web page. For now, I got the wss working and am just trying to insert some of the data in the collection to see if it works, but it doesn't and I can't figure out why!
Note: The collection works, I've manually inserted a record with the shell.
Here is the code I have now:
import { Meteor } from 'meteor/meteor';
import * as autobahn from "autobahn";
import { Mongo } from 'meteor/mongo'
import { SimpleSchema } from 'meteor/aldeed:simple-schema'
//quick DB
Maindb = new Mongo.Collection('maindb');
Maindb.schema = new SimpleSchema({
place: {type: String},
pair: {type: String},
last: {type: Number, defaultValue: 0}
});
Meteor.startup(() => {
var wsuri = "wss://api.poloniex.com";
var Connection = new autobahn.Connection({
url: wsuri,
realm: "realm1"
});
Connection.onopen = function(session)
{
function tickerEvent (args,kwargs) {
console.log(args[0]);
Maindb.insert({place: 'Poloniex', pair: args[0]});
}
session.subscribe('ticker', tickerEvent);
Connection.onclose = function () {
console.log("Websocket connection closed");
}
}
Connection.open();
});
The console logs the feed but then the insert does not work.
I looked online and it said that to get an insert to work when in a 'non Meteor' function, you need to use Meteor.bindEnvironment which I did:
I changed
function tickerEvent (args,kwargs) {
console.log(args[0]);
Maindb.insert({place: 'Poloniex', pair: args[0]});
}
which became
var tickerEvent = Meteor.bindEnvironment(function(args,kwargs) {
console.log(args[0]);
Maindb.insert({place: 'Poloniex', pair: args[0]});
}); tickerEvent();
Which doesn't do anything - not even print the feed on my console. Using this same structure but simply removing Meteor.bindEnvironmentprints again to the console but doesn't update.
Am I doing something wrong?
I'm somewhat new to React, and using the re-base library to work with Firebase.
I'm currently trying to render a table, but because of the way my data is structured in firebase, I need to get a list of keys from two locations- the first one being a list of user keys that are a member of a team, and the second being the full user information.
The team node is structured like this: /teams/team_id/userkeys, and the user info is stored like this: /Users/userkey/{email, name, etc.}
My table consists of two react components: a table component and a row component.
My table component has props teamid passed to it, and I'm using re-base's bindToState functionality to get the associated user keys in componentWillMount(). Then, I use bindToState again to get the full user node, like so:
componentWillMount() {
this.ref = base.bindToState(`/teams/${this.props.data}/members`, {
context: this,
state: 'members',
asArray: true,
then() {
this.secondref = base.bindToState('/Users', {
context: this,
state: 'users',
asArray: true,
then() {
let membersKeys = this.state.members.map(function(item) {
return item.key;
});
let usersKeys = this.state.members.map(function(item) {
return item.key;
});
let onlyCorrectMembersKeys = intersection(membersKeys, usersKeys);
this.setState({
loading: false
});
}
});
}
});
}
As you can see, I create membersKeys and usersKeys and then use underscore.js's intersection function to get all the member keys that are in my users node (note: I do this because there are some cases where a user will be a member of a team, but not be under /Users).
The part I'm struggling with is adding an additional rebase call to create the full members array (ie. the user data from /Users for the keys in onlyCorrectMembersKeys.
Edit: I've tried
let allKeys = [];
onlyCorrectMembersKeys.forEach(function(element) {
base.fetch(`/Users/${element}`, {
asArray: true,
then(data) {
allKeys.prototype.concat(data);
}
});
});
But I'm receiving the error Error: REBASE: The options argument must contain a context property of type object. Instead, got undefined
I'm assuming that's because onlyCorrectMembersKeys hasn't been fully computed yet, but I'm struggling with how to figure out the best way to solve this..
For anyone dealing with this issue as well, I seemed to have found (somewhat) of a solution:
onlyCorrectMembersKeys.map(function(item) {
base.fetch(`/Users/${item}`, {
context: this,
asObject: true,
then(data) {
if (data) {
allKeyss.push({item,data});
this.setState({allKeys: allKeyss});
}
this.setState({loading: false});
},
onFailure(err) {
console.log(err);
this.setState({loading: false});
}
})
}, this);
}
This works fine, but when users and members state is updated, it doesn't update the allkeys state. I'm sure this is just due to my level of react knowledge, so when I figure that out I'll post the solution.
Edit: using listenTo instead of bindToState is the correct approach as bindToState's callback is only fired once.
I am working on an admin page that shows a list of the user's orders. For some reason, publication function is not receiving the user's ID as a parameter from my subscription function.
My user object contains the attributes:
_id
userId
confirmed
My orders object contains the attributes:
_id
userId
amount
date
My publication:
Meteor.publish('clientOrders', function(userId) {
console.log('user is ' + userId);
return Order.find({_id: userId});
});
My subscription:
viewClientOrders = RouteController.extend({
loadingTemplate: 'loading',
waitOn: function () {
Meteor.subscribe('clientOrders', this.params._id);
},
action: function() {
this.render('viewClientOrders');
}
});
My route:
Router.route('/viewClientOrders/:id', {name: 'viewClientOrders', controller: 'viewClientOrders', onBeforeAction: requireLogin});
I did a console.log for the userId and it returns null, although I clearly passed a userId parameter (this.params._id) in my subscription function. However, if I pass a parameter of 3, for testing purposes, it will work. Also, I used a similar publication/subscription method to view a client's profile, passing in the exact same parameters and it works fine...
Anyone know what's going on? Thanks!
Make sure you return in your waitOn. Iron Router will only proceed to render if it knows the subscription is ready:
viewClientOrders = RouteController.extend({
waitOn: function () {
return Meteor.subscribe('clientOrders', this.params._id);
}
...
Make sure you've also set a loadingTemplate (docs).
I'm using Backbone.js to route profile views so I can view data belonging to /user, and that part works fine. I'm able to generate an _id based on the username and pass it into the server publish function, which logs it. However, when I log the results back to the client in the subscribe function, my result looks like this:
Object {stop: function, ready: function}
//Client Side
Template.userquery.userproject = function() {
var query = Session.get('userquery');
var user = Meteor.users.findOne({username: query});
if (user) {
console.log(user._id); //(works)
campaigns = Meteor.subscribe('userquery', user._id, function() {
console.log('ready'); //(works)
});
console.log(campaigns); //(returns Object {stop: function, ready: function})
return campaigns;
}
}
//Server Side
Meteor.publish('userquery', function(userid) {
console.log('break');
console.log(userid); //(I get userid in Terminal)
var campaigns = Campaigns.find({owner: userid}, {fields: {owner: 1, name: 1}});
if (campaigns) {
console.log(campaigns);
return campaigns;
}
});
Am I missing something in this function? I have autopublish turned off because it was generating my search twice.
Meteor.subscribe, according to the docs, "Returns a handle that provides stop() and ready() methods." So the behaviour you're seeing is intended.
I have implemented find() and findAll() methods on my Property model. Both methods make asynchronous calls to an API. findAll() is called while connecting the outlets for my home route, and works fine. find() is called by Ember.js while connecting the outlets for my property route. Note that find() is not called when navigating to a property route through actions, but is called when you go directly to the route through the URL.
Here is my router:
App.Router = Ember.Router.extend({
root: Ember.Route.extend({
showProperty: Ember.Route.transitionTo('property'),
home: Ember.Route.extend({
route: '/',
connectOutlets: function(router) {
router.get('applicationController').connectOutlet('home', App.Property.findAll());
}
}),
property: Ember.Route.extend({
route: '/property/:property_id',
connectOutlets: function(router, property) {
router.get('applicationController').connectOutlet('property', property);
}
}),
})
});
And here are my findAll() and find() methods:
App.Property.reopenClass({
find: function(id) {
var property = {};
$.getJSON('/api/v1/property/' + id, function(data) {
property = App.Property.create(data.property);
});
return property;
},
findAll: function() {
var properties = [];
$.getJSON('/api/v1/properties', function(data) {
data.properties.forEach(function(item) {
properties.pushObject(App.Property.create(item));
});
});
return properties;
}
});
When I go to a route other than index, for example http://app.tld/#/property/1, the route gets rewritten to http://app.tld/#/property/undefined. Nothing is being passed to the content property of the Property controller. How can I make asynchronous calls in the find() method? Unless I am mistaken, asynchronous calls work fine in the findAll() method, which is the source of my confusion.
This question is similar to Deserialize with an async callback, but I'm using the find() method instead of overriding the deserialize() method.
Thanks in advance.
I found that setting the id property explicitly solves this problem. In your case this would look like this.
find: function(id) {
var user = App.User.create();
$.getJSON('/api/v1/property/' + id, function(data) {
user.setProperties(data.user)
});
user.set("id",id); // <-- THIS
return user;
}
Once your user gets its properties set the view updates as normal. Ember just need the id part before in order to update the URL.
Hope this helps :-)
Here's what you want to be doing. I changed the model to User to make things a little clearer.
In the case of find(), you return a blank model instance that gets it's properties filled in when the AJAX request comes back. The nice thing about Ember's data-binding is that you can display this model in a view immediately and the view will update when the AJAX request returns and updates the model instance.
In the case of findAll(), you return a blank array that gets filled in when the AJAX request comes back. In the same way as find(), you can display this list of models (which at first will be blank) in a view and when the AJAX request returns and fills in the array, the view will update.
App.User.reopenClass({
find: function(id) {
var user = App.User.create();
$.getJSON('/api/v1/property/' + id, function(data) {
user.setProperties(data.user)
});
return user;
},
findAll: function() {
var userList = [];
$.getJSON('/api/v1/properties', function(data) {
var users = data.users.map(function(userData) {
return App.User.create(userData);
});
userList.pushObjects(users);
});
return userList;
}
});