How do I update a specific document from a form through mongoose? - node.js

Struggling with this problem because I'm missing some fundamentals I think, but after hours looking through the docs I think I need some help :)
I have a (simplified) collection as so:
const ExampleSchema= new Schema ({
Word: String,
Difficulty: String,
)};
Which I am succesfully displaying on a page as so:
<% ExampleSchemas.forEach(exampleschema=> { %>
<p><%= exampleschema.word %></p>
<p style="display: none" name="ExampleSchemaID" value="<%= ExampleSchema._id %>"</p>
<% }) %>
For each word I have a form below, I would like the user to be able to select easy, ok or hard and for this to set the value in the DB.
<form method="PUT">
<button formaction="/review/feedback/easy">Easy</button>
<button formaction="/review/feedback/ok">Ok</button>
<button formaction="/review/feedback/hard">Hard</button>
/form>
I have played around with a route like this to no avail
router.put("/review/feedback/easy", function (req,res){
var ID = req.body.ExampleSchemaID;
ExampleSchema.findByIdAndUpdate(
req.params.ExampleSchema._id,
req.Difficulty= easy);
The further issue is that I would like to display x number of these documents to the user, therefore I need a route that gets only that specific word.

router.put("/review/feedback/easy", function (req,res){
var ID = req.body.ExampleSchemaID;
ExampleSchema.findByIdAndUpdate({"_id": req.params.ExampleSchema._id},{"Difficulty": "3"});
but please not your difficulty is a number, so why not use a number instead:
const ExampleSchema= new Schema ({
Word: String,
Difficulty: Number,
)};
router.put("/review/feedback/easy", function (req,res){
var ID = req.body.ExampleSchemaID;
ExampleSchema.findByIdAndUpdate({"_id": req.params.ExampleSchema._id},{"Difficulty": 3});

Related

trying to pass check boxes as string (node)

im doing a small project to learn fullstack (mongo&node&express&ejs), what i have in the project:
-object - name film. it got a couple of fields, like length, genre, director.
-an add film form.
i store the objects in mongo. overall everything is working good.
in the add film, the genre label is an options one - and i decided it would be more professional to make it check boxes, so user can choose how many genres as he want (after all most films are belong to a couple of genres.) i need one of the 2 next options:
-to change the genre field type of the object from string to array, and store an array of all the select check boxes, and show them in the film page. (i didnt manage to achieve an array of all the selected check boxes name)
-leave the genre field type as string, and then just display it as string. (didnt work so far, gets blank)
right now when its just options, it get passed as string, and all is working good and really simple. but i cant manage to work with the check boxes so far. tried as array and as String and so far all errors or undefined. i looked alot of solutions and all i saw are solutions in php or solutions that require document property (which doesnt work, from googling i understood its because of node.)
any idea how can i implement such thing?
this is what i have now (genre as options, and its working):
the object (not all the fields)
const filmSchema = new mongoose.Schema({
title: {
type: String,
required: true
},
description: {
type: String,
required: true
},
releaseYear: {
type: Number,
required: true
},
genre: {
type: String,
required: true
},
#the form genre part
<label>Genre</label>
<select name="genre">
<% genre.forEach(g => { %>
<% if (g === film.genre) { %>
<option selected label="<%= g %>" value="<%= g %>"></option>
<% } else { %>
<option value="<%= g %>"><%= g %></option>
<% } %>
<% }) %>
</select>
the post (relevant parts)
router.post('/', async (req, res) => {
const film = new Film({
title: req.body.title,
director: req.body.director,
releaseYear: req.body.releaseYear,
genre: req.body.genre,
length: req.body.length,
description: req.body.description,
})
displaying a film
<h2> <%= film.title %> </h2>
<img height="150" width="100" src="<%= film.posterImagePath %>">
<div>Director: <%= film.director.name %></div>
<div>Release Year: <%= film.releaseYear %> </div>
<div>Genre: <%= film.genre %> </div>
<div>Length: <%= film.length %></div>
<div>Description: <%= film.description %></div>
im stuck on this problem for a few hours, any help would be greatly appreciated!

Checkbox value is refreshed after page reload in node JS

"I am creating TODO list using Node as backend. after adding every new item, a checkbox is also generating in front of them so I can apply "CSS line-through" to let user know that item is done or of no use. But when I add another item, the page refreshes and that checkbox is unchecked as I am not storing that value anywhere. Can you tell me how to store the value of that checkbox in the backend?
HTML -
<div class="box" >
<% for (var i=0; i<newListItems.length; i++) { %>
<div class="item">
<input type="checkbox" id="checkBox">
<p> <%= newListItems[i] %> </p>
</div>
<% } %>
<form action="/" method="post" class="item">
<input class="inputBox" type="text" name="newItem" placeholder="New item" autocomplete="off" required="required">
<button type="submit" name="list" value=<%= listTitle%>> +</button>
</form>
</div>
Node JS -
const items = [];
app.post("/", function(req, res){
let item = req.body.newItem;
items.push(item);
res.redirect("/");
});
The answer involves a lot of code, so I will give you a set of steps that can help in your case.
You need to change your data scheme. Currently looks like you are just storing the string in an array of items. You need to change it to be array of objects. Each object should have the field task and done. So you could know which task is done or not.
app.post("/", function(req, res) {
let item = req.body.newItem;
items.push({ name: item, done: false });
res.redirect("/");
});
Next step will be adding an endpoint that will be changing the done field of an array item to true.
Then on a front-end you will need to write some JS code that will be sending an HTTP request to the endpoint that marks the task as done. You need to use AJAX call for that, for example, NPM package axios.
Change the template to reflect the changes to the data. e.g. instead of <%= newListItems[i] %> do <%= newListItems[i].name %> and add logic to render checked checkbox based on done property.
It worth to mention, that you should not store data in memory, because once the process is done, you will lose your data. It is okay for learning purposes, but in production, you should use a database.

Cast to ObjectId failed for value "req.params.id" at path "_id" for model "Project"

I am trying to create a clone of employee management system. I want to implement a feature that when admin opens a details page for any project he will get list of available employees as checkboxes and when select them and submit then those employees will be added to the given project.
I am developing it using Node.js and MongoDB
My project schema is -
const ProjectSchema = new mongoose.Schema({
name: String,
deadline: Date,
description: String,
manager:{
id: {
type: mongoose.Schema.Types.ObjectId,
ref: "User"
},
},
employees: [
{
type: mongoose.Schema.Types.ObjectId,
ref: "User"
}
],
created_on: {
type: Date,
default: Date.now
}
});
Form -
<form action="/admin/project/<%= project._id %>/addmembers" method="POST">
<div class="row">
<% employees.forEach((employee) => { %>
<% if(!employee.projects.includes(project._id)){ %>
<div class="col-12 col-sm-6 col-md-4 col-xl-3">
<input type="checkbox" value="<%= employee._id %>" name="newMembers"> <%= employee.name %> </input>
</div>
<% } %>
<% }); %>
</div>
<hr class="my-2">
<p class="lead">
<button class="btn btn-blue btn-lg">Add Members</button>
</p>
</form>
Code for handling the request -
router.post("/project/:id/addmembers", (req, res) => {
console.log("add member post route");
Project.findById(req.params.id, (err, foundProject) => {
if(err){
console.log(err);
req.flash("error", err.message);
res.redirect("back");
} else {
req.body.newMembers.forEach((employee) => {
foundProject.employees.push(employee);
})
foundProject.save()
console.log(foundProject);
req.flash("success", "Successfully added new members");
res.redirect("/admin/project/req.params.id");
}
})
});
Now whenever I submit form users are added but I always get an error Cast to ObjectId failed for value "req.params.id" at path "_id" for model "Project" instead of success message now I am new to mongo so I googled it but can't solve my problem can anyone explain me why this error is coming and how can I fix it?
Also I know that if if only one checkbox is selected then req.body.newMembers will not be array. If you can you provide better method to do it, it will be very helpful?
Update -
I also tried findOne(), find(), findByIdAndUpdate(), and id = mongoose.Types.ObjectId(req.params.id) but still get the same error message
It looks like what is happening is you aren't properly handling ids and _id. Given that you haven't mentioned anything, I assume you see this error only once. However, given that the error has to do with the project model/schema, and you use almost the same code for both manager and employees, the error should have to do with a difference between the two. The main difference I see is manager has id: {*id stuff*}, but employee has {*id stuff*} with no id:. However, I don't think this is the actual error, but just something to keep in mind. Try also changing the id type to something like number just to see if this is related to the issue.
What I think that actual error is is the fact that you use req.params.id in a findById(). This searches your database using the _id. First, having id and _id both is a bit redundant (if you have a reason, that's fine, but _id is something MongoDB needs, so it might be better to just stick with it as it is precreated and handled). Second, you are using a string/int as an id search. To test whether this is the issue, try changing findById() to something like findOne() or find(). Just to clarify, findById() doesn't explicitly need an ObjectId, so the type may not be the issue, but findById() does handle the search differently than find({_id: id}).
Another thing to check is that req.params.id actually gives the right value. It should, but this is something to verify. To do so, just console.log() it. If this is the problem, try moving :id to the end of the path.
In response to your edit:
Make sure that the problem doesn't have to do with not having id: in the employee (as stated above) and that it doesn't have to do with the id being a ObjectId rather than a number. If you change the id to a number, make sure to change the search so that it finds the object by id not _id. Just to let you know, every MongoDB object has an _id (even subobjects like employee and manager), so using id is somewhat redundant.
To give you more things to try: make sure you console.log() req.params.id to make sure it has the right value. Then, also try commenting sections of code until you find which line it is on. Try hardcoding some input until it works.

returning or looking up object from html input in node express

I have an html/handlebars form set up with a Node/Express backend. the form offers options populated from a database. I am able to get the form to return a single user selected value and save it to my mongodb, but I really need the whole object.
{{#each proxyObj}}
<p>
<label>
<input type="radio" name="proxyTitle" value="{{title}}"/>
<span>{{title}}</span>
</label>
</p>
{{/each}}
and this is the express:
router.post("/proxies/:id", ensureAuthenticated, (req, res) => {
Project.findOne({
_id: req.params.id
}).then(project => {
const newProxy = {
proxyTitle: req.body.proxyTitle
// I need the other object values to go here, or to be able to retrieve them later
};
// Add to proxy array on the Project object in the collection
project.proxies.push(newProxy);
project.save().then(project => {
res.redirect(`/projects/stakeholders/${project.id}`);
});
});
});
Is it more sensible to try to load in the entire object as a value in the input field, or to return the id of the object, and look it up in the db? I need to display some of the returned object information on the same page, and also to use it later. Which is more efficient, and what is the best way to achieve it?
If I'm getting it right, the problem is that you're trying to put multiple inputs with the same name on one form in <input type="radio" name="proxyTitle" value="{{title}}"/>, which gives you something like
<input type="radio" name="proxyTitle" value="Title 1"/>
<input type="radio" name="proxyTitle" value="Title 2"/>
<input type="radio" name="proxyTitle" value="Title 3"/>
As explained here, the browsers will chew it, but the server-side handling may require some adjustments.
In your case, the easiest fix would be to add index to the names of parameters. So, your form would be looking like this:
{{#each proxyObj}}
<p>
<label>
<input type="radio" name="proxies[{{#key}}]" value="{{this}}"/>
<span>{{this}}</span>
</label>
</p>
{{/each}}
(note that if proxyObj is an array, you would have to use #index instead of #key; also, depending on the proxyObj fields' structure, you may have to use this.title as the values to display and whatnot).
As for your server-side handling, you'll have to loop through the proxies you receive and handle them one by one, e.g.
router.post("/proxies/:id", ensureAuthenticated, (req, res) => {
Project.findOne({
_id: req.params.id
}).then(project => {
project.proxies = []; // this is only in case you wanna remove the old ones first
const proxies = req.body.proxies;
for(let i = 0; i < proxies.length; i++) {
// Add to proxy array on the Project object in the collection
project.proxies.push({ proxyTitle: proxies[i].title });
}
project.save().then(project => {
res.redirect(`/projects/stakeholders/${project.id}`);
});
});
});

How to catch the object_id of a post submit in Node.js (MongoJS)?

I have this code in app.js where I insert the content of the title and content fields to their respective document fields in MongoDB:
//post to 'post/new'..
app.post('/post/new', function(req, res){
//get the `title` and `content` fields & save them as variables to be later reused (they rely on the `name:` values).
var title = req.body.title;
var content = req.body.content;
//call the database and find the `_id` to be used in the redirect later..
db.local.find({_id: ObjectId(req.params.id)}, function(id) {});
//insert the title and content in the database (taken from the submit form)
db.local.insert ( {title: title, content: content},
//not sure if I really need to define an err&doc right now so to be ignored..
function(err, doc) {
//redirect to "localhost.com/post/(post)_id/(post)title"
res.redirect('/post/'+req.params.id+'/'+req.body.title);
});
});
This is what I have on post_new.ejs:
<form method="post">
<div>
<div>
<span>Title :</span>
<input type="text" name="title" id="editPostTitle" />
</div>
<div>
<span>Content :</span>
<textarea name="content" rows="20" id="editPostBody"></textarea>
</div>
<div id='editPostSubmit'>
<input type="submit" value="Send" />
</div>
</div>
</form>
The problem is that I get all but no _id in the res.redirect to work, meaning, the title works wonderfully, but the _id no..
How can I get the object id to work in the redirect? Thank you!
EDIT
This is the problem I get and think it's unrelated..but I'll include it for the full view of the issue.
500 Error: Argument passed in must be a single String of 12 bytes or a string of 24 hex characters
Assuming you are using the native node MongoDB driver the callback of the insert function returns the array of inserted objects.
The example in those docs is a pretty good one. Adjusted for your example, it would like something like this:
var MongoClient = require('mongodb').MongoClient;
MongoClient.connect('mongodb://127.0.0.1:27017/test', function(err, db) {
db.collection('local').insert({title: title, content: content}, function(err, docs) {
console.log('Inserted _id is '+docs[0]._id);
});
});
Regarding your error, it sounds like req.params.id is not a valid BSON ObjectId.

Resources