How to control recomputation order of helpers - meteor

Why does the helper calcFoo rerun even though the element in the the array is not present anymore after you click on div? I guess because the calcFoo-helper depends on fooRD and gets rerun before the foo-helper gets rerun. Is there a way to control the recomputation order, so that foo is recomputed first and calcFoo does not need to rerun anymore (for elements that are already deleted)?
foo.js
if (Meteor.isClient) {
Template.foo.onCreated(function() {
this.fooRD = new ReactiveDict()
this.fooRD.set('field', [1,2])
})
Template.foo.helpers({
foo: function() {
ti = Template.instance()
return ti.fooRD.get('field')
},
calcFoo: function(v) {
ti = Template.instance()
// establish dependency
ti.fooRD.get('field')
console.log("run: " + v)
return v * 2
}
})
Template.foo.events({
'click': function (e, t) {
ti = Template.instance()
field = ti.fooRD.get('field')
field.pop()
ti.fooRD.set('field', field)
}
})
}
foo.html
<body>
{{> foo}}
</body>
<template name="foo">
{{#each foo}}
<div class="foo">{{calcFoo this}}</div>
{{/each}}
</template>
My workaround so far (which does work in this simple case, but not in more complex scenarios):
calcFoo: function(v) {
ti = Template.instance()
field = ti.fooRD.get('field')
if (field.indexOf(v) != -1)
console.log("run: " + v)
return v * 2
}

With this new helper, calcFoo is not anymore called :
Template.foo.helpers({
hasFoo: function(v) {
ti = Template.instance()
return ti.fooRD.get('field').length > 0;
}
})
<template name="foo">
{{#if hasFoo}}
{{#each foo}}
<div class="foo">{{calcFoo this}}</div>
{{/each}}
{{/if}}
</template>

Related

inject div element between handlebars each

I want to place a div after fourth element of #each
<script type="text/template" id="template">
{{#each names}}
{{name}}
{{/each}}
</script>
If there are no 4 elements then in last. How can I do achieve this?
html
{{#each names}}
{{name}}
{{#if fourth #index count}}
<div></div>
{{/if}}
{{/each}}
js
Template.templateName.helpers(
{
count: function(){
//if you are using iron router to return names
return Router.current().data().names.find().count();
// or you could get length from a reactive variable or session
// or if you are returning names as a helper, then set the length there
},
fourth: function(i, count){
i = i+1;
return i%4===0 || count===i;
}
}
)

Reuse Meteor template as inclusion with different helpers

I'd like to re-use a Meteor template as an inclusion (i.e. using {{> }}) in two different contexts. I know I can pass in different data with the inclusion by using {{> templateName data1=foo data2=bar}}, but I'm struggling to figure out how I can provide different helpers based on the context. Here's the template in question:
<template name="choiceQuestion">
<div class="choice-grid" data-picks="{{numberOfPicks}}" data-toggle="buttons">
{{! Provide for the user to make multiple selections from the multiple choice list }}
{{#if hasMultiplePicks}}
{{#unless canPickAll}}<span class="help-block text-center">Pick up to {{numberOfPicks}}</span>{{/unless}}
{{#each choices}}
<label class="btn btn-default"><input type="checkbox" class="choice" name="{{this}}" autocomplete="off" value="{{this}}" checked="{{isChecked}}"> {{this}}</label>
{{/each}}
{{/if}}{{! hasMultiplePicks}}
{{#if hasSinglePick}}
{{#each choices}}
<label class="btn btn-default"><input type="radio" class="choice" name="{{this}}" id="{{this}}" autocomplete="off" value="{{this}}" checked="{{isChecked}}"> {{this}}</label>
{{/each}}
{{/if}}{{! hasSinglePick}}
</div>
</template>
and here's how I've reused it:
{{> choiceQuestion choices=allInterests picks=4}}
The key component of the template is a checkbox. In one context, it will never be checked. In another, it may be checked based on the contents of a field in the user document. I've added checked={{isChecked}} to the template. I've read this boolean attribute will be omitted if a falsey value is returned from the helper which should work well for my purposes.
The template's JS intentionally does not have an isChecked helper. I had hoped I could provide one on the parent where the template is included in the other context in order to conditionally check the box by returning true if the checked conditions are met, but the template doesn't acknowledge this helper.
Here's the template's JS:
Template.choiceQuestion.helpers({
hasSinglePick: function() {
return this.picks === 1;
},
hasMultiplePicks: function() {
return this.picks > 1 || !this.picks;
},
numberOfPicks: function() {
return this.picks || this.choices.length;
},
canPickAll: function() {
return !this.picks;
},
});
and the parent's JS:
Template.dashboard.helpers({
postsCount: function() {
var count = (Meteor.user().profile.posts||{}).length;
if (count > 0) {
return count;
} else {
return 0;
}
},
isChecked: function() {
return (((Meteor.user() || {}).profile || {}).contentWellTags || []).indexOf(this) > -1 ? 'checked' : null;
}
});
Template.dashboard.events({
'click .js-your-profile-tab': function(){
facebookUtils.getPagesAssumeLinked();
}
});
I've tried a few other approaches as well. I tried passing the helper to the template along with the other context (i.e. {{> templateName data1=foo data2=bar isChecked=isChecked}}. This kinda works, but it calls the helper immediately. This breaks these since I need to use a value from the context to determine what to return from my helper. Since this value doesn't exist when the function returns, the function always returns undefined.
If I return a function from this helper rather than the value and then pass the helper into the template inclusion along with the data context, I get better results. In fact, my console logs show the desired output, but I still don't end up with the checked box I expect.
Here's what that looks like. Returning a function:
isChecked: function() {
var self = this;
return function() {
return (((Meteor.user() || {}).profile || {}).contentWellTags || []).indexOf(this) > -1 ? 'checked' : null;
};
}
and passing that to the template:
{{> choiceQuestion choices=allInterests picks=4 isChecked=isChecked}}
Is there an established pattern for overriding template helpers from the parent or for including helpers on the parent that are missing from the child template? How can I achieve this?

meteor test equality of a input value to a document field

Hello: I’m using the Tutorials todo.
I’ve been trying to compare an Input value
<input type="text" name="text" placeholder="Type Or Scan to add new Name" />
To the field named ‘text’ within a Document named ‘Tasks’.
Have tried EasySearch and {{#if $eq a b}} ... {{ /if }}.
May because I’m new to Meteor not setting up <templates> correctly.
Was hoping there is a short template or helper to compare or check the values to.
You can do this using a reactive variable.
Here's some example code:
JS:
Template.yourTemplateName.onCreated( function() {
this.input = new ReactiveVar(""); // Declare the reactive variable.
});
Template.yourTemplateName.helpers({
tasks() {
return Tasks.find(); // Find all tasks as an example.
},
isInputEqualToTaskText( taskId ) {
var task = Tasks.findOne({ _id: taskId });
if( task && task.text ) {
return task.text == Template.instance().input.get();
}
}
});
Template.yourTemplateName.events({
'change input': function( event, template ) {
template.input.set(event.target.value);
}
});
HTML:
<template name="yourTemplateName">
{{#each task in tasks}}
{{#if isInputEqualToTaskText task._id}}
<p>I was equal: {{task.text}}</p>
{{/if}}
{{/each}}
<input type="text">
</template>

Rendering Template in Meteor and Iron Router depending on value in document

I am trying to render a template depending on a value of a field in a document.
I tried using a switch case in a helper but the return value comes out incorrect.
units_list.html
<template name="unitsList">
{{#each units}}
{{> unitItem}}
{{/each}}
</template>
units_list.js
Template.unitsList.helpers({
units: function() {
return Units.find({}, {sort: {name: 1}});
}
});
unit_item.html
<template name="unitItem">
{{name}}
</template>
unit_item.js
Template.unitItem.helpers({
unitType: function() {
var unitType = this.unitType;
switch(unitType){
case 'first': return "{{pathFor 'unitPageFirst'}}";
case 'second': return "{{pathFor 'unitPageSecond'}}";
}
}
});
I'm either going about this the wrong way or missing something elementary...
I've cut out a lot of code to focus on the problem.
Any ideas on how to get this working, or any suggestions on how to do it better?
You can't return uncompiled Spacebars strings from JS at execution time.
You can either use Router.path to get the path for your routes within your template helper :
Template.unitItem.helpers({
unitType: function() {
var unitType = this.unitType;
switch(unitType){
case 'first':
return Router.path('unitPageFirst', this);
case 'second':
return Router.path('unitPageSecond', this);
}
}
});
Or you can use plain Spacebars by declaring template helpers to check against the unitType.
HTML
<template name="unitItem">
{{#if unitTypeIs 'unitTypeFirst'}}
{{name}}
{{/if}}
{{#if unitTypeIs 'unitTypeSecond'}}
{{name}}
{{/if}}
</template>
JS
Template.unitItem.helpers({
unitTypeIs: function(unitType){
return this.unitType == unitType;
}
});
Have a look at Rendering Templates in the Iron-router guide, specifically the this.render('xyz'); statement
https://github.com/iron-meteor/iron-router/blob/devel/Guide.md#rendering-templates

Meteor Conditionally displaying nested templates

I have a template with several sub nested templates which should conditionally show based on the data saved in TemplateC collection as shown below, so I used the if condition in my template as shown below, but I am always having all sub templates displayed despite if the condition return true or false. Can someone please check my code and tell me what I am missing here? Thanks
var templateArray = ['false', 'false'];
Template.formBuilderPreview.created = function() {
var cursor = TemplatesC.find({}, { sort: { templateCode: 1 }});
if (!cursor.count()) return;
cursor.forEach(function (row) {
//only case 1 return in the switch below as case 2 never exist
switch(row.templateCode) {
case 1: templateArray[0] = true; break;
case 2: templateArray[1] = true; break;
default: templateArray[0] = true;
}
});
};
Template.formBuilderPreview.helpers({
template1box: function(){
console.log(templateArray[0]); //This returns true
return templateArray[0];
},
template2box: function(){
console.log(templateArray[1]); //This returns false
return templateArray[1];
}
});
Template:
<template name="formBuilderPreview">
<div id="fullpage">
{{#if template1box}}
{{> temp01}}
{{/if}}
{{#if template2box}}
{{> temp02}}
{{/if}}
</div>
</template>
You defined an array of strings, which I believe is causing the trouble, so I suggest you change
var templateArray = ['false', 'false'];
to
var templateArray = [false, false];
and it will work smoothly
Put that helpers together.
Template.formBuilderPreview.helpers({
template1box: function(){
if(templateArray[1]){
return true;
}else{
return false;
}
});
Now the Template should look like this.
<template name="formBuilderPreview">
{{#if template1box}}
<!-- If helper return TRUE this temp01 will be showed. -->
{{> temp01}}
{{else}}
<!-- If helper return FALSE this temp01 will be showed. -->
{{> temp02}}
{{/if}}
</template>
you get the idea with the helper, make it only on 1 helper, retiring true/false.

Resources