Can handlebars access unique object names in a loop? - handlebars.js

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>

Related

How to fetch query parameters in a template?

I am using Meteor 1.2.1 + iron-router with autopublish turned on
I would like to construct an anchor href based on collection values returned by a helper and query params
Is it possible to do this in the template tag, e.g. query_param1 should be read from the URL?
<template name="dataEntry">
{{#each data}}
<li>
<a href="/listing?name={{name}}&color=<query_param1>">
Data name
</a>
</li>
{{/each}}
</template>
Above, {{name}} is returned by the collection and query parameters are appended to that to create a full hyperlink for the href.
You can use #Stephen's suggestion like this.
In your template html,
<template name="dataEntry">
{{#each data}}
<li>
<a href="/listing?{{queryParams}}">
Data name
</a>
</li>
{{/each}}
</template>
In your template JS,
Template.dataEntry.helpers({
"queryParams": function () {
var name = "";
//get name from collection here like...
//name = Meteor.user().profile.firstName;
var color = Router.current().params.color;
return "name=" + name + "&color=" + color;
}
});
Or you can use two separate helpers
In your template html,
<template name="dataEntry">
{{#each data}}
<li>
<a href="/listing?name={{name}}&color={{color}}">
Data name
</a>
</li>
{{/each}}
</template>
In your template JS,
Template.dataEntry.helpers({
"name": function () {
var name = "";
//get name from collection here like...
//name = Meteor.user().profile.firstName;
return name;
},
"color": function () {
return Router.current().params.color;
}
});
You can access an IronRouter param through a helper, e.g.:
Router.current().params.query_param1

Meteor: Spacebars each parameter

I'm new to Meteor.js and have run into a problem.
I am passing in a user object to a profile template e.g.:
{
_id: "D8JpXRQskm3grykjg",
username: "foo",
profile: {communities: ["AkGCakz6mSgMb8qyS", "j8aB3i5iscrC4ehkA"]},
}
<template name="profile">
<h1> {{username}}: {{_id}} </h1>
<h3>Communities</h3>
<hr>
{{#each profile.communities}}
{{> communityItem}}
{{/each}}
</template>
The problem is I've already written a communityItem template that I am using elsewhere which accepts the communityName. Is there a way that I can write a helper function, passing in the communityIds list that would return a list of community names? I would like:
...
{{#each getCommunityNames(profile.communities)}}
{{> communityItem}}
{{/each}}
...
I could very well be approaching the problem the wrong way or not writing in a "Spacebars" fashion. Thanks!
sure you can:
Template.myTemplate.helpers({
getCommunityNames: function(commIds) {
var communities = Communities.find({_id: {$in: commIds}}).fetch();
return _.pluck(communities, 'name'); // returns ['Name 1', 'Name 2'];
}
});
Note, the syntax method param not method(param)
{{#each getCommunityNames profile.communities}}
{{>communityItem}}
{{/each}}

How to do foreach in Meteor with Mongo?

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();
});

Translate Handlebars block helpers using end tags into Meteor?

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.

Handlebars.js - how to pick a "logout" value and append to "div" element

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>

Resources