EJS cannot read property of undefined, how can I fix this? - node.js

My site has broken between now and last night, even though I didn't change anything. I'm making a site which grabs weather data. It's based directly on a medium article here: https://codeburst.io/build-a-weather-website-in-30-minutes-with-node-js-express-openweather-a317f904897b
Here's the server js content...
app.post('/', function (req, res) {
let city = req.body.city;
let country = req.body.country;
let url = `http://api.openweathermap.org/data/2.5/weather?q=${city}${
country.length > 0 ? "," + country : ""
}&units=imperial&appid=${api}`
request(url, function(err, response, body){
if (err) {
res.render('index',
{weather: null, error: "Please Enter Valid Location"});
}
else {
let weather = JSON.parse(body)
if (weather.main == undefined) {
res.render('index',
{weather: null, error: "Please Enter Valid Location"});
}
else {
let content = {
temp: weather.main.temp,
city: weather.name,
condition: weather.weather[0].description,
icon: "svgs/" + weather.weather[0].icon + ".svg"
}
res.render('index', {weather: content, error: null});
}
}
})
});
And here's the corresponding EJS templates...
<% if (locals.weather !== null){ %>
<p><%= locals.weather.city %></p>
<p><%= locals.weather.condition %></p>
<% } %>
<% if(error !== null){ %>
<p><%= error %></p>
<% } %>
It worked last night, but now it's saying "Cannot read property 'city' of undefined".

Related

Using Each Loop in EJS Template and Express JS

I am working on a form validation and i am trying to loop the errors in my template but i keep getting error.
In my posts routes i have this
router.post('/create', async (req, res)=>{
try{
var errors = [];
if(!req.body.title){
errors.push({message: 'Please Add a Title'})
}
if(errors.length > 0){
res.render('admin/posts/create',{errors: errors})
} else{
let filename = 'Nissan.jpeg';
if(!isEmpty(req.files)){
const file = req.files.file
filename = Date.now() + '-' + file.name
file.mv('./public/uploads/' + filename, (err)=>{
if(err) throw err
})
}
let allowComments = true;
if(req.body.allowComments){
allowComments = true;
} else{
allowComments = false;
}
const newPost = await new Post({
title: req.body.title,
status: req.body.status,
allowComments: allowComments,
body: req.body.body,
file: filename
});
const savedPost = await newPost.save();
// console.log(savedPost);
res.redirect('/admin/posts');
}
} catch (error){
console.log(error);
}
});
And in my ejs template with url (admin/posts/create) i have this
<% errors.forEach(errors => { %>
<%-errors.message %>
<% }) %>
But i keep getting this error message in my browser
ReferenceError: /blog/views/admin/posts/create.ejs:2
1| <%- include('../admin-partials/head') %>
>> 2| <% errors.forEach(error => { %> <%-error.message %> <% }) %>
3| <div class="dashboard-col-2">
4| <h1>Create Post</h1>
5|
errors is not defined
What can i do to solve this error message?
When i do console.log(errors) i get this
[{ message: 'Please Add a Title' } ]
if(errors.length > 0){
res.render('admin/posts/create',{errors: errors})
You are only rendering errors if it's > 0, otherwise it's undefined.

How to implement pagination with mongoose and EJS and keep the search query when paging?

Im using Nodejs, EJS, Mongoose and MongoDB and i have a table that is created from the documents in my DB and cant get paging buttons to work without clearing my search query.
The way my app works is
Click on the search link which opens a search filter page.
My Search page
Then you select you filer and search. Results are then shown. With Search Query in URL
Searched Results with Search Query
3.When you click on the next page it clears your query.
Page buttons
Here is my paging buttons and below is my route
My search filters are on another page.
<div class="container">
<nav aria-label="...">
<ul class="pagination float-right">
<li class="page-item disabled">
<span class="page-link">Previous</span>
</li>
<li class="page-item active">
<a class="page-link" name="1" href="/searched/1">1</a>
</li>
<li class="page-item">
<a class="page-link" name="2" href="/searched/2">2</a>
</li>
<li class="page-item">
<a class="page-link" name="3" href="/searched/3">3</a>
</li>
<li class="page-item">
<a class="page-link">Next</a>
</li>
</ul>
</nav>
</div>
app.get("/searched/:page/:limit", function (req, res) {
if (req.isAuthenticated()) {
// const { page, limit } = req.params;
// const options = {
// sort: { dateAdded: -1 },
// page: page,
// limit: limit,
// };
const query = req.query;
if (query.btu === "") {
delete query.btu;
}
if (query.sn1 === "") {
delete query.sn1;
}
if (query.sn2 === "") {
delete query.sn2;
}
if (query.userCreated === "") {
delete query.userCreated;
}
if (query.split === "") {
delete query.split;
}
if (query.supplier === "") {
delete query.supplier;
}
if (query.issued === "") {
delete query.issued;
}
// Aircon.paginate(query, options, function (err, foundAircons) {
// if (err) {
// console.log(err);
// } else {
// console.log(foundAircons);
// res.render("instock", {
// foundAircons: foundAircons.docs,
// });
// }
// });
Aircon.find(query)
.sort({
dateAdded: "desc",
})
.exec((err, foundAircons) => {
if (err) {
console.log(err);
} else {
res.render("instock", {
foundAircons: foundAircons,
});
}
});
} else {
res.redirect("/login");
}
});
Actually, your structure looks unfamiliar to me. I'm not sure have you ever heard "pagination token" term. If you didn't you can check this magnificent guide.
I wrote searching endpoint with parameters like searchTerm, limit and pageToken to paginate. pageToken is important. If you want to go page: 2 for example. page token should be first record after the last record of the first page results. I used _id parameter in this example
Note: Creating index is mandatory for filter the records with searchTerm. Index creation is like this:
await db.collection(feedSettings._collection).createIndex({ "$**": "text" }, { name: "TextIndex" });
Code:
exports.pagination = async (req, res, next) => {
const db = await database.mongo;
const feedSettings = req.feedSettings;
// Query parameters
const limit = parseInt(req.query.limit) || 100;
let searchTerm = req.query.searchTerm;
let pageToken = req.query.pageToken;
const query = { _feedName: feedSettings.name };
// Start from last item
let paginatedQuery = {
_feedName: feedSettings.name,
_id: { $gt: ObjectID(pageToken) },
_trashed: { $ne: true }
}
// If we don't have a pageToken start from first item
if (!pageToken) {
let firstFeed = await db.collection(feedSettings._collection).findOne(query, { projection: { _id: 1 } });
if (!firstFeed) {
return res.status(200).json({
success: 1,
data: []
});
}
paginatedQuery._id = { $gte: ObjectID(firstFeed._id) };
}
// If user doesn't want to search a term in items
if (typeof searchTerm === 'string') {
await db.collection(feedSettings._collection).createIndex({ "$**": "text" }, { name: "TextIndex" });
paginatedQuery.$text = { $search: searchTerm };
}
feedsData = await db.collection(feedSettings._collection)
.find(paginatedQuery)
.limit(limit)
.toArray();
return res.status(200).json({
success: 1,
data: feedsData
});
}
managed to get it working as well with mongoose paginate v2 and found a function to rebuild the query string and pass back to the buttons
function objectToQueryString(obj) {
var str = [];
for (var p in obj)
if (obj.hasOwnProperty(p)) {
str.push(encodeURIComponent(p) + "=" + encodeURIComponent(obj[p]));
}
return str.join("&");
}
app.get("/searched", function (req, res) {
if (req.isAuthenticated()) {
const { page } = req.query;
const options = {
sort: { dateAdded: -1 },
page: !page ? 1 : page,
limit: 20,
};
const query = req.query;
delete query.page;
if (query.btu === "") {
delete query.btu;
}
if (query.sn1 === "") {
delete query.sn1;
}
if (query.sn2 === "") {
delete query.sn2;
}
if (query.userCreated === "") {
delete query.userCreated;
}
if (query.split === "") {
delete query.split;
}
if (query.supplier === "") {
delete query.supplier;
}
if (query.issued === "") {
delete query.issued;
}
var queryString = objectToQueryString(query);
console.log(queryString);
Aircon.paginate(query, options, function (err, results) {
if (err) {
console.log(err);
} else {
res.render("instock", {
foundAircons: results.docs,
total: results.totalDocs,
hasPrev: results.hasPrevPage,
hasNext: results.hasNextPage,
pageCount: results.totalPages,
page: results.page,
url: queryString,
});
}
});
} else {
res.redirect("/login");
}
});
<div class="container">
<nav aria-label="...">
<ul class="pagination float-right">
<% let prev = "disabled"; if(hasPrev){ prev = "" }; %>
<li class="page-item <%= prev %>">
<a class="page-link" href="/searched/?<%= url %>&page=<%= page - 1 %>"
>Previous</a
>
</li>
<% for(let i = 1; i <= pageCount; i++){ %> <% let active = ""; %>
<%if(page === i) { active = "active"} %>
<li class="page-item <%= active %>">
<a class="page-link" name="1" href="/searched/?<%= url %>&page=<%= i %>"
><%= i %></a
>
</li>
<% }; %> <% let next = "disabled"; if(hasNext){ next = "" }; %>
<li class="page-item <%= next %>">
<a class="page-link" href="/searched/?<%= url %>&page=<%= page + 1 %>"
>Next</a
>
</li>
</ul>
</nav>
</div>

NodeJS render html file with form not working on angular side

I am using ExpressJS with EJS template view engine. I am trying to show an HTML file on the angular component, but the form tag and its child input tag do not work on the angular side. They show only label data.
On NodeJS
agreementController.js
exports.getAgreementHtml = async (request, response, next) => {
const params = request.query
let reqPath = path.join(__dirname, '../agreements');
var agreementObj = {
user: { email: "example#gmail.com" }
}
// render domestic rent html
ejs.renderFile(reqPath + '/domestic_rent.ejs', agreementObj, {}, function (err, str) {
if (err !== null) {
responseObj.status = errorCodes.DATA_NOT_FOUND
responseObj.message = language.getMessage('NO_RECORD_FOUND')
response.send(responseObj)
return
}
responseObj.status = errorCodes.OK
responseObj.data = str
response.send(responseObj);
return;
});
}
domestic_rent.js
<form>
<div class="form-group">
<p><%= user.email %></p>
<div class="col-sm-offset-2 col-sm-10">
<input type="text" class="form-control" id="inputEmail3" placeholder="test" required name="test">
</div>
</div>
</form>
On Angular 8 Side
agreement-show.component.ts
getAgreementData() {
const params = {
id: this.agreementId
};
this.agreementService.getAgreementHtml(params).subscribe(
(result) => {
console.log('result agreement data::: ', result);
if (result.status !== 200) {
this.commonService.change.emit({ status: 'error', message: 'unknown error' });
return;
}
this.someHtml = result.data;
return;
}, (error) => {
console.log('error', error)
this.commonService.change.emit({ status: 'error', message: error.message });
}
);
}
agreement-show.component.html
<div [innerHTML]="someHtml"></div>
Output Attachment
By using ElementRef function we can add html runtime.
Please use following step:
#ViewChild('showitems') showitems: ElementRef;
const elemt: HTMLElement = this.showitems.nativeElement;
this.someHtml = result.data;
elemt.innerHTML = this.someHtml;

I do need a code filling combo box in front-end from database data in back-end using EJS

This is the back-end
register.get('/register', function (req, res) {
function get_position(callback) {
tempCont.query('SELECT * from `positions`',function (error, results) { if (error) callback(null);
callback(results);
console.log("from query = " + results);});
To call the function of query
get_position(function (data) {
if (!!error) {
console.log('Error in getting positions to combo box');}
else {
res.render('register', {positions:positions.id}); } }) })
This is my trial for the front-end
<input type=text list=browsers >
<datalist id=browsers >
<% positions.forEach(function(item){ %>
<option> <%= item %>
<%});%>
</datalist>
The expected result
positions to be appears in the combo box in the web page for user to select a value from it
The received result
An error: Positions is not defined
In back-end
function get_dept(callback) {
tempCont.query('SELECT * from `positions`', function (error, results) {
if (error) callback(null);
callback(results);
});
}
get_dept(function (izo) {
if (!!error) {
console.log('Error in getting departments to combo box');
}
else {
res.render('./register', { errors: null, positions: izo });
}
})
In front-end
<label>Position</label><br>
<input type=text list=browsers >
<datalist id=browsers >
<% positions.forEach(function(item){ %>
<option> <%= item.role_name %>
<%});%>
</datalist>

Data from mongodb not displaying in view using EJS

I'm using mongoose and express along with EJS. For some reason, data I have in my mongodb is not appearing in the view. I get no errors, it's just blank.
var Person = require('.././schema.js');
module.exports = function(app) {
app.get('/about', function(req, res) {
var peopleList = [];
var title = "Users in Database:";
Person.find(function (err, people) {
if (err) return console.error(err);
for(var i = 0; i < people.length; i++) {
peopleList.push({name: people[i].name, role: people[i].role, wage: people[i].wage});
}
console.log(peopleList);
console.log(peopleList[0].name + ' ' + peopleList[0].wage + ' ' + peopleList[0].role);
});
res.render('pages/about', {
peopleList: peopleList,
title: title
});
});
}
And in my view:
<h3><%= title %></h3>
<blockquote>
<ul>
<% for(var i = 0; i < peopleList.length; i++) { %>
<li><%= peopleList[i].name %> : <%= peopleList[i].role %> : <%= peoplelist[i].wage %></li>
<% }; %>
</ul>
Alternate attempt:
<ul>
<% peopleList.forEach(function(peopleList) { %>
<li><%= peopleList.name %> - <%= peopleList.role %></li>
<% }); %>
</ul>
<%= title %> works just fine, just not the data. If I create my own array with objects in it and use the same forEach loop, it also works.
var Person = require('.././schema.js');
module.exports = function(app) {
app.get('/about', function(req, res) {
var peopleList = [];
var title = "Users in Database:";
Person.find(function (err, people) {
if (err)
return console.error(err);
for(var i = 0; i < people.length; i++)
{
peopleList.push({name: people[i].name, role: people[i].role, wage: people[i].wage});
}
console.log(peopleList);
console.log(peopleList[0].name + ' ' + peopleList[0].wage + ' ' + peopleList[0].role);
res.render('pages/about', {
peopleList: peopleList,
title: title
});
});
});
}
Please change your code to this.
Note: you should put res.render in the callback function of Person.find.

Resources