I have a Meteor method that returns all user accounts on my application
returnUsers: function(){
return Meteor.users.find().fetch();
}
I'm using new ReactiveVar to pass the return value of the Meteor method into my template helper:
Template.listViewTemplate.created = function (){
var self = this;
self.myAsyncValue = new ReactiveVar("Waiting for response from serv er...");
Meteor.call('returnUsers', function (err, users) {
if (err)
console.log(err);
else
self.myAsyncValue.set(users);
});
}
Template.listViewTemplate.helpers({
userCollection: function(){
return Template.instance().myAsyncValue.get();
}
});
But when I go to render the users into the view, I get a console error that reads
{{#each}} currently only accepts arrays
When I render without the #each iterator, using
<ul id='usersList'>
{{userCollection}}
</ul>
the output on my web-page accurately reflects the number of users (2), but reads
[object Object],[object Object]
I'm pretty sure that there is some funkiness going on here because I'm using a global Meteor collection (Meteor.users.find().fetch(), as opposed to having defined my own collection), but I'm not sure how to get around it.
I want to display a list of all users so the current user can click another user and share a document with them--not sure how to get around this.
You don't need to use a reactive variable for this. The function at Template.listViewTemplate.created is not container in an autorun, which means: It won't get recomputed.
The best approach for your scenario is: Use a variable to get the status ( loading, loaded, error) and another variable to save the array itself attach to self. Reactivity is cool but you should only use it when needed.
About:
[object Object],[object Object]
This is happening because you're not extracting any value form the object provided nor looping using {{#each}}.
Your solutions for listing users is dangerous and inefficient. You're sending to the client all the fields from the user collection, including login tokens.
The best approach is to create a subscription that send only the necessaries fields like: _id, info.firstName. You should also have some criteria to the list users and use pagination. Consider also a search feature for such purpose.
ReactiveVar doesn't like arrays. You could install the ReactiveArray package which should accomplish exactly what you want.
Update
Based on comment of mper
In the latest versions of Meteor you can put an array in a ReactiveVar.
Tested on
meteor#1.6.0
reactive-var#1.0.11
I have several remarks about your question:
Do not fetch
You don't need .fetch() on your method. When you call find() on collections, such as Meteor.users a cursor is returned. The template (and #each in particular) can iterate through cursors. Cursors are usually better because you don't load the entire collection into memory at once - fetch does.
Meteor collections are reactive
Meteor collections are already reactive, meaning that if they change, they will trigger changes on your templates as well. So, you don't need to use a ReactiveVar to wrap your collection.
Query your local database
You don't need to use a method to get the users and in fact, you shouldn't, because usually you want to make queries to the database stored locally, not make calls to the server. Just call Meteor.users.find() directly in your template helper. You can (and should) control what is available locally through subscriptions.
Use #each with else
You can use the following in your template:
{{#each userCollection}}
...
{{else}}
Waiting for response from server...
{{/each}}
If userCollection is empty, the template will render the else block, just like you wanted.
Summarizing
Delete your method and onCreated with everything inside, change whatever is inside your template helper to only return Meteor.users.find() and use {{#each userCollection}}...{{else}}Waiting for response from server...{{/else}}
By the way
In the latest versions of Meteor you can put an array in a ReactiveVar.
Template.onCreated(function(){}) only gets run once and meteor methods only run once
You need reactivity here.
Collections sre reactive meaning pub/sub.
You need to create a publish function that allows certain users to fetch other users in the database. So all uses with maybe if the currentUser has permission to read all user info. Id limit the fields too.
Related
I am using Meteor with React JS.
I get the collection of "list" by this code,
Meteor.subscribe('getList', {status:'active'},function(){
self.setState({lists:Lists.find().fetch()});
});
Here is the code for publish,
Meteor.publish('getList', function(data){
data.user = this.userId;
return Lists.find(data);
});
So it is working. The problem is that I have two components that calling Meteor.subscribe('getList'). But the status is not the same.
So in other component, I have this code,
Meteor.subscribe('getList', {status:'archived'},function(){
self.setState({lists:Lists.find().fetch()});
});
So what happens here is, if the user go to FirstComponent, this.state.lists is empty (which is correct). Then when the user navigate to SecondComponent, this.state.lists is populated with data (which is correct). But when the user go back to FirstComponent, this.state.lists still populated with data (which is wrong).
It is like that the first collection (is empty) in client is still there. Then the second collection (not empty) is added. I want to clear the collection in client before subscribing again.
By the way I am using flow-router.
Since subscriptions are cumulative you should repeat the query condition in your setState functions:
let query = {status:'active'};
Meteor.subscribe('getList',query,function(){
self.setState({lists:Lists.find(query).fetch()});
});
query = {status:'archived'};
Meteor.subscribe('getList',query,function(){
self.setState({lists:Lists.find(query).fetch()});
});
Please note however that from a security perspective you really don't want to pass a query object from the (untrusted) client as a parameter to the subscription! From the console someone can just do:
Meteor.subscribe('getList',{});
It looks like you are setting the React state in the Meteor.subscribe onReady callback. Does each component have it's own state?
Where are you calling Meteor.subscribe from? Remember that Meteor puts subscription data in local mini-mongo and you are reading from there when you call Lists.find().
The way you have this set up, the data will not be reactive. You need to store handle to the Meteor.subscribe cursor and manage the subscription lifecycle with that.
If you could share more code, I could give a more concise answer.
I am relatively new to Meteor, and I'm trying to create a web store for my sister-in-law that takes data from her existing Etsy store and puts a custom skin on it. I've defined all of my Meteor.methods to retrieve the data, and I've proofed the data with a series of console.log statements... So, the data is there, but it won't render on the screen. Here is an example of some of the code on the server side:
Meteor.methods({
...
'getShopSections': function() {
this.unblock();
var URL = baseURL + "/sections?api_key="+apiKey;
var response = Meteor.http.get(URL).data.results;
return response;
}
...
});
This method returns an array of Object. A sample bit of JSON string from one of the returned Objects from the array:
{
active_listing_count: 20,
rank: 2,
shop_section_id: 1******0,
title: "Example Title",
user_id: 2******7
}
After fetching this data without a hitch, I was ready to make the call from the client side, and I tried and failed in several different ways before a Google search landed me at this tutorial here: https://dzone.com/articles/integrating-external-apis-your
On the client side, I have a nav.js file with the following bit of code, adapted from the above tutorial:
Template.nav.rendered = function() {
Meteor.call('getShopSections', function(err, res) {
Session.set('sections', res);
return res;
});
};
Template.nav.helpers({
category: function() {
var sections = Session.get('sections');
return sections;
}
});
And a sample call from inside my nav.html template...
<ul>
{{#each category}}
<li>{{category.title}}</li>
{{/each}}
</ul>
So, there's a few things going on here that I'm unsure of. First and foremost, the DOM is not rendering any of the category.title String despite showing the appropriate number of li placeholders. Secondly, before I followed the above tutorial, I didn't define a Session variable. Considering that the list of shop categories should remain static once the template is loaded, I didn't think it was necessary from what I understand about Session variables... but for some reason this was the difference between the template displaying a single empty <li> tag versus a number of empty <li>'s equal to category.length --- so, even though I can't comprehend why the Session variable is needed in this instance, it did bring me one perceived step closer to my goal... I have tried a number of console.log statements on the client side, and I am 100% sure the data is defined and available, but when I check the source code in my Developer Tools window, the DOM just shows a number of empty li brackets.
Can any Meteor gurus explain why 1) the DOM is not rendering any of the titles, and 2) if the Session variable indeed necessary? Please let me know if more information is needed, and I'll be very happy to provide it. Thanks!
You set the data context when you use #each, so simply use:
<li>{{title}}</li>
If a Session is the right type of reactive variable to use here or not is hard to determine without knowing what you are doing but my rough guess is that a Mini Mongo collection may be better suited for what it appears you are doing.
To get you started on deciding the correct type of reactive variable to use for this head over to the full Meteor documentation and investigate: collections, sessions, and reactive vars.
Edit: To step back and clarify a bit, a Template helper is called a reactive computation. Reactive computations inside of helpers will only execute if they are used in their respective templates AND if you use a reactive variable inside of the computation. There are multiple types of reactive variable, each with their own attributes. Your code likely didn't work at all before you used Session because you were not using a reactive variable.
I have a collection in my Meteor JS app:
MenuItems = new Mongo.Collection('menu_items');
In my Template Helper file I access this collection:
Template.admin_menu_items.helpers({
menuItems: function(){
//return items from DB
console.log('inside menuItems');
snapshot = MenuItems.find().fetch();
console.log(snapshot);
return snapshot;
},
});
Then in my html template file I call this helper:
{{#each menuItems}}
{{#each items}}
{{this}}
{{/each}}
{{/each}}
Then in my Template rendered helper callback section I again access that collection:
Template.admin_menu_items.rendered = function(){
console.log('Template.admin_menu_items.rendered');
var snapshotRendered = MenuItems.find().fetch();
//
console.log(snapshotRendered);
}
If I refresh the page I get the following output:
inside menuItems admin_m...4d3e6ec (line 9)
[] admin_m...4d3e6ec (line 13)
Template.admin_menu_items.rendered admin_m...4d3e6ec (line 36)
[] admin_m...4d3e6ec (line 40)
inside menuItems admin_m...4d3e6ec (line 9)
[Object { _id="nHZBfwAt64dwiPjCB", items=[3]}]
What I would like to understand and ask about is: why is it that the first call to MenuItems.find().fetch() inside the menuItems helper and the first call to MenuItems.find().fetch() inside the Template rendered callback function returns a empty [] array when the MenuItems collection has documents inside it already???
Is it because my template and template helper files are deeper in the hierarchy of files and therefore is loaded earlier than my menu_item.js file that instantiates the MenuItems variable by:
MenuItems = new Mongo.Collection('menu_items');
How do I ensure that the MongoDB will return the right number of documents from the very initial call or access done to it???
Thank you very much
This is more to do with subscriptions and data-over-the-wire than it is to do with the load order of files. When you refresh the page, you get the template first and the data later, so the order of operations is something like this:
The template starts building itself without data, so the helper runs with an empty array
The template is finished building and renders, but without data it too has an empty array
The data arrives
The helper reactively re-runs with the new data, and returns a full array
The solution to this, if you need the data in the rendered callback, is to prevent this template from running before the data arrives. You can handle this with a router that waits on a subscription, or by checking the data and only rendering the template once the number of items is not zero, or by using the Template.subscribe function and guarding your template with an if statement until the data is ready.
On a side note, you may want to return the result of find() in your helper rather than find().fetch(), it'll work the same in an {{#each}} block, but with finer-grained reactivity as Blaze can iterate over the cursor itself. Unless of course you really need the array.
How do I ensure that the MongoDB will return the right number of documents from the very initial call or access done to it???
Well, in the Meteor spirit... You don't.
The whole thing about Meteor is reactivity. You don't want to have to wait for the data to arrive to start rendering. It would mean that your user would be watching at a blank page for a while before seeing some action, because the page blocks rendering, waiting in dark oblivion for the full data.
Instead you go for reactivity. You execute reactive computations which are executed immediately, then each time reactive data inside them updates. Helpers are reactive computations, Mongo cursors in Meteor are reactive data. Helpers are run initially (maybe returning nothing because the data hasn't arrived yet), then each time some new data comes in.
You have to take the case of "Nope, no data yet" into account when coding these reactive computations. For example, if(someCollection.findOne()) would make sure that there is at least one document in someCollection.
In your case it may not really be useful though since Spacebars handles cursors pretty well.
ya'll I have a bit of a structural/procedural question for ya.
So I have a pretty simple ember app, trying to use ember-data and I'm just not sure if I'm 'doing it right'. So the user hits my index template, I grab their location coordinates and encode a hash of it (that part works). Then on my server I have a db that stores 'tiles' named after there hash'd coords (if i hit my #/tiles/H1A2S3H4E5D route I get back properly formatted JSON).
What I would like to happen next, if to display each of the returned tiles to the user on the bottom of the first page (like in a partial maybe? if handlebars does that).
I have a DS.Model for the tiles, if I hard code the Hash'd cords into a App.find(H1A2S3H4E5D); I can see my server properly responding to the query. However, I cannot seem to be able to figure out how to access the returned JSON object, or how to display it to the user.
I did watch a few tutorial videos but they all seem to be outdated with the old router.
Mainly I would like to know:
1. Where does the information returned by App.find(); live & how to access it?
2. what is the 'correct' way to structure my templates/views to handle this?
3. how should I pass that id (the hash'd coords) to App.find? as a global variable? or is there a better way?
the biggest problem(to me) seems to be that the id I search by doesn't exist until the user hit the page tho first time. (since its dynamically generated) so I can't just grab it when the page loads.
I can post a fiddle if required, but I'm looking for more of a conceptual/instructional answer rather then some one to just write my code for me
I'm still learning a lot with Ember as well, but this is my understanding. When you follow the guides and the tutorials out there, you'll have something like this:
App.TileController = Ember.ObjectController.extend();
App.TileRoute = Ember.Route.extend({
setupController: function(controller) {
controller.set('content', App.Tile.find(MYHASH));
}
});
What it does is set the special content object to the result. So since we're declaring an object controller, and calling find with a parameter, it knows that a single result is expected. So a view & template that follow the naming convention of Tile will be loaded. And in there you can access properties on the Tile object:
<p>{{lat}}</p><p>{{lng}}</p>
I have to admit that this feels a bit mystical at times. The core to it is all in the naming convention. You need to be pretty specific in how you name all your various controllers, routes, etc. Once that's nailed down, it's a matter of binding what data you want to the controller's content.
1) Aside from the generic answer of "in memory", the .find() calls live where ever you return it to. Generally speaking, this is meant to be set on a 'content' property of a controller.
2) I more or less answered this, but generally speaking you take the name of your route, and base it off that. So for a route TileRoute, you have:
TileController = Ember.ObjectController.extend
Tile = DS.Model.extend
TileView = Ember.View.extend
tile.handlebars
I generally store all my handlebars files in a templates/ folder. If you nest them deeper, just specify the path in your view object:
App.TileView = Ember.View.extend({
templateName: "tiles/show"
});
3) This really depends on your app. Generally speaking its better for the id to be either obtained from the URL, or constructed locally in a function. Since you are encoding a hash, i imagine you're doing this in a function, and then calling find. I do something a bit similar for an Array controller.
I don't know at what point you are generating a hash, so let's say it's onload. You should be able to generate the hash just in the setupController function.
App.TileRoute = Ember.Route.extend({
generateHashBasedOnCoords: function() {
// ...
},
setupController: function(controller) {
var MYHASH = this.generateHashBasedOnCoords();
controller.set('content', App.Tile.find(MYHASH));
}
});
I hope that helps.
I believe that you can make use of the data binding in ember and basically have an array controller for tiles and set the content initially to an empty array. Then we you get back your response do a App.find() and set the content of the tiles controller with the data that is returned. This should update the view through the data binding. (Very high level response)
The data itself is stored in a store that is setup with ember data. You access it with the same method you are using the model methods App.Tile.find() ect. It checks to see if the data that is needed is in the store if so it returns the data otherwise it makes a call to the api to get the data.
First of all, thanks to the guys of DocumentCloud for releasing those two super-useful tools.
Here goes my question(s):
I'm trying to use visulasearch.js in a backbone.js app.
In my app I have a basic index.html and a myapp.js javascript file wich contains the main application done with backbone.js
I use CouchDB as data storage, and I successfully can retrieve in a restful way all the data to be put in the collection.
I must retrieve the search query given by visualsearch.js and use it to filter a collection.
I surely need a view for the searchbox, to trigger an event when enter is hit, but..
Should I initialze the searchbox externally to myapp.js, within an additional js file or my index.html page (as suggested in the visualsearch mini.tutorial)?
Or I should initialize it within the searchbox view (myapp.js)? This latter solution seems to be too tricky (it was what I was trying to do, but even when I succeed, it's too complicated and I lost the simplicity of bacbone mvc).
Let's say I succeed in retrieving the search string as a JSON object like {name:'Fat DAvid', address:'24, slim st', phone:'0098876534287'}. Once done that, which function can I use to retrieve, in the collection, only the models whose fields match with the given string. I understand that I should do a map or a filter, but those function seems to serve natively for slightly different tasks.
a. is it really the best way to filter results? It charges the client (which must filter the results), while making a new query (a view or a filter) to CouchDB would be quite simple and, considered the small amount of data and the low access rate to the site, not so expensive. However, making all the filtering action client-side, it's much simpler than making new view(or list or filters) in CouchDB and linking it the the backbone.js view
You can initialize your VisualSearch.js search box right in your myapp.js. Just make sure you keep a reference to it so you can then extract out the facets and values later.
For example:
var visualSearch = VS.init({...})
// Returns the unstructured search query
visualSearch.searchBox.value()
// "country: "South Africa" account: 5-samuel title: "Pentagon Papers""
// Returns an array of Facet model instances
visualSearch.searchQuery.facets()
// [FacetModel<country:"South Africa">,
// FacetModel<account:5-samuel>,
// FacetModel<title:"Pentagon Papers">]
If you have these models in a Backbone collection, you can easily perform a filter:
var facets = visualSearch.searchQuery.models;
_.each(facets, function(facet) {
switch (facet.get('category')) {
case 'country':
CountriesCollection.select(function(country) {
return country.get('name') == facet.get('value');
});
break;
// etc...
}
});