I'd like to generate a template with something like this:
<ul>
<li>A B C D</li>
<li>E F G H</li>
</ul>
The data i'm providing to the template is a cursor that has 8 rows (one for each letter).
I know I could do a fetch and split the results in groups of 4, but if I'm not mistaken, the fetch is less efficient, if e.g. I update the value of A to 1, it reloads everything.
Any suggestion on how to do this is appreciated.
html:
<template name="outer">
<ul>
{{#each list}}
{{> quadItem }}
{{/each}}
</ul>
</template>
<template name="quadItem">
<li>{{#each quad}}{{item}} {{/each}}</li>
</template>
js:
Template.outer.helpers({
list: function(){
var arrayOfQuads = [];
var array = myCollection.find().fetch();
for ( var i = 0; i < array.length; i += 4 ){
arrayOfQuads.push(array.slice(i,i+3));
}
return arrayOfQuads;
}
});
Template.quadItem.helpers({
quad: function(){
return this; // data context should be one row of arrayOfQuads
},
item: function(){
return this; // data context should be one element of a quad
}
});
This is very quick and dirty, I'm sure it can be made more elegant.
For example see how to make an array element selectable by index in blaze.
Related
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.
I have a collection A which have an embedded array of different IDs.
These IDs are id of collection B.
How can I loop inside a template to all of these B's id and get the associated ?
A template :
<template name="Atemplate">
<h1>Name of A : {{name}}</h1>
{{#each Bs}}
{{> Btemplate}}
{{/each}}
</template>
B template :
<template name="Btemplate">
<h1>Name of B : {{name}}</h1>
</template>
what about B template.js ?
If I understand the question right, the context for Btemplate is an id. So in the name helper you could find the document by that id like so:
Template.Btemplate.helpers({
name: function() {
var doc = B.findOne(String(this));
return doc && doc.name;
}
});
Alternatively (and someone preferably) is to set the Btemplate context to an instance of a B:
Template.Atemplate.helpers({
allBs: function() {
return B.find({_id: {$in: this.Bs}});
}
});
You'd need to modify you A template code as follows:
{{#each allBs}}
{{> Btemplate}}
{{/each}}
And now you don't need to modify the original implementation of Btemplate. I'd also recommend reading the templates articles from here.
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.
If I have something like :
{
people: [
"Yehuda Katz",
"Alan Johnson",
"Charles Jolley"
]
}
How do I get the position of each of the values in handlebar?
{{#each people}}
{{this}}
<!--{{this.position}} -->
{{/each}}
I expect the answer to be
Yehuda Katz
0
Alan Johnson
1
Charles Jolley
2
You can define your own block expression, like 'each_with_index', to accomplish this. You can find out how to create this custom expression in the Handlebars documentation here: http://handlebarsjs.com/#block-expressions
Here's a quick and dirty way to do it:
var people = [ 'Yehuda Katz', 'Alan Johnson', 'Charles Jolley' ];
var source = '{{#each_with_index people}}' +
'{{ this.index }}: {{ this.item }}\n' +
'{{/each_with_index}}';
Handlebars.registerHelper('each_with_index', function(items, options) {
var output = '';
var item_count = items.length;
var item;
for (var index = 0; index < item_count; index ++) {
item = items[index];
output = output + options.fn({ item: item, index: index });
}
return output;
});
var $output = $('#output');
var template = Handlebars.compile(source);
var result = template({ people: people });
$output.html(result);
To see it working: http://jsfiddle.net/EECFR/1/
Use #index:
{{#each people}}
{{this}}
{{#index}}
{{/each}}
JSFiddle demo (modified from Carl's)
From http://handlebarsjs.com/#iteration:
When looping through items in each, you can optionally reference the current loop index via {{#index}}
{{#each array}}
{{#index}}: {{this}}
{{/each}}
I'm running into a template context situation that I'm having a hard time finding a way around.
Here's the template in question:
{{#each votes}}
<h3>{{question}}</h3>
<ul>
{{#each participants}}
<li>
<p>{{email}}</p>
<select name="option-select">
{{#each ../options}}
<option value="{{option}}" class="{{is_selected_option}}">{{option}}</option>
{{/each}}
</select>
</li>
{{/each}}
</ul>
</div>
{{/each}}
And here's an example of a vote document:
{
_id: '32093ufsdj90j234',
question: 'What is the best food of all time?'
options: [
'Pizza',
'Tacos',
'Salad',
'Thai'
],
participants: [
{
id: '2f537a74-3ce0-47b3-80fc-97a4189b2c15'
vote: 0
},
{
id: '8bffafa7-8736-4c4b-968e-82900b82c266'
vote: 1
}
]
}
And here's the issue...
When the template drops into the #each for participants, it no longer has access to the vote context, and therefore doesn't have access to the available options for each vote.
I can somewhat get around this by using the ../options handlebars path to jump back into the parent context, but this doesn't affect the context of the template helper, so this in Template.vote.is_selected_option refers to the current participant, not to the current vote or option, and has no way of knowing which option we are currently iterating through.
Any suggestions on how to get around this, without resorting to DOM manipulation and jQuery shenanigans?
This is a templating issue that has come up multiple times for me. We need a formal way of reaching up the template context hierarchy, in templates, template helpers, and template events.
It seems since Spacebars (Meteor's new template engine), you have access to the parent context within {{#each}} blocks using ../.
In Meteor 0.9.1, you can also write a helper and use Template.parentData() in its implementation.
It's not particularly pretty, but I've done something like this:
<template name='forLoop'>
{{#each augmentedParticipants}}
{{> participant }}
{{/each}}
</template>
<template name='participant'>
...
Question: {{this.parent.question}}
...
</template>
// and in the js:
Template.forLoop.helpers({
augmentedParticipants: function() {
var self = this;
return _.map(self.participants,function(p) {
p.parent = self;
return p;
});
}
});
It's similar to the approach that AVGP suggested, but augments the data at the helper level instead of the db level, which I think is a little lighter-weight.
If you get fancy, you could try to write a Handlebars block helper eachWithParent that would abstract this functionality. Meteor's extensions to handlebars are documented here: https://github.com/meteor/meteor/wiki/Handlebars
I don't know the formal way (if there is one), but to solve your issue, I would link the participants with the parent ID like this:
{
_id: "1234",
question: "Whats up?",
...
participants: [
{
_id: "abcd",
parent_id: "1234",
vote: 0
}
]
}
and use this parent_id in helpers, events, etc. to jump back to the parent using findOne.
That is obviously a sub optimal thing to do, but it's the easiest way that comes to my mind as long as there is no way of referencing the parent context.
Maybe there is a way but it is very well hidden in the inner workings of Meteor without mention in the docs, if so: Please update this question if you find one.
It's a long shot, but maybe this could work:
{{#with ../}}
{{#each options}}
{{this}}
{{/each}}
{{/with}}
This should make life easier.
// use #eachWithParent instead of #each and the parent._id will be passed into the context as parent.
Handlebars.registerHelper('eachWithParent', function(context, options) {
var self = this;
var contextWithParent = _.map(context,function(p) {
p.parent = self._id;
return p;
});
var ret = "";
for(var i=0, j=contextWithParent.length; i<j; i++) {
ret = ret + options.fn( contextWithParent[i] );
}
return ret;
});
Go ahead and change
p.parent = self._id;
to whatever you want to access in the parent context.
Fixed it:
// https://github.com/meteor/handlebars.js/blob/master/lib/handlebars/base.js
// use #eachWithParent instead of #each and the parent._id will be passed into the context as parent.
Handlebars.registerHelper('eachWithParent', function(context, options) {
var self = this;
var contextWithParent = _.map(context,function(p) {
p.parent = self._id;
return p;
});
return Handlebars._default_helpers.each(contextWithParent, options);
});
This works :) with no error
Simply register a global template helper:
Template.registerHelper('parentData',
function () {
return Template.parentData(1);
}
);
and use it in your HTML templates as:
{{#each someRecords}}
{{parentData.someValue}}
{{/each}}
======= EDIT
For Meteor 1.2+, you shold use:
UI.registerHelper('parentData', function() {
return Template.parentData(1);
});
I was stuck in a similar way and found that the Template.parentData() approach suggested in other answers currently doesn't work within event handlers (see https://github.com/meteor/meteor/issues/5491). User Lirbank posted this simple workaround:
Pass the data from the outer context to an html element in the inner context, in the same template:
{{#each companies}}
{{#each employees}}
Do something
{{/each}}
{{/each}}
Now the company ID can be accessed from the event handler with something like
$(event.currentTarget).attr('companyId')
"click .selected":function(e){
var parent_id = $(e.currentTarget).parent().attr("uid");
return parent_id
},
<td id="" class="staff_docs" uid="{{_id}}">
{{#each all_req_doc}}
<div class="icheckbox selected "></div>
{{/each}}
</td>
{{#each parent}}
{{#each child}}
<input type="hidden" name="child_id" value="{{_id}}" />
<input type="hidden" name="parent_id" value="{{../_id}}" />
{{/each}}
{{/each}}
The _id is NOT the _did of the thing, it's the id of the parent!