Добавление и удаление товара из корзины со страницы категории и товара

Периодически клиенты просят функционал, чтобы можно было изменять количество товара не только находясь в корзине, но и со страниц категорий и самого товара.
Поэтому поделюсь своим решением. Сразу отмечу, что оно не претендует на позицию идеального, так что если есть замечания и предложения, то с радостью их выслушаю.
ВАЖНО! У меня версия miniShop2 4.2.0. Код будет отличаться для версий ниже. Так например до 4.1.4 отличается метод генерации ключа товара, а до версии 4.1.0 методы add и change корзины возвращают только ключ товара, когда в более новых версиях также есть измененное кол-во товара и прочая информация о нем.

1) Итак, для начала создадим сниппет inCart, который будет отвечать за вывод HTML-разметки, в зависимости от того, есть товар в корзине или нет. И соответственно два чанка tpl.inCart.btn и tpl.inCart.btn.not

Код сниппета inCart
<?php
$output = '';

$product = (int) $modx->getOption('product', $scriptProperties, $modx->resource->id);
if (!$productItem = $modx->getObject('msProduct', $product)) {
    return;
}

$miniShop2 = $modx->getService('miniShop2');
$miniShop2->initialize($modx->context->key);
$cart = $miniShop2->cart->get();

$options = array();
//$key = md5($product . $productItem->getPrice() . $productItem->getWeight() . (json_encode($options))); - используйте этот код, если у вас версия miniShop2 ниже 4.1.4

if (!function_exists('getProductKey')){
    function getProductKey(array $product, array $options = [])
    {
        $key_fields = ['id','options'];
        $product['options'] = $options;
        $key = '';
    
        foreach ($key_fields as $key_field) {
            if (isset($product[$key_field])) {
                if (is_array($product[$key_field])) {
                    $key .= json_encode($product[$key_field]);
                } else {
                    $key .= $product[$key_field];
                }
            }
        }
        return 'ms' . md5($key);
    }
}

$key = getProductKey($productItem->toArray(), $options);

if (array_key_exists($key, $cart)) {
    $count = $cart[$key]['count'];
    $output = $modx->getChunk('tpl.inCart.btn', ['count' => $count, 'key' => $key]);
} else {
    $output = $modx->getChunk('tpl.inCart.btn.not');
}

return $output;

Код чанка tpl.inCart.btn
Замените ID ресурса в ссылке на корзину.
<div class="incart-wrap d-flex">
    <button type="button" class="incart-btn btn-minus btn" data-count="[[+count:decr=`1`]]">-</button>
    <a href="[[~27]]"><span>В корзине <span class="incart-count" data-key="[[+key]]">[[+count]]</span> шт</span><span>Перейти</span></a>
    <button type="submit" class="incart-btn btn-plus btn" name="ms2_action" value="cart/add">+</button>
</div>

Код чанка tpl.inCart.btn.not
<button type="submit" class="btn" name="ms2_action" value="cart/add">
    <span>В корзину</span>
    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" fill="#fff">
        <path d="M16,18C17.1,18 18,18.9 18,20C18,21.1 17.1,22 16,22C14.9,22 14,21.1 14,20C14,18.9 14.9,18 16,18M16,19C15.45,19 15,19.45 15,20C15,20.55 15.45,21 16,21C16.55,21 17,20.55 17,20C17,19.45 16.55,19 16,19M7,18C8.1,18 9,18.9 9,20C9,21.1 8.1,22 7,22C5.9,22 5,21.1 5,20C5,18.9 5.9,18 7,18M7,19C6.45,19 6,19.45 6,20C6,20.55 6.45,21 7,21C7.55,21 8,20.55 8,20C8,19.45 7.55,19 7,19M18,6H4.27L6.82,12H15C15.33,12 15.62,11.84 15.8,11.6L18.8,7.6V7.6C18.93,7.43 19,7.22 19,7C19,6.45 18.55,6 18,6M15,13H6.87L6.1,14.56L6,15C6,15.55 6.45,16 7,16H18V17H7C5.9,17 5,16.1 5,15C5,14.65 5.09,14.32 5.25,14.03L5.97,12.56L2.34,4H1V3H3L3.85,5H18C19.1,5 20,5.9 20,7C20,7.5 19.83,7.92 19.55,8.26L16.64,12.15C16.28,12.66 15.68,13 15,13Z" />
    </svg>
</button>

2) Теперь нужно немного расширить методы add и remove класса msCartHandler.
Зачем это нужно? Метод add будет возвращать HTML-разметку после успешного добавления товара в корзину, а remove соответственно — HTML-разметку после успешного удаления товара из корзины.

Как расширять класс я описывать не буду, информация по этому есть в документации, поэтому просто напишу, что и куда добавить.
//Переопределяем метод add и в return добавляем incart_html
return $this->success(
            'ms2_cart_add_success',
            $this->status([
                'key' => $key,
                'cart' => $this->cart,
                'row' => $this->cart[$key],
                'incart_html' => $this->modx->getChunk('tpl.inCart.btn', ['count' => $count, 'key' => $key])
            ]),
            ['count' => $count]
        );

//Переопределяем метод remove и в return добавляем incart_html и ключ deleted со значением true
return $this->success(
            'ms2_cart_remove_success',
            $this->status([
                'cart' => $this->cart,
                'row' => $row,
                'deleted' => true,
                'incart_html' => $this->modx->getChunk('tpl.inCart.btn.not')
            ])
        );

3) Допишем нужные скрипты. Для этого копируем default.js от minishop2 и создаем условный custom.js (не забудьте потом указать его в системных настройках по ключу ms2_frontend_js).
//Находите строку (в 4.2.0 197)  var xhr = function (callbacks, userCallbacks) и дописываем строчки сразу после if
var xhr = function (callbacks, userCallbacks) {
    return $[method](url, data, function (response) {
        if (response.success) {
            //Добавляем код ниже
            if (data[3] && data[3].name == 'ms2_action' && data[3].value == 'cart/add') {
                var thisForm = miniShop2.sendData.$form;
                if (thisForm.find('.incart-container').length) {
                    thisForm.find('.incart-container').html(response.data.incart_html); //после успешного добавления товара в корзину меняем вид кнопки на ту, что возвращаем метод add из предыдущего пункта
                }
            }
        ...

//Почти в самом низу этого скрипта вставляем следующий код внутри $(document).ready(function ($)
miniShop2.Callbacks.Cart.add.response.success = function(response) {
    let key = response.data.key;
    if ($('.incart-count[data-key="'+key+'"]').length) {
        $('.incart-count[data-key="'+key+'"]').text(response.data.row.count); //меняем кол-во товара в кнопке
    }
};

$(document).on('click', '.incart-btn.btn-minus', function() {
    let btn = $(this),
        count = $(this).attr('data-count'),
        key = $(this).next().find('.incart-count').attr('data-key');        

    $.post(document.location, {ms2_action: 'cart/change', key:key, count:count, ctx:'web'}, function(data) {
        let response = JSON.parse(data);
        if (response.success == true) {
            if (response.data.deleted == true) {
                let container = btn.closest('.incart-container');
                container.html(response.data.incart_html); //меняем кнопку после удаления товара из корзины
                miniShop2.Message.success(response.message);
            } else {
                btn.attr('data-count', parseInt(response.data.row.count) - 1);
                btn.next().find('.incart-count').text(response.data.row.count); 
                miniShop2.Message.success(response.message);
            }                
        }
    });
});
Вот и все.
В чанке карточки товара на странице категории, вместо сабмит-кнопки вставляете следующий код:
<div class="incart-container">
   {'!inCart' | snippet : ['product' => $id]}
</div>
На странице товара делаете то же самое, просто можно не указывать параметр product у сниппета.

Осталось добавить нужные стили и на выходе должно получится что-то наподобие этого (кликабельная gif-ка)
Dan
Dan
25 декабря 2023, 01:30
modx.pro
2
1 423
+9

Комментарии: 13

deleted
25 декабря 2023, 17:16
+1
Делал вот такое. Если что, там критика ниже, поправить статью пока руки не дошли.
modx.pro/howto/23874
    Dan
    Dan
    25 декабря 2023, 17:26
    0
    Интересное решение с использованием ZoomX!
    Ну, как я и писал, мое решение не претендует на топ. Если будет критика и правки, я только за — поправлю на сайтах своих)
    Futuris
    27 декабря 2023, 12:09
    0
    Пытаюсь реализовать метод, описанный в статье, но еще с кодом не слишком знаком, чтобы самостоятельно разобраться.) Как понял по докам нужно:

    1. Создать свой php-файл. У меня это — mscarthandlerbtn.class.php;
    2. Зарегистрировать новый метод вызовом в консоли:
    if ($miniShop2 = $modx->getService('miniShop2')) {
      $miniShop2->addService('cart', 'msCartHandlerBtn',
          '{core_path}components/minishop2/custom/cart/mscarthandlerbtn.class.php');
    }
    3. Указать свой класс в системной настройке ms2_cart_handler_class.
    4. А вот какой код должен быть в моем новом, расширяющем классе msCartHandlerBtn? Пока я просто скопировал в него «переопределенные методы add и return с добавленным incart_html» — описанные в статье. При этом в родительском классе они также присутствуют, т.к. на их удаление IDE реагирует ошибкой. В итоге у меня появились кнопки "+" и "-" на карточке товара (на странице категории пока еще не разобрался) и ими можно добавлять товар в корзину. Работает правда немного косячно. Думаю, что из-за неправильного наследования класса.
      Dan
      Dan
      27 декабря 2023, 19:52
      0
      Все верно. Копируете полностью методы add и remove (полный код функций function add и function remove). И добавляете им в return $this->success ключ incart_html с нужным чанком.

      на странице категории пока еще не разобрался
      В чанке tpl товара, вместо кнопки сабмит вставляете код, который указан в статье (сниппет inCart с параметром product обернутый в incart-container)

      Работает правда немного косячно
      Что не так работает?
        Futuris
        29 декабря 2023, 09:49
        0
        Спасибо за информацию! У меня странно эти плюсы-минусы работают на странице товара. Плюсуешь — все работает. А вот минусовка не работает — т.е. в корзине число меняется, а на странице при минусации не уменьшается. Тут с нашим фронтом разбираться нужно. У нас не bootstrap, а свой UI-фреймворк стоит.
          Dan
          Dan
          29 декабря 2023, 10:11
          +1
          Класс может не добавили или в скрипте ошибка какая. А тут нет разницы какой CSS-фреимворк, я ими вообще не пользуюсь например) если что можете написать доступы мне телеграмм (аккаунт в профиле) гляну сегодня и поправлю, заодно напишу в чем была проблема
            Futuris
            29 декабря 2023, 13:03
            0
            Спасибо за помощь! Пока нет неоходимости. У меня это не рабочий проект, я просто хотел попрактивоваться в коде.)) Но жена, которая в нашем семейном подряде отвечате за фронтенд, предложила более простое (для нашего случая) решение, которое не требует изменений в php- и js-файлах Minishop2. Поскольку, как упоминал, у нас свой фреймворк, она просто взяла из корзины (где у нее также свои стили) свои стилизованные инпуты и добавила их на страницу категории.


            Единственное отличие — в вашем решении больше динамики. Т.е. у вас при каждом нажатии изменяется количество в корзине. У нас сначала нужно «наплюсовать» нужное количество, а потом оно отправляется в корзину.

              Dan
              Dan
              29 декабря 2023, 16:03
              0
              То, про что вы говорите, не требует PHP вообще. Это чистый js в 3 строчки. Мое решение позволяет видеть какой товар есть в корзине и в каком количестве на любой странице сайта, и в то же время позволяет удалять товар из корзины, не находясь на ее странице.
                Futuris
                29 декабря 2023, 16:14
                0
                Да, у вас это уже почти мини-корзина. После праздников я еще внимательно поразбираюсь. Если будут вопросы — напишу. Спасибо!
        Артур Шевченко
        27 декабря 2023, 21:08
        0
        Осмелюсь предложить msAltCart я уже за вас покодил.
          Futuris
          28 декабря 2023, 09:26
          0
          Спасибо! Одно другому не мешает) Я видел уже msAltCart на демосайте. Полезный компонент. После праздников, думаю, поставим на один из клиентских магазинов и посмотрим.
        Futuris
        30 декабря 2023, 10:20
        +1
        Для начинающих кодеров, типа меня, стоит упомянуть, чтобы в чанке tpl.inCart.btn в ссылке
        <a href="[[~27]]">
        поставили ID своей корозины. Не сразу доходит, почему ссылка не работает.))
          Dan
          Dan
          30 декабря 2023, 15:37
          0
          точно, забыл, добавил
          Авторизуйтесь или зарегистрируйтесь, чтобы оставлять комментарии.
          13