Решение для серия строительных интернет-магазинов на WooCommerce — часть 2

Продолжаю цикл заметок о разработке интернет-магазинов на WordPress & WooCommerce.

Главными героями этой заметки будут вот эта замечательная табличка «Доступные Варианты» с псевдо-3D эффектом и супер-полезная панелька «Тулбар» для посетителей (aka покупателей) интернет-магазина, с секциями «Пользователь», «Избранное», «Сравнение», «Корзина», «Поиск»:

Я опишу основные моменты, интеграцию со сторонними плагинами, примеры кода — информация, более ориентированная на разработчиков.

Если вы просто хотите получить описанный функционал на свой интернет-магазин, то переходите сразу в конец заметки.

Таблица «Доступные Варианты»

Обычно подобный список в экосистеме WooCommerce формируется из вариаций (Product Variation), в карточке вариативного товара (Variable Product). Но в моем случае, список формировался из товаров, находящихся в одной категории (подкатегории) — это более простой способ организовать «варианты», хоть это и не right way с точки зрения разработчиков WooCommerce. «Дайте мне точку опоры, и я переверну Землю» — слова Архимеда, а нам нужен какой-то признак/поле/атрибут по которому будут группироваться «варианты», и один из самых простых — категории.

Так получаем «варианты» в открытой карточке, передав текущий товар (global $product;).

/**
 * 
 * @param WC_Product $product
 * 
 * @return WC_Product[] variations
 */
function wcvl_get_variations($product) {
    $cat_id = end($product->get_category_ids());
    $category = get_term($cat_id, 'product_cat');
    $args = [
        'category' => isset($category->slug) ? $category->slug : [],
        'exclude' => [$product->get_id()]
    ];
    $variations = wc_get_products($args);
    return $variations;
}

Отбираем с них массив атрибутов для генерации колонок, следующих после «Артикул» и заканчивающихся на колонке «Цена», у нас это «Количество ступеней», «Рабочая высота», …, «Вес».

Атрибуты должны быть заполнены, чтобы попасть в список (спасибо, Кэп). Таким образом, таблица формируется автоматически, без какой-либо дополнительной конфигурации, просто на основании заполненных атрибутов.

Эта функция возвращает многомерный массив с variation_id (product_id) => массив объектов WC_Product_Attribute (атрибуты товара).

/**
 * 
 * @param WC_Product[] $variations
 * 
 * @return WC_Product_Attribute[] attributes
 */
function wcvl_get_attributes($variations) {
    if (empty($variations))
        return null;
    $attributes = [];
    foreach ($variations as $variation) {
        $attributes[$variation->get_id()] = $variation->get_attributes();
    }
    return $attributes;
}

Берем из массива первую «строку» (массив атрибутов первого товара в списке).

/**
 * 
 * @param WC_Product_Attribute[] $attributes
 * 
 * @return WC_Product_Attribute[] attributes
 */
function wcvl_get_key_attributes($attributes) {
    return empty($attributes) ? null : current($attributes);
}

Так формируется часть заголовка таблицы с атрибутами:

<?php foreach ($key_attributes as $key => $attribute): ?>                    
<td class="variation-table-th">
<div class="variation-table-th__wrap">
<span class="variation-table-th__inner">
<?= $attribute->get_name() ?>
</span>
</div>
</td>
<?php endforeach; ?>

Магия псевдо-3D заключается в повороте заголовка таблицы под углом в 45 градусов — это можно сделать одними средствами CSS, правда не обойтись без лишних тегов, первый из которых, див-враппер с магическим свойством transform:

.variation-table-th__wrap {
    transform: translate(58px,-34px) rotate(315deg);
    width: 60px;
}

Чтобы граница-разделитель заголовков столбцов была по длине надписи, необходимо заключить ее в инлайн тег, со стилем:

.variation-table-th__inner {
    border-bottom: 1px solid #ddd;
    padding: 24px 0;
}

Свойство white-space: nowrap не позволит переносить текст.

Ячейки со значениями свойства товара:

<?php foreach ($key_attributes as $key => $attribute): ?>                    
<td class="<?= wcvl_get_attribute_class($variation, $key, $attributes) ?>" data-th="<?= esc_attr($key_attributes[$key]->get_name()) ?>">
<div class="variation-item__val">
<?= wcvl_get_attribute_val($variation, $key, $attributes) ?>
</div>
</td>
<?php endforeach; ?>

В мобильной версии строки таблицы превращаются в «карточки» (card/thumbnail component). Тут секрет прост: все, что было display: table/table-row/table-cell (соотв. table/tr/td) преобразуем в block, и стилизуем по вкусу под мобайл.

@media (max-width: 991px) {
.variation-item {
        display: block;        
        box-shadow: rgba(0, 0, 0, 0.05) 0px 2px 2px 0px;
        border-radius: 4px;
        margin-bottom: 15px;
    }
}

Кнопки добавления в избранное, сравнение и пр. рассмотрим вместе с соответствующими секциями тулбара.

Тулбар для WooCommerce

Некоторые секции задействуют сторонние плагины, остальные на голом WP/WC API. Разберем каждую секцию по порядку.

Пользователь

Простая секция с 2-мя сосотяниями/сценариями использования:

Авторизованный пользователь: иконка с отображением юзернейма, при нажатии — переход в личный кабинет.

Гость: иконка, при нажатии — переход на страницу регистрации/входа.

TODO: сделать дополнительные опции для пользователя, всплывающее юзер-меню, аватары для магазинов с развитым комьюнити.

Избранное

Как «движок» для списка желаемого, используется плагин YITH WooCommerce Wishlist.

У плагина есть небольшое JS API с нужными событиями, которые просто перехватываем.

added_to_wishlist — добавлено в избранное.

$('body').on('added_to_wishlist', function (e, el, el_wrap) {
            $('.svg-spin-loader').hide() // Прячем индикатор-спиннер

// Показываем бутстраповское всплывающее окно (html-dom предварительно прописан в footer.php)
            $('.global-modal__title').text('Товар добавлен в избранное')
            $('.global-modal__button_cancel').text('Вернуться к сайту')
            $('.global-modal__button_ok').attr('href', '/wishlist')
            $('.global-modal__button_ok').text('Перейти в «Избранное»')
            $('.global-modal__body').hide()
            $('.global-modal').modal('show')

// Получаем обновленные счетчики для панельки (на сервере собственный wp_ajax hook)
            $.post(ajax_object.ajax_url, {action: 'b2w_wc_stat'}, function (a) {
                $('.b2w-app-bar__count_wishlist').text(a.wishlist_count)
            })
        })

removed_from_wishlist — соответственно, удалено из вишлист.

Шаблон блока, кнопки добавить/удалить, а так же вид самого списка избранного можно легко изменять, просто скопировав
соотв. файл из директории /wp-content/plugins/yith-woocommerce-wishlist/templates в директорию с вашей темой.

Сравнение

Для сравнения выбран простенький плагин WooCommerce Compare List

К сожалению, у него не оказалось JS API, но я решил исправить этот недостаток 🙂

Вешаем на кнопку обработчик onclick, и по наличию класса (в примере variation-item__button_active) определяем находится ли товар в списке сравнения; удалять его от туда или добавлять.

$('.variation-item__button_compare').on('click', function (e) {
            e.preventDefault()
            const t = $(this)
            if (t.hasClass('variation-item__button_active')) {
                $('body').trigger('removing_from_compare', [t])
                $.get(t.data('remove-url'), function (a) {
                    $('body').trigger('removed_from_compare', [t])
                })
            } else {
                $('body').trigger('adding_to_compare', [t])
                $.get(t.data('add-url'), function (a) {
                    $('body').trigger('added_to_compare', [t])
                })
            }
        })

Url для GET-запроса берется из data-атрибутов кнопки, значения которых получаются из функций на бекенде.

$remove_url = wccm_get_compare_link($product_id, 'remove-from-list');
$add_url = wccm_get_compare_link($product_id, 'add-to-list');

В итоге имеем 4 события: adding_to_compare, removing_from_compare — на старте операции добавления/удаления из списка сравнения — можно на эти события повесить показ индикатора загрузки, а added_to_compare и removed_from_compare — события при успешном завершении операции — убираем индикатор, показываем сообщение, изменяем состояние кнопки/иконки и пр.

$('body').on('added_to_compare', function (e, el) {
            $('.svg-spin-loader').hide()

            $('.global-modal__title').text('Товар добавлен в список сравнения')
            $('.global-modal__button_cancel').text('Вернуться к сайту')
            $('.global-modal__button_ok').attr('href', '/compare')
            $('.global-modal__button_ok').text('Список сравнения')
            $('.global-modal__body').hide()
            $('.global-modal').modal('show')

            el.addClass('variation-item__button_active')
            el.attr('title', 'Удалить из сравнения')
            el.tooltip('hide')
            el.tooltip('fixTitle')
            $.post(ajax_object.ajax_url, {action: 'b2w_wc_stat'}, function (a) {
                $('.b2w-app-bar__count_compare').text(a.compare_count)
            })
        })

Корзина

Тут стандартное JS API WooCommerce: вешаем обработчик на adding_to_cart, если хотим сделать что-то в самом начале процесса добавления в корзину — я запускаю все тот же крутящийся спин-
прогрессбар, как индикатор того, что операция выполняется. И added_to_cart — по завершению добавления в корзину, убираем спин, выводим бутсраповский диалог с вариантами, обновляем количество в разделе тулбара.

Поиск

На десктопе: при наведении курсора — расширяется область поля запроса, устанавливается фокус; при уходе указателя мыши — возвращается в исходное положение.

На мобилках: при тапе/клике — инпут раздвигается на всю ширину экрана, устанавливается фокус; при тапе вне поля — исходный вид.

Как получить подобные улучшения на свой сайт?

Этот, и любой другой функционал, я готов реализовать для вашего интернет-магазина!

Для связи: bridge2web@gmail.com или @bridge2web