The code below should insert the selected item value in the Tasks collection, retain the info for later use, and a headerLabel should show the task selected. I am not able to get the headerLabel to show the task when the click .menuItem function runs. Thanks
Tasks = new Mongo.Collection('tasks');
Template.mainMenu.events({
'click .menuItem': function(event){
Tasks.insert({menuItem: $(event.currentTarget).data('value')});
}
});
Template.header.helpers({
headerLabel: function( id ){
return Tasks.findOne({_id: id}).menuItem;
},
tasks: function(){
return Tasks.find();
}
});
<template name="mainMenu">
<div class="container">
<div class="row">
<section class="col-xs-12">
<div class="list-group">
{{#each menuItems}}
<a href="#" class="list-group-item menuItem" data-value={{menuItem}}>
<img src="/abc.png">
{{menuItem}} <span class="badge">></span>
</a>
{{/each}}
</div>
</section>
</div>
<template name="header">
<h1><button class="col-xs-2 mainMenu" type="button">☰</button></h1>
<h3>
<label class="col-xs-8 text-center">
{{#if headerLabel}} {{headerLabel}} {{else}} Select an item {{/if}}
</label>
</h3>
<h1><button class="col-xs-2" type="button">⋮</button></h1>
</template>
Assuming that the click can happen multiple times, you'll need to pass an _id for the appropriate task into your helper:
Tasks = new Mongo.Collection('tasks');
Template.mainMenu.events({
'click .menuItem': function(event){
Tasks.insert({menuItem: $(event.currentTarget).data('value')});
}
});
Template.header.helpers({
headerLabel: function( id ){
var task = Tasks.findOne({_id: id});
if( task ) {
return task.menuItem;
}
},
tasks: function() {
return Tasks.find();
}
});
So what I'm doing there is saying find one task's menuItem that has the ID passed to the helper. I also added a tasks helper to get all tasks. So your template might look something like:
<template name="mainMenu">
{{#each task in tasks}}
<h4>{{headerLabel task._id}}</h4>
<!-- Additional code here... -->
{{/each}}
</template>
You'll obviously need to customize the HTML to your specific situation, but that should give you the gist. Hope that helps!
Related
This Meteor client Template.payment.onRendered gets fired twice, which calls the server side method twice, How can I only get it to fire the backend method once if it must do the rendering twice, else how can it get it to render only once? thx
//client
Template.mainMenu.events({
'click .menuItem': (event) => {
let menuShortName = event.currentTarget.dataset.template;
Session.set('taskSelected', menuShortName);
Meteor.call('mainAction', menuShortName);
}
});
Template.index.helpers({
'taskInputs': function () {
let task = Session.get('taskSelected');
let tasks = task ? task.split(',') : [];
let data = DisplayCol.find({action: {$in: tasks}}, {sort: {createdAt: 1}}).fetch();
return {items: data};
}
});
//server
'mainAction': function (menuShortName) {
DisplayCol.remove({userId: this.userId});
lib.displayMakeUp({'action': menuShortName});
},
'displayMakeUp': (doc) => {
for (let i = 0; i < attrItems.length; i++) {
if (attrItems[i].action === doc.action || attrItems[i].action.indexOf(doc.action + '_') >= 0) {
let group = {};
group = attrItems[i];
group.userId = Meteor.userId();
console.log(JSON.stringify(group));
DisplayCol.insert(group);
}
}
},
Template.payment.onRendered(function () {
Meteor.call('getClientToken', function (error, clientToken) {
if (error) {
console.log(error);
} else {
braintree.setup(clientToken, "dropin", {
container: "payment-form", // Injecting into <div id="payment-form"></div>
onPaymentMethodReceived: function (response) {
var nonce = response.nonce;
console.log(nonce);
}
});
}
});
});
Templates:
<body>
{{> header}}
{{#if currentUser}}
{{#if isVerified}}
{{> index}} <-------------------------- (1)
{{else}}
<br><br><br><br>
<p>Check your email for your verification link!</p>
{{/if}}
{{else}}
{{> terms}}
{{/if}}
</body>
<template name="index">
<div id="main">
{{#if (display 'mainMenu')}}
{{> mainMenu}}
{{else}} {{#if (display 'content')}}
{{> Template.dynamic template="content" data=taskInputs}} <------------------- (2)
{{#if (session 'showFooter')}}
{{> footer}}
{{/if}}
{{/if}}{{/if}}
</div>
</template>
<template name="content">
{{> subMenu}}
<div id="main">
<div id="content">
<form>
<button type="submit" style="display:none"></button>
{{#if Template.subscriptionsReady}}
{{#each this.items}}
{{> sub}} <---------------------------------- (3)
{{/each}}
{{/if}}
</form>
</div>
</div>
</template>
<template name="sub">
{{#if isEqual element "payment"}}
{{> payment}} <--------------------------------------- (4)
{{/if}}
</template>
<template name="payment">
<form role="form">
<div class="row">
<div class="col-md-6 col-xs-12">
<br>
<div id="payment-form"></div>
<button type="submit" class="btn btn-success">Submit</button>
</div>
</div>
</form>
</template>
Based on what I see, it looks like you are rendering the template inside an each block. This will render one instance of your template for each each iteration.
{{#each this.items}}
{{> sub}} <---------------------------------- (3)
{{/each}}
Keep in mind that each template instance will fire all its callbacks (including onRendered). If you only want one instance then take it out of the each
Try using this package to limit rendering. You can also yield sub to payment (instead of checking the element) try checking if this is the first run inside your route.
//html
{{> yield sub}}
//router
if (Tracker.currentComputation.firstRun) {
this.render("payment",{to:"sub"});
}
Im creating a an instant messenger app and im having a little trouble routing it. So, once you go into the app. There is a list of available users. You can click on a user and start chatting. The issue I have is once I click send, the console show an Uncaught TypeError: Cannot read property 'value' of undefined. Im not sure what im doing wrong here. Also I need help show the chat messages sent above. As if you can see the recent and previous messages. Here are my codes. Any examples and helps would be great.
HTML
minstant
<body>
</body>
<!-- this is the main template used by iron:router to build the page -->
<template name="ApplicationLayout">
{{> yield "header"}}
<div class="container">
{{> yield "main"}}
</div>
</template>
<!-- top level template for the nav bar -->
<template name="navbar">
<nav class="navbar navbar-default">
<div class="container-fluid">
<div class="navbar-header">
<a class="navbar-brand" href="/">
Minstant!
</a>
</div>
<div class="nav navbar-nav">
{{> loginButtons}}
</div>
</div>
</nav>
</template>
<!-- Top level template for the lobby page -->
<template name="lobby_page">
{{> available_user_list}}
</template>
<!-- display a list of users -->
<template name="available_user_list">
<h2>Choose someone to chat with:</h2>
<div class="row">
{{#each users}}
{{> available_user}}
{{/each}}
</div>
</template>
<!-- display an individual user -->
<template name="available_user">
<div class="col-md-2">
<div class="user_avatar">
{{#if isMyUser _id}}
<div class="user_avatar">{{getUsername _id}} (YOU)
<br/>
<img src="/{{profile.avatar}}" class="avatar_img">
</div>
{{else}}
<a href="/chat/{{_id}}">
{{getUsername _id}}
<br/>
<img src="/{{profile.avatar}}" class="avatar_img">
</a>
{{/if}}
</div>
</div>
</template>
<!-- Top level template for the chat page -->
<template name="chat_page">
<h2>Type in the box below to send a message!</h2>
<div class="row">
<div class="col-md-12">
<div class="well well-lg">
{{#each recentMessages}}
{{> message}}
{{/each}}
</div>
</div>
</div>
<div class="row">
<div class="col-md-12">
<form class="new-message">
<input class="input" type="text" name="chat" placeholder="type a message here...">
<button class="btn btn-default">Send</button>
</form>
</div>
</div>
</template>
<!-- simple template that displays a message -->
<template name="message">
<div class = "container">
<div class = "row">
<div class = "username">{{username}}
<img src="/{{profile.avatar}}" class="avatar_img">
</div>
<div class = "message-text"> said: {{messageText}}</div>
</div>
</div>
</template>
Here is my JS
Messages = new Mongo.Collection("messages");
if (Meteor.isClient) {
Meteor.subscribe("messages");
Meteor.subscribe("userStatus");
// set up the main template the the router will use to build pages
Router.configure({
layoutTemplate: 'ApplicationLayout'
});
// specify the top level route, the page users see when they arrive at the site
Router.route('/', function () {
console.log("rendering root /");
this.render("navbar", {to:"header"});
this.render("lobby_page", {to:"main"});
});
// specify a route that allows the current user to chat to another users
Router.route('/chat/:_id', function () {
this.render("navbar", {to:"header"});
this.render("chat_page", {to:"main"});
});
///
// helper functions
///
Template.available_user_list.helpers({
users:function(){
return Meteor.users.find();
}
})
Template.available_user.helpers({
getUsername:function(userId){
user = Meteor.users.findOne({_id:userId});
return user.profile.username;
},
isMyUser:function(userId){
if (userId == Meteor.userId()){
return true;
}
else {
return false;
}
}
})
Template.chat_page.helpers({
recentMessages: function () {
return Messages.find({}, {sort: {createdAt: 1}});
return Meteor.users.find();
},
});
Template.chat_page.events({
// this event fires when the user sends a message on the chat page
'submit .new-message':function(event){
event.preventDefault();
var text= event.target.text.value;
// stop the form from triggering a page reload
event.target.text.value = "";
// see if we can find a chat object in the database
// to which we'll add the message
Meteor.call("SendMessage", text);
},
});
};
Meteor.methods({
sendMessage: function (messageText) {
if (! Meteor.userId()) {
throw new Meteor.Error("not-authorized");
}
Messages.insert({
messageText: messageText,
createdAt: new Date(),
username: Meteor.user().username
});
}
});
// start up script that creates some users for testing
// users have the username 'user1#test.com' .. 'user8#test.com'
// and the password test123
if (Meteor.isServer) {
Meteor.startup(function () {
if (!Meteor.users.findOne()){
for (var i=1;i<9;i++){
var email = "user"+i+"#test.com";
var username = "user"+i;
var avatar = "ava"+i+".png"
console.log("creating a user with password 'test123' and username/ email: "+email);
Meteor.users.insert({profile:{username:username, avatar:avatar}, emails: [{address:email}],services:{ password:{"bcrypt" : "$2a$10$I3erQ084OiyILTv8ybtQ4ON6wusgPbMZ6.P33zzSDei.BbDL.Q4EO"}}});
}
}
},
),
Meteor.publish("messages", function () {
return Messages.find();
});
Meteor.publish("userStatus", function() {
return Meteor.users.find({ "status.online": true });
});
};
The error is with your form submit code. In the console you can see the error is in Template.chat_page.events.submit .new-message on line 73. That will take you right to the error code:
'submit .new-message':function(event){
event.preventDefault();
var text = event.target.text.value;
// stop the form from triggering a page reload
event.target.text.value = "";
}
event.target.text.value doesn't exist. event.target does, but there is no field for text. There is textContent, and you can access the children of the target (which in this case is the form). Put in a console.log(event); and figure out what you are trying to access within the javascript console and then use that to determine what your code should look like. This might work for you:
'submit .new-message':function(event){
event.preventDefault();
var text = event.target.elements.chat.value;
// stop the form from triggering a page reload
event.target.text.value = "";
}
event.target.elements.chat.value comes from the name field of the <input>.
I'm having a problem to insert a template after and not before a node. For example:
//Html looks like this
<div class="questions">
<div class="question"></div>
<div class="question"></div>
<div class="question"></div>
</div>
<template name="question">
<div class="question"></div>
</div>
<template name="questionExtraInfo">
<div class="extra"></div>
</template>
I'm trying to get the following:
<div class="questions">
<div class="question"></div>
<div class="extra"></div>
<div class="question"></div>
<div class="question"></div>
</div>
Calling blaze render inside question event
Template.question.events({
'click .more-details': function () {
var instance = Template.instance();
Blaze.renderWithData(Template.questionExtraInfo, {}, document.querySelector('.questions'), instance.find('.question')));
});
I can only figure out how render it before or inside how about after?
<div class="extra"></div>
<div class="question"></div>
<div class="question"><div class="extra"></div></div>
I think a better approach would be to take advantage of reactivity:
Change your questions template to:
<template name="question">
<div class="question"></div>
{{# if shouldIncludeExtra }}
{{> questionExtraInfo }}
{{/if}}
</template>
The above template should be inside an each loop.
Then in your js something like:
Template.question.helpers({
'shouldIncludeExtra': function() {
// replace 'n' with the actual index. I think `this.index` is
// provided within #each blocks, or you can use the new `#each` helper.
var index = n;
return Session.get('shouldIncludeExtra' + index);
}
});
Then, in your click event, you set a session var based on the index to true:
Template.questions.events({
'click .question': function(e, tpl) {
var question = e.currentTarget;
// You can probably come up with something better here..
var index = $(question).parent().find('> .question').index(question);
Session.set('shouldIncludeExtra' + index, true);
}
});
Because of reactivity, you would see the inserts right away when you fire the click event.
I realize this doesn't really answer the headline of your question, but it should get you the desired outcome.
In my Mongo database, I have products and each of these products have images.
I would like to know how I can call each image in put them in a Meteor template. I know things like {{#each}}{{/each}} exist, but I don't know how to capture the images object.
With this code...
Template.products.helpers({
product: function () {
return ProductList.find();
}
});
I am able to call the first image like:
<img src="{{images.[0]}}" alt="{{title}}"/>
I could hardcode the rest of the images, but I would like to do this dynamically. Any tips?
EDIT: Here is the expected HTML output (the extra classes on .content__product are added dynamically via a separate function):
<section class="content__products">
<figure class="content__product selected">
<img src="/images/product/tt-shirt-01.jpg" alt="Time Travel Tee">
</figure>
<figure class="content__product move-down">
<img src="/images/product/tt-shirt-02.jpg" alt="Time Travel Tee">
</figure>
<figure class="content__product">
<img src="/images/product/tt-shirt-03.jpg" alt="Time Travel Tee">
</figure>
</section>
each works for both cursors and arrays, so you can do something like this:
{{#each products}}
<h2>{{title}}</h2>
{{#each images}}
<img src={{this}} alt={{../title}}>
{{/each}}
{{/each}}
Recommended reading: A Guide to Meteor Templates & Data Contexts
{{#each}} only accepted array or cursor of Mongo collection. So you have to change your object to array and make another template.
This is sample code for you.
if(Meteor.isClient){
Template.product.helpers({
list : function(){
return Products.find({});
}
});
Template.productImg.helpers({
imgList: function(){
var imgs = [];
_.each(this.images, function(img){
imgs.push({imgPath: img});
});
return imgs;
}
});
}
<template name="product">
{{#each list}}
{{> productImg}}
{{/each}}
</template>
<template name="productImg">
{{#each imgList}}
<img src="{{imgPath}}" alt="{{../title}}"/>
{{/each}}
</template>
For an individual product page I would do it like this:
Template.products.helpers({
'products': function() {
return ProductList.find();
}
});
Multiple images per product:
<section class="content__products">
{{#each products}}
<figure class="content__product">
{{#each images}}
<img src="{{this}}" alt="{{../title}}">
{{/each}}
</figure>
{{/each}}
</section>
Single image per product:
<section class="content__products">
{{#each products}}
<figure class="content__product">
<img src="{{images.[0]}}" alt="{{../title}}">
</figure>
{{/each}}
</section>
Okay, so the code posted here helped me out, but didn't solve my issue out of the box. Here is how I changed my code:
if (Meteor.isClient) {
Template.products.helpers({
product: function () {
return ProductList.find();
}
});
Template.product.helpers({
imgList: function () {
var imgs = [];
_.each(this.images, function (img) {
imgs.push({imgPath: img});
});
return imgs;
}
});
}
And, my template:
<template name="product">
<section class="content__products">
{{#each imgList}}
<figure class="content__product">
<img src="/{{imgPath}}" alt="{{../title}}"/>
</figure>
{{/each}}
</section>
</template>
According to the docs.meteor, in the body of the "Template.myTemplate.rendered" callback, "this" is a template instance object.
However, when I insert a "debugger" line in the callback and use the browser dev tools to check, the value of "this" is "window". Am I doing something wrong?
I'm using the Leaderboard example - here's the handlebars template:
<template name="leaderboard">
{{#each players}}
{{> player}}
{{/each}}
{{#if selected_name}}
<div class="details">
<div class="name">{{selected_name}}</div>
<input type="button" class="inc" value="Give 5 points" />
<input type="button" class="fastclick inc" value="Give 5 points - fast" />
</div>
{{/if}}
{{#unless selected_name}}
<div class="none">Click a player to select</div>
{{/unless}}
</template>
<template name="player">
<div class="player {{selected}} fastclick">
<span class="name">{{name}}</span>
<span class="score">{{score}}</span>
</div>
</template>
And the "Template.leaderboard.rendered" callback:
Template.leaderboard.rendered = function (){
Meteor.defer(function() {
debugger;
new FastClick(document.body);
console.log("Template.leaderboard.rendered: " + JSON.stringify(this));
});
}
}
I think your problem is that this is inside the Meteor.defer callback, which means that the context of this has changed. Try caching this in a variable, and then outputting that variable in console.log(). For example:
Template.leaderboard.rendered = function (){
var self = this;
Meteor.defer(function() {
debugger;
new FastClick(document.body);
console.log("Template.leaderboard.rendered: " + self );
});
}
}
In the body of the callback, this is a template instance object that is unique to this occurrence of the template and persists across re-renderings. Use the created and destroyed callbacks to perform initialization or clean-up on the object.
Source: http://docs.meteor.com/#template_rendered
So this references the particular instance of your template that you are rendering.