Работаете над веб-приложением для электронной коммерции? Делаете проект самостоятельно, чтобы улучшить свои навыки? Нужно реализовать разбиение на страницы? И, возможно, изо всех сил пытается найти, как лучше всего реализовать разбиение на страницы.
В этой статье я покажу вам, как реализовать разбиение на страницы в серверной части. Мы будем использовать следующий стек:
- 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">$ <%= 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">$ <%= 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 часов, и я подумал, что смогу сэкономить чужое время, написав эту статью, если этот кто-то хочет достичь чего-то вроде этого.
Конечно, это не то, что нужно Единственная статья, которую вы найдете о разбивке на страницы на бэкэнде, держу пари, есть много других, которые делают что-то по-другому, и это стоит проверить. Чем больше вы знаете подходов для решения конкретной проблемы или проблемы, похожей на эту, тем лучше.
В моем случае я не смог найти именно то, что хотел, поэтому я провел исследование, написал пару примеров и сделал это так.
Я получил часть кода из этой классной статьи о разбиении на страницы в серверной части!
Удачи в создании чего-то удивительного!
А теперь ты можешь подписаться на меня в твиттере!