Yandex Metrika
sanches.free 15 просмотров

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 дней гарантии