I am Meteor newbie, so please, don't blame me too much for stupid question! I am fairly trying to find answer in Google and Meteor.docs, but my foolishness is stronger than me at now.
I am trying to build simple Catalog of Products from two Collection.
Catalogs = new Mongo.collection('catalogs');
Products = new Mongo.collection('products');
My target - is to achieve view like this:
Vegetables <-- #1 catalog
*Tomato <-- product from #1 catalog
*Cucumber <-- another product from #1 ctalog
Fruits <-- #2 catalog
*Apple
*Pineapple
*Banana
I have no problem with creating catalogs and adding products to them with parent ID's. But now I'm stuck with Meteor templates and have no ideas, how to show products, that nested in their parents catalogs.
Sometime I do similar thing with Laravel, and in that case I check for each product with Laravel's Blade like this:
{{$catalog->name}}
#foreach ($products as $product)
#if ($catalog->id == $product->parentId)
{{$product->name}}
#endif
#endforeach
I agree, maybe it's not elegant solution, but it works for me.
But in Meteor, with changing "data contexts" (if I understand their role right), I cannot grasp, how to do this thing, if I can't get parent or child properties. I believe that it must have clear and straight way of solving, but I can't see it by myself :(
Please, can you help me with this problem?
Assuming each catalog has a name and each product has a name and a catalogId, here's a template which shows all catalogs and products as a series of lists:
<template name='catalogProducts'>
{{#each catalogs}}
<div class='catalog-name'>{{name}}</div>
<ul>
{{#each products}}
<li>{{name}}</li>
{{/each}}
</ul>
{{/each}}
</template>
Template.catalogProducts.helpers({
catalogs: function() {
// find all of the catalogs
return Catalogs.find();
},
products: function() {
// here the context is a catalog, so this._id is the id
// of the current catalog - this finds all of its products
return Products.find({catalogId: this._id});
}
});
Recommended reading: A Guide to Meteor Templates & Data Contexts
Related
Just starting with Meteor, and going through the Meteor Tutorial by Matthew Platts.
In this tutorial, as well as in the official Meteor Documentation, there are many references to the concept of data context, but I can't seem to find a cristal clear definition / explanation (with examples) of what this is.
For instance, in the 2.4.3 Rendering Data with Helpers section, we read:
Notice that inside of the #each block we go {{name}}, even though
we have no name helper defined. This is because the data context
changes inside the #each block. We loop through each item in the
teams array, and since each item has a “name” attribute Meteor will
automatically create a {{ name }} helper.
UPDATE: Actually, just reading through the end of this very section, the author recommends a resource that makes things pretty clear: A Guide to Meteor Templates & Data Contexts. Still no accurate definition though.
So, in Meteor, what is data context exactly?
I'll try to explain as much as I know, correct me if I'm wrong.
I'll explain using following snippet:
<template name="posts">
{{#each posts}}
<p>{{name}}</p>
{{/each}}
</template>
Let's assume it will display all the posts names from a blog:
First Post
Second post
Third post
..........
..........
I assume you know the concept of helpers and events.
In the above snippet, in general for {{name}}, meteor searches for the helper called name in helpers:
Template.posts.helpers({
name: function(){
return "dummy text";
}
});
If it finds any, it runs that helpers and displays the value.
So here, it outputs:
dummy text
dummy text
dummy text
...........
But if it doesn't find any helpers, it will search in data context.
Let's assume for posts we're returning some data:
Template.posts.helpers({
posts: function(){
return Posts.find().fetch();
}
});
The data we're sending to posts helper looks like this:
{name: "First post", _id: "xxx", ......},
{name: "Second post", _id: "yyy", ......}
{name: "Third post", _id: "zzz", ......}
.................
In the code for {{#each posts}}, it loops through every object and displays name property("First Post","Second Post,"Third Post").
It displays name property because it doesn't find any helper for name, and then it searches in the current data context and found property with the same name name and displays that.
Data context in helpers and events
Let's take the same snippet and add a delete button:
<template name="posts">
{{#each posts}}
<p>{{name}}</p>
<button>Delete Post</button>
{{/each}}
</template>
It displays like below:
First Post <Delete Post>
Second post <Delete Post>
Third post <Delete Post>
..........
..........
In the events:
Template.posts.events({
'click button': function(){
console.log(this)
Posts.remove({_id: this._id });
}
})
Here, when you click on any delete button, it will delete respective post.
Here we're using this._id: this means data context.
this will give you the data that the helper takes as input to display.
For example, if you click on the delete button beside First Post, then in the events it will give you following data as this:
{name: "First post", _id: "xxx", ......},
because that is the data context available when it displays that content.
Same if you click on the button beside second post:
{name: "Second post", _id: "yyy", ......},
And same goes with the helpers too.
I hope this helps at least someone out there.
This is not easy to explain. Like you I used it in tutorial without knowing it. After some research I found the best explanation, a visual one. You can have a look at Discover Meteor article about "Templates & Data Contexts". Hope it will clarify your mind about it.
A data context can be one of 3 things: (unless I've missed some)
A cursor, i.e. the result of a Collection.find()
An array of objects, i.e. just some array or the result of a Collection.find().fetch()
An individual object, i.e. { _id: "123", name: "Orthoclase E. Feldspar" }
{{#each foo}} loops over a cursor or array context and changes the context to an individual object. {{#with bar}} just says which helper to use (in this case bar) to set the data context.
During development, but especially while learning Meteor, it helps to have console.log(this) at the top of your helper code just to double check what the data context is. It is this.
I am wondering how to solve this problem:
I have a template which contains some text with some template helpers inside:
<template>Hello {{who}}, the wheather is {{weather}}</template>
Now I need to change the content of the template dynamically at runtime, while maintaining the helper functionality. For example I would need it like this:
<template>Oh, the {{weather}}. Good evening {{who}}</template>
The text changes and the helpers are needed at different positions. Think of an application where users can create custom forms with placeholders for certain variables like the name of the user who fills out the form. Basically, the content of the template is stored in a mongo document and needs to be turned into a template at runtime, or an existing template needs to be changed.
How to approach this? Can I change the contents of a template at runtime?
To solve this use case you need to use two techniques.
Firstly you need to be able to change the template reactivel. To do this you can use Template.dynamic. eg:
{{> Template.dynamic template=helperToReturnName [data=data] }}
See here: http://docs.meteor.com/#/full/template_dynamic
Now that you can change template, you need to be able to create new templates on the fly from you database content. This is non trivial, but it's possible if you're willing to write code to create them, like this:
Template.__define__("postList", (function() {
var view = this;
return [
HTML.Raw("<h1>Post List</h1>\n "),
HTML.UL("\n ", Blaze.Each(function() {
return Spacebars.call(view.lookup("posts"));
},
function() {
return [ "\n ", HTML.LI(Blaze.View(function() {
return Spacebars.mustache(view.lookup("title"));
})), "\n " ];
}), "\n ")
];
}));
That code snippet was taken from this article on Meteorhacks, and the article itself goes into far more detail. After reading the article you'll be armed with the knowledge you need to complete the task...
Just have a helper dynamically build the entire string (remembering that this refers to the current data context):
Template.foo.helpers({
dynamicString: function(switch){
if ( switch == 1) return "Hello "+this.who+", the wheather is "+this.weather;
else return "Oh, the "+this.weather+". Good evening "+this.who;
}
});
Then in your template:
<template name="foo">
{{dynamicString}}
</template>
Alternatively, just use {{#if variable}} or {{#unless variable}} blocks to change the logic in your template. Much, much simpler.
<template name="foo">
{{#if case1}}
Hello {{who}}, the wheather is {{weather}}
{{else}}
Oh, the {{weather}}. Good evening {{who}}
{{/if}}
</template>
You can always have a template helper that computes the necessary boolean variables (e.g. case1).
Ok so I'm working with meteor! Woo hoo I love it so far, but I've actually run into an architecture problem (or maybe its super simple and i just dont know it yet).
I have a list of names that belong to a user. And a delete button that is aligned next to the name
name - x
name - x
name - x
and I want a functionality to click the 'x', and then proceed to clearing the name from the database using the meteor event handler. I'm finding trouble thinking about how I'm going to pass the name along with the click to proceed to delete it from the database.
I can't use a unique id in the template to call a document.getElementById() (unless I came up with an integer system that followed the database.)
Does anyone have a good thought on this?
Here is a complete working example:
html
<body>
{{> userEdit}}
</body>
<template name="nameChoice">
<p>
<span>{{name}}</span>
x
</p>
</template>
<template name="userEdit">
{{#each nameChoices}}
{{> nameChoice name=this}}
{{/each}}
</template>
js
Users = new Meteor.Collection(null);
if (Meteor.isClient) {
Meteor.startup(function () {
Users.insert({nameChoices: ['foo', 'bar', 'baz']});
});
Template.userEdit.nameChoices = function () {
return Users.findOne() && Users.findOne().nameChoices;
};
Template.nameChoice.events({
'click .remove': function () {
_id = Users.findOne()._id;
Users.update(_id, {$pull: {'nameChoices': this.name}});
}
});
}
This actually does a bunch of stuff you wouldn't do in a real application (defined a client-only Users collection, assumes there is only one user, etc). But the main takeaway is that you can use the data context in each nameChoice template to respond to the remove event. This approach can nearly always replace the need for coming up with your own artificial id system. Feel free to ask questions if any of this is unclear.
If I have a collection of posts when entering a view of a posts collection, and each of these posts has a collection of comments on them, how could I list the top comment for each post along side of them?
E.g:
this.route('postsList', {
path: '/:posts',
waitOn: function() {
return Meteor.subscribe('posts');
},
data: function() {
return Posts.find({});
}
});
And then I'm iterating through the collection of posts on a page.
{{#each posts}}
{{> postItem}}
{{/each}}
<template name="postItem">
{{title}}
{{topComment}}
</template>
I'd like to put the top comment for each post item.
How can I do this with my templates/subscriptions/publications?
Posts and comments are separate collections.
If it were an embedded collection I could see the ease of use but how to deal with separate collections?
If I published a recent comment type of publication how could I subscribe to it for each post as the most recent one? Or am I thinking the wrong way here?
If you insist on having two totally separated collections, you would get into problems with efficient database queries. What you could do is to have something like recentComment field in your posts collection. Should this field point to id of the most recent comment related to the given post, you could alter your posts subscription to include the recent comments as well:
Meteor.publish('posts', function() {
var listOfIds = _.pluck(Posts.find({}, {fields: recentComment}).fetch(), 'recentComment');
return [
Posts.find(),
Comments.find({_id:{$in:listOfIds}})
];
});
Note that this solution is not fully reactive but it's good enough in most cases.
I'm trying to wrap my head around the meteor dependencies and reactive variables.
I have two select boxes. One lists a category (fruit, vegetables, poultry, etc) the second will list the sub category (apples, pears, grapes, etc).
I'd like when the user changes the category dropdown to display and populate the subcategory dropdown.
I know I can watch for Template.action_form.events ={'change #category'}... but I'm not sure what steps to take from here. One thought (hack) is to output all the subcategories to a multidimensional array and use jquery to manage it. I have to think there is a smarter way to to do this with meteor.
for the category dropdown I have something like this:
Template.action_form.category = function(id){
return Category.find();
}
I'm not sure how to setup the template for the subcategory...right now I have this (not working)
Template.action_form.subcategory = function(parent){
if (document.getElementById(parent)){
category = document.getElementById(parent).value;
return Subcategories.find({category_id:parent});
}
}
The HTML/Template looks like this:
<template name="action_form">
<select id="category" class="action-selects">
{{#each category _id}}
<option value="{{_id}}">{{name}}</option>
{{/each}}
</select>
<select id="subcategory" class="action-selects">
{{#each subcategory "category"}}
<option value="{{_id}}">{{name}}</option>
{{/each}}
</select>
<template>
Thanks in for any pointers you all can offer.
if you want to use the whole reactivity magic of meteor for this, you could set an Session variable if the first select changes.
Template.action_form.events = {
'change #category': function(evt) {
Session.set("selected_category", evt.currentTarget.value);
}
}
Your subscription of Subcategories passes the selected category as a parameter into the servers publish method.
// Client
Meteor.autosubscribe(function () {
Meteor.subscribe("subcategories",Session.get("selected_category"));
}
// Server
Meteor.publish("subcategories", function(selectedCategory) {
Subcategories.find({category_id: selectedCategory})
});
The template for subcategories than displays all Subcategories if finds.
Template.action_form.subcategory = function(parent){
Subcategories.find();
};
You could, of course, publish all Subcategories at once (no idea how many you'll have there) and filter the subcategories in the client, not in the subscribe/publish methods.
Template.action_form.subcategory = function(parent){
Subcategories.find({category_id: Session.get("selected_category")});
};