Создаем корзину покупателя на чистом JavaScript и Local Storage

Вариантов создания корзины с использованием jQuery, на просторах интернета достаточно, но так как не все хотят подключать громоздкие библиотеки, особенно для каких-то разовых задач, я хочу показать вариант реализации на чистом JS. К тому же, хранить выбранные пользователем товары, мы будем не в cookie, а Local Storage (локальное хранилище). Эта технология поддерживается практически во всех современных браузерах и даже в IE8.

Буквально два слова о Local Storage для тех, кто с этим способом хранения данных на стороне клиента не знаком. Объем хранимой информации в LS по сравнению с cookie значительно выше: около 5Мб(!) против 4Кб. К тому же, в LS данные хранятся в зашифрованном виде. Однако, как и в cookie, так и в LocalStorage, мы можем записывать только строковые данные. Если нужно добавить массив или объект, то его можно предварительно преобразовать в JSON-строку (JSON.stringify(obj)), а после получения данных из LS - производим обратное преобразование (JSON.parse(json_string)). Работать с Local Storage не просто, а очень просто. Вот его основные методы:

localStorage.setItem('key', 'value');
Обновляет или создает новую запись с ключом "key" и строковым значением "value"
var lsData = localStorage.getItem('key');
Возвращает данные связанные с ключом "key" или "null", если записи с таким ключом не обнаружено
localStorage.removeItem('key');
Удаляет данные со связанным ключом "key"
localStorage.clear();
Удаляет все записи из Local Storage

Переходим к делу и для примера, создадим такую HTML-структуру для вывода товара:

<div class="item_box">
  <h3 class="item_title">Samsung Galaxy S10</h3>
  <p>Цена: <span class="item_price">20</span>$</p>
  <button class="add_item" data-id="7">Добавить в корзину</button>
</div>
<div class="item_box">
  <h3 class="item_title">LG Optimus G E100500</h3>
  <p>Цена: <span class="item_price">100</span>$</p>
  <button class="add_item" data-id="2">Добавить в корзину</button>
</div>
  <div class="item_box">
  <h3 class="item_title">Nokia 2110</h3>
  <p>Цена: <span class="item_price">1000</span>$</p>
  <button class="add_item" data-id="5">Добавить в корзину</button>
</div>
<button id="checkout">Оформить заказ</button>
<button id="clear_cart">Очистить корзину</button>
<div id="cart_content"></div>

Все необходимые данные, такие как наименование или цена товара, мы можем брать прямо из элементов страницы. Остается важная составляющая - ID товара, которую можно выводить в каком-нибудь атрибуте. Для таких целей, я предпочитаю атрибут data-*, который я уже упоминал в других статьях. Его-то и добавим в кнопку "Добавить в корзину" каждого из товаров.
Теперь в дело вступает JavaScript. Ничего сверхъестественного тут нет и большую часть, я прокомментирую прямо в коде:

var d = document,
    itemBox = d.querySelectorAll('.item_box'), // блок каждого товара
    cartCont = d.getElementById('cart_content'); // блок вывода данных корзины
// Функция кроссбраузерной установка обработчика событий
function addEvent(elem, type, handler){
  if(elem.addEventListener){
    elem.addEventListener(type, handler, false);
  } else {
    elem.attachEvent('on'+type, function(){ handler.call( elem ); });
  }
  return false;
}
// Получаем данные из LocalStorage
function getCartData(){
  return JSON.parse(localStorage.getItem('cart'));
}
// Записываем данные в LocalStorage
function setCartData(o){
  localStorage.setItem('cart', JSON.stringify(o));
  return false;
}
// Добавляем товар в корзину
function addToCart(e){
  this.disabled = true; // блокируем кнопку на время операции с корзиной
  var cartData = getCartData() || {}, // получаем данные корзины или создаём новый объект, если данных еще нет
      parentBox = this.parentNode, // родительский элемент кнопки "Добавить в корзину"
      itemId = this.getAttribute('data-id'), // ID товара
      itemTitle = parentBox.querySelector('.item_title').innerHTML, // название товара
      itemPrice = parentBox.querySelector('.item_price').innerHTML; // стоимость товара
  if(cartData.hasOwnProperty(itemId)){ // если такой товар уже в корзине, то добавляем +1 к его количеству
    cartData[itemId][2] += 1;
  } else { // если товара в корзине еще нет, то добавляем в объект
    cartData[itemId] = [itemTitle, itemPrice, 1];
  }
  if(!setCartData(cartData)){ // Обновляем данные в LocalStorage
    this.disabled = false; // разблокируем кнопку после обновления LS
  }
 return false;
}
// Устанавливаем обработчик события на каждую кнопку "Добавить в корзину"
for(var i = 0; i < itemBox.length; i++){
  addEvent(itemBox[i].querySelector('.add_item'), 'click', addToCart);
}
// Открываем корзину со списком добавленных товаров
function openCart(e){
  var cartData = getCartData(), // вытаскиваем все данные корзины
      totalItems = '';
  // если что-то в корзине уже есть, начинаем формировать данные для вывода
  if(cartData !== null){
    totalItems = '<table class="shopping_list"><tr><th>Наименование</th><th>Цена</th><th>Кол-во</th></tr>';
    for(var items in cartData){
      totalItems += '<tr>';
      for(var i = 0; i < cartData[items].length; i++){
        totalItems += '<td>' + cartData[items][i] + '</td>';
      }
      totalItems += '</tr>';
    }
    totalItems += '</table>';
    cartCont.innerHTML = totalItems;
  } else {
    // если в корзине пусто, то сигнализируем об этом
    cartCont.innerHTML = 'В корзине пусто!';
  }
  return false;
}
/* Открыть корзину */
addEvent(d.getElementById('checkout'), 'click', openCart);
/* Очистить корзину */
addEvent(d.getElementById('clear_cart'), 'click', function(e){
  localStorage.removeItem('cart');
  cartCont.innerHTML = 'Корзина очишена.';
});

Объект "cartData" собираем по следующей схеме: ключ к товару - его ID и данные в виде массиве [название_товара, цена_товара, количество_товара]. Если бы вы вывели такой объект средствами php, то получили бы примерно следующее:

stdClass Object (
  [2] => Array (
    [0] => LG Optimus G E100500
    [1] => 100
    [2] => 1
  )
  [7] => Array (
    [0] => Samsung Galaxy S10
    [1] => 20
    [2] => 2
  )
)

Это я показал, чтобы было понимание того, как потом можно работать с этими данными на стороне сервера. И плавно подошли к тому, как же эти данные отправить на сервер. В отличии от cookie, Local Storage работает только на стороне клиента. Кто-то может и записать это в минусы LS, но я не вижу проблемы, т.к. есть достаточно способов превратить минус в плюсы. Легко и непринужденно, мы можем отправить данные Ajax-запросом, а это гораздо приятней посетителю, т.к. его не перебрасывает на другую страницу, экономит время и трафик, что немаловажно, если пользователь зашёл с мобильного устройства или скорость подключения не такая высокая.


Как видите, нет ничего сложного и объем кода, без использования сторонних библиотек, получился совсем небольшим. Если кому-то нужно учитывать более старые версии Internet Explorer, то он может добавить cookie, как "fallback" к Local Storage. То есть, проверять в функциях "getCartData" и "setCartData" возможности браузера и, если он не поддерживает LS, то в качестве хранилища использовать Cookie, а остальной код останется без изменений.

Incode Pro logo

144 комментария

Страница 2 из 6  
anubissk 09.06.2016 12:24
@Incode, Я понимаю, что вопросы совсем тупые, помогите пожалуйста, совсем ступор из-за таблицы этой :(
Incode 09.06.2016 16:18
@anubissk, попробуйте задать класс "item_box" для <tr>, а не для <div>, как это сейчас у вас.
anubissk 14.06.2016 14:38
@Incode, Блин, застрял я на этой элементарщине и не могу понять где косяк :(
В этом варианте всё работает, но он мне не подходит, т.к. всё находится в оной ячейке:
Показать код

Пытаюсь распихать всё по ячейкам таблицы и присвоить class="item_box" для строки таблицы, но не работает. корзина перестаёт реагировать:
Показать код
anubissk 14.06.2016 15:10
@Incode,
Последнее сообщение не правильное.
присвоил класс "item_box" тегу <tr>
Показать код

Проверил, в таком варианте - работает. Но мне нужно разбить по ячейкам таблицы "описание" и "цена + кнопка" или "описание +цена" и "кнопка".
Например так
Показать код
Но после нажатия кнопки, кнопка ломается, становится не активной и в корзину ничего не падает. Ткните носом, что не так :(
anubissk 15.06.2016 12:44
@Incode, Разобрался, классы назначил тегам tr и td и переделал родительский элемент на : parentBox = this.parentNode; // родительский элемент кнопки &quot;Добавить в корзину&quot;
parentBox = parentBox.parentNode;
Теперь всё работает! :) Спасибо!
Гость 30.07.2016 16:35
Отлично, спасибо.
mcsweb 04.09.2016 20:55
Самая простая и самая функциональная корзина из всех, что я нашел в инетах.
Что удалось доработать:
- изменение количества товара
- добавил в таблицу ячейку с кнопкой удаления товара из чека.

Сейчас буду думать над функцией автоматического результата суммы всех товаров по их количеству.
Это довольно просто было.
Я не знаком с куками и операциями над ними.
Прошу автора добавить функцию удаления товара из куков при удалении из чека.
Incode 04.09.2016 21:47
@mcsweb, самая простая потому, что это не готовый продукт, а только пример, как можно корзину сделать самому )) По поводу удаления товаров, я уже давал в комментариях ответ, но т.к. в сотне комментов отыскать сложновато, то дублирую ссылку на пример (смотрите исходный код).
Rodion 08.11.2016 02:12
Добавил свое modal окно, не могу сделать возможность увеличения кол-во, помогите пожалуйста очень прошу.

b3a9fca6.png
Incode 08.11.2016 08:52
@Rodion, во-первых, я вам дал ответ по вопросу, связанному с плагином jqCart. Надеюсь, что поможет. А во-вторых, на какие-то глобальные доработки, у меня не будет времени как минимум до следующего лета. Тем более, что код в данной статье - это лишь наглядный пример того, как человек может сам сделать корзину, а не использовать его, как готовый продукт.
Гость 25.11.2016 20:34
как добавить новые товары?
Гость 25.11.2016 20:37
я просто не так давно начала в этом разбираться
RikitikitaVi 02.12.2016 11:59
каким образом можно отправить данные из cartData (именно сформированную табличку в totalItems), пробовал таким вот образом

<script language="javascript">
var tablet = cartData;
window.location.href = '../post.php?tablet'+tablet;

но не вышло.
в обработчике писал
echo $_GET['tablet']
Incode 02.12.2016 14:56
@RikitikitaVi, во-первых,
var tablet = getCartData();

и в переменной "tablet", если всё нормально, вы получите объект.
отправить данные
Отправить данные можно с помощью Ajax. Если принципиально важно, чтобы данные передавались с переходом на указанную страницу, то строку нужно предварительно подготовить. Например, так:
function serialize(obj, prefix) {
    var str = [],
        p;
    for (p in obj) {
        if (obj.hasOwnProperty(p)) {
            var k = prefix ? prefix + "[" + p + "]" : p,
                v = obj[p];
            str.push((v !== null && typeof v === "object") ?
                serialize(v, k) : encodeURIComponent(k) + "=" + encodeURIComponent(v));
        }
    }
    return str.join("&");
}
var tablet = getCartData();
location.href = '../post.php?' + serialize({tablet: tablet});
RikitikitaVi 02.12.2016 15:41
@Incode, спасибо. Я так понимаю для вызова будет достаточно
onclick="serialize()"
написать?
Incode 02.12.2016 16:10
@RikitikitaVi, нет. Если посмотреть внимательно, то в функцию "serialize" передаётся объект. Если вы хотите, к примеру, передавать данные по клику на какой-то кнопке, то схематически это будет выглядеть так:
function serialize(obj, prefix) {
    var str = [],
        p;
    for (p in obj) {
        if (obj.hasOwnProperty(p)) {
            var k = prefix ? prefix + "[" + p + "]" : p,
                v = obj[p];
            str.push((v !== null && typeof v === "object") ?
                serialize(v, k) : encodeURIComponent(k) + "=" + encodeURIComponent(v));
        }
    }
    return str.join("&");
}
document.getElementById('my_button').addEventListener('click', function() {
    var tablet = getCartData();
    location.href = '../post.php?' + serialize({
        tablet: tablet
    });
});
Только учтите, что в примере я использовал getElementById (поиск элемента по ID). Напоминаю, что на странице не может быть несколько элементов с одинаковым ID! Если нужно установить обработчик события по какому-то другому признаку или на группу элементов, то используйте querySelector и querySelectorAll
RikitikitaVi 02.12.2016 18:09
@Incode, большое спасибо за оперативность.
RikitikitaVi 02.12.2016 18:31
@Incode, еще один вопрос, вот имеется валидатор
Показать код



Когда делаю условие в
Показать код

То форма не отправляется, и очень печально(
Если допустим поменять условие, что elems == showError, то уже проверку проскакивает и переходит на страницу.
Я очень извиняюсь, я новичок, а на странице данного скрипта не отвечают в комментах. Буду вам благодарен премного, если вы сможете мне помочь.
RikitikitaVi 02.12.2016 18:38
@Incode, эта форма записывается на
type="button"

если впихнуть ее в
type="submit"

то будет проскакивать и переходить на страницу.
RikitikitaVi 03.12.2016 02:24
@Incode, с валидацией разобрался.) Еще раз извините:)
Гость 12.12.2016 10:18
А как сделать если несколько страниц с товарами, чтобы товары записывались в корзину с разных страниц сайта
Гость 20.03.2017 01:52
Спасибо. Пригодится, забираю
Гость 22.05.2017 09:40
Спасибо большое! Самая простая корзина, которую нашёл в рунете.
Но не могли бы Вы подсказать, как прикрутить в корзину кнопку удаления товара?
Incode 22.05.2017 11:38
как прикрутить в корзину кнопку удаления товара?
Уже делал подобный пример выше в комментариях.
Гость 26.05.2017 09:42
Уже делал подобный пример выше в комментариях.

Не увидел кнопку "все комментарии". Ещё раз большое спасибо! Всё получилось!
Страница 2 из 6  
Ваш комментарий:
X