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")});
};
Related
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
I loaded several templates at startup,
Once the page is loaded the user can select several items that will define the content of another template. That's where I am stuck.
How after the call of my method and store the result in my session I can send it to the other Template.
I have looked at Deps.Dependency I sure it is very simple in fact.
Suppose on the first template you had a select element you wanted to save the result of :
<template name="foodSelect">
<select id="favorite-food"> <option> Taco </option> <option> Burrito </option> </select>
</template>
In the helper for that template you can check for an event on that type of input :
Template.foodSelect.events({
"change #favorite-food" : function(event, template) {
var input = $(event.target).val();
Session.set('favorite_food', input);
}
});
In your other template you can access this by using Session.get('favorite_food') and change the content accordingly.
I'm trying to compare two variables to see if they match.
This is to decide if I need a selected attr on an <option>.
The template looks like this:
<select>
<option disabled>Please choose...</option>
{{#each themes}}
<option {{selected}}>{{this.themeName}}</option>
{{/each}}
</select>
In the template helper I set a currentTheme var like so:
currentTheme: function() {
return this.theme;
}
The trouble is that this here is different to this within the #each loop above and placing {{currentTheme}} inside the #each renders nothing. Basically, I can't compare currentTheme with this.themeName to see if they are the same because one is always undefined :(
So... I'm wondering what I would have to do inside
selected: function() {
// ???
}
Many thanks!
As explained in this Discover Meteor blog post, since Meteor 0.8 you can pass the parent context as an argument to a template helper using the .. keyword.
<select>
<option disabled>Please choose...</option>
{{#each themes}}
<option {{selected ..}}>{{this.themeName}}</option>
{{/each}}
</select>
selected: function(parentContext) {
return this.themeName === parentContext.theme ? "selected" : '';
}
In this case, the currentTheme template helper would be unnecessary if you're using it just for this functionality.
You can use UI._parentData():
selected: function() {
// ???
var dataOutsideEach = UI._parentData(1);
if(currentTheme && this.theme) return "selected";
}
If that doesn't work, try removing the '1' and placing '0' or '2' instead (not quite sure about which). The integer represents the number of parent views to look for the data context through.
I have items with a category field and name field, e.g.:
{ category: 'CategoryOne', name: "ItemOne" },
{ category: 'CategoryOne', name: "ItemTwo" },
{ category: 'CategoryTwo', name: "ItemThree" },
... etc
What I would like to do is display these under a heading for the category.
I am new to meteor, and having quite the time doing two things:
(1) Getting a reactive list of categories, or
(2) Iterating through the items, displaying them grouped by category.
I'm not sure what is the correct Meteor approach here.
Unfortunately minimongo doesn't have support for aggregation yet so this is a bit difficult. The following is how I would approach it.
First create 2 template helpers. The first just puts together a list of the categories and returns an array of category names, The second takes the category name as a parameter and returns a cursor of all of the records in that category.
Template.categories.helpers({
categories: function(){
var added = [];
return Items.find().map(function (item) {
if(_(added).indexOf(item.category) === -1){
return item.category;
}
});
},
categoryItems: function(category){
return Items.find({category:category});
}
});
Next the template needs nested {{#each}} blocks with the first one iterating over the categories array and passing the category names to the next each as the parameter of the next helper.
<template name="categories">
{{#each categories}}
<h1>{{this}}</h1>
<ul>
{{#each items this}}
<li>{{name}}</li>
{{/each}}
</ul>
{{/each}}
</template>
I am new to Meteor and wondering how to solve what seems to me is a common problem.
Let's say I have a handlebars template listing restaurants:
<template name="Restaurants">
{{#each Restaurant}}
{{name}}
{{/each}}
</template>
Now when user clicks on a restaurant template I want to display a menu for that restaurant.
I added a subtemplate named "menuItems" that contains all menu items for a given restaurant:
<template name="Restaurants">
{{#each Restaurant}}
{{name}}
{{> menuItems}}
{{/each}}
</template>
I want to render only one instance of menuItems subtemplate when user clicks anywhere on Restaurant template (render only the menu items for the selected restaurant).
It should go something like:
Template.Restaurants.events({
'click' : function (e) {
// This is where I need help - what's the right way to display only one subtemplate instance?
}
});
My question is - how I can select and display only the correct menuItems template instance?
Also I would like to place menuItems template instance in DOM only after the click and not before (having all the menu items for all restaurants and only hiding those divs is not an option because of high number of those items in db).
If you think I should approach the solution in some other way please let me know, thanks!
You should use {{#if}} and Session. Like this:
<template name="Restaurants">
{{#each Restaurant}}
{{name}}
{{#if restaurantSelected}}
{{> menuItems}}
{{/if}}
{{/each}}
</template>
By using Session, a reactive data source, you can set a global flag indicating whether a restaurant is selected.
Template.Restaurants.restaurantSelected = function() {
// check whether this restaurant is selected. "this" refers to the current
// context, eg. the current restaurant in the loop
return Session.equals("restaurantSelected", this._id);
}
Whenever you change that session key, the value will update and the template will be redrawn. So, you can toggle it when clicking a restaurant:
Template.Restaurants.events({
'click' : function (e) {
// store the current restaurant ID
// make sure the event selector is correct!
Session.set("restaurantSelected", this._id);
}
});
Edit For clarity's sake I created a complete example that you can copy into your project and try out.
I almost always avoid Session. I think it pollutes the global scope. Also it prevents you from running multiple instances of the template. I recommend using a reactiveVar or reactiveDict scoped to the template instance. Thanks to Rahul for starting a demo project. I took his example and modified it to show my recommended approach.
attach a reactiveDict to the template instance onCreate. Use this to store state instead of global Session var!
Template.Restaurants.onCreated(function() {
this.state = new ReactiveDict;
this.state.set('currentRestaurant', null); // could set a init value here
});
this event handler will set the state of the reactiveDict on click
'click': function(e, t) {
t.state.set('currentRestaurant', this._id);
}
this helper is used to show/hide the menu template
currentRestaurant: function() {
// check whether this restaurant is selected. "this" refers to the current
// context, eg. the current restaurant in the loop
return Template.instance().state.equals("currentRestaurant", this._id);
},
menu template receives the selected id from data context instead of from Session
<template name="Restaurants">
<ul>
{{#each Restaurant}}
<li>
{{name}}
{{#if currentRestaurant}}
{{> menuItems restaurant=_id}}
{{/if}}
</li>
{{/each}}
</ul>
</template>
<template name="menuItems">
<ul>
<li class="menu">I'm a menu for {{restaurantName}}!</li>
</ul>
</template>
added this helper just to show we really got the id
Template.menuItems.helpers({
restaurantName: function() {
var restaurantMenu = Restaurants.findOne(this.restaurant);
return restaurantMenu.name;
},
})
Posted a fully working project to github.
https://github.com/white-rabbit-japan/scopedReactivityDemo
App is hosted on meteor.com
http://scopedreactitivydemo.meteor.com/