Loading more content from api in Node.js - javascript

So I just started learning Node.js and I'm trying to figure out how to load more content on the button click. I think I'm either misunderstanding something or I'm confusing myself.
The code might be a bit of a mess at the moment as I've been trying a bunch of different things.
So far I have a homepage route:
//Home
app.get('/', (req, res)=> {
res.render('homepage');
});
On the homepage, I have a search bar that calls the /search route with get:
<form action="/search" method="get" id="form">
<input class="search" name="searchQuery"/>
<button type="submit">Submit</button>
</form>
Then the search route which makes a call to the API, parses the XML, renders the search page, and sends some data:
//Search
app.get('/search', async (req, res) => {
g_searchQuery = req.query.searchQuery;
const fetch_response = async (url, query) => {
try {
const response = await fetch(url);
const xml = await response.text();
xml2js.parseString(xml, (error, result) => {
var paginations = result.GoodreadsResponse.search[0];
totalResults = parseInt(paginations['total-results']);
resultsEnd = parseInt(paginations['results-end']);
g_resultsEnd = resultsEnd;
var test = result.GoodreadsResponse.search[0].results[0].work.map( w => {
let bestBook = w.best_book[0];
return {
title: bestBook.title[0],
author: bestBook.author[0].name,
image_url: bestBook.image_url[0],
small_image: bestBook.small_image_url[0],
original_publication_year: w.original_publication_year[0]._,
}
});
res.render('search', {data : {
goodReadsResponse : test,
searchQuery : query,
totalResults : totalResults,
resultsEnd : g_resultsEnd,
currentPage : g_currentPage
}});
res.end();
});
}
};
fetch_response(goodreadsapi + '?key=' + goodreadskey + '&q=' + g_searchQuery + '&page=' + g_currentPage, g_searchQuery);
});
The search page renders the data and has a load more button which with Fetch api makes a call to /search POST:
<body>
<h1>You searched for: <%= data.searchQuery %></h1>
<% if(data.goodReadsResponse) { %>
<ul class="data-container">
<%= data.totalResults %>
<%= data.currentPage %>
<% data.goodReadsResponse.forEach(function(book) { %>
<li>
<span><%= book.title %></span>
<span><%= book.author %></span>
<span><img src="<%= book.image_url %>"/></span>
<span><img src="<%= book.small_image %>"/></span>
<span><%= book.original_publication_year %></span>
</li>
<% }); %>
</ul>
<% if(data.resultsEnd <= data.totalResults) { %>
<div class="load-more__container">
<button class="load-more__button" type="button">Load More</button>
</div>
<% } %>
<% } %>
</body>
<script type="text/javascript">
window.addEventListener('load', function() {
const loadMore = document.querySelector('.load-more__button');
if(loadMore) {
loadMore.addEventListener('click', (event) => {
event.preventDefault('Load More');
fetch('/search', {
method : 'POST'
})
});
}
});
</script>
Then this is the part where I get stuck and confused. I have a app.post /search route for the load more button but I'm not sure whats the best way to pass data to that route/if I even need to.
My thinking is that I need to pass most of the data I passed through the render in /search app.get route to the app.post /search route add 1 to the current page, then append the data that I get from the API to the previous data then render the page with that updated data. But I'm slightly stuck.
Here is my repo for this, it's not 1:1 since I tried to clean some things up for the question.
Any help would be greatly appreciated,
Thank you.

let bestBook = w.best_book[0];
In the above line, Considering w.best_book is a list of books, u could just loop it and save all the books in a seperate variable, and render it when Load More button is pressed.

Related

Error cannot read property of undefined Express app

I am currently learning to build a blog website using Node.js, Express and ejs
I got the following error when I try to render the "show" page
I found a similar problem here but there is a different usage of my code, I still couldn't get mine solved.
The similar problem
I did also had an same type error earlier says "cannot read property 'username' of undefined" , but after I restart the server it gives me a different error.
The error I got now:
TypeError: C:\Users\kevin\Desktop\MyBlog\views\blogs\show.ejs:22
20|
21| <div class="text-muted">
22| <%= blog.date.toLocaleDateString()%> By <%= blog.author.displayName %> **<------**
23| </div>
24| </div>
25|
Cannot read property 'toLocaleDateString' of undefined
Portion of router.js:
//show the specific blog
router.get("/home/:slug", async(req, res) => {
const blog = await Blog.find({ slug: req.params.slug });
console.log(blog);
if (blog == null) {
res.redirect("/home");
}
res.render("blogs/show", {blog: blog});
})
Portion of show.ejs
<div class="card-header">
<h1 class="mb-1">
<%= blog.title %>
<i class="fas fa-home fa-lg"></i>
<!-- Override DELETE method -->
<form action="/home/<%= blog.id %>?_method=DELETE" method="POST" class="d-inline">
<% if((currentUser) && (currentUser.username === blog.author.username)){ %>
<span class="far fa-edit fa-lg"></span>
<button type="submit" class="btn btn-light btn-sm float-right"><i class="fas fa-trash-alt fa-lg"></i></button>
<% } %>
</form>
</h1>
<div class="text-muted">
<%= blog.date.toLocaleDateString()%> By <%= blog.author.displayName %>
</div>
</div>
I checked that the blog object is saved into the database correctly:
{
"_id" : ObjectId("5f4722cc1a623a7710871d6d"),
"author" : {
"id" : ObjectId("5f47177faa8e6e3e4c80f3e9"),
"username" : "333#gmail.com",
"displayName" : "Jacky Chan"
},
"date" : ISODate("2020-08-27T03:04:44.646Z"),
"title" : "1321321321321",
"coverImg" : "",
"contents" : "<p>3213131321321321</p>",
"slug" : "1321321321321",
"sanitizedHtml" : "<p>3213131321321321</p>",
"__v" : 0
}
I used console.log in the ejs file and it seems that it did found the correct blog....
I am guessing that the blog object is referenced incorrectly, maybe?
Finally I don't know if it is the problem of async/await but I tried all I can which eventually lead me here...
Any help will be appreciated! Thanks in advance!
Update 1:
Thanks to #AdamExchange
I tried the answer from #AdamExchange that switched find() to findOne().
The previous error is gone but there is a new error that when I use my create new blog route, it is calling the show route and trying to find the blog.
New Error:
TypeError: C:\Users\kevin\Desktop\MyBlog\views\blogs\show.ejs:9
7| <div class="card-header">
8| <h1 class="mb-1">
9| <%= blog.title %>
10| <i class="fas fa-home fa-lg"></i>
11| <!-- Override DELETE method -->
12| <form action="/home/<%= blog.id %>?_method=DELETE"
method="POST" class="d-inline">
Cannot read property 'title' of null
My question is why would the show route get involved when I use the GET method on create routes? (I do have a button on the create blog page that uses the show route)
Here I will post my entire router hopefully that will help:
const express = require("express");
const router = express.Router({mergeParams:true});
const Blog = require("../models/blog");
const middleware = require("../middleware");
router.post("/home", (req, res, next) => {
req.blog = new Blog();
next()
}, saveBlogAndRedirect('new'))
router.get("/home/new", middleware.isLoggedIn, async(req, res) => {
let blog = new Blog();
res.render("blogs/new", { blog : blog });
})
//Get the blog with the correctid
router.get("/home/edit/:id", async(req, res) => {
const blog = await Blog.findById(req.params.id);
res.render("blogs/edit", { blog : blog });
})
router.put("/home/:id", async(req, res, next) => {
req.blog = await Blog.findById(req.params.id);
next()
}, saveBlogAndRedirect('edit'))
//show the specific blog
router.get("/home/:slug", async(req, res) => {
const blog = await Blog.findOne({ slug: req.params.slug });
if (blog == null) {
res.redirect("/home");
}
res.render("blogs/show", {blog: blog});
})
router.delete("/home/:id", async(req, res) => {
await Blog.findByIdAndDelete(req.params.id)
res.redirect("/home");
})
function saveBlogAndRedirect(path){
return async(req, res) => {
let blog = req.blog;
blog.title = req.body.title;
blog.coverImg = req.body.coverImg;
blog.contents = req.body.contents;
blog.author = {
id : req.user._id,
username: req.user.username,
displayName: req.user.displayName
}
try{
blog = await blog.save();
res.redirect(`/home/${blog.slug}`);
}
catch (e) {
console.log(e)
res.render(`blogs/${path}`, { blog : blog });
}
}
}
module.exports = router;
const blog = await Blog.find({ slug: req.params.slug });
Will return an array of the blogs with that slug. If you know there is only one, you can change it to .findOne. The array of blogs is getting passed to the ejs template as blog (noted by your screenshot
[
{
author: ...
}
]
The 'date' property of the array is meaningless, but if you make sure to only pass your ejs template one blog object, that should fix it
Updated your code for, it will also work if the number of document is more than one. Since you are using find() method it will return all the document with matching filter.
Router
//show the specific blog
router.get("/home/:slug", async(req, res) => {
const blog = await Blog.find({ slug: req.params.slug });
console.log(blog);
if(blog == null) {
res.redirect("/home");
}
res.render("blogs/show", {blog:blog});
});
ejs section
<% for(var i=0; i<blog.length; i++){ %>
<div class="card-header">
<h1 class="mb-1">
<%= blog[i].title %>
<i class="fas fa-home fa-lg"></i>
<!-- Override DELETE method -->
<form action="/home/<%= blog[i].id %>?_method=DELETE" method="POST" class="d-inline">
<% if((currentUser) && (currentUser.username === blog[i].author.username)){ %>
<span class="far fa-edit fa-lg"></span>
<button type="submit" class="btn btn-light btn-sm float-right"><i class="fas fa-trash-alt fa-lg"></i></button>
<% } %>
</form>
</h1>
<div class="text-muted">
<%= blog[i].date.toLocaleDateString()%> By <%= blog[i].author.displayName %>
</div>
</div>
<% } %>

Data creation in Mongodb using Node JS

I am using node js as my backend language. I am creating data in mongodb through submitting data using a form but I want to find the latest entered data and display it in my ejs template.
Here is my code :
/*mongoose schema setup*/
const codeSchema = new mongoose.Schema({
first: String,
second: String,
third: String,
event: String,
link: String
});
const Code = mongoose.model('Code', codeSchema);
/* GET home page. */
router.get('/welcome', async function(req, res, next) {
await Code.find({}).sort({_id: -1}).exec(function(err, data) {
if(err)
{
console.log(err);
}
else
{
//console.log(data);
res.render('index', {data: data});
}
});
});
/* GET new form form page. */
router.get('/update', function(req, res, next) {
res.render('newform');
});
/* POST update welcome page. */
router.post('/welcome', async function(req, res, next) {
const info = {
first: req.body.first,
second: req.body.second,
third: req.body.third,
event: req.body.event,
link: req.body.link
};
await Code.create(info, function(err){
if(err) {
console.log(err);
}
else {
res.redirect('/welcome');
}
});
});
The code works fine, it creates and extracts the last n record from my database but when I try to display the data in my html it does'nt shows up.I also tried using findOne() method which actually worked and displayed the last n record from my db. The problem arised when my database had no data the findOne() did'nt worked and generated error in my ejs template.
I find the data from my db using find({}) method. Like if I do First name - <%= data.first %> in my ejs template, my data does'nt show.
I also checked my mongo database which had all the information which was passed through form.
PLEASE HELP !!!!!
P.S. - I can display all the data from db using for loop but I only want to display a particular data.
Nithin K Joy here is my index.ejs
<h1>Index Page</h1>
<h4> Person1- <%= data.first %> <h4/>
<h4> Person2- <%= data.second %> <h4/>
<h4> Person3- <%= data.third %> <h4/>
<h4> Event name- <%= data.event %> <h4/>
<h4> Registration Link- <%= data.link %> <h4/>
here is my form through which recors are being submitted
<form action="/welcome" method="POST" style="width: 30%; margin: 40px auto;">
<input type="text" id="winner1" name="first" placeholder="1st">
<input type="text" id="winner2" name="second" placeholder="2nd" >
<input type="text" id="winner3" name="third" placeholder="3rd" >
<input type="text" id="event" name="event" placeholder="Event Name" >
<input type="text" id="link" name="link" placeholder="Event Regestration link" >
<button type="submit class="btn btn-success btn-block">Submit</button>
If i put my index.ejs code inside a loop it works fine but I donot want several inputs from FORM to show up in index.ejs I want only the last n record

EJS function parameter is not defined

I have 50 different image inputs and I dont want to write them all, so
I want to use a function where I can set the parameters and only have to call the func with the parameters. Inside the ejs (html) I want to set the name attribut like this name="<% name2 %>" but if I call the func with value painting3 for name it throws
58| <% callPainting(post.paintings, paintings3, ) %>
paintings3 is not defined
I defined post in app.js
router.get('/:id/edit', async (req, res) => {
try {
const findPost = await Post.findById(req.params.id)
res.render('admin/edit', {
layout: 'layouts/admin',
post: findPost,
})
} catch {
res.redirect('/admin')
}
})
and every input should looks like this <input type="file" class="filepond" name="paintings2" value="<%= post.paintings2 %>">
So i did this, but its not working as I expected
<% function callPainting(postimage, postname, postdate, postprice, checkImage, postshowImage, name, name2, date, price, showImage) { %>
<input type="file" class="filepond" name="<% name %>" value="<%= postimage %>">
<% } %>
I got some more parameters but thats the same problem

Flash message doesn't show until refresh or secondary post?

I am jumping into Sails.js again and working through to create flash messages throughout my app (for errors, successes, or alerts). I was looking for a good way to do it and found this discussion, I implemented the solution they had suggested.
The general mechanism works great, however, the flash message is only seen after a secondary refresh or after another post. It does not show at first upon page load. Here is how I have everything structured and I am using "sails": "~0.10.0-rc7" currently:
In my api/policies folder, I have flash.js:
// flash.js policy
module.exports = function(req, res, next) {
res.locals.messages = {
success: [],
error: [],
warning: []
};
if(!req.session.messages) {
req.session.messages = { success: [], error: [], warning: [] };
return next();
}
res.locals.messages = _.clone(req.session.messages);
// Clear flash
req.session.messages = { success: [], error: [], warning: [] };
return next();
};
In my api/services, I have FlashService.js:
// FlashService.js
module.exports = {
success: function(req, message) {
req.session.messages['success'].push(message);
},
warning: function(req, message) {
req.session.messages['warning'].push(message);
},
error: function(req, message) {
req.session.messages['error'].push(message);
}
}
My config/policies.js is also configured with the flash policy:
// config/policies.js
module.exports.policies = {
'*': [true, 'flash'],
'UserController': {
'join': ['flash'],
},
};
Now, with all that setup, an example of how I am using it is in my UserController for my join action:
module.exports = {
join: function(req, res) {
// If username, email, and password compare come back true: create user.
if(newUser.username && newUser.email && newUser.password == newUser.confirmPassword) {
// Logic to create user.
res.view();
// If not, then report an issue.
} else {
FlashService.error(req, 'There was an issue.');
res.view();
};
}
};
Finally, my view is exactly the same code as that discussion I linked. I am using EJS on this:
<% if (messages && messages['error'].length > 0) { %>
<div class="alert alert-danger">
<% messages['error'].forEach(function(message) { %>
<%= message %>
<br>
<% }); %>
</div>
<br>
<% } %>
<% if (messages && messages['warning'].length > 0) { %>
<div class="alert alert-warning">
<% messages['warning'].forEach(function(message) { %>
<%= message %>
<br>
<% }); %>
</div>
<br>
<% } %>
<% if (messages && messages['success'].length > 0) { %>
<div class="alert alert-success">
<% messages['success'].forEach(function(message) { %>
<%= message %>
<br>
<% }); %>
</div>
<br>
<% } %>
What could I be doing wrong? Any help would be much appreciated!
Thanks,
Your messages are being read before the the controller is executed (and just message is set after it is read). The req object is available in your view, you should just read req.session.messages['xxxx'] directly into your view.

nodejs/express/mongo Have res.render wait until after database search is complete to load

I'm having some trouble getting this table to load properly because the page is loading before all the information is passed to my ejs template. Pretty new to all of this and would appreciate any help!
I should note that owneditems is an array of IDs in the user schema.
routes.js:
app.get('/profile/:username', function(req, res) {
User.findOne({username: req.params.username}, function(err, user) {
var newDocs = [];
if (!user) {
req.flash('profilemessage', 'No such user exists.');
} else {
user.owneditems.map(function(i) {
Items.findById(mongoose.Types.ObjectId(i), function(err, idoc) {
newDocs.push("<tr><td>" + idoc.name + "</td><td>" + idoc.brand</td></tr>");
});
});
}
res.render('profile.ejs', {title: 'Profile', items: newDocs, message: req.flash('profilemessage')});
});
});
Profile.ejs:
<!-- content -->
<div class="wrapper row2">
<div id="container" class="clear">
<section>
<% if (message) { %>
<h4><%= message %></h4>
<% } %>
<table id="owneditems" class="sortable">
<tr><th>Name</th><th>Brand</th></tr>
<% for(var i=0; i<items.length; i++) {%>
<%- items[i] %>
<% } %>
</table>
</section>
</div>
</div>
<% include layoutBottom %>
This type of setup works for me on another page, I just can't get it working here. Thanks!
The reason why the page is rendered before information is loaded is becauseItems.findById is asynchronous. This means newDocs will not return the array of items you're expecting when it's passed to res.render.
When you want to load (arrays of) subdocuments with Mongoose, it's best to use query#populate. This method will allow you to swap out the item IDs in your user.owneditems array for the actual item document in one go.
I think this would work in your case:
app.get('/profile/:username', function(req, res) {
User.findOne({username: req.params.username})
.populate('owneditems')
.exec(function(err, user) {
var newDocs = [];
if (!user) {
req.flash('profilemessage', 'No such user exists.');
} else {
user.owneditems.forEach(function(i) {
newDocs.push("<tr><td>" + i.name + "</td><td>" + i.brand</td></tr>");
});
}
res.render('profile.ejs', {title: 'Profile', items: newDocs, message: req.flash('profilemessage')});
});
});
Also note I switched map with forEach (which is what it seems you're going for given your callback)

Categories

Resources