Корзина покупателя на jQuery.

Плагин jqCart.

Когда писал предыдущую статейку на эту тему, которая преследовала цель всего лишь показать абстрактный пример реализации корзины покупателя, то не думал, что она породит такое количество вопросов и просьб, посыпавшихся и в самом блоге, и на мою почту, и т.д. Собственно, поэтому и решил написать этот плагинчик "jqCart", но хочу сразу подчеркнуть несколько моментов:

  1. Хоть код и оформлен в виде плагина, но плагином его можно назвать с большой натяжкой. Да и вообще, в этом направлении, на мой взгляд, сделать плагин полностью универсальным - достаточно сложно;
  2. Писа́лся плагин на скорую руку, поэтому достаточно сыроват, хотя и вполне рабочий;
  3. На данный момент, код не документированный;
  4. Планирую ли я его дорабатывать? Да, но при достаточном количестве свободного времени;

Итак, для работы плагина требуется библиотека jQuery >= 1.8, которая должна быть подключена до подключения самого плагина. Работать должно во всех современных браузера и, по идее, даже в IE8. Проблема для старых "осликов", может заключаться только в применяемых CSS-свойствах и версии jQuery (напомню, что jQuery 2.x - не поддерживает Internet Explorer 6, 7, и 8). Данные передаются на сервер с помощью Ajax и поэтому, я крайне рекомендую использовать кодировку для файлов UTF-8 без BOM!

Подключение:

<link href="css/jqcart.css" rel="stylesheet" type="text/css">
<script src="js/jquery-1.11.3.min.js"></script>
<script src="js/jqcart.min.js"></script>

Использование:

$(function() {
    'use strict';
    // инициализация плагина
    $.jqCart({
        buttons: '.add_item',        // селектор кнопок, аля "Добавить в корзину"
        handler: '/php/handler.php', // путь к обработчику
        visibleLabel: false,         // показывать/скрывать ярлык при пустой корзине (по умолчанию: false)
        openByAdding: false,         // автоматически открывать корзину при добавлении товара (по умолчанию: false)
        currency: '&euro;',          // валюта: строковое значение, мнемоники (по умолчанию "$")
        cartLabel: '.label-place'    /* селектор элемента, где будет размещен ярлык, 
                                        он же - "кнопка" для открытия корзины */
    });
    
    // дополнительные методы
    $.jqCart('openCart'); // открыть корзину
    $.jqCart('clearCart'); // очистить корзину
});

В кнопках ("Добавить в корзину"), должены быть прописаны следующие data-атрибуты:

  1. data-id - ID товара
  2. data-title - Наименование товара
  3. data-price - Цена товара
  4. data-img - URL фото товара (опционально)

Все значения вышеуказанных data-атрибутов, принимают участие при формировании окна корзины. Можно добавлять дополнительные data-атрибуты, значения которых будут отправлены с остальными данными в обработчик. Ключи в полученном массиве на сервере, будут соответствовать имени атрибута после "data-", т.е. значение атрибута "data-somevalue", будет в массиве под ключем "somevalue" Тег для кнопки и её расположение на странице - значения не имеет.

Пример:

<button class="add_item" data-id="7" data-title="Bugatti Veyron Super Sports" data-price="200" data-img="http://site.com/img/photo.png">Добавить в корзину</button>

В архиве найдете пример обработчика (handler.php) с подготовкой и отправкой письма на почту. В конце обработчика, обязательно должен быть ответ клиенту в формате JSON.

<?php
// какой-то код обработки заказа...

// Ответ на запрос
// !для версии PHP < 5.4, используйте традиционный синтаксис инициализации массивов array() вместо короткого []
$response = [
    'errors' => !$send_ok,
    'message' => $send_ok ? 'Заказ принят в обработку!' : 'Хьюстон! У нас проблемы!'
];
exit( json_encode($response) );

Где переменная $send_ok - булевое значение (true/false), в зависимости от результата обработки заказа. Если это будет отправка на почту, то можно так:

<?php
$send_ok = mail($to, $subject, $body, implode("\r\n", $headers));

Ахтунг! В обработчике я не делал фильтрацию данных, поэтому внимательно и тщательно их обрабатывайте перед использованием, а особенно, если вы собираетесь записывать данные в БД!

Версия 1.1.0

  • Данные распределены по разным data-атрибутам кнопки
  • Добавлена возможность отображения фото товара в корзине
  • Добавлен параметр currency для вывода в корзине валюты рядом со стоимостью и ценой
  • Добавлена возможность передавать на сервер дополнительные данные, которые берутся из пользовательских data-атрибутов кнопки

Версия 1.1.1

  • Добавлена опция visibleLabel. Показывать ярлык при пустой корзине
  • Добавлена опция openByAdding. Автоматически открывать корзину при добавлении товара
  • Добавлена кнопка вывода заказа на печать

Версия 1.1.2

  • Исправлен расчет сумм с плавающей точкой

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

Incode Pro logo

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

Страница 23 из 33  
infinitymd 16.01.2017 16:15
@altlogik, по поводу кодировки - у меня тоже 1251 и никаких проблем с этим не было...
Гость 16.01.2017 22:03
@infinitymd,
Тогда уже лучше делать корзину на статичной странице

поделитесь кодом для статичной страници
infinitymd 17.01.2017 16:06
если бы он у меня был... Мы у создателя плагина давно это просим :)
Гость 17.01.2017 16:36
Как сделать на основе вашей корзины, динамическую? надо добавлять один и тот же продукт с разными опциями, попробовал подменять дата-айди кнопки, чот не получилось , пример //a.blog777.info/test.html
Гость 18.01.2017 17:45
@Incode,
По всем остальным вопросам - в сотый раз повторю, что в ближайшее время у меня нет возможности заняться модернизацией плагина.

Будем помогать...

И так....
Как сделать на основе вашей корзины, динамическую? надо добавлять один и тот же продукт с разными опциями, попробовал подменять дата-айди кнопки, чот не получилось , пример http://a.blog777.info/test.html


примерно так
addToCart:...........

else {
var discount_date_change = document.getElementById('...ваш select....' + itemData.id).value;
itemData.count == discount_date_change;


//а дальше идет переменная для цены которая формируется исходя из позиции в select отдельной функцией на странице товара.

var main_On_change = document.getElementById('main_On_' + itemData.id).value;
itemData.price == main_On_change;

cartData[itemData.id] = itemData;

}

@infinitymd,
если бы он у меня был... Мы у создателя плагина давно это просим :)


1. создаем в проекте файл типа index1.html и копируем туда этот код:

<!doctype html>
<html>
<head>
<meta charset="utf-8">
 
<title>Корзина</title>
<link href="css/jqcart.css" rel="stylesheet" type="text/css">
<script src="js/jquery-1.11.3.min.js"></script>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.6.2/css/font-awesome.min.css">
</head>
<body>
<script>
$(function(){
	'use strict';	
	// инициализация плагина
	$.jqCart({
			handler: './php/handler.php', //!!!!!!!!!
			currency: '&#8381;'
	});		
});
</script>
<script>
(function($) {
  'use strict';
  var cartData,
    itemData,
    orderPreview = '',
    totalCnt = 0,
    visibleLabel = false,
    orderform = '<p class="jqcart-cart-title">Контактная информация:</p><form class="jqcart-orderform"><p><label>ФИО:</label><input type="text" name="user_name"></p><p><label>Телефон:</label><input type="text" name="user_phone"></p><p><label>Email:</label><input type="text" name="user_mail"></p><p><label>Адрес:</label><input type="text" name="user_address"></p><p><label>Коментарий:</label><textarea name="user_comment"></textarea></p><p><input type="submit" value="Отправить заказ"><input type="reset" value="Вернуться к покупкам"></p></form><p><input type="button" class="jqcart-clearCart-order" value="Очистить корзину"></p></div>';
  var opts = {
		buttons: '.add_item',
		cartLabel: 'body',
		visibleLabel: false,
		openByAdding: false,
        handler: '/',
		currency: '&#8381;'
  };
  
  var actions = {
    init: function(o) {
            opts = $.extend(opts, o);
		
		
      $(document)
        .on('click', '.jqcart-incr', actions.changeAmount)
		.on('input keyup', '.jqcart-amount', actions.changeAmount)
        .on('click', '.jqcart-del-item', actions.delFromCart)
        .on('submit', '.jqcart-orderform', actions.sendOrder)
        .on('reset', '.jqcart-orderform', actions.hideCart)
		.on('click', '.jqcart-print-order', actions.printOrder)
		.on('click', '.jqcart-clearCart-order', methods.clearCart);
      return false;
    },
   
    delFromCart: function() {
      var $that = $(this),
        line = $that.closest('.jqcart-tr'),
        itemId = line.data('id');
      cartData = actions.getStorage();
      actions.changeTotalCnt(-cartData[itemId].count);
      delete cartData[itemId];  
      actions.setStorage(cartData);
      line.remove();
      actions.recalcSum();
      return false;			 
    },
    changeAmount: function() {
      var $that = $(this),
		manually = $that.hasClass('jqcart-amount'),
        amount = +(manually ? $that.val() : $that.data('incr')),
        itemId = $that.closest('.jqcart-tr').data('id');
      cartData = actions.getStorage();
			if(manually) {
      	cartData[itemId].count = isNaN(amount) || amount < 1 ? 1 : amount;
			} else {
				cartData[itemId].count += amount;
			}
      if (cartData[itemId].count < 1) {
        cartData[itemId].count = 1;
      }
	  if (cartData[itemId].count > 12) {
        cartData[itemId].count = 12;
      }
			if(manually) {
				$that.val(cartData[itemId].count);
			} else {
      	$that.siblings('input').val(cartData[itemId].count);
			}
      actions.setStorage(cartData);
      actions.recalcSum();
      return false;
    },
    recalcSum: function() {
      var subtotal = 0,
        amount,
        sum = 0,
        totalCnt = 0;
      $('.jqcart-tr').each(function() {
        amount = +$('.jqcart-amount', this).val();
        sum = Math.ceil((amount * $('.jqcart-price', this).text()) * 100) / 100;
        $('.jqcart-sum', this).html(sum + ' ' + opts.currency);
				subtotal = Math.ceil((subtotal + sum) * 100) / 100;
        totalCnt += amount;
      });
      $('.jqcart-subtotal strong').text(subtotal);
	        if (totalCnt <= 0) {
				location.reload(); ///////////////////////actions.hideCart();
				
      }
      return false;
    },
    changeTotalCnt: function(n) {
      var cntOutput = $('.jqcart-total-cnt');
      cntOutput.text((+cntOutput.text() + n));
      return false;
    },
    sendOrder: function(e) {
      e.preventDefault();
      var $that = $(this);
      if ($.trim($('[name=user_name]', $that).val()) === '' || $.trim($('[name=user_phone]', $that).val()) === '') {
        $('<p class="jqcart-error">Пожалуйста, укажите свое имя и контактный телефон!</p>').insertBefore($that).delay(3000).fadeOut();
        return false;
      }
      $.ajax({
        url: opts.handler,
        type: 'POST',
				dataType: 'json',
        data: {
          orderlist: $.param(actions.getStorage()),
          userdata: $that.serialize()
        },
        error: function() {},
        success: function(resp) {
          $('.jqcart-checkout').html('<p>' + resp.message + '</p>');
					if(!resp.errors) {
						setTimeout(methods.clearCart, 2000);
					}
        }
      });
    },
		printOrder: function (){
                var contents = $(this).closest('.jqcart-checkout').prop('outerHTML');
							if(!contents) {
				return false;
			};
			var d = new Date();
			var curDate = ('0' + d.getDate()).slice(-2) + '-' + ('0' + (d.getMonth() + 1)).slice(-2) + '-' + d.getFullYear() + ' ' + ('0' + d.getHours()).slice(-2) + ':' + ('0' + d.getMinutes()).slice(-2) + ':' + ('0' + d.getSeconds()).slice(-2);
					
                var frame1 = $('<iframe />');
                frame1[0].name = "frame1";
                frame1.css({ "position": "absolute", "top": "-1000000px" });
                $("body").append(frame1);
                var frameDoc = frame1[0].contentWindow ? frame1[0].contentWindow : frame1[0].contentDocument.document ? frame1[0].contentDocument.document : frame1[0].contentDocument;
                frameDoc.document.open();
                //Create a new HTML document.
                frameDoc.document.write('<html><head><title>Заказ ' + curDate + '</title>');
                frameDoc.document.write('</head><body>');
                //Append the external CSS file.
                frameDoc.document.write('<link href="css/jqcart.css" rel="stylesheet" type="text/css" />');
                //Append the DIV contents.
                frameDoc.document.write(contents);
                frameDoc.document.write('</body></html>');
                frameDoc.document.close();
                setTimeout(function () {
                    window.frames["frame1"].focus();
                    window.frames["frame1"].print();
                    frame1.remove();
                }, 500);
              },		
		
    setStorage: function(o) {
      localStorage.setItem('jqcart', JSON.stringify(o));
      return false;
    },
    getStorage: function() {
      return JSON.parse(localStorage.getItem('jqcart'));
    }
  };
  var methods = {
		clearCart: function(){
			localStorage.removeItem('jqcart');
			location.reload(); ///////////////////////actions.hideCart();
		},
		printOrder: actions.printOrder,
		test: function(){
			actions.getStorage();
		}
	};
  $.jqCart = function(opts) {
    if (methods[opts]) {
      return methods[opts].apply(this, Array.prototype.slice.call(arguments, 1));
    } else if (typeof opts === 'object' || !opts) {
      return actions.init.apply(this, arguments);
    } else {
      $.error('Метод с именем "' + opts + '" не существует!');
    }
  };
  
  
  
      var subtotal = 0,
	  cartHtml = '';
      cartData = actions.getStorage();
	  orderPreview = '<div class="jqcart-checkout">';
      orderPreview += '<p class="jqcart-cart-title">Корзина <span class="jqcart-print-order"></span></p><div class="jqcart-table-wrapper"><div class="jqcart-manage-order"><div class="jqcart-thead"><div>ID</div><div></div><div>Наименование</div><div>Цена</div><div>Месяцев</div><div>Сумма</div><div></div></div>';
      var key, sum = 0;
      for (key in cartData) {
        if (cartData.hasOwnProperty(key)) {
					sum = Math.ceil((cartData[key].count * cartData[key].price) * 100) / 100;
					subtotal = Math.ceil((subtotal + sum) * 100) / 100;
					
          orderPreview += '<div class="jqcart-tr" data-id="' + cartData[key].id + '">';
		  orderPreview += '<div class="jqcart-small-td">' + cartData[key].id + '</div>';
		  orderPreview += '<div class="jqcart-small-td jqcart-item-img"><img src="' + cartData[key].img + '" alt=""></div>';
          orderPreview += '<div>' + cartData[key].title + '</div>';
          orderPreview += '<div class="jqcart-price">' + cartData[key].price + '</div>';
          orderPreview += '<div><span class="jqcart-incr" data-incr="-1">&#8211;</span><input class="jqcart-amount" value="' + cartData[key].count + '"><span class="jqcart-incr" data-incr="1">+</span></div>';
          orderPreview += '<div class="jqcart-sum">' + sum + ' ' + opts.currency + '</div>';
		  orderPreview += '<div class="jqcart-small-td"><span class="jqcart-del-item"></span></div>';
          orderPreview += '</div>';
        }
      }
      orderPreview += '</div></div>';
      orderPreview += '<div class="jqcart-subtotal">Итого: <strong>' + subtotal + '</strong> ' + opts.currency + '</div>';	
			cartHtml = subtotal ? (orderPreview + orderform) : '<h2 class="jqcart-empty-cart">Корзина пуста</h2>';
              console.log(cartData);
		      document.write(cartHtml);
  
  
})(jQuery);
	
	
</script>
</body>
</html>


2. запускаем index.html и кладем в корзину товар
3. запускаем index1.html и смотрим как работает корзина
4. правим всякие там local.где_корзина и location.reload(); под себя
Гость 19.01.2017 18:20
почему может быть корзина пуста, хотя в консоли в local storage есть данные?
Гость 20.01.2017 14:12
ну на пример при добавлении товара через переменную кол-во это не число
itemData.count = kolvo; не правильно
itemData.count == kolvo; правильно
slpgeneretor 25.01.2017 09:52
Не работает кнопка "Отправить заказ", в чем может быть причина? Спасибо за ответ)
Incode 25.01.2017 11:29
@slpgeneretor, причины могут быть разные. В первую очередь, нужно смотреть ошибки в консоли, если они есть.
polkovnikov 29.01.2017 12:19
Спасибо за скрипт, отличный, супер. Автору 1000 раз спасибо. Подскажите иногда в корзине находятся товары, но при вызове открытия пишет, что корзина пуста, хотя товар есть точно. Уже все испробовал, такое иногда происходит. Скажите как сделать открытие корзины всегда, даже если она пустая. Спасибо очень надо
Incode 29.01.2017 12:41
иногда в корзине находятся товары, но при вызове открытия пишет, что корзина пуста

@polkovnikov, в вашем случае, я бы постарался найти причину такого поведения. Вполне возможно, что это баг моего кода и выявление его помогло бы другим пользователям. А открывать корзину в любом случае можно:
// Найдите и закомментируйте строку (удалить всегда успеете)
cartHtml = subtotal ? (orderPreview + orderform) : '<h2 class="jqcart-empty-cart">Корзина пуста</h2>'; 
// Добавьте ниже
cartHtml = orderPreview + orderform;
polkovnikov 29.01.2017 12:47
Спасибо, буду пробовать. Цена,название и другие данные приходят из базы. Модальное окно немного поправил для мобильный устройств. Вот одна беда с не открыванием корзины. Очень классный скрипт.Можно еще узнать какой параметр можно добавить чтобы, рядом с кнопкой купить было поле количества. В смысле добавлять определенное количество товара еще на момент выбора а не в корзине. Спасибо
Incode 29.01.2017 13:14
рядом с кнопкой купить было поле количества
В методе addToCart (смотрите естественно не минимизированный файл плагина) измените инкремент количества, указывая значение из соответствующего поля. Т.е., вместо строки
cartData[itemData.id].count++;
должно получится что-то вроде:
cartData[itemData.id].count += +$(this).siblings('.amount_field').val() || 1;
Где ".siblings('.amount_field')", в данном случае, это поле с классом "amount_field", которое находится рядом с кнопкой
Гость 30.01.2017 06:46
иногда в корзине находятся товары, но при вызове открытия пишет, что корзина пуста


... ,а для всего остального есть волшебная функция
clearCart
infinitymd 31.01.2017 10:48
Показать код

Спасибо! Сделал свою корзину по этому примеру. Все работает, за исключением сообщения об успешной отправке заказа. Такое ощущение, что ajax не видит resp.message
infinitymd 31.01.2017 10:55
Вот код моего скрипта:
Показать код
Гость 01.02.2017 20:35
Не работает printOrder в Opera
infinitymd 06.02.2017 12:34
Все, разобрался сам :)
infinitymd 07.02.2017 12:28
Люди, помогите!
Сделал статическую корзину. Для этого использую два файла скрипта. Один облегченный на страницах с кнопками "купить", другой - полный в самой корзине.
Решил прикрутить к cartLabel еще и общую сумму заказа. В самой корзине все работает как надо, но на остальных страницах не пашет. Скорее всего это из за того, что в корзине изменения идут методами delFromCart, changeAmount, а на остальных страницах методом addToCart.
Так вот как сделать, чтобы при использовании метода addToCart просчитывалась сумма товаров в корзине и отправлялась в cartLabel.
Вот мой облегченный код скрипта:
Показать код
Гость 07.02.2017 17:07
@infinitymd,
Как то я врубится не могу, есть метод get и есть конструкция

cartData = actions.getStorage();
var key, sum = 0;
for (key in cartData) {
if (cartData.hasOwnProperty(key)) {
sum = Math.ceil((cartData[key].count * cartData[key].price) * 100) / 100;
subtotal = Math.ceil((subtotal + sum) * 100) / 100;
orderPreview = '<div>Итого: <strong>' + subtotal + '</strong></div>'

$('.Ваш div').append(orderPreview);

}

что так не работает?
Гость 07.02.2017 17:11
orderPreview = '<div>Итого: <strong>' + subtotal + '</strong></div>';

ну и {} я небрежно расставил просто сам принцип
Гость 07.02.2017 19:13
@infinitymd,
Вот написал тебе проверки

<script>
/***************** korzina ********************************/
function InPreviewLabel(){
function InGetStorage(){
return JSON.parse(localStorage.getItem('jqcart'));
};
var InCartData = InGetStorage();
var subtotal = 0;
var key, sum = 0;


if (InCartData !== null && Object.keys(InCartData).length) {
for (key in InCartData) {
if (InCartData.hasOwnProperty(key)) {
sum = Math.ceil((InCartData[key].count * InCartData[key].price) * 100) / 100;
subtotal = Math.ceil((subtotal + sum) * 100) / 100;

InPreview = '<a href="">' + subtotal + '</a>';

InPreviewHtml = subtotal ? (InPreview) : '';

$('.твой div!!!').append(InPreviewHtml);
}
}
}
}
InPreviewLabel();
/***************** Next ********************************/
</script>


когда типа положил в корзину вызывай InPreviewLabel();
infinitymd 08.02.2017 13:08
Спасибо огромное - все заработало! Вот окончательный вариант скрипта:
Показать код
Гость 10.02.2017 19:56
@infinitymd,
да пожалуйста, вот тебе еще для раздумья

// товар, который просматривает пользователь в магазине
var viewItem = {
id: "5456098",
name: "Смартфон LG G4",
dateView: new Date() // дата просмотра товара в интернет-магазине
};
// сохраняем, просматриваемый в данный момент пользователем товар в хранилище
localStorage.setItem (viewItem.id, JSON.stringify(viewItem));

// удаляем старые записи
var key, value;
// перебираем все данные в хранилище
for (var i=0; i<localStorage.length; i++) {
//получаем ключ записи
key = localStorage.key(i);
//получаем по ключу объект
value = JSON.parse(localStorage.getItem(key));
// если с момента просмотра товара прошло более чем 30 дней, то удаляем данную запись
if (Date.parse(value.dateView) < (new Date() - 1000*60*60*24*30)) {
localStorage.removeItem(key);
}
}
Гость 10.02.2017 20:07
@infinitymd,
да и спасибо тебе, что выкладываешь код, а то все остальные как в танке...
Страница 23 из 33  
Ваш комментарий:
X