[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
modx.pro
27
2 454
+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 тоже надо экранировать и строкой отправлять, только где и когда это отловить пока не разобрался
        Вячеслав Варов
        29 апреля 2019, 16:02
        0
        Сделал по интструкции но modx автоматически определяет фильтры как чекбоксы а надо бы слайдер и селекты такое уже не реализовать?
          Павел Гвоздь
          29 апреля 2019, 16:35
          0
          Это указывается непосредственно в сниппете mFilter2 в качестве параметра шаблона для конкретной опции фильтра.
            Вячеслав Варов
            29 апреля 2019, 16:37
            0
            получается не совсем универсально, ведь если появились опции новые пользователь не зайдет и не добавит сам шаблон
              Павел Гвоздь
              29 апреля 2019, 16:37
              0
              Так и опции сами не добавятся.
                Вячеслав Варов
                29 апреля 2019, 16:38
                0
                это можно сделать из админки и простому пользователю
                  Вячеслав Варов
                  29 апреля 2019, 17:27
                  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}
          Авторизуйтесь или зарегистрируйтесь, чтобы оставлять комментарии.
          13