Списки избранного для MODX Revolution 3 и MiniShop3: пользователи сохраняют товары и ресурсы,
возвращаются к покупке позже, делятся списком по ссылке и оформляют заказ со страницы
/wishlist/.
Данные в браузере (localStorage или cookie), синхронизация в БД для
авторизованных и гостей (по настройкам). Кнопки data-favorites-toggle, счётчик
data-favorites-count, сниппеты ms3Favorites, ms3FavoritesIds,
ms3FavoritesPage и др., интеграция с mxQuickView и mFilter.
/wishlist/share?token=… и копирование чужого списка к себе.ms3FavoritesIds → pdoPage → ms3Favorites.resource_type.ms3fConfig.notify → MiniShop3 → iziToast.Постановка: показать минимальную кнопку «в избранное» для товара MiniShop3 (resource_type=products).
Вызов: разметка с data-favorites-toggle, data-id, data-resource-type="products" (в чанке подставьте id товара).
<button type="button" class="btn btn-outline-secondary ms3f__button"
data-favorites-toggle
data-id="{$product_id}"
data-resource-type="products"
title="{$_modx->lexicon('ms3favorites_add_tooltip')}">
…
</button>
Постановка: добавить в избранное документ сайта (MODX resource), а не товар MS3.
Вызов: кнопка с data-resource-type="resources" и data-id текущего или нужного ресурса.
<button type="button" class="btn btn-outline-secondary ms3f__button"
data-favorites-toggle
data-id="{$_modx->resource.id}"
data-resource-type="resources"
title="{$_modx->lexicon('ms3favorites_add_tooltip')}">
…
</button>
Постановка: та же кнопка избранного через сниппет (удобно для единообразия с параметрами).
Вызов:
{'ms3FavoritesBtn' | snippet : [
'id' => 26,
'resource_type' => 'products'
]}
Постановка: показать число позиций в избранном без сниппета — значение обновляет favorites.js.
Вызов: разметка + при необходимости скрыть «0» до инициализации JS.
<span data-favorites-count style="display: none;">0</span>
Постановка: то же количество через сниппет (удобно для первого кадра и кэша).
Вызов: минимальный вызов, с параметрами и некэшируемый (! в имени сниппета — для pdoTools).
{'ms3FavoritesCounter' | snippet}
{'ms3FavoritesCounter' | snippet : [
'list' => 'default',
'resource_type' => 'products'
]}
{'!ms3FavoritesCounter' | snippet : [
'list' => 'default',
'resource_type' => 'products'
]}
Постановка: считать только товары, не включая ресурсы в том же списке.
Вызов:
<span data-favorites-count data-resource-type="products" style="display: none;">0</span>
Постановка: перед кликом «добавить» выбрать именованный список (default, gifts, plans и др.).
Вызов: чанк селектора + кнопка без data-list (список берётся из селектора).
{'tplFavoritesListSelector' | chunk}
<button type="button" class="btn btn-outline-primary"
data-favorites-toggle
data-id="{$product_id}">
…
</button>
Постановка: явно класть товар в нужный список через атрибут data-list.
Вызов:
<button data-favorites-toggle data-id="{$product_id}" data-list="default">…</button>
<button data-favorites-toggle data-id="{$product_id}" data-list="gifts">…</button>
<button data-favorites-toggle data-id="{$product_id}" data-list="plans">…</button>
Постановка: отрисовать карточки избранного в контейнере через API фронтенда, несколько списков и переключение табами.
Вызов:
ms3Favorites.render('#container');
ms3Favorites.render('#box', { list: 'gifts' });
ms3Favorites.switchList('plans');
ms3Favorites.render('#tabs', { list: 'plans' });
9a. Основной список
9b. Разные списки (default / gifts / plans)
9d. Переключение списка по табам
Постановка: проверить отображение пустого списка после очистки избранного.
Вызов: ms3Favorites.render('#id', { list: 'default' }) при пустом хранилище.
ms3Favorites.render('#favorites-empty-demo', { list: 'default' });
Очистите избранное и обновите страницу.
Постановка: на странице списка избранного кнопки работают как «удалить из списка», а не «добавить».
Вызов: обёртка с data-favorites-mode="list", внутри — карточки с data-favorites-toggle.
<div data-favorites-mode="list">
<button type="button" data-favorites-toggle
data-id="$product_id"
data-list="default">
Удалить
</button>
</div>
Постановка: перейти на отдельный ресурс wishlist из системной настройки ms3favorites.list_page.
Вызов: ссылка на URI списка из настройки или на id ресурса.
<a href="{$_modx->makeUrl(123)}">Избранное</a>
<a href="/{('ms3favorites.list_page' | option) | trim : '/'}/">Избранное</a>
<a href="{$id | url}">Избранное</a>
Постановка: одним действием очистить выбранный именованный список на клиенте и синхронизировать с сервером (по настройкам).
Вызов:
<button type="button" data-favorites-clear data-list="default">…</button>
Постановка: получить публичную ссылку на копию списка (нужна сессия пользователя MODX).
Вызов:
<button type="button" data-favorites-share data-list="default">…</button>
Нужна авторизация на сайте (сессия MODX). Это кнопка для коннектора; для страницы просмотра списка по ссылке используйте сниппет ms3FavoritesShare (см. документацию, пример 22).
Постановка: вывести, у скольких пользователей в избранном данный товар/ресурс.
Вызов:
{'!ms3FavoritesPopularity' | snippet : [
'resource_id' => $product_id
]}
{'!ms3FavoritesPopularity' | snippet : [
'resource_id' => $product_id,
'tpl' => '@INLINE У [[+count]] в избранном',
'minCount' => 1
]}
Постановка: серверная цепочка: в ms3FavoritesIds задать список и resource_type (фильтр типа при сборке id), затем пагинированный вывод через pdoPage → ms3Favorites по строке ids — у ms3Favorites параметр resource_type не передаётся (см. документацию).
Вызов:
{'!ms3FavoritesIds' | snippet : [
'list' => 'default',
'resource_type' => 'products',
'toPlaceholder' => 'ms3f.ids'
]}
{set $idsStr = $_modx->getPlaceholder('ms3f.ids')}
{'!pdoPage' | snippet : [
'element' => 'ms3Favorites',
'ids' => $idsStr,
'list' => 'default',
'limit' => 3,
'pageLimit' => 5,
'tpl' => 'tplFavoritesItem',
'emptyTpl' => 'tplFavoritesEmpty',
'pageVarKey' => 'favpage',
'totalVar' => 'ms3ffav.total',
'pageNavVar' => 'ms3ffav.nav'
]}
{$_modx->getPlaceholder('ms3ffav.nav')}
Почему список «замирает» после удаления: HTML от pdoPage собран на сервере при ответе страницы. Добавление/удаление в избранном обрабатывает JavaScript и не перезапускает MODX-сниппеты, поэтому разметка сама не меняется.
Как синхронизировать: по событиям ms3f:added / ms3f:removed вызывать ms3Favorites.render() в контейнер списка (как ниже), либо перезагружать страницу, либо отдавать новый HTML своим AJAX к ресурсу. После первого такого обновления серверная навигация pdoPage скрывается — без отдельного запроса она уже не соответствует клиентскому списку.
Список пуст при загрузке. Добавьте товары кнопками выше — блок обновится через ms3Favorites.render.
Постановка: вывести каталог товаров с пагинацией и чанком строки, где есть кнопка избранного.
Это обычный каталог, а не «только избранное»: снятие товара с избранного не удаляет строку из таблицы — меняется только кнопка. После событий избранного ниже вызывается updateButtonStates(); при ajaxMode у pdoPage добавьте тот же вызов в callback подгрузки страницы (см. документацию интеграции).
Вызов:
{'!pdoPage' | snippet : [
'element' => 'msProducts',
'parents' => 0,
'limit' => 10,
'tpl' => 'tpl.msProducts.row',
'pageVarKey' => 'catpage',
'totalVar' => 'ms3fcat.total',
'pageNavVar' => 'ms3fcat.nav'
]}
{$_modx->getPlaceholder('ms3fcat.nav')}
Чанк tplCatalogRowMs3f из пакета или замените tpl на свой (например msProducts_row с кнопкой избранного).
В избранное 0
Постановка: подписаться на кастомные события добавления/удаления для аналитики или UI.
Вызов:
document.addEventListener('ms3f:added', function (e) { console.log(e.detail); });
document.addEventListener('ms3f:removed', function (e) { console.log(e.detail); });
Постановка: выполнить свой JS после успешного добавления в избранное (без правки ядра коннектора).
Вызов: в конфиге после ms3fLexiconScript задать window.ms3fConfig.onAdd = function (id, list, resourceType) { … } (см. блок scripts в шаблоне).
window.ms3fConfig = window.ms3fConfig || {};
window.ms3fConfig.onAdd = function (id, list, resourceType) {
console.log('onAdd', id, list, resourceType);
};
Вверху задан onAdd(id, list, resourceType) — при добавлении смотрите консоль. Тосты при этом показываются (iziToast / MS3). Чтобы проверить режим без тостов: в консоли выполните window.ms3fConfig.showToast = false и снова нажмите кнопку.
Постановка: пользователь оставляет текстовую заметку к товару в списке (до 500 символов). Работает при включённой настройке ms3favorites.comments_enabled; на странице /wishlist/ заметки выводятся в чанке tplFavoritesPageItem.
Документация: ms3Favorites — интеграция, раздел «Заметки к элементам».
Вызов · HTML: элемент с data-favorites-comment, data-product-id, data-list; ограничение длины — maxlength="500".
<textarea
data-favorites-comment
data-product-id="{$product_id}"
data-list="default"
data-resource-type="products"
maxlength="500"
rows="3"
class="form-control"
placeholder="Заметка к позиции"></textarea>
Вызов · JavaScript: программное сохранение текста заметки.
ms3Favorites.updateComment(productId, list, comment);
Коннектор: action update_comment (см. обзор компонента).
Сохранение с textarea на странице выполняет favorites.js (blur/отправка на сервер). Если comments_enabled выключено, разметку с data-favorites-comment в пакете обычно не выводят — проверяйте настройку в менеджере MODX. На этой тестовой странице поля заметок в списках примеров 9–19 и в серверных выводах скрыты стилями — остаётся только блок ниже.
Живая проверка (нужны избранное + comments_enabled)
Добавьте товар в список default, затем введите заметку и уйдите с поля — запрос уйдёт в коннектор.
Постановка: вывести именованные списки избранного с количеством элементов (ms3FavoritesLists).
Сниппет отдаёт одну строку на каждый именованный список (название + число позиций), а не карточки товаров. В выборку обычно попадают только списки, в которых есть элементы (пустые «Подарки» / «Планы» могут не показываться). Если с tplWrapper видна одна строка — проверьте чанк обёртки (плейсхолдер для всех строк, например [[+output]] в pdoTools); ниже живой вывод без tplWrapper и с явным limit = 0.
Вызов:
<div class="vstack gap-1">
{'!ms3FavoritesLists' | snippet : [
'tpl' => 'tplMs3fListsRow',
'limit' => 0,
'sortby' => 'name',
'sortdir' => 'ASC'
]}
</div>
<!-- Вариант с tplWrapper (в чанке обёртки — плейсхолдер [[+output]]): -->
{'!ms3FavoritesLists' | snippet : [
'tpl' => 'tplMs3fListsRow',
'tplWrapper' => 'tplMs3fListsWrapper',
'limit' => 0,
'sortby' => 'name',
'sortdir' => 'ASC'
]}
<!-- Только имена и счётчики, без ms3f_ids: -->
{'!ms3FavoritesLists' | snippet : [
'tpl' => 'tplMs3fListsRow',
'withItems' => '0',
'limit' => 0
]}
Живой вывод (без tplWrapper, limit = 0, сортировка по имени)
Тот же сниппет для resource_type = resources
Постановка: разметка страницы шаринга по токену из URL (/wishlist/share?token=…), а не кнопка «поделиться» из примера 14.
Вызов: в шаблоне ресурса share — документация ms3FavoritesShare. Без токена в URL сниппет должен вернуть текст из лексикона ms3favorites_share_not_found; если на странице пусто — проверьте лексикон и кэш. Ниже — вызов на этой странице и ссылка с заведомо неверным token для проверки разметки.
{'!ms3FavoritesShare' | snippet}
{'!ms3FavoritesShare' | snippet : [
'tpl' => 'tplFavoritesItem',
'emptyTpl' => 'tplFavoritesEmpty',
'wrapperTpl' => 'tplFavoritesSharePage'
]}
Результат {'!ms3FavoritesShare' | snippet} здесь (без ?token= в адресе этой страницы):
Если блок выше пустой — откройте страницу шаринга с токеном (валидным из «Скопировать ссылку») или с тестовой строкой: