Been trying to solve this one for quite a while now.
I am saving an array which contains objects to my data base,
When I try to map() through it to retrieve the object's properties its just rendering nothing.
This is the app.js code :
app.get("/:plasticcategory/product/:plasticproduct", (req, res) => {
const plasticCategory = _.startCase(_.toLower(req.params.plasticcategory));
const plasticProduct = req.params.plasticproduct;
Product.find({category: plasticCategory, title: plasticProduct},'alt', (err, foundItem) => {
if(err){
console.log(err)
}else {
console.log(foundItem);
res.render('alternatives', {altProduct: foundItem});
}
});
});
When I console.log(foundItem) the result is [ { _id: 5f5f9b2a9f999b1e9009072b, alt: [ [Object] ] } ]
This is my ejs code(trying to render the alt's array objects properties:
<% altProduct.map(alt => {%>
<div class="col-lg-3">
<h1><%=alt.altTitle %></h1>
<img src="<%=alt.altImage%>" alt="alt-image">
Get it now!
</div>
<% }) %>
I have added images to make it more clear, Thank You <3
enter image description here
When you render the template, I see you call it like this:
res.render('alternatives', {altProduct: foundItem});
Where foundItem is the array [{ id: 'something', alt: [{ someObject }] }].
This is an array of results. Each of those results has a key called 'alt' with items in it. If you want to render all of those items together, you will need to compile them all into a single array. (This is called 'flat-mapping'.)
Starting inside the else block of the callback of Product.find:
const itemArrays = foundItem.map(item => item.alt); // Get the inner array from each result
const allAlternativeProducts = [].concat(...itemArrays); // Collect all these products into a single array
res.render('alternatives', {altProduct: allAlternativeProducts});
Related
I want to access the data from my local mongoDB and I want to search for all keys without making a state{} wherein I will have to pre-define what kind of keys are present in the database.
Now although this problem has been answered numerous times by different people , most of them have the data in the following format ( it is a random example):
{
"object": {
"name": "Pluralsight",
"number": 1,
"address": "India",
"website": "https://www.pluralsight.com/"
}
}
or like this:
{
"String":"EternalSunsetOfCloudedMind",
"Rating": 2
}
In the above two examples we can easily use the function : <p>Name: {sampleJSON.object.name}</p> or <p>String : {sampleJSON.string}</p>
But what if the database looked like this:
[{"_id":"60d1b5493b470b3884325872","Title":"CatManBegins" ,"Image":"https://m.media-amazon.com/images/catmanbegins.jpg","Rating":9},
{"_id":"60d1b5d0b25e04287318a072", "Title":"Cabdriver","Image":"https://m.media-amazon.com/images/cabdriver.jpg"},
{"_id":"60d314a981ecb624dc6966a2","Title":"Pulpnonfiction" ,"Image":"https://m.media-amazon.com/images/pulpnonfiction.jpg","Review":{"AlphaReview":"WOW!","BetaReview":"Okay nice","GamaReview":"Hmm"}},
{"_id":"60d32406affa146b4b1428a2", "Title":"DoctorNormal","Category":"Marvellous Movies"},
{"_id":"60d5cfc6326f1c336d3478e2", "Title":"GameOfKingdoms","BudgetInDollars":100000}]
How can I do the same in this kind of database?
My current knowledge and progress:
Till now I have been able to fetch the data from MongoDB to the server-side, from which I have been able to send the JSON to the client-side after parsing it using the following code:
Server.js
app.get('/run-query',async(req,res)=>{
MongoClient.connect('mongodb://localhost:27017',{useNewUrlParser: true, useUnifiedTopology: true}, function(err,db){
if(err) throw err;
var dbo = db.db("MyDatabaseName");
dbo.collection("MyCollectionName").find({}).toArray(function(err,result){
if(err) throw err;
res.json(result);
db.close();
});
});
})
Client/src/App.js
function App() {
const [data, setData]= useState(null);
useEffect(()=>{
fetch('/run-query')
.then ((res)=>res.json())
.then (setData)
.catch(console.error)
}, []);
return (
<div>
<h1>All results </h1>
<div class="box">
<p>{!data ? "loading..." : JSON.stringify(data)}</p>
</div>
</div>
);
}
When you want to display an array, usually you’ll want to map each item in the array to a React element.
To get all property names and values of a JavaScript object, you can use the Object.entries() function.
Putting these two together:
// maps each object in the array to an unordered list of key/value pairs
const dataItemToKeyValues = (item) => {
const entries = Object.entries(item);
const listItems = entries.map(([key, value]) => (
<li>
{key}: {value}
</li>
));
return <ul>{listItems}</ul>;
};
return (
<div>
{!data ? (
"Loading"
) : (
<ul>
{data.map((item) => (
<li>{dataItemToKeyValues(item)}</li>
))}
</ul>
)}
</div>
);
The React documentation has a section on lists that has more information.
UPDATE : Solution is at bottom of question
I have an express site using mongoose.
I'll greatly simplify to say that I have adults, kids, and house models. When I create methods on kids, I can call them from within methods on adults and get a result. I can also call them from my .ejs views. However, when I create methods on house, I can only get a result from my .ejs views and get undefined when called from within methods on adults. Example code follows.
adult.js
const mongoose = require('mongoose');
const adultSchema = mongoose.Schema({
name: { type: String },
size: {type: String},
kids: [{type: mongoose.Schema.Types.ObjectId, ref: 'Kid', required: true}]
house:{type: mongoose.Schema.Types.ObjectId, ref: 'House', required: true}
});
adultSchema.method({
getKidsDescription: function() {
if (this.kids.length < 1) {
return 'No kids yet';
} else {
let ev = 'Kids, aged: ';
let kds = this.kids;
kds.forEach(function(k){
ev = ev + 'k.getAge()' // works
})
return ev;
}
},
getHouseDescription: function(){
return 'A fabulous house on '+this.house.getFullStreet(); // does not work
}
})
module.exports = mongoose.model('Adult', adultSchema);
kid.js
const mongoose = require('mongoose');
const kidSchema = mongoose.Schema({
name: { type: String },
size: {type: String},
birthdate: {type:Date}
});
kidSchema.method({
getAge: function() {
return (Math.floor(new Date() - this.birthdate)/(1000*60*60*24*365))
},
})
module.exports = mongoose.model('Kid', kidSchema);
house.js
const mongoose = require('mongoose');
const houseSchema = mongoose.Schema({
name: { type: String },
city: {type: String},
street: {type:String}
});
houseSchema.method({
getFullStreet: function() {
return this.street + ' Road';
},
})
module.exports = mongoose.model('House', houseSchema);
When I make a query for theAdult, it looks like this:
controller.js
exports.main = async (req, res, next) => {
if (req.theAdult) {
try {
const found = await db.fetchAdult(req.theAdult._id)
res.render('/main', {
//theHouse: found.house //below I show this working
});
} catch(e) {
throw new Error(e.message)
}
} else {
res.redirect('/');
}
}
db.js
exports.fetchAdult = (id) => {
return Adult.findById(id)
.populate({ path: 'kids'})
.populate({ path: 'house'})
.exec()
.then(doc => {
return doc;
});
}
Assuming house is passed to view as an object when rendered (commented out above), this works
view.ejs
<p> <%= theHouse.getFullStreet() %></p>
Assuming house populated on the call to load the Adult, this returns undefined.
view.ejs
<p> <%= theAdult.house.getFullStreet() %></p>
At the same time, both of these work
view.ejs
<ul> <% theAdult.kids.forEach(function(k) { %>
<li><%= k.getAge() %> </li>
<% }); %>
</ul>
<p> <% theAdult.getKidsDescription() %> </p>
What I am not understanding is how the method calls work for objects in array and work in the view but do not work for objects on in an array. This is a single child error (for me). If it did not work in the view, I would assume that the method getFullStreet() was the problem, but it works in the view. If the array methods could not be called within the parent, I would assume the issue was with trying to access getFullStreet() in the parent.
What am I missing?
SOLUTION
I was fetching theAdult in my call to show view.ejs, but I was then actually relying on currentAdult which referred to req.adult and did not have the fields populated. My solution was to add a pre hook to the adult schema that always populates house on find.
in adult.js
adultSchema.pre('find', function() {
this.populate('house')
})
Have you tried passing a hydrated theAdult? It might only see the ObjectID, without any other data or methods.
I am trying to avoid creating db queries everytime I use
router.get("/whatever",function()...)
so I created a dbController which returns an array of documents by means of
db.collection.find().toArray()
which is working fine .
I choked here though
router.get( "/test" , function ( req , res , err ) {
if (err) console.dir( err );
var categoriesArray = require ( "./Controllers/dbController" ).getCategsArray();
console.log( "!!!!!!!!!!!!!!!" + categoriesArray );
res.render( "hello" , {
_: _ ,
title: "la naiba" ,
items: categoriesArray
});
});
because although the console.log shows the documents array
[Function: next]
!!!!!!!!!!!!!!!
[ { _id: ObjectID { _bsontype: 'ObjectID', id: 'QrÑUÿY?ó#M_?' },
categories: [ [Object], [Object] ],
id: 'mens',
name: 'Mens',
page_description:
................................
I am not geting it in the EJS template
..............................................
<% _.each(items, function(topC) { %>
<li>
<h1><%= topC.name %></h1>
..........................
title property though gets rendered. Any help is greatly appreciated since I'm a complete n00b and I've lost all night trying to make progress
The name and list to interate is inside categoriesArray, then you need to interate categoriesArray. Somelike this:
<% items.forEach(function (topC) { %>
<li>
<h1><%= topC.name %></h1>
</li>
<% }) %>
a user has fields in mongoose which will get updated if the user decided to update.
Here's the user schema
var User = Schema({
education: [{ type: String}],
});
So basically a user has fields that they could update or add, for example a user can add additional education and skills information using a form.
How do I properly do it in ejs and route?
my attempt in the route.js
router.post('/update-resume', function(req, res) {
User.findById(req.user._id, function(err, foundUser) {
// This part how do I update ?
if (req.body.education) foundUser.resume.education.push(req.body.education);
foundUser.save();
});
});
The value keeps pushing, i want to , I know that it is obvious that I'm pushing the data to the field, but how do I update it properly?
Form.ejs
<div class="form-group">
<label for="education">Education:</label>
<% for(var i = 0; i < user.resume.education.length; i++) { %>
<input type="text" class="form-control" name="education" id="education" value="<%= user.resume.education[i] %>">
<% } %>
</div>
Is it true that I need to for loop each field? if I want to update the specific data?
1st option:
Depending on how you use the application, you might not even care about updating - you could just delete the previous educations and save new ones instead.
2nd option:
To properly update you really need some kind of an ID that you can refer to when updating, right?
You'll still need to use your for loop, you just need to insert the id hidden field.
To do that you will need to pass an object and not an only-string value. I have made my object array look like this:
var education = [
{content:'Education 1',id:1},
{content:'Education 2',id:3},
{content:'Education 3',id:5},
{content:'Education 4',id:2},
];
Then you can do something like that:
<% for(var i = 0; i < education.length; i++) { %>
<input type="hidden" type="hidden" name="education_id" value="<%= education[i].id %>"/>
<input type="text" class="form-control" name="education" id="education" value="<%= education[i].content %>">
<% } %>
Array will always get passed the way you have sent it to the server. In my case, I'll get this (you can see that everything's in the order that it should be):
{education_id: [ '1', '3', '5', '2' ],
education: [ 'Education 1', 'Education 2', 'Education 3', 'Education 4' ] }
Now, let's look at the POST backend, you will need to tie everything back to an object (you don't really need to, but you can and probably should for the sake of sanity):
var education_i;
var education_req = [];
for(education_i=0;education_i<req.body.education.length;education_i++) {
console.log(req.body.education[education_i]);
education_req.push({
content:req.body.education[education_i],
id:req.body.education_id[education_i]
});
}
A few more notes:
You HAVE TO check for the user of every education record. You don't want to let anyone mess with the IDs and ruin someone else's profile.
You should probably save the array length variable separately, outside of the loops as it might cause performance issues on very large arrays because the length is parsed on every loop iteration.
This is the way I would do it:
The model:
var User = new Schema({
education: [{ content: String }]
});
The EJS/HTML:
<form id="education">
<% user.education.forEach(function(item) { %>
<input type="text" data-id="<%= item.id %>" value="<%= item.content %>" />
<% }); %>
<button type="submit">Save</button>
</form>
The client side javascript (JQuery):
$('#education').on('submit', function(e) {
e.preventDefault();
var data = [];
$(this)
.find('input')
.each(function() {
var $this = $(this);
// Collect the data with the id and value
data.push({
id: $this.data('id'),
value: $this.val()
});
});
$.ajax({
url: '/update-resume',
type: 'post',
data: { data: JSON.stringify(data) }
})
.done(function(data) {
if (data.success) {
// Lazy: refresh window
window.location.reload();
}
})
.fail(function() {
// Show an error or something fancy
});
});
The above javascript will read the data-ids from the input and the values and put them into an array of education objects. It will then stringify the object add and the string to the key 'data'. This means that you can pull the string in the route from req.body.data and parse it.
The server side javascript/in the route:
router.post('/update-resume', function(req, res, next) {
User.findById(req.user._id, function(err, user) {
var parsed = JSON.parse(req.body.data);
// update and remove
var results = user
.education
.filter(function(item) {
return parsed.some(function(input) {
return input.id === item.id;
});
})
.map(function(item) {
var related = getRelated(item.id, parsed);
return { content: related.value };
});
// Add new items
user.education = results
.concat(parsed.reduce(function(prev, curr) {
if (!getRelated(curr.id, results)) {
prev.push({ content: curr.value });
}
return prev;
}, []));
user.save(function(err) {
if (err) return next(err);
res.json({ success: true });
});
});
});
Get related helper:
var getRelated = function(id, arr) {
for (var i = 0; i < arr.length; i++) {
if (String(arr[i].id) === String(id)) return arr[i];
}
};
Mongoose will automatically give your education array items an id. The above will allow you to be able to add, remove and update existing education items on the page.
First of all, let me tell you that I'm a novice in the world of javascript and node.js. I have been searching for help in trying to do what i want but haven't found yet.
I am using the MEAN stack(http://mean.io/) and I am trying to implement a search feature in the included articles model. The search would look for articles with a specific tag and would be implemented in the index page. Follow me and see if you can find what I am missing please.
In the backend:
app/models/
/**
* Article Schema
*/
var ArticleSchema = new Schema({
created: {
type: Date,
default: Date.now
},
title: {
type: String,
default: '',
trim: true
},
content: {
type: String,
default: '',
trim: true
},
tag: {
type: String,
default: '',
trim: true
},
user: {
type: Schema.ObjectId,
ref: 'User'
}
});
app/controllers/
exports.searcharticle = function(req, res) {
Article.find({'tag': req.params.tag}).sort('-created').populate('user', 'name username').exec(function(err, articles) {
if (err) {
res.render('error', {
status: 500
});
} else {
res.jsonp(articles);
}
});
};
Added the route for the search in app/routes/articles.js
app.get('/articles/search/:tag', articles.searcharticle);
In the frontend:
Created the view for the search wich will display the search results - public/views/articles/search.html
<section data-ng-controller="ArticlesController" data-ng-init="searchart()">
<ul class="articles unstyled">
<li data-ng-repeat="article in articles">
<span>{{article.created | date:'medium'}}</span> /
<span>{{article.user.name}}</span>
<h2><a data-ng-href="#!/articles/{{article._id}}">{{article.name}}</a></h2>
<div>{{article.tag}}</div>
</li>
</ul>
<h1 data-ng-hide="!articles || articles.length">Your search hasn't returned any results. <br> Why don't you Create One?</h1>
</section>
The view for the index.html, where the searchbox will be implemented
<section data-ng-controller="ArticlesController">
<form role="form" data-ng-submit="searchart()">
<div>
<div>
<input type="text" id="tag" ng-model="selected" class="form-control" placeholder="Tag">
</div>
<div>
<button type="submit" class="btn btn-default">Submit</button>
</div>
</div>
</form>
Added the route to the config.js
when('/articles/search/:tag', {
templateUrl: 'views/articles/search.html'
}).
And added the search function to the articles controller
$scope.searchart = function() {
Articles.query(function(articles) {
$scope.articles = articles;
});
};
Right now, with this code implemented, when I click in the submit button in the index page, nothing happens.
Can you find what am I missing?
Thanks in advance!
In order to use a URL in your client Article Service, you should define the URL Parameter in the articles service at: packages/articles/public/services/article.js, like the articleId parameter already defined in there like this:
angular.module('mean.articles').factory('Articles', ['$resource',
function($resource) {
return $resource('articles/:articleId', {
articleId: '#_id'
}, {
update: {
method: 'PUT'
}
});
}
]);
Then you need to pass it in your angular controller search method, like the function that gets one by id, like this:
$scope.findOne = function() {
Articles.get({
articleId: $stateParams.articleId
}, function(article) {
$scope.article = article;
});
};
Personally I don't know how to add another parameter to the $resource object in addition to the existing one (articleId), you may have to create another $resource service with the new parameter (:tag) and use it in your search method in your angular controller.
Another way that sounds more simple and flexible to me is to just pass the search parameters in the query method, like this:
$scope.searchart = function() {
Articles.query({tag:$scope.selectedTag}, function(articles) {
$scope.articles = articles;
});
};
and then at the server side controller, read your query parameters like this:
exports.searcharticle = function(req, res) {
Article.find(req.query).sort('-created').populate('user', 'name username').exec(function(err, articles) {
if (err) {
res.render('error', {
status: 500
});
} else {
res.jsonp(articles);
}
});
};