[tvSuperSelect] Кейс. Удобное указание опций для фильтрации в mFilter2


Часто интернет магазину требуется большое кол-во разделов каталога (500) и такое же большое кол-во опций (200). Заказчик хочет, чтобы:
— опции можно было легко выбирать при редактировании категории товаров,
— на фронте, если у категории не выбрано ни одной опции, брать эти опции из родительской категории, у которой они указаны.
С tvSuperSelect теперь сделать это не так уж сложно, как может показаться.

Подготовка
Нам потребуется miniShop2, mSearch2, tvSuperSelect, Fenom.
И даже не просите меня помочь сделать это без Fenom!

Шаг 1
Создаём ТВ поле mfilter_options с типом tvSuperSelect и указываем такие настройки:
  • Connector URL
    /assets/custom/tvssconnector.php
  • Процессор
    tvss/getms2options
Шаг 2
Создаём файл-коннектор.
<?php
require_once dirname(dirname(dirname(__FILE__))) . '/config.core.php';
require_once MODX_CORE_PATH . 'config/' . MODX_CONFIG_KEY . '.inc.php';
require_once MODX_CONNECTORS_PATH . 'index.php';
$modx->request->handleRequest(array(
    'processors_path' => MODX_CORE_PATH . 'custom/processors/',
    'location' => '',
));
Местонахождение: /assets/custom/tvssconnector.php

Шаг 3
Создаём процессор, в котором будет происходить выборка опций miniShop2 и формирование массива для отображения в списке ComboBox.
<?php
class customTvssComboGetMs2OptionsProcessor extends modObjectProcessor
{
    public $classKey = 'msOption';
    /** @var miniShop2 $ms2 */
    protected $ms2;
    /** @var tvSuperSelect $tvss */
    protected $tvss;

    /**
     * @return bool
     */
    public function initialize()
    {
        $this->ms2 = $this->modx->getService('minishop2', 'miniShop2', MODX_CORE_PATH . 'components/minishop2/model/minishop2/');
        $this->tvss = $this->modx->getService('tvsuperselect', 'tvSuperSelect',
            $this->modx->getOption('tvsuperselect_core_path', null, MODX_CORE_PATH . 'components/tvsuperselect/') . 'model/tvsuperselect/');

        return parent::initialize();
    }

    /**
     * @return string
     */
    public function process()
    {
        $query = trim($this->getProperty('query'));
        $limit = (int)$this->getProperty('limit', 0);
        $resource_id = (int)$this->getProperty('resource_id', 0);
        if (!$tv_id = (int)$this->getProperty('tv_id', 0)) {
            //
        }
        $q = $this->modx->newQuery($this->classKey);
        $q->select(array(
            "{$this->classKey}.key as `key`",
            "{$this->classKey}.caption as `caption`",
        ));
        if (!empty($query)) {
            $q->where(array(
                "{$this->classKey}.key:LIKE" => "%{$query}%",
                "OR:{$this->classKey}.caption:LIKE" => "%{$query}%"
            ));
        }
        $q->limit($limit);
        $q->sortby($this->classKey . '.key', 'ASC');
        
        $rows = array();
        if ($q->prepare() && $q->stmt->execute()) {
            if ($tmp = $q->stmt->fetchAll(PDO::FETCH_ASSOC)) {
                foreach ($tmp as $v) {
                    $rows[] = array(
                        'display' => '(' . $v['key'] . ') <b>' . $v['caption'] . '</b>',
                        'value' => $v['key'],
                    );
                }
            }
        }
        foreach ($rows as &$row) {
            if (empty($row['display'])) {
                $row['display'] = $row['value'];
            }
        }
        unset($row);

        return $this->outputArray($rows);
    }
}

return 'customTvssComboGetMs2OptionsProcessor';
Местонахождение: /core/custom/processors/tvss/getms2options.class.php

Шаг 4
Формирование списка опций пригодного для mFilter2 и вывод на фронте будет происходить при помощи Fenom.
У вас должно получиться, что-то вроде этого:
{var $filters = []}

{* Мы можем указать свойства фильтра, которые отобразятся до опций, выбранных нами *}
{var $filters[] = 'ms|price:number'}
{var $filters[] = 'parent:categories'}

{* Магия *}
{var $mfilter_options = $_modx->resource['mfilter_options']}
{if !is_array($mfilter_options)}
    {var $mfilter_options = ($mfilter_options | fromJSON)}
{/if}
{if $mfilter_options is empty}
    {foreach $_modx->getParentIds($_modx->resource['id']) as $parent_id}
        {if $parent_id == 0}
            {continue}
        {/if}
        {var $mfilter_options = ($parent_id | resource : 'mfilter_options')}
        {if !is_array($mfilter_options) AND $mfilter_options?}
            {var $mfilter_options = ($mfilter_options | fromJSON)}
        {/if}
        {if $mfilter_options?}
            {break}
        {/if}
        {unset $mfilter_options}
    {/foreach}
{/if}
{if $mfilter_options?}
    {foreach $mfilter_options as $v}
        {var $filters[] = ('msoption|' ~ $v)}
    {/foreach}
{/if}

{* Вывод mFilter2 *}
{'!mFilter2' | snippet : [
    ...
    'filters' => ($filters | join : ','),
    ...
]}

Итого
Как видите, всё довольно просто с новой версией tvSuperSelect. С этим функционалом теперь можно и не такое сделать!
Павел Гвоздь
20 сентября 2018, 11:07
23
966
+18
Поблагодарить автора Отправить деньги

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

Олег
20 сентября 2018, 11:27
+1
Павел, спасибо за примеры! Отличное дополнение
Дмитрий
20 сентября 2018, 12:18
+1
Годный кейс, спасибо!
Aleksandr Huz
20 сентября 2018, 20:14
0
Паша, за примеры, ОГРОМНОЕ спасибо.

Очень хочется, чтобы поле работало в migx таблице.
    Павел Гвоздь
    20 сентября 2018, 20:16
    0
    Оно не работает?
      Aleksandr Huz
      20 сентября 2018, 21:09
      +1
      Не сохраняет данные.
      mngatoff
      27 октября 2018, 04:30
      0
      что-то не так на стороне js, потому что процессор migx ловит пустое значение. оно тупо не отправляется на сохранение.
      у меня две тв-шки рядом: «video» (listbox-multiply) и «gallery» (tvsuperselect)
      в migx сырые данные приходят в виде json, и этот кусок выглядит так:
      {"video":["1","2","3"],"gallery":""}
      что касается поля migx внутри migx (которое тоже json по идее), то оно экранируется и приходит вот так:
      {"somemigxfield":"[{\"field\":\"value\"}]"}
      и думается мне, что tvsuperselect тоже надо экранировать и строкой отправлять, только где и когда это отловить пока не разобрался
Сделал по интструкции но modx автоматически определяет фильтры как чекбоксы а надо бы слайдер и селекты такое уже не реализовать?
    Павел Гвоздь
    29 апреля 2019, 16:35
    0
    Это указывается непосредственно в сниппете mFilter2 в качестве параметра шаблона для конкретной опции фильтра.
      получается не совсем универсально, ведь если появились опции новые пользователь не зайдет и не добавит сам шаблон
        Павел Гвоздь
        29 апреля 2019, 16:37
        0
        Так и опции сами не добавятся.
          это можно сделать из админки и простому пользователю
            Дополнил скрипт, теперь при создании опции с типом number скрипт распознает ее и вместо вывода чекбоксами будет выводить ее бегунками от — до:) можно расширить функционал как душе угодно я добавил в getms2options.class.php switch по типу опции где помимо number можно указать и другие типы и следовательно шаблоны к ним:)

            Шаг 3 заменил на это:
            <?php
            class customTvssComboGetMs2OptionsProcessor extends modObjectProcessor
            {
                public $classKey = 'msOption';
                /** @var miniShop2 $ms2 */
                protected $ms2;
                /** @var tvSuperSelect $tvss */
                protected $tvss;
            
                /**
                 * @return bool
                 */
                public function initialize()
                {
                    $this->ms2 = $this->modx->getService('minishop2', 'miniShop2', MODX_CORE_PATH . 'components/minishop2/model/minishop2/');
                    $this->tvss = $this->modx->getService('tvsuperselect', 'tvSuperSelect',
                        $this->modx->getOption('tvsuperselect_core_path', null, MODX_CORE_PATH . 'components/tvsuperselect/') . 'model/tvsuperselect/');
            
                    return parent::initialize();
                }
            
                /**
                 * @return string
                 */
                public function process()
                {
                    $query = trim($this->getProperty('query'));
                    $limit = (int)$this->getProperty('limit', 0);
                    $resource_id = (int)$this->getProperty('resource_id', 0);
                    if (!$tv_id = (int)$this->getProperty('tv_id', 0)) {
                        //
                    }
                    $q = $this->modx->newQuery($this->classKey);
                    $q->select(array(
                        "{$this->classKey}.key as `key`",
                        "{$this->classKey}.caption as `caption`",
            			"{$this->classKey}.type as `type`",
                    ));
                    if (!empty($query)) {
                        $q->where(array(
                            "{$this->classKey}.key:LIKE" => "%{$query}%",
                            "OR:{$this->classKey}.caption:LIKE" => "%{$query}%"
                        ));
                    }
                    $q->limit($limit);
                    $q->sortby($this->classKey . '.key', 'ASC');
                    
                    $rows = array();
                    if ($q->prepare() && $q->stmt->execute()) {
                        if ($tmp = $q->stmt->fetchAll(PDO::FETCH_ASSOC)) {
                            foreach ($tmp as $v) {
            					switch ($v['type']) {
            						case 'numberfield':
            							$v['type'] = 'number';
            							break;
            						default:
            							$v['type'] = '';
            							break;
            					}
                                
            						
            						if ($v['type'] != '') {
            							$rows[] = array(
                                    'display' => '(' . $v['key'] . ') <b>' . $v['caption'] . '</b>',
            						'value' => $v['key'] . ':' .  $v['type'],
                                );
            						} else {
            							$rows[] = array(
                                    'display' => '(' . $v['key'] . ') <b>' . $v['caption'] . '</b>',
            						'value' => $v['key'],
                                );
            						}
                            }
                        }
                    }
                    foreach ($rows as &$row) {
                        if (empty($row['display'])) {
                            $row['display'] = $row['value'];
                        }
                    }
                    unset($row);
            
                    return $this->outputArray($rows);
                }
            }
            
            return 'customTvssComboGetMs2OptionsProcessor';

            Шаг 4 заменил на это:
            {var $filters = []}
            
            {* Мы можем указать свойства фильтра, которые отобразятся до опций, выбранных нами *}
            {var $filters[] = 'ms|vendor:vendors'}
            {var $filters[] = 'msop|price:number'}
            
            {* Магия *}
            {var $mfilter_options = $_modx->resource['mfilter_options']}
            {if !is_array($mfilter_options)}
                {var $mfilter_options = ($mfilter_options | fromJSON)}
            {/if}
            {if $mfilter_options is empty}
                {foreach $_modx->getParentIds($_modx->resource['id']) as $parent_id}
                    {if $parent_id == 0}
                        {continue}
                    {/if}
                    {var $mfilter_options = ($parent_id | resource : 'mfilter_options')}
                    {if !is_array($mfilter_options) AND $mfilter_options?}
                        {var $mfilter_options = ($mfilter_options | fromJSON)}
                    {/if}
                    {if $mfilter_options?}
                        {break}
                    {/if}
                    {unset $mfilter_options}
                {/foreach}
            {/if}
            {if $mfilter_options?}
                {foreach $mfilter_options as $v}
                    {var $filters[] = ('msoption|' ~ $v)}
                {/foreach}
            {/if}
            
            {* Все параметры mFilter2 указываем тут *}
            {var $options = [
                      'element'=>'msProducts',
                      'parents'=> $_modx->resource.id,
                      'setTotal'=>'1',
                      'totalVar'=>'total',
                      'loadModels'=>'easycomm',
                      'includeTVs'=>'smallDescription',
                      'tplOuter'=>'tpl.catalogWrap',
                      'tpl'=>'catalog.Row',
                      'limit'=>'20',
                      'ajaxMode'=>'button',
                      'includeThumbs'=>'small',
                      'filterOptions'=>'{
                                "more_tpl":"<button class=\"standart-button align-center how-more-button btn_more btn w-button\">Показать ещё 20 товаров<\/button>"
                        }',
                      'leftJoin'=>'{
            	                "ecThread": {
            		                "class": "ecThread",
            	                	"on": "msProduct.id = ecThread.resource"
            	            }
                     }',
                    'aliases'=>'
                         ms|price==price,
                         resource|parent==parent,
                         ms|vendor==vendor
                      ',
                      'sortAliases'=>'{
                      "ecThread":"ecThread"
                      }',
                      'sort'=>'ms|menuindex:asc',
                      'suggestionsRadio'=>'vendor:vendors',
                      'class'=>'msProduct',
                      'filters' => ($filters | join : ','),
                      'tplFilter.outer.msop|price'=>'tpl.mFilter2.filter.slider',
            	      'tplFilter.row.msop|price'=>'tpl.mFilter2.filter.number',
                      'tplFilter.outer.vendor'=>'tpl.mfilter2.filter.select',
                      'tplFilter.row.vendor'=>'tpl.mfilter2.filter.option'
            ]}
            {var $tplFilters = []}
            {foreach $filters as $row}
                    {if ':number' | in : $row}
                        {* тут мы проверяем какого типа у нас опция  *}
                        {var $row = $row | replace : ":number" : ""}
                            {* удалив название типа из строки формируем шаблоны для вывода *}
                            {set $tplFilters['tplFilter.outer.' ~ $row] = 'tpl.mFilter2.filter.slider'}
                            {set $tplFilters['tplFilter.row.' ~ $row] = 'tpl.mFilter2.filter.number'}
                    {/if}
            {/foreach}
            
            {* Соединяем два массива - один с опциями mfilter2 другой c нашими шаблонами  ОБЯЗАТЕЛЬНО должен быть включен параметр "использование PHP в FENOM, если кто знает как выполнить слияние двух массивов средствами Fenom иначе - поделитесь *} 
            {set $options = array_merge($tplFilters, $options)}
            
            {* Вывод mFilter2 *}
            {'!mFilter2' | snippet : $options}
Авторизуйтесь или зарегистрируйтесь, чтобы оставлять комментарии.