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 trial

JavaScript

Killeon Patterson
Killeon Patterson
18,528 Points

Outputting 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
STAFF
James Churchill
Treehouse Teacher

Killeon,

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

This 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 and name aren't properties of a product. They are properties of a user: https://github.com/Satellite9/MTMARKET/blob/master/models/user.js

Killeon Patterson
Killeon Patterson
18,528 Points

James,

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.

table.listing-table.table-dark.table-striped
  tr
    th Name
  each n in products
    tr
      td n
James Churchill
James Churchill
Treehouse Teacher

Killeon,

Can you post what your output looks like?

Thanks ~James

Killeon Patterson
Killeon Patterson
18,528 Points

Hello 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
STAFF
James Churchill
Treehouse Teacher

Killeon,

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
Killeon Patterson
18,528 Points

Hello 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
James Churchill
Treehouse Teacher

Killeon,

This bit...

each product in user.products.length

should be...

each product in user.products

Thanks ~James

James Churchill
James Churchill
Treehouse Teacher

Killeon,

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
Killeon Patterson
18,528 Points

Hello 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
STAFF
James Churchill
Treehouse Teacher

Killeon,

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
Killeon Patterson
18,528 Points

Hi 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