How to pass 2 values in .render() to Handelbars - node.js

I am trying to pass multiple values into res.render to be passed into my handlebars template. It seems to fulfil the condition but it doesn't show the content.
I'm trying to show a special navbar and a users snippets (their data) if they're logged in. Otherwise I want to display my normal navbar and all snippets (data from all users).
I've got it working if I just hand in a single value, but I can't seem to hand in multiple values.
Express code:
router.route('/')
.get(function (request, response) {
if (request.session.name) {
Snippet.find({ user: request.session.name._id }, function (err, data) {
if (err) {
console.log(err)
}
data.forEach(function (element, err) {
if (err) {
console.log(err)
}
})
let context = {
snippets: data.map(function (snippet) {
return {
_id: snippet._id,
user: snippet.user,
title: snippet.title,
body: snippet.body,
createdAt: snippet.createdAt
}
})
}
response.render('home/index', {loggedIn: true, context: context}) //Here is the issue
})
}
Handlebars template:
<ul class="people_list">
{{#if loggedIn}}
{{!-- Logged in --}}
{{#each snippets}}
<h2><i class="star fa fa-star-o" aria-hidden="true"></i> {{title}}</h2>
<h4>{{ createdAt }}</h4>
<pre>
<code class="language-javascript">{{body}}</code>
</pre>
<hr />
{{/each}}
{{else}}
{{!-- Not logged in --}}
{{#each snippets}}
<h2><i class="star fa fa-star-o" aria-hidden="true"></i> {{title}}</h2>
<h4>{{ createdAt }}</h4>
<pre>
<code class="language-javascript">{{body}}</code>
</pre>
<hr />
{{/each}}
{{/if}}
</ul>

You're passing in an array called context but referring to it as snippets in the handlebars code.
Either change your handlebars template to called {{#each context}} or change your nodejs code to response.render('home/index', {loggedIn: true, snippets: context}).

Related

Trouble rendering of documents with referenced fields on Vue.js mongoose

Recently, I picked up Vue.js and Mongoose to develop a personal project to mainly track Ingredients' on hand quantities on a certain online game.
Different dishes require different ingredients. Lotus Seed - Bird Egg soup, Jewelry Soup and Jade Parcels all require different number of Lotus Head as its ingredients.
I update an ingredient's quantity by using updateOne on ingredients collection.
Unfortunately, I originally embedded the ingredients on foods / dishes, which I realized problematic
recently, coz literally you just count ingredients what you currently have.
So a food document now looks like this
{
"_id" : ObjectId("5fca4ada32195d5814510242"),
"foodName" : "Lotus Seed and Bird Egg Soup",
"onHandQty" : 20,
"ingredients" : [
"5fca481432195d581451023f",
"5fca483932195d5814510240",
"5fca48a232195d5814510241"
]
}
I read about Mongoose's populate(), and tested to output one food/dish. Unfortunately there's nothing coming out of Vue.js front-end after trying that code.
server/models/Food.js
const { Router } = require('express');
const FoodItem = require('../../models/Food');
const IngredientItem = require('../../models/Ingredient');
const router = Router()
router.get('/', async(req, res) =>{
try {
const food = await FoodItem.findOne({
foodName: 'Lotus Seed and Bird Egg Soup'
}).populate('ingredients').
exec(function (err, food) {
if (err) return handleError(err);
console.log('The food is %s', food.foodName);
});
res.send(food);
} catch (error) {
res.status(500).json({
message: error.message
})
}
});
module.exports = router
A portion of component where ingredients are rendered
client/src/components/Food.vue
<div class="tile is-ancestor">
<div class="tile">
<div class="tile is-parent">
<div class="tile is-child box">
<template v-if="food.ingredients">
<div class="ingredients-block">
<p>Ingredients List:</p>
<ul class="ingredients-list">
<li class="row" v-for="ingredient in food.ingredients" :key="ingredient._id">
<div id="ingredient-image-container">
<img class="image is-64x64" :src="require(`../assets/images/food_inv/${ingredient.imagePath}.png`)" alt="ingredient.ingredientName" :title="ingredient._id">
{{ingredient.ingredientName}}
</div>
<div class="required-qty-container">
<!-- <i class="material-icons" id="required-inner-qty">food_bank</i> -->
Required:
{{ ingredient.requiredQty }}
</div>
<div class="on-hand-qty-container">
<p>On Hand:</p>
<input v-if="ingredient.onHandQty < ingredient.requiredQty" class="input is-danger on-hand-input" type="number" v-model="ingredient.onHandQty" min="0">
<input v-else class="input is-primary on-hand-input" type="number" v-model="ingredient.onHandQty" min="0">
<!-- <button class="button is-primary save-button" #click="test({ingredient_id: ingredient._id, onhandqty: ingredient.onHandQty})"><i class="material-icons">save</i></button> -->
<button class="button is-primary save-button" #click="$emit('update-qtys', {ingredient_id: ingredient._id, onhandqty: ingredient.onHandQty})"><i class="material-icons">save</i></button>
</div>
</li>
</ul>
</div>
</template>
</div>
</div>
</div>
</div>
Whole project on Github: Food Inventory
Quick Fixes,
change your food schema's ingredients field from object to array,
const foodSchema = new mongoose.Schema(
{
foodName: String,
imagePath: String,
effect: String,
onHandQty: Number,
// correct this to array
ingredients: [{
type: mongoose.Schema.Types.ObjectId,
ref: 'Ingredient'
}]
}
);
there are 2 ways to call mongoose methods first exec() with callback and second without exec() callback,
exec with callback that you have used buy need to send response (res.send(food) or res.json(food)) from inside the exec call function,
router.get('/', async(req, res) =>{
try {
await FoodItem.find()
.populate('ingredients')
.exec(function (err, food) {
if (err) return handleError(err);
console.log('The food is %s', food);
// put response here
res.json(food);
});
} catch (error) {
res.status(500).json({ message: error.message })
}
});
exec without call back
router.get('/', async(req, res) =>{
try {
const food = await FoodItem.find()
.populate('ingredients')
.exec();
res.json(food);
} catch (error) {
res.status(500).json({ message: error.message })
}
});

How to display express errors in ejs

I am validating emails users enter using "emailCheck" and a piece of code I found on another question, this is the code in my app:
app.post("/blog", (req, res) => {
const name = req.body.name;
const email = req.body.email;
emailCheck(email).then(() => {
const newSubscriber = {name: name, email: email};
Subscriber.create(newSubscriber).then(() => {
res.redirect("/blog")
})
.catch((error) => {
res.json({serverErrorEmailExistence: "This email adress is already in use!"})
})
})
.catch(() => {
res.json({serverErrorEmailExistence: "This Email doesn't exist!"})
})
})
This works as it is, but the errors are shown on a new blank page. I would like to show the error under the form that I have. Form is in included as a partial in my app.
Here is the form html:
<section id="emailSub">
<div id="emailContainer">
<h1>Subscribe to my Newsletter</h1>
<p>You will get weekly emails when a post is published.</p>
<form action="blog" method="POST" id="emailForm" autocomplete="off">
<div class="field">
<input type="text" placeholder="Name: " name="name" required>
</div>
<div class="field">
<input type="email" placeholder="Email: " name="email" required>
</div>
<button type="submit">Subscribe!</button>
</form>
</div>
<div id="thankYouMsg">
<h1>Thank you for subscribing!</h1>
<p><i class="far fa-check-circle"></i></p>
</div>
<button id="exitForm"><i class="fas fa-times"></i></button>
</section>
I include this on the blog main page with:
<%-include("partials/subscribe") %>
And here is my subscriber model:
const mongoose = require("mongoose");
const SubscriberSchema = new mongoose.Schema({
name: {
type: String,
required: true
},
email: {
type: String,
required: true,
unique: true
}
});
module.exports = mongoose.model("Subscriber", SubscriberSchema)
How can I show that error in the form?
The div with the ID thankYouMSg is shown after a successful form submit, usually it is hidden with Css.
I tried searching for this and I found a lot of answers but I either don't know how to include them in my code or I don't understand enough to search for the right answer (might be both). To be honest, I just included the emailcheck code in my app the best I know how. I don't really understand what .catch(error) is delivering.
Thank you
Following the answer I tried:
.catch(() => {
res.render("/blog", {errorMessage: "This email adress is already in use!"});
})
})
.catch(() => {
res.render("/blog", {errorMessage: "This Email doesn't exist!"})
})
But, I get the "cannot look up view /blog in views". I tried the same with
res.redirect and it just loads without anything happening.
What's happening is that in case of an error, you catch this error and return a json-response which the browser cannot render directly in html.
What you can do instead, is re-send your subscribe page and pass the caught error message to that page, which you can render there. Something like this should help you get started:
in your app.js
...
.catch(() => {
res.render("your-subscribe-template.ejs", {
errorMessage: 'This Email doesn\'t exist!'
});
});
...
in your template.ejs:
...
<% if (typeof errorMessage !== "undefined") { %>
<p>Form could not be submitted due to the following error:</p>
<p><%= errorMessage %></p>
<% } %>
...

Displaying a detailed post from ngFor

I'm creating a Blog using Angular 7, MongoDB and NodeJS. So far I have created a component that loops through all the posts from the database and displays them on a single page.
<div class="container-fluid">
<div class="row" *ngIf="posts.length > 0" >
<div class="col-lg-12 col-md-12 col-xl-12 text-center" *ngFor="let post of posts ">
<div class="post-preview" >
<h2 class="post-title text-center">
{{ post.title }}
</h2>
<h3 class="post-subtitle text-center">
{{ post.subtitle }}
</h3>
<br>
<div class="post-image">
<img [src]="post.imagePath" [alt]="post.title">
</div>
<br>
<p class="post-meta text-center">{{post.date}}</p>
</div>
</div>
</div>
</div>
This is for displaying all the blog posts on a single page. When a user clicks on a single post he should be directed to a page that shows detailed information for that post only (like the blog content). How do I implement that?
In the posts.service I have the following function for getting a single post.
getPost(id: string) {
return this.http.get<{_id: string, title: string, subtitle: string, content: string, date: Date, imagePath: string}>(
'http://localhost:3000/api/posts/' + id);
}
And on the backend I have the following route:
router.get("/:id", (req, res, next) => {
Post.findById(req.params.id).then(post => {
if (post) {
res.status(200).json(post);
} else {
res.status(404).json({
message: 'Post not found!'
});
}
});
});
I found the answer here: https://codecraft.tv/courses/angular/routing/parameterised-routes/
The soloution:
[1]
Spefify the parameterised route in router as:
{ path: 'blog/:postId', component: SinglePostComponent }
[2]
Create a link to direct the user to the the specified blog post
<a [routerLink]="['/blog', post.id]" > </a>
[3]
In the SinglePostComponent.ts put the following code:
export class SinglePostComponent implements OnInit {
post: Post;
private postId: string;
constructor(
private route: ActivatedRoute,
private postsService: PostsService
) {}
ngOnInit() {
this.route.paramMap.subscribe((paramMap: ParamMap) => {
this.postId = paramMap.get('postId');
this.postsService.getPost(this.postId).subscribe(postData => {
this.post = {id: postData._id, title: postData.title, subtitle: postData.subtitle,
content: postData.content, date: postData.date, imagePath: postData.imagePath};
});
}
);
}
}

How to use route parameters with Express

I'm having difficulty connecting the dots with the router.params objects with express.
Right now I'd like to have the button-success button in the code below point to a url that reads someaddress.com/formula/:item._id
HTML: (EJS Templating Engine)
<% formulas.forEach(function(item){ %>
<div class="pure-u-1 pure-u-sm-1-2 pure-u-md-1-4 pure-u-xl-1-5">
<div class="formula-block centered" id="<%= item._id %>">
<h4 class="padded-top"> <%= item.name %></h4>
<p> <%= item.description %></p>
<button class="button-success pure-button">Show</button>
<button class="button-warning pure-button">Delete</button>
</div>
</div>
<% }); %>
I am pairing that with this Express route:
router.get('/formula/:id', function(req, res){
var db = req.db;
var collection = db.get('formulas');
var id = req.params.id;
collection.find({"_id": id}, {}, function(e, doc){
res.render('formula/:id', {
formula: doc,
title: `formula for ${doc.name}`,
description: `modify and view ${doc.name} formula`
});
});
});
which then uses the information from the MongoDB document to generate the page.
It's not clear to me how you do this from looking at the documentation.
Thank you for your help.

Editing text on a page in node js

I am trying to implement a simple edit feature in my app. In my profile.handlebars file, I have an edit button. When clicked, I'd like the user's information to appear in the text input fields on the form in order to allow the user to edit their existing information.
Right now, they would have to input all of their information over again (and every field in the form would need to be filled out due to validation that I have implemented), click Submit, and their profile can be updated. Is this possible without using a framework (like Angular)? For example, in LinkedIn, a user can hover over a section of their profile causing the edit buttons to highlight, then click a single edit button, and they're instantly in editing mode. That might be too advanced for my purposes right now, but eventually, I'd love to have functionality like that.
I have a post request in my routes file to handle a user posting information to their profile:
router.post('/add', function(req, res) {
req.checkBody({
'city': {
errorMessage: 'Please enter your city'
},
'state': {
errorMessage: 'Please enter your state',
notEmpty: true
},
'zip': {
errorMessage: 'Please enter your zip code',
notEmpty: true
},
'about': {
errorMessage: 'Please briefly describe yourself',
notEmpty: true
}
});
console.log("req.user " + req.user);
var errors = req.validationErrors();
if (errors) {
res.render('profile', {
errors: errors
});
} else {
var user_info = new User_Info({
city: req.body.city,
state: req.body.state,
zip: req.body.zip,
about: req.body.about,
user_name: req.user.username
});
user_info.save(function(err, user_info) {
if (err) throw err;
});
res.redirect('profile/' + req.user.username)
}
})
Then, I have my profile.handlebars file:
{{#if errors}}
Uh oh! Something went wrong. Please review the below errors, and try again.<br><br>
<ul>
{{# each errors }}
<li style="color: red">{{this.msg}}</li>
{{/each}}
</ul>
{{else}}
<h3 align="center">Profile ({{user_name.name}})</h3>
<div class="row">
<div class="col-md-4 col-md-offset-4">
<div class="thumbnail" style="border-radius: 12px">
<div class="caption">
<p>City: {{# each information }} {{this.city}} {{/each}}</p>
<p>State: {{# each information }} {{this.state}} {{/each}}</p>
<p>Zip: {{# each information }} {{this.zip}} {{/each}}</p>
<p>About: {{# each information }} {{this.about}} {{/each}}</p>
<div class="btn-group">
<button type="Edit" class="btn btn-danger dropdown-toggle deleteLocation" data-id="{{this.id}}">Edit</button>
</div>
</div>
</div>
</div>
</div>
<br>
<center>
<form method="POST" action="/users/add">
<input type="text" name="city" placeholder="City" style="text-align: left">
<br><br>
<input type="text" name="state" placeholder="State" style="text-align: left">
<br><br>
<input type="text" name="zip" placeholder="Zip" style="text-align: left">
<br><br>
<textarea name="about" placeholder="About You" style="text-align: left; resize: both;" rows="5" cols="50"></textarea>
<br><br>
<div class="btn-group">
<button type="submit" class="btn btn-success dropdown-toggle" aria-haspopup="true" aria-expanded="false">Submit</button>
</div>
<br><br>
</form>
</center>
{{/if}}
Please let me know if you need additional info to help me solve this issue. Thanks!
you can use this code for node for editing the parameters , city,state,zip and about.
router.post('/add', function (req, res) {
var users = req.Collection;
var city = req.body.city;
var state = req.body.state;
var zip = req.body.zip;
var about = req.body.about;
var user_id = req.body.user_id;
if (city && state && ) {
users.findOneAndUpdate({_id: user_id}, {$set: {city: city, state: state, zip: zip, about:about}}, function (err, user) {
if (err) {
res.json({status: 0, message: err});
}
if (!user) {
res.json({status: 0, msg: "not found"});
} else {
res.json({status: 1, city: city, state: state, zip: zip, about:about, message: " edit success"});
}
})
} else {
res.json({status: 0, msg: "Invalid Fields"});
}
});

Resources