Полный цикл в digital

Что такое REST API

Я использую Docker, поэтому привожу ссылку на готовый образ в GitFlic, образ содержит все необходимое для удобной работы.

Во многих приложениях REST API необходим, потому что это самый легкий способ создания, чтения, обновления или удаления информации между различными приложениями через Интернет или протокол HTTP. Эта информация представляется пользователю в одно мгновение, особенно если вы используете JavaScript для отображения данных на веб-странице.

REST API может использоваться любым приложением, которое может подключаться к Интернету. Если данные из приложения могут быть созданы, прочитаны, обновлены или удалены с помощью другого приложения, это обычно означает, что используется REST API.

В итоге у нас должно появится приложение, которое будет работать через REST API:

Файловая структура

  • Папка public_html
  • Папка api
    • Папка config
      • Файл database.php подключение к базе данных
      • Файл core.php базовая конфигурация
    • Папка objects
      • Файл product.php чтение данных из базы продуктов
      • Файл category.php чтение данных из базы категорий
    • Папка product
      • Файл create.php создание товара
      • Файл delete.php удаление товара
      • Файл read.php получение товаров
      • Файл read_paging.php получение товаров с пагинацией
      • Файл read_one.php получение одного товара
      • Файл update.php обновление товара
      • Файл search.php поиск по товарам
    • Папка category
      • Файл read.php создание категории
    • Папка shared
      • Файл utilities.php массив для пагинации
  • Папка app
    • Папка assets
      • Папка css
        • Файл style.css стили приложения
      • Папка js
        • Файл bootbox.min.js всплывающие окна (CDN)
        • Файл jquery.min.js Jquery (CDN)
    • Папка product
      • Файл read-one-prod.js обработчик нажатия кнопки «Просмотр товара»
      • Файл create-prod.js обработчик нажатия кнопки «Создать товар»
      • Файл delete-prod.js обработчик нажатия кнопки «Удалить товар»
      • Файл read-prod.js показывает товары на странице при загрузки
      • Файл update-prod.js обработчик нажатия кнопки «Обновления товара»
      • Файл create-prod.js обработчик нажатия кнопки «Создать товар»
      • Файл products.js функции для других компонентов
    • Файл app.js основные функции HTML и JavaScript
  • Файл index.html исполняемый файл

База данных api/config/database.php

Используя PhpMyAdmin, создаем новую базу данных api_db. После этого выполняем следующие SQL-запросы, чтобы создать новые таблицы с образцами данных.

Таблица категорий

CREATE TABLE IF NOT EXISTS `categories` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(256) NOT NULL,
  `description` text NOT NULL,
  `created` datetime NOT NULL,
  `modified` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=19 ;

Наполнение таблицы категорий

INSERT INTO `categories` (`id`, `name`, `description`, `created`, `modified`) VALUES
(1, "Fashion", "Category for anything related to fashion.", "2014-06-01 00:35:07", "2014-05-30 17:34:33"),
(2, "Electronics", "Gadgets, drones and more.", "2014-06-01 00:35:07", "2014-05-30 17:34:33"),
(3, "Motors", "Motor sports and more", "2014-06-01 00:35:07", "2014-05-30 17:34:54"),
(5, "Movies", "Movie products.", "2019-05-20 10:22:05", "2019-08-20 10:30:15"),
(6, "Books", "Kindle books, audio books and more.", "2018-03-14 08:05:25", "2019-05-20 11:29:11"),
(13, "Sports", "Drop into new winter gear.", "2016-01-09 02:24:24", "2016-01-09 01:24:24");

Таблица товаров

CREATE TABLE IF NOT EXISTS `products` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(32) NOT NULL,
  `description` text NOT NULL,
  `price` decimal(10,0) NOT NULL,
  `category_id` int(11) NOT NULL,
  `created` datetime NOT NULL,
  `modified` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=65 ;

Наполнение таблицы товаров

INSERT INTO `products` (`id`, `name`, `description`, `price`, `category_id`, `created`, `modified`) VALUES
(1, "LG P880 4X HD", "My first awesome phone!", "336", 3, "2014-06-01 01:12:26", "2014-05-31 17:12:26"),
(2, "Google Nexus 4", "The most awesome phone of 2013!", "299", 2, "2014-06-01 01:12:26", "2014-05-31 17:12:26"),
(3, "Samsung Galaxy S4", "How about no?", "600", 3, "2014-06-01 01:12:26", "2014-05-31 17:12:26"),
(6, "Bench Shirt", "The best shirt!", "29", 1, "2014-06-01 01:12:26", "2014-05-31 02:12:21"),
(7, "Lenovo Laptop", "My business partner.", "399", 2, "2014-06-01 01:13:45", "2014-05-31 02:13:39"),
(8, "Samsung Galaxy Tab 10.1", "Good tablet.", "259", 2, "2014-06-01 01:14:13", "2014-05-31 02:14:08"),
(9, "Spalding Watch", "My sports watch.", "199", 1, "2014-06-01 01:18:36", "2014-05-31 02:18:31"),
(10, "Sony Smart Watch", "The coolest smart watch!", "300", 2, "2014-06-06 17:10:01", "2014-06-05 18:09:51"),
(11, "Huawei Y300", "For testing purposes.", "100", 2, "2014-06-06 17:11:04", "2014-06-05 18:10:54"),
(12, "Abercrombie Lake Arnold Shirt", "Perfect as gift!", "60", 1, "2014-06-06 17:12:21", "2014-06-05 18:12:11"),
(13, "Abercrombie Allen Brook Shirt", "Cool red shirt!", "70", 1, "2014-06-06 17:12:59", "2014-06-05 18:12:49"),
(26, "Another product", "Awesome product!", "555", 2, "2014-11-22 19:07:34", "2014-11-21 20:07:34"),
(28, "Wallet", "You can absolutely use this one!", "799", 6, "2014-12-04 21:12:03", "2014-12-03 22:12:03"),
(31, "Amanda Waller Shirt", "New awesome shirt!", "333", 1, "2014-12-13 00:52:54", "2014-12-12 01:52:54"),
(42, "Nike Shoes for Men", "Nike Shoes", "12999", 3, "2015-12-12 06:47:08", "2015-12-12 05:47:08"),
(48, "Bristol Shoes", "Awesome shoes.", "999", 5, "2016-01-08 06:36:37", "2016-01-08 05:36:37"),
(60, "Rolex Watch", "Luxury watch.", "25000", 1, "2016-01-11 15:46:02", "2016-01-11 14:46:02");

Подключение к базе данных

Приведенный ниже код показывает учетные данные базы данных и метод для получения подключения к базе данных с помощью PDO:

api/config/database.php<?
// подключение к базе данных
class Database
{
    // укажите свои учетные данные базы данных
    private $host = "db";
    private $db_name = "api_db";
    private $username = "root";
    private $password = "root123";
    public $conn;
    // получаем соединение с БД
    public function getConnection()
    {
        $this->conn = null;
        try {
            $this->conn = new PDO("mysql:host=" . $this->host . ";dbname=" . $this->db_name, $this->username, $this->password);
            $this->conn->exec("set names utf8");
        } catch (PDOException $exception) {
            echo "Ошибка подключения: " . $exception->getMessage();
        }
        return $this->conn;
    }
}

Базовые настройки api/config/core.php

Файл содержит нашу базовую конфигурацию, такую как базовый URL и переменные пагинации:

api/config/core.php<?
// показывать сообщения об ошибках
ini_set("display_errors", 1);
error_reporting(E_ALL);
// URL домашней страницы
$home_url = "http://site.loc/api/";
// страница указана в параметре URL, страница по умолчанию одна
$page = isset($_GET["page"]) ? $_GET["page"] : 1;
// установка количества записей на странице
$records_per_page = 5;
// расчёт для запроса предела записей
$from_record_num = ($records_per_page * $page) - $records_per_page;

Работа с продуктами api/objects/product.php

Класс для работы с продуктами в базе данных:

api/objects/product.php<?
// получение продуктов
class Product
{
    // подключение к базе данных и таблице "products"
    private $conn;
    private $table_name = "products";
    // свойства объекта
    public $id;
    public $name;
    public $description;
    public $price;
    public $category_id;
    public $category_name;
    public $created;
    // конструктор для соединения с базой данных
    public function __construct($db)
    {
        $this->conn = $db;
    }
    // метод для получения товаров
    function read()
    {
        // выбираем все записи
        $query = "SELECT c.name as category_name, p.id, p.name, p.description, p.price, p.category_id, p.created
        FROM " . $this->table_name . " p
        LEFT JOIN categories c ON p.category_id = c.id
        ORDER BY p.created DESC";
        // подготовка запроса
        $stmt = $this->conn->prepare($query);
        // выполняем запрос
        $stmt->execute();
        return $stmt;
    }
    // метод для создания товаров
    function create()
    {
        // запрос для вставки (создания) записей
        $query = "INSERT INTO " . $this->table_name . "
        SET name=:name, price=:price, description=:description, category_id=:category_id, created=:created";
        // подготовка запроса
        $stmt = $this->conn->prepare($query);
        // очистка
        $this->name = htmlspecialchars(strip_tags($this->name));
        $this->price = htmlspecialchars(strip_tags($this->price));
        $this->description = htmlspecialchars(strip_tags($this->description));
        $this->category_id = htmlspecialchars(strip_tags($this->category_id));
        $this->created = htmlspecialchars(strip_tags($this->created));
        // свяжем значения переменных
        $stmt->bindParam(":name", $this->name);
        $stmt->bindParam(":price", $this->price);
        $stmt->bindParam(":description", $this->description);
        $stmt->bindParam(":category_id", $this->category_id);
        $stmt->bindParam(":created", $this->created);
        // выполняем запрос
        if ($stmt->execute()) {
            return true;
        }
        return false;
    }
    // метод для получения конкретного товара по ID
    function readOne()
    {
        // запрос для чтения одной записи (товара)
        $query = "SELECT c.name as category_name, p.id, p.name, p.description, p.price, p.category_id, p.created
        FROM " . $this->table_name . " p
        LEFT JOIN categories c ON p.category_id = c.id
        WHERE p.id = ?
        LIMIT 0,1";
        // подготовка запроса
        $stmt = $this->conn->prepare($query);
        // привязываем id товара, который будет получен
        $stmt->bindParam(1, $this->id);
        // выполняем запрос
        $stmt->execute();
        // получаем извлеченную строку
        $row = $stmt->fetch(PDO::FETCH_ASSOC);
        // установим значения свойств объекта
        $this->name = $row["name"];
        $this->price = $row["price"];
        $this->description = $row["description"];
        $this->category_id = $row["category_id"];
        $this->category_name = $row["category_name"];
    }
    // метод для обновления товара
    function update()
    {
        // запрос для обновления записи (товара)
        $query = "UPDATE " . $this->table_name . "
        SET name = :name, price = :price, description = :description, category_id = :category_id
        WHERE id = :id";
        // подготовка запроса
        $stmt = $this->conn->prepare($query);
        // очистка
        $this->name = htmlspecialchars(strip_tags($this->name));
        $this->price = htmlspecialchars(strip_tags($this->price));
        $this->description = htmlspecialchars(strip_tags($this->description));
        $this->category_id = htmlspecialchars(strip_tags($this->category_id));
        $this->id = htmlspecialchars(strip_tags($this->id));
        // привязываем значения
        $stmt->bindParam(":name", $this->name);
        $stmt->bindParam(":price", $this->price);
        $stmt->bindParam(":description", $this->description);
        $stmt->bindParam(":category_id", $this->category_id);
        $stmt->bindParam(":id", $this->id);
        // выполняем запрос
        if ($stmt->execute()) {
            return true;
        }
        return false;
    }
    // метод для удаления товара
    function delete()
    {
        // запрос для удаления записи (товара)
        $query = "DELETE FROM " . $this->table_name . " WHERE id = ?";
        // подготовка запроса
        $stmt = $this->conn->prepare($query);
        // очистка
        $this->id = htmlspecialchars(strip_tags($this->id));
        // привязываем id записи для удаления
        $stmt->bindParam(1, $this->id);
        // выполняем запрос
        if ($stmt->execute()) {
            return true;
        }
        return false;
    }
    // метод для поиска товаров
    function search($keywords)
    {
        // поиск записей (товаров) по "названию товара", "описанию товара", "названию категории"
        $query = "SELECT c.name as category_name, p.id, p.name, p.description, p.price, p.category_id, p.created
        FROM " . $this->table_name . " p
        LEFT JOIN categories c ON p.category_id = c.id
        WHERE p.name LIKE ? OR p.description LIKE ? OR c.name LIKE ?
        ORDER BY p.created DESC";
        // подготовка запроса
        $stmt = $this->conn->prepare($query);
        // очистка
        $keywords = htmlspecialchars(strip_tags($keywords));
        $keywords = "%{$keywords}%";
        // привязка значений переменных
        $stmt->bindParam(1, $keywords);
        $stmt->bindParam(2, $keywords);
        $stmt->bindParam(3, $keywords);
        // выполняем запрос
        $stmt->execute();
        return $stmt;
    }
    // получение товаров с пагинацией
    public function readPaging($from_record_num, $records_per_page)
    {
        // выборка
        $query = "SELECT c.name as category_name, p.id, p.name, p.description, p.price, p.category_id, p.created
        FROM " . $this->table_name . " p
        LEFT JOIN categories c ON p.category_id = c.id
        ORDER BY p.created DESC
        LIMIT ?, ?";
        // подготовка запроса
        $stmt = $this->conn->prepare($query);
        // свяжем значения переменных
        $stmt->bindParam(1, $from_record_num, PDO::PARAM_INT);
        $stmt->bindParam(2, $records_per_page, PDO::PARAM_INT);
        // выполняем запрос
        $stmt->execute();
        // вернём значения из базы данных
        return $stmt;
    }
    // данный метод возвращает кол-во товаров
    public function count()
    {
        $query = "SELECT COUNT(*) as total_rows FROM " . $this->table_name . "";
        $stmt = $this->conn->prepare($query);
        $stmt->execute();
        $row = $stmt->fetch(PDO::FETCH_ASSOC);
        return $row["total_rows"];
    }
}

Работа с категориями api/objects/category.php

Класс для работы с категориями в базе данных:

api/objects/category.php<?
class Category
{
    // подключение к базе данных и таблице "categories"
    private $conn;
    private $table_name = "categories";
    // свойства объекта
    public $id;
    public $name;
    public $description;
    public $created;
    public function __construct($db)
    {
        $this->conn = $db;
    }
    // метод для получения всех категорий товаров
    public function readAll()
    {
        $query = "SELECT id, name, description
        FROM " . $this->table_name . "
        ORDER BY name";
        $stmt = $this->conn->prepare($query);
        $stmt->execute();
        return $stmt;
    }
}

Получение товаров api/product/create.php

Файл для создания новых продуктов:

api/product/create.php<?
// необходимые HTTP-заголовки
header("Access-Control-Allow-Origin: *");
header("Content-Type: application/json; charset=UTF-8");
header("Access-Control-Allow-Methods: POST");
header("Access-Control-Max-Age: 3600");
header("Access-Control-Allow-Headers: Content-Type, Access-Control-Allow-Headers, Authorization, X-Requested-With");
// получаем соединение с базой данных
include_once "../config/database.php";
// создание объекта товара
include_once "../objects/product.php";
$database = new Database();
$db = $database->getConnection();
$product = new Product($db);
// получаем отправленные данные
$data = json_decode(file_get_contents("php://input"));
// убеждаемся, что данные не пусты
if (
    !empty($data->name) &&
    !empty($data->price) &&
    !empty($data->description) &&
    !empty($data->category_id)
) {
    // устанавливаем значения свойств товара
    $product->name = $data->name;
    $product->price = $data->price;
    $product->description = $data->description;
    $product->category_id = $data->category_id;
    $product->created = date("Y-m-d H:i:s");
    // создание товара
    if ($product->create()) {
        // установим код ответа - 201 создано
        http_response_code(201);
        // сообщим пользователю
        echo json_encode(array("message" => "Товар был создан."), JSON_UNESCAPED_UNICODE);
    }
    // если не удается создать товар, сообщим пользователю
    else {
        // установим код ответа - 503 сервис недоступен
        http_response_code(503);
        // сообщим пользователю
        echo json_encode(array("message" => "Невозможно создать товар."), JSON_UNESCAPED_UNICODE);
    }
}
// сообщим пользователю что данные неполные
else {
    // установим код ответа - 400 неверный запрос
    http_response_code(400);
    // сообщим пользователю
    echo json_encode(array("message" => "Невозможно создать товар. Данные неполные."), JSON_UNESCAPED_UNICODE);
}

Удаление товара api/product/delete.php

Файл для удаления продуктов:

api/product/delete.php<?
// HTTP-заголовки
header("Access-Control-Allow-Origin: *");
header("Content-Type: application/json; charset=UTF-8");
header("Access-Control-Allow-Methods: POST");
header("Access-Control-Max-Age: 3600");
header("Access-Control-Allow-Headers: Content-Type, Access-Control-Allow-Headers, Authorization, X-Requested-With");
// подключим файл для соединения с базой и объектом Product
include_once "../config/database.php";
include_once "../objects/product.php";
// получаем соединение с БД
$database = new Database();
$db = $database->getConnection();
// подготовка объекта
$product = new Product($db);
// получаем id товара
$data = json_decode(file_get_contents("php://input"));
// установим id товара для удаления
$product->id = $data->id;
// удаление товара
if ($product->delete()) {
    // код ответа - 200 ok
    http_response_code(200);
    // сообщение пользователю
    echo json_encode(array("message" => "Товар был удалён"), JSON_UNESCAPED_UNICODE);
}
// если не удается удалить товар
else {
    // код ответа - 503 Сервис не доступен
    http_response_code(503);
    // сообщим об этом пользователю
    echo json_encode(array("message" => "Не удалось удалить товар"));
}

Чтение товара api/product/read.php

Файл для чтения продуктов:

api/product/read.php<?
// необходимые HTTP-заголовки
header("Access-Control-Allow-Origin: *");
header("Content-Type: application/json; charset=UTF-8");
// подключение базы данных и файл, содержащий объекты
include_once "../config/database.php";
include_once "../objects/product.php";
// получаем соединение с базой данных
$database = new Database();
$db = $database->getConnection();
// инициализируем объект
$product = new Product($db);
// запрашиваем товары
$stmt = $product->read();
$num = $stmt->rowCount();
// проверка, найдено ли больше 0 записей
if ($num > 0) {
    // массив товаров
    $products_arr = array();
    $products_arr["records"] = array();
    // получаем содержимое нашей таблицы
    // fetch() быстрее, чем fetchAll()
    while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
        // извлекаем строку
        extract($row);
        $product_item = array(
            "id" => $id,
            "name" => $name,
            "description" => html_entity_decode($description),
            "price" => $price,
            "category_id" => $category_id,
            "category_name" => $category_name
        );
        array_push($products_arr["records"], $product_item);
    }
    // устанавливаем код ответа - 200 OK
    http_response_code(200);
    // выводим данные о товаре в формате JSON
    echo json_encode($products_arr);
} else {
    // установим код ответа - 404 Не найдено
    http_response_code(404);
    // сообщаем пользователю, что товары не найдены
    echo json_encode(array("message" => "Товары не найдены."), JSON_UNESCAPED_UNICODE);
}

Чтение товара с пагинацией api/product/read_paging.php

Файл для чтения продуктов с пагинацией:

api/product/read_paging.php<?
// установим HTTP-заголовки
header("Access-Control-Allow-Origin: *");
header("Content-Type: application/json; charset=UTF-8");
// подключение файлов
include_once "../config/core.php";
include_once "../shared/utilities.php";
include_once "../config/database.php";
include_once "../objects/product.php";
// utilities
$utilities = new Utilities();
// создание подключения
$database = new Database();
$db = $database->getConnection();
// инициализация объекта
$product = new Product($db);
// запрос товаров
$stmt = $product->readPaging($from_record_num, $records_per_page);
$num = $stmt->rowCount();
// если больше 0 записей
if ($num > 0) {
    // массив товаров
    $products_arr = array();
    $products_arr["records"] = array();
    $products_arr["paging"] = array();
    // получаем содержимое нашей таблицы
    while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
        // извлечение строки
        extract($row);
        $product_item = array(
            "id" => $id,
            "name" => $name,
            "description" => html_entity_decode($description),
            "price" => $price,
            "category_id" => $category_id,
            "category_name" => $category_name
        );
        array_push($products_arr["records"], $product_item);
    }
    // подключим пагинацию
    $total_rows = $product->count();
    $page_url = "{$home_url}product/read_paging.php?";
    $paging = $utilities->getPaging($page, $total_rows, $records_per_page, $page_url);
    $products_arr["paging"] = $paging;
    // установим код ответа - 200 OK
    http_response_code(200);
    // вывод в json-формате
    echo json_encode($products_arr);
} else {
    // код ответа - 404 Ничего не найдено
    http_response_code(404);
    // сообщим пользователю, что товаров не существует
    echo json_encode(array("message" => "Товары не найдены"), JSON_UNESCAPED_UNICODE);
}

Чтение товара с пагинацией api/product/read_one.php

Файл для получения одного продукта:

api/product/read_one.php<?
// необходимые HTTP-заголовки
header("Access-Control-Allow-Origin: *");
header("Access-Control-Allow-Headers: access");
header("Access-Control-Allow-Methods: GET");
header("Access-Control-Allow-Credentials: true");
header("Content-Type: application/json");
// подключение файла для соединения с базой и файл с объектом
include_once "../config/database.php";
include_once "../objects/product.php";
// получаем соединение с базой данных
$database = new Database();
$db = $database->getConnection();
// подготовка объекта
$product = new Product($db);
// установим свойство ID записи для чтения
$product->id = isset($_GET["id"]) ? $_GET["id"] : die();
// получим детали товара
$product->readOne();
if ($product->name != null) {
    // создание массива
    $product_arr = array(
        "id" =>  $product->id,
        "name" => $product->name,
        "description" => $product->description,
        "price" => $product->price,
        "category_id" => $product->category_id,
        "category_name" => $product->category_name
    );
    // код ответа - 200 OK
    http_response_code(200);
    // вывод в формате json
    echo json_encode($product_arr);
} else {
    // код ответа - 404 Не найдено
    http_response_code(404);
    // сообщим пользователю, что такой товар не существует
    echo json_encode(array("message" => "Товар не существует"), JSON_UNESCAPED_UNICODE);
}

Обновление товара api/product/update.php

Файл для обновления продукта:

api/product/update.php<?
// HTTP-заголовки
header("Access-Control-Allow-Origin: *");
header("Content-Type: application/json; charset=UTF-8");
header("Access-Control-Allow-Methods: POST");
header("Access-Control-Max-Age: 3600");
header("Access-Control-Allow-Headers: Content-Type, Access-Control-Allow-Headers, Authorization, X-Requested-With");
// подключаем файл для работы с БД и объектом Product
include_once "../config/database.php";
include_once "../objects/product.php";
// получаем соединение с базой данных
$database = new Database();
$db = $database->getConnection();
// подготовка объекта
$product = new Product($db);
// получаем id товара для редактирования
$data = json_decode(file_get_contents("php://input"));
// установим id свойства товара для редактирования
$product->id = $data->id;
// установим значения свойств товара
$product->name = $data->name;
$product->price = $data->price;
$product->description = $data->description;
$product->category_id = $data->category_id;
// обновление товара
if ($product->update()) {
    // установим код ответа - 200 ok
    http_response_code(200);
    // сообщим пользователю
    echo json_encode(array("message" => "Товар был обновлён"), JSON_UNESCAPED_UNICODE);
}
// если не удается обновить товар, сообщим пользователю
else {
    // код ответа - 503 Сервис не доступен
    http_response_code(503);
    // сообщение пользователю
    echo json_encode(array("message" => "Невозможно обновить товар"), JSON_UNESCAPED_UNICODE);
}

Поиск по товарам api/product/search.php

Файл для поиска продукта:

api/product/search.php<?
// HTTP-заголовки
header("Access-Control-Allow-Origin: *");
header("Content-Type: application/json; charset=UTF-8");
// подключение необходимых файлов
include_once "../config/core.php";
include_once "../config/database.php";
include_once "../objects/product.php";
// создание подключения к БД
$database = new Database();
$db = $database->getConnection();
// инициализируем объект
$product = new Product($db);
// получаем ключевые слова
$keywords = isset($_GET["s"]) ? $_GET["s"] : "";
// запрос товаров
$stmt = $product->search($keywords);
$num = $stmt->rowCount();
// проверяем, найдено ли больше 0 записей
if ($num > 0) {
    // массив товаров
    $products_arr = array();
    $products_arr["records"] = array();
    // получаем содержимое нашей таблицы
    // fetch() быстрее чем fetchAll()
    while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
        // извлечём строку
        extract($row);
        $product_item = array(
            "id" => $id,
            "name" => $name,
            "description" => html_entity_decode($description),
            "price" => $price,
            "category_id" => $category_id,
            "category_name" => $category_name
        );
        array_push($products_arr["records"], $product_item);
    }
    // код ответа - 200 OK
    http_response_code(200);
    // покажем товары
    echo json_encode($products_arr);
} else {
    // код ответа - 404 Ничего не найдено
    http_response_code(404);
    // скажем пользователю, что товары не найдены
    echo json_encode(array("message" => "Товары не найдены."), JSON_UNESCAPED_UNICODE);
}

Получение категорий api/category/read.php

Файл для получения категорий продуктов:

api/category/read.php<?
// установим HTTP-заголовки
header("Access-Control-Allow-Origin: *");
header("Content-Type: application/json; charset=UTF-8");
// подключение файлов для соединения с БД и файл с объектом Category
include_once "../config/database.php";
include_once "../objects/category.php";
// создание подключения к базе данных
$database = new Database();
$db = $database->getConnection();
// инициализация объекта
$category = new Category($db);
// получаем категории
$stmt = $category->readAll();
$num = $stmt->rowCount();
// проверяем, найдено ли больше 0 записей
if ($num > 0) {
    // массив для записей
    $categories_arr = array();
    $categories_arr["records"] = array();
    // получим содержимое нашей таблицы
    while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
        // извлекаем строку
        extract($row);
        $category_item = array(
            "id" => $id,
            "name" => $name,
            "description" => html_entity_decode($description)
        );
        array_push($categories_arr["records"], $category_item);
    }
    // код ответа - 200 OK
    http_response_code(200);
    // покажем данные категорий в формате json
    echo json_encode($categories_arr);
} else {
    // код ответа - 404 Ничего не найдено
    http_response_code(404);
    // сообщим пользователю, что категории не найдены
    echo json_encode(array("message" => "Категории не найдены"), JSON_UNESCAPED_UNICODE);
}

Получение массива для пагинации api/shared/utilities.php

Файл для получения массива пагинации продуктов:

api/shared/utilities.php<?
class Utilities
{
    public function getPaging($page, $total_rows, $records_per_page, $page_url)
    {
        // массив пагинации
        $paging_arr = array();
        // кнопка для первой страницы
        $paging_arr["first"] = $page > 1 ? "{$page_url}page=1" : "";
        // подсчёт всех товаров в базе данных для подсчета общего количества страниц
        $total_pages = ceil($total_rows / $records_per_page);
        // диапазон ссылок для показа
        $range = 1;
        // отображать диапазон ссылок вокруг текущей страницы
        $initial_num = $page - $range;
        $condition_limit_num = ($page + $range) + 1;
        $paging_arr["pages"] = array();
        $page_count = 0;
        for ($x = $initial_num; $x < $condition_limit_num; $x++) {
            // убедимся, что $x > 0 И $x <= $total_pages
            if (($x > 0) && ($x <= $total_pages)) {
                $paging_arr["pages"][$page_count]["page"] = $x;
                $paging_arr["pages"][$page_count]["url"] = "{$page_url}page={$x}";
                $paging_arr["pages"][$page_count]["current_page"] = $x == $page ? "yes" : "no";
                $page_count++;
            }
        }
        // кнопка для последней страницы
        $paging_arr["last"] = $page < $total_pages ? "{$page_url}page={$total_pages}" : "";
        // формат json
        return json_encode($paging_arr);
    }
}

Дальше мы будем использовать REST API, созданный на PHP. В моем случае, есть два адреса, где можно получить доступ к REST API.

На странице http://site.loc/api/product/read.php выводится json строка всех товаров без пагинации:

На странице http://site.loc/api/product/read_paging.php выводится json строка всех товаров с пагинацией:

Не все скрипты и стили мы подгружаем с сервера, что-то подгружается с CDN.

Стартовая страница приложения index.html

index.html<!DOCTYPE html>
<html lang="ru">
  <head>
    <meta charset="utf-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>Получение товаров</title>
    <!-- bootstrap CSS -->
    <link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous" />
    <!-- основной CSS -->
    <link href="app/assets/css/style.css" rel="stylesheet" />
  </head>
  <body>
    <!-- здесь будет выводиться наше приложение -->
    <div id="app"></div>
    <!-- jQuery -->
    <script src="https://code.jquery.com/jquery-3.4.1.min.js"></script>
    <!-- bootstrap JavaScript -->
    <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
    <!-- JS для всплывающих окон -->
    <script src="https://cdnjs.cloudflare.com/ajax/libs/bootbox.js/4.4.0/bootbox.min.js"></script>
    <!-- основной файл скриптов -->
    <script src="app/app.js"></script>
    <!-- product scripts -->
    <script src="app/product/read-prod.js"></script>
    <script src="app/product/create-prod.js"></script>
    <script src="app/product/read-one-prod.js"></script>
    <script src="app/product/update-prod.js"></script>
    <script src="app/product/delete-prod.js"></script>
    <script src="app/product/products.js"></script>
    <script src="app/product/search-prod.js"></script>
  </body>
</html>

Стили для приложения app/assets/css/style.css

app/assets/css/style.css.m-r-10px {
  margin-right: 10px;
}
.m-b-10px {
  margin-bottom: 10px;
}
.m-b-15px {
  margin-bottom: 15px;
}
.m-b-20px {
  margin-bottom: 20px;
}
.w-5-pct {
  width: 5%;
}
.w-10-pct {
  width: 10%;
}
.w-15-pct {
  width: 15%;
}
.w-20-pct {
  width: 20%;
}
.w-25-pct {
  width: 25%;
}
.w-30-pct {
  width: 30%;
}
.w-35-pct {
  width: 35%;
}
.w-40-pct {
  width: 40%;
}
.w-45-pct {
  width: 45%;
}
.w-50-pct {
  width: 50%;
}
.w-55-pct {
  width: 55%;
}
.w-60-pct {
  width: 60%;
}
.w-65-pct {
  width: 65%;
}
.w-70-pct {
  width: 70%;
}
.w-75-pct {
  width: 75%;
}
.w-80-pct {
  width: 80%;
}
.w-85-pct {
  width: 85%;
}
.w-90-pct {
  width: 90%;
}
.w-95-pct {
  width: 95%;
}
.w-100-pct {
  width: 100%;
}
.display-none {
  display: none;
}
.padding-bottom-2em {
  padding-bottom: 2em;
}
.width-30-pct {
  width: 30%;
}
.width-40-pct {
  width: 40%;
}
.overflow-hidden {
  overflow: hidden;
}
.margin-right-1em {
  margin-right: 1em;
}
.right-margin {
  margin: 0 0.5em 0 0;
}
.margin-bottom-1em {
  margin-bottom: 1em;
}
.margin-zero {
  margin: 0;
}
.text-align-center {
  text-align: center;
}
.pagination a {
  cursor: pointer;
}

Обработчик кнопки «Просмотр товара» app/product/read-one-prod.js

app/product/read-one-prod.jsjQuery(($) => {
  // Обрабатываем нажатие кнопки «Просмотр товара»
  $(document).on("click", ".read-one-product-button", function () {
    // Получаем ID товара
    const id = $(this).attr("data-id");
    // Чтение записи товара на основе данного идентификатора
    $.getJSON("http://site.loc/api/product/read_one.php?id=" + id, (data) => {
      // Начало HTML
      let read_one_product_html =`
  <!-- При нажатии будем отображать список товаров -->
  <div id="read-products" class="btn btn-primary pull-right m-b-15px read-products-button"><span class="glyphicon glyphicon-list"></span> Все товары</div>
  <!-- Полные данные о товаре будут показаны в этой таблице -->
  <table class="table table-bordered table-hover">
      <tr>
          <td class="w-30-pct">Название</td>
          <td class="w-70-pct">`+ data.name +`</td>
      </tr>
      <tr>
          <td>Цена</td>
          <td>`+ data.price +`</td>
      </tr>
      <tr>
          <td>Описание</td>
          <td>`+ data.description +`</td>
      </tr>
      <tr>
          <td>Категория</td>
          <td>` + data.category_name +`</td>
      </tr>
  </table>`;
      // Вставка HTML в «page-content» нашего приложения
      $("#page-content").html(read_one_product_html);
      // Изменяем заголовок страницы
      changePageTitle("Просмотр товара");
    });
  });
});

Обработчик кнопки «Создать товар» app/product/create-prod.js

app/product/create-prod.jsjQuery(($) => {
  // Показать html форму при нажатии кнопки «создать товар»
  $(document).on("click", ".create-product-button", () => {
    // Загрузка списка категорий
    $.getJSON("http://site.loc/api/category/read.php", (data) => {
      // Перебор возвращаемого списка данных и создание списка выбора
      let categories_options_html = `<select name="category_id" class="form-control">`;
      $.each(data.records, (key, val) => {
        categories_options_html += `<option value="` + val.id + `">` + val.name + `</option>`;
      });
      categories_options_html += `</select>`;
      let create_product_html =`
      <!-- Кнопка для показа всех товаров -->
      <div id="read-products" class="btn btn-primary pull-right m-b-15px read-products-button"><span class="glyphicon glyphicon-list"></span> Все товары</div>
      <!-- html форма «Создание товара» -->
      <form id="create-product-form" action="#" method="post" border="0">
          <table class="table table-hover table-responsive table-bordered">
              <tr>
                  <td>Название</td>
                  <td><input type="text" name="name" class="form-control" required /></td>
              </tr>
              <tr>
                  <td>Цена</td>
                  <td><input type="number" min="1" name="price" class="form-control" required /></td>
              </tr>
              <tr>
                  <td>Описание</td>
                  <td><textarea name="description" class="form-control" required></textarea></td>
              </tr>
              <!-- Список выбора категории -->
              <tr>
                  <td>Категория</td>
                  <td>` + categories_options_html + `</td>
              </tr>
              <!-- Кнопка отправки формы -->
              <tr>
              <td></td>
              <td><button type="submit" class="btn btn-primary"><span class="glyphicon glyphicon-plus"></span> Создать товар</button></td>
              </tr>
          </table>
      </form>`;
      // Вставка html в «page-content» нашего приложения
      $("#page-content").html(create_product_html);
      // Изменяем тайтл
      changePageTitle("Создание товара");
    });
  });
  // Будет работать, если создана форма товара
  $(document).on("submit", "#create-product-form", function () {
    // Получение данных формы
    let form_data = JSON.stringify($(this).serializeObject());
    // Отправка данных формы в API
    $.ajax({
      url: "http://site.loc/api/product/create.php",
      type: "POST",
      contentType: "application/json",
      data: form_data,
      success: (result) => {
        // Товар был создан, вернуться к списку товаров
        showProducts();
      },
      error: (xhr, resp, text) => {
        // Вывести ошибку в консоль
        console.log(xhr, resp, text);
      },
    });
    return false;
  });
});

Обработчик кнопки «Удалить товар» app/product/delete-prod.js

app/product/delete-prod.jsjQuery(($) => {
  // Будет работать, если была нажата кнопка удаления
  $(document).on("click", ".delete-product-button", function () {
    // Получение ID товара
    const product_id = $(this).attr("data-id");
    // Bootbox для подтверждения во всплывающем окне
    bootbox.confirm({
      message: "<h4>Вы уверены?</h4>",
      buttons: {
        confirm: {
          label: "<span class='glyphicon glyphicon-ok'></span> Да",
          className: "btn-danger",
        },
        cancel: {
          label: "<span class='glyphicon glyphicon-remove'></span> Нет",
          className: "btn-primary",
        },
      },
      callback: (result) => {
        if (result == true) {
          // Отправим запрос на удаление в API / удаленный сервер
          $.ajax({
            url: "http://site.loc/api/product/delete.php",
            type: "POST",
            dataType: "json",
            data: JSON.stringify({ id: product_id }),
            success: (result) => {
              // Покажем список всех товаров
              showProducts();
            },
            error: (xhr, resp, text) => {
              console.log(xhr, resp, text);
            },
          });
        }
      },
    });
  });
});

Показывает товары на странице при загрузки app/product/read-prod.js

Логика нашего тестового приложения позволяет выводить товары с пагинацией и без.

С пагинацией

app/product/read-prod.jsjQuery(($) => {
  // Показать список товаров при первой загрузке
  showProducts();
  // Когда была нажата кнопка «Все товары»
  $(document).on("click", ".read-products-button", function () {
    showProducts();
  });
  // Когда была нажата кнопка пагинации
  $(document).on("click", ".pagination li", function () {
    // Получаем JSON URL
    const json_url = $(this).find("a").attr("data-page");
    // Покажем список товаров с пагинацией
    showProducts(json_url);
  });
});
// Функция для отображения списка товаров
function showProducts(json_url = "http://site.loc/api/product/read_paging.php") {
  // Получаем список товаров из API
  $.getJSON(json_url, function (data) {
    // HTML для перечисления товаров
    readProductsTemplate(data, "");
    // Изменим заголовок страницы
    changePageTitle("Все товары");
  });
}

Без пагинации

app/product/read-prod.jsjQuery(($) => {
  // Показать список товаров при первой загрузке
  showProducts();
  // При нажатии кнопки
  $(document).on("click", ".read-products-button", () => {
    showProducts();
  });
});
// Функция для отображения списка товаров
function showProducts() {
  // Получаем список товаров из API
  $.getJSON("http://site.loc/api/product/read.php", (data) => {
    // HTML для перечисления товаров
    readProductsTemplate(data, "");
    // Изменяем заголовок страницы
    changePageTitle("Все товары");
  });
}
// Функция для показа списка товаров
function showProducts() {
  // Получить список товаров из API
  $.getJSON("http://site.loc/api/product/read.php", (data) => {
    // HTML для списка товаров
    let read_products_html = `
    <!-- При нажатии загружается форма создания товара -->
    <div id="create-product" class="btn btn-primary pull-right m-b-15px create-product-button"><span class="glyphicon glyphicon-plus"></span> Создание товара</div>
    <!-- Таблица товаров -->
    <table class="table table-bordered table-hover">
    <!-- Создание заголовков таблицы -->
    <tr>
        <th class="w-15-pct">Название</th>
        <th class="w-10-pct">Цена</th>
        <th class="w-15-pct">Категория</th>
        <th class="w-25-pct text-align-center">Действие</th>
    </tr>`;
    // Перебор списка возвращаемых данных
    $.each(data.records, function (key, val) {
      // Создание новой строки таблицы для каждой записи
      read_products_html +=`
        <tr>
          <td>`+ val.name +`</td>
            <td>`+ val.price +`</td>
            <td>`+ val.category_name +`</td>
            <!-- Кнопки "действий" -->
            <td>
                <!-- Кнопка чтения товара -->
                <button class="btn btn-primary m-r-10px read-one-product-button" data-id="`+ val.id +`">
                    <span class="glyphicon glyphicon-eye-open"></span> Просмотр
                </button>
                <!-- Кнопка редактирования -->
                <button class="btn btn-info m-r-10px update-product-button" data-id="`+ val.id +`">
                    <span class="glyphicon glyphicon-edit"></span> Редактирование
                </button>
                <!-- Кнопка удаления товара -->
                <button class="btn btn-danger delete-product-button" data-id="`+ val.id +`">
                    <span class="glyphicon glyphicon-remove"></span> Удаление
                </button>
            </td>
        </tr>`;
    });
    read_products_html += `</table>`;
    // Вставка в "page-content" нашего приложения
    $("#page-content").html(read_products_html);
    // Изменяем заголовок страницы
    changePageTitle("Все товары");
  });
}

Обработчик кнопки «Обновления товара» app/product/update-prod.js

app/product/update-prod.jsjQuery(($) => {
  // Показывать HTML форму при нажатии кнопки «Обновить товар»
  $(document).on("click", ".update-product-button", function () {
    // Получаем ID товара
    const id = $(this).attr("data-id");
    // Читаем одну запись на основе данного идентификатора товара
    $.getJSON("http://site.loc/api/product/read_one.php?id=" + id, (data) => {
      // Значения будут использоваться для заполнения нашей формы
      const name = data.name;
      const price = data.price;
      const description = data.description;
      const category_id = data.category_id;
      const category_name = data.category_name;
      // Загрузка списка категорий
      $.getJSON("http://site.loc/api/category/read.php", (data) => {
        // Строим список выбора
        // Перебор полученного списка данных
        let categories_options_html = `<select name="category_id" class="form-control">`;
        $.each(data.records, (key, val) => {
          // Опция предварительного выбора - это идентификатор категории
          if (val.id == category_id) {
            categories_options_html += `<option value="` + val.id + `" selected>` + val.name + `</option>`;
          } else {
            categories_options_html += `<option value="` + val.id + `">` + val.name + `</option>`;
          }
        });
        categories_options_html += `</select>`;
        // Сохраним html в переменной «update product»
        let update_product_html =`
          <div id="read-products" class="btn btn-primary pull-right m-b-15px read-products-button"><span class="glyphicon glyphicon-list"></span> Все товары</div>
          <!-- Построение формы для изменения товара -->
          <!-- Мы используем свойство "required" html5 для предотвращения пустых полей -->
          <form id="update-product-form" action="#" method="post" border="0">
              <table class="table table-hover table-responsive table-bordered">
                  <tr>
                      <td>Название</td>
                      <td><input value=\"`+ name +`\" type="text" name="name" class="form-control" required /></td>
                  </tr>
                  <tr>
                      <td>Цена</td>
                      <td><input value=\"`+ price +`\" type="number" min="1" name="price" class="form-control" required /></td>
                  </tr>
                  <tr>
                      <td>Описание</td>
                      <td><textarea name="description" class="form-control" required>`+ description +`</textarea></td>
                  </tr>
                  <tr>
                      <td>Категория</td>
                      <td>` + categories_options_html +`</td>
                  </tr>
                  <tr>
                      <!-- Скрытый «идентификатор товара, чтобы определить, какую запись удалить -->
                      <td><input value=\"`+ id +`\" name="id" type="hidden" /></td>
                      <!-- Кнопка отправки формы -->
                      <td>
                          <button type="submit" class="btn btn-info">
                              <span class="glyphicon glyphicon-edit"></span> Обновить товар
                          </button>
                      </td>
                  </tr>
              </table>
          </form>`;
        // Добавим в «page-content» нашего приложения
        $("#page-content").html(update_product_html);
        // Изменим title страницы
        changePageTitle("Обновление товара");
      });
    });
  });
  // Будет запущен при отправке формы обновления товара
  $(document).on("submit", "#update-product-form", function () {
    // Получаем данные формы
    const form_data = JSON.stringify($(this).serializeObject());
    // Отправка данных формы в API
    $.ajax({
      url: "http://site.loc/api/product/update.php",
      type: "POST",
      contentType: "application/json",
      data: form_data,
      success: (result) => {
        // Товар был успешно обновлён, возврат к списку товаров
        showProducts();
      },
      error: (xhr, resp, text) => {
        // Вывод ошибки в консоль
        console.log(xhr, resp, text);
      },
    });
    return false;
  });
});

Обработчик кнопки «Создать товар» app/product/create-prod.js

app/product/create-prod.jsjQuery(($) => {
  // Показать html форму при нажатии кнопки «создать товар»
  $(document).on("click", ".create-product-button", () => {
    // Загрузка списка категорий
    $.getJSON("http://site.loc/api/category/read.php", (data) => {
      // Перебор возвращаемого списка данных и создание списка выбора
      let categories_options_html = `<select name="category_id" class="form-control">`;
      $.each(data.records, (key, val) => {
        categories_options_html += `<option value="` + val.id + `">` + val.name + `</option>`;
      });
      categories_options_html += `</select>`;
      let create_product_html =`
      <!-- Кнопка для показа всех товаров -->
      <div id="read-products" class="btn btn-primary pull-right m-b-15px read-products-button"><span class="glyphicon glyphicon-list"></span> Все товары</div>
      <!-- html форма «Создание товара» -->
      <form id="create-product-form" action="#" method="post" border="0">
          <table class="table table-hover table-responsive table-bordered">
              <tr>
                  <td>Название</td>
                  <td><input type="text" name="name" class="form-control" required /></td>
              </tr>
              <tr>
                  <td>Цена</td>
                  <td><input type="number" min="1" name="price" class="form-control" required /></td>
              </tr>
              <tr>
                  <td>Описание</td>
                  <td><textarea name="description" class="form-control" required></textarea></td>
              </tr>
              <!-- Список выбора категории -->
              <tr>
                  <td>Категория</td>
                  <td>`+ categories_options_html +`</td>
          </tr>
              <!-- Кнопка отправки формы -->
              <tr>
              <td></td>
              <td><button type="submit" class="btn btn-primary"><span class="glyphicon glyphicon-plus"></span> Создать товар</button></td>
              </tr>
          </table>
      </form>`;
      // Вставка html в «page-content» нашего приложения
      $("#page-content").html(create_product_html);
      // Изменяем тайтл
      changePageTitle("Создание товара");
    });
  });
  // Будет работать, если создана форма товара
  $(document).on("submit", "#create-product-form", function () {
    // Получение данных формы
    let form_data = JSON.stringify($(this).serializeObject());
    // Отправка данных формы в API
    $.ajax({
      url: "http://site.loc/api/product/create.php",
      type: "POST",
      contentType: "application/json",
      data: form_data,
      success: (result) => {
        // Товар был создан, вернуться к списку товаров
        showProducts();
      },
      error: (xhr, resp, text) => {
        // Вывести ошибку в консоль
        console.log(xhr, resp, text);
      },
    });
    return false;
  });
});

Функции других компонентов app/product/create-prod.js

app/product/create-prod.js// HTML список товаров
function readProductsTemplate(data, keywords) {
  let read_products_html =`
        <!-- Форма для поиска товаров -->
        <form id="search-product-form" action="#" method="post">
            <div class="input-group pull-left w-30-pct"><input type="text" value="`+ keywords +`" name="keywords" class="form-control product-search-keywords" placeholder="Поиск товаров..." />
                <span class="input-group-btn">
                    <button type="submit" class="btn btn-default" type="button">
                        <span class="glyphicon glyphicon-search"></span>
                    </button>
                </span>
            </div>
        </form>
        <!-- При нажатии загружается форма создания товара -->
        <div id="create-product" class="btn btn-primary pull-right m-b-15px create-product-button">
            <span class="glyphicon glyphicon-plus"></span> Создать товар
        </div>
        <!-- Начало таблицы -->
        <table class="table table-bordered table-hover">
            <!-- Создание заголовков колонок -->
            <tr>
                <th class="w-25-pct">Название</th>
                <th class="w-10-pct">Цена</th>
                <th class="w-10-pct">Категория</th>
                <th class="w-25-pct text-align-center">Действие</th>
            </tr>`;
  // Перебор возвращаемого списка данных
  $.each(data.records, (key, val) => {
    // Создание новой строки таблицы для каждой записи
    read_products_html +=`
      <tr>
        <td>`+ val.name +`</td>
        <td>$`+ val.price +`</td>
        <td>`+ val.category_name +`</td>
            <!-- Кнопки "действий" -->
            <td>
                <!-- Кнопка для просмотра товара -->
                <button class="btn btn-primary m-r-10px read-one-product-button" data-id="`+ val.id +`">
                    <span class="glyphicon glyphicon-eye-open"></span> Просмотр
                </button>
                <!-- Кнопка для изменения товара -->
                <button class="btn btn-info m-r-10px update-product-button" data-id="`+ val.id +`">
                    <span class="glyphicon glyphicon-edit"></span> Редактировать
                </button>
                <!-- Кнопка для удаления товара -->
                <button class="btn btn-danger delete-product-button" data-id="`+ val.id +`">
                    <span class="glyphicon glyphicon-remove"></span> Удалить
                </button>
            </td>
      </tr>`;
  });
  // Конец таблицы
  read_products_html += `</table>`;
  // Если есть пагинация
  if (data.paging) {
    read_products_html += `<ul class="pagination pull-left margin-zero padding-bottom-2em">`;
    // Первая страница
    if ($.parseJSON(data.paging).first != "") {
      read_products_html += `<li><a data-page="${$.parseJSON(data.paging).first}">Первая страница</a></li>`;
    }
    // Перебор страниц
    $.each($.parseJSON(data.paging).pages, (key, val) => {
      //debugger;
      const active_page = val.current_page == "yes" ? "class='active'" : "";
      read_products_html += `<li ${active_page}><a data-page="${val.url}">${val.page}</a></li>`;
    });
    // Последняя страница
    if ($.parseJSON(data.paging).last != "") {
      read_products_html += `<li><a data-page="${$.parseJSON(data.paging).last}">Последняя страница</a></li>`;
    }
    read_products_html += "</ul>";
  }
  // Добавим в «page-content» нашего приложения
  $("#page-content").html(read_products_html);
}

Основные функции HTML и JavaScript app/app.js

app/app.jsjQuery(($) => {
  // HTML приложения
  let app_html = `
        <div class="container">
            <div class="page-header"><h1 id="page-title">Все товары</h1></div>
            <!-- Здесь будет показано содержимое страницы -->
            <div id="page-content"></div>
        </div>`;
  // Вставка кода на страницу
  $("#app").html(app_html);
});
// Изменение заголовка страницы
function changePageTitle(page_title) {
  // Изменение заголовка страницы
  $("#page-title").text(page_title);
  // Изменение заголовка вкладки браузера
  document.title = page_title;
}
// Функция для создания значений формы в формате json
$.fn.serializeObject = function () {
  var o = {};
  var a = this.serializeArray();
  $.each(a, function () {
    if (o[this.name] !== undefined) {
      if (!o[this.name].push) {
        o[this.name] = [o[this.name]];
      }
      o[this.name].push(this.value || "");
    } else {
      o[this.name] = this.value || "";
    }
  });
  return o;
};

Заполните форму уже сегодня!
Для начала сотрудничества необходимо заполнить заявку или заказать обратный звонок. В ответ получите коммерческое предложение, которое будет содержать индивидуальную стратегию с учетом требований и поставленных задач
Работаем по будням с 9:00 до 18:00. Заявки, отправленные в выходные, обрабатываем в первый рабочий день до 12:00.
Спасибо, ваш запрос принят и будет обработан!
Эйч Маркетинг