From b3b80fdc3144e651f53821adeea39564f1589073 Mon Sep 17 00:00:00 2001 From: Jennifer Mourek Date: Tue, 19 Mar 2019 13:45:31 +0100 Subject: [PATCH] Make active first-level nav items better distinguishable --- .../Icinga/Web/Navigation/NavigationItem.php | 40 +++++++++++++ .../Renderer/NavigationRenderer.php | 4 ++ .../Renderer/NavigationRendererInterface.php | 7 +++ public/css/icinga/menu.less | 34 +++++------ public/js/icinga/behavior/navigation.js | 59 +++++++++++++++---- public/js/icinga/loader.js | 2 +- public/js/icinga/ui.js | 2 +- 7 files changed, 114 insertions(+), 34 deletions(-) diff --git a/library/Icinga/Web/Navigation/NavigationItem.php b/library/Icinga/Web/Navigation/NavigationItem.php index 17360a3f5..abc63ad90 100644 --- a/library/Icinga/Web/Navigation/NavigationItem.php +++ b/library/Icinga/Web/Navigation/NavigationItem.php @@ -37,6 +37,13 @@ class NavigationItem implements IteratorAggregate */ protected $active; + /** + * Whether this item is selected + * + * @var bool + */ + protected $selected; + /** * The CSS class used for the outer li element * @@ -213,6 +220,39 @@ class NavigationItem implements IteratorAggregate return $this; } + /** + * Return whether this item is selected + * + * @return bool + */ + public function getSelected() + { + if ($this->selected === null) { + $this->active = false; + if ($this->getUrl() !== null && Icinga::app()->getRequest()->getUrl()->matches($this->getUrl())) { + $this->setSelected(); + } + } + + return $this->selected; + } + + /** + * Set whether this item is active + * + * If it's active and has a parent, the parent gets activated as well. + * + * @param bool $selected + * + * @return $this + */ + public function setSelected($selected = true) + { + $this->selected = (bool) $selected; + + return $this; + } + /** * Get the CSS class used for the outer li element * diff --git a/library/Icinga/Web/Navigation/Renderer/NavigationRenderer.php b/library/Icinga/Web/Navigation/Renderer/NavigationRenderer.php index b02b7c7b2..e2fb40b4f 100644 --- a/library/Icinga/Web/Navigation/Renderer/NavigationRenderer.php +++ b/library/Icinga/Web/Navigation/Renderer/NavigationRenderer.php @@ -322,6 +322,10 @@ class NavigationRenderer implements RecursiveIterator, NavigationRendererInterfa $cssClasses[] = static::CSS_CLASS_ACTIVE; } + if ($item->getSelected()) { + $cssClasses[] = static::CSS_CLASS_SELECTED; + } + if ($cssClass = $item->getCssClass()) { $cssClasses[] = $cssClass; } diff --git a/library/Icinga/Web/Navigation/Renderer/NavigationRendererInterface.php b/library/Icinga/Web/Navigation/Renderer/NavigationRendererInterface.php index a2db929e1..4495b7379 100644 --- a/library/Icinga/Web/Navigation/Renderer/NavigationRendererInterface.php +++ b/library/Icinga/Web/Navigation/Renderer/NavigationRendererInterface.php @@ -22,6 +22,13 @@ interface NavigationRendererInterface */ const CSS_CLASS_ACTIVE = 'active'; + /** + * CSS class for selected items + * + * @var string + */ + const CSS_CLASS_SELECTED = 'selected'; + /** * CSS class for dropdown items * diff --git a/public/css/icinga/menu.less b/public/css/icinga/menu.less index 0efa32692..4b37887fb 100644 --- a/public/css/icinga/menu.less +++ b/public/css/icinga/menu.less @@ -79,10 +79,16 @@ > a { padding: 0.5em 0.5em 0.5em .75em; + } - &:focus, &:hover { - color: @menu-highlight-color; - } + &.active:not(.selected) > a:focus, + &.active:not(.selected) > a:hover { + background-color: mix(#000, @menu-active-bg-color, 20); + } + + &:not(.selected) > a:hover, + &:not(.selected) > a:focus { + background-color: mix(#000, @menu-bg-color, 20); } // Balance icon weight for non active menu items @@ -102,9 +108,6 @@ ul:not(.nav-level-2) > .selected > a { background-color: @menu-highlight-color; color: @text-color-inverted; - &:focus { - background-color: mix(#000, @menu-highlight-color, 20); - } &:hover { color: @text-color-inverted; @@ -134,26 +137,19 @@ ul:not(.nav-level-2) > .selected > a { > a { color: @menu-2ndlvl-color; font-size: @font-size-small; - padding: 0.364em 0.545em 0.364em .8em; + padding: 0.364em 0.545em 0.364em (@icon-width + .75em); + } - &:hover, &:focus { - color: @menu-2ndlvl-highlight-color; - } + &:not(.selected):not(.active) > a:hover, + &:not(.selected):not(.active) > a:focus { + background-color: mix(#000, @menu-active-bg-color, 20); + color: @menu-2ndlvl-highlight-color; } &.active { background-color: @menu-highlight-color; overflow: hidden; position: relative; - - &.active { - background-color: @menu-highlight-color; - overflow: hidden; - position: relative; - } - - &.active > a:focus { - background-color: mix(#000, @menu-highlight-color, 20); } // Little caret on active level-2 item diff --git a/public/js/icinga/behavior/navigation.js b/public/js/icinga/behavior/navigation.js index dc1532dc2..c7c8b0907 100644 --- a/public/js/icinga/behavior/navigation.js +++ b/public/js/icinga/behavior/navigation.js @@ -52,11 +52,11 @@ var $active = _this.$menu.find('li.active'); if ($active.length) { $active.each(function() { - _this.setActive($(this)); + _this.setActiveAndSelected($(this)); }); } else { // if no item is marked as active, try to select the menu from the current URL - _this.setActiveByUrl($('#col1').data('icingaUrl')); + _this.setActiveAndSelectedByUrl($('#col1').data('icingaUrl')); } } @@ -70,7 +70,7 @@ // restore selection to current active element if (this.active) { var $el = $(this.icinga.utils.getElementByDomPath(this.active)); - this.setActive($el); + this.setActiveAndSelected($el); /* * Recreate the html content of the menu item to force the browser to update the layout, or else @@ -98,9 +98,9 @@ if (href.match(/#/)) { // ...it may be a menu section without a dedicated link. // Switch the active menu item: - _this.setActive($a); + _this.setActiveAndSelected($a); } else { - _this.setActive($(event.target)); + _this.setActiveAndSelected($(event.target)); } // update target url of the menu container to the clicked link var $menu = $('#menu'); @@ -116,7 +116,7 @@ * * @param url {String} The url to match */ - Navigation.prototype.setActiveByUrl = function(url) { + Navigation.prototype.setActiveAndSelectedByUrl = function(url) { var $menu = $('#menu'); if (! $menu.length) { @@ -124,17 +124,17 @@ } // try to active the first item that has an exact URL match - this.setActive($menu.find('[href="' + url + '"]')); + this.setActiveAndSelected($menu.find('[href="' + url + '"]')); // the url may point to the search field, which must be activated too if (! this.active) { - this.setActive($menu.find('form[action="' + this.icinga.utils.parseUrl(url).path + '"]')); + this.setActiveAndSelected($menu.find('form[action="' + this.icinga.utils.parseUrl(url).path + '"]')); } // some urls may have custom filters which won't match any menu item, in that case search // for a menu item that points to the base action without any filters if (! this.active) { - this.setActive($menu.find('[href="' + this.icinga.utils.parseUrl(url).path + '"]').first()); + this.setActiveAndSelected($menu.find('[href="' + this.icinga.utils.parseUrl(url).path + '"]').first()); } }; @@ -143,11 +143,12 @@ * * @param url */ - Navigation.prototype.trySetActiveByUrl = function(url) { + Navigation.prototype.trySetActiveAndSelectedByUrl = function(url) { var active = this.active; - this.setActiveByUrl(url); + this.setActiveAndSelectedByUrl(url); + if (! this.active && active) { - this.setActive($(this.icinga.utils.getElementByDomPath(active))); + this.setActiveAndSelected($(this.icinga.utils.getElementByDomPath(active))); } }; @@ -160,6 +161,15 @@ } }; + /** + * Remove all selected elements + */ + Navigation.prototype.clearSelected = function() { + if (this.$menu) { + this.$menu.find('.selected').removeClass('selected'); + } + }; + /** * Select all menu items in the selector as active and unfold surrounding menus when necessary * @@ -184,6 +194,11 @@ } }; + Navigation.prototype.setActiveAndSelected = function ($el) { + this.setActive($el); + this.setSelected($el); + }; + /** * Change the active menu element * @@ -202,6 +217,15 @@ // TODO: push to history }; + Navigation.prototype.setSelected = function($el) { + this.clearSelected(); + $el = $el.closest('li'); + + if ($el.length) { + $el.addClass('selected'); + } + }; + /** * Reset the active element to nothing */ @@ -210,6 +234,14 @@ this.active = null; }; + /** + * Reset the selected element to nothing + */ + Navigation.prototype.resetSelected = function() { + this.clearSelected(); + this.selected = null; + }; + /** * Show the fly-out menu * @@ -318,9 +350,10 @@ ); return; } - this.setActive($(active)); + this.setActiveAndSelected($(active)) } else { this.resetActive(); + this.resetSelected(); } }; diff --git a/public/js/icinga/loader.js b/public/js/icinga/loader.js index d17465e09..b25b08229 100644 --- a/public/js/icinga/loader.js +++ b/public/js/icinga/loader.js @@ -604,7 +604,7 @@ var url = req.url; if (req.$target[0].id === 'col1') { - this.icinga.behaviors.navigation.trySetActiveByUrl(url); + this.icinga.behaviors.navigation.trySetActiveAndSelectedByUrl(url); } var $forms = $('[action="' + this.icinga.utils.parseUrl(url).path + '"]'); diff --git a/public/js/icinga/ui.js b/public/js/icinga/ui.js index 31195351e..e89038380 100644 --- a/public/js/icinga/ui.js +++ b/public/js/icinga/ui.js @@ -186,7 +186,7 @@ var kill = this.cutContainer($('#col1')); this.pasteContainer($('#col1'), col2); this.fixControls(); - this.icinga.behaviors.navigation.trySetActiveByUrl($('#col1').data('icingaUrl')); + this.icinga.behaviors.navigation.trySetActiveAndSelectedByUrl($('#col1').data('icingaUrl')); }, cutContainer: function ($col) {