Handlebars #each within #if issue - handlebars.js

I have an #if condition that I have tested is false, but it enters anyway. Here is the Handlebar script :
{{#each this.properties}}
<span class="values">
{{#if is-object-property}}
{{#each values}}
<span class="value" style="color:blue;">{{this}}</span>
{{/each}}
{{else}}
{{#each values}}
<span class="value">{{this}}</span>
{{/each}}
{{/if}}
</span>
{{/each}}
The thing is that all I have displayed is blue while my object looks like that:
properties: Array[3]
0: Object
code: "pays"
reliability: 100
values: Array[1]
1: Object
code: "pnb"
reliability: 100
values: Array[1]
2: Object
code: "source"
is-object-property: "true"
reliability: 100
values: Array[2]
And as you can see, except for the object 2, the objects 0 and 1 should have an undefined result for is-object-property. They do have that result, that's why I don't understand why I have all in blue.
I assumed it entered the condition, but as it is written on the handlebars website : "You can use the if helper to conditionally render a block. If its argument returns false, undefined, null, "" or [] (a "falsy" value), Handlebars will not render the block."
(see "The if block helper" at http://handlebarsjs.com/)

This solved the problem :
<span class="values">
{{#each values}}
{{#if ../is-object-property}}
<span class="value" style="color:blue;">{{this}}</span>
{{else}}
<span class="value">{{this}}</span>
{{/if}}
{{/each}}
</span>

Related

Handlebars Array Cycle

I have this array:
{{#each weather.today }}
<p class="VNAelement timeStamp">BlaBla</p>
<p class="VNAelement">{{ rr }}mm</p>
{{/each}}
Now there will be three loops and on each loop the text BlaBla should be replaced. What is the best way to do this? The data comes from a json API but that string is set manuall.
The best solution is to write a custom helper to check the array index during traversal.
But here, as you are sure of the number of loops (which is 3 array items), you can rely on the #first and #last data-variables.
{{#each weather.today}}
<p class="VNAelement timeStamp">
{{#if #first}}
BlaBla-1 <!-- First item -->
{{else}}
{{#if #last}}
BlaBla-3 <!-- Third item -->
{{else}}
BlaBla-2 <!-- Second item -->
{{/if}}
{{/if}}
</p>
<p class="VNAelement">{{ rr }}mm</p>
{{/each}}

Nested #Each values are not available outside of the context

I'm having a nested each pair like this:
{{#each goal in goals}}
<template name="task">
{{#each goal in goals}}
{{#each task in relatedTasks goal}}
<li>
<span class="text task"><strong>{{task.taskName}}</strong> to {{goal.goalName}}<br> taskid: {{task._id}}
{{task.taskPostpone}}</span>
{{#afModal class="btn btn-primary" collection="Tasks" operation="update" doc=task._id}}
Update {{task.taskName}}
{{/afModal}}
</li>
{{/each}}
{{/each}}
</template>
and would like to get the value of the task._id in my client.js like here:
Template.task.events({
'click .task': function() {
Session.set("selectedTask", this._id);
//console.log(this._id);
//console.log(goal._id);
console.log(task._id);
//console.log('Click event happened: this._id saved as selectedItem Session variable.');
}
});
When I click on a task I receive this error on the console: "undefined" and I don't really understand the reason behind. I did some research and found a possible solution: Maybe 'click .task': function(task) should receive the task context or input so it could be able to grasp the meaning of this._id.
I have a {{#afModal doc=task._id}} which also should receive the value of task._id and does not seem to work, although it is placed in the right context I think.
I have a feeling that the two issues are related somehow.
The problem is that the {{#each goal in goals}} loop syntax does not change the data context within the loop (see docs). It simply adds a goal variable for use in your spacebars template.
One solution would be to move the contents of the {{#each task in ...}} loop to another template like so.
<template name="task">
{{#each goal in goals}}
{{#each task in relatedTasks goal}}
{{> goalTask goal=goal task=task}}
{{/each}}
{{/each}}
</template>
<template name="goalTask">
<li>
<span class="text task">
<strong>{{task.taskName}}</strong>
to {{goal.goalName}}<br>
taskid: {{task._id}} {{task.taskPostpone}}
</span>
{{#afModal class="btn btn-primary" collection="Tasks" operation="update" doc=task._id}}
Update {{task.taskName}}
{{/afModal}}
</li>
</template>
Your event handler would then look like this.
Template.goalTask.events({
'click .task': function() {
console.log(this.goal._id);
console.log(this.task._id);
}
});
This is a common problem with events in nested objects, how to get the data context of the object that was clicked on?
The simplest way to approach this problem is to create a template for each level of nesting. The proper context is then provided automatically.
<template name="goals">
{{#each goals}}
{{#each task}}
{{> task}}
{{/each}}
{{/each}}
</template>
<template name="task">
<li>
<span class="text task"><a href="#modal-taskedit" data-toggle="modal">
<strong>{{task.taskName}}</strong></a> to {{goal.goalName}}<br>
taskid: {{task._id}}{{task.taskPostpone}}</span>
{{#afModal class="btn btn-primary" collection="Tasks" operation="update" doc=_id}}
Update {{task.taskName}}
{{/afModal}}
</li>
</template>
Then your event handler can be defined on the inner task template with the data context automatically being the individual task and not the data context of the outer template.
Template.task.events({
'click .task': function(){
Session.set("selectedTask", this._id);
}
});

Treat error when when key does not exist

How can one make spacebars react when the set key does not exist in the template?
Example:
//JavaScript
Template.foo.helpers({
//"zaz" : "hello",
"bar" : 1
});
...
<!-- Template -->
<template name="foo">
{{bar}}
{{#ifExist zaz}}
{{zaz}}
{{else}}
"fill-me"
{{/ifExist}}
</template>
You can just check if your helper is defined by using a simple if statement followed by the helper or value you want to check:
<template name="foo">
{{bar}}
{{#if zaz}}
{{zaz}}
{{else}}
fill-me
{{/if}}
</template>

Fastest way to check whether the cursor returned by a template helper is empty?

I often do something like this, using the items helper twice:
{{#if items}}
<h1>Items</h1>
{{#each items}}
{{> item}}
{{/each}}
{{/if}}
Template.foo.helpers
items: ->
Items.find
bar: true
,
sort: created: -1
transform: (item) ->
i.good = true
i
Is Meteor doing extra work in this scenario? Would it be more efficient to switch the if to use something like areItems?
areItems: ->
Items.find
bar: true
.count() > 0
You can use {{else}}
{{#each this}}
{{> item}}
{{else}}
<h1>No Items</h1>
{{/each}}
In the template, you can use {{#with items}} and then either 'this.count' or 'this.length' to check whether your helper returned any items.
Use this.count if 'items' is a cursor, e.g. the result of a find() operation:
{{#with items}}
{{#if this.count}}
<h1>Items</h1>
{{#each this}}
{{> item}}
{{/each}}
{{/if}}
{{/with}}
Use this.length if 'items' is an array:
{{#with items}}
{{#if this.length}}
<h1>Items</h1>
{{#each this}}
{{> item}}
{{/each}}
{{/if}}
{{/with}}
Use #with, #if this.length, and .fetch:
{{#with items}}
{{#if this.length}}
<h1>Items</h1>
{{#each this}}
{{> item}}
{{/each}}
{{/if}}
{{/with}}
Template.foo.helpers
items: ->
Items.find
bar: true
,
sort: created: -1
transform: (item) ->
i.good = true
i
.fetch()
You can do what you want using spacebars' #with block tag.
Like this:
{{#with items}}
{{#if this.count}}<h1>Items</h1>{{/if}}
{{#each this}}
{{> item}}
{{/each}}
{{/with}}
The block tag is documented here. The relevant quote is:
If the argument to #with is falsy (by the same rules as for #if), the content is not rendered.
update: fixed code for desired behaviour. Also while my example demonstrates making one helper call it is better practice to make an 'itemList' template and include it by using {{> itemList items}}.
I found that simply {{#if items.count}} was sufficient.
{{#if items.count}}
<h2>Below there are items</h2>
{{/if}}
{{#each items}}
<div class="item-name">{{this.name}}</div>
{{else}}
<h2>There are no items</h2>
{{else}}

Iterating through arrays of objects in meteor templates

[
{name:"foo",last:"bar",age:100},
{name:"baz",last:"foobar",age:200},
]
Is there a way on how to iterate that to a meteor template using {{#each}} helper like
<template name='boo'>
{{#each objectInArray}}
<span> {{namehere}} </span>
{{/each}}
</template>
You can do it like this:
<template name="boo">
{{#each objectInArray}}
<span> {{foo}} </span>
<span> {{last}} </span>
<span> {{age}} </span>
{{/each}}
</template>
Template.boo.objectInArray = function() {
return [
{name:"foo",last:"bar",age:100},
{name:"baz",last:"foobar",age:200},
]
}

Resources