A free and open-source book on ZF3 for beginners


6.9. Написание собственных помощников видов

Ранее в этой главе мы создали лэйаут, общий для всех страниц веб-сайта. Однако нужно сделать еще пару вещей, чтобы лэйаут стал полностью функционирующим. Если помните, шаблон лэйаута содержит панель навигации и навигационную цепочку. Но оба этих компонента интерфейса на данный момент "статичны", а их необходимо сделать более интерактивными.

Например, активный элемент панели навигации должен зависеть от действия контроллера, выполняемого в данный момент. А навигационная цепочка должна отображать путь к текущей просматриваемой странице. В этом разделе мы сделаем эти виджеты полностью готовыми для работы на сайте с помощью наших собственных помощников видов.

Типичный помощник вида - это PHP-класс, который наследуется от класса Zend\View\Helper\AbstractHelper, который в свою очередь реализует интерфейс Zend\View\Helper\HelperInterface (диаграмма наследования классов представлена на рисунке 6.9).

Рисунок 6.9. Диаграмма класса помощника вида Рисунок 6.9. Диаграмма класса помощника вида

6.9.1. Меню

Для начала реализуем класс помощника вида Menu, который будет визуализировать HTML-код навигационной панели. Класс Menu будет содержать несколько методов, позволяющих задать элементы меню в виде массива, определить активный пункт меню и отобразить меню (описание методов см. в таблице 6.4)

Таблица 6.4. Методы помощника вида Menu
Имя метода Описание
__construct($items) Конструктор класса.
setItems($items) Метод для задания пунктов меню.
setActiveItemId($activeItemId) Метод для задания активного пункта меню.
render() Визуализирует меню.
renderItem($item) Визуализирует один пункт меню.

Информация, описывающая один пункт меню будет представлена в виде массива, как показано ниже (например, у элемента Home будут ID, текстовая надпись и URL для гиперссылки):

[
    'id' => 'home',
    'label' => 'Home',
    'link' => $this->url('home')
]

Помимо этого мы хотим добавить поддержку выпадающих меню в качестве элементов навигации. Например, в случае с выпадающим меню Support, содержащим подэлементы Documentation и Help, описание элемента будет выглядеть следующим образом:

[
    'id' => 'support',
    'label' => 'Support',
    'dropdown' => [
        [
            'id' => 'documentation',
            'label' => 'Documentation',
            'link' => $this->url('doc', ['page'=>'contents'])
        ],
        [
            'id' => 'help',
            'label' => 'Help',
            'link' => $this->url('static', ['page'=>'help'])
        ]
    ]                        
]

Мы хотим поместить класс Menu в пространство имен Application\View\Helper. Поэтому, начнем с создания файла Menu.php в директории View/Helper под корневым каталогом модуля Application (рисунок 6.10).

Рисунок 6.10. Каталог помощника вида Рисунок 6.10. Каталог помощника вида

Почему мы размещаем класс помощника вида под корневым каталогом модуля?

Помощники видов (в отличие от шаблонов представления .phtml) хранятся под корневой директорией модуля src/, потому что они являются обычными PHP-классами и должны быть разрешены (resolve) функцией автозагрузки. С другой стороны, шаблоны представления разрешаются специальным ZF3-классом, называемым разрешатель представления (view resolver), и по этой причине шаблоны представления хранятся под каталогом модуля view/.

Затем напишем код класса Menu:

<?php
namespace Application\View\Helper;

use Zend\View\Helper\AbstractHelper;

// Этот класс помощника вида отображает панель меню.
class Menu extends AbstractHelper 
{
    // Массив пунктов меню.
    protected $items = [];
    
    // ID активного пункта.
    protected $activeItemId = '';
    
    // Конструктор.
    public function __construct($items=[]) 
    {
        $this->items = $items;
    }
    
    // Задаем пункты меню.
    public function setItems($items) 
    {
        $this->items = $items;
    }
    
    // Задаем ID активных пунктов.
    public function setActiveItemId($activeItemId) 
    {
        $this->activeItemId = $activeItemId;    
    }  
}

В этом фрагменте мы определили несколько private-полей для класса Menu. Поле $items (строка 10) - это массив, который будет хранить информацию об элементах меню; а поле $activeItemId - ID активного пункта меню. Этот пункт будет визуально выделен.

В строках 16-19 мы определили метод конструктора класса, который (опционально) принимает массив элементов для инициализации меню. Другой способ инициализировать меню - через метод setItems() (строки 22-25). И наконец, метод setActiveItemId() задает ID текущего активного пункта меню.

Теперь давайте добавим метод render(), который будет генерировать HTML-код для всей панели навигации и вернет его в виде строки:

// Визуализация меню.
public function render() 
{
    if (count($this->items)==0)
        return ''; // Do nothing if there are no items.
        
    $result = '<nav class="navbar navbar-default" role="navigation">';
    $result .= '<div class="navbar-header">';
    $result .= '<button type="button" class="navbar-toggle" ';
    $result .= 'data-toggle="collapse" data-target=".navbar-ex1-collapse">';
    $result .= '<span class="sr-only">Toggle navigation</span>';
    $result .= '<span class="icon-bar"></span>';
    $result .= '<span class="icon-bar"></span>';
    $result .= '<span class="icon-bar"></span>';
    $result .= '</button>';
    $result .= '</div>';
       
    $result .= '<div class="collapse navbar-collapse navbar-ex1-collapse">';
    $result .= '<ul class="nav navbar-nav">';
        
    // Визуализация элементов
    foreach ($this->items as $item) {
        $result .= $this->renderItem($item);
    }
        
    $result .= '</ul>';
    $result .= '</div>';
    $result .= '</nav>';
        
    return $result;
}

В представленном выше коде мы создаем HTML-разметку для компонента панели навигации Bootstrap. Панель будет использовать стандартную тему и будет складной (то есть, адаптироваться к различным размерам экрана). Также у нее не будет текста с названием компании в заголовке. В строках 22-24 мы перебираем все пункты меню и визуализируем каждый с помощью метода renderItem(). После этого метод render() возвращает получившийся HTML-код в виде строки.

В завершение создания класса Menu, реализуем метод renderItem(). Он будет создавать HTML-код для одного пункта меню:

// Визуализирует элемент.
protected function renderItem($item) 
{
    $id = isset($item['id']) ? $item['id'] : '';
    $isActive = ($id==$this->activeItemId);
    $label = isset($item['label']) ? $item['label'] : '';
        
    $result = ''; 
        
    if(isset($item['dropdown'])) {
            
        $dropdownItems = $item['dropdown'];
            
        $result .= '<li class="dropdown ' . ($isActive?'active':'') . '">';
        $result .= '<a href="#" class="dropdown-toggle" data-toggle="dropdown">';
        $result .= $label . ' <b class="caret"></b>';
        $result .= '</a>';
           
        $result .= '<ul class="dropdown-menu">';
 
        foreach ($dropdownItems as $item) {
            $link = isset($item['link']) ? $item['link'] : '#';
            $label = isset($item['label']) ? $item['label'] : '';
                
            $result .= '<li>';
            $result .= '<a href="'.$link.'">'.$label.'</a>';
            $result .= '</li>';
        }
    
        $result .= '</ul>';
        $result .= '</a>';
        $result .= '</li>';
            
    } else {        
        $link = isset($item['link']) ? $item['link'] : '#';
            
        $result .= $isActive?'<li class="active">':'<li>';
        $result .= '<a href="'.$link.'">'.$label.'</a>';
        $result .= '</li>';
    }
    
    return $result;
}

Итак, выше, в коде метода renderItem() мы проделали следующее. Сначала мы проверили, является ли элемент выпадающим меню или обычным элементом (строка 10). В первом случае мы проходим по всем пунктам выпадающего меню и визуализируем их по очереди (строки 21-28). Строки 35-39 содержат код визуализации для второго случая.

Чтобы иметь возможность использовать помощник вида Menu в шаблоне представления, нужно зарегистрировать его в конфигурации. Для этого добавьте ключ view_helpers в файл module.config.php:

<?php
return [

    // ...
    
    // Следующие строки регистрируют наши пользовательские классы
    // помощников видов в менеджере плагинов.
    'view_helpers' => [
        'factories' => [
            View\Helper\Menu::class => InvokableFactory::class,                    
        ],
       'aliases' => [
            'mainMenu' => View\Helper\Menu::class
       ]
    ],    
];

В примере выше мы зарегистрировали наш класс Menu как помощник вида mainMenu. Мы сможем обращаться к нему из любого шаблона представления.

<!-- Панель навигации -->
<?php      
$this->mainMenu()->setItems([
    [
        'id' => 'home',
        'label' => 'Home',
        'link' => $this->url('home')
    ],
    [
        'id' => 'downloads',
        'label' => 'Downloads',
        'link' => $this->url("application", ['action'=>'downloads'])
    ],
    [
        'id' => 'support',
        'label' => 'Support',
        'dropdown' => [
            [
                'id' => 'documentation',
                'label' => 'Documentation',
                'link' => $this->url('doc', ['page'=>'contents'])
            ],
            [
                'id' => 'help',
                'label' => 'Help',
                'link' => $this->url('static', ['page'=>'help'])
            ]
        ]                        
    ],
    [
        'id' => 'about',
        'label' => 'About',
        'link' => $this->url('about')
    ],
]);

echo $this->mainMenu()->render(); 
?>

В этом фрагменте мы обращаемся к зарегистрированному ранее помощнику вида mainMenu и задаем элементы панели навигации с помощью метода setItems() (строка 3). В качестве параметра мы передаем методу массив элементов. Затем мы визуализируем панель навигации с помощью метода render().

Для задания активного элемента панели навигации, можно вызвать метод setActiveItemId() из любого шаблона представления. Например, добавьте следующий код в начало шаблона представления страницы About (application/index/about.phtml).

<?php
$this->mainMenu()->setActiveItemId('about');
?>

Если вы теперь откроете страницу About в своем браузере, вы увидите, что пункт About меню навигации выделен другим цветом. Чтобы должным образом отобразить активный элемент, надо вызывать метод setActiveItemId() для каждой страницы, находящейся в панели навигации (Home, Downloads, Documentation и т.д.) Можете посмотреть, как это сделано, в нашем примере Hello World.

6.9.2. Навигационная цепочка

Теперь, когда вы знаете, как реализовать помощник вида, давайте создадим еще один для отображения навигационной цепочки. Он полностью аналогичен помощнику вида Menu, так что мы просто приведем код класса Breadcrumbs целиком.

<?php
namespace Application\View\Helper;

use Zend\View\Helper\AbstractHelper;

// Этот класс помощника вида визуализирует навигационную цепочку.
class Breadcrumbs extends AbstractHelper 
{
    // Массив элементов.
    private $items = [];
    
    // Конструктор.
    public function __construct($items=[]) 
    {                
        $this->items = $items;
    }
    
    // Задаем элементы.
    public function setItems($items) 
    {
        $this->items = $items;
    }
    
    // Визуализируем навигационную цепочку.
    public function render() 
    {
        if(count($this->items)==0)
            return ''; // Ничего не делать, если элементов нет.
        
        // Полученный HTML-код будет храниться в этой переменной
        $result = '<ol class="breadcrumb">';
        
        // Получаем количество элементов
        $itemCount = count($this->items); 
        
        $itemNum = 1; // счетчик элементов
        
        // Проходим по элементам
        foreach ($this->items as $label=>$link) {
            
            // Делаем последний элемент неактивным
            $isActive = ($itemNum==$itemCount?true:false);
            
            // Визуализируем текущий элемент
            $result .= $this->renderItem($label, $link, $isActive);
                        
            // Инкрементируем счетчик элементов
            $itemNum++;
        }
        
        $result .= '</ol>';
        
        return $result;
    }
    
    // Визуализируем элемент.
    protected function renderItem($label, $link, $isActive) 
    {
        $result = $isActive?'<li class="active">':'<li>';
       
        if(!$isActive)
            $result .= '<a href="'.$link.'">'.$label.'</a>';
        else
            $result .= $label;
                    
        $result .= '</li>';
    
        return $result;
    }
}

Чтобы иметь возможность использовать помощник вида Breadcrumbs, нужно зарегистрировать его в файле module.config.php следующим образом:

<?php
return [

    //...
    
    // The following registers our custom view helper classes.
    'view_helpers' => [
        'factories' => [      
            View\Helper\Breadcrumbs::class => InvokableFactory::class,          
        ],
        'aliases' => [
            'pageBreadcrumbs' => View\Helper\Breadcrumbs::class,
        ]
    ],
];

Так как мы планируем использовать помощник вида Breadcrumbs в шаблоне представления лэйаута, замените разметку навигационной цепочки в файле layout.phtml следующим кодом:

<!-- Навигационная цепочка -->
<?= $this->pageBreadcrumbs()->render(); ?>

В этих строках мы обращаемся к помощнику вида pageBreadcrumbs() и вызываем его методом render(). Оператор echo затем выводит HTML-код навигационной цепочки.

После этого нужно передать элементы навигационной цепочки для каждого шаблона представления. Например, для страницы About добавьте следующий фрагмент кода в шаблоне представления.

<?php
$this->pageBreadcrumbs()->setItems([
            'Home'=>$this->url('home'),
            'About'=>$this->url('about'),
            ]);
?>

Теперь при открытии страницы вы увидите навигационную цепочку как на рисунке 6.11. Пользователям сайта будет видно, на какой странице они находятся в данный момент, и они не смогут заблудиться.

Figure 6.11. Навигационная цепочка для страницы About Figure 6.11. Навигационная цепочка для страницы About


Top