How to re-render the same page with additional data - node.js

I have two routing functions that have to render the same page but with different data based on the URL.But once I have rendered a page, I am unable to re-render the page, it throws an error
Error: Can't set headers after they are sent.
I have two routes like this
app.post('/abc',function(req,res){
...
res.render('template',{....})
...
});
app.get('/abc/xyz/:params',function(req,res){
...
res.render('template',{...})
...
});
I have a submit button on the /abc page that directs to /abc/xyz/params but it gives me the header error.
Then, I tried to send some data with res.json which I could later handle with AJAX, but once the submit button is pressed the rendered page is lost and hence it only returns a JSON file.
I actually am trying to update a portion of a page without reloading the whole thing,but I am unable to preserve the template once the submit button is pressed.
I even tried the HTML5 History API to change URL without refresh but that even gives me the same error.
This is my whole code
app.post('/login',function(req,res){
dbo.collection("user").find({$and:[{"email":req.body.email},{'password':req.body.password}]}).count().then(function(result){
if(result==1){
dbo.collection("user").findOne({$and:[{"email":req.body.email},{'password':req.body.password}]}, function(err, r) {
req.session.email=req.body.email;
if (err) throw err;
res.render('login', {name:r.name,email: req.body.email,password: req.body.password,status: "Match Found!"});
});
}
else{
console.log('Match not found');
res.render('error_msg', { email: req.body.email,password: req.body.password,status: "Match Not found!"});
}
});
});
app.get('/login/email/:email',function(req,res){
dbo.collection("user").findOne({"email":req.params.email},function(er,r){
if(er)
throw er;
if(r){
res.render('login',{email_search:r.name});
}
else{
res.render('login',{email_search:'No User Found!'});
}
});
});

Related

Listen for res.download() on html form submission

I'm using a simple HTML form to define some parameters and provide a template PDF which is then uploaded and edited using NodeJS and PDF. The updated PDF is then returned to the user as a download.
The problem I'm having is that I can't seem to 'catch' the download event. The actual download works well using this code on the server:
function downloadPDF() {
let outputPath = "./completed/" + pdfFilename + "-logos.pdf";
res.download(path.resolve(outputPath), function(err) {
if (err) { console.log(err); }
//remove the original uploaded file
console.log('Tidying up file store, deleting original (input) report...');
fs.unlink('./uploads/' + pdfFilename + '.pdf', function(err) {
if(err) return console.log(err);
console.log('Input file deleted successfully');
});
//remove the completed file
console.log('Tidying up file store, deleting completed (output) report...');
fs.unlink(outputPath, function(err) {
if(err) return console.log(err);
console.log('Output file deleted successfully');
});
});
}
Client side I'm using a simple HTML form with a submit function that includes my validation etc:
function submit_form() {
const forms = document.querySelectorAll('.requires-validation')
Array.from(forms)
.forEach(function (form) {
if (!form.checkValidity()) {
event.preventDefault()
event.stopPropagation()
form.classList.add('was-validated');
}else {
//show splash screen
galleryModal = new bootstrap.Modal(document.getElementById('galleryModal'), {
keyboard: false
});
galleryModal.show();
form.classList.remove('was-validated');
document.dataForm.submit();
document.dataForm.reset();
isChecked()
}
})
}
As you can see, I'm showing a splash screen / modal to let the user know the PDF is processing, I can visibly see the file download, but have no trigger to hide my modal again and confirm to the user that the process was a success.
I tried using AJAX to submit the form but since Node is returning a file I wasn't sure on how to handle the response?
(The form is being reset as the client wants to re-use it straight away, there's no confirmation page or similar to display)
Edit to add: Safari and Chrome both throw this error although the file is correctly downloaded: Failed to load resource: Frame load interrupted

Unable to render page after querying model.findOne

I am using this query and am positively getting a user but unable to render pages.
How do I render the pages inside findOne() without getting errors or warnings?
This is my code:
Company.findOne({'vaccine.name': req_url}, function(err, user){
if(user){
console.log('User Found: ', user.username);
}
return res.render('transaction');
});
This is occurring because there are two return statements and the might already have been sending a render response. Try adding a else statement in the other res.render

Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client. Unable to redirect different page than what it is supposed to be

In my program I am trying to make the server redirect the user to a different page if the wrong details of some sort are put in. The main problem that I am having is that whenever I try to use res.redirect() with another web page as a parameter I get the following error back: Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client
I am not sure if I am referencing the web page in terms of where its stored on my computer wrong but something else about http headers was mentioned.
Here is my code that might potentially help to show what is wrong:
app.post("/profile.html", (req, res)=>
{
if(req.body.login_name == undefined)
{
var sql = "SELECT * FROM connectfour.players WHERE username =" + "'" + req.body.signup_name + "';";
connection.query(sql, function(error, results)
{
if(error) throw error;
if(results.length > 0)
{
console.log(res);
res.redirect("/start");
var already_taken = "username " + req.body.signup_name + " is already taken";
res.render("start", {already_taken});
}
else
{
var signup_name = req.signup_name;
res.render("profile", {signup_name});
}
});
}
So the form on another page posts the request for the profile.html page. Then something is checked to detect if the username is logging in or signing up. An SQL query happens with the data sent over. If the user already exists then the user will get sent over to a page that will make them try to signup again. This contains the res.redirect problem:
profile.ejs is what the page requested is supposed to be. But I need to redirect it to start.ejs if the if statement mentioning if(results.length > 0) happens. I would try to post an image showing what my problem is but the problem is that my reputation is not high enough.
You either call res.redirect or res.render. Both sets status and returns it and some info to a client. If '/start' is processed by some Express route handler (that renders using res.render("start", {already_taken});) when just redirect to it and that's all.

Handling delete operation in express + pug template engine

I'm learning express and have created a to-do list app in express with pug template engine. In this app, I can add and view to-dos. Now I want to add a delete functionality.
My pug code to render the to-do list is:
ol
each val in toDoList
li= val.taskName
a(class='button' href='/todos/'+val._id) delete
Here's my express code to handle the delete:
app.delete('/todos/:id', function(req, res){
db.collection('todos').findOneAndDelete({id: req.params.id}, function(err, results) {
if (err) return res.send(500, err);
res.redirect('/');
});
});
I am getting this error Cannot GET /todos/5e570f67ed9efba99c938719.
What am I doing wrong and how can I fix it?
Thanks!
Why you are getting the error:
The link element: a(class='button' href='/todos/'+val._id) delete
is going to render a link element like this: <a class='button' href='/todos/5e570f67ed9efba99c938719'>delete</a>(assuming val._id is 5e570f67ed9efba99c938719).
Clicking this link won't delete the todo but instead, take you to a page whose URL is something like this /todos/5e570f67ed9efba99c938719 and this would cause the client app make a GET request to your server for that route. Since you do not have any route-handler matching the requested route from the client app in the server, you get the error: Cannot GET /todos/5e570f67ed9efba99c938719.
The Fix
To delete the todo, what you need to do is add an event listener to the link such that when you click it, instead of just redirecting you to some page, the handler attached to the event listener would be executed to reach out to your server and delete the todo. A sample implementation:
// Add some more properties to the delete button
a(class='button delete-todo' data-articleid=val._id href='/todos/'+val._id) delete
And in a <script /> tag or a javascript file that would be loaded with the pug template, add this:
// SOURCE: https://github.com/elvc/node_express_pug_mongo/blob/master/public/js/main.js
$(document).ready(function(){
$('.button.delete-todo').on('click', function(e){
e.preventDefault();
$target = $(e.target);
const id = $target.attr('data-articleid');
$.ajax({
type: 'DELETE',
url: '/todos/'+id,
success: function (response){
// Whatever you want to do after successful delete
console.log(response);
alert('Deleting article');
},
error: function(err){
// Whatever you want to do after a failed delete
console.error(err);
}
});
});
});

SailsJS : Redirecting to controller action with variables

I am using sailsJS with ejs engine and i want to redirect the user back to the input page with messages ( validation errors ... ) .
i used to use this easily with laravel in PHP ( return redirect('dashboard')->with('status', 'Profile updated!'); )
i.e : i need to redirect the user back saying that this site dont exist
find : function(req,res){
var id = req.param(íd');
Site.find(id).where({isDeleted : null })
.exec(function(err,siteFound){
if(err) console.log(err);
if(siteFound) {
return res.view('site/show', {
site : siteFound
});
} else {
return res.redirect('/site');
}
})
},
i searched in sails documentation but found nothing. how can this be performed in SailsJS ?
thanks
UPDATE : i found what i needed exactly by installing sails-hook-flash . the feature i needed is called flash messages.
Thank you for your help !
Blockquote
I can't quite tell if you want a true browser redirect. A browser redirect means sending a message back to the browser that says "use this other url instead", and then it gets fresh data (meaning new req and res objects) from your app. If this is what you want, I'd say the only real options for passing data are query strings, like:
return res.redirect('/site?message=notfound');
Then in your recieving controller action for site you can access this via req.param('message').
However, if you just want to return the appropriate content now without getting the browser to redirect, you can just call whatever view or controller action you like:
if(siteFound) {
return res.view('site/show', {
site : siteFound
});
} else {
// uncomment one of the options:
// ONE: return another view
// return res.view('site/notfound, {requested: id});
// TWO: pass to another controller action in same controller
// req.options.message = 'not found';
// return module.exports.someOtherAction(req, res);
// THREE: pass to a controller action in another controller
// req.options.message = 'not found';
// var OtherController = require('./OtherController');
// return OtherController.someOtherAction(req, res);
}
Any of those three options will keep you at the user's requested url ("/site/abc123" or whatever), but display the content you specify.
res.notfound("my message string"); should do it right?
You can work with res.json() if it is an ajax request expecting a custom response.
Read the docs about the res object HERE and the custom notfound response HERE.

Resources