Welcome to the Treehouse Community
Want to collaborate on code errors? Have bugs you need feedback on? Looking for an extra set of eyes on your latest project? Get support with fellow developers, designers, and programmers of all backgrounds and skill levels here with the Treehouse Community! While you're at it, check out some resources Treehouse students have shared here.
Looking to learn something new?
Treehouse offers a seven day free trial for new students. Get access to thousands of hours of content and join thousands of Treehouse students and alumni in the community today.
Start your free trialKilleon Patterson
18,528 PointsOutputting MongoDb array/object literal using PUG
Hello,
I'm attempting to output an array's "name" and "email" object literals. Using PUG, I've been successful in displaying the Mongo document's whole record by looping through the array with the "each" method. I have been unsuccessful in displaying multiple, selected object literals.
if products.length
table.listing-table.table-dark.table-striped
tr
th Name
each n in products
tr
td= #{n.name}
// alt: td= n.name
else
p NOPE
With interpolation, I receive an unexpected "#" character error. When I use dot notation the page view displays nothing under the " name" tag. The record is present. I just can't display it. My github repository link is https://github.com/Satellite9/MTMARKET. Thank you.
Killeon
6 Answers
James Churchill
Treehouse TeacherKilleon,
Have you tried removing the equals sign (=
) after the td
tag?
table.listing-table.table-dark.table-striped
tr
th Name
each n in products
tr
td #{n.name}
If that doesn't resolve the issue, I'd next look at the format of the products
parameter. Within your Express route handler, add a console.log()
call and pass in the variable that holds the data for the Pug products
parameter. Then take a look in the terminal to see what the format of the data looks like (you could also do this by debugging your app using Chrome DevTools or Visual Studio Code).
Thanks ~James
Killeon Patterson
18,528 PointsHello James,
The first link is the result is when I attempt to show partials of the record. The second link is the result of me showing the full record. The third link is to my GitHub repo. Thank you for your time.
https://drive.google.com/open?id=1hXwK4SQ3PjQoKb74sSEewLOKDYbiAVlk https://drive.google.com/open?id=1Ht7v3EHldm-XtWUjvYItr1nf7M_IfUpE https://github.com/Satellite9/MTMARKET
Killeon
James Churchill
Treehouse TeacherKilleon,
In order to fully understand the data binding issue in your PUG template, let's start with taking a closer look at the data that you're passing to the template (something that I suggested to try in my earlier response).
Within your Express request handler, add a console.log()
method call and pass in the variable that holds the data for the Pug products
parameter. Then take a look in the terminal to see what the format of the data looks like (you could also do this by debugging your app using Chrome DevTools or Visual Studio Code).
Here's what that code looks like:
router.get('/profile/jelly', function(req, res, next) {
User.find(function(err, docs) {
var productChunks = [];
var chunkSize = 2;
for (var i = 0; i < docs.length; i += chunkSize) {
productChunks.push(docs.slice(i, i + chunkSize));
}
// log the `productChunks` variable to the console in order to verify the format of the data
console.log(productChunks);
return res.render('product', { title: 'Product Page', products: productChunks });
});
});
Looking in the console, you'll see that each item in the productChunks
array is an array itself (which I'd assume is what your code was originally intended to accomplish).
Here's what the first item in the array looks like:
[
{
products: [],
_id: 5b39662aab29f26a1440a3d0,
email: 'dave@whoever.com',
name: 'Dave Mays',
organization: 'MIT',
password: '$2b$10$QjyUsmvFs0z38/Xv0EvCZ.m/V3mvpT7PYr6E5bujwa4xVOoVt8rDK',
product: [],
__v: 0
},
{
_id: 5b79fb06ffdb6638b0531e13,
email: 'top@secret.com',
name: 'Top Secrets',
organization: 'MIT',
password: '$2b$10$BBctP6BZ9YQTQmvWfNES.OQQiOT3trXckTnNQ.Ejwa9U6qQff9AkG',
products:
[
{
createdAt: 2018-08-19T23:19:43.967Z,
updatedAt: 2018-08-19T23:19:43.967Z,
_id: 5b79fb0fffdb6638b0531e14,
title: 'Philosophy of Fly',
cost: 15,
location: 'beaverton',
description: 'lol'
},
{
createdAt: 2018-08-19T23:30:12.078Z,
updatedAt: 2018-08-19T23:30:12.078Z,
_id: 5b79fd84230f683a909da3d0,
title: 'ad',
cost: 13,
location: 'portland',
description: 'jake'
}
],
__v: 2
}
]
Keeping that item data structure in mind, let's take another look at your PUG template.
extends layout
block content
.main.container.clearfix
.row
.col-md-8.col-md-offset-2
h1.title Title
| #{title}
h2.cost Cost
| #{cost}
h2.location Location
| #{location}
h2.description Description
| #{description}
.col-md-8.col-md-offset-2
h1.title Title
| #{title}
h2.lulu lulu
if products.length
table.listing-table.table-dark.table-striped
tr
th Name
each n in products
tr
td=n
else
p NOPE
Let's take a closer look at the first part of the template:
.main.container.clearfix
.row
.col-md-8.col-md-offset-2
h1.title Title
| #{title}
h2.cost Cost
| #{cost}
h2.location Location
| #{location}
h2.description Description
| #{description}
.col-md-8.col-md-offset-2
h1.title Title
| #{title}
In this section of the template, you're attempting to bind to title
, cost
, location
, and description
properties. Only the title
property is defined on your template's data object, but that title
property is the title for the page, not the title
for a product. This section of the template will need to be fixed, but for now, we can safely ignore it (it's not what's causing the problem with your products list).
Now let's take a closer look at the products list:
if products.length
table.listing-table.table-dark.table-striped
tr
th Name
each n in products
tr
td=n
else
p NOPE
The initial if
statement, if products.length
, is fine, as the products
property is in fact an array. But the loop itself is an issue:
each n in products
tr
td=n
Remember, the products
property is an array of arrays. Additionally, the inner array is an array of users, not products. Given that, I'd try something like this:
each n in products
tr
td
h3= n.name
ul
li= n.email
li= n.organization
if n.products.length
each p in n.products
h4= p.title
ul
li= p.cost
li= p.location
li= p.description
Let's break this down a bit.
First, we loop through the products
array, which is really an array of user profiles.
each n in products
tr
td
h3= n.name
ul
li= n.email
li= n.organization
Then, after rendering the user's name
, email
, and organization
property values, we check if the user's products
property, which is an array, has any items (in your test data, not every user has products).
if n.products.length
And if the user products
array has items, then we loop over that array, rendering a list of the user's products.
each p in n.products
h4= p.title
ul
li= p.cost
li= p.location
li= p.description
The template products
variable should probably be renamed to users
, since it's actually a list of users, not products. Making that change (along with some renaming of template variables to make things clearer), here's what the completed request handler and template would look like:
router.get('/profile/jelly', function(req, res, next) {
User.find(function(err, docs) {
var userChunks = [];
var chunkSize = 2;
for (var i = 0; i < docs.length; i += chunkSize) {
userChunks.push(docs.slice(i, i + chunkSize));
}
// log the `userChunks` variable to the console in order to verify the format of the data
console.log(userChunks);
return res.render('product', { title: 'Product Page', users: userChunks });
});
});
extends layout
block content
.main.container.clearfix
.row
.col-md-8.col-md-offset-2
each user in users
tr
td
h3= user.name
ul
li= user.email
li= user.organization
if user.products.length
each product in user.products
h4= product.title
ul
li= product.cost
li= product.location
li= product.description
Sorry for such a long post. I hope this helps :)
Thanks ~James
Killeon Patterson
18,528 PointsHello James-
No, I appreciate the length of your explanation. It makes sense that I wasn't reaching the array of the array properly. Originally when you asked me to console.log the variable I misunderstood and thought you meant the product rendering. I made the appropriate changes to the code. The error is no longer showing but the data isn't either. I've attached a copy of what I'm seeing, along with my update github. Did I miss a tab? Thank you.
https://drive.google.com/file/d/1u07LhN2VoK-tTn7lCxHRqCXPWRbK9r8o/view?usp=sharing - product page https://github.com/Satellite9/MTMARKET/commits?author=Satellite9 - GitHub
Hello James,
My mistake, I forgot to include in the last post that I tried both ways and the page rendered the same. He is a link to the alternate code in my github.
https://github.com/Satellite9/MTMARKET/commit/427b080c4b606630fb9c7f6ac5e5e9022d6e8340 - GitHub
Thank you-Killeon
James Churchill
Treehouse TeacherKilleon,
This bit...
each product in user.products.length
should be...
each product in user.products
Thanks ~James
James Churchill
Treehouse TeacherKilleon,
Sorry to hear that your solution still isn't working as you expect. Can you please describe for me what you've done to debug the problem? Please provide as much detail as possible (i.e. the step by step instructions of your debugging process).
I realize that this probably isn't the response you're looking for, but at this point, I need to understand more about your development and debugging processes, so that I can offer additional suggestions and/or insights.
Hang in there! I'm sure that you're getting close to solving the problem.
Thanks ~James
Killeon Patterson
18,528 PointsHello James,
Thank you for your time and patience in assisting me with this matter.
Before you asked that question, I had only done blind trouble shooting for the application in between the routes index.js and the product view. Afterwards I re-installed the debug module https://www.npmjs.com/package/debug. I ran a test on from the url "localhost:3000?test=4". The command line returned a 200 code for that url. I required the module in the routes/index.js file and along with the ('http') option. I added -
debugWarn = debug('warn')
debugError = debug('error')
My plan was to add the break point in the '/profile/jelly' route handler just above the 'return res.render portion of the call.' My logic is because I was able to display the entire doc on one version of the test, that the array of the array is at least making it that far in the application. This is my first time using the debugger to find a bug that wasn't an experiment //per the debugging node.js workshop.
Before I placed the break point above the render call, I did another test in the '/profile''debugWarn' and the debugError.
router.get('/profile', mid.requiresLogin, function(req, res, next) {
debugError('error happened');
debugWarn('warn happened');
User.findById(req.session.userId)
.exec(function (error, user) {
if (error) {
return next(error);
} else {
return re.render('profile', { title: 'Profile', name: user.name, organization:
user.organization });
}
});
});
This is where I slowed down in the debugging effort. I didn't see anything in the stack trace the echoed the
debugError('error happened');
debugWarn('warn happened');
It did appear to communicate that the http warn +200ms http error +1ms was working?
Thank you, again.
Killeon
James Churchill
Treehouse TeacherKilleon,
The debug
package is a great debugging tool, but you can also just use console.log()
method calls. Doing that isn't as flexible as using debug
, but it's a quick and dirty way to temporarily verify the behavior of your code.
For example, in your route handler, you could add the following console.log()
method calls to verify the structure of your data:
router.get('/profile/jelly', function(req, res, next) {
User.find(function(err, docs) {
console.log('Docs: ' + JSON.stringify(docs));
var productChunks = [];
var chunkSize = 2;
for (var i = 0; i < docs.length; i += chunkSize) {
productChunks.push(docs.slice(i, i + chunkSize));
}
console.log('Product Chunks: ' + JSON.stringify(productChunks));
return res.render('product', { title: 'Product Page', users: productChunks });
});
Notice how I'm using the JSON.stringify()
method to get a string representation of your data objects.
We can use a similar trick in your PUG template.
block content
p= 'Users: ' + JSON.stringify(users)
.main.container.clearfix
.row
.col-md-8.col-md-offset-2
each user in users
tr
td
h3= user.name
ul
li= user.email
li= user.organization
if user.products
each product in user.products
h4= product.title
ul
li= product.cost
li= product.location
li= product.description
Notice the new line of code that I added just below block content
line:
p= 'Users: ' + JSON.stringify(users)
This line of code will render the users
data property to the page so that we can verify it's structure. Here's what my users
data looks like:
[
[
{
"_id":"5b735de2f5b93521c674e962",
"email":"james@smashdev.com",
"name":"James Churchill",
"organization":"SmashDev",
"password":"$2b$10$VbhL6vSIAazYcbipUNfYFO2SgWgoazB.hYg12uAcyD7xrVr5gNrTa",
"products":[
{
"createdAt":"2018-08-14T23:36:25.797Z",
"updatedAt":"2018-08-14T23:36:25.797Z",
"_id":"5b7367791bf3dd25de04b48d",
"title":"LOTR",
"cost":25,
"location":"Portland",
"description":"Some desc"
},
{
"createdAt":"2018-08-14T23:39:20.697Z",
"updatedAt":"2018-08-14T23:39:20.697Z",
"_id":"5b73682826d48c26245ebacd",
"title":"Matrix",
"cost":50,
"location":"Portland",
"description":"Some desc"
},
{
"createdAt":"2018-08-14T23:41:39.421Z",
"updatedAt":"2018-08-14T23:41:39.421Z",
"_id":"5b7368b3e297702663818c8c",
"title":"Star Wars",
"cost":44,
"location":"Portland",
"description":"Some desc"
}
],
"__v":3
},
{
"_id":"5bc51e664955048fa8ceffc1",
"email":"jenny@smashdev.com",
"name":"Jenny Churchill",
"organization":"Something",
"password":"$2b$10$q3mn64Td.lRiiLcohcd6UuM8z7nksTnD9/x50HwsO.XRWfIQvmgG.",
"products":[
{
"createdAt":"2018-10-15T23:10:42.881Z",
"updatedAt":"2018-10-15T23:10:42.881Z",
"_id":"5bc51e724955048fa8ceffc2",
"title":"LOTR",
"cost":12,
"location":"Portland",
"description":"Blah"
}
],
"__v":1
}
]
]
Is this the data structure that you're expecting (hint: there's something definitely unexpected about the overall structure of the data)?
Thanks ~James
Killeon Patterson
18,528 PointsHi James,
I did not necessarily expect the data to render the product count. Although that should not be an issue.I was expecting the User's data to be an array objects, where one of the properties on an object holds another array ie. products: []. Is the data being enclosed in (1) additional array? Thank you.
Killeon
Iain Simmons
Treehouse Moderator 32,305 PointsIain Simmons
Treehouse Moderator 32,305 PointsThis should do it, as the equals sign tells it that the part following is an expression, not text with interpolation, but
#{n.name}
isn't valid JS.Also, looking at https://github.com/Satellite9/MTMARKET/blob/master/models/product.js,
email
andname
aren't properties of a product. They are properties of a user: https://github.com/Satellite9/MTMARKET/blob/master/models/user.jsKilleon Patterson
18,528 PointsKilleon Patterson
18,528 PointsJames,
Thank you for your reply. I attempted removing the equals sign from the line. The page renders all of the page except what's under the "name" tag. The console shows no errors. When I run the below, I'm able to display all of the records.
James Churchill
Treehouse TeacherJames Churchill
Treehouse TeacherKilleon,
Can you post what your output looks like?
Thanks ~James