Handlebars custom helper error: "options.fn is not a function" - javascript

I'm pretty new to handlebars.js and I tried writing a simple if/else helper.
I used this codepen as a guideline.
I already found out that you shouldn't use the # in front of a custom helper but I can't figure out why i'm still getting this error.
This is my index.html :
<html>
<head>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/handlebars.js/4.0.5/handlebars.min.js">
</script>
<script type="text/javascript" src="
https://cdnjs.cloudflare.com/ajax/libs/jquery/3.0.0-beta1/jquery.js"></script>
<script src="data.js"></script>
<script src="helper.js"></script>
</head>
<body>
<script id="entry-template" type="text/x-handlebars-template">
<ul class="test">
{{#each people}}
{{ifThird #index}}
<li>
{{firstName}}
</li>
{{else}}
<li>
{{firstName}}{{lastName}}
</li>
{{/each}}
</ul>
</div>
</div>
</script>
</body>
</html>
... and this is data.js :
$(function(){
var templateScript = $("#entry-template").html();
var theTemplate = Handlebars.compile(templateScript);
var context = {
people: [
{firstName: "Yehuda", lastName: "Katz"},
{firstName: "Carl", lastName: "Lerche"},
{firstName: "Alan", lastName: "Johnson"},
{firstName: "Dik", lastName:"Neok"},
{firstName: "Bob", lastName:"van Dam"},
{firstName: "Erik", lastName: "Leria"}
]
};
var html = theTemplate(context);
$('.test').html(html);
$(document.body).append(html);
})
... and this is helper.js (the error should be in here) :
Handlebars.registerHelper('ifThird', function (index, options) {
if(index%3 == 0){
return options.fn(this);
} else {
return options.inverse(this);
}
});

You need to edit your template to:
<ul class="test">
{{#each people}}
{{#ifThird #index}}
<li>
{{firstName}}
</li>
{{else}}
<li>
{{firstName}}{{lastName}}
</li>
{{/ifThird}}
{{/each}}
</ul>
You need to start the block with {{#ifThird}} and close the block with {{/ifThird}}.

Related

Handlebars - split string and iterate within {{each}}

Basically I am rendering a JSON file into a HTML template with Handlebars and all works fine except for one value where I get returned a string with comma separated values.
JSON file:
[
{
"venue_state": "Everywhere",
"venue_country": "United States",
"venue_lat": "34.2347",
"venue_lng": "-77.9482",
"rating_score": "4",
"created_at": "2022-07-01 17:13:16",
"flavor_profiles": "malty,biscuity,grainy,clove,banana"
},
{
....
}
]
HTML
<script src="https://code.jquery.com/jquery-3.6.0.slim.min.js" integrity="sha256-u7e5khyithlIdTpu22PHhENmPcRdFiHRjhAuHcs05RI=" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/handlebars#latest/dist/handlebars.js"></script>
<script src="script.js"></script>
<!--handlebars template-->
<script id="checkin-template" type="text/x-handlebars-template">
{{#each this}}
<div class="container">
<h1>{{venue_country}}<h2>
<p>{{venue_state}}</p>
<div class="some-other-container">
<h1>{{rating_score}}</h1>
</div>
<ul class="tags">
<li>{{flavor_profiles}}</li>
</ul>
</div>
{{/each}}
</script>
<div class="output"></div>
script.js
$(document).ready(function () {
var request = new XMLHttpRequest();
request.open("GET", "testdata.json", false);
request.send(null);
var data = JSON.parse(request.responseText);
//console.log(data);
//compile template and fill with data
var source = $("#checkin-template").html();
var template = Handlebars.compile(source);
$('.output').append(template(data));
});
This works except for {{flavor_profiles}}. What I want to get rendered is this
<ul class="tags">
<li>malty</li>
<li>biscuity</li>
<li>grainy</li>
<li>clove</li>
<li>banana</li>
</ul>
What I actually get is
<ul class="tags">
<li>malty,biscuity,grainy,clove,banana</li>
</ul>
and I guess it is because I get returned a string of comma separated values instead of an array?
I tried something like this
<script id="checkin-template" type="text/x-handlebars-template">
{{#each this}}
<div class="container">
<h1>{{venue_country}}<h2>
<p>{{venue_state}}</p>
<div class="some-other-container">
<h1>{{rating_score}}</h1>
</div>
<ul class="tags">
{{#each flavor_profiles}}
<li>{{this}}</li>
{{/each}}
</ul>
</div>
{{/each}}
</script>
and I also tried to register some helper function in script.js which did not work. This is a small hobby thing where I am just visualizing something in another way. So I don't want to use further frameworks or server side processing. The JSON gets pulled from elsewhere so I can't change how it looks. Is there any way within Handlebars to render my desired output?
I solved it like this
script.js
var data = JSON.parse(request.responseText, function(key, x) {
if (key === "flavor_profiles") {
x = x.split(',');
return x;
}
return x;
});
HTML
<ul class="tags">
{{#each flavor_profiles}}
<li>{{this}}</li>
{{/each}}
</ul>
I think little bit of j Query can help with your problem. I don't know this is what you expected.
HTML
<script id="checkin-template" type="text/x-handlebars-template">
{{#each this}}
<div class="container">
<h1>{{venue_country}}<h2>
<p>{{venue_state}}</p>
<div class="some-other-container">
<h1>{{rating_score}}</h1>
</div>
<ul class="tags" id="stringToList">
{{flavor_profiles}}
</ul>
</div>
{{/each}}
</script>
<div class="output"></div>
and new function to script.js
$(document).ready(function () {
$("[id='stringToList']").each(function () {
let el = $(this).text().toString().split(",").map(item => `<li>${item}</li>`).join('')
$(this).html(el)
})
});

How Hide data before searching and display when searching with Anguler.js

**So, I have this table that displays users taken from a server in angularjs. Here is the users object:
:**
angular.module('myApp', []).controller('namesCtrl', function($scope) {
$scope.names = [
'Jani',
'Carl',
'Margareth',
'Hege',
'Joe',
'Gustav',
'Birgit',
'Mary',
'Kai'
];
});
Here html :
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.9/angular.min.js"></script>
<div ng-app="myApp" ng-controller="namesCtrl">
<p><input type="text" ng-model="test"></p>
<ul>
<li ng-repeat="x in names | filter:test">
{{ x }}
</li>
</ul>
</div>
You can hide list until user enters something in input like
<ul ng-if="test">
...
</ul>

What does equals in ngRepeat do?

What does the equals in the ng-repeat attribute value mean?
<li ng-repeat="person in people = (people | orderBy: firstname)">
instead of doing:
<li ng-repeat="person in people | orderBy: firstname">
I can't see any examples explaining its use in the documentation for ngRepeat.
It is usefull for count how many objects were filtered, eg.
function People($scope) {
$scope.people = [{
firstname: 'a'
}, {
firstname: 'c'
}, {
firstname: 'b'
}, {
firstname: 'c'
}]
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app ng-controller="People">
<ul>
<li ng-repeat="person in filteredPeople = (people | filter: 'c')">{{person.firstname}}</li>
</ul>
Total filtered: {{ filteredPeople.length }}
</div>
#Krzysztof - There is no requirement of "=" operator usage to show number of objects filtered. It can be done without it. So, you are completely wrong.
<html>
<head>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.4.8/angular.min.js"></script>
</head>
<body ng-app="myApp" ng-controller="myCtrl">
<p ng-repeat="x in people | orderBy: 'age'">{{x.name}},{{x.age}}</p>
<p>Total Filtered: {{people.length}}</p>
<script>
//Module declaration
var app = angular.module('myApp',[]);
//controller declaration
app.controller('myCtrl',function($scope,$timeout){
$scope.people = [{name:"Peter",age:15},{name:"Julie",age:28},{name:"Roger",age:17}];
});
</script>
</body>
</html>

Emberjs - real time changes not working?

I've followed the tutorial of one of the creators of Emberjs: http://www.youtube.com/watch?v=Ga99hMi7wfY
The goal of tutorial is to present basics of emberjs on an example, where there's a list of posts and if you edit one, the content gets mapped to the corresponding textarea or textfield. Then, the changes are visible in real time.
This, however, doesn't work. And I've been following the official tutorial (obviously, emberjs has changed a lot since this yt video).
Anyways, what am I missing?
Here's my code (HTML):
<script type="text/x-handlebars">
<div class="navbar">
<div class="navbar-inner">
<a class="brand" href="#">Bloggr</a>
<ul class="nav">
<li>{{#linkTo 'posts'}}Posts{{/linkTo}}</li>
<li>{{#linkTo 'about'}}About{{/linkTo}}</li>
</ul>
</div>
{{outlet}}
</div>
</script>
<script type="text/x-handlebars" id="about">
<div class="about">
random text about
</div>
</script>
<script type="text/x-handlebars" id="posts">
{{#if isEditing}}
{{partial 'post/edit'}}
<button {{action 'save'}}>Done</button>
{{else}}
<button {{action 'edit'}}>Edit</button>
{{/if}}
<div class="about">
<table>
<tr>
<th>Id</th>
<th>Title</th>
<th>Author</th>
<th>Date</th>
<th>Utilities</th>
</tr>
{{#each model}}
<tr>
<td>{{id}}</td>
<td>{{title}}</td>
<td>{{author}}</td>
<td>{{publishedAt}}</td>
<td>{{#linkTo "post" this}}Details{{/linkTo}}</td>
</tr>
{{/each}}
</table>
</div>
<div>
{{outlet}}
</div>
</script>
<script type="text/x-handlebars" id="post/_edit">
<p>{{view Ember.TextArea valueBinding="title" placeholder="test.."}}</p>
<p>{{view Ember.TextField valueBinding="author"}}</p>
</script>
<script type="text/x-handlebars" id="post">
<h1>{{title}}</h1>
<h2>by {{author}}</h2>
<small>{{date publishedAt}}</small>
</script>
This is the emberjs part:
App = Ember.Application.create();
App.Store = DS.Store.extend({
revision: 12,
adapter: 'DS.FixtureAdapter'
});
App.Router.map(function(){
this.resource("posts", function(){
this.resource("post", { path: ':post_id'});
});
this.resource("about");
});
App.PostsRoute = Ember.Route.extend({
model: function(){
return App.Post.find();
}
});
App.PostsController = Ember.ObjectController.extend({
isEditing: false,
edit: function(){
this.set("isEditing", true);
},
save: function() {
this.set("isEditing", false);
}
});
App.Post = DS.Model.extend({
title: DS.attr('string'),
author: DS.attr('string'),
publishedAt: DS.attr('date')
});
App.Post.FIXTURES = [
{
id:1,
title: "This is my title",
author: "John Doe",
publishedAt: new Date('12-27-2012')
},
{
id:2,
title: "This is another title",
author: "Jane Doe",
publishedAt: new Date('02-03-2013')
}
];
Ember.Handlebars.registerBoundHelper('date', function(date){
return date.getFullYear();
});
Name of controller should be PostController instead of PostsController. They are responsible for handling different routes.
#wedens is right you have a wrongly named controller for the single post, you should also check out this github repo which contains the (correct) source code for that example app build in the video.

Multiple loops fail in Handlebars.js

In my Handlebars template, I am trying to loop over the same data twice, but it fails on the second loop. This is how my template looks:
<div id="placeholder"></div>
<script type="text/x-handlebars" id="people-template">
First loop:<br />
<ul>
{{#.}}
<li>{{name}}</li>
{{/.}}
</ul>
Second loop:<br />
<ul>
{{#.}}
<li>{{name}}</li>
{{/.}}
</ul>
</script>
And this is the JavaScript:
var context= [
{ name: "John Doe", location: { city: "Chicago" } },
{ name: "Jane Doe", location: { city: "New York"} }
];
var template = Handlebars.compile($("#people-template").text());
var html = template(context);
$('#placeholder').html(html);
However, it does not render anything for the second loop:
First loop:
John Doe
Jane Doe
Second loop:
I created a jsFiddle for this here: http://jsfiddle.net/G83Pk/ and have even logged this in as a bug https://github.com/wycats/handlebars.js/issues/408. Does anyone know how to fix this or know what the problem is?
As far as I know, the correct way to iterate over an array is through a each block helper
Your template would be written as
<script type="text/x-handlebars" id="people-template">
First loop:<br />
<ul>
{{#each .}}
<li>{{name}}</li>
{{/each}}
</ul>
Second loop:<br />
<ul>
{{#each .}}
<li>{{name}}</li>
{{/each}}
</ul>
</script>
An updated Fiddle http://jsfiddle.net/nikoshr/G83Pk/1/
<div id="placeholder"></div>
<script id="people-template" type="text/x-handlebars">
First loop:<br />
<ul>
{{#each context}}
<li>{{name}}</li>
{{/each}}
</ul>
Second loop:<br />
<ul>
{{#each context}}
<li>{{name}}</li>
{{/each}}
</ul>
</script>
<script type="text/javascript">
var template = Handlebars.compile($("#people-template").html());
var json = {
"context": [
{ "name": "John Doe", "location": { "city": "Chicago" } },
{ "name": "Jane Doe", "location": { "city": "New York"} }
]
};
var html = template(json);
$('#placeholder').html(html);
</script>
You need to correct your iterator to use the each block helper. And your context variable is invalid input for the each iterator. The above code is the proper way to do what you want.
Not sure with the comments, but I encountered very strange behavior while having similar kind of scenario in my code.
{{#with xxxxx}}
{{#each models}}
{{#with attributes}}
{{value}} ---- Worked here
{{/with}}
{{/each}}
{{#each models}}
{{#with attributes}}
{{value}} ---- Didn't Worked here
{{/with}}
{{/each}}
{{/with}}
It worked with first loop but didnt worked with second loop. I did all scenarios at the ended with some strange solution. If I add any Html script or comment at the end of {{#each models}} of second loop, then second loop regains its context and display values properly.
So this worked flawlessly.
{{#with xxxxx}}
{{#each models}}
{{#with attributes}}
{{value}} ---- Worked here
{{/with}}
{{/each}}
{{#each models}} {{! Comment added }}
{{#with attributes}}
{{value}} ---- It works now.
{{/with}}
{{/each}}
{{/with}}

Categories