Этот пост не предлагает готового решения. Он адресован разработчикам, и носит, скорее, исследовательский характер.
Я попытаюсь описать задачу, и
немного поразмышляю в поисках возможных вариантов её решения.
Суть задачи сводится к разработке универсального сниппета, который позволил бы максимально полно использовать класс DirectoryLister (в приложении) предназначенный для построения стилизованного листинга дирректорий.
Класс можно использоват уже сейчас, он полностью отлажен и практически готов. Но для реализации каких либо специфичных задач, вам почти наверняка придётся написать свой вызывающий сниппет.
Цель этой
публикации: выяснить, нужно ли, и возможно ли, разработать законченный, более-менее универсальный вариант сниппета, который позволил бы с чистой совестью стереть отсюда первое предложение. В крайнем случае, если мы выясним, что сниппет всё-таки не нужен, то всё написанное послужит отличным руководство тому, кто захочет использовать класс.
Заранее прошу прощение за мног
а бук
ав. Я ещё как следует не научился излагать свои мысли коротко, но исчерпывающе.
Задача:
Вывести на странице листинг файлов из заданного каталога.
Условия:
1. Выводить нужно не все файлы — должна присутствовать возможность отфильтровать лишние.
2. Стиль и формат вывода должны полностью настраиваться.
2.1 Кроме полного имени файла, необходимо иметь возможность вывести:
- путь к файлу начиная от корневой папки сайта (для формирования ссылки)
- имя файла без расширения
- расширение файла (тип файла; можно использовать для стилизации, "на лету" формируя CSS-стили в шаблоне)
- размер файла в байтах
- размер в кратных единицах и используемую единицу (по отдельности)
- время последней модификации
- время прошедшее с момента последней модификации
- иконку соответствующую типу файла (с возможностью использовать свои наборы)
3. Если вывод оказался пуст, то нужно иметь возмножность вместо него вывести своё сообщение.
Особенности:
Папки в вывод не попадают. Смысла в этом пока нет, т. к. переход в них не реализован. Хотя, я думаю, это будет не сложно сделать.
Всё должно работать быстро и содержать как можно меньше кода.
Что при желании должно получаться на выходе ("рыба"):

Обратите внимание: все файлы тут из одного каталога.
И так.
Для начала нужно выяснить, можно ли разработать такой сниппет, вызвав который единожды, можно было бы получить то, что мы видим на скриншоте.
Кажется, что да. Но это сильно увеличит количество параметров, которые нужно будет передавать такому сниппету, и неоправдано усложнит его (нарушится одно из условий). К тому же, чем конкретней подход, тем меньшей универсальностью будет обладать решение.
Значит сниппет нужно оставлять простым, а для получения такого списка просто вызывать на странице трижды.
К примеру так:
<h3>Текущая версия</h3>
[[DirLister? &src=`download/pde` &includeMask=`0.9.7`]]
<h3>Текущая бета-версия</h3>
[[DirLister? &src=`download/pde` &includeMask=`beta`]]
<h3>Предыдущие выпуски</h3>
[[DirLister? &src=`download/pde` &excludeMask=`0.9.7,beta`]]
Самое время описать фильтрующий механизм.
- Если задействована включащая маска, то в список попадут только файлы соответствует этой маске.
- Если зайдествована маска исключения, то из списка будут исключены все файлы соответствующие маске.
- Если зайдествованы обе маски, то сначала из списка будет исключено всё, что не соответствует маске включения, а оставшиеся файлы отфильтруется маской исключения.
Соответствует — означает, что в имени файла содержится указанная строка. Т. е. маска действует на самом деле так: *маска*
Казалось бы, всё хорошо. Но встаёт вопрос: как рационально оргаризовать шаблонизацию?
1. Многие привыкли к чанкам, но если использовать их как шаблоны, то получится слишком накладно: нужно будет дёргать как минимум по два штуки (один для списка, другой для элемента списка) на каждый вызов сниппета.
2. Альтернативой являются конфиги. Использовать их для передачи параметров можно, но конфиги править неудобно — кроме браузера нужен дополнительный софт. Однако для шаблонов они подходят замечательно. Да и в плане производительности и расширяемости лучше их ничего нет.
В итоге, логичным решением будет использовать гибридный вариант: маски и пути задаются через параметры, а шаблоны — через конфиг (или прямо в сниппете, как будет показано ниже). К тому же, при необходимости, в конфиге можно будет переопределить любые заданные сниппету параметры.
Имя конфига также передаётся через параметр:
[[DirLister? &srcDir=`download/pde` &includeMask=`0.9.7` &config=`ulist`]]
В итоге, код сниппета получается примерно следущим:
<?php
// settings **********************************************************************
if (!isset($srcDir)) return;
$includeMask = isset($includeMask) ? $includeMask : '';
$excludeMask = isset($excludeMask) ? $excludeMask : '';
$itemsStyle = isset($itemsStyle) ? $itemsStyle : 'row0,row1';
$listTpl = "<ul>\n[+list+]</ul>";
$itemTpl = "<li>[+name+]</li>\n";
// code **************************************************************************
$basePath = $modx->config['base_path'];
include_once $basePath . 'assets/snippets/dirlisting/dir_listing.class.php';
if (isset($config))
require_once $basePath . 'assets/snippets/dirlisting/configs/'.$config.'.cfg.php';
$dirLister = new DirectoryLister( $basePath );
$dirLister->setIncludeMask( $includeMask );
$dirLister->setExcludeMask( $excludeMask );
$dirLister->setListTpl( $listTpl );
$dirLister->setItemTpl( $itemTpl );
$dirLister->setItemsStyle( $itemsStyle );
// $dirLister->setIcons('/assets/snippets/dirlisting/icons/', 'png');
// $dirLister->setDateFmt('d F Y');
// $dirLister->setNotFoundMsg('nothing found');
return $dirLister->listDir( $srcDir );
?>Я думаю закоментированные методы (устанавливающие параметры по умолчанию), да собственно как и все остальные, кроме setItemsStyle(), в пояснениях не нуждаются. Однако если вопросы всё же возникнут — спрашивайте.
Дефолтовые перменнные $listTpl и $itemTpl, используемые для шаблонов, переопределяются в конфиге:
// ulist.cfg.php
<?php
// item teplate
$itemTpl = '
<li class="[+style+]">
[+icon+]<a href="/[+url+]">[+name+]</a> <span class="size">([+size.num+] [+size.val+])</span>
</li>';
// list teplate
$listTpl = '<ul class="filelist">
[+list+]
</ul>';
?>
Шаблон из $itemTpl будет в цикле использоваться для формирования списка, а когда список будет построен, результат помещается на место [+list+] в шаблоне из $listTpl.
Если список окажется пуст, вместо пустого шаблона вернётся фраза назначенная методом с говорящим названием — setNotFoundMsg();
Допустимые в шаблонах плейсхолдеры:
[+name+] — имя файла
[+bname+] — имя без расширения
[+ext+] — расширение
[+size+] — размер в байтах
[+size.num+] — размер в чём-то кратном
[+size.val+] — значение кратности (МБ, КБ, Байт)
[+time+] — дата последней модификации
[+time.ago+] — время прошедшее с момента последней модификации
[+url+] — путь к файлу (от корня сайта)
[+icon+] — тег содержащий иконку
[+style+] — имя CSS-стиля
Пояснения требует только последний плейсхолдер. Вместе с методом setItemsStyle() он позволяет оформить список как угодно. На скриншоте у меня он полосатый.
Вот в чём фишка: методом setItemsStyle() вы задаёте список стилей, которые по кругу будут присвоены каждому элементу в списке поочерёдно (при условии, что вы использовали [+style+] там, где нужно). Если стилей будет больше чем файлов, круг просто не завершится, а если наоброт — у некоторых элементов в списке будут общие классы, — это очевидно.
Но не смотря на всё вышесказанное, мне лично всё-равно не хочется друг за другом трижды вывать сниппет в одном месте, и возиться с конфигами ради двух строчек шаблона. Поэтому, для того, чтоб один вызов давал то, что изображено на скриншоте, я написал другой сниппет:
// SETTINGS **********************************************************************
$listTpl = "\n<ul class=\"fileListing\">[+list+]</ul>";
$itemTpl = '<li class="[+style+]">[+time.ago+] ([+time+]) <span class="size">[+size.num+] [+size.val+]</span><a href="[+url+]" style="background: url(\'http://eppz.net/assets/snippets/dirlisting/icons/[+ext+].png\') no-repeat;">[+name+]</a></li>';
$itemsStyle = isset($itemsStyle) ? $itemsStyle : 'grey,white';
$mask = isset($mask) ? $mask : '';
$basePath = $modx->config['base_path'];
// CODE **************************************************************************
require_once $basePath . 'assets/snippets/dirlisting/dir_listing.class.php'; // подлкючаем класс
$output = '';
// создаём объект инициализируя его путём от корня сервера до корня сайта (со слешем на конце)
$dirLister = new DirectoryLister( $basePath );
$dirLister->setItemTpl( $itemTpl ); // устанавливаем шаблон элемента списка
$dirLister->setListTpl( $listTpl ); // устанавливаем шаблон списка
$dirLister->setItemsStyle( $itemsStyle ); // устанавливаем перечень стелей
$output .= '<div class="version current"><h2>Текущая версия</h2>';
$dirLister->setNotFoundMsg('<p><em>Ещё не готова.</em></p>'); // текст для пустого списка
$dirLister->setIncludeMask( $mask ); // маска для включения
$dirLister->setExcludeMask( 'beta' ); // и для исключения
$output .= $dirLister->listDir( $sourceDir ); // забираем вывод
$output .= "</div>\n<div class=\"verion beta\"><h3>Текущая <em>бета—</em>версия</h3>";
$dirLister->setNotFoundMsg('<p><em>В данный момент нет.</em></p>');
$dirLister->setExcludeMask( '' ); // очищаем маску для исключения (т.е. исключать мы больше ничего не хотим)
$dirLister->setIncludeMask( 'beta' ); // и настраиваем маску для включения
$output .= $dirLister->listDir( $sourceDir ); // получаем вывод
$output .= "</div>\n<div class=\"version old\"><h3>Предыдущие выпуски</h3>"; // и последний список, для старых выпусков
$dirLister->setNotFoundMsg('<p><em>Не было.</em></p>');
$dirLister->setIncludeMask( '' ); // сбрасываем маску для включения (т.е. включаем в вывод всё)
$dirLister->setExcludeMask( $mask.',beta' ); // и устанавливам для исключения (выводим всё, кроме того, что укажем тут)
$output .= $dirLister->listDir( $sourceDir ); // получаем
$output .= '</div>';
echo $output; // показываем
И вызываю его так:
[[dirListing? &sourceDir=`/download/[+app.name+]` &mask=`[+app.version+]`]]
[+app.name+] и [+app.version+] — это мои внутренние переменные, которые содержат имя проекта и его текущую версию. Таким образом, где я не вызовови этот сниппет, он выведет файлы для тещего проекта.
Один вызов на весь сайт — красота.
Кто там просил поделиться? Утащите?