Im trying to subscribe my client side to my userFriends collection and Chrome's console display: userFriends is not defined
This is my code:
Server side...
userFriends = new Mongo.Collection("friends");
console.log(userFriends.find().fetch())
Meteor.publish("friends", function () {
return userFriends.find();
});
NOTE: The console.log display in the terminal an empty array which is good
Client side...
Meteor.subscribe("friends");
console.log(userFriends.find().fetch())
NOTE: This is where Chrome's console display the error
what am I doing wrong ?
Thank you
UPDATE 1: Now I can see the Friends collection in Chrome's console, but i cant insert data. I have the subscribe in client.js inside my client folder and my insert code is in friend.js inside client folder aswell.
The collection needs to be defined on both the client and the server. Typically this is done by placing the definition in a shared directory like lib:
lib/collections/user-friends.js
userFriends = new Mongo.Collection('friends');
Note the convention is to name the collection with the capitalized camel case version of the collection name. So calling it Friends would be more typical.
You need to declare the collection on both environments using shared code.
lib/user-friends.js
userFriends = new Mongo.Collection("friends");
client/user-friends.js
Meteor.subscribe("friends", function(){
console.log(userFriends.find().fetch());
});
In the client, be aware that collection subscriptions are asynchronous by nature (there's network latency on the client, inherent to fetching the documents from the server).
This is why if you console.log your collection content right after Meteor.subscribeing you'll get [], but if you wait until the subscription is ready using a callback, documents will be displayed correctly.
You have two correct answers but they do assume some knowledge for you. Here's what it looks like using Meteor's file structure (available at http://docs.meteor.com/#/full/structuringyourapp).
In your /lib (shared) directory
Make a file called "collections.js" and in it create your collection.
userFriends = new Mongo.Collection("friends");
I would instead do userFriends = new Mongo.Collection("userfriends"); so that your are always using the same word for your collection and you change the capitalization depending on if you're working on client or server. This is very helpful.
In Your /client directory
Make a file called "subscriptions.js" and in it subscribe to your collection.
Meteor.subscribe('friends');
In Your /server directory
Make a file called "publications.js" and in it publish your collection.
Meteor.publish('friends',function(){
return userFriends.find();
});
You don't need a fetch or anything there.
Essentially your code is failing because of where you're trying to house everything. What I've given you is three points of where you work. Client, Shared, Server. Set your app up that way and it will be easy to immediately figure out where you're working.
Hope that helps.
Related
In my app I have an admin area, this area also has some admin-client MiniMongo that goes with it. So the admin is subscribing to a publication, then this publication puts data from several collections on this admin-client MiniMongo collection.
The access to the subscription is restricted, and so is the publication. But I noticed that the collection still registers for every user, though (I believe) it is being populated with data only when an admin logs in.
Still I would like to know if I can create the collection only when an admin logs in and not before?
Now the code is being run on app startup.
Right now I have it like this:
export const AdminData = new Mongo.Collection("admin_data");
I also tried this, but it doesn't work:
let AdminData;
if(Roles.userIsInRole(Meteor.userId(), 'admin'){
AdminData = new Mongo.Collection("admin_data");
}
export default AdminData;
If it make a difference this is my project structure
client/
server/
imports/
--api/
----admin/
------client/
--------adminData.js (the above file)
------server/
--------publication.js
--startup/
----client/
----both/
----server/
--ui/
----adminArea/
------adminComponent.jsx (the collection is only imported here)
You should create the collection on the server. (move adminData.js besides publication.js). Also you should check security in the publication itself. Meteor.userId() will only work in meteor methods on the server and you will have an error if you leave it there
On the client your adminComponent needs to subscribe to the server publication to have access to the collection's data. To do so you'll need to wrap your component in something like react-komposer
Hope this helps
After lots of reading, I'm starting to get a better handle on Meteor's publish/subscribe model. I've removed the autopublish training wheels from my first app and while I have most everything working, I am seeing one issue.
When the app first loads, my publish and subscribe hooks work great. I have a block of code that runs in a Tracker.autorun() block which makes the subscribe calls, I am able to sequentially wait for data from the server using ready() on my subscribe handles, etc.
One feature of my app is that it allows the user to insert new documents into a collection. More specifically, when the user performs a certain action, this triggers an insert. At that point, the client-side JS runs and the insert into MiniMongo completes. The reactive autorun block runs and the client can see the inserted documented. The client updates the DOM with the new inserted data and all is well.
Furthermore, when I peek into the server-side MongoDB, I see the inserted document which means the server-side JS is running fine as well.
Here's where it gets weird. The client-side autorun block runs a second time (I'm not sure why) and this time, the client no longer has the inserted item. When the DOM renders, the newly inserted item is now gone. If I reload the page, all is well again.
Has anyone seen this behavior before? I'm also noticing that the server-side publish call runs once on page load but then it doesn't run again after the insert. This seems wrong because how else will the client get the reconciled data from the server after the insertion (i.e. after Meteor's client-side latency compensation)?
The important functions (ComponentInstances is the collection that is bugging out):
Publish block:
Meteor.publish('allComponentInstances', function (documentId, screenIndex) {
console.log(`documentId: ${documentId} screenIndex: ${screenIndex}`)
const screens = Screens.find({ownerDocumentId: documentId})
const selectedScreen = screens.fetch()[screenIndex]
return ComponentInstances.find({_id: {$in: selectedScreen.allComponentInstanceIds}})
})
Subscription block in autorun:
// ... a bunch of irrelevant code above
const allComponentInstancesHandle = Meteor.subscribe('allComponentInstances', document._id, 0)
if (allComponentInstancesHandle.ready()) {
isReady = true
screens = Screens.find({ownerDocumentId: document._id}).fetch()
const componentInstanceObjects = ComponentInstances.find().fetch()
allComponentInstances = {}
componentInstanceObjects.map((componentInstance) => {
allComponentInstances[componentInstance._id] = componentInstance
})
}
This is most probably you're inserting documents from client side. And you have not set up your permission rules properly. When you remove autopublish and insecure from your app, you are not allowed to insert/update/remove documents into collection unless you have allow/deny rules set up in the server side.
Meteor has a great feature called latency compensation which tries emulate your db operations before it gets the actual write operation in the db. And when the server tries to write in the db, it looks for allow/deny rules.If the permission rules doesn't allow the db operation or Whatever the reason( either allow/deny or authentication) for not actually written in the db, then the server data gets synchronized with your client side db.
This is why i assume you are seeing your document being inserted for the first time and gets disappeared within a second.
check this section of meteor docs.
http://docs.meteor.com/#/full/allow
I ended up solving this a different way. The core issue, I believe, has nothing to do with accept/deny rules. In fact, their role is still hazy to me.
I realize now what I've been reading all along in the Meteor docs: the publish functions return cursors. If the cursor itself doesn't change (e.g. if you're passing specific keys you want to fetch), then it won't really work as a reactive data source in the sense that new documents in a collection will not make the data publish again. You are, after all, still requesting the same keys.
The way forward is to come up with a publish cursor that accurately reflects the reactive data you want to retrieve. This sounds abstract but in practice, it means make sure the cursor is general, not specific to the specific keys you are retrieving.
In Meteor, I got a collection that the client subscribes to. In some cases, instead of publishing the documents that exists in the collection on the server, I want to send down some bogus data. Now that's fine using the this.added function in the publish.
My problem is that I want to treat the bogus doc as if it were a real document, specifically this gets troublesome when I want to update it. For the real docs I run a RealDocs.update but when doing that on the bogus doc it fails since there is no representation of it on the server (and I'd like to keep it that way).
A collection API that allowed me to pass something like local = true this would be fantastic but I have no idea how difficult that would be to implement and I'm not to fond of modifying the core code.
Right now I'm stuck at either creating a BogusDocs = new Meteor.Collection(null) but that makes populating the Collection more difficult since I have to either hard code fixtures in the client code or use a method to get the data from the server and I have to make sure I call BogusDocs.update instead of RealDocs.update as soon as I'm dealing with bogus data.
Maybe I could actually insert the data on the server and make sure it's removed later, but the data really has nothing to do with the server side collection so I'd rather avoid that.
Any thoughts on how to approach this problem?
After some further investigation (the evented mind site) it turns out that one can modify the local collection without making calls to the server. This is done by running the same methods as you usually would, but on MyCollection._collection instead of just on Collection. MyCollection.update() would thus become MyCollection._collection.update(). So, using a simple wrapper one can pass in the usual arguments to a update call to update the collection as usual (which will try to call the server which in turn will trigger your allow/deny rules) or we can add 'local' as the last argument to only perform the update in the client collection. Something like this should do it.
DocsUpdateWrapper = function() {
var lastIndex = arguments.length -1;
if (arguments[lastIndex] === 'local') {
Docs._collection.update(arguments.slice(0, lastIndex);
} else {
Docs.update(arguments)
}
}
(This could of course be extended to a DocsWrapper that allows for insertion and removals too.)(Didnt try this function yet but it should serve well as an example.)
The biggest benefit of this is imo that we can use the exact same calls to retrieve documents from the local collection, regardless of if they are local or living on the server too. By adding a simple boolean to the doc we can keep track of which documents are only local and which are not (An improved DocsWrapper could check for that bool so we could even omit passing the 'local' argument.) so we know how to update them.
There are some people working on local storage in the browser
https://github.com/awwx/meteor-browser-store
You might be able to adapt some of their ideas to provide "fake" documents.
I would use the transform feature on the collection to make an object that knows what to do with itself (on client). Give it the corruct update method (real/bogus), then call .update rather than a general one.
You can put the code from this.added into the transform process.
You can also set up a local minimongo collection. Insert on callback
#FoundAgents = new Meteor.Collection(null, Agent.transformData )
FoundAgents.remove({})
Meteor.call 'Get_agentsCloseToOffer', me, ping, (err, data) ->
if err
console.log JSON.stringify err,null,2
else
_.each data, (item) ->
FoundAgents.insert item
Maybe this interesting for you as well, I created two examples with native Meteor Local Collections at meteorpad. The first pad shows an example with plain reactive recordset: Sample_Publish_to_Local-Collection. The second will use the collection .observe method to listen to data: Collection.observe().
I've actually been toying with Meteor for a little bit now, but I realized that I still lack some (or a lot!) comprehension on the topic.
For example, here is a tutorial that uses node.js/express/socket.io to make a simple real-time chat: http://net.tutsplus.com/tutorials/javascript-ajax/real-time-chat-with-nodejs-socket-io-and-expressjs/
In that above example, through socket.io, the webserver receives some data and passes it onto all of the connected clients -- all without any database accesses.
With Meteor, in all the examples that I've seen, clients are updated by writing to the mongodb, which then updates all the clients. But what if I don't need to write data to the database? It seems like an expensive step to pass data to all clients.
I am sure I am missing something here. What would be the Meteor way of updating all the clients (say, like with a simple chat app), but without needing the expense of writing to a database first?
Thank you!
At the moment there isn't an official way to send data to clients without writing it to a collection. Its a little tricker in meteor because the step to send data to multiple clients when there isn't a place to write to comes from when multiple meteor's are used together. I.e items sent from one meteor won't come to clients subscribed on the other.
There is a temporary solution using Meteor Streams (http://meteorhacks.com/introducing-meteor-streams.html) that can let you do what you want without writing to the database in the meanwhile.
There is also a pretty extensive discussion about this on meteor-talk (https://groups.google.com/forum/#!topic/meteor-talk/Ze9U9lEozzE) if you want to understand some of the technical details. This will actually become possible when the linker branch is merged into master, for a single server
Here's a bit of way to have a 'virtual collection, its not perfect but it can do until Meteor has a more polished way of having it done.
Meteor.publish("virtual_collection", function() {
this.added("virtual_coll", "some_id_of_doc", {key: "value"});
//When done
this.ready()
});
Then subscribe to this on the client:
var Virt_Collection = new Meteor.Collection("virtual_coll");
Meteor.subscribe("virtual_collection");
Then you could run this when the subscription is complete:
Virt_Collection.findOne();
=> { _id: "some_id_of_doc", key: "value"}
This is a bit messy but you could also hook into it to update or remove collections. At least this way though you won't be using any plugins or packages.
See : https://www.eventedmind.com/posts/meteor-how-to-publish-to-a-client-only-collection for more details and a video example.
The publish function on the server sends data to clients. It has some convenient shortcuts to publish query results from the database but you do not have to use these. The publish function has this.added(), this.removed(), and this.changed() that allow you to publish anything you choose. The client then subscribes and receives the published data.
For example:
if ( Meteor.isClient ){
var someMessages = new Meteor.Collection( "messages" ); //messages is name of collection on client side
Meteor.subscribe ( "messagesSub" ); //messagesSub tells the server which publish function to request data from
Deps.autorun( function(){
var message = someMessages.findOne({});
if ( message ) console.log( message.m ); // prints This is not from a database
});
}
if (Meteor.isServer ) {
Meteor.publish( "messagesSub", function(){
var self = this;
self.added ( "messages", "madeUpId1", { m: "This is not from a database"} ); //messages is the collection that will be published to
self.ready();
});
}
There is an example in meteor docs explained here and another example here. I also have an example that shares data between clients without ever using a database just to teach myself how the publish and subscribe works. Nothing used but basic meteor.
It's possible to use Meteor's livedata package (their DDP implementation) without the need of a database on the server. This was demoed by Avital Oliver and below I'll point out the pertinent part.
The magic happens here:
if (Meteor.isServer) {
TransientNotes = new Meteor.Collection("transientNotes", {connection: null});
Meteor.publish("transientNotes", function () {
return TransientNotes.find();
});
}
if (Meteor.isClient) {
TransientNotes = new Meteor.Collection("transientNotes");
Meteor.subscribe("transientNotes");
}
Setting connection: null specifies no connection (see Meteor docs).
Akshat suggested using streams. I'm unable to reply to his comment due to lack of reputation, so I will put this here. The package he links to is no longer actively maintained (see author's tweet). I recommend using the yuukan:streamy (look it up on Atmosphere) package instead or use the underlying SockJS lib used in Meteor itself—you can learn how to do this by going through the Meteor code and look how Meteor.server.Stream_server, and Meteor.connection._stream are used, which is what the Streamy package does.
I tested an implementation of the Streamy chat example and found performance to be negligibly different but this was only on rudimentary tests. Using the first approach you get the benefits of the minimongo implementation (e.g. finding) and Meteor reactivity. Reactivity is possible with Streamy, though is does through things like using ReactiveVar's.
There is a way! At least theoretically The protocol used by Meteor to sync between client and server is called DDP. The spec is here
And although there some examples here and here of people implementing their own DDP clients, I'm afraid haven't seen examples of implementations of DDP servers. But I think the protocol is straightforward and would guess it wouldn't be so hard to implement.
I want to add and return a calculated field from a server Meteor.publish that doesn't actually persist in MongoDB. Is this possible? Something like where I format some markdown:
Meteor.publish('recentEdits', function(pageId) {
var edits, formattedContent;
edits = WikiEdits.find({pageId: pageId}, {sort: {ts: -1},
limit: RECENT_EDIT_LIMIT});
edits.forEach(function(edit) {
formattedContent = marked(edit.content);
edit.formattedContent = formattedContent;
});
return edits;
});
To the client it should seem like that formattedContent field came like any other, but it's not actually in MongoDB. Is this possible and if so what is the best way? Even if I should store the formattedContent, I would still like to know how to do this.
I tried using the transform option in Meteor.Collection and that only ran on the client, but I want this to happen from the server.
you can use the transform option of the find
the only problem is that it is also executed on the client. If you can live with that, it's a solution.
Another option could be to add an unmanaged collection on the client and get the contents from a Meteor method
The local unmanaged collection will not be stored in the MongoDB you can call the Meteor.method from an autorun and observe will work on the local collection.