Handlebars - Nest objects in #each - node.js

I use express-handlebars with Node JS and I am trying to display informations from the array games on my HTML page using #each. But handlebars won't display the values inside my object. Even if it is not a nested object.
But if I only write {{this}}, it displays all the object like if it was a string. And if I do {{this.time}}, nothing is displayed.
I'm using Node JS v14.18.1, express v4.17.2 and express-handlebars v6.0.2.
Thank you in advance for your help !
getHomePage: async (req, res) => {
try {
const games = await gameModel.find().sort({time: -1}).limit(3).populate('user');
res.render('home', {title: 'Hello world', games});
} catch (error) {
console.error(error);
res.status(500).json({error});
}
}
<table>
<thead>
<th>Position</th>
<th>Score</th>
<th>Nom</th>
</thead>
<tbody>
{{#each games}}
<tr>
<td>#{{#index}}</td>
<td>{{this}}</td>
<td>{{this.time}}</td>
</tr>
{{/each}}
</tbody>
</table>

Either {{this.time}} or {{time}} should work, see snippet with output below, {{this.team}} and {{time}} is used.
If time still shows nothing, try confirming games is defined as expected.
// shortened data for brevity
const data = {
games: [
{team: 'Man U', time: '10am'},
{team: 'Arsenal', time: '1pm'}
]
};
//inline template for snippet simplicity
const template = '' +
'<table>' +
'<thead>' +
'<tr>'+
'<th>#</th>' +
'<th>Team Name</th>' +
'<th>Time</th>' +
'</tr>' +
'</thead>' +
'<tbody>' +
'{{#each games}}' +
'<tr>' +
// BEGIN
'<td>{{#index}}</td>' +
'<td>{{this.team}}</td>' +
'<td>{{time}}</td>' +
// END
'</tr>' +
'{{/each}}' +
'</tbody>' +
'</table>';
var output = Handlebars.compile(template)(data);
console.log(output)
// for snippet simplicity
$('body').html(output);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/handlebars.js/4.7.7/handlebars.min.js"></script>

I thought the problem came from Handlebars or NodeJS. But it came from my MongoDB request.
I resolved it by adding .lean() at the end of my Mongo request. By default, Mongo returns a document, not a javascript object. So we have to "convert" it to a javascript to allow handlebars to display it:
gameModel.find().sort({time: -1}).limit(3).populate('user').lean()

Related

How do I rendering views based on multiple queries in Express?

I have a collection with various data that I would like to use when rendering my ejs page. I only seem to be able to render contents from ONE query and not other queries.
Kindly help me figure out how to render the contents from other queries.
Find below some of the contents of my collection:
Find below my rendered ejs page
Pay special attention to the Unidentified in the Transactions from: Unidentified county sentence.
Now... lets take a look at the code that generates this page:
function county (req, res) {
transModel.findOne({ transCounty : /Nairobi/i,
})
.limit(5)
.select({ transCounty:1})
.exec( (err, result)=> {
if (err) throw err;
console.log('>> ' +result);
console.log('We in county function!');
return result;
});
};
app.get('/list', async (req,res)=> {
console.log('## ' +county());
transModel.find({transIndustry: 'Pharmacy'}, (err, docs)=> {
if (!err)
{
res.render('list', {data : docs, countyName: county});
}
else
{
// res.status(status).send(body);
}
})
});
In the console, the code above logs:
## undefined
>> { _id: 609f7ed8fe1fd8b3193b0b77, transCounty: 'Nairobi' }
We in county function!
And now find below the ejs file contents:
<body>
<h1>
Transactions
</h1>
<p> Transactions from: <b> <%= countyName.transCounty %> </b> County: </p>
<table>
<tr>
<th> _id </th>
<th> Transaction Amount </th>
<th> Transaction Industry </th>
</tr>
<% data.forEach(function(entry) {%>
<tr>
<td> <%=entry._id%> </td>
<td> <%=entry.transAmount%> </td>
<td> <%=entry.transIndustry%> </td>
</tr>
<%});%>
</table>
Kindly help me understand where I am going wrong and how I can get the <%= countyName.transCounty %> to display Nairobi in my rendered ejs file
The main difference between docs and county is that docs is the results from a database-query (ok!), while county is a function which you only reference to and never run (problem 1), but if you were to run it still returns nothing (problem 2), but it does run a query, so you have that going for you!
so, not the complete solution, but just to point you in the right direction:
You need to call county at some point using county() with parenthesises.
Since the nature of database queries is that they are asynchronous you need to either use a callback pattern or a promise-based solution.
A callback solution could look like:
// Here we are calling the function, and the entire function-argument is the callback.
county((countyResult /* the callback will be called with result in the future */) => {
// wrap your existing code,
// because the result will only be available inside the callback
transModel.find({transIndustry: 'Pharmacy'}, (err, docs) => {
...
res.render('list', {data : docs, countyName: countyResult});
});
});
And the called function could look something like this:
function county (callback /* accept a callback */) {
transModel.findOne({ transCounty : /Nairobi/i })
.limit(5)
.select({ transCounty:1})
.exec((err, result) => {
if (err) throw err;
callback(result); // Use the callback when you have the result.
});
}
Because the need to wrap callbacks the result never becomes very pretty.
Instead you could use promises. for example:
let countyResult = await county();
let docs = await transModel.find({transIndustry: 'Pharmacy'}).exec();
res.render('list', {data : docs, countyName: countyResult});
And the called function could look something like this:
function county (req, res) {
return transModel
.findOne({ transCounty : /Nairobi/i })
.limit(5)
.select({ transCounty: 1})
.exec(); // If you use exec without a callback it'll return a promise.
};
This is much cleaner but error handling needs to be done using try/catch.
For example:
let countyResult;
try {
countyResult = await county();
catch(err) {
throw err;
// Of course, if all you do is throw, then you don't even need the `try/catch`.
// Try/catch is to catch thrown errors and handle them.
}
You still might want to check that the result contains anything. A query with no data found is not an error.
Disclaimer: The code is for describing the flow. I've tried to get it right, but nothing here is tested, so there is bound to be errors.

how can i retrieve objectId of parse server through req.params.id

I am trying to retrieve a parse object with objectId in the show route on nodeJS. Below is my code to help you understand better.
//SHOW route
app.get("/books/:id", function(req, res) {
var Books = Parse.Object.extend("Books");
var query = new Parse.Query(Books);
query.equalTo("objectId", req.params.id);
query.find().then(function(foundBook){
res.render("show", {book: foundBook});
}, function(error) {
res.send("Error: " + error.code + " " + error.message);
});
});
Basically, The req.params.id does not return the objectID. when i try console.log(req.params.id), it returns the Title of the book stored in the database instead of the objectId which is important for linking to the /books/:id page.
Even when i try to retrieve all the objects from the database in the index route, i noticed that <%= book.get('objectId') %> is not displayed on the ejs page.
Please help me out of this. i am a beginner MEAN stack web developer but i am using parse server because the android and web applications would be sharing the same database on parse.com.
Thank You.
<% books.forEach(function(book) { %>
<div class="col-md-3 col-sm-6">
<div class="thumbnail">
<!-- this line of code gets the image content of the array and puts it in the img tag -->
<img src="<%= book.get('coverPictureLink') %>">
<div class="caption">
<h4><%= book.get('Title') %></h4>
</div>
<p>
<!-- This code adds the button and links it to the ID of the campground that was clicked on!-->
More Info
</p>
</div>
</div>
<% }); %>
</div>
Above is sample of the html page for displaying details of a particular book
I finally figured it out. The right way to retrieve object id on the html is book.id not book.get("objectId").
app.get("/books/:id", function(req, res) {
//find the book with provided ID
var Books = Parse.Object.extend("Books");
var query = new Parse.Query(Books);
query.get(req.params.id).then(function(book) {
console.log('retrieved! ' + book.id);
res.render('show', {book: book});
}, function(error) {
console.log('error occured');
res.send('could not be retrieved');
});
});
On the html file,
<p>
More Info
</p>
This is also the same if you are using node.js. with the parse server framework. Using .get('objectId') returns undefined values. Therefore you have to use.
for (i = 0; i < result.length; i++){
console.log('ID:' + result[i].id)
}

Can a lodash template be used in Node.js to send HTML version of an email?

I am using Node.js to send a confirmation email when a user submits an order. I would like the email to include include all items that the user submits. The number of items will vary with each submission. I'd like the email body to include a table. Can a lodash template be used for this? Or should this be handled differently?
When I used the following code, the resulting email includes what I assume to be uncompiled code.
var tpl = _.template('<% _.forEach(items, function(item) { %><td><%- item %></td><% }); %>');
tpl({ 'items': ['Guitar', 'Harmonica'] });
var data = {
from: 'support#example.com',
to: email,
subject: 'Your Order Confirmation',
html: '<p>Thank you for submitting your order.</p>
<table>
<tr>
<thead>
<tr>
<th><strong>Items</strong></th>'
+ tpl +
// The template should insert each item here
// <td>Guitar</td>
// <td>Harmonica</td>
'</tr>
</thead>
</tr>
</table>'
};
Output in actual email sent:
function (obj) { obj || (obj = {}); var __t, __p = '', __e = _.escape,
__j = Array.prototype.join; function print() { __p +=
__j.call(arguments, '') } with (obj) { _.forEach(items,
function(item) { ; __p += ' ' + __e( item ) + ' '; }); ; } return
__p }
change:
tpl({ 'items': ['Guitar', 'Harmonica'] });
to
var html = tpl({ 'items': ['Guitar', 'Harmonica'] });
and
ng>Items</strong></th>'
+ tpl +
to
ng>Items</strong></th>'
+ html +
if you look at the docs at: https://lodash.com/docs#template
you will see the compiled function returns an output which you need to use.
You instead of using the output, you used the actual function itself.

How to access elements in the current template in meteor?

I have a template like this,
<template name = "foo">
<p id="loading" >LOADING...</p>
<p> {{theResult}} </p>
</template>
This is how I create foos,
// foos = [a, b, c, d]. With each button click I add a new item to the array
{{#each foos}}
{{> foo .}}
{{/each}}
And how a foo works,
Template.foo.created = function(){
var name = Template.currentData();
api_call(name, function(err, result){
Session.set(name, result);
});
}
Template.foo.helpers({
'theResult': function(){
var name = Template.currentData();
if(Session.get(name)) {
$("#loading").hide();
return Session.get(name);
} else {
return "";
}
}
})
So my expectation is to when the data came from the api_call, to hide "LOADING..." para, and to show the result in theResult.
The result is showing correctly. My problem is "LOADING..." is only get hidden on the top most foo. Not the other ones.
How can I fix this?
EDIT:
As suggested instead of,
$("#loading").hide();
I used,
Template.instance().$("#loading").hide();
This didn't work too :)
This is how I'd do it
Template... if theResult is undefined, the else path will be rendered.
<template name="foo">
{{#with theResult}}<p> {{this}} </p>
{{else}}<p id="loading" >LOADING...</p>
{{/with}}
</template>
Javascript... theResult is a simple Session.get call
Template.foo.helpers({
theResult: function(){
var name = Template.currentData();
return name && Session.get(name);
}
});
Thanks to Meteor templating engine, you can access a template scoped jQuery object that will only return elements within the corresponding template.
Template.foo.helpers({
'someText': function(){
var template = Template.instance();
template.$('p').changeSomeattr();
return Session.get('myPara');
}
});

Unable to get Meteor-Pagination to work

I am trying to add pagination support to my meteor app but my template shows a blank page as soon as I add the {{{pagination}}} tag in my template. There are no errors in the log.
My client js (routing info) looks like this
Meteor.Router.add({
'/': function () {
var user;
if (Meteor.loggingIn()) {
console.log('home: loading');
return 'loading';
}
user = Meteor.user();
if (!user) {
console.log('homer: signin');
return 'user_signin';
}
// start on 'start' page
console.log('home: start');
return 'page';
},
'/landing': 'landing',
'*': 'not_found',
'/landing/:page': function (page) {
Session.set('page', page) ;
return 'landing' ;
}
});
My Landing.js looks like this
Template.userList.pagination = function () {
return Pagination.links('/landing', Meteor.users.find({}).count(), {currentPage: Session.get('page'), perPage: 8}) ;
}
My landing template is as follows:
</thead>
<tbody>
{{#each users}}
{{> user}}
{{/each}}
{{{pagination}}}
</tbody>
</table>
I see a couple things with the code posted.
I don't know much about the Pagination addin but it looks like you have it inside the table tag and according to this (https://github.com/egtann/meteor-pagination) it renders a div. I believe that would be invalid.
In your routes you have the wildcard '*' before the 'landing/:page'. I believe it would match that one first. Should have the '*' be the last route you add.

Resources