Адаптируем стороннее меню под Joomla. Делаем свой модуль меню для Joomla 2.5 (Часть 3) | ![]() | ![]() | ![]() |
Written by Максим | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Sunday, 17 November 2013 01:49 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Liked: 18 Доброго времени суток, друзья. Я продолжаю описывать процесс превращения стороннего меню в полноценный модуль меню для Joomla 2.5. Статья довольно сильно растянулась, поэтому хочу в двух словах напомнить вам, что мы делали в Части 1 и Части 2:
Итак, на текущий момент у нас есть полностью готовый файл помощника helper.php, и он содержит в себе всю основную логику получения и подготовки данных меню. Вот, что нам предстоит сделать дальше:
Давайте в таком порядке и пойдем по этим задачам. Готовим файл параметров модуляДля экономии места я не буду включать в текст статьи полный исходный код файла mod_colormenu.xml, т.е. дескриптора модуля, который содержит данные для установки модуля в Joomla, а также параметры модуля. Вместо этого я опишу перечень параметров и их тип, а также краткое описание и назначение параметра. Обращаю внимание, что типы menuselector и menucolorselector - это типы параметров, которых нет в Joomla, чуть позже мы сами напишем классы, которые будут делать следующее:
Почему нам нужны свои типы параметров? А для того, чтобы модуль мог гибко "перенимать" изменения в другой части админки Joomla - при удалении существующих меню из сайта или создании новых - это будет отражаться в настройках нашего модуля. Поэтому нужно динамически из базы получать актуальную информацию о том, какие меню существуют на сайте. Стандартные параметры Joomla не дают такой возможности, поэтому мы создадим свои типы параметров. Но это чуть далее по тексту, а пока посмотрим на то, какие параметры есть в файле дескриптора mod_colormenu.xml нашего модуля:
В целом, перечисленные параметры и их типы - это всё, что заслуживает внимания по части дескриптора модуля. Там же вы найдете перечень всех файлов модуля в секции <files>, все языки установки в секции <languages> и т.д. Пишем классы для своих типов параметров в админке JoomlaКак Вы уже заметили, среди параметров есть те, что имеют типы menuselector и menucolorselector. Чтобы создать эти типы параметров, нужно создать отдельный каталог с именем fields в каталоге нашего модуля. И добавить туда два новых файла - menuselector.php и menucolorselector.php. Имена файлов должны строго соответствовать тому, как называются сами параметры в дескрипторе модуля mod_colormenu.xml. Если не будет этого соответствия, движок Joomla не найдет их и выдаст ошибку о том, что тип параметра не известен. Тип параметра - это свой класс, который должен начинаться обязательно с JFormField<далее_имя_вашего_параметра>. Также класс наследуется от стандартного класса Joomla - JFormField и должен переопределить абстрактный метод getInput() родительского класса JFormField своей логикой (увидеть определение класса JFormField можно в файле /libraries/joomla/form/field.php): <div>defined( '_JEXEC' ) or die( 'Restricted access' );[nl][nl]</div><div>jimport('joomla.html.html');[nl]</div><div>jimport('joomla.form.formfield');[nl][nl]</div><div><div>class JFormFieldMenuselector extends JFormField {[nl]</div><div><div>[tab]protected $type = 'menuselector';[nl]</div><div>[tab]protected $forceMultiple = true;[nl][nl]</div><div><div>[tab]protected function getOptions()[nl]</div><div>[tab]{[nl]</div><div>[tab][tab]$options = array();[nl]</div><div><div>[tab][tab]$db = &JFactory::getDBO();[nl]</div><div>[tab][tab]$query = 'SELECT * ';[nl]</div><div>[tab][tab]$query .= 'FROM ';[nl]</div><div>[tab][tab]$query .= '#__menu_types';[nl]</div><div>[tab][tab]$db->setQuery( $query );[nl]</div><div>[tab][tab]$menutypes = $db->loadObjectList();[nl]</div><div>[tab][tab]if (!empty($menutypes)) {[nl]</div><div>[tab][tab][tab]foreach ($menutypes as $menutype) {[nl]</div><div>[tab][tab][tab][tab]$tmp = JHtml::_([nl]</div><div>[tab][tab][tab][tab][tab]'select.option',[nl]</div><div>[tab][tab][tab][tab][tab](string) $menutype-[gt]menutype,[nl]</div><div>[tab][tab][tab][tab][tab]'[lt]i[gt]'.$menutype-[gt]title . '[lt]/i[gt] ([lt]strong[gt][lt]i[gt]'.$menutype-[gt]menutype.'[lt]/i[gt][lt]/strong[gt])', 'value', 'text', "false"[nl]</div><div>[tab][tab][tab][tab]);[nl]</div><div>[tab][tab][tab][tab]$options[] = $tmp;[nl]</div><div>[tab][tab][tab]}[nl]</div><div>[tab][tab]}[nl][nl]</div><div><div>[tab][tab]reset($options);[nl]</div><div>[tab][tab]return $options;[nl]</div></div></div><div>[tab]}[nl][nl]</div><div>[tab]protected function getInput() {[nl]</div><div><div>[tab][tab]$options = $this->getOptions();[nl]</div><div>[tab][tab]$html = array();[nl]</div><div>[tab][tab]$class = $this-[gt]element['class'] ? ' class="checkboxes ' . (string) $this-[gt]element['class'] . '"' : ' class="checkboxes"';[nl]</div><div><div>[tab][tab]$html[] = '[lt]fieldset id="' . $this-[gt]id . '"' . $class . '[gt]';[nl]</div><div>[tab][tab]$html[] = "[lt]ul[gt]";[nl]</div><div><div>[tab][tab]foreach ($options as $i => $option) {[nl]</div><div>[tab][tab][tab]$checked = (in_array((string) $option-[gt]value, (array) $this-[gt]value) ? ' checked="checked"' : '');[nl]</div><div>[tab][tab][tab]$html[] = '[lt]li[gt]';[nl]</div><div>[tab][tab][tab]$html[] = '[lt]input type="checkbox" id="' . $this-[gt]id . $i . '" name="' . $this-[gt]name . '"' . ' value="'[nl]</div><div>[tab][tab][tab][tab]. htmlspecialchars($option-[gt]value, ENT_COMPAT, 'UTF-8') . '"' . $checked . '/[gt]';[nl]</div><div>[tab][tab][tab]$html[] = '[lt]label for="' . $this-[gt]id . $i . '"' . $class . '[gt]' . JText::_($option-[gt]text) . '[lt]/label[gt]';[nl]</div><div>[tab][tab][tab]$html[] = '[lt]/li[gt]';[nl]</div><div>[tab][tab]}[nl]</div></div></div></div></div></div><div>}[nl]</div></div><p style="text-align: justify;"> Метод getOptions() - это наш, собственный метод. Его нет в родительском классе JFormField. И мы написали его для получения "опций для выбора", а ими у нас являются все доступные меню сайта. Их мы и получаем из таблицы prefix_menu_types, посылая SQL-запрос в базу. Если посмотреть на тело метода getOptions() - мы видим, как сначала отправляем запрос в БД для получения массива объектов "тип меню" - их мы запоминаем в переменной $menutypes. После чего мы бежим в цикле по всем типам меню сайта и для каждого из них с помощью специального вызова JHtml::_() конструируем чекбокс. Увидеть, как работает вызов JHtml можно открыв класс библиотеки Joomla, который расположен в /libraries/joomla/html/html.php, однако он достаточно сложен для понимания. Главное, что нужно знать при работе с вызовом JHtml - это то, где самому без труда найти реализацию. В нашем случае мы первым параметром передали 'select.option'. Что это значит? Это означает, что для построения элемента Joomla должна пойти в каталог /libraries/joomla/html/html/ , найти там файл select.php, а в нём - метод option(). Параметры этого метода option() и определяют то, что мы передаем после строки 'select.option', а именно:
Итак, мы собрали в массиве $options все построенные с помощью вызова JHtml::_() чекбоксы и надписи, осталось теперь сбросить внутренний указатель массива в начало - с помощью вызова reset($options) и вернуть $options в качестве возвращаемого значения метода. Что же касается метода getInput(), то в родительском классе JFormField этот метод является абстрактным (т.е. без тела и полезного кода). Это значит, что в дочернем классе - т.е. нашем JFormFieldMenuselector мы обязательно должны определить реализацию метода getInput() сами. Дело в том, что во время отрисовки страницы параметров нашего модуля в админке движок для формирования элемента управления вызовет именно метод getInput(), поэтому то, как в конечном счете выглядит наш параметр определяет именно этот метод. Первым делом мы вызываем getOptions(), который рассмотрели ранее, и в переменной $options получаем массив чекбоксов. Дальше мы в массив $html добавляем элементы, каждый из которых представляет собой строку с HTML-разметкой элемента управления. Обратите внимание, что через указатель $this мы можем обращаться, во-первых к атрибутам, которые можем задать в самом xml-файле дескриптора модуля. Это делается с помощью вызова $this->element['<имя_атрибута_из_xml>']. Во-вторых, через тот же указатель $this мы обращаемся к тем внутренним переменным, которые заданы в родительском классе JFormField, например вызов $this->id вернет автоматически сгенерированный id для нашего элемента управления. Он будет представлять из себя склейку вида "jform_params_<имя_параметра_в_xml><индекс_элемента_на_странице>" . Имя параметра в xml - это то, как мы назвали параметр в xml, индекс элемента на странице - Joomla вычислит сама. Например, наш параметр называется selected_menus. Предположим, что в xml-дескрипторе мы бы задали 10 параметров такого типа. Тогда в $this->id будет содержаться "jform_params_selected_menus0" для 1-го параметра, "jform_params_selected_menus1" - для 2-го параметра и т.д. Почему я заостряю на этом внимание? Конечно, может и Вам не нужно настолько глубоко вникать в логику движка Joomla. Но если Вы захотите писать свой собственный параметр модуля или компонента, Вам не обойти этот аспект стороной. А этой информации нет вообще нигде, даже на официальных форумах и ресурсах Joomla. Это просто заметки из личного опыта, поэтому будет напоминание не только вам, но и мне самому. Пишем входную точку для модуля mod_colormenu.phpДавайте напишем теперь файл mod_colormenu.php, который является входной точкой нашего модуля. Как я уже говорил, этот файл получает управление, как только движок Joomla начинает подгружать модуль в той позиции, в которой он установлен через панель администрирования. Цель данного файла - подгрузить необходимые библиотеки, файлы, ресурсы к модулю, после чего передать управление файлу шаблона модуля tmpl/default.php, который и осуществляет отрисовку модуля. <div>defined( '_JEXEC' ) or die( 'Restricted access' );[nl]</div><div>// Подключаем файл помощника helper.php с классом MenuItem и modColorMenuHelper[nl]</div><div>require_once(dirname(__FILE__).DS.'helper.php');[nl][nl]</div><div>// Создаем экземпляр помощника[nl]</div><div>$helper = new modColorMenuHelper();[nl][nl]</div><div>// Получаем ссылку на текущий документ[nl]</div><div>$doc = &JFactory::getDocument();[nl][nl]</div><div>// Подключаем файл стилей mod_colormenu.css к сайту[nl]</div><div>$module_name = 'mod_colormenu';[nl]</div><div>$doc-[gt]addStylesheet( JURI::root(true) . "/modules/".$module_name."/css/".$module_name.".css" );[nl][nl]</div><div>// По умолчанию устанавливаем значение для параметра 'selected_menus' как пустой массив[nl]</div><div><div>$params-[gt]def('selected_menus', array());[nl][nl]</div><div>// Получаем действительное значение параметра[nl]</div><div>$menuTypesArray = $params-[gt]get('selected_menus');[nl][nl]</div><div>// Получаем информацию о цвете фона и шрифта меню из параметров модуля[nl]</div><div><div>$bgColors<span class="Apple-tab-span" style="white-space:pre"> </span>= $params-[gt]get('menu_bgcolors');[nl]</div><div>$hoverColors<span class="Apple-tab-span" style="white-space:pre"> </span>= $params-[gt]get('menu_hovercolors');[nl]</div><div>$textColors<span class="Apple-tab-span" style="white-space:pre"> </span>= $params-[gt]get('menu_textcolors');[nl]</div><div>$hoverTextColors<span class="Apple-tab-span" style="white-space:pre"> </span>= $params-[gt]get('menu_hovertextcolors');[nl][nl]</div><div>// Задаем значения по умолчанию для параметров модуля[nl]</div><div><div>$params->def('mnu_item_width', '220px');[nl]</div><div>$params->def('enable_drop_symbol_main', '1');[nl]</div><div>$params->def('drop_symbol', '&' . '#x25ba;');[nl]</div><div>$params->def('enable_drop_symbol_dd', '1');[nl]</div><div>$params->def('drop_symbol_dd', '&' . '#x25ba;');[nl]</div><div><div>$mnuItemWidth = $params->get('mnu_item_width');[nl][nl]</div><div>// Параметры символа стрелки для выпадающих пунктов меню[nl]</div><div>$enableDropSymbol = intval($params->get('enable_drop_symbol_main'));[nl]</div><div>$dropSymbol = $params->get('drop_symbol');[nl]</div><div>$enableDropSymbolDD = intval($params->get('enable_drop_symbol_dd'));[nl]</div><div>$dropSymbolDD = $params->get('drop_symbol_dd');[nl]</div><div>$numMenuTypes = count($menuTypesArray);[nl][nl]</div><div>// Часть CSS-стилей мы должны подключить программно, т.к. они будут зависеть от параметров модуля[nl]</div><div><div>$doc->addStyleDeclaration("ul.dropdown li a { display: block; padding: 4px 8px !important; margin:0px !important; text-decoration:none !important; background:none !important; border-width:0px !important; border-radius:0px !important;}" .[nl]</div><div>"ul.dropdown li.hover > a { border-width:0px !important; }" .[nl]</div><div>"ul.dropdown ul li a:hover { text-decoration:none; color: red !important; background:none; background-color:none; }" . [nl]</div><div>"ul.dropdown ul { margin: 0 !important; padding: 0 !important; border: 0 !important; font-size: 100% !important; font: inherit !important; vertical-align: baseline !important; text-transform:none !important; list-style: none !important;" .[nl] "z-index:99; width: ".$mnuItemWidth."; visibility: hidden; position: absolute; top: 100% !important; left: 0 !important; }");[nl][nl]</div><div>// Далее следует довольно большой кусок кода, который сюда не будет вставлен для экономии места[nl]</div><div>// Суть его такова: дальше получаются параметры для каждого меню и те их значения (цвета шрифта, цвета фона и т.д.),[nl]</div><div>// что заданы в админке. Эти значения динамически формируют нужный вид CSS-стилей и эти стили подключаются к сайту[nl]</div></div></div></div></div></div>
Заполняем файл tmpl/default.phpТеперь мы должны взяться за написание файла шаблона модуля. Это тот самый файл, который и рисует финальную HTML-разметку под меню. Начнём с того, что получим текущего пользователя и проверим, а есть ли вообще меню, которые нужно рисовать, т.е. не пустой ли массив? Для начала добавим такую конструкцию: <div>$user = & JFactory::getUser();[nl]</div><div>if (!empty($menuTypesArray)) {[nl]</div><div>[tab]// здесь пробежать по всем типам меню и отрисовать каждый из них[nl]</div><div>}[nl]</div> Если массив типов меню пуст, то модуль вообще ничего не отрисует и мы не войдем в if. Если же у нас есть настроенные через админку Joomla меню, причем для них есть еще и опубликованные пункты меню, то мы должны "нарисовать" это в виде HTML-разметки. Поэтому алгоритм выходит примерно следующий: 1. Определить тип текущего меню из массива$menuTypesArray. Например для главного меню это будет mainmenu 2. Получить параметры для текущего типа меню. Нам нужно знать, каким образом назвать самый верхний уровень меню. И я решил, что это будет название самого типа меню. Если Вы помните, ранее мы уже добавили с Вами в helper.php методgetDefMenuParams, который получает название меню, название и ссылку для главного пункта меню. 3. Когда в п.2 мы отрисуем "верхний" элемент, нам останется получить все пункты для данного меню. Мы тоже об этом уже позаботились - у нас в helper.php уже заготовлена функцияgetMenuItems, которая по тип меню получает все его пункты меню. Мы должны учесть, что теоретически через админку можно снять с публикации все пункты меню. Поэтому мы поставим проверку на то, есть ли у нас опубликованные пункты меню. 4. Получив список пунктов меню, пробежаться по каждому пункту и нарисовать его. Переводя описанный выше алгоритм на PHP, получается следующий код: <div>$user = & JFactory::getUser();[nl]</div><div>if (!empty($menuTypesArray)) {[nl]</div><div>[tab]echo "[lt]ul class='dropdown'[gt]";[nl]</div><div>[tab]foreach ($menuTypesArray as $menuType) {[nl]</div><div>[tab][tab]$menuType = trim($menuType);[nl]</div><div>[tab][tab]$menuDefParams = $helper->getDefMenuParams($menuType);[nl]</div><div>[tab][tab]// Получить в $menuItems все пункты меню для данного меню[nl]</div><div>[tab][tab]$menuItems = $helper->getMenuItems($menuType);[nl]</div><div>[tab][tab]// Есть ли пункты меню, опубликованные в админке?[nl]</div><div>[tab][tab]if (!empty($menuItems)) {[nl]</div><div>[tab][tab][tab]// Отрисовываем пункт меню[nl]</div><div><div>[tab][tab][tab]echo "[lt]li class='". $menuType . "'[gt]";[nl]</div><div>[tab][tab][tab]echo "[lt]a href='" . JRoute::_($menuDefParams-[gt]link) . "'[gt]";[nl]</div><div>[tab][tab][tab]// Если разрешен символ стрелки в параметрах модуля - нарисуем его...[nl]</div><div>[tab][tab][tab]echo $enableDropSymbol ? $dropSymbol . "&" . "nbsp;" . "&" . "nbsp;" : "";[nl]</div><div>[tab][tab][tab]// Выводим непосредственно название меню[nl]</div><div>[tab][tab][tab]echo $menuDefParams-[gt]menutitle;[nl]</div><div>[tab][tab][tab]echo "[lt]/a[gt]";[nl]</div><div>[tab][tab][tab]// Теперь рисуем внутренний UL-список со всеми пунктами меню[nl]</div><div>[tab][tab][tab]echo "[lt]ul class='sub_menu ". $menuType . "'[gt]";[nl]</div><div>[tab][tab][tab]// Пошел цикл по пунктам меню:[nl]</div><div><div>[tab][tab][tab]foreach ($menuItems as $item) {[nl]</div><div>[tab][tab][tab][tab]$item->setEnableDropSymbol($enableDropSymbolDD);[nl]</div><div>[tab][tab][tab][tab]$item->setDropSymbol($dropSymbolDD);[nl]</div><div>[tab][tab][tab][tab]// Данный пункт меню доступен и виден текущему пользователю с его набором прав?[nl]</div><div>[tab][tab][tab][tab]// Доступность определяется методом isAccessable, который мы добавляли в класс MenuItem[nl]</div><div>[tab][tab][tab][tab]if ($item->isAccessable($user)) {[nl]</div><div>[tab][tab][tab][tab][tab]// Если пункт меню видим - отрисовать его методом render(). Метод рекурсивен, т.е. если пункт меню сам[nl]</div><div>[tab][tab][tab][tab][tab]// является родительским, и у него есть подменю - render() отрисует еще один внутренний UL-список...[nl]</div><div>[tab][tab][tab][tab][tab]echo $item->render();[nl]</div><div>[tab][tab][tab][tab]}[nl]</div><div>[tab][tab][tab]}[nl]</div><div>[tab][tab][tab]echo "[lt]/ul[gt]";[nl]</div><div>[tab][tab][tab]echo "[lt]/li[gt]";[nl]</div></div></div><div>[tab][tab]}[nl]</div><div>[tab]}[nl]</div><div>[tab]echo "[lt]/ul[gt]";[nl]</div><div>[tab]echo "[lt]br clear='all' /[gt]";[nl]</div><div>}[nl]</div> В тексте статьи я использовал команды echo для вывода и отрисовки HTML-разметки - потому что так проще форматировать код на сайте. В скачанном модуле вы увидите, что PHP-код отделен в шаблоне от HTML-разметки, но суть от этого ничуть не меняется. Готовим CSS-стили для отрисовки менюПоскольку мы адаптируем внешнее меню SimplejQueryDropdowns, которое мы обсуждали и скачали в части 1 данной статьи, то целесообразно взять за основу те CSS-стили, которые уже есть в поставке этого меню. При распаковке архива с сторонним меню, мы увидим 2 файла во вложенном каталоге css - это файлы ie.css и style.css. Первый - это специализированный файл стилей, который подключается к сайту, если пользователь заходит на сайт через Internet Explorer (IE настолько специфичен, что для него как правило включается отдельный файл стилей, чтобы позволить разметке сайта отображаться корректно). Второй файл - style.css - является основным файлом стилей стороннего меню. Копируем из него все стили и для начала вставляем в файл mod_colormenu.css, который располагается в каталоге /mod_colormenu/css/mod_colormenu.css нашей заглушки. Если сейчас наш модуль-заглушка включен на сайте и мы зайдем на сайт и обновим страницу в браузере, то увидим ужасный результат: вместо ожидаемого стороннего меню все блоки, пункты меню и т.д. "поплыли", т.е. CSS-стили, которые идут вместе с сторонним меню и прекрасно с ним работают почему-то напрочь отказываются работать на нашем сайте Joomla! Почему это происходит? Все дело в шаблонах Joomla. Обычно они уже содержат огромную массу стилей - как общих, т.е. стилизующих HTML-элементы вроде <div>, <ul>, <li> и т.д., так и узкоспециализированных CSS-стилей, которые придают вид боковым панелям сайта, основной области, шапке сайта и так далее. Так вот, когда мы вставили CSS-стили, идущие в поставке стороннего меню и оставили их "как есть", то с вероятностью 99,9% наше внешнее меню не заработает сразу на конкретном Joomla шаблоне. Здесь начинается поистине колдовство и "подгонка" CSS-разметки модуля таким образом, чтобы он отображался в шаблоне Joomla правильно. Какой-то общей методики не существует, результат зависит от знания CSS и практики. В целом же совет таков: использовать как можно больше инструментов, встроенных в Ваш браузер, чтобы понять, какой набор CSS стилей применяется к интересующему нас элементу - например, пункту меню. Также нужно постараться выявить в текущем шаблоне Joomla самые общие стили. Например, следующего плана:
Итак, на текущий момент у нас есть полностью готовый файл помощника helper.php, и он содержит в себе всю основную логику получения и подготовки данных меню. Вот, что нам предстоит сделать дальше:
Давайте в таком порядке и пойдем по этим задачам. Готовим файл параметров модуляДля экономии места я не буду включать в текст статьи полный исходный код файла mod_colormenu.xml, т.е. дескриптора модуля, который содержит данные для установки модуля в Joomla, а также параметры модуля. Вместо этого я опишу перечень параметров и их тип, а также краткое описание и назначение параметра. Обращаю внимание, что типы menuselector и menucolorselector - это типы параметров, которых нет в Joomla, чуть позже мы сами напишем классы, которые будут делать следующее:
Почему нам нужны свои типы параметров? А для того, чтобы модуль мог гибко "перенимать" изменения в другой части админки Joomla - при удалении существующих меню из сайта или создании новых - это будет отражаться в настройках нашего модуля. Поэтому нужно динамически из базы получать актуальную информацию о том, какие меню существуют на сайте. Стандартные параметры Joomla не дают такой возможности, поэтому мы создадим свои типы параметров. Но это чуть далее по тексту, а пока посмотрим на то, какие параметры есть в файле дескриптора mod_colormenu.xml нашего модуля:
В целом, перечисленные параметры и их типы - это всё, что заслуживает внимания по части дескриптора модуля. Там же вы найдете перечень всех файлов модуля в секции <files>, все языки установки в секции <languages> и т.д. Пишем классы для своих типов параметров в админке JoomlaКак Вы уже заметили, среди параметров есть те, что имеют типы menuselector и menucolorselector. Чтобы создать эти типы параметров, нужно создать отдельный каталог с именем fields в каталоге нашего модуля. И добавить туда два новых файла - menuselector.php и menucolorselector.php. Имена файлов должны строго соответствовать тому, как называются сами параметры в дескрипторе модуля mod_colormenu.xml. Если не будет этого соответствия, движок Joomla не найдет их и выдаст ошибку о том, что тип параметра не известен. Тип параметра - это свой класс, который должен начинаться обязательно с JFormField<далее_имя_вашего_параметра>. Также класс наследуется от стандартного класса Joomla - JFormField и должен переопределить абстрактный метод getInput() родительского класса JFormField своей логикой (увидеть определение класса JFormField можно в файле /libraries/joomla/form/field.php): <div>defined( '_JEXEC' ) or die( 'Restricted access' );[nl][nl]</div><div>jimport('joomla.html.html');[nl]</div><div>jimport('joomla.form.formfield');[nl][nl]</div><div><div>class JFormFieldMenuselector extends JFormField {[nl]</div><div><div>[tab]protected $type = 'menuselector';[nl]</div><div>[tab]protected $forceMultiple = true;[nl][nl]</div><div><div>[tab]protected function getOptions()[nl]</div><div>[tab]{[nl]</div><div>[tab][tab]$options = array();[nl]</div><div><div>[tab][tab]$db = &JFactory::getDBO();[nl]</div><div>[tab][tab]$query = 'SELECT * ';[nl]</div><div>[tab][tab]$query .= 'FROM ';[nl]</div><div>[tab][tab]$query .= '#__menu_types';[nl]</div><div>[tab][tab]$db->setQuery( $query );[nl]</div><div>[tab][tab]$menutypes = $db->loadObjectList();[nl]</div><div>[tab][tab]if (!empty($menutypes)) {[nl]</div><div>[tab][tab][tab]foreach ($menutypes as $menutype) {[nl]</div><div>[tab][tab][tab][tab]$tmp = JHtml::_([nl]</div><div>[tab][tab][tab][tab][tab]'select.option',[nl]</div><div>[tab][tab][tab][tab][tab](string) $menutype-[gt]menutype,[nl]</div><div>[tab][tab][tab][tab][tab]'[lt]i[gt]'.$menutype-[gt]title . '[lt]/i[gt] ([lt]strong[gt][lt]i[gt]'.$menutype-[gt]menutype.'[lt]/i[gt][lt]/strong[gt])', 'value', 'text', "false"[nl]</div><div>[tab][tab][tab][tab]);[nl]</div><div>[tab][tab][tab][tab]$options[] = $tmp;[nl]</div><div>[tab][tab][tab]}[nl]</div><div>[tab][tab]}[nl][nl]</div><div><div>[tab][tab]reset($options);[nl]</div><div>[tab][tab]return $options;[nl]</div></div></div><div>[tab]}[nl][nl]</div><div>[tab]protected function getInput() {[nl]</div><div><div>[tab][tab]$options = $this->getOptions();[nl]</div><div>[tab][tab]$html = array();[nl]</div><div>[tab][tab]$class = $this-[gt]element['class'] ? ' class="checkboxes ' . (string) $this-[gt]element['class'] . '"' : ' class="checkboxes"';[nl]</div><div><div>[tab][tab]$html[] = '[lt]fieldset id="' . $this-[gt]id . '"' . $class . '[gt]';[nl]</div><div>[tab][tab]$html[] = "[lt]ul[gt]";[nl]</div><div><div>[tab][tab]foreach ($options as $i => $option) {[nl]</div><div>[tab][tab][tab]$checked = (in_array((string) $option-[gt]value, (array) $this-[gt]value) ? ' checked="checked"' : '');[nl]</div><div>[tab][tab][tab]$html[] = '[lt]li[gt]';[nl]</div><div>[tab][tab][tab]$html[] = '[lt]input type="checkbox" id="' . $this-[gt]id . $i . '" name="' . $this-[gt]name . '"' . ' value="'[nl]</div><div>[tab][tab][tab][tab]. htmlspecialchars($option-[gt]value, ENT_COMPAT, 'UTF-8') . '"' . $checked . '/[gt]';[nl]</div><div>[tab][tab][tab]$html[] = '[lt]label for="' . $this-[gt]id . $i . '"' . $class . '[gt]' . JText::_($option-[gt]text) . '[lt]/label[gt]';[nl]</div><div>[tab][tab][tab]$html[] = '[lt]/li[gt]';[nl]</div><div>[tab][tab]}[nl]</div></div></div></div></div></div><div>}[nl]</div></div><p style="text-align: justify;"> Метод getOptions() - это наш, собственный метод. Его нет в родительском классе JFormField. И мы написали его для получения "опций для выбора", а ими у нас являются все доступные меню сайта. Их мы и получаем из таблицы prefix_menu_types, посылая SQL-запрос в базу. Если посмотреть на тело метода getOptions() - мы видим, как сначала отправляем запрос в БД для получения массива объектов "тип меню" - их мы запоминаем в переменной $menutypes. После чего мы бежим в цикле по всем типам меню сайта и для каждого из них с помощью специального вызова JHtml::_() конструируем чекбокс. Увидеть, как работает вызов JHtml можно открыв класс библиотеки Joomla, который расположен в /libraries/joomla/html/html.php, однако он достаточно сложен для понимания. Главное, что нужно знать при работе с вызовом JHtml - это то, где самому без труда найти реализацию. В нашем случае мы первым параметром передали 'select.option'. Что это значит? Это означает, что для построения элемента Joomla должна пойти в каталог /libraries/joomla/html/html/ , найти там файл select.php, а в нём - метод option(). Параметры этого метода option() и определяют то, что мы передаем после строки 'select.option', а именно:
Итак, мы собрали в массиве $options все построенные с помощью вызова JHtml::_() чекбоксы и надписи, осталось теперь сбросить внутренний указатель массива в начало - с помощью вызова reset($options) и вернуть $options в качестве возвращаемого значения метода. Что же касается метода getInput(), то в родительском классе JFormField этот метод является абстрактным (т.е. без тела и полезного кода). Это значит, что в дочернем классе - т.е. нашем JFormFieldMenuselector мы обязательно должны определить реализацию метода getInput() сами. Дело в том, что во время отрисовки страницы параметров нашего модуля в админке движок для формирования элемента управления вызовет именно метод getInput(), поэтому то, как в конечном счете выглядит наш параметр определяет именно этот метод. Первым делом мы вызываем getOptions(), который рассмотрели ранее, и в переменной $options получаем массив чекбоксов. Дальше мы в массив $html добавляем элементы, каждый из которых представляет собой строку с HTML-разметкой элемента управления. Обратите внимание, что через указатель $this мы можем обращаться, во-первых к атрибутам, которые можем задать в самом xml-файле дескриптора модуля. Это делается с помощью вызова $this->element['<имя_атрибута_из_xml>']. Во-вторых, через тот же указатель $this мы обращаемся к тем внутренним переменным, которые заданы в родительском классе JFormField, например вызов $this->id вернет автоматически сгенерированный id для нашего элемента управления. Он будет представлять из себя склейку вида "jform_params_<имя_параметра_в_xml><индекс_элемента_на_странице>" . Имя параметра в xml - это то, как мы назвали параметр в xml, индекс элемента на странице - Joomla вычислит сама. Например, наш параметр называется selected_menus. Предположим, что в xml-дескрипторе мы бы задали 10 параметров такого типа. Тогда в $this->id будет содержаться "jform_params_selected_menus0" для 1-го параметра, "jform_params_selected_menus1" - для 2-го параметра и т.д. Почему я заостряю на этом внимание? Конечно, может и Вам не нужно настолько глубоко вникать в логику движка Joomla. Но если Вы захотите писать свой собственный параметр модуля или компонента, Вам не обойти этот аспект стороной. А этой информации нет вообще нигде, даже на официальных форумах и ресурсах Joomla. Это просто заметки из личного опыта, поэтому будет напоминание не только вам, но и мне самому. Пишем входную точку для модуля mod_colormenu.phpДавайте напишем теперь файл mod_colormenu.php, который является входной точкой нашего модуля. Как я уже говорил, этот файл получает управление, как только движок Joomla начинает подгружать модуль в той позиции, в которой он установлен через панель администрирования. Цель данного файла - подгрузить необходимые библиотеки, файлы, ресурсы к модулю, после чего передать управление файлу шаблона модуля tmpl/default.php, который и осуществляет отрисовку модуля. <div>defined( '_JEXEC' ) or die( 'Restricted access' );[nl]</div><div>// Подключаем файл помощника helper.php с классом MenuItem и modColorMenuHelper[nl]</div><div>require_once(dirname(__FILE__).DS.'helper.php');[nl][nl]</div><div>// Создаем экземпляр помощника[nl]</div><div>$helper = new modColorMenuHelper();[nl][nl]</div><div>// Получаем ссылку на текущий документ[nl]</div><div>$doc = &JFactory::getDocument();[nl][nl]</div><div>// Подключаем файл стилей mod_colormenu.css к сайту[nl]</div><div>$module_name = 'mod_colormenu';[nl]</div><div>$doc-[gt]addStylesheet( JURI::root(true) . "/modules/".$module_name."/css/".$module_name.".css" );[nl][nl]</div><div>// По умолчанию устанавливаем значение для параметра 'selected_menus' как пустой массив[nl]</div><div><div>$params-[gt]def('selected_menus', array());[nl][nl]</div><div>// Получаем действительное значение параметра[nl]</div><div>$menuTypesArray = $params-[gt]get('selected_menus');[nl][nl]</div><div>// Получаем информацию о цвете фона и шрифта меню из параметров модуля[nl]</div><div><div>$bgColors<span class="Apple-tab-span" style="white-space:pre"> </span>= $params-[gt]get('menu_bgcolors');[nl]</div><div>$hoverColors<span class="Apple-tab-span" style="white-space:pre"> </span>= $params-[gt]get('menu_hovercolors');[nl]</div><div>$textColors<span class="Apple-tab-span" style="white-space:pre"> </span>= $params-[gt]get('menu_textcolors');[nl]</div><div>$hoverTextColors<span class="Apple-tab-span" style="white-space:pre"> </span>= $params-[gt]get('menu_hovertextcolors');[nl][nl]</div><div>// Задаем значения по умолчанию для параметров модуля[nl]</div><div><div>$params->def('mnu_item_width', '220px');[nl]</div><div>$params->def('enable_drop_symbol_main', '1');[nl]</div><div>$params->def('drop_symbol', '&' . '#x25ba;');[nl]</div><div>$params->def('enable_drop_symbol_dd', '1');[nl]</div><div>$params->def('drop_symbol_dd', '&' . '#x25ba;');[nl]</div><div><div>$mnuItemWidth = $params->get('mnu_item_width');[nl][nl]</div><div>// Параметры символа стрелки для выпадающих пунктов меню[nl]</div><div>$enableDropSymbol = intval($params->get('enable_drop_symbol_main'));[nl]</div><div>$dropSymbol = $params->get('drop_symbol');[nl]</div><div>$enableDropSymbolDD = intval($params->get('enable_drop_symbol_dd'));[nl]</div><div>$dropSymbolDD = $params->get('drop_symbol_dd');[nl]</div><div>$numMenuTypes = count($menuTypesArray);[nl][nl]</div><div>// Часть CSS-стилей мы должны подключить программно, т.к. они будут зависеть от параметров модуля[nl]</div><div><div>$doc->addStyleDeclaration("ul.dropdown li a { display: block; padding: 4px 8px !important; margin:0px !important; text-decoration:none !important; background:none !important; border-width:0px !important; border-radius:0px !important;}" .[nl]</div><div>"ul.dropdown li.hover > a { border-width:0px !important; }" .[nl]</div><div>"ul.dropdown ul li a:hover { text-decoration:none; color: red !important; background:none; background-color:none; }" . [nl]</div><div>"ul.dropdown ul { margin: 0 !important; padding: 0 !important; border: 0 !important; font-size: 100% !important; font: inherit !important; vertical-align: baseline !important; text-transform:none !important; list-style: none !important;" .[nl] "z-index:99; width: ".$mnuItemWidth."; visibility: hidden; position: absolute; top: 100% !important; left: 0 !important; }");[nl][nl]</div><div>// Далее следует довольно большой кусок кода, который сюда не будет вставлен для экономии места[nl]</div><div>// Суть его такова: дальше получаются параметры для каждого меню и те их значения (цвета шрифта, цвета фона и т.д.),[nl]</div><div>// что заданы в админке. Эти значения динамически формируют нужный вид CSS-стилей и эти стили подключаются к сайту[nl]</div></div></div></div></div></div>
Заполняем файл tmpl/default.phpТеперь мы должны взяться за написание файла шаблона модуля. Это тот самый файл, который и рисует финальную HTML-разметку под меню. Начнём с того, что получим текущего пользователя и проверим, а есть ли вообще меню, которые нужно рисовать, т.е. не пустой ли массив? Для начала добавим такую конструкцию: <div>$user = & JFactory::getUser();[nl]</div><div>if (!empty($menuTypesArray)) {[nl]</div><div>[tab]// здесь пробежать по всем типам меню и отрисовать каждый из них[nl]</div><div>}[nl]</div> Если массив типов меню пуст, то модуль вообще ничего не отрисует и мы не войдем в if. Если же у нас есть настроенные через админку Joomla меню, причем для них есть еще и опубликованные пункты меню, то мы должны "нарисовать" это в виде HTML-разметки. Поэтому алгоритм выходит примерно следующий: 1. Определить тип текущего меню из массива$menuTypesArray. Например для главного меню это будет mainmenu 2. Получить параметры для текущего типа меню. Нам нужно знать, каким образом назвать самый верхний уровень меню. И я решил, что это будет название самого типа меню. Если Вы помните, ранее мы уже добавили с Вами в helper.php методgetDefMenuParams, который получает название меню, название и ссылку для главного пункта меню. 3. Когда в п.2 мы отрисуем "верхний" элемент, нам останется получить все пункты для данного меню. Мы тоже об этом уже позаботились - у нас в helper.php уже заготовлена функцияgetMenuItems, которая по тип меню получает все его пункты меню. Мы должны учесть, что теоретически через админку можно снять с публикации все пункты меню. Поэтому мы поставим проверку на то, есть ли у нас опубликованные пункты меню. 4. Получив список пунктов меню, пробежаться по каждому пункту и нарисовать его. Переводя описанный выше алгоритм на PHP, получается следующий код: <div>$user = & JFactory::getUser();[nl]</div><div>if (!empty($menuTypesArray)) {[nl]</div><div>[tab]echo "[lt]ul class='dropdown'[gt]";[nl]</div><div>[tab]foreach ($menuTypesArray as $menuType) {[nl]</div><div>[tab][tab]$menuType = trim($menuType);[nl]</div><div>[tab][tab]$menuDefParams = $helper->getDefMenuParams($menuType);[nl]</div><div>[tab][tab]// Получить в $menuItems все пункты меню для данного меню[nl]</div><div>[tab][tab]$menuItems = $helper->getMenuItems($menuType);[nl]</div><div>[tab][tab]// Есть ли пункты меню, опубликованные в админке?[nl]</div><div>[tab][tab]if (!empty($menuItems)) {[nl]</div><div>[tab][tab][tab]// Отрисовываем пункт меню[nl]</div><div><div>[tab][tab][tab]echo "[lt]li class='". $menuType . "'[gt]";[nl]</div><div>[tab][tab][tab]echo "[lt]a href='" . JRoute::_($menuDefParams-[gt]link) . "'[gt]";[nl]</div><div>[tab][tab][tab]// Если разрешен символ стрелки в параметрах модуля - нарисуем его...[nl]</div><div>[tab][tab][tab]echo $enableDropSymbol ? $dropSymbol . "&" . "nbsp;" . "&" . "nbsp;" : "";[nl]</div><div>[tab][tab][tab]// Выводим непосредственно название меню[nl]</div><div>[tab][tab][tab]echo $menuDefParams-[gt]menutitle;[nl]</div><div>[tab][tab][tab]echo "[lt]/a[gt]";[nl]</div><div>[tab][tab][tab]// Теперь рисуем внутренний UL-список со всеми пунктами меню[nl]</div><div>[tab][tab][tab]echo "[lt]ul class='sub_menu ". $menuType . "'[gt]";[nl]</div><div>[tab][tab][tab]// Пошел цикл по пунктам меню:[nl]</div><div><div>[tab][tab][tab]foreach ($menuItems as $item) {[nl]</div><div>[tab][tab][tab][tab]$item->setEnableDropSymbol($enableDropSymbolDD);[nl]</div><div>[tab][tab][tab][tab]$item->setDropSymbol($dropSymbolDD);[nl]</div><div>[tab][tab][tab][tab]// Данный пункт меню доступен и виден текущему пользователю с его набором прав?[nl]</div><div>[tab][tab][tab][tab]// Доступность определяется методом isAccessable, который мы добавляли в класс MenuItem[nl]</div><div>[tab][tab][tab][tab]if ($item->isAccessable($user)) {[nl]</div><div>[tab][tab][tab][tab][tab]// Если пункт меню видим - отрисовать его методом render(). Метод рекурсивен, т.е. если пункт меню сам[nl]</div><div>[tab][tab][tab][tab][tab]// является родительским, и у него есть подменю - render() отрисует еще один внутренний UL-список...[nl]</div><div>[tab][tab][tab][tab][tab]echo $item->render();[nl]</div><div>[tab][tab][tab][tab]}[nl]</div><div>[tab][tab][tab]}[nl]</div><div>[tab][tab][tab]echo "[lt]/ul[gt]";[nl]</div><div>[tab][tab][tab]echo "[lt]/li[gt]";[nl]</div></div></div><div>[tab][tab]}[nl]</div><div>[tab]}[nl]</div><div>[tab]echo "[lt]/ul[gt]";[nl]</div><div>[tab]echo "[lt]br clear='all' /[gt]";[nl]</div><div>}[nl]</div> В тексте статьи я использовал команды echo для вывода и отрисовки HTML-разметки - потому что так проще форматировать код на сайте. В скачанном модуле вы увидите, что PHP-код отделен в шаблоне от HTML-разметки, но суть от этого ничуть не меняется. Готовим CSS-стили для отрисовки менюПоскольку мы адаптируем внешнее меню SimplejQueryDropdowns, которое мы обсуждали и скачали в части 1 данной статьи, то целесообразно взять за основу те CSS-стили, которые уже есть в поставке этого меню. При распаковке архива с сторонним меню, мы увидим 2 файла во вложенном каталоге css - это файлы ie.css и style.css. Первый - это специализированный файл стилей, который подключается к сайту, если пользователь заходит на сайт через Internet Explorer (IE настолько специфичен, что для него как правило включается отдельный файл стилей, чтобы позволить разметке сайта отображаться корректно). Второй файл - style.css - является основным файлом стилей стороннего меню. Копируем из него все стили и для начала вставляем в файл mod_colormenu.css, который располагается в каталоге /mod_colormenu/css/mod_colormenu.css нашей заглушки. Если сейчас наш модуль-заглушка включен на сайте и мы зайдем на сайт и обновим страницу в браузере, то увидим ужасный результат: вместо ожидаемого стороннего меню все блоки, пункты меню и т.д. "поплыли", т.е. CSS-стили, которые идут вместе с сторонним меню и прекрасно с ним работают почему-то напрочь отказываются работать на нашем сайте Joomla! Почему это происходит? Все дело в шаблонах Joomla. Обычно они уже содержат огромную массу стилей - как общих, т.е. стилизующих HTML-элементы вроде <div>, <ul>, <li> и т.д., так и узкоспециализированных CSS-стилей, которые придают вид боковым панелям сайта, основной области, шапке сайта и так далее. Так вот, когда мы вставили CSS-стили, идущие в поставке стороннего меню и оставили их "как есть", то с вероятностью 99,9% наше внешнее меню не заработает сразу на конкретном Joomla шаблоне. Здесь начинается поистине колдовство и "подгонка" CSS-разметки модуля таким образом, чтобы он отображался в шаблоне Joomla правильно. Какой-то общей методики не существует, результат зависит от знания CSS и практики. В целом же совет таков: использовать как можно больше инструментов, встроенных в Ваш браузер, чтобы понять, какой набор CSS стилей применяется к интересующему нас элементу - например, пункту меню. Также нужно постараться выявить в текущем шаблоне Joomla самые общие стили. Например, следующего плана: /*Пример того, как шаблон Joomla может использовать */ /* слишком общие стили для всего сайта. Тем самым шаблон может портить */ /* нашу разметку меню, заставляя её съезжать и приобретать ненужные свойства */ li { border: 1px solid black; } ul { background-color: #000; } В приведенном выше примере два стиля в шаблоне автоматически применятся ко всем UL и LI тегам, если они встречаются в нашем модуле. Как с этим бороться? Добавлять собственные классы и использовать в них конструкцию !important, которая заставляет форсированно применить наш стиль, даже если более общий задан в шаблоне. Добавляем языковые файлыВ каталоге language создаем два каталога - en-GB и ru-RU. В каждый каталог помещаются сооветствующие языковые файлы нашего модуля для поддержки нескольких языков. Например, в каталоге ru-RU создаем два файла: ru-RU.mod_colormenu.ini и ru-RU.mod_colormenu.sys.ini. Содержимое файлов - это перевод для всех строк, встречающихся на странице настроек модуля и в других его частях. Посмотреть, как наполнить эти файлы можно, скачав готовый модуль внизу этой страницы. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Last Updated on Monday, 23 December 2013 01:41 |