AJAX-добавление в корзину в Битрикс: чистый JavaScript и обновление мини-корзины
Зачем выносить добавление в корзину в AJAX
Страница каталога или карточки товара не обязана перезагружаться целиком: достаточно отправить на сервер пару полей в формате обычной POST-формы и затем обновить мини-корзину в шапке. Стандартная связка «магазин + компонент каталога» уже умеет обрабатывать действие ADD2BASKET, если передать флаг ajax_basket=Y и идентификатор товарной позиции.
Что нужно в вёрстке
Кнопке покупки задают произвольный data-*-атрибут с ID элемента каталога — в примере ниже это data-purchase-btn. Контейнер мини-корзины помечают отдельным атрибутом data-cart-widget: в него позже подставится HTML, который вернёт sale.basket.basket.line.
- Сессия на сайте уже открыта (в шаблоне доступен
window.sessidили аналог). - Первый запрос уходит на текущий адрес страницы или на тот URL, который использует ваш компонент списка товаров.
- Заголовок
X-Requested-With: XMLHttpRequestпомогает ядру отличить фоновый вызов от обычного визита.
Основной сценарий на чистом JavaScript
Цепочка простая: добавляем позицию → читаем JSON → при успехе запрашиваем фрагмент мини-корзины у /bitrix/components/bitrix/sale.basket.basket.line/ajax.php и подменяем содержимое контейнеров. Параметры siteId, templateName и массив arParams должны совпадать с теми, что вы передаёте в IncludeComponent в PHP.
document.querySelectorAll('[data-purchase-btn]').forEach(function (trigger) {
trigger.addEventListener('click', function (ev) {
ev.preventDefault();
var productNode = this;
var xhrAdd = new XMLHttpRequest();
var addUrl = window.location.pathname + window.location.search;
var addBody = 'action=ADD2BASKET&ajax_basket=Y&id=' + encodeURIComponent(productNode.dataset.purchaseBtn);
xhrAdd.responseType = 'json';
xhrAdd.open('POST', addUrl, true);
xhrAdd.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded;charset=UTF-8');
xhrAdd.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
xhrAdd.onreadystatechange = function () {
if (xhrAdd.readyState !== 4) return;
if (xhrAdd.status !== 200) {
alert('Ошибка сети при добавлении в корзину');
return;
}
var payload = xhrAdd.response;
if (!payload || payload.STATUS === 'ERROR') {
alert((payload && payload.MESSAGE) ? payload.MESSAGE : 'Не удалось добавить товар');
return;
}
if (payload.STATUS !== 'OK') return;
var xhrLine = new XMLHttpRequest();
var lineUrl = '/bitrix/components/bitrix/sale.basket.basket.line/ajax.php';
var lineBody = 'sessid=' + encodeURIComponent(window.sessid) + '&siteId=s1&templateName=storefront_bar';
lineBody += '&arParams[HIDE_ON_BASKET_PAGES]=N';
lineBody += '&arParams[PATH_TO_BASKET]=/personal/cart/';
lineBody += '&arParams[PATH_TO_ORDER]=/personal/order/make/';
lineBody += '&arParams[PATH_TO_PERSONAL]=/personal/';
lineBody += '&arParams[PATH_TO_PROFILE]=/personal/';
lineBody += '&arParams[PATH_TO_REGISTER]=/auth/';
lineBody += '&arParams[POSITION_FIXED]=Y';
lineBody += '&arParams[POSITION_HORIZONTAL]=right';
lineBody += '&arParams[POSITION_VERTICAL]=top';
lineBody += '&arParams[SHOW_AUTHOR]=Y';
lineBody += '&arParams[SHOW_DELAY]=N';
lineBody += '&arParams[SHOW_EMPTY_VALUES]=Y';
lineBody += '&arParams[SHOW_IMAGE]=Y';
lineBody += '&arParams[SHOW_NOTAVAIL]=N';
lineBody += '&arParams[SHOW_NUM_PRODUCTS]=Y';
lineBody += '&arParams[SHOW_PERSONAL_LINK]=N';
lineBody += '&arParams[SHOW_PRICE]=Y';
lineBody += '&arParams[SHOW_PRODUCTS]=N';
lineBody += '&arParams[SHOW_SUBSCRIBE]=N';
lineBody += '&arParams[SHOW_SUMMARY]=N';
lineBody += '&arParams[SHOW_TOTAL_PRICE]=N';
lineBody += '&arParams[COMPONENT_TEMPLATE]=storefront_bar';
lineBody += '&arParams[PATH_TO_AUTHORIZE]=';
xhrLine.responseType = 'text';
xhrLine.open('POST', lineUrl, true);
xhrLine.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded;charset=UTF-8');
xhrLine.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
xhrLine.onreadystatechange = function () {
if (xhrLine.readyState !== 4) return;
if (xhrLine.status !== 200) {
alert('Ошибка обновления блока корзины');
return;
}
var htmlChunk = xhrLine.responseText;
document.querySelectorAll('[data-cart-widget]').forEach(function (slot) {
slot.innerHTML = htmlChunk;
});
};
xhrLine.send(lineBody);
};
xhrAdd.send(addBody);
});
});Адрес addUrl можно заменить на пустую строку, если браузер подставляет корректную страницу компонента, либо на абсолютный путь к вашему обработчику — главное, чтобы серверная часть уже обрабатывала ADD2BASKET.
Делегирование кликов внутри AJAX-каталога
Если список товаров подгружается фрагментами, обработчик вешают на устойчивый родительский узел ([data-dynamic-root]) и находят реальную кнопку через closest. Логика запросов остаётся той же.
var root = document.querySelector('[data-dynamic-root]');
if (root) {
root.addEventListener('click', function (ev) {
var btn = ev.target.closest('[data-purchase-btn]');
if (!btn) return;
ev.preventDefault();
var xhrAdd = new XMLHttpRequest();
var addUrl = window.location.pathname + window.location.search;
var addBody = 'action=ADD2BASKET&ajax_basket=Y&id=' + encodeURIComponent(btn.dataset.purchaseBtn);
xhrAdd.responseType = 'json';
xhrAdd.open('POST', addUrl, true);
xhrAdd.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded;charset=UTF-8');
xhrAdd.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
xhrAdd.onreadystatechange = function () {
if (xhrAdd.readyState !== 4 || xhrAdd.status !== 200) return;
var payload = xhrAdd.response;
if (!payload || payload.STATUS === 'ERROR') {
alert((payload && payload.MESSAGE) ? payload.MESSAGE : 'Ошибка');
return;
}
var xhrLine = new XMLHttpRequest();
xhrLine.open('POST', '/bitrix/components/bitrix/sale.basket.basket.line/ajax.php', true);
xhrLine.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded;charset=UTF-8');
xhrLine.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
xhrLine.responseType = 'text';
var lineBody = 'sessid=' + encodeURIComponent(window.sessid) + '&siteId=s1&templateName=storefront_bar';
lineBody += '&arParams[HIDE_ON_BASKET_PAGES]=N&arParams[PATH_TO_BASKET]=/personal/cart/';
lineBody += '&arParams[PATH_TO_ORDER]=/personal/order/make/&arParams[PATH_TO_PERSONAL]=/personal/';
lineBody += '&arParams[PATH_TO_PROFILE]=/personal/&arParams[PATH_TO_REGISTER]=/auth/';
lineBody += '&arParams[POSITION_FIXED]=Y&arParams[POSITION_HORIZONTAL]=right&arParams[POSITION_VERTICAL]=top';
lineBody += '&arParams[SHOW_AUTHOR]=Y&arParams[SHOW_DELAY]=N&arParams[SHOW_EMPTY_VALUES]=Y';
lineBody += '&arParams[SHOW_IMAGE]=Y&arParams[SHOW_NOTAVAIL]=N&arParams[SHOW_NUM_PRODUCTS]=Y';
lineBody += '&arParams[SHOW_PERSONAL_LINK]=N&arParams[SHOW_PRICE]=Y&arParams[SHOW_PRODUCTS]=N';
lineBody += '&arParams[SHOW_SUBSCRIBE]=N&arParams[SHOW_SUMMARY]=N&arParams[SHOW_TOTAL_PRICE]=N';
lineBody += '&arParams[COMPONENT_TEMPLATE]=storefront_bar&arParams[PATH_TO_AUTHORIZE]=';
xhrLine.onreadystatechange = function () {
if (xhrLine.readyState === 4 && xhrLine.status === 200) {
document.querySelectorAll('[data-cart-widget]').forEach(function (slot) {
slot.innerHTML = xhrLine.responseText;
});
}
};
xhrLine.send(lineBody);
};
xhrAdd.send(addBody);
});
}Файл ajax_template.php
В шаблоне компонента bitrix:sale.basket.basket.line без отдельного AJAX-файла цепочка обрывается. Достаточно подключить обычный template.php одной строкой: require __DIR__ . '/template.php'; — так фронт получит тот же HTML, что и при полной отрисовке.
Подключение компонента в шапке
Набор ключей массива $arParams ниже сознательно повторяет запрос из JavaScript: любое расхождение приведёт к «пустому» или урезанному виджету. Пути скорректируйте под структуру своего магазина.
<div data-cart-widget>
<?php
$APPLICATION->IncludeComponent(
'bitrix:sale.basket.basket.line',
'storefront_bar',
[
'HIDE_ON_BASKET_PAGES' => 'N',
'PATH_TO_BASKET' => '/personal/cart/',
'PATH_TO_ORDER' => '/personal/order/make/',
'PATH_TO_PERSONAL' => '/personal/',
'PATH_TO_PROFILE' => '/personal/',
'PATH_TO_REGISTER' => '/auth/',
'POSITION_FIXED' => 'Y',
'POSITION_HORIZONTAL' => 'right',
'POSITION_VERTICAL' => 'top',
'SHOW_AUTHOR' => 'Y',
'SHOW_DELAY' => 'N',
'SHOW_EMPTY_VALUES' => 'Y',
'SHOW_IMAGE' => 'Y',
'SHOW_NOTAVAIL' => 'N',
'SHOW_NUM_PRODUCTS' => 'Y',
'SHOW_PERSONAL_LINK' => 'N',
'SHOW_PRICE' => 'Y',
'SHOW_PRODUCTS' => 'N',
'SHOW_SUBSCRIBE' => 'N',
'SHOW_SUMMARY' => 'N',
'SHOW_TOTAL_PRICE' => 'N',
'COMPONENT_TEMPLATE' => 'storefront_bar',
'PATH_TO_AUTHORIZE' => '',
],
false
);
?>
</div>Отладка и безопасность
- Проверяйте
sessidи ограничения CSRF теми же средствами, что использует типовая корзина. - Тип ответа на шаг добавления обязан быть JSON — иначе
responseType='json'вернётnull. - Строку с параметрами удобно собрать через
URLSearchParams, когда закончите прототип: так проще экранировать нестандартные поля каталога. - Для ошибок пользователю лучше вывести всплывающую плашку, а не только
alert— но для чернового сценария достаточно сообщений из ответа сервера.
Не хотите копаться сами?
Починю за 1-3 дня. Без предоплаты — оплата по результату.
15+ лет опыта с 1С-Битрикс · Без предоплаты · 7 дней гарантии