Таксономия в modx revo (custom TV input)
Таксономия, поправьте, если я не прав, — это такой способ классификации и систематизации, при котором один объект может относиться к нескольким классам. Например, у нас есть терьер Тузик, овчарка Рекс, кот Барсик и рыбка Немо. И есть такая структура:
- Млекопитающие
- Собаки
- терьеры
- овчарки
- Коты
- Собаки
- Рыбы
Тузик, будучи терьером, относится и к собакам, и к млекопитающим. То же самое с Рексом, он — собака и млекопитающее. Барсик относится к котам и млекопитающим, а Немо — рыба и млекопитающим не является.
Таксономия может быть очень полезна при разработке сайтов-каталогов, в которых один объект так же может относиться к нескольким категориям и группам категорий. Вот, например, каталог артистов. Артист может быть одновременно и музыкантом, и ведущим, и фокусником. Кроме того, музыку он может исполнять в различных стилях.
Таксономия «из коробки» имеется во многих CMS. Особенно хорошо этот термин знаком пользователям Drupal. Для моего любимого ModX существует плагин Taxonomies, но работает он... несколько странно... Надо что-то делать.
Начнем
Для начала создадим структуру каталога в дереве ресурсов. Для ресурсов в этой структуре нам понадобится отдельный шаблон — назовем его «section». Кроме того, шаблон понадобится и для объектов — назовем его «object».
Так как каждый объект каталога у нас может принадлежать к нескольким категориям, нет никакого смысла хранить объекты внутри категорий. Можно было бы, конечно, создать ресурс внутри одной из категорий, а в остальных сделать ссылку на него, но это, по-моему, извращение. Работать с таким каталогом будет очень не удобно. Поэтому для хранения объектов создадим ещё один ресурс, который будет нашей «Базой данных» :-) Чтоб не потеряться среди тысяч дочерних ресурсов «базы» имеет смысл воспользоваться плагином Collections.
Теперь нужно объекты из «базы» как-то привязать к структуре каталога. Причём каждый объект может быть привязан сразу к нескольким категориям. Более того, при выборе дочерней категории, было бы неплохо сразу отмечать и родительские — именно поэтому мы не можем использовать теги — у них нет структуры, они не бывают дочерними/родительскими. В принципе, мы могли бы создать отдельное TV с тегами для каждой группы категорий каталога. Тогда само TV обозначало бы группу, у которой были бы дочерние элементы — теги, но таких TV может получится слишком много, да и каждое TV — это лишние запросы в БД.
TV cо своими параметрами ввода
Итак, нам нужен TV, в котором будет храниться список родительских ресурсов, и нам нужен удобный способ редактирования этого списка. То есть нужен свой TV 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 нужно выбрать тип вывода «разделитель» и в качестве разделителя указать ||.
Создаем новую запись в «базе» и видим структуру каталога в TV taxonomy. При выборе овчарки выбираются и собаки и млекопитающие — все родительские элементы. Выключаем чекбокс — выключаются родительские. Вложенность возможна бесконечная.
BIG
Фильтрация в 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 нашей «базы данных». В принципе, всё уже должно работать. При этом у нас в базе вполне может присутствовать Котопёс и он отобразиться в разделах: Кошки, Собаки и Млекопитающие.
Ссылки на разделы в шаблоне 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'
.
Второй сниппет taxonomyLinks вызывается из чанка object-list (tpl getResources). Он принимает два параметра: tvs — это наш TV taxonomy и string — это строка от первого сниппета. В примере чанк object-list выглядит так:
[[+pagetitle]]
[[!taxonomyLinks? &string=`[[+string]]` &tvs=`[[+taxonomy]]`]]
TaxonomyLinks как раз и занимается формированием ссылки из id, но при этом он не обращается к БД сайта, а берёт все необходимые данные из строки, полученной от taxonomyString. Каждой ссылке присваивается класс taxonomy и класс в зависимости от id группы: taxgroup5, taxgroup10 и т. д. Ссылка на текущую страницу заменяется span'ом с теми же классами.
Результат:
Скорее всего всё точно так же должно работать и с 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.
Спасибо за полезную статью! Давно искал способ сделать полноценную мультикатегорийность на modx.
Не подскажете, а как сделать вывод ссылок на разделы, непосредственно внутри материала (объекта)?
на последний модекс поставил, php 7 (локальная макось) - родительский каталог показывается, дочерние нет(