I'm using Spacebars and Meteor 1.2. In {{#each post}}, how to enter data after a specific iteration. For example after the second iteration.
Example:
1º iteration |
2º iteration |
data |
3º iteration |
4º iteration ...
Firstly, convert your cursor to an array and add an index attribute. You're also going to need a helper to check equality (or to tell you when the conditions are right to display something different):
Template.myTemplate.helpers({
post: function(){
var cursor = Posts.find({}); // whatever your query is
var array = _.map(cursor, function(doc, index) {
doc.iteration = index + 1; // add an 'iteration' key starting at 1 instead of 0
return doc;
});
return array;
},
equals: function(a,b){ // determine equality of a and b for use in spacebars
return a==b;
}
});
Then in your html template:
<template name="myTemplate">
{{#each post}}
Title: {{title}}
{{#if equals iteration 2}} Second iteration {{/if}}
{{/each}}
</template>
Related
I'm trying to find a nice, Meteor-style way to handle this issue.
I have a set of Mongo documents sorted by date that I can easily display in a list:
<template name="logbook">
<h1>{{title}}</h1>
<div>
{{#each entries}}
{{> Entry}}
{{/each}}
</div>
</template>
Now, each time the year changes, I'd like to output it, so that I get something like this:
2014
doc 1
doc 2
2013
doc 3
doc 4
doc 5
etc.
This this is Meteor, I'd like the list to be reactive. If a new document arrives then it should be inserted in the right place in the list, and the year added if necessary.
Can anyone suggest a sensible approach to handle this?
You could probably use helper that will check if year is the same as in the last record, if not - he will output it, something like
<template name="Entry">
{{year}}
{{data}}
</template>
And in js
year: function(){
//operations that will return year to some variable, for example year_variable
if(global_variable===undefined){
global_variable=year_variable;
return year_variable;
}
if(global_variable===year_variable) return false;
else return year_variable;
}
There is no need to make it global tho, you can use sessions for it
This may not be precisely what you are looking for with respect to naming conventions, but it will give you an idea of how I would approach this problem:
Create a list of unique years
For each year, render a template (logbook)
Within each logbook, iterate over all of the entries for that year
Here is a complete working solution:
app.html
<body>
{{#each years}}
{{> logbook}}
{{/each}}
</body>
<template name="logbook">
<h2>{{year}}</h2>
<ol>
{{#each entries}}
<li>{{text}}</li>
{{/each}}
</ol>
</template>
app.js
if (Meteor.isClient) {
// create a client-side collection for testing
Entries = new Mongo.Collection(null);
Meteor.startup(function() {
// insert some data in the wrong order to test sorting
Entries.insert({text: 'doc6', date: new Date('1/3/2013')});
Entries.insert({text: 'doc4', date: new Date('1/1/2013')});
Entries.insert({text: 'doc5', date: new Date('1/2/2013')});
Entries.insert({text: 'doc3', date: new Date('1/3/2014')});
Entries.insert({text: 'doc1', date: new Date('1/1/2014')});
Entries.insert({text: 'doc2', date: new Date('1/2/2014')});
});
Template.body.helpers({
years: function() {
// return a list of unique sorted objects with a year field
return _.chain(Entries.find().fetch())
// pluck out the dates
.pluck('date')
// convert each date to a year
.map(function(date) {return date.getFullYear();})
// sort the years in reverse order
.sortBy(function(year) {return -year;})
// find only the unique values
.uniq(true)
// '2014' -> {year: '2014'}
.map(function(year) {return {year: year};})
.value();
}
});
Template.logbook.helpers({
entries: function() {
var year = this.year;
var entries = Entries.find({}, {sort: {date: 1}}).fetch();
// return a list of entries only for this year
return _.filter(entries, function(entry) {
return entry.date.getFullYear() === year;
});
}
});
}
I'm iterating over a list in Handlebars using the built-in each helper.
Within the each block, I'm referencing the current loop index {{#index}} to print the consecutive number of an item:
<script id="list-item-template" type="text/x-handlebars-template">
{{#each items}}
<li class="topcoat-list__item">
Item number {{#index}}
</li>
{{/each}}
</script>
This gives the following output:
Item number 0
Item number 1
Item number 2
....
The problem is that I want to display an offsetted index which starts with 1 instead of 0.
I tried to perform calculations on the index like {{#index+1}}, but this just leads to an
Uncaught Error: Parse error
Handlebars gives you the possibility to write a custom helper that handles this situation, e.g. a helper function that lets you perform calculations on expressions like addition and subtraction etc.
Below function registers a new helper, which simply increments a value by 1:
var Handlebars = require('handlebars');
Handlebars.registerHelper("inc", function(value, options)
{
return parseInt(value) + 1;
});
You can then use it within the handlebar expression using the inc keyword, like:
{{inc #index}}
Actual answer: https://stackoverflow.com/a/46317662/1549191
Register a math handlebar and perform all mathematical operations.
app.engine('handlebars', exphbs({
helpers:{
// Function to do basic mathematical operation in handlebar
math: function(lvalue, operator, rvalue) {lvalue = parseFloat(lvalue);
rvalue = parseFloat(rvalue);
return {
"+": lvalue + rvalue,
"-": lvalue - rvalue,
"*": lvalue * rvalue,
"/": lvalue / rvalue,
"%": lvalue % rvalue
}[operator];
}
}}));
app.set('view engine', 'handlebars');
Then you can directly perform operation in your view.
{{#each myArray}}
<span>{{math #index "+" 1}}</span>
{{/each}}
I believe you can use...
{{math #index "+" 1}}
To expand on Mobiletainment's answer, this solution allows for the value to be incremented by to be passed in as an argument. If no value is passed, then a default value of 1 is used.
Handlebars.registerHelper('inc', function(number, options) {
if(typeof(number) === 'undefined' || number === null)
return null;
// Increment by inc parameter if it exists or just by one
return number + (options.hash.inc || 1);
});
Within your template you can then write:
{{inc #index inc=2}}
I solved this issue for myself by adding a short script tag to the bottom of my handlebars code!
Add a class to wherever you are calling #index and then the below jQuery code works (can also be done using vanilla JS).
<p class="create_index">
{{#index}}
</p>
<script>
$(".create_index").text(parseInt($(".create_index").text())+1)
</script>
edit 4/28- This has changed to use vanilla JS for better backwards compatibility (i.e. IE7, 8):
<span class="create_index"></span>
<script>
var divs = document.querySelectorAll('.create_index');
for (var i = 0; i < divs.length; ++i) {
divs[i].innerHTML = i + 1;
}
</script>
document.querySelectorAll has great compatibility but could also be document.getElementsByClassName("create_index")
Throwing my solution in here. CSS counters.
body {
counter-reset: section; /* Set a counter named 'section', and its initial value is 0. */
}
h3::before {
counter-increment: section; /* Increment the value of section counter by 1 */
content: counter(section); /* Display the value of section counter */
}
I was stuck on this and it was a nicer solution compared to adding a new helper.
for more accuracy in devlopment server use this
var hbs=require('express-handlebars')
var app = express();
app.set('view engine', 'hbs');
app.engine('hbs', hbs({
helpers: {
inc: function (value, options) {
return parseInt(value) + 1;
}
},
extname: 'hbs',
defaultLayout: 'layout',
layoutsDIR: __dirname + '/views/layout/',
partialsDIR: __dirname + '/views/partials/'
}))
this part create a function called inc for increasing the value of {{#index}} by one
app.engine('hbs', hbs({
helpers: {
inc: function (value, options) {
return parseInt(value) + 1;
}
},
we can use inc function with {{#index}} like this {{inc #index}}
here i include a screenshot of one of my project hosted in glitch.com and the link is given below
https://library-management-system-joel.glitch.me/view-books
(this project is under construction)
thanks to Mobiletainment and Drkawashima
The handlebars-helpers library has a fairly thorough mathematics library in lib/math.js, including a general purpose {{add a b}} helper defined as follows:
/**
* Return the product of a plus b.
*
* #param {Number} a
* #param {Number} b
* #api public
*/
helpers.add = function(a, b) {
return a + b;
};
If you don't want to copy and paste this into your project and you have the possibility to use npm, you can get this dependency as follows:
npm install handlebars-helpers --save
Then, you can register the math helpers as follows:
const handlebars = require('handlebars'),
handlebarsHelpers = require('handlebars-helpers');
handlebarsHelpers.math({
handlebars: handlebars
});
I was using nodejs and express-handlebars as template engine and facing same problem. And this is how I managed to solve.
You can create a folder and a js file inside it where you can create your own custom helpers that takes index and returns incrementing it by 1.
module.exports = {
formatIndex: function(index) {
return index+1;
}
}
Remember to register helper in your application(in my case app.js). I have used express-handlebars so I have reistered helper in this way:
app.engine('handlebars', exphbs({defaultLayout: 'home', helpers: { formatIndex }}));
Note: You have to import formatIndex before registering.
Then you can use it in your view as:
{{#each assignments}}
<div>{{formatIndex #index }}</div>
{{/if}}
Create method eval
Handlebars.registerHelper('eval', function(...e)
{
e.pop();
const args = e.join('');
return eval(args) ;
}
);
and use in any math actions
{{eval #index " + 1"}}
or
{{eval #index " + 1 - 2 * 4"}}
or
{{eval #index " + 1" "- 2" "* 4"}}
I refer in Helpers of express-handlebars document.
https://www.npmjs.com/package/express-handlebars
-----index.js-----
app.engine(
'hbs',
engine({
extname: '.hbs',
helpers: {
sum: (a, b) => a + b,
},
}),
);
-----handlebars file-----
<tbody>
{{#each courses}}
<tr>
<th scope="row">{{sum #index 1}}</th>
<td>{{this.name}}</td>
<td>{{this.description}}</td>
<td>{{this.createdAt}}</td>
</tr>
{{/each}}
</tbody>
there is an easy way to incitement with the {{#index}} value.
solution is to call the funtion and pass the #index value o it and than on JS, use helper to return #index + 1
here is go
{{incitement #index}}
and here is code for the js
Handlebars.registerHelper("incitement", function (inindex) {
return inindex + 1
});
Say I have JSON:
{
userinput: [
{name: "brian", "value": "i like pies"},
{name: "susan", "value": "memes are stupid"}
],
feedback: [
{value: "i also like pies"},
{value: "null"}
]
}
And I'm trying to draw a table like this:
name ..... | input ...... | feedback
-----------|----------------|-----------------
brian | I like pies | I also like pies
susan | mems are stupid| null
And while I recognise that it would be better to have feedback as a value of "userinput", what I have is not done like that ...
I'm trying to get the index of feedback inside {{#each userinput}}`, e.g.
{{#each userinput}}
<td>{{name}}</td><td>{{value}}</td><td>{{../feedback[#index].value}}</td>
{{/each}}
But of course {{../feedback[#index].value}} does not work.
What is the best way (without changing the structure of the json) to grab the value of the matching index inside the feedback array?
This can be accomplished using the lookup helper:
The lookup helper allows for dynamic parameter resolution using Handlebars variables. This is useful for resolving values for array indexes.
So the template for your example would look like this:
{{#each userinput}}
<td>{{name}}</td>
<td>{{value}}</td>
<td>
{{#with (lookup ../feedback #index)}}
{{value}}
{{/with}}
</td>
{{/each}}
I guess you will have to write a block helper for this, as it seems #index can only be used as a stand-alone.
I modified the "list" example, to allow a template like this: "{{#list userinput feedback}}<td>{{name}}</td><td>{{value}}</td><td>{{#feedback.value}}</td>{{/list}}". The implementation is like this, accepting two parameters "input" and "feedback" (plus the standard "options").
Handlebars.registerHelper('list', function(input, feedback, options) {
var out = "", data;
// iterate over the input
for (var i=0; i<input.length; i++) {
if (options.data) {
data = Handlebars.createFrame(options.data || {});
// add "feedback" item to the current frame's data
data.feedback = feedback[i];
}
out += "<tr>" + options.fn(input[i], { data: data }) + "</tr>";
}
return out;
});
Here's the Fiddle.
How can I access to an array element inside handlebars template using a variable instead of an hardcoded value?
I need to do something like:
{{#each condition in conditions}}
{{App.ops.[condition.op].name}}
{{/each}}
at the moment doesn't give me a parse error but on runtime doesn't return me nothing.
If i do something like this:
{{App.ops.[1].name}}
it works but it's not what i'm looking for
Related to my answer on another question
You can use the built-in lookup helper:
The lookup helper allows for dynamic parameter resolution using Handlebars variables. This is useful for resolving values for array indexes.
Using lookup, your example could be written as
{{#each condition in conditions}}
{{#with (lookup ../App.ops condition.op)}}
{{name}}
{{/with}}
{{/each}}
(Note that without knowledge of the structure of your data, I'm making an assumption about the location of App.ops.)
You can write a simple helper just to get value from array
Handlebars.registerHelper('getmyvalue', function(outer, inner) {
return outer[inner.label];
});
and then use it in template like
{{#each outer}}
{{#each ../inner}}
{{getmyvalue ../this this }}
{{/each}}
../this references to current outer item, and this - to current inner item
Example of data coming to template:
{
outer: {
1: { foo: "foo value" },
2: { bar: "bar value" }
},
inner: {
1: { label: "foo" },
2: { label: "bar" }
}
}
You need to create a helper for your problem. Below is the sample solution to your problem using index values. If you want to use some conditions you can also do that.
Handlebars.registerHelper("each_with_index", function(array, options) {
if(array != undefined && array != null && array.length > 0){
var html = new StringBuffer();
for (var i = 0, j = array.length; i < j; i++) {
var item = array[i];
item.index = i+1;
// show the inside of the block
html.append(options.fn(item));
}
// return the finished buffer
return html.toString();
}
return "";
});
Then you can do something like this
{{#each_with_index condition in conditions}}
{{App.ops.[condition.index].name}}
{{/each_with_index}}
This question already has answers here:
How can I get the index of an array in a Meteor template each loop?
(6 answers)
Closed 7 years ago.
The below example will generate a list of names of players, where players is a data set from a MongoDB database.
<template name="players">
{{#each topScorers}}
<div>{{name}}</div>
{{/each}}
</template>
However, I want to display four of them in a row, and after four players is printed, I want to divide the line by <hr /> and then continue. For instance,
<template name="players">
{{#each topScorers}}
<div style="float:left;">{{name}}</div>
{{if index%4==0}}
<hr style="clear:both;" />
{{/if}
{{/each}}
</template>
How can I do something like that while iterating through collections?
Another solution, in line with maintaining the reactivity of the collection, is to use a template helper with the map cursor function.
Here's an example showing how to return the index when using each with a collection:
index.html:
<template name="print_collection_indices">
{{#each items}}
index: {{ this.index }}
{{/each}}
</template>
index.js:
Items = new Meteor.Collection('items');
Template.print_collection_indices.items = function() {
var items = Items.find().map(function(doc, index, cursor) {
var i = _.extend(doc, {index: index});
return i;
});
return items;
};
There's no easy way to do this right now, the latest version of handlebars supports an #index field (which would do what you want), but it's not yet implemented in meteor's version - https://github.com/meteor/meteor/issues/489.
Certainly you could implement your own {{#each_with_index}} helper, it would look something like this:
Handlebars.registerHelper('each_with_index', function(items, options) {
var out = '';
for(var i=0, l=items.length; i<l; i++) {
var key = 'Branch-' + i;
out = out + Spark.labelBranch(key,function(){
options.fn({data: items[i], index: i});
});
}
return out;
});
The downside of this is you lose the niceness of meteor's {{#each}} helper, which doesn't reactively re-render the whole list when a single item changes.
EDIT: thanks #zorlak for pointer to https://github.com/meteor/meteor/issues/281#issuecomment-13162687