Таксономия в modx revo (custom TV input)

Таксономия, поправьте, если я не прав, — это такой способ классификации и систематизации, при котором один объект может относиться к нескольким классам. Например, у нас есть терьер Тузик, овчарка Рекс, кот Барсик и рыбка Немо. И есть такая структура:

  • Млекопитающие
    • Собаки
      • терьеры
      • овчарки
    • Коты
  • Рыбы

Тузик, будучи терьером, относится и к собакам, и к млекопитающим. То же самое с Рексом, он — собака и млекопитающее. Барсик относится к котам и млекопитающим, а Немо – рыба и млекопитающим не является.

Таксономия может быть очень полезна при разработке сайтов-каталогов, в которых один объект так же может относиться к нескольким категориям и группам категорий. Вот, например, каталог артистов. Артист может быть одновременно и музыкантом, и ведущим, и фокусником. Кроме того, музыку он может исполнять в различных стилях.

Таксономия «из коробки» имеется во многих CMS. Особенно хорошо этот термин знаком пользователям Drupal. Для моего любимого ModX существует плагин Taxonomies, но работает он… несколько странно… Надо что-то делать.

Начнем

Для начала создадим структуру каталога в дереве ресурсов. Для ресурсов в этой структуре нам понадобится отдельный шаблон — назовем его «section». Кроме того, шаблон понадобится и для объектов — назовем его «object».

listТак как каждый объект каталога у нас может принадлежать к нескольким категориям, нет никакого смысла хранить объекты внутри категорий. Можно было бы, конечно, создать ресурс внутри одной из категорий, а в остальных сделать ссылку на него, но это, по-моему, извращение. Работать с таким каталогом будет очень не удобно. Поэтому для хранения объектов создадим ещё один ресурс, который будет нашей «Базой данных» :-) Чтоб не потеряться среди тысяч дочерних ресурсов «базы» имеет смысл воспользоваться плагином Collections.

Теперь нужно объекты из «базы» как-то привязать к структуре каталога. Причём каждый объект может быть привязан сразу к нескольким категориям. Более того, при выборе дочерней категории, было бы неплохо сразу отмечать и родительские — именно поэтому мы не можем использовать теги — у них нет структуры, они не бывают дочерними/родительскими. В принципе, мы могли бы создать отдельное TV с тегами для каждой группы категорий каталога. Тогда само TV обозначало бы группу, у которой были бы дочерние элементы — теги, но таких TV может получится слишком много, да и каждое TV — это лишние запросы в БД.

TV cо своими параметрами ввода

Итак, нам нужен TV, в котором будет храниться список родительских ресурсов, и нам нужен удобный способ редактирования этого списка. То есть нужен свой TV input. Я сделал такой:

tv-custom-input

Как делаются custom TV input? Нужно создать 4 файла:

  • /core/model/modx/processors/element/tv/renders/mgr/input/taxonomytv.class.php — основной код на php, который передает данные в шаблон TV.
  • /core/model/modx/processors/element/tv/renders/mgr/inputproperties/taxonomytv.php — вызов шаблона с дополнительными настройками TV.
  • /manager/templates/default/element/tv/renders/input/taxonomytv.tpl — шаблон TV. Форма из смеси JavaScript, EXT.js и Smarty. Обрабатывает данные, полученные от taxonomytv.class.php. Здесь важны id элементов формы из которых modx получит обновленные данные.
  • /manager/templates/default/element/tv/renders/inputproperties/taxonomytvprops.tpl — шаблон дополнительных настроек TV.

Я сделал эти файлы на основе стандартных TV инпутов: resourceList и checkbox.

Ещё раз. Я сделал TV «taxonomy» и назначил ему параметр ввода «taxonomytv», который является кастомным и скачать вы его сможете в конце статьи. Естественно, не забываем назначить TV  для шаблона Object.

В настройках TV, во вкладке «параметры ввода» нам нужно указать id родителя структуры. Чекбоксы могут выстраиваться в несколько колонок. width-fix — костыль для правильного отображения чекбоксов, так как у модикса с этим есть некоторые проблемы — неправильно высчитывается ширина блока .x-column-inner. Если у вас не отображаются чекбоксы или колонки чекбоксов слишком широкие, попробуйте включить width-fix.

tv-settings

В параметрах вывода TV  нужно выбрать тип вывода «разделитель» и в качестве разделителя указать ||.

tv-settings-2

Создаем новую запись в «базе» и видим структуру каталога в TV taxonomy. При выборе овчарки выбираются и собаки и млекопитающие — все родительские элементы. Выключаем чекбокс — выключаются родительские. Вложенность возможна бесконечная.

Фильтрация в getResources

Теперь в TV taxonomy хранится список id категорий, к которым принадлежит данный ресурс вида: id||id2||id3. Для того, чтобы отобразить все ресурсы в данной категории, нужно задать фильтрацию по TV для getResources в шаблоне Section. В простейшем случае это делается так:

&tvFilters=`taxonomy==%[[*id]]%`

Но проценты здесь — любое продолжение строки, поэтому вместе с ресурсами с id 8 отобразятся и 18 и 88 и все остальные, в id которых присутствует 8. Поэтому параметры фильтрации нужно уточнить:

&tvFilters=`taxonomy==[[*id]]||taxonomy==[[*id]]|%||taxonomy==%|[[*id]]||taxonomy==%|[[*id]]|%`

— получаем 8| или |8| или |8, других вариантов быть не может.

Итак, у нас есть «база данных» — ресурс типа «Коллекция», где хранятся все наши записи. У нас есть структура каталога в виде дерева обычных ресурсов. И есть кастомный TV, чтоб присваивать записям «родителей» из структуры. В шаблоне каталога «section» у нас вызов getresources такого вида:

[[!getResources? 
&parents=`9` 
&tpl=`object-list` 
&showHidden=`1` 
&includeContent=`1` 
&includeTVs=`1` 
&processTVs=`1` 
&tvPrefix=`` 
&tvFilters=`taxonomy==[[*id]]||taxonomy==[[*id]]|%||taxonomy==%|[[*id]]||taxonomy==%|[[*id]]|%`]]

Здесь в качестве параметра parents указан id нашей «базы данных».

В принципе, всё уже должно работать. При этом у нас в базе вполне может присутствовать Котопёс и он отобразиться в разделах: Кошки, Собаки и Млекопитающие.

result1

Ссылки на разделы в шаблоне getResources

Не хватает отображения ссылок на родителей под каждой записью — по типу тегов. Если в чанке object-list (шаблон getResources) мы попытаемся отобразить тв, просто написав его название, то на выходе мы получим простую строку из id документов-родителей через разделитель ||. Нужно превратить эту строку из TV taxonomy в осмысленные ссылки на разделы.

Можно было бы написать сниппет, который разбивает строку из TV, ищет по каждому id нужный ресурс и формирует на него ссылку. Но в таком случае, при отображении каждой записи из «базы», для каждого «родителя» этой записи нам нужен был бы отдельный запрос к MySQL (или что у вас там). Более того, можно было бы сделать custom TV output, но у него были бы те же проблемы.

Поэтому я решил сделать 2 сниппета. Первый taxonomyString — вызывается один раз вместе с getResources и формирует строку со всем необходимым для создания ссылки из id в TV. Строка имеет вид:

id_родителя1:pagetitle1==id_ресурса1||id_родителя2:pagetitle2==id_ресурса2 — и так для всех категорий каталога.

В качестве параметра taxonomyString принимает id корневого ресурса структуры каталога.

Таким образом вызов getResources в шаблоне Section у нас становится таким:

[[!getResources? 
&parents=`9` 
&tpl=`object-list` 
&showHidden=`1` 
&includeContent=`1` 
&includeTVs=`1` 
&processTVs=`1` 
&tvPrefix=``  
&tvFilters=`taxonomy==[[*id]]||taxonomy==[[*id]]|%||taxonomy==%|[[*id]]||taxonomy==%|[[*id]]|%` 
&string=`[[!taxonomyString? &parent=`12`]]`]]

Заметьте, что parents для getResources — это id базы, а parent для taxonomyString — id корня каталога.

По сути, код taxonomyString — это вызов getResources внутри getResources, так что если вы, например, хотите отображать «папки» категорий, вы можете убрать отсюда ‘hideContainers’=>’1’.

taxonomyString

Второй сниппет taxonomyLinks вызывается из чанка object-list (tpl getResources). Он принимает два параметра: tvs — это наш TV taxonomy и string — это строка от первого сниппета. В примере чанк object-list выглядит так:

<b><a href="/[[~[[+id]]]]">[[+pagetitle]]</a></b><br/>
<p>[[!taxonomyLinks? &string=`[[+string]]` &tvs=`[[+taxonomy]]`]]</p>
<hr>

TaxonomyLinks как раз и занимается формированием ссылки из id, но при этом он не обращается к БД сайта, а берёт все необходимые данные из строки, полученной от taxonomyString. Каждой ссылке присваивается класс taxonomy и класс в зависимости от id группы: taxgroup5, taxgroup10 и т. д. Ссылка на текущую страницу заменяется span’ом с теми же классами.

Результат:

result2

Скорее всего всё точно так же должно работать и с pdoResources. Достаточно заменить getResources на pdoResources в шаблоне section и в сниппете taxonomyLinks.

 Установка

  • Качаем архив taxonomyTV.zip
  • Создаем 2 сниппета taxonomyString и taxonomyLinks и вставляем в них содержимое файлов snippet-taxonomyString.php и snippet-taxonomyLinks.php соответственно.
  • taxonomytv.class.php копируем в /core/model/modx/processors/element/tv/renders/mgr/input/
  • taxonomytv.php копируем в /core/model/modx/processors/element/tv/renders/mgr/inputproperties/
  • taxonomyTV.tpl копируем в /manager/templates/default/element/tv/renders/input/
  • taxonomytvprops.tpl копируем в /manager/templates/default/element/tv/renders/inputproperties/
  • Теперь можно создать TV «taxonomy», присвоить ему параметр ввода taxonomytv и заново перечитать статью следуя всем указаниям :-)

В очередной раз напомню, что я дизайнер, а не программист и используете вы этот код на свой страх и риск. Если вы найдёте баги, и тем более если вы сможете их исправить, пишите на denis@dyranov.ru.

Теги: , , , , ,

Вторник, 24 Фев 2015 Разработка

1 комментарий

  1. Max

    Спасибо за полезную статью! Давно искал способ сделать полноценную мультикатегорийность на modx.
    Не подскажете, а как сделать вывод ссылок на разделы, непосредственно внутри материала (объекта)?

    [Ответить]

Оставить комментарий