diff --git a/library/Icinga/Web/Navigation/DropdownItem.php b/library/Icinga/Web/Navigation/DropdownItem.php new file mode 100644 index 000000000..98f153500 --- /dev/null +++ b/library/Icinga/Web/Navigation/DropdownItem.php @@ -0,0 +1,21 @@ +children->setLayout(Navigation::LAYOUT_DROPDOWN); + } +} + diff --git a/library/Icinga/Web/Navigation/Navigation.php b/library/Icinga/Web/Navigation/Navigation.php new file mode 100644 index 000000000..3ec0fd7de --- /dev/null +++ b/library/Icinga/Web/Navigation/Navigation.php @@ -0,0 +1,235 @@ + + * setLayout(Navigation::LAYOUT_TABS); + * $home = new NavigationItem(); + * $home + * ->setIcon('home') + * ->setLabel('Home'); + * ->setUrl('/home'); + * $logout = new NavigationItem(); + * $logout + * ->setIcon('logout') + * ->setLabel('Logout') + * ->setUrl('/logout'); + * $dropdown = new DropdownItem(); + * $dropdown + * ->setIcon('user') + * ->setLabel('Preferences'); + * $preferences = new NavigationItem(); + * $preferences + * ->setIcon('preferences'); + * ->setLabel('preferences') + * ->setUrl('/preferences'); + * $dropdown->addChild($preferences); + * $navigation + * ->addItem($home) + * ->addItem($logout); + * ->addItem($dropdown); + * echo $navigation + * ->getRenderer() + * ->setCssClass('example-nav') + * ->render(); + */ +class Navigation implements ArrayAccess, Countable, IteratorAggregate +{ + /** + * Flag for dropdown layout + * + * @var int + */ + const LAYOUT_DROPDOWN = 1; + + /** + * Flag for tabs layout + * + * @var int + */ + const LAYOUT_TABS = 2; + + /** + * Navigation items + * + * @var NavigationItem[] + */ + protected $items = array(); + + /** + * Navigation layout + * + * @var int + */ + protected $layout; + + /** + * {@inheritdoc} + */ + public function offsetExists($offset) + { + return isset($this->items[$offset]); + } + + /** + * {@inheritdoc} + */ + public function offsetGet($offset) + { + return isset($this->items[$offset]) ? $this->items[$offset] : null; + } + + /** + * {@inheritdoc} + */ + public function offsetSet($offset, $value) + { + $this->items[$offset] = $value; + } + + /** + * {@inheritdoc} + */ + public function offsetUnset($offset) + { + unset($this->items[$offset]); + } + + /** + * {@inheritdoc} + */ + public function count() + { + return count($this->items); + } + + /** + * {@inheritdoc} + */ + public function getIterator() + { + return new ArrayIterator($this->items); + } + + /** + * Ad a navigation item + * + * @param NavigationItem|array $item The item to append + * + * @return $this + * @throws InvalidArgumentException If the item argument is invalid + */ + public function addItem(NavigationItem $item) + { + if (! $item instanceof NavigationItem) { + if (! is_array($item)) { + throw new InvalidArgumentException( + 'Argument item must be either an array or an instance of NavigationItem' + ); + } + $item = new NavigationItem($item); + } + $this->items[] = $item; + return $this; + } + + /** + * Get the item with the given ID + * + * @param mixed $id + * @param mixed $default + * + * @return NavigationItem|mixed + */ + public function getItem($id, $default = null) + { + if (isset($this->items[$id])) { + return $this->items[$id]; + } + return $default; + } + + /** + * Get the items + * + * @return array + */ + public function getItems() + { + return $this->items; + } + + /** + * Get whether the navigation has items + * + * @return bool + */ + public function hasItems() + { + return ! empty($this->items); + } + + /** + * Get whether the navigation is empty + * + * @return bool + */ + public function isEmpty() + { + return empty($this->items); + } + + /** + * Get the layout + * + * @return int + */ + public function getLayout() + { + return $this->layout; + } + + /** + * Set the layout + * + * @param int $layout + * + * @return $this + */ + public function setLayout($layout) + { + $this->layout = (int) $layout; + return $this; + } + + /** + * Get the navigation renderer + * + * @return RecursiveNavigationRenderer + */ + public function getRenderer() + { + return new RecursiveNavigationRenderer($this); + } +} + diff --git a/library/Icinga/Web/Navigation/NavigationItem.php b/library/Icinga/Web/Navigation/NavigationItem.php new file mode 100644 index 000000000..4bc0936cc --- /dev/null +++ b/library/Icinga/Web/Navigation/NavigationItem.php @@ -0,0 +1,483 @@ +setProperties($properties); + } + $this->children = new Navigation(); + $this->init(); + } + + /** + * Initialize the navigation item + */ + public function init() + { + + } + + /** + * {@inheritdoc} + */ + public function count() + { + return $this->children->count(); + } + + /** + * {@inheritdoc} + */ + public function getIterator() + { + return $this->children; + } + + /** + * Get whether the item is active + * + * @return bool + */ + public function getActive() + { + return $this->active; + } + + /** + * Set whether the item is active + * + * Bubbles active state. + * + * @param bool $active + * + * @return $this + */ + public function setActive($active = true) + { + $this->active = (bool) $active; + $parent = $this; + while (($parent = $parent->parent) !== null) { + $parent->setActive($active); + } + return $this; + } + + /** + * Get an attribute's value of the item's element + * + * @param string $name Name of the attribute + * @param mixed $default Default value + * + * @return mixed + */ + public function getAttribute($name, $default = null) + { + $name = (string) $name; + if (array_key_exists($name, $this->attributes)) { + return $this->attributes[$name]; + } + return $default; + } + + /** + * Set an attribute of the item's element + * + * @param string $name Name of the attribute + * @param mixed $value Value of the attribute + * + * @return $this + */ + public function setAttribute($name, $value) + { + $name = (string) $name; + $this->attributes[$name] = $value; + return $this; + } + + /** + * Get the item's attributes + * + * @return array + */ + public function getAttributes() + { + return $this->attributes; + } + + /** + * Set the item's attributes + * + * @param array $attributes + * + * @return $this + */ + public function setAttributes(array $attributes) + { + foreach ($attributes as $name => $value) { + $this->setAttribute($name, $value); + } + return $this; + } + + /** + * Add a child item to this item + * + * Bubbles active state. + * + * @param NavigationItem|array $child The child to add + * + * @return $this + * @throws InvalidArgumentException If the child argument is invalid + */ + public function addChild($child) + { + if (! $child instanceof NavigationItem) { + if (! is_array($child)) { + throw new InvalidArgumentException( + 'Argument child must be either an array or an instance of NavigationItem' + ); + } + $child = new NavigationItem($child); + } + $child->parent = $this; + $this->children->addItem($child); + if ($child->getActive()) { + $this->setActive(); + } + return $this; + } + + /** + * Get the item's children + * + * @return Navigation + */ + public function getChildren() + { + return $this->children; + } + + /** + * Get whether the item has children + * + * @return bool + */ + public function hasChildren() + { + return ! $this->children->isEmpty(); + } + + /** + * Set children + * + * @param Navigation $children + * + * @return $this + */ + public function setChildren(Navigation $children) + { + $this->children = $children; + return $this; + } + + /** + * Get the icon + * + * @return string|null + */ + public function getIcon() + { + return $this->icon; + } + + /** + * Set the icon + * + * @param string $icon + * + * @return $this + */ + public function setIcon($icon) + { + $this->icon = (string) $icon; + return $this; + } + + /** + * Get the item's ID + * + * @return mixed + */ + public function getId() + { + return $this->id; + } + + /** + * Set the item's ID + * + * @param mixed $id ID of the item + * + * @return $this + */ + public function setId($id) + { + $this->id = $id; + return $this; + } + + /** + * Get the label + * + * @return string|null + */ + public function getLabel() + { + return $this->label; + } + + /** + * Set the label + * + * @param string $label + * + * @return $this + */ + public function setLabel($label) + { + $this->label = (string) $label; + return $this; + } + + /** + * Get the URL + * + * @return Url|null + */ + public function getUrl() + { + if ($this->url !== null && ! $this->url instanceof Url) { + $this->url = Url::fromPath((string) $this->url); + } + return $this->url; + } + + /** + * Set the URL + * + * @param Url|string $url + * + * @return $this + */ + public function setUrl($url) + { + $this->url = $url; + return $this; + } + + /** + * Get the URL parameters + * + * @return array + */ + public function getUrlParameters() + { + return $this->urlParameters; + } + + /** + * Set the URL parameters + * + * @param array $urlParameters + * + * @return $this + */ + public function setUrlParameters(array $urlParameters) + { + $this->urlParameters = $urlParameters; + return $this; + } + + /** + * Get the view + * + * @return View + */ + public function getView() + { + if ($this->view === null) { + $this->view = 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 properties for the item + * + * @param array $properties + * + * @return $this + */ + public function setProperties(array $properties = array()) + { + foreach ($properties as $name => $value) { + $setter = 'set' . ucfirst($name); + if (method_exists($this, $setter)) { + $this->$setter($value); + } + } + return $this; + } + + /** + * Render the navigation item to HTML + * + * @return string + */ + public function render() + { + $view = $this->getView(); + $label = $view->escape($this->getLabel()); + if (null !== $icon = $this->getIcon()) { + $label = $view->icon($icon) . $label; + } + if (null !== $url = $this->getUrl()) { + $content = sprintf( + '%s', + $view->propertiesToString($this->getAttributes()), + $view->url($url, $this->getUrlParameters()), + $label + ); + } else { + $content = sprintf( + '<%1$s%2$s>%3$s', + static::LINK_ALTERNATIVE, + $view->propertiesToString($this->getAttributes()), + $label + ); + } + return $content; + } + + /** + * Render the navigation item to HTML + * + * @return string + */ + public function __toString() + { + return $this->render(); + } +} diff --git a/library/Icinga/Web/Navigation/NavigationRenderer.php b/library/Icinga/Web/Navigation/NavigationRenderer.php new file mode 100644 index 000000000..60cfcf900 --- /dev/null +++ b/library/Icinga/Web/Navigation/NavigationRenderer.php @@ -0,0 +1,340 @@ +iterator = $navigation->getIterator(); + $this->navigation = $navigation; + $this->flags = $flags; + } + + /** + * {@inheritdoc} + */ + public function getChildren() + { + return new static($this->current()->getChildren(), $this->flags & static::NAV_DISABLE); + } + + /** + * {@inheritdoc} + */ + public function hasChildren() + { + return $this->current()->hasChildren(); + } + + /** + * {@inheritdoc} + * @return \Icinga\Web\Navigation\NavigationItem + */ + public function current() + { + return $this->iterator->current(); + } + + /** + * {@inheritdoc} + */ + public function key() + { + return $this->iterator->key(); + } + + /** + * {@inheritdoc} + */ + public function next() + { + $this->iterator->next(); + } + + /** + * {@inheritdoc} + */ + public function rewind() + { + $this->iterator->rewind(); + if (! (bool) ($this->flags & static::NAV_DISABLE) && ! $this->inIteration) { + $this->content[] = $this->beginMarkup(); + $this->inIteration = true; + } + } + + /** + * {@inheritdoc} + */ + public function valid() + { + $valid = $this->iterator->valid(); + if (! (bool) ($this->flags & static::NAV_DISABLE) && ! $valid && $this->inIteration) { + $this->content[] = $this->endMarkup(); + $this->inIteration = false; + } + return $valid; + } + + /** + * Begin navigation markup + * + * @return string + */ + public function beginMarkup() + { + $content = array(); + if ($this->flags & static::NAV_MAJOR) { + $el = 'nav'; + } else { + $el = 'div'; + } + if (($elCssClass = $this->getCssClass()) !== null) { + $elCss = ' class="' . $elCssClass . '"'; + } else { + $elCss = ''; + } + $content[] = sprintf( + '<%s%s role="navigation">', + $el, + $elCss + ); + $content[] = sprintf( + '%2$s', + static::HEADING_RANK, + $this->getView()->escape($this->getHeading()) + ); + $content[] = $this->beginChildrenMarkup(); + return implode("\n", $content); + } + + /** + * End navigation markup + * + * @return string + */ + public function endMarkup() + { + $content = array(); + $content[] = $this->endChildrenMarkup(); + if ($this->flags & static::NAV_MAJOR) { + $content[] = ''; + } else { + $content[] = ''; + } + return implode("\n", $content); + } + + /** + * Begin children markup + * + * @return string + */ + public function beginChildrenMarkup() + { + $ulCssClass = static::CSS_CLASS_NAV; + if ($this->navigation->getLayout() & Navigation::LAYOUT_TABS) { + $ulCssClass .= ' ' . static::CSS_CLASS_NAV_TABS; + } + if ($this->navigation->getLayout() & Navigation::LAYOUT_DROPDOWN) { + $ulCssClass .= ' ' . static::CSS_CLASS_NAV_DROPDOWN; + } + return ''; + } + + /** + * Begin item markup + * + * @param NavigationItem $item + * + * @return string + */ + public function beginItemMarkup(NavigationItem $item) + { + $cssClass = array(); + if ($item->getActive()) { + $cssClass[] = static::CSS_CLASS_ACTIVE; + } + if ($item->hasChildren() + && $item->getChildren()->getLayout() === Navigation::LAYOUT_DROPDOWN + ) { + $cssClass[] = static::CSS_CLASS_DROPDOWN; + $item + ->setAttribute('class', static::CSS_CLASS_DROPDOWN_TOGGLE) + ->setIcon(static::DROPDOWN_TOGGLE_ICON) + ->setUrl('#'); + } + if (! empty($cssClass)) { + $content = sprintf('
  • ', implode(' ', $cssClass)); + } else { + $content = '
  • '; + } + return $content; + } + + /** + * End item markup + * + * @return string + */ + public function endItemMarkup() + { + return '
  • '; + } + + /** + * {@inheritdoc} + */ + public function getCssClass() + { + return $this->cssClass; + } + + /** + * {@inheritdoc} + */ + public function setCssClass($class) + { + $this->cssClass = trim((string) $class); + return $this; + } + + /** + * {@inheritdoc} + */ + public function getHeading() + { + return $this->heading; + } + + /** + * {@inheritdoc} + */ + public function setHeading($heading) + { + $this->heading = (string) $heading; + return $this; + } + + /** + * Get the view + * + * @return View + */ + public function getView() + { + if ($this->view === null) { + $this->view = 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; + } + + /** + * {@inheritdoc} + */ + public function render() + { + foreach ($this as $navigationItem) { + /** @var \Icinga\Web\Navigation\NavigationItem $navigationItem */ + $this->content[] = $this->beginItemMarkup($navigationItem); + $this->content[] = $navigationItem->render(); + $this->content[] = $this->endItemMarkup(); + } + return implode("\n", $this->content); + } +} diff --git a/library/Icinga/Web/Navigation/NavigationRendererInterface.php b/library/Icinga/Web/Navigation/NavigationRendererInterface.php new file mode 100644 index 000000000..c3e014f44 --- /dev/null +++ b/library/Icinga/Web/Navigation/NavigationRendererInterface.php @@ -0,0 +1,122 @@ +content[] = $this->getInnerIterator()->beginMarkup(); + } + + /** + * {@inheritdoc} + */ + public function endIteration() + { + $this->content[] = $this->getInnerIterator()->endMarkup(); + } + + /** + * {@inheritdoc} + */ + public function beginChildren() + { + $this->content[] = $this->getInnerIterator()->beginChildrenMarkup(); + } + + /** + * {@inheritdoc} + */ + public function endChildren() + { + $this->content[] = $this->getInnerIterator()->endChildrenMarkup(); + } + + /** + * {@inheritdoc} + */ + public function getCssClass() + { + return $this->getInnerIterator()->getCssClass(); + } + + /** + * {@inheritdoc} + */ + public function setCssClass($class) + { + $this->getInnerIterator()->setCssClass($class); + return $this; + } + + /** + * {@inheritdoc} + */ + public function getHeading() + { + return $this->getInnerIterator()->getHeading(); + } + + /** + * {@inheritdoc} + */ + public function setHeading($heading) + { + $this->getInnerIterator()->setHeading($heading); + return $this; + } + + /** + * {@inheritdoc} + */ + public function render() + { + foreach ($this as $navigationItem) { + /** @var \Icinga\Web\Navigation\NavigationItem $navigationItem */ + $this->content[] = $this->getInnerIterator()->beginItemMarkup($navigationItem); + $this->content[] = $navigationItem->render(); + if (! $navigationItem->hasChildren()) { + $this->content[] = $this->getInnerIterator()->endItemMarkup(); + } + } + return implode("\n", $this->content); + } +}