Наш блог

​Добавить значки-иконки в меню CMenu – это просто!

​Добавить значки-иконки в меню CMenu – это просто!

Свяцкий Вова
Team Lead Developer
01.07.16
1898

Yii Framework

Существует достаточно большая вероятность того, что как в новом, так и в старом проекте на фреймворке Yii появится необходимость создания меню с иконками. Но также это может понадобиться при верстке уже готового шаблона. Давайте детально рассмотрим несколько возможных решений и сравним их между собой с точки зрения семантики, и архитектурной чистоты. Например, значки для пунктов меню или навигатора удобно использовать прямо в панели администрирования (как на изображении).

Стандартное использование widgets CMenu в представлении выглядит следующим образом:

<?php $this->widget('zii.widgets.CMenu', array(
   'items'=>array(
       array(
           'label'=>'->Home', 
           'url'=>array('site/index')
       ),
       array(
           'label'=>'Logout', 
           'url'=>array('site/logout'),
           'visible'=>!Yii::app()->user->isGuest
       ),
       array(
           'label'=>'Login', 
           'url'=>array('site/login),
           'visible'=>Yii::app()->user->isGuest
       ),
   ),
)); ?>

Массив пунктов для items удобнее генерировать в модели. В модель OscorpCategory можно добавить метод getMenuList() (его можно взять из поведения DCategoryBehavior) и выводить меню «Категории» простым использованием данного метода:

class OscorpCategory extends CActiveRecord
{     
   public function getMenuList()
   {
       $item = array();  


       $model = $this->findAll(array('order'=>'mtitle ASC');        
       foreach ($model as $models)
       {
           $item[] = array(
               'label'=>$models->mtitle,
               'url'=>Yii::app()->createUrl('article/OscorpCategory' array('id'=>$models->id)),      
           );
       }
       return $item;
   }
}
<?php $this->widget('zii.widgets.CMenu', array(
   'items'=>OscorpCategory::model()->getMenuList();
)); ?>

Теперь можно рассмотреть примеры размещения иконок меню.

1.) Ручное размещение иконки в качестве фона элемента

Данный способ предполагает размещение иконки фоном в инлайновом стиле элемента, что генерирует код <li style="background-image:url(...)"> для каждого пункта.

<?php $this->widget('zii.widgets.CMenu', array(
   'items'=>array(
       array(
           'label'=>'->Home', 
           'url'=>array('site/index'),      'linkOptions'=>array('style'=>'background-image:url(/icons/home-ico.gif);')          
       ),
   ),
)); ?>

2.) Ручное размещение тега IMG в надписи

Приведенный способ подразумевает прямую вставку пользователем тега <img src="..." /> к надписи для передачи полю label. Для поддержки HTML кода в надписях нужно отключить экранирование HTML-сущностей, указав для этого 'encodeLabel'=>false:

<?php $this->widget('zii.widgets.CMenu', array(
   'encodeLabel'=>false,
   'items'=>array(
       array(
           'label'=>'<img src="/icons/home-ico.gif" /> ->Home', 
           'url'=>array('site/index')         
       ),
   ),
)); ?>

Так как кодирование отключено, то в этом случае нам необходимо самостоятельно экранировать символы названия категории в методе getMenuList() вызывая CHtml::encode($model->name):

<?php $this->widget('zii.widgets.CMenu', array(
   'encodeLabel'=>false,
   'items'=>OscorpCategory::model()->getMenuList();
)); ?>
class OscorpCategory extends CActiveRecord
{
   protected $iconPath = 'images/icons',
   public function getMenuList()
   {
       $items = array();  
       $models = $this->findAll(array('order'=>'mtitle ASC');        
       foreach ($models as $model)
       {
           $image = CHtml::image($model->getIconUrl());
           $items[] = array(
               'label'=>$image . ' ' . $model->mtitle,
               'url'=>Yii::app()->createUrl('article/OscorpCategory' array('id'=>$model->id)),         
           );
       }


       return $items;
   }
   public function getIconUrl()
   {
       return Yii::app()->request->baseUrl . '/' . $this->iconPath . '/' . $this->icon;
   }    
}

Оценка решений

Приведенные выше способы являются немного «костыльными», так как, во-первых, они не универсальны, а во-вторых несемантические. Несемантические, потому что это заставляет нас заботиться о представлении пунктов и генерировать HTML/CSS код. Не универсальны и не полиморфны – так как не позволяют прозрачно использовать один и тот же метод getMenuList() в разных типах меню.

У нас может возникнуть потребность в выводе данных категорий как выпадающие подпункты без иконок в главном меню, находящимся в шапке сайта

<?php $this->widget('zii.widgets.CMenu', array(
   'items'=>array(
       array(
           'label'=>'->Home', 
           'url'=>array('site/index'),         
       ),
       array(
           'label'=>'->Article', 
           'url'=>array('article/index'),
           'items'=>OscorpCategory::model()->getMenuList();          
       ),
       array(
           'label'=>'->Contact', 
           'url'=>array('site/contact'),         
       ),
   ),
)); ?>

и одновременно как меню разделов с иконками в сайдбаре:

<?php $this->widget('zii.widgets.CMenu', array(
   'items'=>OscorpCategory::model()->getMenuList();
)); ?>

Тогда для каждого типа меню придется делать свой метод, вроде getSimpleMenuList(), getIconMenuList() и т.д.

Следовательно, 2 данных подхода нарушают архитектурную чистоту кода, так как поставщик данных, не должен производить никаких стилистических манипуляций с данными.

3. Семантическое решение

С точки зрения семантики правильнее будет производить генерацию HTML-разметки или CSS-стилей в самом widgets, на основе данных, переданных ему от модели. Для этого нужно создать свой модернизированный widgets меню для каждого конкретного случая. Отнаследуемся от стандартного класса меню и перепишем в своем методе генерации пункта:

Yii::import('zii.widgets.CMenu');


class DOscorpMenus extends CMenu
{
   public $iconsPath = '/';
   protected function renderMenuItem($item)
   {
       $icon = !empty($item['icon']) ? CHtml::image($this->iconsPath . $item['icon'], $item['label']) : '';
       $options = isset($item['linkOptions']) ? $item['linkOptions'] : array();


       if(isset($item['url']))
       {
           if ($this->linkLabelWrapper !== null)
               $label = '<' . $this->linkLabelWrapper . '>' . $item['label'] . '</' . $this->linkLabelWrapper . '>';
           else
               $label = $item['label'];


           return $icon . CHtml::link($label, $item['url'], $options);
       }
       else
           return $icon . CHtml::tag('span', $options, $item['label']);
   }
}

Здесь мы воспользовались вариантом добавления тега <img>. При желании можно подсмотреть реализацию варианта с background-image:url(...) в компоненте OscorpMenu.

Где необходимо (например, в главном меню или же в боковой колонке сайта) использовать данное меню, вместо стандартного:

<?php $this->widget('DOscorpMenus', array(
   'iconPath'=>Yii::app()->request->baseUrl . '/icons-path/',
   'items'=>array(
       array(
            'label'=>'->Home',
            'url'=>array('site/index'),
            'icon'=>'nome-ico.png'
       ),
   ),
)); ?>

Мы добавили новый параметр icon для каждого пункта и параметр iconPath для указания пути. Модернизированная модель OscorpCategory теперь может выглядеть следующим образом

class OscorpCategory extends CActiveRecord
{    
   public $iconsPath = '/';    


   public function getMenuList()
   {
       $items = array();  


       $models = $this->findAll(array('order'=>'mtitle ASC');        
       foreach ($models as $model)
       {
           $items[] = array(
               'label'=>$model->mtitle,
               'icon'=>$model->getIconUrl(),         
               'url'=>$model->getUrl(),         
           );
       }


       return $items;
   }    


   public function getIconUrl()
   {
       return Yii::app()->request->baseUrl . '/' . $this->iconPath . '/' . $this->icon;
   } 


   private $_url;
   public function getUrl()
   {
       if ($this->_url === null)
           $this->_url = Yii::app()->createUrl('article/OscorpCategory' array('id'=>$model->id));
       return $this->_url;
   }
}

То есть данный метод ничем не отличается от первоначального стандартного варианта, за исключением добавленного поля иконки. Один и тот же getMenuList() можно полиморфно использовать для DOscorpMenus, CMenu и остальных виджетов меню.

Этот виджет сформирует меню с иконками вида

<ul>
   <li><img src="/icons/home-ico.png" alt="Home" /><a href="/">->Home</a></li>
</ul>

При желании вы можете написать свой класс, который так же как в OscorpMenu будет добавлять стили к пунктам вроде

<ul>
   <li><a href="/" style="background-image:url('/icons/home-ico.png');" alt="Home" />->Home</a></li>
</ul>

На этом можно поставить точку. Приведенные примеры пригодятся для выполнения аналогичных задач. Не стоит бояться модернизировать уже готовые чужие классы, наследуясь от них и переопределяя некоторые их методы. Это намного удобнее, чем каждый раз придумывать что-то новое или изменять системные файлы, таким образом только усложняя собственную работу.



Поделиться с друзьями

Сделайте заказ услуги прямо сейчас