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

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

Related

Passing data using Handlebars.js

I've created a basic app to test out handlebars.js and I'm struggling to pass data between my pages. What I want is to have a list of countries being read from a JSON file, then the user can click on a country to be taken to more detail. The data loads fine on the first page but then when I click onto the link, it takes me to the next page were the data isn't loading in.
Could someone explain where I'm going wrong?
First page:
<div data-page="countrylist" class="page">
<div class="page-content">
<div class="content-block-title">Choose a country:</div>
<div class="list-block">
<ul>
{{#each this}}
<li>
<a href="detail.html" class="item-content item-link" data-context-name="countrylist.{{#index}}​">
<div class="item-inner">
<div class="item-title">{{Country}}​</div>
</div>
</a>
</li>
{{/each}}
</ul>
</div>
</div>
</div>
Detailed page:
<div data-page="countrylist" class="page">
<div class="page-content">
<div class="content-block inset">
<div class="content-block-inner">
<h3>{{Country}}​</h3>
<ul>
<li>Region: {{Region}}</li>
<li>Population: {{Population}}</li>
<li>Area Sq. Mile: {{AreaSqMile}}</li>
<li>Population Density per sq. Mile: {{PopulationDensity}}</li>
<li>NetMigration: {{NetMigration}}</li>
<li>GDP $ per capita: {{GDP}}</li>
<li>Phones per 1000: {{Phones}}</li>
<li>Arable %: {{Arable}}</li>
<li>Climate: {{Climate}}</li>
<li>Birthrate: {{Birthrate}}</li>
<li>Deathrate: {{Deathrate}}</li>
<li>Agriculture: {{Agriculture}}</li>
<li>Industry: {{Industry}}</li>
<li>Service: {{Service}}</li>
</ul>
</div>
</div>
</div>
</div>
Javascript:
Template7.registerHelper('json_stringify', function (context) {
return JSON.stringify(context);
});
var myApp = new Framework7({
animateNavBackIcon: true,
// Enable templates auto precompilation
precompileTemplates: true,
// Enabled pages rendering using Template7
template7Pages: true
// Specify Template7 data for pages
});
// Export selectors engine
var $$ = Dom7;
function getCountries() {
$$.getJSON('../countries.json', function (json) {
myApp.template7Data.countrylist = json ;
});
};
getCountries();
// Add main View
var mainView = myApp.addView('.view-main', {
// Enable dynamic Navbar
dynamicNavbar: true,
});

Compile error for Angular JS with js/ajax.js and index.html

When I compile my code there is an error with angular.
The code is written from a book, and it works when I load the page using the code at the end of the question.
There seems to be two issues from what I have read. Where do I put the $scope and $compile code?
Mostly, the question is how do I load a document ready trigger with a button for angular JS?
Or should I always load the angular js and hide the code?
<div id="myTabs">
<div class="menu">
<ul class= "tabs">
<li >LIST</li>
<li >GRID</li>
<li >GRID</li>
</ul>
</div>
<div class="container" style="margin-top:100px">
<div id="a">
</div>
<div id="b">
<ul class="gallery-items" id="grid">
</ul>
</div>
<div id="c" >
<div ng-controller="myController">
<div ng-repeat="item in items">
<img ng-src="{{item.img}}" />
{{item.description}}<br>
Rating: {{item.rating}} stars<br>
<span ng-repeat="idx in stars"
ng-class=
"{true: 'star', false: 'empty'}[idx <= item.rating]"
ng-click="adjustRating(item, idx)">
</span>
<hr>
</div>
</div>
</div>
</div>
ajax.js has a function that calls the #c tab to load
$(document).ready(function(){
$('#listC').click(function(){
angular.module('myApp', [])
.controller('myController', ['$scope', function($scope) {
$scope.stars = [1,2,3,4,5];
$scope.items = [100];
$.ajax({
type:'post',
url:'changeView.php',
data:{action: 'getGrid'},
success:function(data){
var data = JSON.parse(data);
for (i=0;i<data.length;i++) {
var imageLI = makeImage(data[i]['imageID'], data[i]['name'], data[i]['desc']);
$scope.items[i] = imageLI;
}
}
});
console.log($scope.items);
$scope.adjustRating = function(item, value){
item.rating = value;
};
}]);
});
$('#listC').trigger('click');
function makeImage(ID, name, description){
var image = {
description: description,
img: '../uploads/'+name,
rating: 4
}
return image;

renderWith output visible in console, but does not update browser

I'm unable to get my controller to properly render the results of an AJAX request in my browser. I receive the correct html response using the specified Include for Ajax requests, but it is only visible in my browser's console. The page itself does not render the new content. What am I missing?
Here is my simplified controller for searching products:
class ProductSearch_Controller extends Page_Controller {
public function index(SS_HTTPRequest $request) {
// external api request, parsing response, returning as ArrayList $units
$return_data = array(
'Products' => $units
);
if($request->isAjax()) {
return $this->customise($return_data)->renderWith('SearchResults');
} else {
return $return_data;
}
}
}
The index method actually pulls in json data from an external source, parses it, and then passes it into an ArrayList, $units, which is then made available to the template as Products. Here is the simplified ProductSearch.ss template:
<section>
<div class="container">
<div class="productsearch__container">
<aside>$SearchForm</aside>
<main>
<h2>Search Results</h2>
<% include SearchResults %>
</main>
</div>
</div>
</section>
Here's the SearchResults template Include:
<div class="product__container">
<% loop $Products %>
<a href="$URL" class="product">
<h3>Unit $UnitNo</h3>
<ul>
<li>Price: $Lot_Price</li>
<li>SQFT: $SQFT</li>
<li>Bedrooms: $bedrooms_no</li>
<li>Baths: $baths_no</li>
</ul>
</a>
<% end_loop %>
</div>
And here is my simplified javascript:
$("[name='example-filter-variable']").on('change', function(e) {
e.preventDefault();
var params = $( "#Form_SearchForm" ).serialize();
$.get("url/example?" + params, function(data) {
console.log("Data: " + data);
});
});
Triggering the ajax event returns the appropriate response using the SearchResults.ss Include, which is visible in my console.log() output:
<div class="product__container">
<a href="/homes/search/show/6/101" class="product">
<h3>Unit 101</h3>
<ul>
<li>Price: </li>
<li>SQFT: 1531</li>
<li>Bedrooms: 2</li>
<li>Baths: 2</li>
</ul>
</a>
<a href="/homes/search/show/12/102" class="product">
<h3>Unit 102</h3>
<ul>
<li>Price: </li>
<li>SQFT: 1535</li>
<li>Bedrooms: 2</li>
<li>Baths: 2</li>
</ul>
</a>
... and so on
</div>
The problem is the template is actually being rendered in the browser. Am I doing something wrong?
This line in the template: <% include SearchResults %> is causing your template to include the SearchResults template.
You also don't have anything in your javascript to append your data into the DOM.
Try this template:
<section>
<div class="container">
<div class="productsearch__container">
<aside>$SearchForm</aside>
<main>
<h2>Search Results</h2>
<div class="results"></div>
</main>
</div>
</div>
</section>
With this js:
$("[name='example-filter-variable']").on('change', function(e) {
e.preventDefault();
var params = $( "#Form_SearchForm" ).serialize();
$.get("url/example?" + params, function(data) {
console.log("Data: " + data);
$(".results").append(data); // add something along these lines
});
});

to do list won't show data from collection that i ran trough filter

this is my code(i don't know where is the error for two days already.). And when I put .task and .Done helpers it won't show templates anymore. I know I have an error somewhere but I cannot pin point it for two days already. What I want is to pull tasks from database run them trough filter. Thank you in advance for your help.
<template name="Done">
<li>
<div>
<span class="text">{{title}}</span>
</div>
</li>
</template>
<template name="task">
<li>
<button class="completed">Completed</button>
<!--<li><input type="text" name="task" class="edit"></li>!-->
<span class="text" onclick="true">{{title}}</span>
<button class="saveItem">Save</button><button class="cancelItem">Cancel</button>
<button class="editItem">Edit</button><button class="delete">Delete</button>
</li>
<div>
<h1>To do list</h1>
<ul>
{{#each tasks}}
{{>task}}
{{/each}}
</ul>
</div>
<div>
<h1>Done list</h1>
<ul>
{{#each tasks}}
{{>Done}}
{{/each}}
</ul>
</div>
.js file
Meteor.subscribe("tasks");
Template.body.helpers({
tasks: function(){
return Tasks.find();
}});
Template.Done.helpers({
taskDone: function () {
return Tasks.find({}, {fields: {completed: "yes"}});
}
});
Template.task.helpers({
taskNotDone: function(){
return Tasks.find({completed: "no"});
}
});
Meteor.publish("tasks", function () {
return Tasks.find({});
});
First of all, I strongly recommend to store the completed field as Boolean values instead of String values. Secondly, the helper functions taskDone and taskNotDone should belong to the body template. In addition, the fields modifier is usually used to limit certain fields for publishing and not for querying documents.
For example, if you want to publish all tasks, minus the completed info, the publish function would look like this:
Meteor.publish("tasks", function () {
return Tasks.find({}, {fields: {completed: 0}});
});
Instead, if you want to return only Tasks documents which have been completed, you would have the following query:
Tasks.find({completed: true});
This code may fix your problem:
<body>
<div>
<h1>To do list</h1>
<ul>
{{#each tasksToDo}}
{{>task}}
{{/each}}
</ul>
</div>
<div>
<h1>Done list</h1>
<ul>
{{#each taskDone}}
{{>done}}
{{/each}}
</ul>
</div>
</body>
<template name="done">
<li>
<div>
<span class="text">{{title}}</span>
</div>
</li>
</template>
<template name="task">
<li>
<button class="completed">Completed</button>
<span class="text" onclick="true">{{title}}</span>
<button class="saveItem">Save</button>
<button class="cancelItem">Cancel</button>
<button class="editItem">Edit</button>
<button class="delete">Delete</button>
</li>
</template>
if (Meteor.isClient) {
Template.body.helpers({
tasksToDo: function() {
return Tasks.find({
completed: false
});
},
taskDone: function() {
return Tasks.find({
completed: true
});
}
});
}
Here's a MeteorPad.

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