Skip to content
msBundles
msBundles
Комплекты товаров miniShop3 с общей ценой, скидкой и синхронизацией в корзине
  • MODX 3
  • PHP 8.2
  • miniShop3
  • Vue 3
  1. Компоненты
  2. msBundles
  3. Витрина
  4. Вывод на сайте

Вывод на сайте ​

Вызов сниппета, плейсхолдеры чанков, CSS/JS и комплект в корзине. Параметры: Сниппеты.

Требования:

  1. msBundles.initialize в <head> — без него кнопки и стили не работают.
  2. selector у msCart — иначе toast появится, а блок не обновится.

Как выглядит карточка ​

Карточка: Рабочее место

Карточка: Набор для путешествий

Карточка: qty=2

Карточка: Кухня (fixed + optional)

Готовая страница товара ​

fenom
{extends 'file:templates/base.tpl'}

{block 'head'}
  {'!msBundles.initialize' | snippet}
{/block}

{block 'content'}
  <h1>{$_modx->resource.pagetitle}</h1>

  {'!msBundles' | snippet : [
    'product' => $_modx->resource.id,
    'tpl' => 'tplMsBundlesItem',
    'wrapperTpl' => 'tplMsBundlesList',
    'emptyTpl' => 'tplMsBundlesEmpty',
    'activeOnly' => true,
    'quantity' => 1
  ]}

  <section id="ms-cart" data-ms-cart aria-live="polite">
    {'!msCart' | snippet : [
      'tpl' => 'tpl.msCart',
      'return' => 'tpl',
      'selector' => '#ms-cart'
    ]}
  </section>
{/block}
modx
[[!msBundles.initialize]]

[[!msBundles?
  &product=`[[*id]]`
  &tpl=`tplMsBundlesItem`
  &wrapperTpl=`tplMsBundlesList`
  &emptyTpl=`tplMsBundlesEmpty`
  &activeOnly=`1`
  &quantity=`1`
]]

<div id="ms-cart" data-ms-cart aria-live="polite">
  [[!msCart?
    &tpl=`tpl.msCart`
    &return=`tpl`
    &selector=`#ms-cart`
  ]]
</div>

Хук в tplMsCart ​

В tpl.msCart: tplMsBundlesCartInfo.

Логика выбора ​

Сниппет, REST и корзина учитывают текущий контекст. Комплект с другим context_key не появится.

  1. bundle > 0 — один комплект по ID. Неактивный при activeOnly=1 или чужой контекст → пусто.
  2. bundles — комплекты по списку ID (порядок как в параметре).
  3. list=all — все комплекты контекста.
  4. product > 0 — комплекты с этим товаром.
  5. Иначе — emptyTpl.

При msbundles_stock_behavior=hide и недоступном остатке карточка не выводится. Режимы block / message показывают карточку с --blocked / --warning.

Страница каталога ​

Несколько карточек по списку ID:

fenom
{'!msBundles' | snippet : [
  'bundles' => '5,8,12',
  'wrapperTpl' => 'tplMsBundlesList',
  'tpl' => 'tplMsBundlesItem'
]}
{'!msBundles.initialize' | snippet}
modx
[[!msBundles?
  &bundles=`5,8,12`
  &wrapperTpl=`tplMsBundlesList`
  &tpl=`tplMsBundlesItem`
]]
[[!msBundles.initialize]]

Все комплекты контекста:

fenom
{'!msBundles' | snippet : [
  'list' => 'all',
  'wrapperTpl' => 'tplMsBundlesList'
]}
{'!msBundles.initialize' | snippet}
modx
[[!msBundles?
  &list=`all`
  &wrapperTpl=`tplMsBundlesList`
]]
[[!msBundles.initialize]]

Тема сайта (CSS) ​

Переопределите токены на .msbundles (или :root):

css
.msbundles {
  --msbundles-color-accent: #your-brand;
  --msbundles-color-accent-hover: #your-brand-dark;
  --msbundles-color-text: #111827;
  --msbundles-color-muted: #6b7280;
  --msbundles-color-border: #e5e7eb;
  --msbundles-color-surface: #fff;
  --msbundles-radius: 0.5rem;
}
ПеременнаяНазначение
--msbundles-color-accentАкцент: ссылки, кнопка, бейджи
--msbundles-color-accent-hoverHover / active
--msbundles-color-textТекст
--msbundles-color-mutedВторичный текст
--msbundles-color-borderРамки
--msbundles-color-surfaceФон
--msbundles-radiusСкругление

Другие токены в msbundles.css: --msbundles-space-*, --msbundles-font-*, --msbundles-color-savings, --msbundles-color-free*, --msbundles-color-warning*, --msbundles-color-danger*, --msbundles-shadow-sm, --msbundles-focus-ring.

Полная таблица CSS-переменных ​

ПеременнаяНазначениеПо умолчанию
--msbundles-color-accentАкцент: ссылки, кнопка, бейджи#2563eb
--msbundles-color-accent-hoverHover / active для акцента#1d4ed8
--msbundles-color-textОсновной текст#111827
--msbundles-color-mutedВторичный текст#6b7280
--msbundles-color-borderРамки и разделители#e5e7eb
--msbundles-color-surfaceФон карточки#fff
--msbundles-color-savingsБейдж экономии (зелёный)#16a34a
--msbundles-color-freeБейдж «бесплатно»#2563eb
--msbundles-color-warningПредупреждения (жёлтый)#ca8a04
--msbundles-color-warning-bgФон предупреждений#fef9c3
--msbundles-color-dangerОшибки, нет остатка (красный)#dc2626
--msbundles-color-danger-bgФон ошибок#fee2e2
--msbundles-radiusСкругление углов0.5rem
--msbundles-shadow-smТень карточки0 1px 2px rgba(0,0,0,0.05)
--msbundles-focus-ringОбводка фокуса0 0 0 3px rgba(37,99,235,0.2)
--msbundles-font-familyШрифтinherit
--msbundles-font-sizeРазмер шрифта1rem
--msbundles-space-1Отступ 0.25rem0.25rem
--msbundles-space-2Отступ 0.5rem0.5rem
--msbundles-space-3Отступ 0.75rem0.75rem
--msbundles-space-4Отступ 1rem1rem
--msbundles-space-5Отступ 1.25rem1.25rem
--msbundles-space-6Отступ 1.5rem1.5rem

Плейсхолдеры карточки (tplMsBundlesItem) ​

ПлейсхолдерОписание
idID комплекта
nameНазвание
descriptionОписание
image, image_urlURL изображения
image_htmlБлок <div class="msbundles__media">…</div> или пусто
has_image0 / 1 — есть изображение
active0 / 1 — активен
quantityЧисло комплектов
item_countСумма единиц в составе
product_countЧисло позиций
productsHTML строк состава
composition_htmlСекция «Состав» или пусто
price_htmlЦена, зачёркнутая, экономия. При 0 — «Бесплатно»
totalИтог (число)
original_totalСумма по оригинальным ценам
savingsЭкономия (число)
total_formattedИтог с валютой или «Бесплатно»
original_total_formattedОригинал с валютой
savings_formattedЭкономия с валютой
available0 / 1 — можно заказать
stock_messageСообщение об остатках
state_modifiermsbundles__item--available / --warning / --blocked
stock_behaviorblock, message, hide
max_bundle_quantityЛимит поля «Комплектов»

tplMsBundlesList: items — HTML карточек. Без image и при imageFallback=1 берётся thumb первого товара.

Плейсхолдеры строки состава (tplMsBundlesProduct) ​

Штатный чанк использует {$product_id}. В своём чанке для атрибутов как в каталоге MS3 можно дублировать id = product_id.

ПлейсхолдерОписание
product_idID товара
nameНазвание
name_htmlНазвание в ссылке или без неё
articleАртикул
urlURL товара
image, image_urlThumb или картинка
image_htmlБлок <span class="msbundles__product-media">…</span> или пусто
quantityЕдиниц товара в составе
bundle_quantityЧисло комплектов
line_quantityquantity × bundle_quantity
required0 / 1 — обязательная позиция
required_label«Обязательно» / «Необязательно»
price_modeoriginal, fixed, discount_percent, discount_amount, free
is_free0 / 1 — цена строки 0
unit_priceЦена единицы с учётом режима
unit_price_formattedЦена с валютой или «Бесплатно»
unit_price_html<span class="msbundles__price">…</span>
original_unit_priceОригинальная цена единицы
original_unit_price_formattedОригинал с валютой
original_price_htmlЗачёркнутая цена или пусто
line_totalИтог строки
line_total_formattedИтог с валютой или «Бесплатно»

Кастомный чанк карточки ​

Классы (msbundles__*) — для стилей. JS ищет узлы по data-атрибутам.

fenom
<article
  data-msbundles="item"
  data-ms-bundle="{$id}"
  data-ms-bundle-available="{$available}"
>
  <h3>{$name}</h3>
  <div data-msbundles="summary">
    <p data-msbundles="price">{$price_html}</p>
    <span data-msbundles="product-count">{$product_count}</span>
  </div>
  <p data-msbundles="stock" role="status">{$stock_message}</p>
  <div data-msbundles="controls">
    <button type="button" data-msbundles-action="qty-dec" data-ms-bundle="{$id}">−</button>
    <input type="number" data-bundle-quantity data-ms-bundle="{$id}" value="{$quantity}" min="1" />
    <button type="button" data-msbundles-action="qty-inc" data-ms-bundle="{$id}">+</button>
    <button type="button" data-msbundles-action="add-bundle" data-ms-bundle="{$id}">
      {'msbundles.add_bundle' | lexicon}
    </button>
  </div>
</article>

При смене qty calculate обновляет [data-msbundles="price"] и [data-msbundles="stock"].

Data-атрибуты витрины ​

АтрибутГдеНазначение
data-msbundles="list"обёртка спискаКорень списка
data-msbundles="item"карточкаКорень карточки
data-ms-bundleкарточка / контролыID комплекта
data-ms-bundle-availableкарточка1 / 0 после calculate
data-ms-bundle-stockкарточкаavailable / warning / blocked
data-msbundles="summary"блок итогаЯкорь для stock-сообщения
data-msbundles="price"строка ценыЗамена HTML после calculate
data-msbundles="product-count"число позицийМета
data-msbundles="stock"сообщение остаткаОшибки и предупреждения
data-msbundles="controls"qty + CTAГруппа действий
data-bundle-quantityinputЗначение «Комплектов»
data-msbundles-action="qty-dec" / qty-incкнопкиСтеппер количества
data-msbundles-action="add-bundle"кнопкаДобавление в корзину
data-msbundles="product"строка составаПозиция комплекта
data-product-idстрока составаID товара
data-msbundles="product-price"цена позицииЦена в составе
data-msbundles="cart-info"блок в корзинеLead/member метаданные
data-bundle-hashcart-info / строка корзиныСвязка строк комплекта
data-bundle-leadcart-info / строка1 = lead
data-msbundles="name"название в корзинеИмя для confirm/remove
data-msbundles-action="remove-bundle"кнопкаУдаление комплекта

Legacy data-action принимается. Классы можно менять: JS использует data-атрибуты.

Подключение CSS и JS ​

Через сниппет:

fenom
{'!msBundles.initialize' | snippet}
modx
[[!msBundles.initialize]]

Сниппет подключает msbundles.css, msbundles-helpers.js, msbundles-cart.js, msbundles.js. Выводит window.msbundlesConfig и window.msbundlesLexicon.

КлючПо умолчаниюНазначение
autoBindtrueАвтопривязка обработчиков
apiBaseUrl''Префикс API, если Router не на стандартном пути
maxBundleQuantityиз настройкиЛимит поля qty
calculateDebounceMs350Дебаунс пересчёта
confirmRemoveBundlefalseconfirm() перед удалением

Свой конфиг до вызова:

html
<script>window.msbundlesConfig = { confirmRemoveBundle: true };</script>

«Удалить комплект» работает как обычный товар: ms3.cartUI.handleRemove, toast, плагин снимает остальные позиции. REST /bundle/remove — если нет ключа товара.

Add и remove требуют ms3_token.

События JS ​

СобытиеКогда
msbundles:beforeПеред calculate / add / remove
msbundles:successУспешное add / remove
msbundles:errorОшибка API
msbundles:updatedПересчёт цены или изменение
html
<script>
document.addEventListener('msbundles:success', function (e) {
  console.log(e.detail.action, e.detail.result);
});
</script>

После add/remove: ms3.message и MsBundles.refreshCart().

Публичный API window.MsBundles ​

МетодНазначение
bindОбработчики
addBundlePOST add
removeBundleУдаление по bundle_hash, если обычный путь корзины недоступен
calculateПересчёт цены и остатков
applyCalculateToCardОбновить цену на карточке
enhanceCartDisplayLead/member UI
applyCartRenderFallbackHTML, если MS3 не отдал render
refreshCartGET /api/v1/cart/get с render и обновить блоки msCart
requestНизкоуровневый API
eventsИмена событий

Комплект в корзине ​

Комплект — несколько строк miniShop3 с общим bundle_hash. Главная строка управляет количеством, остальные следуют. Кнопка «Удалить комплект» снимает весь набор.

Без selector у msCart toast появится, а блок не обновится. selector регистрирует блок в ms3Config.render.cart. После add/remove фронт запрашивает /api/v1/cart/get.

Хук в tpl.msCart ​

Под названием товара:

fenom
{if $product.options.msbundles?}
    {'tplMsBundlesCartInfo' | chunk : $product.options.msbundles}
{elseif $product.options.bundle_hash?}
    {'tplMsBundlesCartInfo' | chunk : [
        'id' => $product.options.bundle_id,
        'name' => $product.options.bundle_name,
        'hash' => $product.options.bundle_hash
    ]}
{/if}

На <tr> добавьте data-bundle-hash из options.

Цену со скидкой: актуальную в msbundles-cart-price__now, старую — в msbundles-cart-price__was.

Плейсхолдеры tplMsBundlesCartInfo ​

ПлейсхолдерОписание
idID комплекта
nameНазвание
hashbundle_hash
is_lead1 у главной строки
line_indexНомер строки (1 = главная)
lines_countПозиций в комплекте
quantityЧисло комплектов
СтрокаКак понятьЧто видит покупатель
Главнаяis_lead=1 или line_index=1Бейдж «Комплект», «Удалить комплект»
Остальныетот же bundle_hash«Входит в комплект…»

Старые строки без options.msbundles: плагин на msOnGetCart помечает главную для бейджа.

Что делает JS ​

enhanceCartDisplay:

  • помечает строки атрибутами и классами
  • прячет крестик у позиций комплекта
  • блокирует количество у «вложенных» строк
  • оставляет количество только у главной строки

При смене количества плагин (msOnBeforeChangeInCart) пересчитывает остальные.

При удалении строки: ms3.cartUI.handleRemove. Плагин (msOnBeforeRemoveFromCart) снимает остальные. REST /bundle/remove — если обычный путь недоступен. См. События.

msbundles.css стилизует блок. Логика использует data-msbundles и data-bundle-*. Превью: 4rem (object-fit: cover).

Без tplMsBundlesCartInfo бейдж и кнопка не появятся.

Если корзина не обновляется ​

СимптомЧто проверить
Toast есть, блок старыйНет selector у msCart или id контейнера не совпадает
Нет бейджейНет tplMsBundlesCartInfo или msOnGetCart
Qty у member кликабеленJS не загрузился или enhanceCartDisplay не вызван после ручной перерисовки

REST ​

База: /api/v1/msbundles через роутер miniShop3.

text
/assets/components/minishop3/api.php?route=/api/v1/msbundles/…

Нужен ms3_token. Штатный msbundles.js ходит в маршруты сам.

Ответ:

json
{
  "success": true,
  "message": "",
  "data": {},
  "errors": [],
  "warnings": []
}

Ошибка: HTTP 4xx, success: false, data: null.

GET /bundle/calculate ​

Пересчёт цены и остатков.

Query: id или bundle_id, quantity.

Пример: …/api.php?route=/api/v1/msbundles/bundle/calculate&id=5&quantity=2

data: { "price": {…}, "stock": {…}, "quantity": N }

ПолеОписание
total_formattedИтог с валютой или «Бесплатно»
original_total_formattedОригинал
savings_formattedЭкономия
price_htmlHTML для [data-msbundles="price"]

POST /bundle/add ​

Добавить комплект. Лимит: msbundles_max_bundle_quantity.

Body:

json
{
  "id": 5,
  "quantity": 2
}

Алиас: bundle_id.

data:

json
{
  "bundle_hash": "a1b2c3…",
  "quantity": 2
}

Необязательные позиции с нулевым остатком могут уйти в warnings. Остальной комплект всё равно добавится.

POST /bundle/remove ​

Удалить строки с bundle_hash.

Body:

json
{
  "bundle_hash": "a1b2c3…"
}

Алиас: hash.

data:

json
{
  "removed_count": 3,
  "bundle_hash": "a1b2c3…"
}

GET /bundle/get ​

Карточка с ценой и остатками.

Query: id или bundle_id, quantity (по умолчанию 1).

data: { "bundle": {…}, "price": {…}, "stock": {…}, "quantity": N }

Маршруты ​

МетодПутьОписание
GET/bundle/calculateПересчёт цены
POST/bundle/addДобавление
POST/bundle/removeУдаление
GET/bundle/getДанные комплекта