I am having a problem with ejs templates. I've already worked with it in another project and it worked but in this one it doesn't seem to work all it shows is a blank page and it does not render the cards with the passed data. Here is my "view products" route code and my html code for "viewproducts.ejs".
view products route:
app.get('/viewproducts', function (req, res) {
var names = [];
var prices = [];
var descriptions = [];
var product = [];
var length;
Product.find(function (err, products) {
if (err) {
console.log(err);
} else {
length = products.length;
names = products.map(function (i) {
return i.name;
});
prices = products.map(function (i) {
return i.price;
});
descriptions = products.map(function (i) {
return i.description;
});
}
for (var i = 0; i < length; i++) {
product.push({
name: names[i],
description: descriptions[i],
price: prices[i],
});
}
console.log(product);
});
res.render('viewproducts', { artisanproduct: product });
});
viewproducts.ejs
<body>
<% artisanproduct.forEach((product)=>{ %>
<div>
<div class="card-body">
<div class="row">
<div class="col-lg-3">
<img class="w-100"src="" alt="">
</div>
<div class="col-lg-9">
<h5 class="card-title"><%=product.name%></h5>
<p class="card-text"><%=product.description%></p>
</div>
</div>
<h2><%=product.price%><span>MAD</span></h2>
<button type="submit">BUY</button>
</form>
</div>
</div>
</div>
<%})%>
</body>
You are calling res.render() BEFORE you've collected any data for it. Product.find() is non-blocking and asynchronous. So, to use its result, your code has to be inside its callback or in a function you call from that callback. Change and simplify to this:
app.get('/viewproducts', function (req, res) {
Product.find(function (err, products) {
if (err) {
console.log(err);
res.sendStatus(500); // send error response
return;
}
const product = products.map(item => {
return {
name: item.name,
price: item.price,
description: item.description
};
});
res.render('viewproducts', { artisanproduct: product });
});
});
Summary of changes:
Move res.render() inside the Product.find() callback so it has access to that data.
Build product array in one .map() instead of many.
Send error response when there's an error.
Use const or let instead of var.
Remove unnecessary temporary arrays and temporary variables.
Related
So basically, I'm making a request to the newsapi, translate the response in English and then store the translated in an object (Since I only want certain data from the response).
I'm using EJS to pass the data from backend to frontend. I've been stuck on this problem for a while now and have done countless research.
For instance, I only want to access the title in the object, pass it on to the frontend via EJS and use h1 for it. Use h3 for the description and image tag for images etc.
Here's my code:
response.on("end", function () {
const newsData = JSON.parse(newsItems);
for (let i = 0; i < newsData.articles.length; i++) {
async function quickStart() {
try {
const [translation_title] = await translate.translate(newsData.articles[i].title, 'en');
const [translation_desc] = await translate.translate(newsData.articles[i].description, 'en');
const [translation_content] = await translate.translate(newsData.articles[i].content, 'en');
const readMore = newsData.articles[i].url;
const img = newsData.articles[i].urlToImage;
const publishedAt = newsData.articles[i].publishedAt;
const emptyObjArray = {
title: translation_title,
description: translation_desc,
content: translation_content,
datePublished: publishedAt,
url: readMore,
imgURL: img
};
//Testing loop
for (const values in emptyObjArray) {
console.log(emptyObjArray);
}
res.render("newsList", { newsItem: emptyObjArray });
} catch (err) {
console.error();
}
}
quickStart();
}
});
My ejs code:
<section id="headline">
<div class="row">
<div class="col-lg-6">
<h1>Before for loop</h1>
<h1>==============</h1>
<h1><%= newsItem.title %></h1>
<h4><%= newsItem.content %></h4>
<h6>Published : <%= newsItem.datePublished %></h6>
</div>
<div class="col-lg-6">
<img src="<%= newsItem.imgURL %>" alt="" />
</div>
</div>
</section>
Sorry for my English. I am trying to pre select those checkboxes whos values have been saved in the database. I did it using javascript in vuejs but those selected checkboxes values are not storing in array.
My code is like
role.component.js
getRoleRowData(data) {
this.roleaction = "edit";
this.addrolemodal = true;
console.log(data.role_id);
axios
.post(apiUrl.api_url + "getRolePermissionData", {
role_id: data.role_id
}).then(
result => {
this.permid = result.data;
var list = [];
result.data.forEach(function(value) {
list.push(value.perm_id);
});
var options = list;
for (var i = 0; i < options.length; i++) {
if (options[i]) document.querySelectorAll('input[value="' + options[i] + '"][type="checkbox"]')[0].checked = true;
}
},
error => {
console.error(error);
}
);
this.addrole = data;
},
And role.component.html
<div class="col-md-8">
<b-form-fieldset>
<div class="form" id="demo">
<h6>Permissions</h6>
<span v-for="perm_name_obj in listPermissionData">
<input type="checkbox" class="perm_id" v-bind:value="perm_name_obj.perm_id" name="perm_id" id="perm_name" v-model="checkedPerm_Id"> {{perm_name_obj.perm_name}}<br>
</span>
<span>Checked names: {{ checkedPerm_Id }}</span>
</div>
</b-form-fieldset>
</div>
And the Output
And the Ouput I got
In simple words I want to pre check checkboxes in vuejs of which values are stored in database.
See the following example, using simulation data
var app = new Vue({
el: '#app',
data () {
return {
listPermissionData: [],
checkedPerm_Id: []
}
},
created () {
setTimeout(_=>{
//Here simulates axois to request Permission data
this.listPermissionData = [
{perm_id:1,perm_name:'perm_name1'},
{perm_id:2,perm_name:'perm_name2'},
{perm_id:3,perm_name:'perm_name3'},
{perm_id:4,perm_name:'perm_name4'},
{perm_id:5,perm_name:'perm_name5'}
];
//Here simulates axois to request Selected Permissions (result.data)
var selected = [
{perm_id:2,perm_name:'perm_name2'},
{perm_id:5,perm_name:'perm_name5'}
]
this.checkedPerm_Id = selected.map(o=>o.perm_id)
},1000)
}
})
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<div id="app">
<div class="form">
<h6>Permissions</h6>
<span v-for="perm_name_obj in listPermissionData">
<input type="checkbox" class="perm_id" v-bind:value="perm_name_obj.perm_id" name="perm_id" id="perm_name" v-model="checkedPerm_Id"> {{perm_name_obj.perm_name}}<br>
</span>
<span>Checked names: {{ checkedPerm_Id }}</span>
</div>
</div>
I solved my problem, here is my code
role.component.js
getRoleRowData(data) {
this.roleaction = "edit";
this.addrolemodal = true;
console.log(data.role_id);
let tempthis = this;
axios
.post(apiUrl.api_url + "getRolePermissionData", {
role_id: data.role_id
}).then(
result => {
this.permid = result.data;
var list = [];
result.data.forEach(function(value) {
//by using tempthis variable we provided the current access to the checkedPerm_Id array which not accessible from out of the function which is getRoleRowData
tempthis.checkedPerm_Id.push(value.perm_id);
list.push(value.perm_id);
});
},
error => {
console.error(error);
}
);
this.addrole = data;
},
I'm trying to create divs using an object I pass with res.render(). However, sometimes the divs are created and sometimes they are not (if I refresh the page). I also use Bootstrap.
js/express:
router.get('/', checkSignIn, function(req, res, next) {
db = new sqlite3.Database(file);
var tasks = {};
db.serialize(function () {
var query = "SELECT tasks.id, tasks.name, tasks.status FROM tasks JOIN users ON users.privilege = tasks.privilege WHERE users.id = '" + req.session.userid + "'";
db.all(query, function (err, rows) {
for(i = 0; i < rows.length; i++) {
tasks[i] = {
name: rows[i].name,
status: rows[i].status
};
console.log(tasks[i]);
}
});
});
db.close();
res.render('index', {
title: 'Home',
css: ['style.css', 'dist/wu-icons-style.css'],
username: req.session.username,
tasks: tasks
});
});
hbs:
<div class="container">
<div class="row">
<div class="col-md-6">
<div class="panel-group">
{{#each tasks}}
<div class="panel panel-primary">
<div class="panel-heading">
<h3 class="panel-title">{{name}}</h3></div>
<div class="panel-body">{{status}}</div>
</div>
{{/each}}
</div>
</div>
</div>
</div>
The tasks object is properly populated every time, according to the console.log() that I've added. So I think the problem lies in Handlebars.
I kind of found a solution here: Handlebars not print {{this}} in each helper, but I don't use this. I tried ./name and ./status, but it didn't help. Can someone help me out here?
Your issue is async javascript, not handlebars. Your tasks object is populating, but you're rendering the html prior to that. If you console.log(tasks) right after the current position of db.close(), it will be an empty object. You need to move the render function inside the database call:
router.get('/', checkSignIn, function(req, res, next) {
db = new sqlite3.Database(file);
db.serialize(function () {
var query = "SELECT tasks.id, tasks.name, tasks.status FROM tasks JOIN users ON users.privilege = tasks.privilege WHERE users.id = '" + req.session.userid + "'";
db.all(query, function (err, rows) {
var tasks = {};
for(i = 0; i < rows.length; i++) {
tasks[i] = {
name: rows[i].name,
status: rows[i].status
};
console.log(tasks[i]);
}
res.render('index', {
title: 'Home',
css: ['style.css', 'dist/wu-icons-style.css'],
username: req.session.username,
tasks: tasks
});
});
});
db.close();
});
I'm having some trouble getting this table to load properly because the page is loading before all the information is passed to my ejs template. Pretty new to all of this and would appreciate any help!
I should note that owneditems is an array of IDs in the user schema.
routes.js:
app.get('/profile/:username', function(req, res) {
User.findOne({username: req.params.username}, function(err, user) {
var newDocs = [];
if (!user) {
req.flash('profilemessage', 'No such user exists.');
} else {
user.owneditems.map(function(i) {
Items.findById(mongoose.Types.ObjectId(i), function(err, idoc) {
newDocs.push("<tr><td>" + idoc.name + "</td><td>" + idoc.brand</td></tr>");
});
});
}
res.render('profile.ejs', {title: 'Profile', items: newDocs, message: req.flash('profilemessage')});
});
});
Profile.ejs:
<!-- content -->
<div class="wrapper row2">
<div id="container" class="clear">
<section>
<% if (message) { %>
<h4><%= message %></h4>
<% } %>
<table id="owneditems" class="sortable">
<tr><th>Name</th><th>Brand</th></tr>
<% for(var i=0; i<items.length; i++) {%>
<%- items[i] %>
<% } %>
</table>
</section>
</div>
</div>
<% include layoutBottom %>
This type of setup works for me on another page, I just can't get it working here. Thanks!
The reason why the page is rendered before information is loaded is becauseItems.findById is asynchronous. This means newDocs will not return the array of items you're expecting when it's passed to res.render.
When you want to load (arrays of) subdocuments with Mongoose, it's best to use query#populate. This method will allow you to swap out the item IDs in your user.owneditems array for the actual item document in one go.
I think this would work in your case:
app.get('/profile/:username', function(req, res) {
User.findOne({username: req.params.username})
.populate('owneditems')
.exec(function(err, user) {
var newDocs = [];
if (!user) {
req.flash('profilemessage', 'No such user exists.');
} else {
user.owneditems.forEach(function(i) {
newDocs.push("<tr><td>" + i.name + "</td><td>" + i.brand</td></tr>");
});
}
res.render('profile.ejs', {title: 'Profile', items: newDocs, message: req.flash('profilemessage')});
});
});
Also note I switched map with forEach (which is what it seems you're going for given your callback)
I'm trying to display photos from the NPM FB-Graph (https://npmjs.org/package/fbgraph) package by following this example (http://www.andrehonsberg.com/article/facebook-graph-api-meteor-js). I've managed to connect the API and render data, however I'm having trouble extracting the EJSON data into my picture template.
First off, let me show you the code I'm working with. Here is my client code:
function Facebook(accessToken) {
this.fb = Meteor.require('fbgraph');
this.accessToken = accessToken;
this.fb.setAccessToken(this.accessToken);
this.options = {
timeout: 3000,
pool: {maxSockets: Infinity},
headers: {connection: "keep-alive"}
}
this.fb.setOptions(this.options);
}
Facebook.prototype.query = function(query, method) {
var self = this;
var method = (typeof method === 'undefined') ? 'get' : method;
var data = Meteor.sync(function(done) {
self.fb[method](query, function(err, res) {
done(null, res);
});
});
return data.result;
}
Facebook.prototype.getUserData = function() {
return this.query('me');
}
Facebook.prototype.getPhotos = function() {
return this.query('/me/photos?fields=picture');
}
Meteor.methods({
getUserData: function() {
var fb = new Facebook(Meteor.user().services.facebook.accessToken);
var data = fb.getPhotos();
return data;
}
});
Meteor.methods({
getPhotos: function() {
var fb = new Facebook(Meteor.user().services.facebook.accessToken);
var photos = fb.getPhotos;
return photos;
}
});
Here is my client code:
Template.fbgraph.events({
'click #btn-user-data': function(e) {
Meteor.call('getUserData', function(err, data) {
$('#result').text(JSON.stringify(data, undefined, 4));
});
}
});
var fbPhotos = [];
Template.fbgraph.events({
fbPhotos : function(e) {
Meteor.call('getUserData', function(err, data) {
$('input[name=fbPhotos]').text(EJSON.stringify(data, undefined, 4));
});
}
});
Template.facebookphoto.helpers({
pictures: fbPhotos
});
And here are my templates:
<template name="fbgraph">
<div id="main" class="row-fluid">
{{> facebookphoto}}
</div>
<button class="btn" id="btn-user-data">Get User Data</button>
<div class="well">
<pre id="result"></pre>
</div>
</template>
<template name="facebookphoto">
<div class="photos">
{{#each pictures}}
{{> photoItem}}
{{/each}}
</div>
</template>
<template name="photoItem">
<div class="photo">
<div class="photo-content">
<img class="img-rounded" src="{{picture}}">
</div>
</div>
</template>
Right now, I'm testing the data with the id="results" tag and the Facebook API returns data as below.
{
"data": [
{
"picture": "https://photo.jpg",
"id": "1234",
"created_time": "2013-01-01T00:00:00+0000"
},
{
"picture": "https://photo.jpg",
"id": "12345",
"created_time": "2013-01-01T00:00:00+0000"
}
}
However I'm having difficulty pulling each of the pictures out of the EJSON and render them in templates. What I'd like to do is to display each picture in the {{picture}} template. I believe the problem with the code is somewhere in the client, but I'm not sure how to fix it.
Thanks in advance!
It looks like in your client code you have
Template.fbgraph.events({ ... })
defined twice. Did you mean to write:
Template.fbgraph.helpers({
fbPhotos : function(e) {
Meteor.call('getUserData', function(err, data) {
$('input[name=fbPhotos]').text(EJSON.stringify(data, undefined, 4));
});
}
});
A simpler way to do it might just be to call the getUserData method in your facebookphoto template itself, thus:
Template.facebookphoto.helpers({
pictures : function(e) {
Meteor.call('getUserData', function(err, data) {
$('input[name=fbPhotos]').text(EJSON.stringify(data, undefined, 4));
});
}
});