Creating a new Zone in the PlatformUI

This task did actually cost me quiet an extensive amount of time.

The steps in this tutorial will teach you how to add new zones to the current PlatformUI for eZPlatform.

Just to prevent any confusion, I know there is a Tutorial for adding new tabs beneath existing zones likes "Content", "Page" and "Performance". This is explained (pretty well) in the developer-documentation created by eZSystems. What this will teach you how to add a new zone to the system.

This (more or less) will be the result after the tutorial. Never mind the missing "Page"- and "Performance"-zone... that's just CSS.

Backend-Tab

Keep in mind that some places might be a little hack-ish due to me just starting to grasp how to use the code within the PlatformUI.

Bundle-Creation

Create a new bundle and register it within your project. As I'm almost falling asleep at my desk... I will use the bundle I just created. The bundle will add a zone called "Controls" and is called "IntProgPlatformUIBundle".

Creating the main-class for the bundle:

IntProgPlatformUIBundle.php (PHP)
<?php

namespace IntProg\PlatformUIBundle;

use Symfony\Component\HttpKernel\Bundle\Bundle;

/**
 * Class IntProgPlatformUIBundle.
 */
class IntProgPlatformUIBundle extends Bundle
{
}

Adding the bundle to you kernel:

AppKernel.php (PHP)
<?php
/**
 * File containing the AppKernel class.
 */

use eZ\Bundle\EzPublishCoreBundle\Kernel;
use Symfony\Component\Config\Loader\LoaderInterface;


class AppKernel extends Kernel
{
    ...
    /**
     * Returns an array of bundles to registers.
     *
     * @return array An array of bundle instances.
     *
     * @api
     */
    public function registerBundles()
    {
        $bundles = [
            new Symfony\Bundle\FrameworkBundle\FrameworkBundle(),
            new Symfony\Bundle\SecurityBundle\SecurityBundle(),
            new Symfony\Bundle\TwigBundle\TwigBundle(),
            ...
            new IntProg\PlatformUIBundle\IntProgPlatformUIBundle(),
        ];

        return $bundles;
    }
}

Registering plugins

Now we add the plugins to the yui-configuration for the PlatformUI. I will talk about those afterwards.

yui_zone.yml (YAML)
system:
    default:
        yui:
            modules:
                intprog-navigationplugin:
                    requires:
                        - 'ez-pluginregistry'
                        - 'ez-viewservicebaseplugin'
                        - 'ez-navigationitemview'
                    dependencyOf:
                        - 'ez-navigationhubviewservice'
                    path: "%int_prog_platformui.public_dir%/js/views/services/plugins/intprog-navigationplugin.js"

                intprog-zone-navigationhubview:
                    requires:
                        - 'ez-pluginregistry'
                        - 'ez-viewservicebaseplugin'
                        - 'ez-navigationitemview'
                    dependencyOf:
                        - 'ez-navigationhubview'
                    path: "%int_prog_platformui.public_dir%/js/views/services/plugins/intprog-zone-navigationhubview.js"

The name of the file is irrelevant so far. If you adjust it, just keep in mind to change the include accordingly.

Notice the "dependencyOf"- and "requires"-sections. In those you can define which script requires another to be ran before or after. One is telling "ez-navigationhubviewservice" to be dependent on it while the other uses "ez-navigationhubview". Those to js-files will extend the menu.

As far as i know, both of them are required.

Importing the new config

The JavaScript-Files aren't there yet. We will still import the configuration for now.

Create the extension-file for the bundle and add the following content.

IntProgPlatformUIExtension.php (PHP)
<?php

namespace IntProg\PlatformUIBundle\DependencyInjection;

use Symfony\Component\Config\Resource\FileResource;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Extension\PrependExtensionInterface;
use Symfony\Component\HttpKernel\DependencyInjection\Extension;
use Symfony\Component\Yaml\Yaml;

/**
 * Class IntProgPlatformUIExtension.
 */
class IntProgPlatformUIExtension extends Extension implements PrependExtensionInterface
{

    /**
     * Loads a specific configuration.
     *
     * @param array            $configs   An array of configuration values
     * @param ContainerBuilder $container A ContainerBuilder instance
     *
     * @throws \InvalidArgumentException When provided tag is not defined in this extension
     */
    public function load(array $configs, ContainerBuilder $container)
    {
    }

    /**
     * Allow an extension to prepend the extension configurations.
     *
     * @param ContainerBuilder $container
     */
    public function prepend(ContainerBuilder $container)
    {
        $this->prependYui($container);
    }

    private function prependYui(ContainerBuilder $container)
    {
        $container->setParameter('int_prog_platformui.public_dir', 'bundles/intprogplatformui');

        $yuiConfigFile = __DIR__ . '/../Resources/config/platformui/yui_zone.yml';
        $config        = Yaml::parse(file_get_contents($yuiConfigFile));
        $container->prependExtensionConfig('ez_platformui', $config);
        $container->addResource(new FileResource($yuiConfigFile));
    }

}

This will load the configuration into the kernel so the PlatformUIBundle knows about those configurations. Keep in mind that you are required to use the "PrependExtensionInterface" to import this configuration-file.

 -> You can also place the value of the config-files into the regular "config.yml" if you add it beneath the "ez_platformui"-section.

Creating the JS-Files

Now comes the hard part which did cost me some hours to put my head around.

Create the following files beneath the path "src/IntProg/PlatformUIBundle/Resources/public/js/views/services/plugins".

intprog-zone-navigationhubview.js (JavaScript)
YUI.add('intprog-zone-navigationhubview', function (Y) {
    Y.namespace('IntProg.Plugin');

    /**
     * @namespace IntProg.Plugin
     * @class NavigationHubPlugin
     * @constructor
     * @extends Plugin.ViewServiceBase
     */
    Y.IntProg.Plugin.NavigationHubPlugin = Y.Base.create('intprogNavigationHubPlugin', Y.eZ.Plugin.ViewServiceBase, [], {
        initializer: function () {
            var service = this.get('host');

            service.set('controlsNavigationItems', []);
            service.get('controlsNavigationItems').push(new Y.eZ.NavigationItemView({
                identifier: 'intprog-nav-main',
                title: 'Main',
                route: {
                    name: "IntProgMain",
                    path: "/intprog/controls",
                    view: "intprogMainView",
                    service: Y.IntProg.MainViewService,
                    sideViews: {'navigationHub': true, 'discoveryBar': false},
                    callbacks: ['open', 'checkUser', 'handleSideViews', 'handleMainView'],
                }
            }));

            var zones = service.get('zones');
            zones.controls = "Controls";
        }
    }, {
        NS: 'intprogNavigation'
    });

    Y.eZ.PluginRegistry.registerPlugin(
        Y.IntProg.Plugin.NavigationHubPlugin, ['navigationHubView']
    );
});

This file will add the zone to the hub.

intprog-navigationplugin.js (JavaScript)
YUI.add('intprog-navigationplugin', function (Y) {
    Y.namespace('IntProg.Plugin');

    /**
     * @namespace IntProg.Plugin
     * @class NavigationPlugin
     * @constructor
     * @extends Plugin.ViewServiceBase
     */
    Y.IntProg.Plugin.NavigationPlugin = Y.Base.create('intprogNavigationPlugin', Y.eZ.Plugin.ViewServiceBase, [], {
        initializer: function () {
            var service = this.get('host');
            service.set('controlsNavigationItems', []);
        }
    }, {
        NS: 'intprogNavigation'
    });

    Y.eZ.PluginRegistry.registerPlugin(
        Y.IntProg.Plugin.NavigationPlugin, ['navigationHubViewService']
    );
});

This file is to get the UI to know the new zone. It will just create an empty list of navigation-items (just an empty array). The list will later be filled up with the tabs added to the previous plugin (intprog-zone-navigationhubview.js).

After this you are almost done. You can now add new tabs to this zone using "controls" instead of "platform". For example like this (adjusted example from eZSystems):

ezconf-navigationplugin.js (from eZSystems) (JavaScript)
YUI.add('ezconf-navigationplugin', function (Y) {
    Y.namespace('eZConf.Plugin');
 
    Y.eZConf.Plugin.NavigationPlugin = Y.Base.create('ezconfNavigationPlugin', Y.eZ.Plugin.ViewServiceBase, [], {
        initializer: function () {
            var service = this.get('host');
 
            console.log("Hey, I'm a plugin for NavigationHubViewService");
            console.log("And I'm plugged in ", service);
 
            console.log("Let's add the navigation item in the Content zone");
            service.addNavigationItem({
                Constructor: Y.eZ.NavigationItemView,
                config: {
                    title: "List contents",
                    identifier: "ezconf-list-contents",
                    route: {
                        name: "eZConfList" // same route name of the one added in the app plugin
                    }
                }
            }, 'controls'); // identifier of the zone called "Content" in the UI
        },
    }, {
        NS: 'ezconfNavigation'
    });
 
    Y.eZ.PluginRegistry.registerPlugin(
        Y.eZConf.Plugin.NavigationPlugin, ['navigationHubViewService']
    );
});

Now isn't that awesome? Thought so... but wait... there is a fifth step?

Cleanup

Tab order

Well would you look at that. It added the new zone after the "Admin Panel"-stuff... that aint right!

Backend-Tab Ugly #1

I actually haven't found a better solution that hacking my way through it. So we will have to get comfy with it for now. What you do is quiet simple though.

Note that this (for some reason) does not change the order of the items when viewing the UI in MS-Edge.

intprog-zone-navigationhubview.js (JavaScript)
                    ...
                }
            }));

            var zones = service.get('zones');
            zones.controls = "Controls";
            
            // you want to add this...
            var admin = zones.admin;
            delete zones.admin;
            zones.admin = admin;
            // to fix the order.
        }
    }, {
        NS: 'intprogNavigation'
    });

Just backup the admin-zone, remove it from the list/object you you just added new new and shiny navigation-item to. And add it back in again. Just like that... It's not "nice" but it works.

Backend-Tab Fixed #1

 

Styling

Now,... if you add items to this zone, you will immediately notice that it looks like crap. Some padding stuff which comes from unordered lists created by the UI.

This is quiet simple to fix though. Just load a css-file for the elements and you are good to go.

Create the CSS file to load into the UI:

menu.css (CSS)
/* remove padding and margin from items in custom zone */
.ez-view-navigationhubview .ez-navigation-controls {
    list-style-type: none;
    margin: 0;
    padding: 0;
}

Create a config-file for the PlatformUI to load and register the css-file.

css.yml (YAML)
system:
    default:
        css:
            files:
                - "%int_prog_platformui.css_dir%/views/menu.css"

Register the css-configuration.

IntProgPlatformUIExtension.php (PHP)
...
        // make sure Assetic can handle the assets (mainly the CSS files)
        $container->prependExtensionConfig('assetic', ['bundles' => ['IntProgPlatformUIBundle']]);

        // prepend the yui.yml and the css.yml from EzSystemsExtendingPlatformUIConferenceBundle
        // of course depending on your needs you can remove the handling of yui.yml or css.yml
        $this->prependYui($container);
        $this->prependCss($container); // this line will be added.
    }
...
    private function prependCss(ContainerBuilder $container) // add this method
    {
        $container->setParameter('int_prog_platformui.css_dir', 'bundles/intprogplatformui/css');

        $cssConfigFile = __DIR__ . '/../Resources/config/platformui/css.yml';
        $config        = Yaml::parse(file_get_contents($cssConfigFile));
        $container->prependExtensionConfig('ez_platformui', $config);
        $container->addResource(new FileResource($cssConfigFile));
    }
...

And we are really done! (btw: that's how I've hidden the two extra tabs the PlatformUI throws at you).