Install the Navigation module

Edit on GitHub

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 by running vendor/bin/console propel:migrate.

  4. Generate ORM models by running 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
  6. To get the new transfer objects, run vendor/bin/console transfer:generate.

  7. 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).

  8. To make the navigation management UI available in Zed navigation, run the vendor/bin/console application:build-navigation-cache command.

  9. Activate the navigation menu collector by adding the NavigationMenuCollectorStoragePlugin to the storage collector plugin stack. To do that, see the following example:

<?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 (for example, “MAIN_NAVIGATION”).
  • $template: Template path used to render the navigation menu (for example, "@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 an 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.

Code sample:

<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.

Code sample:

<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.

This is also the place where we take the visibility controller parameters into account : valid_from, valid_to, and is_active.

Code sample:

{% 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 %}
{% set today = "now"|date("Ymd") %}

{% if node.navigationNode.isActive and
    (node.navigationNode.validFrom is empty or node.navigationNode.validFrom|date("Ymd") = today) and
    (node.navigationNode.validTo is empty or node.navigationNode.validTo|date("Ymd") >= today)
%}
    {% block nodeContainer %}
        {% if url %}
            {% block url %}{% endblock %}
        {% elseif link %}
            {% block link %}{% endblock %}
        {% elseif externalUrl %}
            {% block externalUrl %}{% endblock %}
        {% else %}
            {% block other %}{% endblock %}
        {% endif %}
    {% endblock %}
{% endif %}