Timing functions correctly in Express, Node and Mongo - javascript

I'm working on an app were a parent user needs to create a student object that can be accessed by both the parent user and teacher users. Everything is working right except this one thing. When the parent user creates the student, it redirects to the user's dash where the newly created student should display. However, the student doesn't display until I log out and back in. I'm assuming that this is because my code doesn't run in the order I want it to, but I have no idea. I've been stuck here for a couple days. Help!
Here's the route handling for creating a student
EDIT:
I got it to work by refactoring things here almost entirely. I was creating a student document and saving it and then pushing that document into an array on the parent object. That was dumb for two reasons. One, it wasn't displaying properly in terms of timing. Two, if I ever wanted to update the student, I needed to update it in multiple instances. Here, I'm creating the document and adding a reference to an array on the parent object. Now when I edit/delete a student, I do it in one place, and the ObjectID is immediately accessible for the Student.findById() that runs in the "users/:id" route. Here's my updated code:
CREATE STUDENT
router.post("/users/:id/createStudent", middleware.isLoggedIn, function(req, res){
const firstName = req.body.firstName,
lastName = req.body.lastName,
age = req.body.age,
instrument = req.body.instrument,
parent = {
id: req.user._id,
username: req.user.username
},
newStudent = {
firstName: firstName,
lastName: lastName,
age: age,
instrument: instrument,
parent: parent
}
Student.create(newStudent, function(err){
if(err){
console.log(err);
req.flash("error", "error")
} else {
res.redirect("/users/:id")
}
})
});
And the Dashboard template:
<%- include ("../partials/header") %>
//
code for teachers exists here
//
<% } else if(user.isTeacher === false) {%>
<h1 class="ui huge header"><%= user.username%>'s Dashboard</h1>
<h2 class="ui big header">Here are your students:</h2>
<% if(students.length === 0) {%>
<p>Oops! Looks like you haven't registered a student. You can do that here. </p>
<% } else { %>
<div class="ui relaxed divided list">
<% students.forEach(function(student){ %>
<div class="item">
<div class="content">
<%= student.firstName %> <%= student.lastName %>
<ul>
<li>
<a class="ui secondary mini basic button"
id="assignments"
href="/student/<%= student._id%>">
<%= student.firstName %>'s Assignments
</a>
<form action="/student/<%= student._id %>?_method=DELETE" method="POST">
<button class="ui negative mini basic button" id="assignments">
Remove Student
</button>
</form>
</li>
</ul>
</div>
</div>
<% }) %>
<p>Add a new student.</p>
</div>
<% } %>
<%}%>
</div>
</div>
<%- include ("../partials/footer") %>
Here's the student model:
const mongoose = require("mongoose"),
Schema = mongoose.Schema;
const StudentSchema= new Schema({
firstName: String,
lastName: String,
age: String,
instrument: String,
assignments: [
{ id: {
type: mongoose.Schema.Types.ObjectId,
ref:"Assignment"
},
title: String
}
],
parent: {
id: {
type: mongoose.Schema.Types.ObjectId,
ref: "User"
},
username: String
},
teacher: [{
id: {
type: mongoose.Schema.Types.ObjectId,
ref:"User"
},
username: String
}]
});
const Student = mongoose.model("student", StudentSchema);
module.exports = Student;

Related

I have this code made with nodejs and i try to show in frontend the model of the car

I have a problem with this code.I try to show the car model in frontend with EJS but doesn't work.I make a router to get the model car from a collection and show it, but when i try to open that page, the model don't show up. I don't know what is wrong and what I need to do to work.
This is the code
The EJS part
<div class="row bg-text justify-content-center">
<% if(model !='' ){ %> <% model.forEach(function(model,index) { %>)
<div class="col-auto img-fluid">
<h1 style="color: rgb(196, 196, 196)" class="text-center">
<%= model.modelMasina.model %>
</h1>
<img class="dim-model" src="<%- model.poza %>" alt="<%- model.modelMasina.model %>" />
</div>
<% }) %> <% } %>
</div>
</div>
And here is the JS part
const router = express.Router();
const MasinaCategorie = require("../Models/categorie-modele");
const ModeleMasina = require("../Models/modele-masina");
router.get("/model/:nume", async (req, res) => {
try {
const nume = req.params.nume;
const model = await MasinaCategorie.find({modelMasina:{categorie:nume}});
res.render("modele", {
title: "Modele Masina",
model:model
});
} catch (error) {
res.status(500).send({
message: error.message || "Eroare la afisarea paginii principale",
});
}
});
module.exports = router;
And here is the Schema for MongoDB, i try to nest the model in category. I try in diffrent way to show but didn't work.
var mongoose = require('mongoose');
const modeleMasina= require("../Models/modele-masina");
var Schema = mongoose.Schema;
var categoriemasina = new Schema({
masina:{
type: String,
required: true,
},
poza:{
type: String,
},
locatie:{
type: String,
},
modelMasina:[{
categorie:{
type: String,
required: true,
},
model:{
type: String,
required: true,
},
poza:{
type: String,
},
}]
});
categoriemasina.index({modelMasina:'text'});
var CategorieMasina = mongoose.model('CategorieMasina', categoriemasina);
module.exports = CategorieMasina;

Can't render my object from db - "object not iterable"

I'm trying to show "events" created by an "artist" on the artist's page but am running into error 'artist.events is not iterable'. Below is my 'artist' model (models-> artists.js):
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const passportlocalMongoose = require('passport-local-mongoose');
const artistSchema = new Schema({
email: {
type: String,
required: true,
unique: true
},
location: {
type: String,
required: [true, 'Hometown (so you can be paired with local venues)']
},
genre: {
type: String
},
joined_date: {
type: Date,
default: Date.now
},
about: String,
size: Number,
});
artistSchema.plugin(passportlocalMongoose);
module.exports = mongoose.model('Artist', artistSchema);
Next is my event model (which associates artists with event through the 'artist' array.
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const eventSchema = new Schema({
event_name: String,
location: String,
description: String,
image: String,
artist: {
type: Schema.Types.ObjectId,
ref: 'Artist'
},
});
module.exports = mongoose.model('Event', eventSchema);
Here is where I'm running into my actual problem - whey I try to list events under artists I hit the error. Below is the artist show page (views-> artists-> show.ejs)
<% layout('layouts/boilerplate') %>
<div class="row">
<div class="col-12">
<div class="card mb-3">
<img src="<%= artist.image %>" class="card-img-top" alt="...">
<div class="card-body">
<h5 class="card-title"><%= artist.username %></h5>
<p class="card-text">Genre: <%= artist.genre %></p>
</div>
<ul class="list-group list-group-flush">
<li class="list-group-item">Location: <%= artist.location %></li>
<li class="list-group-item">Number of people in group: <%= artist.size %></li>
<li class="list-group-item">About: <%= artist.about %></li>
</ul>
<% if (currentUser && artist.equals(currentUser._id)) {%>
<div class="card-body">
<a class="card-link btn btn-primary" href="/artists/<%=artist.id%>/edit">Edit</a>
<form class="d-inline" action="/artists/<%=artist.id%>?_method=DELETE" method="POST">
<button class="btn btn-primary">Delete</button>
<% } %>
</form>
<% for(let events of artist.events) { %>
<div class="class mb-3">
<p>Event name: <%= event.event_name %></p>
</div>
<% } %>
</div>
</div>
<div class="card-footer text-muted">
Back to
All Artists
</div>
</div>
</div>
Full error:
TypeError: /Users/chaseschlachter/mtapp/views/artists/show.ejs:22
20| <% } %>
21| </form>
>> 22| <% for(let events of artist.events) { %>
23| <div class="class mb-3">
24| <p>Event name: <%= event.event_name %></p>
25| </div>
artist.events is not iterable
Adding my artists routes for context (routes-> artists.js):
const express = require('express');
const router = express.Router();
const passport = require('passport');
const Artist = require('../models/artist');
const catchAsync = require('../utils/catchAsync');
const ExpressError = require('../utils/ExpressError');
/* lists artists from database */
router.get('/', async (req, res) => {
const artists = await Artist.find({});
res.render('artists/index', { artists })
});
router.get('/new', (req, res) => {
res.render('artists/new');
});
/* shows specific artists that exist in database */
router.get('/:id', catchAsync(async(req, res,) => {
const artist = await Artist.findById(req.params.id);
if (!artist) {
req.flash('error', 'Cannot find that Artist');
return res.redirect('/artists');
}
res.render('artists/show', { artist });
}));
/* artist edits form*/
router.get('/:id/edit', catchAsync(async (req, res) => {
const artist = await Artist.findById(req.params.id);
if (!artist) {
req.flash('error', 'Cannot find that Artist');
return res.redirect('/artists');
}
res.render('artists/edit', { artist });
}))
router.put('/:id', catchAsync(async (req, res) => {
const { id } = req.params;
const artist = await Artist.findByIdAndUpdate(id, { ...req.body.artist });
res.redirect(`/artists/${artist._id}`);
}))
What am I doing wrong?
Looking at your artistSchema, you haven't defined an events field so there isn't anything to iterate over. That field is simply not there inside the artist object, it is undefined.
Your artistSchema creates a collection of artist objects. These objects have only the fields you've supplied in your schema definition. Whereas you have another collection of event objects which are completely separate from artists defined by the eventSchema.
Since you would like to associate artists with events you have several options to do this:
Maintain a list of events as a array inside the artist schema (potentially of ObjectIds which reference the event objects)
Have events retain a reference to the artist (as you currently do) and then query over events using the artist's _id.
Do not store events as a separate collection and instead embed the event objects as an array inside the artist schema.
Each strategy has it's own pros and cons (read more about that here: https://docs.mongodb.com/manual/applications/data-models-relationships/)
I would think your best bet would be to go with option 2 as with option 1 you would need to make sure to have any new events or deletions of events reflected back in the artist's model as well.
In fact, since you're using mongoose you can implement what you want using virtuals (https://mongoosejs.com/docs/populate.html#populate-virtuals) as follows:
artistSchema.virtual('events', {
ref: 'Event', // The model to use
localField: '_id', // Find events where `localField`
foreignField: 'artist', // is equal to `foreignField`
justOne: false // we can have more than 1 event per artist
});
Now we only need to populate this events array, many ways to do this, one way would be:
artist.events // == undefined
artist.populate('events').execPopulate();
artist.events // == [event1, event2, etc...]
If you add this virtual to your artist object and you populate your artist object prior to your piece of code, it should execute as expected. As I mentioned though, this is just one way to achieve this.

Filtering mongoose data on the clientside with EJS based on a dropdown selection

Beginner working on my first project here.
I have 2 Mongoose schematics and models containing categories and products. I nested the products within the categories model.
With Node and Express I send all of the categories to my page. Through using EJS and a for-loop I'm able to make a dropdown(select) list with all of the category names.
Now I'm failing when trying to make a list with all of the products contained inside of the selected categories.
I've went from trying to filter it inside the EJS tags, to combining an external script, to sending a AJAX POST request with the selected data. Nothing has worked.
The schemas
const productSchema = new mongoose.Schema ({
image: String,
name: {type: String, required: true},
description: {type: String, required: true},
category: {type: String, required: true},
price: {type: Number, required: true}
});
const categorySchema = new mongoose.Schema ({
name: {type: String, required: true},
products: [{
type: mongoose.Schema.Types.ObjectId,
ref: "Product"
}]
});
The route
app.get("/proposals/create", (req, res) => {
Category.find({}).populate("products").exec((err, cats) => {
if(err) {
console.log(err);
} else {
res.render("createproposal", {cats: cats});
}
});
});
The loop
<select id="catDropdown" class="ui fluid dropdown">
<option value="">Category</option>
<% for(i = 0; i < cats.length; i++) { %>
<option><%= cats[i].name %></option>
<% }; %>
</select>
I would advise you use optgroup, just to have to have your code simple and your data concentrated.
<select id="catDropdown" class="ui fluid dropdown">
<option value="">Category</option>
<% cats.forEach(category => { %>
<optgroup label="<%= category.name %>" >
<% category.products.forEach(product => { %>
<option> <%= product.name %> </option>
<% }); %>
</optgroup>
<% }); %>
</select>

Creating new array with variables coming back as undefined

I am creating a blog web application. I currently have the blogSchema set up as
var BlogSchema = new mongoose.Schema({
title: String,
image: String,
body: String,
created:
{type: Date, default: Date.now},
author: {
id: {
type: mongoose.Schema.Types.ObjectId,
ref: "User"
},
username: String
},
comments: [
{
type: mongoose.Schema.Types.ObjectId,
ref: "Comment"
}
]
});
module.exports = mongoose.model("Blog", BlogSchema);
I have a post form of the following
<div class="ui main text container segment">
<div class="ui huge header">New Blog</div>
<form class="ui form" action="/blogs" method="POST">
<div class="field">
<label>Title</label>
<input type="text" name="blog[title]" placeholder="title">
</div>
<div class="field">
<label>Image</label>
<input type="text" name="blog[image]" placeholder="image">
</div>
<div class="field">
<label>Blog Content</label>
<textarea name="blog[body]"></textarea>
</div>
<input class="ui violet big basic button" type="submit">
</form>
</div>
On my blog routes page I have my create route as
router.post("/blogs", function(req, res){
// create blog
//req.body.blog.body = req.sanitize(req.body.blog.body);
var title = req.body.title;
var image = req.body.image;
var desc = req.body.body;
var author= {
id: req.user._id,
username: req.user.username
}
var newBlogpost = {title: title, image: image, body: desc, author: author}
Blog.create(newBlogpost, function(err, newBlog){
if(err){
res.render("blogs/new");
} else {
//then, redirect to the index
console.log(newBlogpost)
res.redirect("/blogs");
}
});
});
However when I sign in on the web application and complete the form and view my commmand line for the return of console.log(newBlogpost) after I submit, I get:
{ title: undefined,
image: undefined,
body: undefined,
author: { id: 5a0cbcc3d6c7070a7bb6c45e, username: 'cat' }
I am not sure why the new variable array I am creating has these three variable as undefinded and would appreciate help.

Dynamically setting image in underscore template

I'm currently using underscore templates to render a HTML list that displays a list of contacts.
The template looks something like this:
<li>
<span class="name">Name: <=%data.name%></span>
<span class="email">Name: <=%data.email%></span>
<img class="avatar" src="<=%data.avatar%>"></img>
</li>
The issue is, when I set the template data, the source of the image won't be known. Why? Because my data looks something like this:
contact = {
name: string, // i.e. 'John Doe'
email: string, // i.e 'john#doe.com'
avatar: string // i.e. '11a93150-14d4-11e3'
}
The avatar is actually not a URL, rather a link to a remote database that needs fetching. Something like:
function getAvatar(uuid, cb) { // uuid is something like 11a93150-14d4-11e3
window.db.getImageUrl(function(url) {
cb(url); // url is something like http://foo.com/avatar.png
});
}
Question is, is there a way to write my template so that instead of reading the avatar value of the contact object directly, I can embed a reference to a function like getAvatar that when the template is rendered, fetches the url to the image and sets the avatar image URL?
Thanks in advance
Here's an example to demonstrate how you can call JavaScript functions and asynchornously update src attribute of thumbnails. I've tried to simulate your DB call using setTimeout and the DB using associative array.
HTML:
<script type='text/html' id='contactTemplate'>
<li id="contact-<%= avatar %>">
<span class = "name"> Name: <%= name %> </span>
<span class="email">Name: <%= email %></span>
<img class = "avatar" data-populate-path="<% getPath( avatar ) %>" />
</li>
</script>
<ul id='contactList'></ul>
JavaScript:
var contacts = [
{name: 'John Doe', email: 'john#doe.com', avatar: '11a93150-14d4-11e3'},
{name: 'Hannah Smith', email: 'hannah#smith.com', avatar: '11a93150-14d4-1231' }
],
simulatedDB = [];
simulatedDB['11a93150-14d4-11e3'] = "path to avatar 1";
simulatedDB['11a93150-14d4-1231'] = "path to avatar 2";
$(document).ready(function () {
var compiled = _.template($("#contactTemplate ").html());
_.each(contacts, function (d, i) {
$("#contactList").append(compiled(d));
});
});
function getPath(target) {
setTimeout(updateAvatar, 1000, target);
}
function updateAvatar(target) {
$("#contact-"+target+" img").attr("src", simulatedDB[target]);
}

Categories

Resources