13.03.2016

Ajax-Корзина

Это кажется крайне странным, но в 1c-bitrix в компоненте sale:basket.basket до сих пор нет полноценного Ajax-a, и при изменении количества товара в корзине приходится нажимать архаическую кнопку "пересчитать". Не удивительно, что каждый заказчик просит сделать ajax-овый пересчет корзины без перезагрузки страницы.

На двух разных сайтах мы реализовали задумку немного по разному. Сначала мы сделали корзину тут.

В шаблон корзины мы вставили простой js-код, который ловит события click и change в элементах изменения количества товара, включает битриксовскую ajax-анимацию и делает ajax-запрос к некоторому PHP-скрипту, который обновит корзину и вернет HTML новой корзины. Для данной операции в PHP-скрипте достаточно просто вставить тот же самый компонент корзины.

$('.count').each(function() {         
			var $tmp = $(this); 
			$tmp.find('.minus').click(function() {
				var $count1 = parseInt($tmp.find('input').val());
	            if ($count1>1) {
					$count1--;
					$tmp.find('input').val($count1).change();
				}
	            return false;
	        }); 
	        $tmp.find('a.plus').click(function() {
				var $count2 = parseInt($tmp.find('input').val());
				if ($count2<100) {
					$count2++;
					$tmp.find('input').val($count2).change();
				}
	            return false;
	        });         
	    });
		
$(".order-item-count input").change(function () {
	BX.showWait('cart_page');
	$('#cart_page').load('/actions/update_cart.php?action=update_qty&id=' + $(this).attr('rel') + '&qty=' + $(this).val());
});

Однако в такой реализации есть 2 минуса:

  1. Если сайт работает быстро, то ajax проскочит незаметно, но если вдруг что-то подтормаживает, или у клиента плохой интернет, то запрос может подвиснуть. Пока запрос висит, никто не мешает пользователю (в случае если он не очень умен) нажать кнопку "оформить заказ". В этом случае заказ будет оформлен со старыми данными о количестве товара.
    А что если пользователю захотелось "накликать" кнопкой плюсика 50 товаров? На сервер прилетит 50 запросов, что тоже не очень хорошо.
  2. Нет проблем, если нам необходимо обновить только сам компонент корзины на странице корзины и у нас нет других компонентов корзины в шапке/подвале сайта или в каких-то доп. панельках. Но как только появляются дополнительные блоки с корзинами, возникает необходимость обновить и их тоже. В этом случае в PHP-скрипте мы уже не сможем ограничиться вызовом одного единственного компонента и вернуть готовый HTML. Единственный выход - это возвращать json объект с информацией о корзине и уже java-скриптом "распихивать" информацию во все нужные HTML блоки.
    Для того, чтобы получить json мы должны либо написать новый шаблон для компонента корзины, который "соберет" json-объект, либо обновлять корзину API-функциями bitrix, вообще отказавшись от использования компонента.

С этими проблемами мы столкнулись на сайте компании Ньюком.

Если решение второй проблемы я в общем-то описал, то первая проблема решается так же не сложно. Во-первых, мы поставили таймер - ajax-запросы отпарвляются не сразу, а через пару секунд, после изменения количества товара, чтобы сократить нагрузку на сервер и в итоге ускорить выполнение запросов. Во-вторых мы пересчитываем стоимость товаров в корзине java-скриптом ещё до отправления запроса, что позволяет пользователю быстрее ориентирвоаться в корзине, подбирая нудное кол-во товаров и не ждать ответа на каждый запрос. В-третьих, мы блокируем кнопку "оформить заказ" до тех пор, пока все запросы не будут выполнены, а заодно вместо кнопки ставим прелоадер, чтобы пользователь понимал, что запросы находятся в стадии выполнения.

function UpdateTotalPrice() {

	var summPrice=0;
	var positions=0;
	$('.count').each(function(){
		
		var id = $(this).attr('id');
		var kol = parseInt($(this).find('input').val());
		if(!kol) { kol = 0; /*$(this).parent().find('input').val('0');*/} // защита от ввода букв в инпут

		summPrice += parseFloat(Prices[id])*kol;
		positions++;
	});
	
	$('#baskettotalprice').html(FormatPrice(summPrice));
	$('.cart-total-price').html(FormatPrice(summPrice));
	$('#basketpositions').html(positions);
}

function setTimer(el)/*ставит таймер*/
{
	if(!$(el).is('input')) el = this; 
	// тут мы должны пересчитать сумму только что изменившейся позиции и общую сумму
	
	var id = $(el).parent().attr('id');
	var kol = parseInt($(el).parent().find('input').val());
	if(!kol) { kol = 0; /*$(this).parent().find('input').val('0');*/} // защита от ввода букв в инпут

	var summPrice = parseFloat(Prices[id])*kol;

	if(summPrice != 0) 
		$('#price'+id.substr(4)).html(FormatPrice(summPrice));
	
	UpdateTotalPrice();
	
	lockButton();
	// обновим таймер
	if(timer) clearTimeout(timer);
	timer = setTimeout(UpdateBasket, 1000);
}


function UpdateBasket()/*отправляет запрос на обновление корзины*/
{
	var Basket={}; // массив хранит ид=>количество товаров в корзине. Передается на сервер.
	// собираем массив корзины и отправляем на сервер (там уж он разберется как это обновить)
	$('.count').each(function(indx){
		
		var id = $(this).attr('id');
		var kol = parseInt($(this).find('input').val());
		if(!kol) { kol = 0; /*$(this).parent().find('input').val('0');*/} // защита от ввода букв в инпут
		
		Basket['tov['+id.substr(4)+']'] = kol;

	});
	
	ajaxregs++; // Для lock/unlock
	$.ajax({
	  type: "POST",	
	  url: '/ipol/ajax/newcartajax.php?action=updbasket',
	  data: Basket,
	  cache: false,

	  success: function() {
		unlockButton();
	  }
	});

}