Handling delete operation in express + pug template engine - node.js

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);
}
});
});
});

Related

How to re-render the same page with additional data

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!'});
}
});
});

Send variable from mongoose query to page without reload on click

I have a link on my site. When clicked it'll call a function that does a mongoose query.
I'd like the results of that query to be sent to the same page in a variable without reloading the page. How do I do that? Right now it is just rendering the page again with new query result data.
// List comments for specified chapter id.
commentController.list = function (req, res) {
var chapterId = req.params.chapterId;
var query = { chapterId: chapterId };
Chapter.find({ _id: chapterId }).then(function (chapter) {
var chapter = chapter;
Comment.find(query).then(function (data) {
console.log(chapter);
Chapter.find().then(function(chapters){
return res.render({'chapterlinks', commentList: data, user: req.user, chapterq: chapter, chapters:chapters });
})
});
})
};
You just need to make that request from your browser via AJAX:
https://www.w3schools.com/xml/ajax_intro.asp
This would be in the code for your client (browser), not the code for your server (nodejs).
UPDATE:
Here's a simple example, which uses jQuery to make things easier:
(1) create a function that performs and handles the ajax request
function getChapterLinks(chapterId) {
$.ajax({
url: "/chapterLinks/"+chapterId,
}).done(function(data) {
//here you should do something with data
console.log(data);
});
}
(2) bind that function to a DOM element's click event
$( "a#chapterLinks1" ).click(function() {
getChapterLinks(1);
});
(3) make sure that DOM element is somewhere in you html
<a id="chapterLinks1">Get ChapterLinks 1</a>
Now when this a#chapterLinks1 element is clicked, it will use AJAX to fetch the response of /chaptersLink/1 from your server without reloading the page.
references:
http://api.jquery.com/jquery.ajax/
http://api.jquery.com/jquery.click/

Refresh section of a page using EJS and Express

I have a checkbox that when pressed call a function that does a GET request. Based on the selection I want to display extra checkboxes on the same page. At the moment this is the code I have so far:
Client side
function selectedHouse(house)
{
if(house.checked){
$.get('/', {data: house.value});
}
}
Server side
var routing = function (nav, houses) {
router.route('/')
.get(function (req, res) {
var rooms = [];
rooms = getRooms(req.query.data);
console.log(rooms);
res.render('index', {
title: 'Independent cleaner',
nav: nav,
houses: houses,
roomsForHouse: rooms
});
});
return router;
};
The first time the page loads, it loads with the correct title, nav and houses. When the function is executed on client side, I get back the related rooms for the house and I'm trying to populate the roomsForHouse variable which I'm displaying on the view.
The problem is that the view doesn't render the roomsForHouse variable. So the GET request is called once the page loads and a second time when the function executes. Can this be achieved?
It's a bit more complex. You'll need to use ajax for this. EJS is server side templates (as you are using them) so you'll want to use jQuery to make the call and update your already rendered page.
Server
Your server will need a route that delivers JSON data. Right now you are rendering the entire page. So:
app.get('/rooms/:id', function (req, res) {
// Get the house info from database using the id as req.params.id
...
// Return the data
res.json({
rooms: 2
...
});
});
Client
Using jQuery make a call to your json route once the user selects the house.
function selectedHouse(house)
{
if(house.checked){
// Pass some identifier to use for your database
$.ajax({
type: 'GET',
url: '/rooms/' + house.id,
success: function(data) {
// Update the element - easiet is to use EJS to make sure each house has an id/class with the id in it
// Given an ID of 2 this says find the div with class house_2 and updates its div with class room to the number of rooms
$('.house_' + house.id + ' .rooms').text(data.rooms);
});
}
}
This is more of pseudo code then anything but should put you on the right track.
res.render can't rerender view, for refresh page in second time you need use javascript to replace html. This is not good solution
$.get("/",function(html){
document.open();
document.write(html);
document.close();
});
For better you should use another router to render DOM you want to change

Node Server - res.redirect is using 'PUT' method. Need 'GET'

Using MEAN stack.
I created a mongoose call to update a document in my mongodb collection. That is working, no issue. However when the update is complete I want to "simply" redirect to my '/search' page via server controller.
The redirect works on my other mongoose save call, which is a simple 'POST'.
However when I call redirect after my 'PUT' the redirect is using PUT and not GET, resulting in a 404, that the method PUT /search can't be found. That would be correct!, however I'm not sure why its choosing 'PUT' method. Can I force a 'GET'?
Chrome console log:
PUT http://localhost:3000/search 404 (Not Found)
angular.js:9827 (anonymous function)angular.js:9628 sendReqangular.js:9344 serverRequestangular.js:13189
processQueueangular.js:13205
(anonymous function)angular.js:14401
Scope.$evalangular.js:14217 Scope.$digestangular.js:14506
Scope.$applyangular.js:21440 (anonymous function)angular.js:3014 eventHandler
My express routes are using passport, so all pages that require authentication go through my index.js. (see gist). My mongoose calls calls all go directly from my view or controller to the server via /api/... route.
Hoping this is a quick fix.
My code in a gist.
https://gist.github.com/astemborskim/419d49f0f773fa623a90
Client Controller - call to server to perform update (works)
$scope.updateProduct = function() {
//$scope.edited.inv = $scope.edited.searchSKU;
$scope.edited.inv.SKU = $scope.edited.SKU;
$scope.edited.inv.Product_Name = $scope.edited.Name;
$scope.edited.inv.Product_Description = $scope.edited.Desc;
$scope.edited.inv.Quantity = $scope.edited.quantity;
$scope.edited.inv.Product_Location = $scope.edited.locations;
//console.log('Edited: ' + JSON.stringify($scope.edited.inv));
//console.log('Not Edited: ' + JSON.stringify($scope.prod.currentProd));
var inventory = new Inventory($scope.edited.inv);
//console.log('edited.inv._id: ' + $scope.edited.inv._id);
inventory.$update({id : $scope.edited.inv._id}, inventory, function (err, results){
if(err){};
console.log(results);
});
}
Server Controller - Failing to redirect:
module.exports.editInventory = function(req, res){
Product.update({_id : req.body._id}, req.body, function (err, results){
if(err){
req.flash('message', 'Error Updating Product');
console.log('Error Updating Product: ' + err);
throw err;
}
req.flash('message','Product Updated Successfully!');
res.redirect('/search');
});
};
Let me know if you need additional info.
Try to res.send(200); in server controller and location.reload(); in client's controller callback.

Sending text to the browser

I have managed to get file uploading work in Node.js with Express, and in the code i'm checking whether it's an image or not that the user is trying to upload.
If the file was successfully uploaded I want to show a message to the user, directly to the HTML page with the uploading form. The same should be if the file the user tried to upload wasn't an image, or something else happened during the upload.
The code below works (res.send...) but it opens up a new page containing only the message.
My question is: How can I change my code so that the message is sent directly to the HTML page instead? If it could be of any use, i'm using Jade.
Thanks in advance!
app.post('/file-upload', function(req, res, next) {
var fileType = req.files.thumbnail.type;
var divided = fileType.split("/");
var theType = divided[0];
if (theType === "image"){
var tmp_path = req.files.thumbnail.path;
var target_path = './public/images/' + req.files.thumbnail.name;
fs.rename(tmp_path, target_path, function(err) {
if (err) throw err;
fs.unlink(tmp_path, function() {
if (err) {
throw err;
res.send('Something happened while trying to upload, try again!');
}
res.send('File uploaded to: ' + target_path + ' - ' + req.files.thumbnail.size + ' bytes');
});
});
}
else {
res.send('No image!');
}
});
from what I understand you are trying to send a message to an already open browser window?
a few things you can do,
Ajax it, send the post, and process the return info.
Submit it as you are doing now, but set a flash message (look at http://github.com/visionmedia/express-messages) and either res.render the form page, or res.redirect to the form function
now.js or a similar solution. This would let clientside use serverside functions and serverside code to run clientside functions. So what you would do would be on submit, pass the post values to a serverside function, which will process it and trigger a clientside function (display a message)
For my money option #2 is probably the safest bet, as clients without javascript enabled will be able to use it. As for usability #1 or #3 would give a more streamlined appearance to the end user.
You can use WebSockets. I recommend using Socket.IO, it's very easy to work with. On the client-side you would have an event-handler which would use JavaScript to append the new information to that page.
You could then have the server for example say:
socket.emit('error', "Something happened while trying to upload, try again!");
and the client would use:
socket.on('error', function(data){
//alert?
alert(data);
});
http://socket.io/#how-to-use

Resources