Nodejs and Handlebars - list duplicating on refresh, instead of refreshing - node.js

I've been playing around with Handlebars templating. Current practice is a simple todo list. On first load it's just fine, but if I refresh my page, it doesn't actually refresh. Instead, it duplicates the results of my GET and appends them to the list.
So, first time the list is like:
Get groceries
Take kids to soccer
Then upon refresh I get:
Get groceries
Take kids to soccer
Get groceries
Take kids to soccer
index.js GET method
app.get('/', (req, res) => {
let query = "select * from todos";
db.query(query, function(err, rows) {
if (err) throw err;
rows.forEach(function(todo) {
todos.push(todo.todo);
console.log(todo.todo)
})
res.render('index', {
todos
});
})
});
index.hbs
<h2>Here's some Todos</h2>
<ul id="list">
{{#each todos}}
<li>{{this}}</li>
{{/each}}
</ul>

It seems todos is a global variable, or valid outside of the for loop scope. For each query it adds the existing db items in todos. Before calling the loop make sure the todos is an empty list:
if (err) throw err;
todos = [];
rows.forEach(function(todo) {
todos.push(todo.todo);
console.log(todo.todo)
})

Related

Button in Node.js project works properly after two clicks not one

I'm making a simple project with node.js, express, mongodb, express session etc. It is a travel type site and a user in the database has a username, password and a list of destinations he wants to go to. Each destination page has a button to add that destination to the list and there is also a page which shows the current user's list. The proper functionality is that on the first button press the destination should be added to the users list in the database and the ejs view for the list page should be updated and on the second button press an alert should come up saying that the current destination is already on the user's list. However, seemingly randomly the button sometimes only works properly after two button presses and the alert and ejs view update of the list page occur on the third button press. Here are some code snippets:
This is my get request for the list page where I pass the current session user's want to go list as a parameter:
function isAuthenticated (req, res, next) {
if (req.session.user) next()
else res.redirect('/');
}
app.get('/wanttogo',isAuthenticated,function(req,res){
res.render('wanttogo',{dests:req.session.user.wantgo})
});
I update this list both in the session and in my database in each page's post request. This is an example for one of the pages:
app.post('/inca',function(req,res){
if((req.session.user.wantgo.length === 0) || !(req.session.user.wantgo.includes("Inca Trail to Machu Picchu"))){
req.session.user.wantgo.push("Inca Trail to Machu Picchu");
req.session.save();
db.collection("myCollection").updateOne({username:req.session.user.username},{$set:{wantgo:req.session.user.wantgo}});
db.collection("myCollection").findOne({username: req.session.user.username},(err,result)=>{
req.session.user.wantgo = result.wantgo;
req.session.user=result;
req.session.save();
});
}
else{
alert('This destination is already on your Want-To-Go List');
}
res.redirect('/inca');
and this is the part of the ejs view of the list page where I loop over the user's list and print it using the parameter I passed earlier:
<div>
<% for(var i=0; i < dests.length; i++) { %>
<h1><%= dests[i] %></h1>
<% } %>
</div>
Any help is appreciated!
This could be due to the fact "updateOne" and "findOne" are async functions and thus the database is not updated before you do the second button press.
Since this depends on time when the async operation is completed it sometimes works and sometimes doesn't.
Try using async await like this
app.post('/inca',async function(req,res){
if((req.session.user.wantgo.length === 0) || !(req.session.user.wantgo.includes("Inca Trail to Machu Picchu"))){
req.session.user.wantgo.push("Inca Trail to Machu Picchu");
req.session.save();
await db.collection("myCollection").updateOne({username:req.session.user.username},{$set:{wantgo:req.session.user.wantgo}});
await db.collection("myCollection").findOne({username: req.session.user.username},(err,result)=>{
req.session.user.wantgo = result.wantgo;
req.session.user=result;
req.session.save();
});
}
else{
alert('This destination is already on your Want-To-Go List');
}
res.redirect('/inca');

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.

Express JS Display Data By ID

I use mongodb with collection structure as follows:
chatbot collection
{
"_id" : ObjectId("5a2f8edf84b906480af0d121"),
"botname" : "Welcome Chat",
"description" : "Welcome Chat",
"status" : "Inactive"
}
route.js
app.get('/cpanel/chat-bot/:id', function(req, res) {
if (req.session.user == null) {
res.redirect('/cpanel/login');
} else {
CB.getAllRecords().then(results => {
res.render('cpanel/chat-bot/:id', { udata : req.session.user, chatbot: results});
}).catch(err => {
res.sendStatus(500);
});
}
});
index.ejs
<ul class="menu-sidebar">
<% for(var i = 0; i < chatbot.length; i++) { %>
<li>
<span class="fa fa-circle-o"></span><%= chatbot[i].botname %>
</li>
<% } %>
</ul>
how to display data chatbot by id from mongodb? when I click url 1 (/cpanel/chat-bot/1) it will show data id 1 from mongodb, when I click url 2 (/cpanel/chat-bot/2) it will show data id 2 from mongodb? Thank you
When you configure a route like
app.get('/cpanel/chat-bot/:id', ...)
The :id part of the route says that you want to match any value for that part of the path and then Express will put whatever was matched into req.params.id. So, if you want to use that id value as part of your database query, you need to use req.params.id in your database query in order to select only the desired data from your database.
In addition, you do NOT use :id in the render path so remove it from here:
res.render('cpanel/chat-bot/:id', ...);
That should just be a path to your template file (whatever the filename of the template is):
res.render('cpanel/chat-bot', ...);
You don't disclose much about your data in the database so we can't really help with how you would use the req.params.id value to select the desired data from your database. I presume you would use it in a query of some sort.

HandleBars - How to Iterate 2 Collections

I know how to iterate a single collection, but how do I iterate multiple collections using Handlebars?
Below is my routing file
EmployeePayslip.find({"employeeName":""+name}, function(err, db_payslip){
if(err) throw err;
SalaryItems.find({"employeeName":""+name}, function(err,db_salaryItems){
if(err) throw err;
res.render('AdminView/pages/examples/payslip-details',{
payslip:db_payslip,
salaryItem:db_salaryItems
});
});
});
});
How do I do something like below,
{{each payslip}}
<label>{{employeeName}}</label>
{{each salaryItem}}
<label>{{item}}</label>
{{/each}}
{{/each}}
If I only use one each, the iteration works, but if I nested it, it does not seem to display the data. How do I display the data from both collection?

nodejs handeling a select form element

I have a (probably very stupid) question.
I have a form I submit to my nodeJS server with Express.
This works perfectly with text inputs and radiobuttons, but now I have to add a select.
The server does not give an error but the select is not parsed properly.
my code:
<select id="chooselang">
<option value="nl" name="language">NL</option>
<option value="en" name="language">EN</option>
</select>
and my server looks like this:
app.post('/settings', function(req, res){
// Fill JSON array with new settings
var myData = {
,name : req.body.name
,mail : req.body.email
,language : req.body.language
,location: req.body.location
}
// Write to JSON file
fs.writeFile(configfilepath, JSON.stringify(myData, null, 4), function(err) {
if(err) {
res.send(500);
console.log(err);
} else {
setTimeout(function () {
res.redirect('back');
}, 2000)
}
});
});
Could someone please tell me what I'm doing wrong?
Probably you will need to add a name attribute in your select and use that to capture the values.
Also, only one of the option values will be sent to the server, so there is no point in assigning name to each one of the options, if that was your intention.

Resources