Filtering node.js (express, mongoose) app - node.js

I am trying to a node.js application (express, mongoose) and I want to give the option to the user to filter the data they receive through checkboxes. In the front end I store the user's choice in cookies (I do not know know if there is a better way) but I failed to filter the data in my database. The html code is that:
A modal that gives the user the option to check for specific data
<div id="modal">
<div id="modalContent">
<h3>Filters</h3><svg xmlns='http://www.w3.org/2000/svg' id="closeBtn" class='ionicon' viewBox='0 0 512 512'><title>Close</title><path fill='none' stroke='currentColor' stroke-linecap='round' stroke-linejoin='round' stroke-width='32' d='M368 368L144 144M368 144L144 368'/></svg>
<div>
<form method="GET">
<div>
<input type="checkbox" class="save-cb-state" name="Vasilopoulos" value="Vasilopoulos">Vasilopoulos
</div>
<div>
<input type="checkbox" class="save-cb-state" name="Masoutis" value="Masoutis">Masoutis
</div>
<div>
<input type="checkbox" class="save-cb-state" name="Web Only" value="Web Only">Web Only
</div>
<div>
<input type="checkbox" class="save-cb-state" name="In Store" value="In Store">In Store
</div>
<button type="submit">Αναζήτηση</button>
</form>
</div>
</div>
</div>
My model is that:
The checkboxes are filtering for storeName and/or offerType (The problem is when the user wants to filter both data)
const mongoose = require("mongoose");
const offersSchema = new mongoose.Schema({
imgLink: String,
title: String,
startPrice: Number,
discountPrice: Number,
pageLink: String,
storeName: String,
offerType: String,
discountPercentage: {
type: String,
trim: true
}
});
module.exports = mongoose.model("Offers", offersSchema);
The first two checkboxes are referring to StoreName attribute of my model and the last two are referring to offerType
The code in my controller is this:
async function getOffers(req) {
const cookiesArray = Object.values(req.cookies);
const page = parseInt(req.query.page || 1);
const sort = req.query.sort || "discountPrice";
const itemsPerPage = 10;
let products;
//If cookiesArray length is greater than 0 means that the user has check at least one checkbox
if(cookiesArray.length !== 0) {
products = await Offers.find({ offerType: { $in: cookiesArray}, storeName: { $in: cookiesArray } })
.skip((page - 1) * itemsPerPage)
.limit(itemsPerPage)
.sort(sort);
} else {
products = await Offers.find()
.skip((page - 1) * itemsPerPage)
.limit(itemsPerPage)
.sort(sort);
}
return {
category: "offers",
products,
sort,
currentPage: page,
hasNextPage: itemsPerPage * page < totalProducts,
hasPreviousPage: page > 1,
nextPage: page + 1,
previousPage: page - 1,
lastPage: Math.ceil(totalProducts / itemsPerPage)
}
}
getOffers is a helper function that my controller uses to fetch data. I tried to use the in operator but if the user check one or two values from the first two checkboxes and one or two values from the last two check boxes the in operator fails.

Related

Selecting first element from the dropdown [ select tag ] cause the backend to crash but selecting other works just fine

I'm building a simple app with node.js and reactjs but the problem arises when I want to add book especially while selecting dropdown value.
In the select tag when I select the first element it crashes the backend but when I select the second or below it they work just fine. What could be the reason behind it ? Dropdown menu has been filled correctly. My code is :
import React, { Component } from 'react'
import axios from 'axios'
export default class AddBook extends Component {
state = {
name : '',
author : '',
publishers : '',
pages :'',
genres : [],
genre : '',
addedMessage : null
}
async componentDidMount() {
const genres = await axios.get('http://localhost:5000/api/genres')
this.setState({ genres : genres.data})
}
onInputChange = (e) => {
this.setState({[e.target.name] : e.target.value }) //looks for name
}
onFormSubmit = (e) => {
e.preventDefault();
const book = {
name : this.state.name,
author : this.state.author,
publishers : this.state.publishers,
pages : this.state.pages,
genres : this.state.genre
}
axios.post('http://localhost:5000/api/books', book)
.then(res => console.log(res))
.catch(err => console.log("error occured while posting data ", err))
console.log(book)
//alert("book added successfully !!!")
this.setState({addedMessage : 'Book added successfully'})
window.location = "/books"
}
render() {
const { name, author, publishers, pages, genres, addedMessage } = this.state;
return (
<div className="container">
<h2>ADD BOOK </h2>
{ addedMessage && <h2 style={{textAlign : 'center'}} className="alert alert-info">{addedMessage}</h2> }
<form onSubmit = { this.onFormSubmit }>
<div className="form-group">
<label htmlFor="name">BookName:</label>
<input type="text" required className="form-control" id="name"
placeholder="Enter Book name" name="name"
onChange={this.onInputChange}
value={name}
/>
</div>
<div className="form-group">
<label htmlFor="author">Author:</label>
<input type="text" required={ true } className="form-control"
id="author" placeholder="Enter Author" name="author"
onChange={this.onInputChange}
value={author}
/>
</div>
<div className="form-group">
<label htmlFor="publishers">Publishers:</label>
<input type="text" required className="form-control"
id="publishers" placeholder="Enter Publishers Name"
name="publishers"
onChange={this.onInputChange}
value={publishers}
/>
</div>
<div className="form-group">
<label htmlFor="pages">Pages:</label>
<input type="number" required className="form-control"
id="pages" placeholder="Enter No of Page" name="pages"
onChange={this.onInputChange}
value={pages}
/>
</div>
<div className="form-group">
<label htmlFor="genre">Category:</label>
<select type="select" className="form-control"
id="genre"
name="genre"
onChange={this.onInputChange}
>
{/* <option value="selectCategory" onChange={this.onInputChange}>Select</option> */}
{
genres.map( genre => (
<option key={genre._id} onChange={this.onInputChange} value={genre.name}>
{genre.name }</option>
))
}
</select>
</div>
<button type="submit" className="btn btn-primary">Submit</button>
</form>
</div>
)
}
}
My backend is up and running and has successfully fetched the genres[ categories ] , but I am not able
to figure out why selecting the first doesn't work but others just work fine. How should I solve it ?
It shows the following error for this case.
Error: Book validation failed: genres: Path `genres` is required.
at ValidationError.inspect
(E:\nodejs\nodejs\MERN_STACK_Book\node_modules\mongoose\lib\error\validation.js:48:26)
at formatValue (internal/util/inspect.js:718:31)
at inspect (internal/util/inspect.js:287:10)
at afterInspector (internal/errors.js:682:14) {
errors: {
genres: ValidatorError: Path `genres` is required.
at validate (E:\nodejs\nodejs\MERN_STACK_Book\node_modules\mongoose\lib\schematype.js:1178:13)
at E:\nodejs\nodejs\MERN_STACK_Book\node_modules\mongoose\lib\schematype.js:1161:7
at Array.forEach (<anonymous>)
at SchemaString.SchemaType.doValidate
(E:\nodejs\nodejs\MERN_STACK_Book\node_modules\mongoose\lib\schematype.js:1106:14)
at E:\nodejs\nodejs\MERN_STACK_Book\node_modules\mongoose\lib\document.js:2387:18
at processTicksAndRejections (internal/process/task_queues.js:79:11) {
properties: [Object],
kind: 'required',
path: 'genres',
value: '',
reason: undefined,
[Symbol(mongoose:validatorError)]: true
}
},
_message: 'Book validation failed'
}
[nodemon] app crashed - waiting for file changes before starting...
And code for my book schema is as follows
const mongoose = require('mongoose')
//schema for genres
const bookSchema = mongoose.Schema({
name : {
type : String,
required : true,
trim : true,
unique : true //title is made unique.
},
author : {
type : String,
required : true,
trim : true
},
publishers : {
type : String,
trim : true,
},
pages : {
type : Number,
required : true,
trim : true
},
genres : {
type : String,
required : true,
trim : true
},
create_date : {
type : Date,
default : Date.now
}
})
//It will create books collection in your database and documents
//inside that collection will have fields from bookSchema when you save first document.
const Book = module.exports = mongoose.model('Book', bookSchema)
//get the books
module.exports.getBooks = (callback, limit) => {
// Book.find(callback)
Book.find(callback).limit(limit)
}
//get single book
module.exports.getBookById = (id,callback) => {
// Book.find(callback)
Book.findById(id,callback);
}
module.exports.addBook = (book,callback) => {
Book.create(book, callback);
}
module.exports.updateBook = (id,book,options, callback) => {
const query = {
_id : id
}
const updatedBook = {
name : book.name,
author : book.author,
publishers : book.publishers,
pages : book.pages,
genres : book.genres
}
Book.findByIdAndUpdate(query, updatedBook, {} , callback);
}
module.exports.deleteBook = (id,callback) => {
const query = {
_id : id
}
Book.findByIdAndDelete(query, callback);
}
I checked some of the similar answers in the stackoverflow but couldn't figure out why I'm getting that
error.
Here's the problem with your code :
The first option of the dropdown is chosen by default, and thus "selecting" the first option doesn't trigger the Category dropdown's onChange function (because the selected value doesn't really change).
Since the default value of this.state.genre is an empty string, the genres property of the book you send to the backend also has genres as an empty string.
Finally, Mongoose does not accept an empty string for a required string field, which gives you the Path 'genres' is required error.
Try switching to another option and back to the first. It will work then because the onChange function will then get called and this.state.genre will be set properly.
There are a few ways to fix this:
Add a placeholder option to the dropdown - something like "Select an option...". Then the user will be forced to select a genre from the dropdown and onChange would be called.
Or, in componentDidMount, set this.state.genre to the first genre of the list of genres you fetch.

In React, how do I display the results from an API get request?

I've created API's for a standard transaction application (using mongoDB for DB), I have API's for users, products and orders. I managed to use and display the information stored about users and products, however, when I try to follow the same code for displaying my orders (which shares objects from products and users, in mongoDB collections) I get stuck. I know this has something to do with how I've named (const { _id, username, useremail, productname, productsellprice } = order;) as the _id works.
Other details: using= axios for http requests; context api with hooks (useContext, useReducer)
Im new to all this so forgive me if I've left out crucial information.
import React, { useContext } from 'react';
import PropTypes from 'prop-types';
import OrderContext from '../../context/order/orderContext';
const OrderItem = ({ order }) => {
const orderContext = useContext(OrderContext);
const { deleteOrder, setCurrent, clearCurrent } = orderContext;
const { _id, username, useremail, productname, productsellprice } = order;
const onDelete = () => {
deleteOrder(_id);
clearCurrent();
};
return (
<div className='card bg-light'>
<h3 className='text-primary text-left'>
{_id}{' '}
<span
style={{ float: 'right' }}
className={
'badge ' +
(productsellprice === 'professional' ? 'badge-success' : 'badge-primary')
}
>
{'£ ' + productsellprice}
</span>
</h3>
<ul className='list'>
{useremail && (
<li>
<i className='fas fa-marker' /> {useremail}
</li>
)}
{productname && (
<li>
<i className='fas fa-marker' /> {productname}
</li>
)}
</ul>
<p>
<button
className='btn btn-dark btn-sm'
onClick={() => setCurrent(order)}
>Edit
</button>
<button className='btn btn-danger btn-sm' onClick={onDelete}>
Delete
</button>
</p>
</div >
);
};
OrderItem.propTypes = {
order: PropTypes.object.isRequired
};
export default OrderItem;
I want to display the order data in a simple ui card. Currently it only displays the order_id
When you print order in console, you get this,
Object product: {_id: "5d1a62c40d16f11d94009a7a", name: "Lead18", sellprice: 104} saledate: "2019-07-03T19:08:05.630Z" user: email: "irl1984#yahoo.co.uk" name: "Kevin Kane" _id: "5d1cd8fbc02bc928d83ea109" proto: Object _id: "5d1cfd156cd4673840981c5d" proto: Object OrderItem.js:6 Object product: {_id: "5d1a62c40d16f11d94009a7a", name: "Lead18", sellprice: 104} saledate: "2019-07-03T18:43:14.409Z" user: {_id: "5d1cd8fbc02bc928d83ea109", name: "Kevin Kane", email: "irl1984#yahoo.co.uk"} _id: "5d1cf74207fa3337cceb5e1e" proto: Object
It means you are getting object inside object.
So instead of this,
const { _id, username, useremail, productname, productsellprice } = order;
You should use Destructuring nested objects,
const { product:{_id: product_id}, user:{name: username}, user:{email: useremail}, product:{name: productname}, product:{sellprice: productsellprice} } = order;
Here I am not sure which _id you want to show, if you want _id from user object then use this use:{_id}.

Node.js with express insert new field into existing mongoDB document from server side

Hello I am looking to implement a way to dynamically insert new fields to an existing mongoDB document from the server side with node.js and express.
For example in the local mongoDB the document looks like this.
{
value: 'Google',
url: 'https://google.com',
env: 'Test'
}
I have a route that will already update the current document fields from a form on the UI. However I want to combine that logic with the ability to insert new fields upon updating.
The route below handles updating the document with the existing fields.
router.put("/:id", (req, res) => {
let value = req.body.value;
Application.findByIdAndUpdate(req.params.id, req.body.application, (err,
updatedApp) => {
if(err){
console.log(err);
} else {
console.log(updatedApp)
req.flash("info", updatedApp.value + " " + "successfully edited!");
res.redirect("/qa-hub/applicationmanager");
}
});
});
On the front end I use EJS with a form to update the document. Example below:
<div class="row">
<div class="col-md-6">
<div class="form-group">
<input class="form-control" type="url" name="application[url]" value="<%= application.url %>" required>
</div>
</div>
</div>
<div class="row">
<div class="col-md-6">
<div class="form-group">
<select class="form-control" name="application[env]" required="true">
<option class="text-center" value='<%= application.env %>'><%= application.env %></option>
<option value='Beta'>Beta</option>
<option value='Dev'>Dev</option>
<option value='Does Not Apply'>Does Not Apply</option>
<option value='Prod'>Prod</option>
<option value='QA'>QA</option>
<option value="Test">Test</option>
</select>
</div>
</div>
</div>
<div class="row">
<div class="col-md-4">
<div class="form-group">
<a class="btn btn-outline-warning" href="/qa-hub/applicationmanager">Cancel</a>
</div>
<div class="form-group">
<button class="btn btn-outline-primary" id="btn" >Update</button>
However i'd like to add three additional fields upon submitting the form. I want to capture the currently logged in user that performed the edit and the date and time. I already have that worked out but how could I implement inserting new data to the existing document from the route.put while also keeping the logic to update the current fields if any changes are made.
So after the user makes some changes and updates the three fields the document would look something like below, except id handle the logic to get the currently logged in user at that time and the date/time and pass it in but for the example below I will hardcode it.:
{
value: 'Google',
url: 'https://google.com',
env: 'Test',
updatedBy: "Test User"
timeUpdated: "12:54",
dateUpdated: "7/25/2018"
}
So ultimately I want to keep a log of the changes and than be able to add it to the UI.
So with a little help from this post TypeError: callback.apply is not a function (Node.js & Mongodb) I was able to append new fields to the existing document using $set. However when trying to perform the req.body.application before $set it would throw an error stating that callback.apply is not a function. So I just created a callback if you will to update the document after setting the new fields. I know its messy but just wanted to get it working feel free to use and clean up the code for your self.
router.put("/:id", (req, res) => {
let value = req.body.value;
let value = req.body.value;
let date = new Date();
let hour = date.getHours();
hour = (hour < 10 ? "0" : "") + hour;
let min = date.getMinutes();
min = (min < 10 ? "0" : "") + min;
let time = hour+":"+min;
let year = date.getFullYear();
let month = date.getMonth() + 1;
month = (month < 10 ? "0" : "") + month;
let day = date.getDate();
day = (day < 10 ? "0" : "") + day;
today = month+"/"+day+"/"+year;
let updatedTo = month+"/"+day+"/"+year;
let updatedT = hour+":"+min;
let updatedBy = req.user.username;
//Find the document based on it's ID and than append these three new fields
Application.findByIdAndUpdate(req.params.id,
{ $set: {
updatedTime: `${updatedT}`,
updatedToday: `${updatedTo}`,
updatedBy: `${updatedBy}`
}}, { upsert: true },
(err,updatedApp) => {
if(err){
return handleError(err);
} else {
// Than if any changes were made from the UI we apply those updates taken
// from the form with req.body.application
Application.findByIdAndUpdate(req.params.id, req.body.application,
(err, updatedApp) => {
if(err){
return handleError(err);
} else {
console.log(updatedApp)
req.flash("info", updatedApp.value + " " + "successfully edited!");
res.redirect("/qa-hub/applicationmanager");
}
}
});
});

angular2 add dynamic fields that comes from collection value using formBuilder

I want to add dynamic input field in form using formBuilder (input comes from database ). eg. In collection i saved multiple fields and i want to create from based on that fields that i saved in collection (LIKE::-
Collections Name: Custome_fields
In above collection in save below fields
Age
Name
Height
And now i want to create input in form using above fields name:
For this i am using below that:
<div [formGroup]="studentCustomeField" *ngFor="let customField of customFields;" class="col-md-8">
<div class="form-group">
<label class="" id="Label_padding">{{customField.label | titlecase }}</label>
<input formControlName="customField.label" [(ngModel)]="student.customFields" type="text" class="form-control adstu_btn" placeholder="{{ customField.label | uppercase }}">
</div>
Below is code in my component.ts file:
this.companyService.getCustomFields()
.then(customFieldsData => {
if(customFieldsData) {
this.customFields = customFieldsData;
console.log('Testing: '+JSON.stringify(this.customFields));
this.studentCustomeField = this.formBuilder.group({
customFields.label: [''],
})
} else {
console.log('component else');
}
});
I do not know the exact structure, of your CustomFields object, but assuming it is something like:
const CustomFields = {
Age: 12,
Height: 13,
Name: "Antonio"
}
You can use the following code to generate the form group:
const formGroup = Object.keys(customFields).reduce((acc, curr) => {
acc[curr] = [customFields[curr], Validators.required];
return acc;
}, {});
And you will get the following object as formGroup
// {
// Age: [12, Validators.required],
// Height: [13, Validators.required],
// Name: ['Antonio', Validators.required],
// };
this.studentCustomeField = this.formBuilder.group(formGroup);

Dgrid + Selection Issue

Still trying to work with Dgrid (0.4) and dojo (1.10), I have now another issue with the selection.
My web page contain a Dialog opened when you click on a button.
Inside this dialog, we have the following code which display a grid with data coming from a database through a Json HTTP page. This is working fine, even sorting and query filtering.
What I want to do know is to allow the user to double click on a row, get the selected row Id contains in the first column to update the form in the main page. I use the dgrid/selection for this. However, it always return the last row of the grid instead of the one the user selected.
The selection code is based on this :
http://dgrid.io/tutorials/0.4/hello_dgrid/
Any idea?
Thanks
<script language="javascript">
require
(
[
"dojo/_base/declare",
"dojo/_base/array",
"dgrid/OnDemandList",
"dgrid/OnDemandGrid",
"dgrid/Keyboard",
"dgrid/Selection",
"dgrid/Editor",
"dgrid/extensions/ColumnHider",
"dstore/Memory",
"dstore/RequestMemory",
"dojo/_base/lang",
"dojo/dom-construct",
"dojo/dom",
"dojo/on",
"dojo/when",
"dojo/query",
"dojo/store/Observable",
"dstore/Rest",
"dojo/_base/Deferred",
"dojo/store/Cache",
"dojo/domReady!",
],
function(
declare, arrayUtil, OnDemandList, OnDemandGrid, Keyboard, Selection, Editor, ColumnHider, Memory, RequestMemory, lang, ObjectStore, dom, on, when, query, Observable, Rest, Deferred
){
var fform = dom.byId("filterForm");
var ContactColumns = [
{ label: "", field: "contact_id", hidden: true, unhidable: true},
{ label: "Company Name", field: "company_name", unhidable: true },
{ label: "Contact Name", field: "contact_name", unhidable: true },
{ label: "Email", field: "contact_email", unhidable: true }
];
var ContactGrid=declare([OnDemandGrid, Keyboard, Selection,ColumnHider]);
var contactlist = new Observable(new Rest({ target: './ajax.contactsLoader.php' }));
var selection = [];
window.contactgrid = new ContactGrid(
{
className: "dgrid-selectors",
collection: contactlist,
maxRowsPerPage:10,
selectionMode: 'single',
cellNavigation: false,
columns: ContactColumns
}, "contacttable"
);
on(fform, "submit", function (event) {
var cpy_filter = fform.elements.fcompany_name.value;
var ct_filter = fform.elements.fcontact_name.value;
var email_filter = fform.elements.fcontact_email.value;
contactgrid.set('collection',contactlist.filter({contact_name: ct_filter, company_name: cpy_filter, contact_email: email_filter }));
contactgrid.refresh();
event.preventDefault();
});
contactgrid.on('dgrid-select', function (event) {
// Report the item from the selected row to the console.
console.log('Row selected: ', event.rows[0].data);
});
contactgrid.on('dgrid-deselect', function (event) {
console.log('Row de-selected: ', event.rows[0].data);
});
contactgrid.on('.dgrid-row:click', function (event) {
var row = contactgrid.row(event);
console.log('Row clicked:', row.data);
});
}
);
</script>
<div class="dijitDialogPaneContentArea" style="width:96%;margin-left:5px">
<form id="filterForm">
<div class="dijitDialogPaneActionBar" >
<button data-dojo-type="dijit.form.Button" type="submit">Filter</button>
<button
data-dojo-type="dijit.form.Button"
data-dojo-attach-point="submitButton"
type="submit"
>
Select
</button>
<button
data-dojo-type="dijit.form.Button"
data-dojo-attach-point="cancelButton"
>
Close
</button>
</div>
<div data-dojo-attach-point="contentNode" >
<input type="text" data-dojo-type="dijit.form.TextBox" name="fcompany_name" id="fcompany_name" style="width:33%">
<input type="text" data-dojo-type="dijit.form.TextBox" name="fcontact_name" id="fcontact_name" style="width:32%">
<input type="text" data-dojo-type="dijit.form.TextBox" name="fcontact_email" id="fcontact_email" style="width:33%">
<div id="contacttable">
</div>
</div>
</form>
</div>
Just found the reason.
the columns need to have a 'id' column called ID. I just change the 'contact_id' column to 'id' and it works fine.
thanks

Resources