Since Alexander showed you how to create a new module for Icinga Web 2 pretty easily (German) some time ago, I’ll continue this today by describing how to add some basic navigation elements to the Hello World-Module.

So we’re currently here:
module-final

Subsidiary Menu Entries

To split up our main menu entry on the left hand into additional topics we need to take another look at the configuration.php file:

<?php

$section = $this->menuSection('Hello World', array(
    'url' => 'helloworld'
));

We’ll split it up into two entries in this example:

<?php

$section = $this->menuSection('Hello', array(
    'icon'  => 'globe'
));
$section->add('World', array(
    'url'       => 'helloworld',
    'priority'  => 100
));
$section->add('Universe', array(
    'url'       => 'helloworld/universe',
    'priority'  => 101
));

Who’s taking a closer look will probably notice that our main menu entry doesn’t point to a URL anymore. It’s just a simple entry now whose main purpose is to group other entries together and to expand them once the user clicks on it. But on the other hand it has got a neat icon so that it suits the surrounding entries properly. The two new entries are introducing a new property which is being used to adjust the order of an entry as by default they are in alphabetical order.

The menu should now look as follows:
Subsidiary Menu Entries

Tabs

In case another menu entry is not applicable it’s also possible to show some tabs in our views. For convenience’ sake or because I can’t think of an alternative right now we’ll add both menu entries we’ve created earlier again, but this time as tabs. Let’s take a look a the IndexController:

<?php

use Icinga\Web\Controller\ModuleActionController;

class HelloWorld_IndexController extends ModuleActionController
{
    public function indexAction()
    {
        $this->getTabs()->activate('world');
    }

    public function getTabs()
    {
        $tabs = parent::getTabs();
        $tabs->add(
            'world',
            array(
                'title' => 'World',
                'url'   => 'helloworld'
            )
        );
        $tabs->add(
            'universe',
            array(
                'title' => 'Universe',
                'url'   => 'helloworld/universe'
            )
        );

        return $tabs;
    }
}

To actually show the tabs in our view we’ll need to extend the view script for the indexAction: (index.phtml)

<?php if (! $this->compact): ?>
<div class="controls">
  <?= $tabs; ?>
</div>
<?php endif ?>
<div class="content">
  <h1>Hello World</h1>
</div>

You should consider the new template as a convention. No one forces you to use it but doing it exactly this way you’ll make sure that your view is looking properly when being viewed in full-screen mode or on the dashboard.

Due to the fact that the menu entry and tab for the universe is currently leading into the void.. we need to create a controller for this route. To accomplish this we’ll create a new file called UniverseController.php and the respective view script:

<?php

use Icinga\Web\Controller\ModuleActionController;

class HelloWorld_UniverseController extends ModuleActionController
{
    public function indexAction()
    {
        $this->getTabs()->activate('universe');
    }

    public function getTabs()
    {
        $tabs = parent::getTabs();
        $tabs->add(
            'world',
            array(
                'title' => 'World',
                'url'   => 'helloworld'
            )
        );
        $tabs->add(
            'universe',
            array(
                'title' => 'Universe',
                'url'   => 'helloworld/universe'
            )
        );

        return $tabs;
    }
}
<?php if (! $this->compact): ?>
<div class="controls">
  <?= $tabs; ?>
</div>
<?php endif ?>
<div class="content">
  <h1>Hello Universe</h1>
</div>

The file structure should now look as follows:
helloworld-neue-dateien

Columns

Who’s using the monitoring module has probably noticed that not all URLs cause a complete change of context, but are causing Icinga Web 2 to open a new column on your screen. Now to prevent our new route, which we’re going to define, from changing the context we’ll need to change this explicitly.

UniverseController.php

// *Snip*

    public function galaxyAction()
    {
        $galaxy = $this->params->getRequired('galaxy');
        $this->view->galaxy = $galaxy;
    }

    public function getTabs()
// *Snip*

views/scripts/universe/galaxy.phtml

<?php if (! $this->compact): ?>
<div class="controls">
  <?= $tabs->showOnlyCloseButton(); ?>
</div>
<?php endif ?>
<div class="content">
  <h1>Hello <?= $galaxy; ?></h1>
</div>

views/scripts/universe/index.phtml

<!-- *Snip* -->
  <h1>Hello Universe</h1>
  <?= $this->qlink('Greet the Milky Way', 'helloworld/universe/galaxy', array('galaxy' => 'Milky Way')); ?>
  <br>
  <?= $this->qlink('Greet Andromeda', 'helloworld/universe/galaxy', array('galaxy' => 'Andromeda')); ?>
</div>

To greet a galaxy in a new column we’ll have to apply the following change in the view script above:

views/scripts/universe/index.phtml

<!-- *Snip* -->
<div class="content" data-base-target="_next">
<!-- *Snip* -->

This data- attribute accepts the following values:

Value Description
_main Use the first column and close all others (Default)
_self Use the current column, all others are kept as is
_next Use a new column at the right and close the first left column if there’s not enough space available
<id> Use the container with the id <id>. This can be either any HTML-container or one of the predefined column ids: col1, col2

Usage of this attribute is not limited to a specific sub-set of HTML elements and it can be nested freely. This means that only the most significant attribute is being considered when a URL is processed by Icinga Web 2. (e.g. <div data-base-target=”_next”><a data-base-target=”_self” …></a></div> _self is more significant than _next)

We’ve now arrived at the end of this guide. I hope it is informative enough but if there are still questions I’ll recommend you to ask us (me) on the forums. Thanks, see you next time!