Работаете над веб-приложением для электронной коммерции? Делаете проект самостоятельно, чтобы улучшить свои навыки? Нужно реализовать разбиение на страницы? И, возможно, изо всех сил пытается найти, как лучше всего реализовать разбиение на страницы.

В этой статье я покажу вам, как реализовать разбиение на страницы в серверной части. Мы будем использовать следующий стек:

  • NodeJS - среда выполнения JavaScript
  • ExpressJS - NodeJS Framework
  • MongoDB - база данных NoSQL
  • Mongoose - объектное моделирование MongoDB
  • Встроенные шаблоны JavaScript (EJS) - View Engine

Помимо этого, я использую экспресс-маршрутизатор для лучшей читаемости и организации кода, а также body-parser для анализа тел входящих запросов.

Примечание. Я пропущу базовую настройку среды при запуске. Нет установки пакетов NPM, нет создания дерева каталогов и т. Д.

Мы начнем создавать пагинацию с создания вспомогательной функции в отдельном файле, который мы будем использовать позже. Эта функция будет использоваться в нашей поисковой системе. Функция использует регулярные выражения (RegEx) и экранирует множество символов. Я не буду рассматривать Regex в этой статье. Но, если вы хотите узнать больше о RegEx, который является действительно хорошим инструментом, проверьте эту ссылку.

Вспомогательная функция (regex-escape.js)

// Regex function for search functionality
const escapeRegex = (string) => {
  return string.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
};
// Exporting Function
module.exports = escapeRegex;

ШАГ 1. Настройка схемы продукта MongoDB (Products.js)

Прежде всего, нам нужно будет настроить схему / модель MongoDB для продуктов.
У товаров будут такие данные, как: название, описание, цена, изображение.

// - Importing Mongoose - \\
const mongoose = require('mongoose');
// * ------------- * \\
// - MongoDB Schema - \
// * ------------- * \\
// - Creating Schema for database - \\
const prodSchema = new mongoose.Schema({
 name: String,
 description: String,
 price: Number,
 image: String
});
// - Compiling mongoose Schema to a Model - \\
const Product = mongoose.model('Product', prodSchema);
// Exporting Products Model
module.exports = Product;

Итак, после того, как мы это сделаем, мы убедимся, что модель действительно экспортируется, чтобы мы могли использовать ее в других файлах.

Примечание. Причина, по которой я использую String для свойства изображения, заключается в том, что я храню ссылки на изображения. Вы можете настроить двоичные файлы / двоичные файлы или что-то подобное, если хотите.

ШАГ 2. Настройка Shop Route (shop.js)

Далее мы настроим файл маршрута магазина (shop.js). Маршрут магазина будет включать запросы GET для отображения индексной страницы магазина и страницы продукта. Также мы закодируем нашу небольшую поисковую систему.

1. Требуемые зависимости:

// - Setting Up Dependencies - \\
const express = require('express'),
                router = express.Router();
// - Importing MVC files (Model-View-Controller design pattern) - \\
const Product = require('../models/Products');
// - Importing Other JS Files - \\
const escapeRegex = require('../../js/utilities/regex-escape');

2. Настройка запроса GET для индексной страницы магазина:

// GET - Shop Index Page | - Displaying shop index page - \\
router.get('/shop', async (req, res) => {
try {
  // Rendering EJS Shop Index File
  res.render('shop-index.ejs');
} catch (err) {
  throw new Error(err);
 } 
});

Базовая настройка маршрута запроса GET, которая отображает страницу индекса магазина EJS. Он использует async / await. Хотя мы могли бы использовать обычные обратные вызовы, это не имеет значения, поскольку все, что мы делаем, это рендеринг файла EJS.

3. Настройка GET-запроса для страницы продуктов:

// GET - Shop Product Page | - Displaying demanded product page with page numbers
router.get('/shop/:page', async (req, res, next) => {
// Declaring variable
const resPerPage = 9; // results per page
const page = req.params.page || 1; // Page 
try {
 if (req.query.search) {
// Declaring query based/search variables
   const searchQuery = req.query.search,
   regex = new RegExp(escapeRegex(req.query.search), 'gi');
// Find Demanded Products - Skipping page values, limit results       per page
const foundProducts = await Product.find({name:regex})
      .skip((resPerPage * page) - resPerPage)
      .limit(resPerPage);
// Count how many products were found
const numOfProducts = await Product.count({name: regex});
// Renders The Page
res.render('shop-products.ejs'), {
   products: foundProducts, 
   currentPage: page, 
   pages: Math.ceil(numOfProducts / resPerPage), 
   searchVal: searchQuery, 
   numOfResults: numOfProducts
  });
 }
} catch (err) {
  throw new Error(err);
}
});

Это немного неприятно, но это не так сложно, если вы внимательно посмотрите на него.
- Прежде всего, мы настраиваем маршрут запроса GET для страницы продукта . Он принимает : page в качестве параметра, который указывает страницу, на которой мы находимся.
- Далее мы объявляем некоторые переменные. Первая переменная (resPerPage) определяет, сколько продуктов мы хотим отображать на странице. В данном случае это 9. Вторая переменная (страница) определяет страницу, на которой находится пользователь. По умолчанию это страница 1.
- Затем мы открыли новый блок try, и он начинается с оператора if. Он говорит: только если есть поисковый запрос, выполните следующий код.
- Мы сохраняем поисковый запрос (req.query.search) в переменной с именем searchQuery. Помимо этого, мы применяем нашу вспомогательную функцию регулярного выражения-escape к сохраненной переменной запроса searchQuery. Таким образом, наша вспомогательная функция извлекает то, что находится внутри переменной searchQuery, и мы можем применить ее к следующему методу! req.query.search поступает из отправки формы, которую мы создадим в нашем файле shop-index.ejs.
- В следующем методе Product.find ({name: regex}), ищем товары. Эта переменная regex содержит извлеченные данные req.search.query с использованием функции перехода с регулярным выражением. Помимо этого, мы пропускаем страницу, поэтому первая страница фактически оценивается как страница номер 1 с использованием метода .skip (). И, наконец, мы ограничиваем количество результатов на странице до 9.
- После этого мы подсчитываем, сколько продуктов было найдено с помощью метода .count ().
- И, наконец, мы визуализируем страница shop-products.ejs заполнена переменными!

4. Экспорт интернет-магазина:

// Exporting Shop Router
module.exports = router;

→ Просто экспортируйте роутер shop.js.

Файл shop.js в конце должен выглядеть так:

// - Setting Up Dependencies - \\
const express = require('express'),
                router = express.Router();
// - Importing MVC files (Model-View-Controller design pattern) - \\
const Product = require('../models/Products');
// - Importing Other JS Files - \\
const escapeRegex = require('../../js/utilities/regex-escape');
// GET - Shop Index Page | - Displaying shop index page - \\
router.get('/shop', async (req, res) => {
try {
  // Rendering EJS Shop Index File
  res.render('shop-index.ejs');
} catch (err) {
  throw new Error(err);
 } 
});

// GET - Shop Product Page | - Displaying demanded product page with page numbers
router.get('/shop/:page', async (req, res, next) => {
// Declaring variable
const resPerPage = 9; // results per page
const page = req.params.page || 1; // Page
try {
 if (req.query.search) {
// Declaring query based/search variables
   const searchQuery = req.query.search,
   regex = new RegExp(escapeRegex(req.query.search), 'gi');
// Find Demanded Products - Skipping page values, limit results       per page
const foundProducts = await Product.find({name:regex})
      .skip((resPerPage * page) - resPerPage)
      .limit(resPerPage);
// Count how many products were found
const numOfProducts = await Product.count({name: regex});
// Renders The Page
res.render('shop-products.ejs'), {
   products: foundProducts, 
   currentPage: page, 
   pages: Math.ceil(numOfProducts / resPerPage), 
   searchVal: searchQuery, 
   numOfResults: numOfProducts
  });
 }
} catch (err) {
  throw new Error(err);
}
});
// Exporting Shop Router
module.exports = router;

ШАГ 3. СОЗДАНИЕ СТРАНИЦЫ МАГАЗИНА EJS INDEX (shop-index.ejs)

После того, как мы выполнили всю работу над серверной частью, мы можем создать наш первый файл EJS. Назовем его shop-index.ejs.
Пропуск заголовка, заполненного метаданными и всеми ссылками, заголовком и т. д. Мы сразу перейдем к его основному содержанию.

<div class="standard-wrapper">
  <header>
   <div>
     <h1>PLACEHOLDER TEXT</h1>
     <h2>Search For Products</h2>
   </div>
  <div class="header-form">
    <form action="/shop/1/">
      <input type="text" name="search" placeholder="Search">
      <button type="submit"></button>
    </form>
  </div>
 </header>
</div>

Примечание. Убедитесь, что параметр действия в форме совпадает с вашим запросом GET в shop.js. В этом случае, поскольку мы использовали router.get ('/ shop /: page'), мы должны добавить этот номер 1 к параметру действия (action = '/ shop / 1') в форме, чтобы он начинается со страницы 1.

ШАГ 4. СОЗДАНИЕ СТРАНИЦЫ ТОВАРОВ EJS SHOP (shop-products.ejs)

А вот и настоящая часть. Мы создадим нашу актуальную страницу продукта. Назовем этот файл shop-products.ejs.
Мы еще раз пропустим головную часть и перейдем сразу к ее основной.

1. Контейнер с результатами сборки / Часть страницы | Отображает данные о товарах (количество товаров, количество страниц, поисковый запрос)

<!-- SEARCH RESULTS | START -->
<div class="results">
 <h3>Search results: <span><%= searchVal %></span></h3>
 <h4>Displaying Total <span><%= numOfResults %></span> Results</h4>
 <h4>
   Page 
   <span><%= currentPage %></span> // 1
   of 
   <span><%= pages %></span> // 2
 </h4> // Displays: Page 1 of 2, depends on how many pages there are
</div>
<!-- SEARCH RESULTS | END -->

→ Это простой контейнер / часть созданной нами страницы, которая отображает, что пользователь искал, сколько результатов показано пользователю, сколько страниц существует для искомого продукта.

2. Обработка продуктов

<!-- SHOP | START -->
 <section class="shop">
  <div class="shop-container">
<!-- Rendering Products using EJS -->
<% products.forEach(product => { %>
 <div class="item">
  <div class="shop-row shop-row--1">
   <img data-lazy="<%= product.image %>" class="cover-img">
  </div>
 <div class="item-desc">
  <h2 class="item-desc__name"><%= product.name %></h2>
  <p class="item-desc__text"><%= product.description %></p>
  <h3 class="item-desc__price">&dollar; <%= product.price %>
 </div>
</div>
<% }); %>
</div>

→ Мы рендерим продукты с помощью EJS и цикла forEach. Для каждого продукта в нашей базе данных мы визуализируем новый div, заполненный другими div, заполненными определенными данными, такими как имя, описание, цена, изображение.

3. Построение разбивки на страницы

<!-- PAGINATION -->
<div class="shop-pagination">
 <div class="pagination">
<% if (currentPage == 1 && pages > 1) { %> 
<a 
href="/shop/<%= parseInt(currentPage) + 1 %>/?search=<%=searchVal%>" <span>
  Page <%=  parseInt(currentPage) + 1 %>
  <i class="icon ion-ios-arrow-forward"></i>
</span>
</a>
<% } else if (currentPage < pages) { %>
<a 
href="/shop/<%= parseInt(currentPage) - 1 %>/?search<%=searchVal%>"><span>
  <i class="icon ion-ios-arrow-back"></i>
  Page <%= parseInt(currentPage) - 1 %>
</span>
</a>
<a 
href="/shop/<%= parseInt(currentPage) + 1 %>/?search<%=searchVal%>"><span>
 Page <%= parseInt(currentPage) + 1 %>
 <i class="icon ion-ios-arrow-forward"></i>
</span>
</a>
<% } else if (currentPage == pages && pages > 1) { %>
<a 
href="/shop/<%= parseInt(currentPage) - 1 %>/?search<%=searchVal%>"><span>
  <i class="icon ion-ios-arrow-back"></i>Page 
  <%=parseInt(currentPage) - 1 %>
</span>
</a>
<% } %>
</div>

Здесь мы строим фактическую разбивку на страницы. Прежде всего, если currentPage равен 1, вперед будет только кнопка рендеринга. В противном случае, если currentPage меньше фактического количества страниц (что означает, что мы где-то посередине, например, страница 5 из 9), визуализируйте обе кнопки, вперед и назад. И, наконец, в конце, если currentPage равно общему количеству страниц и их больше 1 (мы на последней странице), отобразится только кнопка для перехода назад.
Если результаты меньше 9 или 9, разбивка на страницы не отображается.

Окончательный файл shop-products.ejs должен выглядеть следующим образом:

<!-- SEARCH RESULTS | START -->
<div class="results">
 <h3>Search results: <span><%= searchVal %></span></h3>
 <h4>Displaying Total <span><%= numOfResults %></span> Results</h4>
 <h4>
   Page 
   <span><%= currentPage %></span> // 1
   of 
   <span><%= pages %></span> // 2
 </h4> // Displays: Page 1 of 2, depends on how many pages there are
</div>
<!-- SEARCH RESULTS | END -->
<!-- SHOP | START -->
<section class="shop">
 <div class="shop-container">
 <!-- Rendering Products using EJS -->
 <% products.forEach(product => { %>
  <div class="item">
   <div class="shop-row shop-row--1">
    <img data-lazy="<%= product.image %>" class="cover-img">
   </div>
  <div class="item-desc">
   <h2 class="item-desc__name"><%= product.name %></h2>
   <p class="item-desc__text"><%= product.description %></p>
   <h3 class="item-desc__price">&dollar; <%= product.price %>
  </div>
 </div>
<% }); %>
</div>
<!-- PAGINATION -->
 <div class="shop-pagination">
  <div class="pagination">
   <% if (currentPage == 1 && pages > 1) { %> 
    <a 
      href="/shop/<%= parseInt(currentPage) + 1 %>
           /?search=<%=searchVal%>" 
      <span>
       Page <%=  parseInt(currentPage) + 1 %>
       <i class="icon ion-ios-arrow-forward"></i>
      </span>
    </a>
  <% } else if (currentPage < pages) { %>
   <a 
     href="/shop/<%= parseInt(currentPage) - 1 %>
          /?search<%=searchVal%>">
     <span>
       <i class="icon ion-ios-arrow-back"></i>
       Page <%= parseInt(currentPage) - 1 %>
     </span>
   </a>
   <a 
     href="/shop/<%= parseInt(currentPage) + 1 %>
          /?search<%=searchVal%>">
     <span>
      Page <%= parseInt(currentPage) + 1 %>
      <i class="icon ion-ios-arrow-forward"></i>
     </span>
   </a>
 <% } else if (currentPage == pages && pages > 1) { %>
  <a 
    href="/shop/<%= parseInt(currentPage) - 1 %>
         /?search<%=searchVal%>">
    <span>
     <i class="icon ion-ios-arrow-back"></i>Page 
     <%=parseInt(currentPage) - 1 %>
    </span>
  </a>
   <% } %>
  </div>
 </div>
<% } %>
</section>

Что я создал с помощью того же кода

Все сделано!

Официально это все. Пагинация завершена и работает. Это действительно занимает пару строк кода на сервере, но, с другой стороны, EJS занимает большую часть этого кода.

Обратите внимание, я удалил классы из определенных элементов в файлах EJS, так как хотел сохранить код чистым и получить больше места при написании этого кода. Я оставил несколько классов для какого-то элемента, чтобы вы могли увидеть, как я реализовал методологию БЭМ в сочетании с SASS, даже если я не буду рассказывать о SASS в этой статье, стоит упомянуть об этом.

Заключение

На создание этого для моего личного проекта у меня ушло более 24 часов, и я подумал, что смогу сэкономить чужое время, написав эту статью, если этот кто-то хочет достичь чего-то вроде этого.
Конечно, это не то, что нужно Единственная статья, которую вы найдете о разбивке на страницы на бэкэнде, держу пари, есть много других, которые делают что-то по-другому, и это стоит проверить. Чем больше вы знаете подходов для решения конкретной проблемы или проблемы, похожей на эту, тем лучше.
В моем случае я не смог найти именно то, что хотел, поэтому я провел исследование, написал пару примеров и сделал это так.

Я получил часть кода из этой классной статьи о разбиении на страницы в серверной части!

Удачи в создании чего-то удивительного!

А теперь ты можешь подписаться на меня в твиттере!