Adding offset to {{#index}} when looping through items in Handlebars - handlebars.js

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

Related

Search and Sort on the same page

I'm trying to implement sort and search to my items, so i started with sort and it works:
Template
<button class="sort">Sort</button>
{{#each cvs}}
{{> Interviu}}
{{/each}}
JS:
Template.Interviuri.onCreated(function () {
var self = this
self.autorun(function () {
self.sortOrder = new ReactiveVar(-1)
})
Template.Interviuri.helpers({
cvs() {
const instance = Template.instance()
return Cvs.find({}, { sort: { createdAt: instance.sortOrder.get() } })
},
})
Template.Interviuri.events({
'click .sort'(event, instance) {
instance.sortOrder.set(instance.sortOrder.get() * -1)
Next i wanted to implement Search on the same page. So the best way i could found was EasySearch.
But using EasySearch, it means i must change the way my items are being displayed. And then the sort doesn't work anymore.
Template
<div class="searchBox pull-right">
{{> EasySearch.Input index=cvsIndex attributes=searchAttributes }}
</div>
{{#EasySearch.Each index=cvsIndex }}
{{> Interviu}}
{{/EasySearch.Each}}
Collection
CvsIndex = new EasySearch.Index({
collection: Cvs,
fields: ['name'],
engine: new EasySearch.Minimongo()
})
JS
cvsIndex: () => CvsIndex,
How can i have both search and sort working at the same time?
With EasySearch you can use two methods on your index, namely getComponentDict() and getComponentMethods().
With getComponentDict() you can access search definition and options:
index.getComponentDict().get('searchDefinition');
index.getComponentDict().get('searchOptions');
You also have the corresponding setters to change the search definition/option.
getComponentMethods has mehods like
index.getComponentMethods().loadMore(integer);
index.getComponentMethods().hasMoreDocuments();
index.getComponentMethods().addProps(prop, value);
index.getComponentMethods().removeProps([prop])
From that you can set your prop, say index.getComponentMethods().addProp('sort', -1) and then on the index definition, in your MongoDB engine, set the sort from that prop:
index = new EasySearch.index({
// other parameters
engine: new EasySearch.MongoDB({
sort: function(searchObject, options) {
if(options.search.props.sort) {
return parseInt(options.search.props.sort);
}
return 1;
}
})
});
See EasySearch Engines for more info.

Meteor: Access Template Helper (or variable) from another helper

How can I reference a template helper from another one? For example...
Template.XXX.helpers({
reusableHelper: function() {
return this.field1 * 25 / 100; //or some other result
},
anotherHelper: function() {
if (this.reusableHelper() > 300) //this does not work
return this.reusableHelper() + ' is greater than 300';
else
return this.reusableHelper() + ' is smaller than 300';
}
});
I have also tried Template.instance().__helpers.reusableHelper - all with no luck.
Alternatively is there a way to define reactive Template instance variables?
XXX is a sub-template that renders multiple times on the same page.
You can but only with global template helpers.
Blaze._globalHelpers.nameOfHelper()
Here is an example calling Iron:Router's pathFor global helper.
Template.ionItem.helpers({
url: function () {
var hash = {};
hash.route = path;
hash.query = this.query;
hash.hash = this.hash;
hash.data = this.data;
var options = new Spacebars.kw(hash);
if (this.url){
return Blaze._globalHelpers.urlFor(options)
} else if( this.path || this.route ) {
return Blaze._globalHelpers.pathFor(options)
}
}
});
EDIT: To your second question. You can call the same template as many times as you like on a page and pass different data attributes directly into it and/or use #each block template wrapper to iterate over data. #each will call a template many times giving it a different data context each time.
#each Example
<template name="listOfPosts">
<ul>
{{#each posts}}
{{>postListItem}} <!--this template will get a different data context each time-->
{{/each}}
</ul>
</template>
Attributes Example
<template name="postDetails">
{{>postHeader title="Hello World" headerType="main" data=someHelper}}
{{>postHeader title="I am a sub" headerType="sub" data=newHelper}}
{{>postBody doc=bodyHelper}}
</template>
This like using of common code, you can make another javascript function which contains the your reusable code and call it from wherever you required.
Like in your code-
function calcField(field){
return field * 25 / 100
}
and in you template helper-
Template.XXX.helpers({
reusableHelper: function() {
return calcField(this.field1);
},
anotherHelper: function() {
if (calcField(this.field1) > 300)
return calcField(this.field1) + ' is greater than 300';
else
return calcField(this.field1) + ' is smaller than 300';
}
});
and
Alternatively is there a way to define reactive Template instance
variables?
you can use Session variables or Reactive variable
Disclaimer: This may not answer your question directly, but it might be helpful for people stuck with a similar use case:
Sometimes it's easy to get locked into the "Meteor way", that standard Javascript rules are forgotten.
Two use cases that sound similar to what you're trying to do:
1. For helpers/events that you can access anywhere on the client-side, simply set a global helper.
Put this in, say, client/helpers.js:
Helpers = {
someFunction: function(params) {
/* Do something here */
}
}
Now Helpers.someFunction() is available to all templates.
If you want to bind the local template instance to it for some reason, again, it's standard JS:
var boundFunction = Helpers.someFunction.bind(this);
2. To create reusable Blaze helpers inside of templates, use Template.registerHelper
For example, this function uses the "numeral" library to format numbers:
Template.registerHelper('numeral', function(context, opt) {
var format = (opt.hash && opt.hash.format) || '0,0.00';
return numeral(context || 0).format(format);
});
You can use this in any template like so:
{{numeral someNumberVariable format='0,0'}}
I found a better solution with collection hooks:
Item = new Mongo.Collection('Items');
Item.helpers({
isAuthor: function(){
return this.authorId == Meteor.userId();
},
color: function(){
if(this.isAuthor())
return 'green';
else
return 'red';
}
});
I then becomes functions of this, usable in both helpers and templates.
i had something similar -- i had 2 helpers in the same template that needed access to the same function. however, that function 1) needed access to a reactive var in the template, and 2) is a filter function, so i couldn't just pass in the data of that reactive var.
i ended up defining the filter function in the templates onCreated() and stored it in a reactive var, so the helpers could access it.
Template.Foo.onCreated(function () {
this.fooData = new ReactiveVar();
function filterFoo(key) {
var foo = Template.instance().fooData.get();
// filter result is based on the key and the foo data
return [true|false];
}
this.filterFoo = new ReactiveVar(filterFoo);
});
Template.Foo.helpers({
helper1: function() {
var filterFn = Template.instance().filterFoo.get();
return CollectionA.getKeys().filter(filterFn);
},
helper2: function() {
var filterFn = Template.instance().filterFoo.get();
return CollectionB.getKeys().filter(filterFn);
},
});
Since this answer is currently missing - I wanted to add an update
In the current meteor version, you should be able to call:
var TEMPLATE_NAME = //the name of your template...
var HELPER_NAME = //the name of your helper...
Template[TEMPLATE_NAME].__helpers[' '+HELPER_NAME]
You should call it like this, if you want to make sure the helper has access to this:
var context = this;
Template[TEMPLATE_NAME].__helpers[' '+HELPER_NAME].call(context,/* args */);
But be careful - this could break in future Meteor versions.
Adding on to Nils' answer, I have been able to access Template level helpers in events using the following code:
'click a#back': (event, instance) ->
if instance.view.template.__helpers[' complete']() && instance.view.template.__helpers[' changed']()
event.preventDefault()
this just came up again at work, and this time we used modules. in this case, we had a number of large, related functions that had to maintain data across calls. i wanted them outside the template file but not totally polluting the Meteor scope. so we made a module (polluting the Meteor scope 1x) and called the functions therein from the template.
lib/FooHelpers.js:
FooHelpers = (function () {
var _foo;
function setupFoo(value) {
_foo = value;
}
function getFoo() {
return _foo;
}
function incFoo() {
_foo++;
}
return {
setupFoo: setupFoo,
getFoo: getFoo,
incFoo: incFoo
}
})();
FooTemplate.js:
Template.FooTemplate.helpers({
testFoo: function() {
FooHelpers.setupFoo(7);
console.log(FooHelpers.getFoo());
FooHelpers.incFoo();
console.log(FooHelpers.getFoo());
}
});
console output is 7, 8.

How to get the parent template instance (of the current template)

Is there a clean way to get the parent template of the current template? Nothing is officially documented in Meteor's API.
I'm talking about the Blaze.TemplateInstance, not the context (i.e. not Template.parentData).
In the end, I've extended the template instances similarly with Meteor's parentData, like this:
/**
* Get the parent template instance
* #param {Number} [levels] How many levels to go up. Default is 1
* #returns {Blaze.TemplateInstance}
*/
Blaze.TemplateInstance.prototype.parentTemplate = function (levels) {
var view = this.view;
if (typeof levels === "undefined") {
levels = 1;
}
while (view) {
if (view.name.substring(0, 9) === "Template." && !(levels--)) {
return view.templateInstance();
}
view = view.parentView;
}
};
Example usage: someTemplate.parentTemplate() to get the immediate parent
Is there a clean way to get the parent template of the current
template?
Currently, none that I know of, but this is supposed to happen sometime in the future as part of a planned "better API for designing reusable components" (this is discussed in the Meteor post 1.0 roadmap).
For the moment, here is a workaround I'm using in my projects :
// extend Blaze.View prototype to mimick jQuery's closest for views
_.extend(Blaze.View.prototype,{
closest:function(viewName){
var view=this;
while(view){
if(view.name=="Template."+viewName){
return view;
}
view=view.parentView;
}
return null;
}
});
// extend Blaze.TemplateInstance to expose added Blaze.View functionalities
_.extend(Blaze.TemplateInstance.prototype,{
closestInstance:function(viewName){
var view=this.view.closest(viewName);
return view?view.templateInstance():null;
}
});
Note that this is only supporting named parent templates and supposed to work in the same fashion as jQuery closest to traverse parent views nodes from a child to the top-most template (body), searching for the appropriately named template.
Once this extensions to Blaze have been registered somewhere in your client code, you can do stuff like this :
HTML
<template name="parent">
<div style="background-color:{{backgroundColor}};">
{{> child}}
</div>
</template>
<template name="child">
<button type="button">Click me to change parent color !</button>
</template>
JS
Template.parent.created=function(){
this.backgroundColor=new ReactiveVar("green");
};
Template.parent.helpers({
backgroundColor:function(){
return Template.instance().backgroundColor.get();
}
});
Template.child.events({
"click button":function(event,template){
var parent=template.closestInstance("parent");
var backgroundColor=parent.backgroundColor.get();
switch(backgroundColor){
case "green":
parent.backgroundColor.set("red");
break;
case "red":
parent.backgroundColor.set("green");
break;
}
}
});
What I've been doing so far is that if I need to access the parent instance in a child template's function, I try to instead refactor this function to declare it on the parent template, and then pass it as argument to the child, who can then execute it.
As an example, let's say I want to increment a template variable on the parent template from within the child template. I could write something like this:
Template.parentTemplate.onCreated(function () {
var parentInstance = this;
parentInstance.count = new ReactiveVar(1);
});
Template.parentTemplate.helpers({
incrementHandler: function () {
var parentInstance = Template.instance();
var count = parentInstance.count.get();
return function () {
var newCount = count + 1;
parentInstance.count.set(newCount);
};
}
});
Then include my child template:
{{> childTemplate handler=loadMoreHandler}}
And set up my event:
Template.childTemplate.events({
'click .increment-button': function (event, childInstance) {
event.preventDefault();
childInstance.data.handler();
}
});
If you don't want to extend Blaze.TemplateInstance you can access the parent instance like this:
Template.exampleTemplate.onRendered(function () {
const instance = this;
const parentInstance = instance.view.parentView.templateInstance();
});
Only tested in Meteor 1.4.x
You can use a package like Aldeed's template-extension
The following method is available there:
templateInstance.parent(numLevels, includeBlockHelpers)

Retaining logicless templates while using conditionals in Handlebars

I'm using the Handlebars library to make templates for my website. But, because templates should be logicless, basic Boolean logic (e.g. print a red or a green div based on a less-than check) is difficult without resorting to hacks. How can I resolve these kinds of problems without adding logic to my templates?
Logicless doesn't mean you can't use logic at all, it just means that you can't use logic in templates.
You should prepare all of your data before passing it to template.
For example, consider this common use case on an MV* app (like a Backbone-powered app):
Model:
{
name: 'Roger',
age: 50
}
View:
...
getTemplateData: function (model) {
var data = model;
if (model.age >= 50) {
data.isTooOld= true;
}
return data;
}
...
render: function () {
var data = this.getTemplateData(model) || {};
this.el.innerHTML = this.template(data);
}
...
Template:
<p>
OK, {{name}},
{{#if isTooOld}}
you're too old for this shit!
{{else}}
let's do this!
{{/if}}
</p>

Handlebars array access with dynamic index

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}}

Resources