diff --git a/library/Icinga/Web/Navigation/NavigationItem.php b/library/Icinga/Web/Navigation/NavigationItem.php
index 2c70bf37e..86aa39467 100644
--- a/library/Icinga/Web/Navigation/NavigationItem.php
+++ b/library/Icinga/Web/Navigation/NavigationItem.php
@@ -9,7 +9,7 @@ use IteratorAggregate;
use Icinga\Application\Icinga;
use Icinga\Exception\IcingaException;
use Icinga\Exception\ProgrammingError;
-use Icinga\Web\View;
+use Icinga\Web\Navigation\Renderer\NavigationItemRenderer;
use Icinga\Web\Url;
/**
@@ -24,6 +24,11 @@ class NavigationItem implements IteratorAggregate
*/
const LINK_ALTERNATIVE = 'span';
+ /**
+ * The class namespace where to locate navigation type renderer classes
+ */
+ const RENDERER_NS = 'Web\\Navigation\\Renderer';
+
/**
* Whether this item is active
*
@@ -97,11 +102,11 @@ class NavigationItem implements IteratorAggregate
protected $urlParameters;
/**
- * View
+ * This item's renderer
*
- * @var View
+ * @var NavigationItemRenderer
*/
- protected $view;
+ protected $renderer;
/**
* Create a new NavigationItem
@@ -500,33 +505,6 @@ class NavigationItem implements IteratorAggregate
return $this;
}
- /**
- * Return the view
- *
- * @return View
- */
- public function view()
- {
- if ($this->view === null) {
- $this->setView(Icinga::app()->getViewRenderer()->view);
- }
-
- return $this->view;
- }
-
- /**
- * Set the view
- *
- * @param View $view
- *
- * @return $this
- */
- public function setView(View $view)
- {
- $this->view = $view;
- return $this;
- }
-
/**
* Set this item's properties
*
@@ -600,6 +578,79 @@ class NavigationItem implements IteratorAggregate
return $this->getUrl() !== $item->getUrl();
}
+ /**
+ * Create and return the given renderer
+ *
+ * @param string $name
+ *
+ * @return NavigationItemRenderer
+ */
+ protected function createRenderer($name)
+ {
+ $renderer = null;
+ foreach (Icinga::app()->getModuleManager()->getLoadedModules() as $module) {
+ $classPath = 'Icinga\\Module\\' . $module->getName() . '\\' . static::RENDERER_NS . '\\' . $name;
+ if (class_exists($classPath)) {
+ $renderer = new $classPath();
+ break;
+ }
+ }
+
+ if ($renderer === null) {
+ $classPath = 'Icinga\\' . static::RENDERER_NS . '\\' . $name;
+ if (class_exists($classPath)) {
+ $renderer = new $classPath();
+ }
+ }
+
+ if ($renderer === null) {
+ throw new ProgrammingError(
+ 'Cannot find renderer "%s" for navigation item "%s"',
+ $name,
+ $this->getName()
+ );
+ } elseif (! $renderer instanceof NavigationItemRenderer) {
+ throw new ProgrammingError('Class %s must inherit from NavigationItemRenderer', $classPath);
+ }
+
+ return $renderer;
+ }
+
+ /**
+ * Set this item's renderer
+ *
+ * @param string|NavigationItemRenderer $renderer
+ *
+ * @return $this
+ *
+ * @throws InvalidArgumentException If the $renderer argument is neither a string nor a NavigationItemRenderer
+ */
+ public function setRenderer($renderer)
+ {
+ if (is_string($renderer)) {
+ $renderer = $this->createRenderer($renderer);
+ } elseif (! $renderer instanceof NavigationItemRenderer) {
+ throw new InvalidArgumentException('Argument $renderer must be of type string or NavigationItemRenderer');
+ }
+
+ $this->renderer = $renderer;
+ return $this;
+ }
+
+ /**
+ * Return this item's renderer
+ *
+ * @return NavigationItemRenderer
+ */
+ public function getRenderer()
+ {
+ if ($this->renderer === null) {
+ $this->setRenderer('NavigationItemRenderer');
+ }
+
+ return $this->renderer;
+ }
+
/**
* Return this item rendered to HTML
*
@@ -607,28 +658,7 @@ class NavigationItem implements IteratorAggregate
*/
public function render()
{
- $label = $this->view()->escape($this->getLabel());
- if (($icon = $this->getIcon()) !== null) {
- $label = $this->view()->icon($icon) . $label;
- }
-
- if (($url = $this->getUrl()) !== null) {
- $content = sprintf(
- '%s',
- $this->view()->propertiesToString($this->getAttributes()),
- $this->view()->url($url, $this->getUrlParameters()),
- $label
- );
- } else {
- $content = sprintf(
- '<%1$s%2$s>%3$s%1$s>',
- static::LINK_ALTERNATIVE,
- $this->view()->propertiesToString($this->getAttributes()),
- $label
- );
- }
-
- return $content;
+ $this->getRenderer()->render($this);
}
/**
diff --git a/library/Icinga/Web/Navigation/Renderer/NavigationItemRenderer.php b/library/Icinga/Web/Navigation/Renderer/NavigationItemRenderer.php
new file mode 100644
index 000000000..3a9d377e2
--- /dev/null
+++ b/library/Icinga/Web/Navigation/Renderer/NavigationItemRenderer.php
@@ -0,0 +1,81 @@
+view === null) {
+ $this->setView(Icinga::app()->getViewRenderer()->view);
+ }
+
+ return $this->view;
+ }
+
+ /**
+ * Set the view
+ *
+ * @param View $view
+ *
+ * @return $this
+ */
+ public function setView(View $view)
+ {
+ $this->view = $view;
+ return $this;
+ }
+
+ /**
+ * Render the given navigation item as HTML anchor
+ *
+ * @param NavigationItem $item
+ *
+ * @return string
+ */
+ public function render(NavigationItem $item)
+ {
+ $label = $this->view()->escape($item->getLabel());
+ if (($icon = $item->getIcon()) !== null) {
+ $label = $this->view()->icon($icon) . $label;
+ }
+
+ if (($url = $item->getUrl()) !== null) {
+ $content = sprintf(
+ '%s',
+ $this->view()->propertiesToString($item->getAttributes()),
+ $this->view()->url($url, $item->getUrlParameters()),
+ $label
+ );
+ } else {
+ $content = sprintf(
+ '<%1$s%2$s>%3$s%1$s>',
+ $item::LINK_ALTERNATIVE,
+ $this->view()->propertiesToString($item->getAttributes()),
+ $label
+ );
+ }
+
+ return $content;
+ }
+}