Изменение количества товара в корзине после добавления с помощью ZoomX и Alpine.js

На многих интернет-магазинах при добавлении товара в корзину появляются кнопки +−, меняющие количество товара. При попытке сделать подобное, используя api miniShop2, пришлось городить костыли, плюс работало это с багами. Поэтому для этой цели пришлось запилить собственное api.

На бэкенде мы создадим метод, который добавляет товар, если его нет в корзине, изменяет его количество, удаляет, если передано количество 0. На фронтенде создадим метод для его вызова.

В статье используются ZoomX и Alpine.js, но можно обойтись и без них. Вместо контроллера можно создать php файл, вместо Smarty-модификатора — обычный сниппет, вместо Alpine.js можно использовать ванильный js или jQuery. Для оповещений я использовал Toastify.

Теперь код.

Для начала добавим роут и создадим контроллер.

Про роуты и контроллеры на сайте уже была статья modx.pro/howto/22727, повторяться, думаю, не стоит.

В core/config/routes.php добавляем:
$router->get('api/ms/cart/set', ['\Site\Controllers\Ms', 'cart_set']);

Контроллеры у меня расположены в core/elements/controllers. Чтобы контроллеры «подцепились» из этой папки, в core/config/elements.php добавляем:
zoomx()->getLoader()->addPsr4('Site\\Controllers\\', MODX_CORE_PATH . 'elements/controllers/');

Создаём контроллер. Файл core/elements/controllers/Ms.php:
<?php
namespace Site\Controllers;
use Zoomx\Controllers;

class Ms extends \Zoomx\Controllers\Controller
{

    public function cart_set()
    {
        zoomx()->autoloadResource(false);
        
        $id = $_GET['id'] ?? 0;
        $count = $_GET['count'] ?? 1;

        $ms2 = $this->modx->getService('minishop2');
        $ms2->initialize($this->modx->context->key ?? 'web');
        $cart = $ms2->cart->get();

        // получаем ключ товара
        $key = false;
        foreach ($cart as $item) {
            if ($item['id'] == $id) {
                $key = $item['key'];
                break;
            }
        }

        // определяем действие
        $action = 'add';
        if ($key && $count) {
            $action = 'change';
        }
        if ($key && $count == 0) {
            $action = 'remove';
        }

        // добавляем или меняем кол-во
        if ($action == 'add') {
            $response = $ms2->cart->add($id, $count);
        } else {
            $response = $ms2->cart->change($key, $count);
        }

        $response['action'] = $action;
        $response['id'] = $id;

        return jsonx($response);
    }

}

Создаём Smarty-модификатор ms_in_cart, который будет получать кол-во товара в корзине. Модификатор — это разновидность плагина Smarty. Плагины Smarty у меня расположены в core/elements/smarty, путь можно прописать в системной настройке zoomx_smarty_custom_plugin_dir. Подробнее про плагины Smarty: modzone.ru/blog/2020/12/19/zoomx-creating-smarty-plugins/.

Файл core/elements/smarty/modifier.ms_in_cart.php:
<?php
/*
 * Smarty plugin
 * -------------------------------------------------------------
 * Файл:       modifier.ms_in_cart.php
 * Тип:        modifier
 * Имя:        ms_in_cart
 * Назначение: получение кол-ва товара в корзине по id
 * -------------------------------------------------------------
 */

function smarty_modifier_ms_in_cart($id)
{
    global $modx;
    $ms2 = $modx->getService('minishop2');
    $ms2->initialize('web');
    $cart = $ms2->cart->get();

    $count = 0;
    foreach ($cart as $item) {
        if ($item['id'] == $id) {
            $count = $item['count'];
            break;
        }
    }

    return $count;
}

В карточке товара поменяем стандартную форму добавления, добавим класс js-ms-item и is-added, если товар добавлен в корзину. Пример карточки:
{$in_cart = $item.id|ms_in_cart}
<div class="prod-card {($in_cart) ? 'is-added' : ''} js-ms-item" data-id="{$item.id}">
    <div class="prod-card-footer"
    x-data="{ qty: {$in_cart} }"
    x-init="$watch('qty', () => { $el.classList.add('is-loading'); Ms.cart.set({$item.id}, qty); })">
        <button class="prod-card-add button" @click="qty++"><span>В корзину</span></button>
        <div class="prod-card-qty">
            <button @click="qty && qty--">−</button>
            <span x-text="qty"></span>
            <button @click="qty++">+</button>
        </div>
    </div>
</div>

Здесь используется Alpine.js. Подробнее про этот фреймворк можно почитать тут: habr.com/ru/post/501312/. С его помощью мы меняем qty (количество) при нажатии на кнопки, а при изменении qty вызываем метод Ms.cart.set(), который добавляет товар в корзину, меняет его количество или удаляет. Также добавляем класс is-loading, его можно использовать для отображения состояния загрузки.

Через css скроем кнопку «в корзину» или кнопки +− в зависимости от того, добавлен товар в корзину или нет:
.prod-card.is-added .prod-card-add,
.prod-card:not(.is-added) .prod-card-qty {
    display: none;
}

Создаём js-файл, в котором добавляем наше api, а также добавляем или удаляем класс is-added у карточки товара.
const Ms = {
    cart: {
        set(id, count = 1) {
            fetch(`/api/ms/cart/set?id=${id}&count=${count}`)
                .then(response => response.json())
                .then(response => {
                    if (response.data.message) {
                        Ms.message(response.data.message, response.data.success);
                    }
                    for (let i of Ms.cart.classes) {
                        for (let el of document.querySelectorAll(i.class)) {
                            el.innerHTML = response.data.data[i.key];
                        }
                    }
                    document.dispatchEvent(new CustomEvent('ms:cart-set', { detail: response }));
                });
        },
        classes: [
            { class: '.ms2_total_weight', key: 'total_weight' },
            { class: '.ms2_total_count', key: 'total_count' },
            { class: '.ms2_total_cost', key: 'total_cost' },
            { class: '.ms2_total_discount', key: 'total_discount' },
        ]
    },
    message(message, success) {
        const theme = success ? 'is-success' : 'is-danger';
        Toastify({
            text: message,
            gravity: 'bottom',
            position: 'center',
            className: `message ${theme}`,
            duration: 3000,
        }).showToast();
    }
}

/** добавляем или убираем .is-added у карточки товара, убираем .is-loading */
document.addEventListener('ms:cart-set', (e) => {
    const item = document.querySelector(`.js-ms-item[data-id="${e.detail.data.id}"]`);
    if (item) {
        if (e.detail.data.action == 'remove') {
            item.classList.remove('is-added');
        } else {
            item.classList.add('is-added');
        }
        item.querySelector('.is-loading').classList.remove('is-loading');
    }
});

Подключим нужные библиотеки и всё, готово.

Советую скачать библиотеки и подключить локально, так как cdn вполне могут забанить.
<link rel="stylesheet" type="text/css" href="//cdn.jsdelivr.net/npm/toastify-js/src/toastify.min.css">
<script src="//cdn.jsdelivr.net/npm/toastify-js"></script>
<script src="//unpkg.com/alpinejs" defer></script>
Лёша
24 апреля 2022, 19:51
modx.pro
1
393
+8

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

Авторизуйтесь или зарегистрируйтесь, чтобы оставлять комментарии.
0