Друзья, сегодня мы с вами сделаем вывод товаров в категорию с помощью снипета mFilter2, который идет в компоненте mSearch2. Данный компонент платный, но я советую вам его приобрести. Если у вас по каким-то причинам это сделать не получается, то вы можете сделать аналогичный вывод товаров, используя pdoPage.
Конечно, чтобы нам вывести товары в категориях, их сначала необходимо создать. Что я и сделал. Сначала я создал несколько брендов:

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

И, конечно, нужно создать товары. Я создал 27 товаров из разных ниш:

Что ж приступим! Для начала предлагаю вам создать чанк для вывода товара, который я называл productTpl, для этого создаем файл в директории core/elements/chunks со следующим содержимым:
<!-- item -->
<div class="col-xl-4 col-lg-6 col-md-6 item-wrap">
<div class="item eqh">
<div class="image">
<div class="labels">
{if $favorite?}
<div>
<span class="labels-hits">Хит продаж</span>
</div>
{/if}
{if $new?}
<div>
<span class="labels-new">Новинка</span>
</div>
{/if}
{if $old_price > 0?}
<div>
<span class="labels-action">Акция</span>
</div>
{/if}
</div>
<div class="bigger">
<a href="{$id | url}" class="text-center" style="display:block">
{if $image?}
<img src="{$image}" alt="{$pagetitle | htmlent}" title="{$pagetitle | htmlent}" class="img-fluid">
{else}
<img src="{$_modx->getPlaceholder('+noimage')}" alt="{$pagetitle | htmlent}" title="{$pagetitle | htmlent}" class="img-fluid">
{/if}
</a>
{if $_pls['vendor.logo']?}
<img src="{$_pls['vendor.logo']}" class="vendor" alt="{$_pls['vendor.name']}">
{/if}
</div>
</div>
<div class="text">
<a href="{$id | url}" class="title">{$pagetitle}</a>
<div class="features">
</div>
<div class="characters">
{$_modx->runSnippet('msProductOptions', [
'onlyOptions' => $parent | resource: 'options',
'product' => $id,
'tpl' => '@FILE chunks/main_opts.tpl'
])}
</div>
<div class="prices-block">
<div class="prices">
{if $old_price > 0?}
<div class="old_price">
<span>{$old_price}</span> <i class="fa fa-ruble"></i>
</div>
{/if}
{if $price > 0?}
<div class="price">
<span>{$price}</span> <i class="fa fa-ruble"></i>
</div>
{/if}
</div>
<form method="post" class="ms2_form">
<input type="hidden" name="id" value="{$id}">
<input type="hidden" name="count" value="1">
<input type="hidden" name="options" value="[]">
<button type="submit" name="ms2_action" value="cart/add" class="btn btn-blue btn-rounded">В корзину</button>
</form>
</div>
</div>
</div>
</div>
<!-- / item -->
Как вы уже успели заметить я не зря заполнял опции товаров, так как по нашему макету на каждой плашке карточки товара должны выводиться основные характеристики товара. Как мы их будем задавать? Я решил, что проще всего это будет сделать следующим образом:
- создать TV-параметр options и привязать его к шаблону “Каталог товаров”
- и для дочерних товаров выводить только те опции, которые указаны в TV
За вывод данного блока у нас отвечает конструкция:
{$_modx->runSnippet('msProductOptions', [
'onlyOptions' => $parent | resource: 'options',
'product' => $id,
'tpl' => '@FILE chunks/main_opts.tpl'
])}
Подробно по каждому из параметров я расскажу в видео. И собственно, чанк main_opts.tpl:
{foreach $options as $option}
<div class="char-item">
<span class="label">{$option.caption}:</span>
<span class="value">
{if $option.value is array}
{$option.value | join : ', '}
{if $option.measure_unit?}
{$option.measure_unit}
{/if}
{else}
{$option.value}
{if $option.measure_unit?}
{$option.measure_unit}
{/if}
{/if}
</span>
</div>
{/foreach}
Кстати, именно на этом чанке видно основное преимущество Fenom над стандартным шаблонизатором MODx: чанков становится на порядок меньше. Мы с вами в прошлом курсе уже разбирали как делать фильтр на mSearch2, думаю, что вы легко справитесь с предстоящей задачей.
Создадим с вами первый чанк mfilter_outer.tpl (я напоминаю, что все чанки у нас хранятся в файлах):
<div class="row msearch2" id="mse2_mfilter">
<div class="col-lg-3">
<div class="sidebar">
<!-- widget -->
<div class="widget filters">
<span class="title">Фильтр</span>
<div class="widget-content">
<form action="{$id | url}" method="post" id="mse2_filters">
{$filters}
</form>
</div>
</div>
<!-- / widget -->
</div>
</div>
<div class="col-lg-9">
<div class="product-list">
<div class="row" id="mse2_results">
{$results}
</div>
<div class="paging mse2_pagination">
<nav>
{'page.nav' | placeholder}
</nav>
</div>
</div>
</div>
</div>
Обратите особое внимание на классы и идентификаторы, которые прописаны в чанке. Теперь нам можно попробовать сделать вызов mFilter2 в шаблоне:
{$_modx->runSnippet('!mFilter2', [
'element' => 'msProducts',
'class' => 'msProduct',
'showEmptyFilters' => 1,
'limit' => 9,
'tplOuter' => '@FILE chunks/mfilter_outer.tpl',
'tplPageWrapper' => '@INLINE <ul class="pagination">{$prev}{$pages}{$next}</ul>',
'tplPageActive' => '@INLINE <li class="page-item"><a class="page-link" href="{$href}">{$pageNo}</a></li>',
'tplPage' => '@INLINE <li class="page-item"><a class="page-link" href="{$href}">{$pageNo}</a></li>',
'tplPagePrev' => '@INLINE <li class="page-item"><a class="page-link" href="{$href}">«</a></li>',
'tplPageNext' => '@INLINE <li class="page-item"><a class="page-link" href="{$href}">»</a></li>',
'tplPagePrevEmpty' => '@INLINE <li class="page-item"><a class="page-link" href="{$href}">«</a></li>',
'tplPageNextEmpty' => '@INLINE <li class="page-item"><a class="page-link" href="{$href}">»</a></li>',
'filters' => 'msoption|sndvvatt,
msoption|sndvvolume,
ms|price:number,
msoption|pokroi,
msoption|rukzkolvootd,
msoption|rukzvolume,
msoption|smartobpam,
msoption|smartoppam',
'aliases' => 'msoption|sndvvatt==sndvvatt,
msoption|sndvvolume==sndvvolume,
ms|price==price,
msoption|pokroi==pokroi,
msoption|rukzkolvootd==rukzkolvootd,
msoption|rukzvolume==rukzvolume,
msoption|smartobpam==smartobpam,
msoption|smartoppam==smartoppam',
'ajaxMode' => 'button',
'filterOptions' => '{
"more_tpl": "<div class=\"text-center\"><button class=\"btn btn-blue btn-rounded btn_more\">Загрузить еще</button></div>"
}',
'tpls' => '@FILE chunks/product_tpl.tpl',
])}
Нам с вами осталось привести в порядок фильтры. Для начала мы с вами переведем все опции, которые у нас не переведены. Для этого переходим в “Управление словарями”, выбираем топик “mSearch2” и язык “RU”. И добавляем наши опции:

После перевода нам нужно добавить еще 4 параметра в вызов mFilter2. Эти параметры, как вы уже догадались, чанки, которые нам также необходимо создать в папке core/elements/chunks. Вызов mFilter2 у нас приобретет следующий вид:
{$_modx->runSnippet('!mFilter2', [
'element' => 'msProducts',
'class' => 'msProduct',
'showEmptyFilters' => 1,
'limit' => 9,
'tplOuter' => '@FILE chunks/mfilter_outer.tpl',
'tplPageWrapper' => '@INLINE <ul class="pagination">{$prev}{$pages}{$next}</ul>',
'tplPageActive' => '@INLINE <li class="page-item"><a class="page-link" href="{$href}">{$pageNo}</a></li>',
'tplPage' => '@INLINE <li class="page-item"><a class="page-link" href="{$href}">{$pageNo}</a></li>',
'tplPagePrev' => '@INLINE <li class="page-item"><a class="page-link" href="{$href}">«</a></li>',
'tplPageNext' => '@INLINE <li class="page-item"><a class="page-link" href="{$href}">»</a></li>',
'tplPagePrevEmpty' => '@INLINE <li class="page-item"><a class="page-link" href="{$href}">«</a></li>',
'tplPageNextEmpty' => '@INLINE <li class="page-item"><a class="page-link" href="{$href}">»</a></li>',
'filters' => 'msoption|sndvvatt,
msoption|sndvvolume,
ms|price:number,
msoption|pokroi,
msoption|rukzkolvootd,
msoption|rukzvolume,
msoption|smartobpam,
msoption|smartoppam',
'aliases' => 'msoption|sndvvatt==sndvvatt,
msoption|sndvvolume==sndvvolume,
ms|price==price,
msoption|pokroi==pokroi,
msoption|rukzkolvootd==rukzkolvootd,
msoption|rukzvolume==rukzvolume,
msoption|smartobpam==smartobpam,
msoption|smartoppam==smartoppam',
'ajaxMode' => 'button',
'filterOptions' => '{
"more_tpl": "<div class=\"text-center\"><button class=\"btn btn-blue btn-rounded btn_more\">Загрузить еще</button></div>"
}',
'tpls' => '@FILE chunks/product_tpl.tpl',
'tplFilter.outer.default' => '@FILE chunks/filter_outer.tpl',
'tplFilter.row.default' => '@FILE chunks/filter_row.tpl',
'tplFilter.outer.price' => '@FILE chunks/filter_outer_price.tpl',
'tplFilter.row.price' => '@FILE chunks/filter_row_price.tpl'
])}
Чанк filter_outer.tpl – это стандартная обертка для наших фильтров:
<div class="widget-filter" id="mse2_{$table}{$delimeter}{$filter}">
<h4 class="filters-title">{$_modx->lexicon('mse2_filter_'~$table~'_'~$filter)}</h4>
<div class="widget-content">
{$rows}
</div>
</div>
Чанк filter_row.tpl – это вывод опции в виде чекбокса:
<div class="custom-control custom-checkbox">
<input type="checkbox" name="{$filter_key}" id="mse2_{$table}{$delimeter}{$filter}_{$idx}" value="{$value}" {$checked} {$disabled} class="custom-control-input">
<label for="mse2_{$table}{$delimeter}{$filter}_{$idx}" class="{$disabled} custom-control-label">
{$title}
</label>
</div>
Чанк filter_outer_price.tpl – обертка для фильтра цены (в виде слайдера):
<div class="widget-filter" id="mse2_{$table}{$delimeter}{$filter}">
<h4 class="filters-title">{$_modx->lexicon('mse2_filter_'~$table~'_'~$filter)}</h4>
<div class="widget-content">
<fieldset>
<div class="mse2_number_inputs row">
{$rows}
</div>
<div class="mse2_number_slider"></div>
</fieldset>
</div>
</div>
Чанк filter_row_price.tpl – это элемент при выводе слайдера цены (минимальное и максимальное значение):
<div class="col-6">
<label for="mse2_{$table}{$delimeter}{$filter}_{$idx}">{$title}
<input type="text" name="{$filter_key}" id="mse2_{$table}{$delimeter}{$filter}_{$idx}" value="{$value}">
</label>
</div>
Как видите все достаточно просто. Мы с вами реализовали вывод товаров в категории. Если вы попытаетесь кликнуть по кнопке “В корзину”, товар туда добавится. На этом мы с вами закончим нашу 4 часть.