I'm creating a blog site and am trying to implement a function so that users can report specific comments. The idea is that the user will click on a 'report' link beneath a specific comment and be taken to a new page which will be pre-populated with both the comment they are reporting and the title of the blog which the comment belongs to.
I have written the following route so far:
router.get('/blogs/:blog_id/comments/:comment_id/report', function(req, res) {
// find comment
Comment.findById(req.params.comment_id, function(err, foundComment){
if(err){
res.redirect('/blogs');
} else {
res.render('report-comment', {comment: foundComment});
}
});
});
This is successfully generating my report comment page and from the report comment page I can then populate the comment using eps <%=comment.text%>. However, I cannot work out in this route how to pass through the blog details that the comment is embedded within. In my report comment form I want to be able to populate the blog title using <%=blog.title%> but where in my route do I pass the blog info through?
You use req object to pass along information from one middleware function to next middleware functions. Also you should specify separate middleware functions for retrieving different data and then finally render it. e.g. You can modify your code something like:
router.get('/blogs/:blog_id/comments/:comment_id/report',
function( req, res, next ){
// this is first middleware and fetches comment data
Comment.findById(req.params.comment_id, function(err, foundComment){
if(err){
res.redirect('/blogs');
}else{
req.comment = foundComment;
next();
}
});//findById()
},
function(req, res, next){
// this is second middleware that fetches blog data
Blog.findById(req.params.blog_id, function(err, foundBlog){
if(err){
res.redirect('/blogs');
} else {
req.blog = foundBlog;
next();
}
});
},
function( req, res ){
// this is third and final middleware that renders the page
var pageInfo = {};
pageInfo.comment = req.comment;
pageInfo.blog = req.blog;
res.render("/report-template", pageInfo);
});
Now, in your report-template file, you can use these variables as comment and blog.
Related
I want to access my DB in my EJS header file, which is a partial that is added to every page.
I have a Schema called Category and I want to get the name for the categories which will be in my header dynamically from the db.
I am tring to run the following commmand:
<% Category.find({}, name, function(err, names) { %>
<% if(err) { console.log(err); } %>
<% console.log("Names: " + names); %>
<% }); %>
But of course the header ejs file doesn't have access to Category.
I know normaly to access my DB in a ejs file I query the DB in the route and then pass the data to the ejs, but here since it is the header that will be added to every page I can't really do this operation in the route unless I do it in every route which does seem like such a good idea.
How can I get this data here?
Thanks
Database requests shouldn't be performed directly in view. This is prescribed by separation of concerns principle that stands behind MV* patterns.
Express route handlers act as MVC controllers, their purpose is to provide data from models to views.
Mongoose supports promises, so using callback-based API just complicates everything. Common data like could be provided as a separate function that returns a promise of data, e.g.:
function getPageData() { ... }
async function routeHandler(req, res, next) {
try {
const pageData = await getPageData();
res.render('index', {
...pageData,
/ * etc */
});
} catch (err) {
next(err);
}
};
routeHandler itself can be refactored to helper function that accepts view, view variables, req, res, and next.
Another approach is to make page data available globally in all or most views with additional middleware, as described in this related question, e.g.:
app.use(async function (req, res, next) {
try {
const pageData = await getPageData();
Object.assign(res.locals, pageData);
next();
} catch (err) {
next(err);
}
});
See the example below:
var apiRouter = express.Router();
apiRouter.post('/api/postAgree', function(req, res, next){
userModel.findOneAndUpdate(
{profileID: req.session.facebookProfileId},
{$push:{postsAgreed: req.query.postID}},
{safe: true, upsert: true},
function(err, model) {
if (err){
console.log(err);
}
}
)
Now, the MongoDB operation is already done and I want to stay on the same page.
Will I be doing this:
res.render('theSamePageIamOn', {foo:bar});
I know this works but it seems like it is a very inefficient way of doing it.
So my question really is: If I have a button on a page which makes an API call but I want to stay on the same page, how will I do that? The res.(options) function sort of is made like it has to take me to other pages
Thanks to #robertklep and #GiladArtzi - it should be an AJAX call and the response should be in the form of:
res.json()
Then the response can be handled by the frontend using other tools like: Angular
I'm not sure what you're talking about, just call the function....
function doesSomething (args) {
console.log(args)
}
apiRouter.post('/api/postAgree', function(req, res, next){
doesSomething("HELLO")
});
Function calls don't expects the user to go to another page each time an API call is handled.
Novice to NodeJS and Express but lets say I have this route for mywebsite.com/tournaments.
router.get('/tournaments', function (req, res) {
TournamentController.getAllTournaments(function (err, docs) {
if (err) {
//render error
} else {
res.render('tournaments', {
data: {
title: 'mysite',
command: 'tournaments',
user: req.session.user,
tournaments: docs
}
});
}
});
});
data.tournaments is an array of tournaments in order of their date. Lets say in the front end I have a select/option form where the user can choose date/prize/players as the order to sort the tournaments by. How can I sort the data.tournaments without having to call another route or refresh the page? I'm using Jade on the front end.
You can always sort them directly in the Browser via Javascript, either do it yourself or use a plugin like datatables.
If you don't wanna do that but do it on the server, you'll need an ajax call for that and a route that handles the sorting (based on the parameters you pass), and afterwards change the DOM according to the response. This goes without refreshing the page, but you'll need a route for that, or change the existing route and extend your controller to take optional parameters, something like
router.get('/tournaments/:sort?', function (req, res) {
TournamentController.getAllTournaments(req.param('sort'), function (err, docs) {
/* ... */
});
});
The following code is the user-facing part of a new node app we are building:
var loadInvoice = function(req, res, next) {
Invoice.findById(req.params.invoiceId, function (err, invoice) {
if (err) {
res.send(404, 'Page not found');
} else {
req.invoice = invoice;
next();
}
});
};
app.namespace('/invoices/:invoiceId', loadInvoice, function () {
app.get('', function(req, res){
var templateVals = {
//some template data
};
res.render('paymentselection', templateVals);
});
app.post('', function(req, res){
var data = {
// some data for the apiCall
};
someAPI.someRequest(data, function(err, data) {
console.log(res.status());
res.redirect(data.url);
});
});
});
The first method returns a confirmation page where the user presses a button to post to the same url, which triggers a redirect to an external website.
This all works exactly once. Every second request will crash the app with the message Cant set headers after they are sent. After carefull inspection of the code I could find no reason for this to happen so I added the console.log line which indeed confirms the location header has been set. But it is set to the value i got from someAPI on the previous request not the current one.
This makes absolutely no sense to me. I do not store this value anywhere nor do I do caching or persistence of this data in any way.
Does anybody know what could be causing this?
I use express, express-namespace, mogoose and swig
I found out the problem was being caused bij the 'Restler' libaray used within 'someAPI'. I have no idea how this is possible but swapping it out with something else fixed the problem.
I'm building a site that has somewhat reddit-like functionality. I want user-submitted content to get its own page. Each submission is assigned a 5 character ID that I want to be in the URL for that page.
I've got this function in the router file which renders a page called titles:
exports.titles = function(req, res){
i = 0
read(function(post){
url = post[i].URL;
res.render('titles', {title: post[i].title, url: post[i].URL});
});
};
It is served by this statement in app.js:
app.get('/titles', home.titles); //home.js is the router file
The titles page has a link with the text post.title and the URL post.URL. When a user clicks on the link (e.g. domain.com/12345) they should be taken to a page called content with the content post.body.
How do I a)pass the URL back to my app.js file to include in an app.get, b) include the app.get function in this router file, or c) solve this in any other way?
Edit: I do have an object 'titles' that is a mongodb collection, but it is in a different module. No reason I can't add it to the router though.
Edit: I tried adding this to app.js to see if it would work:
app.get('/:id', function(req, res){
return titles.findOne({ id: req.params.id }, function (err, post) {
if (err) throw(err);
return res.render('content', {title: post.title, content: post.body});
});
});
Edit: I got it to work. All I did was format the title so that it would look like domain.com/titles/12345 and change app.get('/:id', to app.get('/titles/:id, ...
If I get you right I would do that the other way around.
Short version
I would get the id from the URL
Then I would pull from the database the data associated with this id
And use this data to build the final page.
You don't need to create a new route for each URL. An URL can contain some variable (here the id) and Express can parse the URL in order to get this variable. Then from this id you can get the data needed to build the proper page.
Long version
I assuming someone type in this URL: http://domain.com/1234.
I also assume that you have a variable titles which is a MongoDB Collection.
You can have a route defined like this:
app.get('/:id', function(req, res) {
// Then you can use the value of the id with req.params.id
// So you use it to get the data from your database:
return titles.findOne({ id: req.params.id }, function (err, post) {
if (err) { throw(err); }
return res.render('titles', {title: post.title, url: post.URL /*, other data you need... */});
});
});
Edit
I made some changes according to the last comments...