3

I am working on a blogging application (click the link to see the GitHub repo) with Express, EJS and MongoDB.

I have made a simple pager for the posts.

In the posts controller I have:

exports.getPosts = async (req, res, next) => {

  const posts = await Post.find({}, (err, posts) => {

      const perPage = 10;

      const currPage = req.query.page ? parseInt(req.query.page) : 1;

      const postsCount = posts.length;

      const pageCount = Math.ceil(postsCount / perPage);

      const pageDecrement = currPage > 1 ? 1 : 0;

      const pageIncrement = currPage < pageCount ? 1 : 0;

      if (err) {
        console.log("Error: ", err);
      } else {
        res.render("default/index", {
          moment: moment,
          layout: "default/layout",
          website_name: "MEAN Blog",
          page_heading: "XPress News",
          page_subheading: "A MEAN Stack Blogging Application",
          currPage: currPage,
          pageDecrement: pageDecrement,
          pageIncrement: pageIncrement,
          posts: posts,
        });
      }
    })
      .sort({ created_at: -1 })
      .populate("category")
      .limit(perPage)
      .skip((currPage - 1) * perPage);
};

The pager in the view:

<% if (posts) {%>
  <div class="clearfix d-flex justify-content-center">
    <div class="px-1">
        <a class="btn btn-primary <%= pageDecrement == 0 ? 'disabled' : '' %>" href="/?page=<%= currPage - pageDecrement %>">&larr; Newer Posts</a>
    </div>

    <div class="px-1">
        <a class="btn btn-primary <%= pageIncrement == 0 ? 'disabled' : '' %>" href="/?page=<%= currPage + pageIncrement %>">Older Posts &rarr;</a>
    </div>
  </div>
<% } %>

The problem

The line .limit(perPage) from the controller gives the error perPage is not defined in the console (Git bash).

The solution that does not work

Clearly, I can move these 2 lines above const posts

const perPage = 5;
const currPage = req.query.page ? parseInt(req.query.page) : 1;

but I can not do the same with const postsCount = posts.length; (which I also need in the view).

The objective

I am trying to make the snippet of code regarding the pagination reusable (like a plugin, if possible), since I need to paginate for the posts filtered by category, and also the list of post in the admin section o the application.

What am I doing wrong?

Razvan Zamfir
  • 4,209
  • 6
  • 38
  • 252
  • I didn't run the code, but I think the `perPage` variable is the correct scope. Try to define it before `const posts`. – Denis Lapadatovic May 10 '20 at 21:01
  • Check these answers, they can help you [Pagination Mongoose](https://stackoverflow.com/questions/5539955/how-to-paginate-with-mongoose-in-node-js) – Daniel Cabrera May 13 '20 at 00:44

5 Answers5

5

why are you using callback and await together.seems like you need to look in to async / await and promises.What you can do is as below:

exports.getPosts = async (req, res, next) => {
  const currPage = req.query.page ? parseInt(req.query.page) : 1;
  const perPage = 10;
  try {
    const posts = await Post.find({})
      .sort({ created_at: -1 })
      .populate("category")
      .limit(perPage)
      .skip((currPage - 1) * perPage).exec();

    const postsCount = posts.length;

    const pageCount = Math.ceil(postsCount / perPage);

    const pageDecrement = currPage > 1 ? 1 : 0;

    const pageIncrement = currPage < pageCount ? 1 : 0;
    res.render("default/index", {
      moment: moment,
      layout: "default/layout",
      website_name: "MEAN Blog",
      page_heading: "XPress News",
      page_subheading: "A MEAN Stack Blogging Application",
      currPage: currPage,
      pageDecrement: pageDecrement,
      pageIncrement: pageIncrement,
      posts: posts,
    });
  } catch (err) {
    console.log("Error: ", err);
    // add proper error handling here 
    res.render('default/error', {
      err
    });
  }
};
Razvan Zamfir
  • 4,209
  • 6
  • 38
  • 252
Yalamber
  • 7,360
  • 15
  • 63
  • 89
1

As a comment correctly points out, this seems to be an issue of scoping.

exports.getPosts = async (req, res, next) => {
  // getPosts local scope
  const posts = await Post.find({}, (err, posts) => {
      // ... local scope
      const perPage = 10;

      const currPage = req.query.page ? parseInt(req.query.page) : 1;

      const postsCount = posts.length;

      const pageCount = Math.ceil(postsCount / perPage);

      const pageDecrement = currPage > 1 ? 1 : 0;

      const pageIncrement = currPage < pageCount ? 1 : 0;

      if (err) {
        console.log("Error: ", err);
      } else {
        res.render("default/index", {
          moment: moment,
          layout: "default/layout",
          website_name: "MEAN Blog",
          page_heading: "XPress News",
          page_subheading: "A MEAN Stack Blogging Application",
          currPage: currPage,
          pageDecrement: pageDecrement,
          pageIncrement: pageIncrement,
          posts: posts,
        });
      }
      // ... local scope ended
    })
      .sort({ created_at: -1 })
      .populate("category")
      // perPage == undefined
      .limit(perPage)
      .skip(currPage - 1)) * perPage;
};

Move const perPage to above the const posts and it'll be within the correct scope/context.

exports.getPosts = async (req, res, next) => {
  const perPage = 10;
  // getPosts local scope
  const posts = await Post.find({}, (err, posts) => {
      // ... rest of func
    });
  // .. rest of func
};
Razvan Zamfir
  • 4,209
  • 6
  • 38
  • 252
Isolated
  • 2,167
  • 14
  • 14
  • This solution solves one problem, but leaves/creates others: I have a `postsCount` variable that uses the `posts` variable. – Razvan Zamfir May 10 '20 at 21:27
  • You can reference the outer scope using the local scope, but not the other way around. This article can explain this far better than I can: https://scotch.io/tutorials/understanding-scope-in-javascript – Isolated May 10 '20 at 21:30
1

Maybe if you check this component, you can solve your problem by seeing how it works

Mongoose Paginate

$ npm install mongoose-paginate

/**
* querying for `all` {} items in `MyModel`
* paginating by second page, 10 items per page (10 results, page 2)
**/

MyModel.paginate({}, 2, 10, function(error, pageCount, paginatedResults) {
 if (error) {
   console.error(error);
 } else {
   console.log('Pages:', pageCount);
   console.log(paginatedResults);
 }
}
1

As explained by other comments. This could just be a problem with scope.

exports.getPosts = async (req, res, next) => {
  const perPage = 10; // enable you to use perPage inside getPosts()
  let postsCount = 0; // let, to enable you to use/reassigned postsCount inside getPosts()
  const posts = await Post.find({}, (err, posts) => {
    // ...
    postsCount = posts.length; // updates the variable with the correct count
    // ...
    if (err) {
        console.log("Error: ", err);
      } else {
        res.render("default/index", {
          //...
          posts: posts,
          postsCount: posts.length, // for explicit postsCount in the view

        });
      }

  });

  // ..


for you problem with postsCount in the view, since you already have posts in you view

<% if (posts) {%>
  ...
<% } %>

I think you can just use posts.length or postsCounts in the view like how you use the others(e.g pageIncrement, currPage, pageIncrement);

  • I already did that, with `var postsCount = 0;`, because I can't change the value of a constant. – Razvan Zamfir May 13 '20 at 09:02
  • 1
    Hello @harold-pogi and welcome to SO! Next time please make sure to not just post code but instead explain why this solution might work and what is wrong. It helps a lot! – phaberest May 13 '20 at 12:37
  • While this code may resolve the OP's issue, it is best to include an explanation as to how your code addresses the OP's issue. In this way, future visitors can learn from your post, and apply it to their own code. SO is not a coding service, but a resource for knowledge. Also, high quality, complete answers are more likely to be upvoted. These features, along with the requirement that all posts are self-contained, are some of the strengths of SO as a platform, that differentiates it from forums. You can edit to add additional info &/or to supplement your explanations with source documentation. – SherylHohman May 13 '20 at 12:47
  • Thanks. I've update my answers, also included explanation and added `postsCount` – Harold Pogi May 13 '20 at 14:39
0

As I found out recently postsCount = posts.length counts the posts after they are limited by .skip((currPage - 1) * perPage), so the pageIncrement variable "equation" turns to:

let pageIncrement = postsCount >= perPage ? 1 : 0;

So I get, in the controller:

exports.getPosts = async (req, res, next) => {

    const perPage = 5;

    const currPage = req.query.page ? parseInt(req.query.page) : 1;

    let postsCount = 0;

    const posts = await Post.find({}, (err, posts) => {

            postsCount = posts.length;

            let pageDecrement = currPage > 1 ? 1 : 0;

            let pageIncrement = postsCount >= perPage ? 1 : 0;

            if (err) {
                console.log('Error: ', err);
            } else {
                res.render('default/index', {
                    moment: moment,
                    layout: 'default/layout',
                    website_name: 'MEAN Blog',
                    page_heading: 'XPress News',
                    page_subheading: 'A MEAN Stack Blogging Application',
                    currPage: currPage,
                    posts: posts,
                    pageDecrement: pageDecrement,
                    pageIncrement: pageIncrement
                });
            }
        })
        .sort({
            created_at: -1
        })
        .populate('category')
        .limit(perPage)
        .skip((currPage - 1) * perPage);
};

And in the view:

<a class="btn btn-primary <%= pageDecrement == 0 ? 'disabled' : '' %>" href="/?page=<%= currPage - pageDecrement %>">&larr; Newer Posts</a>

and

<a class="btn btn-primary <%= pageIncrement == 0 ? 'disabled' : '' %>" href="/?page=<%= currPage + pageIncrement %>">Older Posts &rarr;</a>

That works fine unless there are is a number of posts equal to perPage x N, where N is an integer, in which case the "Older Posts" button becomes disabled one page too late.

Razvan Zamfir
  • 4,209
  • 6
  • 38
  • 252