Meteor: Render a template to the DOM on click - javascript

I am having a seemingly simple problem that I cannot really find a solution to. I have 2 columns: One with an overview of different tasks, and one with an area where detailed information about a task should be displayed when the "More information" button attached to each task is clicked. My logic is:
Have 2 templates: task_short and task_long
When the button in task_short is clicked use Blaze.render to render task_long to a div in the second column.
when "More information" is clicked on another task_short, use Blaze.remove to remove the view.
My main problem is: How do I tell Meteor which task should be render in task_long? task_short gets its {{content}},{{name}} etc parameters through the each tasks loop. But how do I do it with a single task? Also, I don't really understand Blaze.remove. Where do I get the ViewId from that needs to be passed in?
I am insanely grateful for any help!

This can be solved with a session variable and some conditionals in your template. You shouldn't need to use Blaze.render/Blaze.remove unless you are doing something really fancy. I don't know exactly how your template is structured, but this example should give you and idea of what to do:
app.html
<body>
{{#each tasks}}
{{> task}}
{{/each}}
</body>
<template name="task">
<div class='short'>
<p>Here are the short instructions</p>
<button>More information</button>
</div>
{{#if isShowingLong}}
<div class='long'>
<p>Here are the long instructions</p>
</div>
{{/if}}
<hr>
</template>
app.js
if (Meteor.isClient) {
Template.body.helpers({
tasks: function () {
// return some fake data
return [{_id: '1'}, {_id: '2'}, {_id: '3'}];
}
});
Template.task.helpers({
isShowingLong: function () {
return (this._id === Session.get('currentTask'));
}
});
Template.task.events({
'click button': function () {
Session.set('currentTask', this._id);
}
});
}

Related

Vue on:click Seems to Call All Methods

I'm currently in the process of learning Vue, and I ran into an issue that I'm hoping someone can help me with.
While using the v-on:click directive to call a method, all other instance methods are being called if the method is also used elsewhere.
HTML:
<div id="exercise">
<div><button class="test" v-on:click="newCheck()">New Challenge</button>
<p >Success check is: {{ passCheck }}</p>
</div>
<div>
<button class="roll" v-on:click="roll20()">Roll!</button>
<p>You Rolled a {{ roll20() }}</p>
</div>
</div>
JS:
new Vue({
el: '#exercise',
data: {
yourRoll: '10',
passCheck: '10',
},
methods: {
roll20: function(){
this.yourRoll = Math.round(Math.random()*19)+1;
return this.yourRoll;
},
newCheck: function(){
this.passCheck = Math.round(Math.random()*19)+1;
}
}
});
When {{ roll20() }} is used in the second paragraph, clicking the 'New Challenge' button runs both roll20() and newCheck(). However, if {{ yourRoll }} is used in the second paragraph instead, this doesn't happen.
In both instances, clicking 'Roll!' only runs roll20().
Can someone help explain what is happening here?
Here is a codepen of the issue:
Codepen of code
Note: I ended up bypassing the issue by using a different approach, but I would still like to know why this was happening: Working Approach
Whenever the DOM is updated, it will run roll20 again, because of the line:
<p>You Rolled a {{ roll20() }}</p>
So anything that triggers an update will trigger roll20 by consequence.
Now, because of the template:
<div><button class="test" v-on:click="newCheck()">New Challenge</button>
We know that when you hit the New Challenge, it calls the newCheck method.
And because the newCheck method changes a variable (passCheck) that is used in the template:
newCheck: function(){
this.passCheck = Math.round(Math.random()*19)+1;
}
That is used here:
<p>Success check is: {{ passCheck }}</p>
Changing passCheck will trigger a DOM update. And DOM updates will call roll20 automatically (because of the reason stated in the first paragraph of this answer).
Working around it:
The simplest way is just not to call roll20 in the template. And, as roll20 actually updates a yourRoll property:
roll20: function(){
this.yourRoll = Math.round(Math.random()*19)+1;
return this.yourRoll;
},
You could just use yourRoll in the template, instead of roll20():
<p>You Rolled a {{ yourRoll }}</p>
CodePen: https://codepen.io/acdcjunior/pen/PePBeo
<div id="exercise">
<div><button class="test" v-on:click="newCheck()">New Challenge</button>
<p >Success check is: {{ passCheck }}</p>
<!-- 3) Call a function to output a random float between 0 and 1 (Math.random()) -->
</div>
<div>
<button class="roll" v-on:click="roll20()">Roll!</button>
<p>You Rolled a {{ yourRoll }}</p> <!-- this changed -->
</div>
</div>
new Vue({
el: '#exercise',
data: {
yourRoll: '10',
passCheck: '10',
},
methods: {
roll20: function(){
this.yourRoll = Math.round(Math.random()*19)+1;
},
newCheck: function(){
this.passCheck = Math.round(Math.random()*19)+1;
}
}
});
Try this it workes fine!Just output your data property because in one your was calling the method! See it in action

How to pass a variable to a helper and display its in the template?

I'd like to collect a variable(user _id)collected from a template, and pass it to another template using session. Then I want to display this variable.
Actually it seems to work in the collection of the variable and the pass to the other template, but I'm not able to display the user's info in the second template...
This is my code:
HTML
<template name="main"><!--Template 1-->
<table>
<tr>
<th>Patient</th>
</tr>
{{#each allUsers}}
<tr>
<th><label><input type="radio" class="selected" name="patient" value="{{this._id}}"><i class="fa fa-user"></i> {{this.profile.lastName}} {{this.profile.firstName}}</label></th>
</tr>
{{/each}}
</table>
{{>test}}
</template>
<template name="test"> <!--Template 2-->
<p>Name is <button class="test" name="patient" value="{{this._id}}">Test</button></p>
<div name="show">Name: {{this.profile.firstName}}</div>
</template>
JS
Template.main.events({
'click .selected': function (){
var selPat = $('input[name="patient"]:checked').val();
Session.set("selPat",selPat);
console.log("collect", selPat);
}
});
Template.test.events({
'click .test': function(){
var PAT= Meteor.users.findOne({ _id: Session.get("selPat")});
console.log("pass", PAT);
return PAT;
}
});
Template.patients.helpers({
allUsers: function() {
return Meteor.users.find({});
}
});
I want to display in the template 2 the first name of the user selected in the template 1 with {{this.profile.firstName}}
I believe this is what you are doing:
You are choosing patient's id from a list of patients via the radio buttons in the main template. [this implementation is correct]
You are setting the patient id in a session in the main template's events. [this implementation is correct]
When you click the "test" button in the test template, it should reveal the user's first name in the div below the button. [...not quite]
You are unable to display anything in <div name="show">Name: {{this.profile.firstName}}</div> because you don't have a relevant helper supplying the template with that information.
Although clicking a button to reveal the patient's firstName in the test template sounds a bit redundant, I'm sure you have some reason to do it in that manner.
I propose that you wrap the div inside an if block. The if condition renders true, when the button is clicked, and hence the div element is shown.
<template name="test"> <!--Template 2-->
<p>Name is <button class="test" name="patient" value="{{this._id}}">Test</button></p>
{{#if isButtonClicked}}
<div name="show">Name: {{data.profile.firstName}}</div>
{{/if}}
</template>
Your helpers and events will be like so:
Template.test.events({
'click .test': function(){
// set this session to true when the button has been clicked.
Session.set("testClicked", true);
}
});
Template.test.helpers({
isButtonClicked: function(){
// return the if block's validation.
return Session.get("testClicked");
},
data: function(){
// return the actual user data retrieved from the collection.
var PAT= Meteor.users.findOne({ _id: Session.get("selPat")});
console.log("pass", PAT);
return PAT;
});
Note:
You might want to make sure that the div does not stay open when you select a different patient from the list of radio buttons. Not doing so will make the div be visible when you first click the button, and remain open until you refresh the page, even when you select a different patient.
You could set testClicked to false or undefined in Template.main.events --> 'click .selected'

How to access functions of a controller from within another controller via scope?

I have the following problem, I want to call a function of another controller from within a controller I want to use for a guided tour (I'm using ngJoyRide for the tour). The function I want to call in the other controller is so to say a translator (LanguageController), which fetches a string from a database according to the key given as parameter. The LanguageController will, if the key is not found, return an error that the string could not be fetched from the database. In my index.html fetching the string works, but I want to use it in the overlay element of my guided tour, which does not work, but only shows the "not fetched yet"-error of the LanguageController.
My index.html looks like this:
<body>
<div class="container-fluid col-md-10 col-md-offset-1" ng-controller="LangCtrl as lc" >
<div ng-controller="UserCtrl as uc" mail='#email' firstname='#firstname'>
<div ng-controller="GuidedTourCtrl as gtc">
<div ng-joy-ride="startJoyRide" config="config" on-finish="onFinish()" on-skip="onFinish()">
...
{{lc.getTerm('system_lang_edit')}}
...
</div>
</div>
</div>
</div>
</body>
The controller I'm using for the guided Tour looks like this:
guidedTourModule.controller('GuidedTourCtrl',['$scope', function($scope) {
$scope.startJoyRide = false;
this.start = function () {
$scope.startJoyRide = true;
}
$scope.config = [
{
type: "title",
...
},
{
type: "element",
selector: "#groups",
heading: "heading",
text: " <div id='title-text' class='col-md-12'>\
<span class='main-text'>"\
+ $scope.lc.getTerm('system_navi_messages') + "\
text text text text\
</span>\
<br/>\
<br/>\
</div>",
placement: "right",
scroll: true,
attachToBody: true
}
];
...
}]);
And the output I ultimately get looks like this for the overlay element:
<div class="row">
<div id="pop-over-text" class="col-md-12">
<div id='title-text' class='col-md-12'>
<span class='main-text'>
not fetched yet: system_navi_messages
text text text text
</span>
<br/>
<br/>
</div>
</div>
</div>
...
I hope someone can see the error in my code. Thanks in advance!
Things needs clarity are,
How you defined the 'getTerm' function in your Language controller, either by using this.getTerm() or $scope.getTerm(). Since you are using alias name you will be having this.getTerm in Language controller.
Reason why you are able to access the getTerm function in your overlay element is, since this overlay element is inside the parent controller(Language Controller) and you are referencing it with alias name 'lc' while calling the getTerm function. Thats' why it is accessible.
But the string you pass as a parameter is not reachable to the parent controller. that's why the error message is rendered in the overlay HTML.
Please make a plunker of your app, so that will be helpful to answer your problem.

How Do I Update A Button's Text in Meteor's Leaderboard Example?

I'm completely new to Meteor, and I was doing the leaderboard example. I have a little problem with my code.
I was trying to add a toggle button to toggle sorting. The toggling and all is working fine, but the button's text doesn't update.
My javascript code:
if (Meteor.isClient) {
Meteor.startup(function () {
Session.set("sortMethod", "score");
});
...
Template.leaderboard.players = function () {
if (Session.equals("sortMethod", "name"))
return Players.find({}, {sort: {name: 1}});
else if(Session.equals("sortMethod", "score"))
return Players.find({}, {sort: {score: 1}});
};
...
Template.leaderboard.sortMethod = function () {
return Session.get("sortMethod");
}
...
Template.leaderboard.events({
'click input.sortToggle': function () {
Session.equals("sortMethod", "name") ? Session.set("sortMethod", "score") : Session.set("sortMethod", "name");
}
});
}
My handlebars template:
<template name="leaderboard">
<!-- this is where it goes wrong, the button text doesn't update at {{sortMethod}} -->
<input type="button" class="sortToggle" value="sort by {{sortMethod}}">
<!-- -->
<div class="leaderboard">
{{#each players}}
{{> player}}
{{/each}}
</div>
{{#if selected_name}}
<div class="details">
<div class="name">{{selected_name}}</div>
<input type="button" class="inc" value="Give some points" />
</div>
{{else}}
<div class="none">Click a player to select</div>
{{/if}}
</template>
Note: I removed some irrelevant code.
It works if you use a button instead:
<button class="sortToggle">sort by {{sortMethod}}</button>
With the corresponding change to events:
'click .sortToggle': function () { ...
Changing the value of an input was reported as an issue in the past, but it was closed. Perhaps it needs to be reopened.
I am not sure if this is a bug or a feature. I think the problem stems from trying to update an input element that has focus. So the fix is to blur() the element on the end of your event handler. Like this:
'click input.sortToggle': function ( event ) {
Session.equals("sortMethod", "name")
? Session.set("sortMethod", "score")
: Session.set("sortMethod", "name");
$( event.currentTarget ).blur();
}

Computed.alias not updating bind-attr

I recently started using Ember.js. In my small application I currently have problems regarding Ember.computed.alias, because an {{#if}}-section is updated properly, but the bind-attr helper in the same template is not updated accordingly.
The application controller and the action influencing the value look as follows:
App.ApplicationController = Ember.ObjectController.extend({
isEditing: false,
actions: {
toggleEdit: function() {
var a = this.get('isEditing');
this.set('isEditing', !a);
}
}
});
The controller taking care of the template causing problems:
App.CategoriesController = Ember.ArrayController.extend({
needs: ['application'],
isEditing: Ember.computed.alias('controllers.application.isEditing'),
general: function() { // example depending on the alias
var result = this.filterBy('type', 1);
if (!this.get('isEditing')) {
result = result.filterBy('isHidden', false);
}
return result;
}.property('#each.type', '#each.isHidden', 'isEditing'),
// ......
The related template:
<ul id="categories">
{{#if isEditing}}YES!{{else}}NO!{{/if}}
{{#each general}}
<li {{bind-attr class=":general isEditing:editing"}}>
{{name}}
</li>
{{/each}}
</ul>
When the action toggleEdit is triggered, the {{#if}} section is updated and swaps between YES! and NO!, but the editing class is not applied to the list element. I tried encapsulated the alias into another property of the controller depending on the alias, but without success.
I assume it's a beginners mistake, but I can't figure out what I am overlooking.
Thanking you in anticipation.
isEditing is no longer in scope, use controller.isEditing, sorry phone response
Here's an example that would keep it in scope, but I'm fully qualifying it just to show you.
{{#each item in general}}
<li {{bind-attr class=":general controller.isEditing:editing"}}>
{{item.name}}
</li>
{{/each}}

Categories

Resources