0

Интернет магазин на javascript

Однажды пришла мне в голову безумная идея написать серию постов про различные подходы к организации javascript-кода. Такая мысль образовалась, когда по рабочей надобности изучал React.js и возрадовался от некоторых идей, заложенных его авторами. Захотелось потрогать его побольше, а потому как писать хеллоуворды из документации скучно, нужна была какая-то идея. Раз уж я начал вести блог на тему веб-разработки, то почему бы не создать простое, но более-менее внятное приложение с применением различных библиотек и фреймворков? И не только реакта, а любых других, до которых доберется дурной и воспаленный ум. В качестве подопытного приложения я возьму простенький интернет-магазин с каталогом и корзиной. Фишка будет в том, что код и каталога, и корзины будет написан на javascript. Корзина на фронте – не самое удачное решение для реальных проектов, но для небольших сайтов и в качестве изучения подойдет неплохо. Для изучения React понадобится сколько-то времени, поэтому для начала развлеку вас статьей, где опишу процесс создания нашего приложения без использования библиотек и фреймворков, но с использованием яваскрипт-модулей. Главная моя цель – это показать различные подходы к созданию приложений на javascript. С версткой заморачиваться сильно не буду, сверстаю на bootstrap, основной упор сделаю на javascript-код. Манипулировать DOM будем всем знакомым добрым jquery. Также подключим underscore для работы с данными и html-шаблонами. Данные для каталога загрузим из внешнего json-файла, а корзину будем хранить в localStorage. Итак, начнем.

Идея приложения и схема работы.

Итак, интернет-магазин на фронте. Что мы хотим от него? Хотим, чтобы была возможность как минимум вывести каталог товаров, реализовать добавление их в корзину, просматривать оную корзину, менять количество товаров в ней и удалять ошибочно добавленные с пересчетом стоимости заказа. Плюс добавим хранение данных в localStorage, дабы после перезагрузки страницы наша корзина не ушла в небытие.

Обращаю внимание, что это не одностраничное приложение. Мы создадим 2 html-страницы, каталог и корзина, но они будут служить только каркасом с основному коду. Основной код – это все-таки javascript.

Функционал подробнее.

Главное меню – 2 кнопки, каталог и корзина. В меню рядом с надписью "корзина" показывается выбранных количество товаров. Страница index – главная страница магазина, она же каталог. Подгружаем товары их из внешнего json-файла. Товары имеют поля: id, name, price, img. У каждого товара есть кнопка "Добавить в корзину". Список товаров храним в localStorage (id, name, count, price). Корзина – таблица с выбранными товарами. Выводим id и название товара, его количество и сумму. Под таблицей показываем общую сумму всех товаров. При изменении количества товаров и его удалении меняем все связанные данные. Каталог и корзину мы оформим в виде отдельных модулей.

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

Структура файлов

В корне проекта разместим 2 файла: index.html (каталог) и cart.html (корзина). И несколько папок: css, там лежит bootstrap.min.css и main.css – наши стили. Папка data содержит один файл goods.json – наши товары. fonts хранит шрифты от bootstrap-иконок. img – картинки товаров и гифку loading, которую мы будем показывать посетителям сайта, пока грузится каталог и корзина. Папка js разделена на 2 подпапки: vendor и modules. vendor содержит нужные нам библиотеки: jquery и underscore, а modules – модули нашего приложения. Их всего 3: main.js отвечает за инициализацию приложения, catalog – за вывод товаров, cart – за работу корзины.

Небольшое отступление: я намеренно не стал вводить лишнюю логику для нашего проекта. Хорошей практикой считается разработка с подходом MVC, разделение кода на модели, контроллеры и представления и разбиение этих частей на отдельные файлы для наглядности. Можно долго спорить на тему правильности применения такого подхода для абсолютно всех проектов, но в нашем конкретном примере мы отойдем от этого постулата и весь код для управления корзиной разместим в одном файле, а уже внутри него логически отделим работу с данными от разметки.

Приступаем к разработке.

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

Создаем разметку.

Создадим в корне нашего проекта файлы index.html и cart.html. Каждый файл – стандартная html5-заготовка. В блоке head подключим шрифт Ubuntu с Google Fonts и 2 css-файла: bootstrap.min.css и наши собственные стили main.css. В конце страницы, перед закрывающим тегом body, подключим скрипты в таком порядке: Разметка для index.html

Из интересного: мы указываем в data-page тега body название страницы и элемент с . При загрузке страницы в список поместим нашу гифку loading, чтобы пользователи не скучали, ожидая загрузки каталога.

Разметка для cart.html Здесь тоже нет ничего особенного: заготовка таблицы корзины, надпись "итого" и кнопка заказа (ее функционал мы реализовывать не будем). Содержимое корзины будет выводиться в tbody , сейчас на этом месте уже знакомая гифка.

На заметку: соглашения по html и css-коду.

В верстке я всегда стараюсь не забывать несколько моментов и рекомендую их Вам.
Первое: верстаем всегда на классах и только на классах. Никаких айдишников в css. id нужны для яваскрипт-кода – для быстрого получения доступа к элементу DOM.
И второе: если в js-коде нам нужно обращаться к множеству элементов (много кнопок добавления в корзину), то навешиваем элементам классы с префиксом "js-". И не используем эти "js-ы" в css-коде. Сначала эти соглашения кажутся излишними, но понимание плюсов быстро проходит с ростом проекта. Лучше сразу привыкать к подобному стилю, тем более, что его используют достаточно много разработчиков.

Читайте также:  Запрос пароля при выходе из спящего режима

Готовим данные и разметку для каталога

Для начала создадим файл для хранения наших товаров: data/goods.json Как видим, это обычный json-массив с четырьмя нужными нам полями. Теперь переходим к созданию каталога. Но прежде чем приступить к написанию js-кода, нам придется написать еще немного разметки для отображения отдельного товара. Мы будем использовать шаблоны html-кода библиотеки underscore для динамической генерации отдельных товаров. Если Вы не знакомы с шаблонами underscore, то у меня есть статья на эту тему. Я же не буду зацикливаться на этом, а просто приведу код шаблона, тем более, что он достаточно тривиален и не требует долгих разбирательств: Что происходит в этом коде? underscore-шаблон представляет собой обычную строку, в которую подставляются нужные данные. Это неплохой способ отделить логику и данные от представления. Вся идея шаблонов в том, что мы не знаем, каким образом получены данные, но мы знаем, как их нужно отобразить. В нашем примере мы даем на вход массив товаров goods (из файла goods.json), перебираем все товары в цикле с помощью функции each библиотеки underscore и для каждого товара выводим свою разметку, подставляя в нужные места id товара, название, картинку и цену. Обратите внимание на дата-атрибуты у кнопки "Добавить в корзину", они будут использованы в дальнейшем. Приведенный код мы поместим в тело body файла index.html. Дальше мы увидим, как связать данные и наш underscore-шаблон.

Пишем js-модуль каталога

Код файла catalog.js будет очень коротким Здесь с помощью замыкания мы объявляем переменную-модуль catalog, пишем функцию init, которая вызывает самую интересную нам функцию _render и экспортируем init наружу, разрешая при этом вызывать catalog.init() из других модулей приложения. На самом деле можно обойтись и без лишней init-функции, но лучше всегда объявлять публичную функцию инициализации во всех модулях для единообразия. При этом функция _render начинается со знака _, чем мы показываем, что эта функция частная и не должна выходить за пределы модуля. Применяя такой подход, мы уже в коде модуля видим, что используется в других модулях, а что предназначено только для внутреннего пользования. Этакая инкапсуляция кода, как в ООП.

Разберем функцию _render. Сначала мы объявляем переменную template = _.template($(‘#catalog-template’).html()).
_.template – это функция underscore, которая рендерит html-разметку, используя шаблон html и данные для него.
$(‘#catalog-template’).html() – содержимое шаблона, т.е. просто строка. $.getJSON получает данные из внешнего файла и передает наш массив товаров в колбэк. Выражение template() означает, что мы передаем функции template объект с данными и генерируем на их основе html-строку. Эта строка вставляется на страницу при помощи $goods.html(htmlString). Теперь вызов функции catalog.init() – это все, что нужно, чтобы загрузить каталог на страницу index.html

Модуль корзины

Наконец мы приступаем к самой интересной части нашего проекта – к корзине. Наш модуль будет разбит на 3 логические части:

  • 1 – логика работы с данными
  • 2 – работа с событиями DOM
  • 3 – общие функции инициализации и настройки

Вот так будет выглядеть заготовка модуля: Разберем нашу заготовку. Имеются 2 глобальных переменных для всего модуля: данные корзины и набор параметров. Этот набор содержит настройки модуля, такие как названия классов и дата-атрибутов, id элементов количества товаров и общей суммы заказа, классы для кнопок добавить, удалить, изменить количество и прочие. Подробнее рассмотрим ниже. Основная функция – инициализация модуля. Вне модуля будет вызываться только она, остальное скрыто в реализации корзины. Инициализируется модуль в таком порядке: назначаются опции модуля, получаем данные для корзины из localStorage, рендерим саму корзину, количество товаров и общую сумму заказа и напоследок привязываем обработчики к событиям DOM. Примечание: если Вы раньше не использовали такой подход, отделение данных от разметки, то удивитесь, насколько интереснее писать отдельно код обработки данных и после проще навешивать события на кнопочки. Дальше мы это продемонстрируем.

Вероятно, Вы обратили внимание, что я экспортирую наружу не только init, но и все функции, касающиеся обработки данных. Сделано это для того, чтобы облегчить тестирование кода. Каким образом проходят тесты? Есть 2 способа: ручной и unit-тесты. При тестировании руками мы в консоли браузера вызываем функции модуля и сразу же видим результат. Например, вызвав cart.add(), мы в консоли же можем сразу увидеть изменения и убедиться, что данные действительно добавлены (или что-то пошло не так). Мы отделили логику работы с данными от остальных функций и увидели, что так тестировать гораздо веселее, чем тыкать по кнопочкам и проверять содержимое localStorage после каждого клика. Второй способ будет напрашиваться сам собой, когда нам надоест набивать команды в консоли и мы наконец разберемся с unit-тестированием кода. Об этом я напишу в одной из ближайших статей.

UPDATED: Для интересующихся unit-тестированием опубликована статья unit-тесты на фронте или изучаем jasmine.js. В ней рассказывается, как тестировать код на примере нашей корзины с помощью популярной библиотеки jasmine.js.

Пишем функции обработки данных

Всего мы напишем 11 функций:

  • 1. updateData – обновляем данные из localStorage, записываем содержимое в переменную cartData
  • 2. getData – возвращаем данные
  • 3. saveData – сохраняем корзину в localStorage
  • 4. clearData – очищаем корзину
  • 5. getById – ищем элемент корзины по id товара
  • 6. add – добавляем товар в корзину
  • 7. remove – удаляем товар из корзины
  • 8. changeCount – меняем количество
  • 9. getCount – возвращаем число уникальных товаров корзины
  • 10. getCountAll – возвращаем число всех товаров корзины
  • 11. getSumma – возвращаем общую сумму заказа

Код всех функций достаточно простой, в несколько строк, поэтому привожу сразу все функции. При обработке используются методы underscore. Если Вы еще не знакомы с этой библиотекой, настоятельно рекомендую ее изучить. Не зря ее называют "швейцарским ножом для javascript-разработчика". Беглое изучение официальной документации (есть на русском языке) займет немного времени, а пользу принесет заметную.

Читайте также:  Для подключения компьютера к глобальной сети

Полный код работы с данными

Из интересного отмечу, что каждая функция возвращает какие-то данные: корзину целиком, добавленный товар или общую сумму. Не все возвращаемые данные используются в модуле, но все облегчают тестирование. Также видим, что в localStorage мы храним сериализованный массив товаров. В функции добавление предусмотрено условие: если мы добавляем товар, уже находящийся в корзине, то не создается дубликат товара, а увеличивается количество уже существующего. При уменьшении количества конкретного товара до нуля, оный удаляется из корзины.

Инициализация настроек. Настройки по умолчанию.

Прежде чем писать функции рендеринга и обработчиков событий, вернемся чуть назад и рассмотрим инициализацию настроек модуля. Функция _initOptions скопирует в переменную opts все настройки, переданные в модуль корзины при его создании. Сначала мы объявляем настройки по умолчанию, а затем "склеиваем" их с данными, пришедшими извне. Для небольшого приложения, как у нас, реализовывать возможность настройки модуля было не обязательно. Но это небольшое увеличение кода дает нам большую гибкость при переносе этого модуля в другой проект.

Рендер корзины и html-шаблон

Для начала создадим шаблон для отображения корзины и поместим его в секцию body файла cart.html. Здесь все знакомо по аналогичному фунционалу в каталоге. В дата-атрибуты помещаем id товаров, чтобы было понятно, с какими именно мы сейчас работаем. Атрибут data-delta показывает, увеличивать или уменьшать количество товара при клике на эту кнопку.

Функции рендеринга.

Операции с DOM традиционно тяжелые, поэтому рендерить корзину мы будем целиком, а вот при генерации количества и суммы – точечно обращаться к элементам.

Обработчики событий.

Приближаемся к завершению.

Напишем вспомогательную функцию получения элемента корзины по его ]’).

Всего у нас будет 4 обработчика-клика: добавление в корзину, изменение количества, удаление и оформление заказа. Смотрим:

На заметку. Код, касающийся рендеринга и навешивания событий, можно было изрядно уменьшить, применив такой нехитрый трюк. Объединяем код отрисовки корзины, количества и суммы в одну функцию render и вызываем ее после любых манипуляций с корзиной. То есть для любого события схема такая: вызвали нужный метод обработки данных – отрендерили ВСЕ. Тогда мы избавляемся от проверок вроде "а не удалился ли товар из корзины после уменьшения его количества" и вызова нескольких функций рендера вместо одной. Для небольших проектов это оправдано, но когда Ваше приложение будет содержать кучу виджетов, сложную верстку и данные будут поступать не только по клику пользователей, а еще и в режиме онлайн от разных источников, перерисовка всего содержимого страницы негативно скажется на производительности браузера. Поэтому даже в нашем простом случае я выбрал чуть усложненную, но более правильную схему.

Собираем модуль корзины в одно целое

Основной код уже написан, нам осталось только написать функцию инициализации корзины и привязки обработчиков событий. Пойдем от обратного, обработчики: Думаю, здесь без особых пояснений, собираем в кучу написанные ранее функции. Иницилизация: Почему мы ввели отдельные настройки renderCartOnInit и opts.renderMenuCartOnInit? Просто потому, что на странице каталога нам нужно инициализировать корзину (мы выводим количество добавленных товаров в меню), но не нужно ее рендерить. Чтобы не усложнять логику лишними проверками, мы разделили эти опции.

Полный код корзины

Код всей корзины мы уже рассмотрели в статье, но чтобы уложить это еще раз в голове, вот ссылка на файл cart.js – модуль целиком.

Главный модуль приложения

Вероятно, даже самый усидчивый читатель порядком устал от чтения букв, поэтому спешу завершить мой трактат.
Файл main.js – главный модуль, запуск приложения, царь-модуль. Идея такова: объявляем главный модуль, настройки приложения (в нашем случае дублируют опции по умолчанию), определяем текущую страницу и настройки модуля cart для текущей страницы и, наконец, инициализируем нужные модули. Приводит все это добро в действие волшебная строчка jQuery(document).ready(app.init);

Подводим итоги.

Итак, мы написали небольшое приложение простого интернет-магазина с каталогом и корзиной.

UPDATED: В связи с большой популярностью этой статьи и интересу к теме интернет-магазинов в целом запилена и опубликована статья-продолжение Реализация оформления заказа в интернет-магазине на клиенте и сервере. В ней рассмотрен полный цикл по сбору данных о клиенте, размещении формы заказа, отправки ajax-запроса на сервер, создание таблиц в базе данных и добавление этих самых данных, а также отправка писем с заказом. Как обычно, все с примерами. Исходники обновлены. Отправка заказа интегрирована с каталогом и корзиной, рассмотренными в этой статье. Это выглядит как цельное приложение, готовое к работе.
Еще одно обновление: готова статья про добавление способа доставки в интернет-магазин. Читайте здесь

UPDATED 2: Для тех, кому интересно, как сделать дерево с вложенными категориями для своего интернет-магазина, опубликована статья Строим дерево категорий на js, php и mysql. Там описывается интересная библиотека jstree и как достаточно просто сообразить каталог и на клиенте, и на сервере.

UPDATED 3: Для продолжающих интересоваться интернет-магазинами, начинается серия уроков на тему фильтров и сортировок в каталоге товаров. Точка входа здесь.

UPDATED 4: Еще новости по развитию магазина.
Новая статья Сравнение товаров в интернет-магазине.

Хотя главной целью была демонстрация модульного подхода при разработке на javascript и отделении логики от представления, все же модуль корзины получился вполне себе самодостаточным и независимым. При желании мы можем включать его в другие проекты. У нас есть только 2 зависимости модуля: jquery и underscore. Хотя полагаю, что люди, знакомые с обеими библиотеками, добавляют их практически в любой свой проект.

Многие моменты в нашем приложении достаточно спорны. Нет жесткого разделения логики и представления, эти функции объединены в один модуль. Также шаблоны underscore вшиты прямо в код страницы, что тоже не самая хорошая практика, нужно выносить их в отдельные файлы. Я намеренно не стал слишком усложнять структуру. В статье я рассмотрел пример создания кода с одной стороны достаточно модульного, чтобы его можно было удобно протестировать, поддерживать в дальнейшем или извлечь какие-то идеи для своих будущих приложений, но с другой стороны не настолько сложного, чтобы в нем нужно было слишком долго разбираться. В конце концов для серьезной javascript-разработки создаются библиотеки и фреймворки, и рано или поздно мы все приходим к пониманию, что их нужно знать и изучать. Backbone, Angular, Ember, React, существует их очень много и постоянно появляются новые. И чем больше мы будем изучать и узнавать различные подходы, тем шире будет наш кругозор и больше возможностей выбора.

Читайте также:  Достоинства и недостатки microsoft windows

В этом руководстве мы создадим корзину для сайта с использованием HTML и CSS3 . При этом мы применим Google Fonts ( шрифт « Roboto » ).

Шаг 1: Создадим HTML-структуру.

Сначала нужно создать контейнер div , который мы назовем « .shopping-cart ».

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

  • Кнопку « Удалить » и кнопку « Добавить в избранное »;
  • Изображение товара;
  • Название и описание товара;
  • Кнопки, с помощью которых можно задавать количество товара;
  • Итоговую цену.

Убедитесь, что при создании корзины для сайта на PHP вы подключили шрифт, который мы используем в этом руководстве. Теперь добавим основные стили для раздела body :

После этого создадим корзину с размерами 750 на 423 пикселя и зададим для нее стили. Обратите внимание, что мы используем flexbox , поэтому устанавливаем для свойства display значение flex , а для flex-direction – column . Потому что по умолчанию для flex-direction установлено значение row :

Теперь создадим первый элемент корзины для сайта на JavaScript , который будет названием товара. Для этого изменим значение высоты на 60 пикселей и зададим несколько основных стилей. Следующие три элемента — это товары в корзине. Для каждого из них мы установим высоту 120 пикселей и display: flex :

Мы задали основные стили. Теперь по порядку установим стили для товаров. Первыми элементами являются кнопки « Удалить » и « Добавить в избранное ».

Мне всегда нравилась анимация кнопок-сердечек Twitter . Я думаю, что она будет отлично смотреться в скрипте корзины для сайта:

Мы устанавливаем класс « is-active » при нажатии кнопки, чтобы анимировать ее с помощью jQuery , но об этом подробнее в следующем разделе:

Следующий элемент скрипта корзины для сайта HTML — это изображение товара, для которого нужно задать поле справа 50 пикселей:

Далее нужно задать стили для количества приобретаемого товара: кнопки для увеличения и уменьшения количества. Сначала мы создадим CSS корзины покупок для сайта, а затем заставим его работать с помощью JavaScript :

Полная стоимость товаров:

Также реализуем функцию адаптивности корзины для HTML сайта , добавив следующие строки кода:

Это все, что касается CSS .

JavaScript

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

Теперь заставим работать кнопки количества приобретаемого товара:

И это наша окончательная версия корзины товаров для сайта :

Руководство завершено! Надеюсь, оно вам понравилось, и вы узнали что-то новое. Если у вас есть вопросы, напишите об этом в комментариях!

Данная публикация представляет собой перевод статьи « How to Create a Shopping Cart UI using CSS & JavaScript » , подготовленной дружной командой проекта Интернет-технологии.ру

При разработке интернет-магазинов мне часто приходилось посредством javascript (а конкретнее его фреймворком — jQuery) проделывать некоторые повторяющиеся от проекта к проекту вещи. Например, добавление товаров в корзину, обновление информации о количестве товаров и общей сумме на странице, удаление товаров из корзины по одному, изменение их количества и полная очистка всех товаров из корзины. И все это, разумеется, без перезагрузки страницы.
И я пришел к выводу, что нужно писать код так, что бы потом его с легкостью можно было использовать в следующих проектах.

Итак пишем модуль для работы с корзиной интернет-магазина с использованием jQuery.
Оговорюсь, что я опубликую только javascript-код, серверную часть трогать не буду, обозначу так же, какими данными будет обмениваться клиент с сервером.

Определяемся с методами:

Инициализация:
Что нам нужно сделать в инициализации?
Необходимо переданные в метод init настройки, должным образом обработать (совместить с параметрами по умолчанию) и применить их, а так же повесить на элементы заданные в настройках соответствующие обработчики событий.
Определяемся с параметрами по умолчанию.

Смешиваем параметры указанные при инициализации с параметрами по умолчанию и применяем их

Делаем обновление блока с информацией о количестве товаров в корзине и общей сумме, вешаем обработчики событий на заданные элементы и возвращаем сущность объекта (может пригодится).

Добавление одного наименования товара в корзину в количестве 1шт.
Метод add, получает параметр context (элемент, на который произошел клик) и отправляет на сервер данные.

Получение информации о количестве товаров в корзине и общей сумме, обновление блока (метод get).
Отличе метода get от add в url на который отправляются данные и в параметрах (метод get не передает параметров).

Думаю, дабы осуществить повторное использование кода, можно добавить некоторый метод updateCart, в который будут переданы url и vars (параметры).

В свою очередь метод add изменится на такой.

Изменение количества товаров одного наименования при потере фокуса поля, соответствующего этому наименованию.

Сообщение о добавлении товара в корзину (в зависимости от типа alert или flash).

Теперь это все объединияем.
По этой ссылке можно посмотреть пример модуля,
а тут можно скачать его архивом.

Модуль будет дописываться и исправляться.

Спасибо за внимание. Жду критики, вопросов и замечаний.
Есть желание доработать это до некого API работы с интернет-магазинами.

admin

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *