Создаем корзину покупателя на чистом 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

155 комментариев

Страница 3 из 7  
dunakov 14.04.2016 15:36
Намудрил я беды в общем) Итак и сяк делал, не работает. А если работает, то только для 1 блока, остальные добавлять не желает. Не могли бы вы пожалуйста сделать для моего примера? Ну хотябы чтобы два блока работало, был LS, и табличный вывод данных. Бесится уже начинаю

Показать код
Incode 15.04.2016 09:49
@dunakov, внимательно пересмотрите код на предмет изменений.
Показать код
dunakov 25.04.2016 19:31
Просто по другому много чего реализовано в варианте, где радиобоксы работаю, но ладно попробую внимательно посмотреть как модифицировать мой вариант до варианта с работающими радиобоксами
dunakov 28.04.2016 19:21
Вроде бы разобрался, кое что добавил еще. Спасибо. Еще вопрос такой. Теперь хочу чтоли создать отдельную страничку. На которую будет заходить по клику, оформить заказ. Там нужно буддет ввести имя, фамилию, например способ оплаты выбрать, и способ доставки, а так же мыло. И чтобы оно либо на почту(Так прощу) Или на сервер(так сложнее) Отправляло данные. То что он выбрал в корзине+ его данные. Это реально сделать даже используя пхп?
Гость 25.05.2016 15:20
Привет! Столкнулся с проблемой: totalSum в 69 строке показывает NaN. Помогите, пожалуйста, исправить.

var d = document,
    itemBox = d.querySelectorAll('.item_box'), // блок каждого товара
    cartCont = d.getElementById('cart_content'); // блок вывода данных корзины
    cartContCount = d.getElementById('cart_content_count'); // блок вывода данных счетчика
// Функция кроссбраузерной установка обработчика событий
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][1] += 1;
  } else { // если товара в корзине еще нет, то добавляем в объект
    cartData[itemId] = [itemTitle, 1, itemPrice];
  }
  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 = '',
                        totalSum = 0,
                        totalCount = 0;
        // если что-то в корзине уже есть, начинаем формировать данные для вывода
        if(cartData !== null){
                totalItems = '<p><table class="shopping_list"><tr class="shopping_list_titles"><th>Наименование</th><th>Кол-во</th><th>Цена</th><th></th></tr>';
                for(var items in cartData){
                        totalItems += '<tr>';
                        for(var i = 0; i < cartData[items].length; i++){
                        
                        if ( i == 1 ) {
                                totalItems += '<td>' + cartData[items][i] + '</td>';
                                } else {
                                totalItems += '<td>' + cartData[items][i] + ' </td>';
                                };
                        }
                        totalCount += cartData[items][1];
                        totalSum += cartData[items][1] * cartData[items][2];
                        totalItems += '<td><button class="del_item" data-id="'+ items +'" onclick=""></button></td>';
                        totalItems += '</tr>';
                }
                totalItems += '<tr><th></th><th></th><th><strong id="total_sum">'+ totalSum +' ₽</strong></th></tr></table>';
                cartCont.innerHTML = totalItems;
				cartContCount.innerHTML = '<p class="cart-count">' + totalCount + '</p>';		
				
        } else {
                // если в корзине пусто, то сигнализируем об этом
                cartCont.innerHTML = 'В корзине пусто!';
        }
        return false;
}
addEvent(d.getElementById('clear_cart'), 'click', function(e){
  localStorage.removeItem('cart');
  cartCont.innerHTML = 'Корзина очищена.';
});
/* Открыть корзину */
window.onload = openCart();
setInterval( openCart, 1000 );
Incode 25.05.2016 15:59
totalSum в 69 строке показывает NaN
Для начала, выведите в консоль объект корзины "cartData" и просмотрите данные. Возможно, что к числовым значениям, примешались какие-то лишние символы.
 // при таком варианте
var a = '45';
var b = 'x78';
console.log(a * b); // даст NaN
anubissk 02.06.2016 17:27
Добрый день! Подскажите, пожалуйста,как сделать описание товара невидимым? Приживляю кновку купить в ячейку таблицы
class="item_title">Samsung Galaxy S10
Incode 02.06.2016 19:47
как сделать описание товара невидимым?

@anubissk, не понимаю ваш вопрос. Что значит "невидимым"? Оберните это название в какой-нибудь элемент, например, <span>. Задайте этому элементу класс со свойством "opacity: 0;" или "display: none;" и текст станет невидимым. В конце концов, если он там не нужен, то его можно и не выводить вовсе.
anubissk 07.06.2016 10:42
anubissk, не понимаю ваш вопрос. Что значит "невидимым"? Оберните это название в какой-нибудь элемент, например, <span>. Задайте этому элементу класс со свойством "opacity: 0;" или "display: none;" и текст станет невидимым. В конце концов, если он там не нужен, то его можно и не выводить во

На странице размещена таблица, в первой колонке номер детали, во второй название, в третьей хочу сделать цену и кнопку "в корзину".
Получается так,что описание у меня уже есть, но убрать значение "itemTitle" я не могу, т.е. тогда название не будет отображаться в корзине.
Прошу сильно не ругаться, я лет 10ть назад писал сайт в блокноте, а сейчас друг попросил помочь накидать ему простенькую страничку с корзиной. Технологии уже давно опередили мои познания :(
anubissk 07.06.2016 13:41
@Incode, Вот таблица куда вживляю кнопку
Показать код

Помогите, пожалуйста советом, как сделать это красиво. Пробовал указать "itemTitle"в третьей колонке, но не получается. Что-то не правильно я делаю
Incode 07.06.2016 14:42
@anubissk, вам нужно спрятать элемент с классом "item_title"? Тогда в CSS ему можно задать display: none;
P.S. Может попробуете мой плагин jqCart? Думаю, что будет проще добиться нужного вам результат (который я всё равно понять не могу), т.к. там данные нужно размещать в самой кнопке. А кнопка, в свою очередь, вообще не зависит от разметки и видимой информации для пользователя.
anubissk 07.06.2016 15:13
@Incode, я пытаюсь сделать вот так https://yadi.sk/i/Q4A0Y0XIsKP2U
Т.е. кнопку отдельно от описания.
От безысходности, уже думал на костылях сделать, типо "item_title" не отображаемым
Incode 07.06.2016 15:27
От безысходности
Это у меня от безысходности уже крыша едет :) Я объясняю, что не могу понять, суть вашей проблемы, а вы мне о том, как думали на костылях сделать. Вот вы показали скрин... Что вас там не устраивает, что хотите скрыть и почему скрыть это "что-то" у вас не получается?
anubissk 07.06.2016 16:12
@Incode, Можно анекдот в тему? :)
Показать код
:) Суть в том, что у меня кнопка есть, но не работает :( Как мне кажется, из-за того, что я не туда приживляю "item_box" в теле таблицы.
Вот как это прописано сейчас (конечно там не правильно всё :( )
Показать код
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
как добавить новые товары?
Страница 3 из 7  
Ваш комментарий:
X