Navigation Module Version 1.*

Overview

The Navigation module manages multiple navigation menus that can be displayed on the frontend (Yves). Every navigation section can contain its own nested structure of navigation nodes. Navigation nodes have types that help define what kind of link they represent.

The following node types are available:

  • Label: These nodes do not link to any specific URL, they are used for grouping other nodes.
  • Category: Nodes can be assigned to category node URLs.
  • CMS Page: Nodes can be assigned to CMS page URLs.
  • Link: These nodes link to internal pages in Yves, i.e. login, registration, etc.
  • External URL: These nodes link to external URLs (typically tabs opened in a new browser).

You can control and adjust Navigation node appearance and add icons by assigning custom CSS classes to them.

This feature is shipped with three modules:

  • Navigation module provides database structure and a public API to manage what’s in the database. It also provides a small toolkit for rendering navigation menus in the frontend.
  • NavigationGui module provides a Zed UI to manage navigation menus.
  • NavigationCollector module provides full collector logic for exporting navigation menus to the KV storage (Redis).

Feature Integration

Prerequisites

To prepare your project to work with Navigation:

  1. Require the Navigation modules in your composer.json.
  2. Install the new database tables by running vendor/bin/console propel:diff. Propel will generate a migration file with the changes.
  3. Apply the database changes: run vendor/bin/console propel:migrate.
  4. Generate ORM models: run vendor/bin/console propel:model:build.
  5. After running this command you’ll find some new classes in your project under `\Orm\Zed\Navigation\Persistence` namespace.

It’s important to make sure that they extend the base classes from the Spryker core, e.g.:

  • \Orm\Zed\Navigation\Persistence\SpyNavigation extends \Spryker\Zed\Navigation\Persistence\Propel\AbstractSpyNavigation
  • \Orm\Zed\Navigation\Persistence\SpyNavigationNode extends \Spryker\Zed\Navigation\Persistence\Propel\AbstractSpyNavigationNode
  • \Orm\Zed\Navigation\Persistence\SpyNavigationNodeLocalizedAttributes extends \Spryker\Zed\Navigation\Persistence\Propel\AbstractSpyNavigationNodeLocalizedAttributes
  • \Orm\Zed\Navigation\Persistence\SpyNavigationQuery extends \Spryker\Zed\Navigation\Persistence\Propel\AbstractSpyNavigationQuery
  • \Orm\Zed\Navigation\Persistence\SpyNavigationNodeQuery extends \Spryker\Zed\Navigation\Persistence\Propel\AbstractSpyNavigationNodeQuery
  • \Orm\Zed\Navigation\Persistence\SpyNavigationNodeLocalizedAttributesQuery extends \Spryker\Zed\Navigation\Persistence\Propel\AbstractSpyNavigationNodeLocalizedAttributesQuery
  1. Run vendor/bin/console transfer:generate to get the new transfer objects.
  2. Make sure that the new Zed UI assets are also prepared for use by running the npm run zed command (or antelope build zed for older versions).
  3. To make the navigation management UI available in zed navigation, run vendor/bin/console application:build-navigation-cache command.
  4. Activate the navigation menu collector by adding the NavigationMenuCollectorStoragePlugin to the storage collector plugin stack, see example below:
<?php

namespace Pyz\Zed\Collector;

use Spryker\Shared\Navigation\NavigationConfig;
use Spryker\Zed\Kernel\Container;
use Spryker\Zed\NavigationCollector\Communication\Plugin\NavigationMenuCollectorStoragePlugin;
// ...

class CollectorDependencyProvider extends SprykerCollectorDependencyProvider
{
    /**
     * @param \Spryker\Zed\Kernel\Container $container
     *
     * @return \Spryker\Zed\Kernel\Container
     */
    public function provideBusinessLayerDependencies(Container $container)
    {
        // ...
       
        $container[self::STORAGE_PLUGINS] = function (Container $container) {
            return [
                // ...
                NavigationConfig::RESOURCE_TYPE_NAVIGATION_MENU => new NavigationMenuCollectorStoragePlugin(),
            ];
        };
        
        // ...
    }
}

Data Setup

You should now be able to manage navigation menus from Zed UI, and the collectors should also be able to export the navigation menus to the KV storage. This is a good time to implement an installer in your project to install a selection of frequently used navigation menus.

Usage in Yves

The KV storage should by now have some navigation menus we can display in our frontend.

The Navigation module ships with a twig extension that provides the spyNavigation() twig function which renders a navigation menu.

spyNavigation() accepts two parameters:

  • $navigationKey: Reference of a navigation menu by its key field (i.e. "MAIN_NAVIGATION").
  • $template: Template path used to render the navigation menu (i.e. "@application/layout/navigation/main.twig").

To enable the navigation twig function register \Spryker\Yves\Navigation\Plugin\Provider\NavigationTwigServiceProvider in your application’s bootstrap.

<?php

namespace Pyz\Yves\Application;

use Spryker\Yves\Navigation\Plugin\Provider\NavigationTwigServiceProvider;
// ...

class YvesBootstrap
{
    /**
     * @return void
     */
    protected function registerServiceProviders()
    {
        // ...
        $this->application->register(new NavigationTwigServiceProvider());
    }
}

Example, of rendering navigation in a yves twig template:

{{ spyNavigation('MAIN_NAVIGATION', '@application/layout/navigation/main.twig') }}

Rendering Navigation Templates

The templates used to render a navigation menu, use the navigationTree template variable to traverse the navigation tree. The variable contains an instance of \Generated\Shared\Transfer\NavigationTreeTransfer with only one localized attribute per node for the current locale.

The following code examples show the Demoshop implementation of how to render MAIN_NAVIGATION which is a multi-level navigation menu. For styling we used the Menu and Dropdown components from Foundation framework.

In Pyz/Yves/Application/Theme/default/layout/navigation/main.twig we traverse the root navigation nodes of the navigation tree and for each root node we render their children nodes as well.

<div class="callout show-for-large">
    <div class="row">
        <div class="large-12 columns">
            <ul class="menu">
                {% for node in navigationTree.nodes %}
                    {% embed '@Application/layout/navigation/_partials/base-node.twig' %}
                        {% block url %}
                            <li {% if node.children|length %}data-toggle="navigation-node-{{ node.navigationNode.idNavigationNode }}-children"{% endif %} class="{{ class }}">
                                <a href="{{ url }}">{{ title }}</a>
                            </li>
                        {% endblock %}

                        {% block link %}
                            <li {% if node.children|length %}data-toggle="navigation-node-{{ node.navigationNode.idNavigationNode }}-children"{% endif %} class="{{ class }}">
                                <a href="{{ link  }}">{{ title }}</a>
                            </li>
                        {% endblock %}

                        {% block externalUrl %}
                            <li {% if node.children|length %}data-toggle="navigation-node-{{ node.navigationNode.idNavigationNode }}-children"{% endif %} class="{{ class }}">
                                <a href="{{ externalUrl }}" target="_blank">{{ title }}</a>
                            </li>
                        {% endblock %}

                        {% block other %}
                            <li {% if node.children|length %}data-toggle="navigation-node-{{ node.navigationNode.idNavigationNode }}-children"{% endif %} class="menu-text {{ class }}">
                                {{ title }}
                            </li>
                        {% endblock %}
                    {% endembed %}
                {% endfor %}
            </ul>

            {% for node in navigationTree.nodes %}
                {% if node.navigationNode.isActive %}
                    {% if node.children|length %}
                        <div class="dropdown-pane" id="navigation-node-{{ node.navigationNode.idNavigationNode }}-children" data-dropdown data-hover="true" data-hover-pane="true">
                            {% include '@Application/layout/navigation/_partials/nodes.twig' with {nodes: node.children} %}
                        </div>
                    {% endif %}
                {% endif %}
            {% endfor %}
        </div>
    </div>
</div>

The children nodes are rendered recursively by Pyz/Yves/Application/Theme/default/layout/navigation/_partials/nodes.twig.

<ul class="vertical menu {% if nested|default(false) %}nested{% endif %}">
    {% for node in nodes %}
        {% embed '@Application/layout/navigation/_partials/base-node.twig' %}
            {% block nodeContainer %}
                <li id="navigation-node-{{ node.navigationNode.idNavigationNode }}" data-id-navigation-node="{{ node.navigationNode.idNavigationNode }}" class="{{ class }}">
                    {{ parent() }}

                    {% if node.children|length %}
                        {% include '@Application/layout/navigation/_partials/nodes.twig' with {nodes: node.children, nested:true} %}
                    {% endif %}
                </li>
            {% endblock %}

            {% block url %}
                <a href="{{ url }}">{{ title }}</a>
            {% endblock %}

            {% block link %}
                <a href="{{ link }}">{{ title }}</a>
            {% endblock %}

            {% block externalUrl %}
                <a href="{{ externalUrl }}" target="_blank">{{ title }}</a>
            {% endblock %}

            {% block other %}
                <span class="menu-text">{{ title }}</span>
            {% endblock %}
        {% endembed %}
    {% endfor %}
</ul>

To prevent code duplication we implemented the Pyz/Yves/Application/Theme/default/layout/navigation/_partials/base-node.twig template which we use to render a node by embedding it in the templates above.

{% set class = node.navigationNode.navigationNodeLocalizedAttributes[0].cssClass %}
{% set url = node.navigationNode.navigationNodeLocalizedAttributes[0].url %}
{% set externalUrl = node.navigationNode.navigationNodeLocalizedAttributes[0].externalUrl %}
{% set link = node.navigationNode.navigationNodeLocalizedAttributes[0].link %}
{% set title = node.navigationNode.navigationNodeLocalizedAttributes[0].title %}

{% if node.navigationNode.isActive %}
    {% block nodeContainer %}
        {% if url %}
            {% block url %}{% endblock %}
        {% elseif link %}
            {% block link %}{% endblock %}
        {% elseif externalUrl %}
            {% block externalUrl %}{% endblock %}
        {% else %}
            {% block other %}{% endblock %}
        {% endif %}
    {% endblock %}
{% endif %}

Under the Hood

Database Schema

The Navigation module provides the spy_navigation table that stores navigation menus. They have a name field which is only used for backend display and they also have a key field used to reference the navigation menus from yves.

Every navigation entity contains some nodes stored in the spy_navigation_node table. The structure of the navigation tree depends on the fk_parent_navigation_node and the position fields which define if a node has a parent on its level, in what position they are ordered. Each navigation node has attributes that can be different per displayed locale. This information is stored in the spy_navigation_node_localized_attributes table.

Navigation Database Schema

 

See also:

 

Last review date: Sep. 21st, 2017