Дипломный проект курса JavaScript

Прим. Back-end часть сайта располагается на herokuapp, который переходит в режим sleep при долгом "простое". Поэтому, при первичном обращении к GithubPages может показаться, что что-то сломалось, т.к. товары на страницу будут загружаться долго

Демонстрация:

В рамках дипломной работы по курсу Нетологии "JavaScript в браузере: создаем интерактивные веб-страницы" была выбрана задача - реализовать функционал интернет-магазина:

  1. Загрузка товаров в виде JSON-объектов с удалённого сервера с помощью REST API
  2. Парсинг входящих объектов и генерация HTML-разметки с помощью DOM API
  3. Добавление\удаление товаров из корзины с помощью Back-end части приложения, основываясь на сессиях с помощью Cookie
  4. Создание административной странички сайта с возможностью отвечать на чат-сообщения, получаемые через WebSocket-соединение
  5. Реализовать поисковую строку на сайте с возможностью голосового поиска, основываясь на MediaStream Recording API технологии Яндекса SpeechKit

Проектирование

Если опускать этап настройки Back-end части моего проекта, то на этапе проектирования Front-end части я выяснил, что для более качественной реализации поставленных задач, мне необходимо:

  • Найти и подготовить HTML-шаблон
  • Настроить маршрутиризатор (Класс Router)
  • Сделать шаблонизатор (Класс TemplateEngine)
  • Создать схемы страниц для шаблонизации
  • Реализовать функционал корзины
  • Сделать возможность отображения по 6, 9 и 12 товаров на странице
  • Настроить пагинацию
  • Разделить чаты на Клиентов и Администратора
  • Настроить Яндекс SpeechKit API
  • Разбить отдельные управляемые элементы на компоненты
  • Настроить события на компонентах

Подготовка html-шаблона

Для придания достойного внешнего вида и сокращения энерго-временных затрат на вёрстку, для реализации учебного проекта были взяты страницы шаблона htmlnewlook с сайта ThemeForest.net, а именно страницы:

  1. Вывод товаров в сетке - http://htmlnewlook.justthemevalley.com/shop_grid.html
  2. Корзина товаров - http://htmlnewlook.justthemevalley.com/shopping_cart.html
  3. Поиск товаров - http://htmlnewlook.justthemevalley.com/shop_list.html
  4. Просмотр конкретного товара - http://htmlnewlook.justthemevalley.com/single_product.html

Далее, шаблон был очищен от ссылок, верхнее меню, правый сайдбар и ссылки в футере ведут на '/' страницу. Были удалены все JavaScript файлы и набор товаров был переведён в JSON формат и погружён на сервер.

Роутинг

Для придания более динамичного вида проекту, мною был написан не сложный роутер, который выполняет различные функции при разных URL а адресной строке, основывается на событии hashchange

Класс Router

Файл ./js/Router/Router.js

На создание роутинга по такому принципу меня вдохновила статья зарубежного коллеги Krasimir Tsonev, который в своём блоге рассказывает, как он готовит собственный фреймворк на JavaScript. Большая часть кода роутера взята из его примеров, но очищена от лишнего кода и доработана в соответствии с задачей проекта.

Конструктор класса не принимает аргументов и создаёт экземпляр с следующими полями:

  • routes
  • root

И обладает следующими методами:

  • getFragment()
  • clearSlashes(path)
  • add(re, handler)
  • check(f)
  • listen()
  • navigate(path)
  • pickUpLinks()

(при эксплуатации класса возникла необходимость создать вспомогательную функцию sendLinkToRouter, которая используется, как обработчик события клика по ссылке и создана с целью избежания утечки памяти)

Поле routes

Хранит в себе все обрабатываемые адреса, добавленные методом add().

Поле root

Хранит в себе адрес корня сайта, от которого рассчитываются все заданные маршруты.

Метод getFragment()

Служит вспомогательной функцией, которая получает фрагмент того маршрута, на котором пользователь находится в данный момент и использует другой метод класса - clearSlashes(). Не принимает аргументов.

Метод clearSlashes(path)

Метод очищает полученный URI от слешей в начале и конце, и возвращает очищенную строку. Принимает 1 аргумент - URI без домена, порта и протокола.

Метод add()

Метод добавляет в массив поля routes объект и возвращает обновлённый экземпляр класса.

Принимает 2 аргумента:

  • re - маршрут
  • handler - обработчик маршрута

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

Метод check(f)

Метод служит для того, чтобы проверить изменения на странице. Принимает 1 не обязательный аргумент - фрагмент урл, который необходимо проверить, находит его в массиве поля routs данного экземпляра класса и вызывает его обработчик к данному маршруту.

Метод listen()

Инициализирует прослушку события hashchange на глобальном объекте window. При изменении хеша, передаёт новый хеш в метод класса check(). Добавлен таймаут в 100 миллисекунд, чтобы событие не наступало слишком часто, иначе возможны проблемы с рендерингом страниц при срабатывании редиректа.

Метод navigate(path)

Метод производит подмену хеша в адресной строке. Используется для обработки кликов по ссылкам.

Метод pickUpLinks()

Инициализирует прослушивание кликов по глобальному DOM-элементу body, делегирует событие только на тег a, на который было произведено нажатие.

Настройки класса Router

Создание и настройка экземпляра класса производится в отдельном файле ./js/Router/Router.config.js Здесь определен корневой элемент приложения, он имеет идентификатор app, внутри которого будет отображаться весь контент, обрабатываемый нашим маршрутиризатором. Так же, реализовано два метода: appBlock.makeEmpty - очищает корневой элемент приложения от всех потомков и appBlock.renderPage(domElements) - принимает на себя DOM-элементы, которые необходимо отрендерить внутри блока приложения, предварительно очистив его.

Инициализирована глобальная переменная window.howMuchProductsShow, которая, по умолчанию, равна 6 и служит для определения количества товаров, выводимых при рендеринге на странице.

Настройка самого экземпляра класса Router происходит после наступления события DOMContentLoaded, чтобы всё DOM-дерево было успешно сформировано и все скрипты подключены к документу.

По умолчанию, при заходе на сайт, роутер перенаправляет на страницу /page/1, где выводится первая часть товаров. Товары загружаются в JSON формате с удалённого сервера. Сервер настроен таким образом, что при обращении к адресу http://localhost:3000/api/v1/goods мы получим не больше 6 товаров.

Мы можем передать следующие параметры в строке запроса:

  • limit - ограничивает кол-во отдаваемых товаров за 1 запрос
  • offset - показывает, сколько нужно пропустить товаров от начала списка
  • count - возвращает только общее кол-во товаров (необходимо для пагинации)
  • search - осуществляет поиск по переданным словам (не по запросу целиком, а по каждому переданному слову)

Обрабатываются следующие виды адресов:

  • /goods/:goodId, где goodId - числовой идентификатор товара. Служит для просмотра конкретных товаров
  • /cart/ - отображает корзину товаров
  • /page/:pageNum, где pageNum - числовой идентификатор страницы. Служит, как параметр offset, при обращении к API и нужен для пагинации (если она требуется)
  • /search/ - для отображения результатов поиска

Каждый роут добавлен с помощью метода add() и использует Шаблонизатор.

Все используемые URL вынесены в отдельный файл ./js/Router/Router.urls.js

Шаблонизация страниц

Идея создания шаблонизатора пришла сразу, т.к. использование DOM API в классическом его представлении превращается в тяжелочитаемый и трудноподдерживаемый код. Пропадает удобство использования и усложняется масштабируемость проектов.

Класс TemplateEngine

Файл ./js/TemplateEngine/TemplateEngine.js

Конструктор класса принимает 1 аргумент, путь до папки с схемами шаблонов, и создаёт экземпляр с следующими полями:

  • schemasFolder
  • schemas

Обладает следующими методами:

  • addSchema(schemaObject)
  • renderPage(schema, renderedData)
  • checkVariable(partOfContent)
  • parseVariable(partOfContent)
  • pickUpDataFromVariable(partOfContent)
  • checkCondition(partOfContent)
  • parseCondition(partOfContent)
  • fulfillCondition(condition, dataSource)
  • renderPagination(itemsCount, currentPage)

Требования к JSON-схемам

К JSON-схемам есть требования:

  1. Корневой элемент должен быть объектом
  2. Названия тегов передаются в виде строки в поле tagName
  3. Названия классов передаются либо строкой, либо массивом строк в поле className
  4. Атрибуты передаются в виде объекта, где название поля - имя атрибута, а значение - его значение, передаются в поле attributes
  5. Если в теге должен быть id, то он передаётся в виде строки отдельным полем id
  6. Контент тега передаётся в поле content в виде строки, тогда будет подставлен, как textNode, либо в виде объекта, либо в виде массива строк/объектов. Вложенные друг в друга теги передаются объектом в поле content

Метод addSchema()

При создании экземпляра класса TemplateEngine я указываю путь до JSON-схем с описанием наших страничек сайта, чтобы сократить урлы при добавлении каждой схемы. После создания экземпляра класса, нужно добавить объекты схем с двумя полями:

  1. name
  2. url

Это производится методом addSchema() в файле ./js/TemplateEngine/TemplateEngine.config.js. После добавления схем, из названия и пути к ним хранятся в самом экземпляре класса.

Метод renderPage()

Основным методом данного класса является renderPage(), который принимает 2 аргумента - схему и данные, которые надо шаблонизировать. Сам метод является рекурсивным и 1 итерация метода возвращает 1 DOM-элемент с настроенными css-классами, атрибутами и переданным контентом. Рекурсивно вызывается, если в поле content схемы передан объект, массив, условие или обрабатываемый элемент является repeater (об этом ниже)

Метод checkVariable()

Такой способ добавления и редактирования страниц сайта, определенно, удобнее, нежели описывать "в лоб" все DOM-элементы, но ему не хватает гибкости. Поэтому, я добавил возможность передачи переменных внутри наших JSON-схем!

Метод checkVariable() как раз ищет строки, вида <%some.variable%> и возвращает булевое значение, в зависимости от наличия подобной строки в переданном аргументе. Принимает 1 аргумент - строку, где производится поиск переменной.

Метод parseVariable()

Метод вызывается сразу после checkVariable() и возвращает очищенную от лишних символов строку с названием переменной. Принимает 1 аргумент - строку, откуда вырезается название переменной.

Метод pickUpDataFromVariable(templateVariable, dataSource)

Принимает название найденной переменной (templateVariable) и источник данных (dataSource), где необходимо произвести поиск данных, возвращает либо найденные данные, либо ничего.

Возможные шаблоны переменных:

  • field.deeperField - определяется по наличию точки в шаблоне, указывает на то, что необходимая переменная находится в поле dataSource.field.deeperField
  • field.deeperField*multiplier - определяется по наличию И точки в шаблоне И звёздочки. Находит переменную до звёздочки и умножает на значение переменной после звёздочки. Используется в корзине, где цена товара умножается на кол-во
  • someSting+variable - определяется по знаку "+" в шаблоне переменной, берет левую часть и подставляет её перед значением найденной переменной. Используется, чтобы формировать URL'ы к картинкам и ссылкам
  • arrayName:index - определяется по знаку ":" в шаблоне и указывает на порядковый номер элемента в предполагаемом массиве, находящемся в поле dataSource.arrayName
  • someSting+arrayName:index - комбинирует предыдущие два шаблона, используется, чтобы выбрать порядковый номер в массиве изображений и сформировать урл
  • condition=result - определяется по знаку "=" в шаблоне переменной, проверяет наличие поля dataSource.condition, если оно есть, возвращает правую часть из шаблона переменной - result
  • varName отсутствуют какие-либо знаки, проверяет наличие поля dataSource.varName, если оно есть, вернёт его значение

Метод checkCondition()

Принимает строку и проверяет её на наличие слова if в начале. Если оно есть, вернёт true, если нет - false. Ожидает строку вида <%if(!!condition)%>

Метод parseCondition()

Убирает лишние символы, возвращает вырезанную строку из круглых скобок строки

Метод fulfillCondition(condition, dataSource)

Принимает 2 аргумента, строку с условием condition и источник данных dataSource. Выполняет проверку на одно из возможных условий:

  • !!variable - произведёт двойное логическое отрицание и вернёт истину, если поле dataSource.variable существует, или ложь
  • varOne==varTwo - произведёт сравнение переменных с приведением типов данных, вернёт результат сравнения

Метод renderPagination(itemsCount, currentPage)

Отвечает за вывод пагинации на сайте, принимает 2 аргумента: itemsCount - общее кол-во элементов, currentPage - номер текущей страницы. Использует 1 глобальную переменную - window.howMuchProductsShow, которая создаётся в файле-настройках Роутера ./js/Router/Router.config.js. Возвращает сформированный элемент пагинации по страницам, вида page/2.

Объект repeater

Такой объект описывает схему повторяющихся данных, получаемых из входного массива данных (список товаров, товары в корзине, список найденных товаров). Чтобы указать на наличие такого объекта, необходимо в схеме добавить поле repeater со значением true, тогда данные из поля content данного элемента будут применимы ко всем элементам в входных данных при инициализации функции renderPage()

Может использоваться определенное кол-во раз, если добавить свойство поле repeaterCount с указанием кол-ва повторений (может использовать переменные (фактически, на сайте используется для вывода звёздочек рейтинга))

Условные блоки if...else

Инициализируется в поле content строкой вида <%if(!!price.saleCost)%>. При этом, можно опустить все остальные поля, например, название тега, css-классы, атрибуты, т.к. после прохождения проверки, отрендерится либо поле if данного элемента, либо поле else

Реализация MVC

В проекте используется реализация паттерна MVC. К Модели относится вся Работа с API сервера, View и Controller находятся в папках с компонентами.

Работа с API

Работа с серверным API полностью находится внутри файлов в директории ./js/ServerAPI/*.js:

  • ApiUrls.js - собраны все необходимые URL для взаимодействия с REST API и WebSocket серверами.
  • ApiConfig.js - хранит в себе различные настройки, в рамках данного проекта, это настройки разных методов запросов к серверу, их хедеры и т.п.
  • CartApi.js - содержит все функции обращения к серверу, управляющие функционалом корзины.

Функционал корзины

Корзина реализована с помощью получения cookie, получаемых при обращении к серверу. Сервер запоминает сессию пользователя и обрабатывает сохранённые в Базе Данных JSON-объекты, относящиеся к тому или иному пользователю.

Для работы корзины, в файле CartApi.js реализованы следующие функции:

  • addProduct()
  • updateProductItemQuantity()
  • deleteProduct()
  • updateCartCount()

Функция addProduct()

Принимает 1 аргумент - id товара и отправляет POST запрос на сервер, в котором передаёт в JSON-строке объект с единственным полем '_id' и значением - id этого товара.

Функция updateProductItemQuantity()

Принимает 2 аргумента - id товара и настоящее количество данного товара. Делает PUT запрос на сервер, возвращает промис, ожидает обновленное количество данного товара.

Функция deleteProduct()

Принимает 1 аргумент - id товара и отправляет DELETE запрос на сервер, удаляя указанный товар. Возвращает промис.

Функция updateCartCount()

Запрашивает на сервере общее количество товаров в корзине пользователя. Возвращает промис, который ожидает получение JSON-объекта, содержащего поле count, где указано общее кол-во товаров.

Компоненты

Некоторые управляемые элементы настраиваются с помощью классов, отвечающих на View и Controller. Их функционал вынесен в отдельные папки и файлы и все они находятся в папке ./js/Components/. Структура директории такова:

Components
├── Cart
│   └── CartController.js
│   └── AddToCartBtn.js
│   └── AddToCartBtnClickHandler.js
│   └── CartItem.js
│   └── CartItemDeleteBtnHandler.js
│   └── CartItemUpdateAmountHandler.js
│   └── CartProductsCounter.js
│   └── CartProductCounterInitialization.js
├── SearchPanel
│   └── SearchPanelController.js
│   └── SearchPanel.js
│   └── SearchPanelValidationHandler.js
│   └── MakeSearchREquestHandler.js
└── ShowProductsBy
    └── ShowProductsByController.js
    └── ShowProductsByChangeHandler.js
  • CartController.js - контроллер, объединяет представление кнопок "Добавить в корзину", счётчиков товаров в корзине и карточек товаров при просмотре корзины и их функционала

  • AddToCartBtn.js - представление кнопки добавления товара в корзину.

  • AddToCartBtnClickHandler.js - обработчик кликов по кнопке.

  • CartItem.js - представление карточки товара в корзине.

  • CartItemDeleteBtnHandler.js - обработчик кликов по кнопке "Удалить товар".

  • CartItemUpdateAmountHandler.js - обработчик изменений количества определенного товара.

  • CartProductsCounter.js - представление счётчиков товаров в корзине.

  • CartProductCounterInitialization.js - инициализатор связки представления и контроллера.

  • SearchPanelController.js - контроллер, в котором заключается вся логика работы с поисковой строкой, валидация введённых данных и обработка событий

  • SearchPanel.js - представление панели поиска по сайту.

  • SearchPanelValidationHandler.js - валидация поля ввода поискового запроса.

  • MakeSearchREquestHandler.js - обработчик событий клика на кнопку "искать" и клавишу "Enter".

  • ShowProductsBy.js - представление лимита отображаемых товаров на странице.

  • ShowProductsByChangeHandler.js - обработчик, меняющий глобальную переменную, отвечающую за лимит выводимых товаров и обрабатывающий события предствления

Каждый компонент инициализируется путём передачи его в конструктор класса в объекте с полем rootElement в класс представления и назначает внутри себя обработку различных событий. Обработчики событий служат для связи представления и контроллера.

Контроллер Корзины товароа

Класс CartController

В конструкторе создаёт пустой массив в поле productsCounters, для хранения счётчиков общего количества товаров в корзине и служит для связки с API Backend части приложения.

Содержит следующие функции:

  • addProduct(productId) - принимает 1 аргумент - ID товара и возвращает промис - результат выполнения запроса к API на добавление товара.
  • updateProductCounters() - делает запрос к API, получая обновлённое кол-во товаров в корзине и делает обход сохраннёных элементов представления счётчиков товаров в корзине, обновляя их.
  • deleteCartItem(productId) - принимает 1 аргумент - ID товара и возвращает промис - результат выполнения запроса к API на удаление товара.
  • updateCartItemAmount(productId, newQuantity) - принимает 2 аргумента - ID товара и новое количество товара. Возвращает промис - результат выполнения запроса к API на обновление количества товара.

Представление Кнопки добавления товара в корзину

Класс AddToCartBtn

В конструкторе создаёт поле addBtn, куда сохраняет переданный DOM-элемент кнопки, поле controller, куда сохраняет переданные объект контроллера кнопки, поле addProductHandler, куда сохраняет переданный обработчик кликов по кнопке, создаёт поле productId из дата-атрибута сохранённого ранее поля addBtn и навешивает обработчиком события по DOM-элементу кнопки функцию из внутреннего поля addProductHandler.

Метод changeState принимает 1 аргумент - название нового состояние (строку) и добавляет/удаляет соответствующие классы к DOM-элементу кнопки. Список состояний:

  • tryingToAdd - вызывается при попытке добавить товар, добавляет css-класс in-process к DOM-элементу кнопки.
  • itAdd - вызывается при успешном добавлении товара, добавляет css-класс it-add к DOM-элементу кнопки и запускает отложенный вызов метода класса cleanStateClasses, который очищает css-классы состояний.
  • addIsFailed - вызывается при неудачной попытке добавить товар, добавляет css-класс it-failed к DOM-элементу кнопки. Работает аналогично с предыдущим состоянием.

Метод cleanStateClasses - удаляет css-классы состояния кнопки с DOM-элемента кнопки. Работает в процедурном стиле.

Обработчик кликов по кнопке, функция addProductHandler

Принимает 2 аргумента: wrapper и controller, внутри себя извлекает из wrapper поле productId и возвращает функцию, принимающую 1 аргумент - объект события, внутри которой обращается к методам wrapper и controller. Из методов wrapper используется метод changeState, а из методов controller - addProduct, куда передаётся ID продукта и метод updateProductCounters. При вызове меняет состояния кнопки на tryingToAdd. Это состояние добавляет к кнопке css-класс in-process, который добавляет в html-код кнопки прелодер. При успешном ответе вызывает внутреннюю функцию контроллера - updateCounters и меняет состояние на itAdd, при возникновении ошибки, меняет состояние на addIsFailed.

Карточка товара в корзине

Класс CartItem

В конструкторе создаёт поля:

  • cartItem, куда сохраняет DOM-элемент представления карточки товара.
  • deleteCartItemHandler - обработчик кликов по кнопке "удалить товар".
  • updateCartItemAmountHandler - обработчик изменения поля с количеством товара.
  • deleteBtn - DOM-элемент кнопки "удалить" в карточке товара.
  • quantityFeild - DOM-элемент поля "количество товара" в карточке.

Имеет 6 методов:

  • get productId() - геттер - возвращает ID товара.
  • get priceField() - геттер - возвращает DOM-элемент - поле с ценой товара.
  • get amountResultField() - геттер - возвращает DOM-элемент - поле с результатом вычисления стоимости товара.
  • deleteItem() - удаляет элемент со страницы.
  • setValueToOne() - устанавливает значение количества товара на 1.
  • renderNewAmount(newQuantity) - рендерит общую цену товаров в зависимости от их количества

Обработчик кликов по кнопке "удалить товар", функция deleteCartItem

Вызывает метод контроллера deleteCartItem и передаёт туда ID товара. При успешном выполнении вызывает метод deleteItem обёртки и метод updateProductCounters контроллера. При неудачном ответе выводит ошибку в консоль.

Обработчик изменения значения в поле "количество товара", функция updateCartItemAmount

Делает проверку нового количества товаров. Если оно меньше 1, то вызывает метод обёртки setValueToOne, иначе вызывает метод контроллера updateCartItemAmount, обновляя обновляя количество товара в корзине. При удачном выполнении вызывает метод обёртки renderNewAmount, куда передаёт обновленное количество товара, и вызывает метод контроллера updateProductCounters, обновляя счётчики товаров на сайте. При неудачном выполнении выводит ошибку в консоль.

Счётчик товаров в корзине

Класс CartProductsCounter

В конструкторе создаёт поле cartCounter, куда сохраняет DOM-элемент представления и вызывает переданный метод-инициализатор счётчика.

Имеет 1 метод - updateCounter, который принимает 1 аргумент - новое количество товаров в счётчике и обновляет представление.

Инициализатор контроллера на счётчиках, функция counterUpdateHandler

Вызывает два метода контроллера - addProductsCounter, куда передаёт сам счётчик, и метод updateProductCounters, обновляя все присутствующие счётчики.

Контроллер Поиска по сайту

Класс SearchPanelController

Содержит 1 метод:

  • goToSearchPage - принимает 1 аргумент - поисковой запрос, внутри себя вызывает метод экземпляра класса Router - navigate, куда передаёт подготовленную строку запроса.`

Поиск по сайту

Класс SearchPanel

Класс инициализируется на родительском элементе, имеющем класс .search-panel, внутри класса создаются поля:

  • rootElement - DOM-элемент, на котором производится инициализация класса.
  • validateSearchPanelHandler - функция-валидатор.
  • makeSearchRequestHandler - функция-обработчик поисковых запросов.
  • inputField - поле ввода запроса.
  • searchBtn - кнопка выполнения запроса.

Назначаетна поле ввода обработку событий input, валидацию на поле, и keypress, обрабатывая поиск по нажатию на клавишу Enter. Так же, назначает обработку события click на элементе searchBtn.

Класс содержит следующие методы:

  • get enteredData - геттер - возвращает значение из поля ввода.
  • get isValid - геттер - проверяет введённые данные по заданным правилам, возвращает логическое значение.
  • clearValidation - удаляет css-классы, отвечающие за валидность поля.
  • setInvalid - задаёт классы, устанавливая поле не валидным.
  • setValid - задаёт классы, устанавливая поле валидным.

Для осуществления поискового запроса, поле поиска должно быть валидным. Оно считается не валидным если длина введённого значения меньше 4 символов. Заполненное поле пробелами так же считается не валидным.

При правильном заполнении, функция не производит запроса к серверу, она перенаправляет нас на роут /search с переданным в неё текстом запроса. А роут уже сам обращается к серверу за нужными данными.

Валидатор формы, функция validateSearchPanel

Проверяет заполнение формы, если форма пустая и не содержит пробелов, вызывает метод обёртки clearValidation, вызывает метод обёртки isValid, если он возвращает истину, то вызывает метод обёртки setInvalid, если ложь, то setValid

Обработчик кликов и нажатия клавиши Enter, функция makeSearchRequest

Выполняет проверку типа события и нажатой клавиши, если тип события keypress и нажатая клавиша отличается от Enter, прекращает своё действие. Иначе вызывает метод обёртки isValid, если он возвращает истину, то функция вызывает метод контроллера goToSearchPage, очищает введённые данные в поле поиска и очищает css-классы валидации с поля поискового запроса.

Лимит отбражаемых товаров

Класс ShowProductsByList

Популярный функционал интернет-магазинов, когда на каждой странице пагинации отображается нужное кол-во товаров. В нашем случае, их может быть 6, 9 или 12

В конструкторе класса сохраняет в поле optionsList DOM-элемент представления и обработчик события change и сразу же назначает на сохраненное поле обработку события.

Обработчик изменения лимита отбражаемых товаров

Сохраняет новое значение из выпадающего поля, сохраняет его в глобальной переменной window.howMuchProductsShow и вызывает метод check экземпляра класса Router.

Чат с администратором магазина

Для организации чата администратора с посетителями, мною был настроен WebSocket-сервер, который раздаёт пользователям порядковые номера (иммитация id) и уже по ним определяет, от кого куда нужно доставлять сообщения. Пользователь может написать администратору или получить от него сообщение. А администратор может только отвечать всем пользователям, которые написали в чат. До этого, администратор не может свободно писать пользователям.

WebSocket-сервер принимает и отправляет кастомные объекты, в которых могут быть поля:

  • eventId - обязательное, определяет тип события. Строка
  • setIsAdmin - устанавливает данного пользователя, как администратора
  • message - сообщение
  • targetId - используется администратором для идентификации получателя сообщения

С клиента на сервер мы можем отправить следующие события:

  • setIsAdmin - устанавливает администратора
  • newMessage - новое сообщение
  • startTyping - пользователь пишет
  • stopTyping - пользователь прекратил писать

А с сервера могут прийти такие события:

  • incomingMessage - входящее сообщение
  • closeСonnection - закрытие соединения

Реализация находится в файлах:

  • ./js/Chat/ShopChat.js - кастомные методы
  • ./js/Chat/ChatClientSide - реализация чата для пользователей
  • ./js/Chat/ChatAdminSide - реализация чата для администраторов

Расширение прототипа класса WebSocket

При работе, мною сперва был создан собственный класс ShopChat, который должен был наследовать все методы и свойства от класса WebSocket, но он возвращал экземпляры класса WebSocket. Поэтому, мною было принято решение добавить методы в класс WebSocket.

  • sendStringify(data) - использует встроенный в WebSocket метод send(), передаёт в него JSON-строку из, созданную из переданного аргумента.
  • setIsAdmin() - отправляет на сервер готовый объект с нужным кастомынм идентификатором события. Не принимает аргументов и ничего не возвращает. Сервер сам установит нужное соединение в состояние setIsAdmin
  • sendMessage(message, targetId) - Принимает два аргумента - текст сообщения и целевой id(не обязательно). Формирует и отправляет на сервер подготовленный объект.
  • startTyping(targetId) - отправляет на сервер готовый объект события startTyping. Принимает 1 аргумент - id пользователя
  • stopTyping(targetId) - отправляет на сервер готовый объект события stopTyping. Принимает 1 аргумент - id пользователя

Прочие функции

  • preloader.show()/hide() - отвечает за отображение и скрытие прелодера, методы добавлены в сам DOM-элемент