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;
}
}
)
Related
I'm returning a 3rd party object to handlebars but i've noticed that some property names are prefixed with unique namespaces. Here's a short extract:
var data={
"soapenv:Envelope":{
"soapenv:Body":{
"CaseDetails":[
{
"Status":"Open",
"Opened":"2018-02-19T10:56:03.783Z",
"ns1:CaseReference": {"id":"111111"}
},
{
"Status":"Closed",
"Opened":"2017-02-19T10:56:03.783Z",
"ns3:CaseReference": {"id":"222222"}
},
{
"Status":"Closed",
"Opened":"2016-02-19T10:56:03.783Z",
"ns8:CaseReference": {"id":"3333"}
}
]
}
}
};
I want to loop through this object and output the information. Is it possible to match these unique names: ns1:CaseReference, ns3:CaseReference and ns8:CaseReference?
{{#each data.soapenv:Envelope.soapenv:Body.CaseDetails}}
<td>{{Status}}</td>
<td>{{Opened}}</td>
<td>{{???.id}}</td>
{{/each}}
You can write a custom HandlebarsHelper, ifKeyHasText to check whether the key contains a specific string (CaseReference in this scenario).
Handlebars.registerHelper('ifKeyHasText', function(array, value, options) {
var key;
if(Object.keys(array).some(function(k){
if(~k.indexOf(value)){
key = k;
}
}));
if(key){
return options.fn(this[key]);
}
});
And in the template, you can pass the string CaseReference to the helper {{#ifKeyHasText}} along with the data JSON as this object.
{{#each soapenv:Envelope.soapenv:Body.CaseDetails}}
<td>{{Status}}</td>
<td>{{Opened}}</td>
<td>
{{#ifKeyHasText this 'CaseReference'}}
{{id}}
{{/ifKeyHasText}}
</td>
{{/each}}
Hope this helps.
I managed to get it based on this post: https://stackoverflow.com/a/21452230/1622376
var data={
"soapenv:Envelope":{
"soapenv:Body":{
"CaseDetails":[
{
"Status":"Open",
"Opened":"2018-02-19T10:56:03.783Z",
"ns1:CaseReference": {"id":"111111"}
},
{
"Status":"Closed",
"Opened":"2017-02-19T10:56:03.783Z",
"ns3:CaseReference": {"id":"222222"}
},
{
"Status":"Closed",
"Opened":"2016-02-19T10:56:03.783Z",
"ns8:CaseReference": {"id":"3333"}
}
]
}
}
};
Handlebar using 'this' to traverse the tree:
<ul>
{{#each soapenv:Envelope}}
{{#each this}}
{{#each this}}
<li>
{{this.Status}}
{{#each this}}
{{this.id}}
{{/each}}
{{this.Opened}}
</li>
{{/each}}
{{/each}}
{{/each}}
</ul>
Have collection
Peoples = new Mongo.Collection('peoples');
Peoples.insert({
name: ["Mark", "John", "Kate"]
});
I want to show all names in name
<template name="pTable">
<tr class="trJob">
<td>
{{#each names}}
{{> peopleName}}
{{/each}}
</td>
</tr>
</template>
<template name="peopleName">
<div>{{name}}</div>
</template>
What in my Temlate helpers
Template.pTable.helpers({
names: function(){
return Posts.tags;
}
});
Template.peopleName.helpers({
name: function(){
return Posts.tags.find();
}
});
I know that i have sh*** code in my Template helpers, any idea how to make it good ?
It must look like (in DOM)
<td>
<div>Mark</div>
<div>John</div>
<div>Kate</div>
</td>
simple array example
Template.home.helpers({
names: function(){
return [1,2,3];
}
});
<template name="home">
{{#each names}}
{{this}}
{{/each}}
</template>
will print:
1 2 3
each item becomes "this" inside the each loop. if you call another template within the loop, then its "this" will be populated by the item
Since you have different names for your collection, I will stick with the first one Peoples
Here is how I would proceed with your helpers:
Template.pTable.helpers({
names: function(){
return People.find({_id:yourId},{fields:{name:1}}).fetch();
}
});
And your peopleName template would be like this:
<template name="peopleName">
<div>{{this}}</div>
</template>
If you need to get all the names of all your documents, I need to nest your {{#each names}} into another {{#each doc}} where doc helper is like this (updated names as well) :
Template.pTable.helpers({
doc: function(){
return People.find().fetch();
},
names: function(){
return People.find({_id:this.id},{fields:{name:1}}).fetch();
});
In Meteor, I have an app where I make a list of items grouped by tags, where non-tagged items come first and then tagged items are hidden under a "tag header" drop down.
I haven't touched this app since 0.8 came out, and I was using a block helper in a template which worked fine in pre-0.8...
See working jsfiddle here
Handlebars.registerHelper('eachItem', function(context, options) {
var ret = "";
for(var i=0, j=context.length; i<j; i++) {
if(!context[i].tag){
ret = ret + options.fn(context[i]);
} else {
if(i===0||!context[i-1].tag ||context[i-1].tag !== context[i].tag){
ret = ret + '<li class="list-group-item"><a data-toggle="collapse" data-target="#'+ context[i].tag +'"> Items tagged '+context[i].tag + '</a></li><div id="'+context[i].tag+'" class="collapse">';
}
ret = ret + options.fn(context[i]);
if(i+1<j && context[i+1].tag !== context[i].tag){
ret = ret + '</div>';
}
}
}
return ret;
});
But I'm struggling a bit to translate this into post-0.8 Meteor
The inserted HTML must consist of balanced HTML tags. You can't, for example,
insert " </div><div>" to close an existing div and open a new one.
One idea I had was to render the non-tagged items and also the containers in a vanilla {{#each}} loop (with 2 different templates), and then do something like this
Template.myListContainer.rendered = function(){
_.each(tags, function(tag){
var tagged_items = _.filter(items, function(item){ return item.tag == tag; });
_.each(tagged_items, function(item){
UI.insert(UI.RenderWithData(listItemTemplate, { item : item }), tagContainer);
});
});
}
Is there a simpler way to do this ? If the items come from a collection, will they keep their reactivity ?
Many thanks in advance !
If you haven't already, it's worth reading the Meteor wiki on migrating to blaze.
Anyhow, this one seems difficult to implement as a straight iteration.
If you don't need them in a specific order, I would just list items w/ and w/o tags, eg:
Template.example.helpers({
dataWithoutTags: function(){
return items.find({tag:{exists: false}});
},
tagList: function(){
// create a distinct list of tags
return _.uniq(items.find({tag:{exists: true}}, {tag: true}));
},
dataForTag: function(){
// use `valueOf` as `this` is a boxed-string
return items.find({tag: this.valueOf()});
}
});
Template:
<template name="example">
<div class='panel panel-default'>
<ul class='list-group'>
{{#each dataWithoutTags}}
<li class='list-group-item'>{{name}}</li>
{{/each}}
{{#each tagList}}
<li class="list-group-item">
<a data-toggle="collapse" data-target="#{{this}}"> Items tagged {{this}}</a>
</li>
<div id="{{this}}" class="collapse">
{{#each dataForTag}}
<li class='list-group-item'>{{name}}</li>
{{/each}}
</div>
{{/each}}
</ul>
</div>
</template>
If you do need them in a specific order - eg. grouping only consecutive items (as per your example). The only option would be to pre-process them.
eg:
Template.example.helpers({
itemsGrouped: function(){
dataGroups = [];
currentGroup = null;
items.find({}, {sort: {rank: 1}}).forEach(function(item){
if (currentGroup && (!item.tag || currentGroup.tag != item.tag)){
dataGroups.push(currentGroup);
currentGroup = null;
}
if (item.tag){
if (!currentGroup){
currentGroup = {
group: true,
tag: item.tag,
items: [item]
};
} else {
currentGroup.items.push(item);
}
} else {
dataGroups.push(item);
}
});
if (currentGroup){ dataGroups.push(currentGroup); }
return dataGroups;
}
});
Template:
<template name="example">
<div class='panel panel-default'>
<ul class='list-group'>
{{#each itemsGrouped}}
{{#if group}}
<li class="list-group-item">
<a data-toggle="collapse" data-target="#{{tag}}"> Items tagged {{tag}}</a>
</li>
<div id="{{tag}}" class="collapse">
{{#each items}}
<li class='list-group-item'>{{name}}</li>
{{/each}}
</div>
{{else}}
<li class='list-group-item'>{{name}}</li>
{{/if}}
{{/each}}
</ul>
</div>
</template>
If you log the value you are returning from the eachItem helper, you will see you are not closing the last div element. Fixing that may get it working again. However, you have div elements as direct children of the ul element, which you are not supposed to according to the HTML5 specification:
Permitted contents: Zero or more li elements
Also, I think it would be a better option for you to prepare your context in a format that makes it easier to let simple templates take care of the rendering. For example, using the same data from your jsfiddle, suppose you had it in the following format:
tagGroups = [{
names: ["item1", "item2"]
},{
tag: "red",
names: ["item3", "item4"]
},{
tag: "blue",
names: ["item5", "item6", "item7"]
}]
The following templates would give you the expected result in a valid html format. The ending result is a bootstrap panel containing collapsible list-groups:
<template name="accordion">
<div class="panel panel-default">
{{#each tagGroups}}
{{> tagGroup}}
{{/each}}
</div>
</template>
<template name="tagGroup">
{{#if tag}}
{{> namesListCollapse}}
{{else}}
{{> namesList}}
{{/if}}
</template>
<template name="namesList">
<ul class="list-group">
{{#each names}}
<li class="list-group-item">{{this}}</li>
{{/each}}
</ul>
</template>
<template name="namesListCollapse">
<div class="panel-heading"><a data-toggle="collapse" data-target="#{{tag}}">Items tagged {{tag}}</a></div>
<ul id="{{tag}}" class="panel-collapse collapse list-group">
{{#each names}}
<li class="list-group-item">
{{this}}
</li>
{{/each}}
</ul>
</template>
And here is an example of a helper to transform your data so you can make a quick test to see it working:
Template.accordion.tagGroups = function () {
var data = [{
name : 'item1'
},{
name : 'item2'
},{
name : 'item3',
tag : 'red'
},{
name : 'item4',
tag : 'red'
},{
name : 'item5',
tag : 'blue'
},{
name : 'item6',
tag : 'blue'
},{
name : 'item7',
tag : 'blue'
}];
data = _.groupBy(data, 'tag');
data = _.map(data, function (value, key) {
var obj = {};
if (key !== 'undefined') {
obj.tag = key;
}
var names = _.map(value, function (item) {
return item.name;
});
obj.names = names;
return obj;
});
//console.dir(data);
return data;
};
And yes, if the items come from a collection, you will get reactivity by default.
In the Handlebars.js - template i am getting a senario to check the value of the array, and if the value is not equal to do a task and equal to do a taks.. i do like this, But it thrown a error, how to handle this scenario.. any one help me?
Or any one give the way to handle this properly.
myJson would be :
{
"links":[{"label":"x","link":"x"},
{"label":"y","link":"y"},
{"label":"Logout","link":"Logout"}]
}
{{#each links}}
{{#if !lable.Logout}}
<li>
{{label}}
{{#if subLinks}}
<ul>
{{#each subLinks}}
<li>{{label}}</li>
{{/each}}
</ul>
{{else}}
<div>{{label}}</div>
{{/if}}
{{/each}}
Handlebars alone cannot handle conditional if statements, you need to use a helper function.
<div id="myDiv"></div>
<script type="text/x-handlebars-template" id="handlebar">
{{#each links}}
{{#ifCond label "Logout" }}
<li>
{{label}}
{{#if subLinks}}
<ul>
{{#each subLinks}}
<li>{{label}}</li>
{{/each}}
</ul>
{{/if}}
{{else}}
<div>
{{label}}
</div>
{{/ifCond}}
{{/each}}
</script>
<script>
$( document ).ready(function() {
var source = $("#handlebar").html();
var template = Handlebars.compile(source);
var context = {
"links":[
{"label":"x","link":"x"},
{"label":"y","link":"y"},
{"label":"Logout","link":"Logout"}
]
};
var html = template(context);
$("#myDiv").html(html);
});
Handlebars.registerHelper('ifCond', function(v1, v2, options) {
if(v1 === v2) {
return options.fn(this);
}
return options.inverse(this);
});
</script>
I have a array of elements, i am appending them all to my form element. all works fine. But i am not able to add a "hr" element to each of my 3rd label.. i tried this.
<script id="locale-template" type="text/x-handlebars-template">
{{#each this}}
{{#if #index % 3 === 0 }}
<hr/>
{{/if}}
<label><input type="checkbox" /> {{name}} </label>
{{/each}}
</script>
But not works.. can any one suggest me the correct way please..?
Thanks in advance
First register helper like showHr
Handlebars.registerHelper("showHr", function(index_count,block) {
if(parseInt(index_count)%3=== 0){
return block.fn(this);}
});
Now In Template
{{#showHr #index}}
<hr/>
{{/showHr}}
Or if you want you can write generic helper refer http://doginthehat.com.au/2012/02/comparison-block-helper-for-handlebars-templates/
Edit for comment question
Handlebars.registerHelper("moduloIf", function(index_count,mod,block) {
if(parseInt(index_count)%(mod)=== 0){
return block.fn(this);}
});
Considering index start from 0
// If index is 0 open div
// if index is 3 means open a div
{{#moduloIf #index 0}}
<div>
{{/moduloIf}}
{{#moduloIf #index 3}}
<div>
{{/moduloIf}}
{{name}}
// if index+1 is modulo 3 close div
{{#moduloIf #index+1 3}}
</div>
{{/moduloIf}}