Zend Navigation es un componente muy notable como muchos que tenemos en Zend Framework 2. En este artículo vamos a poner un ejemplo de implementación y cómo relacionarlo con otro componente como Zend Route. Además no sólo lo emplearemos para componer nuestro menú personalizado sino pasar información a la vista exclusiva de cada página.

La documentación está basada principalmente en la excelente ayuda de la documentación de Zend Framework 2.

Configuración de la aplicación

Deberemos de incorporar al Service Manager el componente Zend\Navigation en la configuración. Podemos elegir cargar el componente en el module.config.php; o bien si lo vamos a utilizar en toda la aplicación lo incluiremos en el global.php. Esto dependerá si vamos a trabajar con módulos y no en todos lo vamos a emplear: es un principio de economía.

[...]
'service_manager' => array(
  'factories' => array(
    'navigation' => 'Zend\Navigation\Service\DefaultNavigationFactory',
  ),
),
[...]

A continuación y una vez incorporado al Service Manager deberemos de incluir nuestro mapa de la aplicación con la estructura del menú. Para ello igual que en el caso anterior, si vamos a utilizar el componente de Navegación en toda la web deberemos de incluir un array en global.php. En caso contrario podemos incluirlo en module.config.php. Por la estructura de bootstrapping de ZF2 podemos incluso definir en cada módulo sólo las partes correspondiente a cada uno de ellos. Como podemos ver todo el componente lo definiremos utilizando el patrón Factory.

[...]
'navigation' => array(
  'default' => array(
    array(
      'label' => 'Inicio',
      'uri' => '/index/index',
    ),
    array(
      'label' => 'Segundo',
      'uri' => '/index/segundo',
    ),
    array(
      'label' => 'Tercero',
      'uri' => '/index/tercero',    
    ),
  ),
),
[...]

Por último una vez configurado la estructura de nuestra web o menú, podemos directamente invocarlo en el layout. Para ello nos dirigiremos a nuestra plantilla y ahí crearemos objeto menú. Podemos eliminar el código existente en el menú del web y sustituirlo por el siguiente.

echo $this->navigation('navigation')->menu();

El resultado será el siguiente a nivel gráfico:

ZF2 Nav1

Efectivamente el resultado no es demasiado espectacular. A nivel de HTML el código que ha generado nuestra configuración es el siguiente:

<ul class="navigation">
 <li>
   <a href="/index/index">Inicio</a>
 </li>
 <li>
   <a href="/index/segundo">Segundo</a>
 </li>
 <li>
   <a href="/index/tercero">Tercero</a>
 </li>
</ul>

Aplicar plantillas a nuestros menús

Lógicamente a nivel estético deja bastante que desear, pero por suerte podemos añadir un partial que nos componga y personalice el esquema HTML. En este caso siguiendo el esquema del componente de menús de Bootstrap. Como decíamos deberemos incluir un nuevo partial (suponemos que ya sabemos como añadir un partial a nuestro layout) que este caso denominaremos partial/menubar, con el siguiente código en su interior:

<ul class="nav navbar-nav">
<?php foreach ($this->container as $page): ?>
<?php /* @var $page Zend\Navigation\Page\Mvc */ ?>
<?php // when using partials we need to manually check for ACL conditions ?>
<?php if (!$page->isVisible() || !$this->navigation()->accept($page)) continue; ?>
<?php $hasChildren = $page->hasPages() ?>
<?php if (!$hasChildren): ?>
<li <?php if ($page->isActive()) echo 'class="active"' ?>>
<a href="<?php echo $page->getHref() ?>">
<?php if ($page->get('icon')): ?>
<i class="<?php echo $page->get('icon') ?>"></i>
<?php endif ?>
<span><?php echo $this->translate($this->translate($page->getLabel())) ?></span>
</a>
</li>
<?php else: ?>
<li class="dropdown
<?php foreach ($page->getPages() as $child): ?>
<?php if ($child->isActive()): ?>
active
<?php break; ?>
<?php endif ?>        
<?php endforeach ?>
">
<a class="dropdown-toggle" data-toggle="dropdown" href="#">
<?php if ($page->get('icon')): ?>
<i class="<?php echo $page->get('icon') ?>"></i>
<?php endif ?>
<span><?php echo $this->translate($page->getLabel()) ?></span><b class="caret"></b>
</a>
<ul class="dropdown-menu">
<?php foreach ($page->getPages() as $child): ?>
<?php // when using partials we need to manually check for ACL conditions ?>
<?php if (!$child->isVisible() || !$this->navigation()->accept($child)) continue; ?>
<li>
<?php $child->setParams(array('action' =>'index'));
// Very important: Set the Action first to avoid problems in URL!!!
?>   
<a href="<?php echo $child->getHref() ?>">
<?php if ($child->get('icon')): ?>
<i class="<?php echo $child->get('icon') ?>"></i>
<?php endif ?>
<?php echo $this->translate($child->getLabel()) ?>
</a>
</li>
<?php endforeach ?>
</ul>
</li>
<?php endif ?>
<?php endforeach ?>
</ul>

Ahora a la hora de invocar nuesto menú en el layout le inyectaremos el nuevo partial.

$this->navigation('navigation')->menu()->setPartial('partial/menubar', '');
echo $this->navigation('navigation')->menu();

Y ahora como podemos comprobar la estética ha mejorado mucho. Además si examinamos el código HTML podremos comprobar cómo estamos aplicando los estilos que hemos incluido en nuestro layout.

ZF2 Nav2

Configuración avanzada

Hasta aquí hemos visto un simple ejemplo de implementación, pero tenemos muchas más funcionalidades que vamos a explorar, como detectar cual es la página activa, anidar páginas y jerarquías en el menú, e incluso añadir iconos y atributos extra que podemos utilizar en el menú o en la página.

Anidar elementos

Para detectar cual de las páginas está activa y por lo tanto añadir una clase CSS al elemento del menú correspondiente deberemos de utilizar como método para generar los enlaces el propio componente Route. En lugar de simplemente informar de una URI, definiremos una ruta. Para ello deberemos de asegurarnos que la ruta application por defecto tiene la siguiente configuración, siendo de tipo Segment:

'application' => array(
'type'  => 'Segment',
'options' => array(
'route'  => '/application/[:controller[/:action]]',
'constraints' => array(
'controller' => '[a-zA-Z][a-zA-Z0-9_-]*',
'action'   => '[a-zA-Z][a-zA-Z0-9_-]*',
),
'defaults' => array(
),
),
),

A continuación, cambiaremos la configuración del factory de nuestro elemento Navigation a una configuración por rutas con elementos anidados. Como podemos comprobar ahora incorporamos la información de controlador e incluso la acción se es necesario. Es la configuración de la ruta quien hace el resto:

'navigation' => array(
'default' => array(
array(
'label' => 'Inicio',
'route' => 'home',
),
array(
'label' => 'Segundo',
'route' => 'application',
'controller' => 'index',
),
array(
'label' => 'Tercero',
'uri' => '#',
'class' => 'dropdown',
'pages' => array(
array(
'label' => 'Anidado 1',
'route' => 'application',
'controller' => 'anidado',
'action'   => 'foo',
),
array(
'label' => 'Anidado 2',
'route' => 'application',
'controller' => 'anidado',
'action'   => 'bar',
),
),
),
),
),

Como podemos comprobar nuestro layout ha generado el correspondiente desplegable siguiendo las pautas HTML de Bootstrap.

ZF2 Nav3

Detectar elementos activos

Otra de las ventajas de utilizar la composición de los elementos y los enlaces por Route es que de este modo nuestra aplicación es capaz de identificar cual de los elementos es el que estamos visitando en función de la ruta. De esta manera puede añadir una clase CSS como active para poder destacar ese elemento de menú, como podéis comprobar en los ejemplos anteriores.

Añadir propiedades extra

Cuando definimos una Page en nuestro Navigation no sólo podemos informarle de su descipción, URI, etc. Podemos incluir cuantas propiedades deseemos, como una la clase de un icono que acompañe a la descripción. E incluso llegando más allá podemos incorporar información relevante para cada página, como el Title, Keywords, Descriptions, etc exclusivos para cada una. Aquí va un ejemplo más elaborado:

'navigation' => array(
'default' => array(
array(
'icon' => 'glyphicon glyphicon-home',  
'label' => 'Inicio',
'route' => 'home',
'title' => 'Página de Inicio'
),
array(
'icon' => 'glyphicon glyphicon-time', 
'label' => 'Segundo',
'route' => 'application',
'controller' => 'index',
'title' => 'Segunda página'
),
array(
'icon' => 'glyphicon glyphicon-inbox', 
'label' => 'Tercero',
'uri' => '#',
'class' => 'dropdown',
'pages' => array(
array(
'label' => 'Anidado 1',
'route' => 'application',
'controller' => 'anidado',
'action'     => 'foo',
'title' => 'Página anidada 1'
),
array(
'label' => 'Anidado 2',
'route' => 'application',
'controller' => 'anidado',
'action'     => 'bar',
'title' => 'Página anidada 2'
),
),
),
),
),

Ahora podemos ver utilizando las posibilidades CSS de Bootstrap que es capaz de mostrar iconos al lado de cada una de las descripciones. Ahora si queremos pasar a la vista simplemente los valores personalizados como el title que hemos definido con anterioridad simplemente deberemos de localizar la página activa y recuperar dicho dato para poder asignarlo a una variable en la Vista y poder utilizarla.

$container = $this->navigation('navigation');
$currentPage = $container->findOneBy('active', true);
 
if ($currentPage)
{
$this->title = $currentPage->get('title');    
}

Conclusión

Como podéis ver la clase Zend Navigation nos ofrece una gran cantidad de posibilidades, no sólo de administrar el menú y sus enlaces de forma sencilla, sino de poder administrar nuevas variables a la vista sin tener que pasar por el controlador sino como un servicio. De este modo podemos personalizar cada una de las páginas se vuelve en una tarea mucho más sencilla y cualquier cambio en el menú no nos hace necesario editar HTML en el menú sino directamente en la configuración, nuevamente desacoplada del contenido.

Como siempre se admiten comentarios, sugerencias y críticas.

Fuentes

Comparte si te ha gustado

Autor:
Última actualización:

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.

De acuerdo con lo dispuesto en el Reglamento (UE) 2016/679 de 27 de abril de 2016, consiento que mis datos sean tratados bajo la responsabilidad de Oscar Gascón Arjol para recibir respuesta a consultas. publicación de comentarios del blog y que las conserve mientras haya un interés mutuo para ello. Me doy por informado que tengo derecho a revocar este consentimiento en cualquier momento y a ejercer los de acceso, rectificación, portabilidad y supresión de mis datos y los de limitación y oposición al tratamiento dirigiéndome por email a me@oscargascon.es. También estoy informado de que puedo reclamar ante la autoridad de control a www.agpd.es.