mirror of
https://github.com/Icinga/icingaweb2.git
synced 2025-07-27 15:54:03 +02:00
commit
70b50a57a9
@ -4,8 +4,6 @@
|
|||||||
|
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
var activeMenuId;
|
|
||||||
|
|
||||||
Icinga.Behaviors = Icinga.Behaviors || {};
|
Icinga.Behaviors = Icinga.Behaviors || {};
|
||||||
|
|
||||||
var Navigation = function (icinga) {
|
var Navigation = function (icinga) {
|
||||||
@ -17,49 +15,93 @@
|
|||||||
this.on('mouseenter', '#menu > nav > ul > li', this.menuTitleHovered, this);
|
this.on('mouseenter', '#menu > nav > ul > li', this.menuTitleHovered, this);
|
||||||
this.on('mouseleave', '#sidebar', this.leaveSidebar, this);
|
this.on('mouseleave', '#sidebar', this.leaveSidebar, this);
|
||||||
this.on('rendered', this.onRendered, this);
|
this.on('rendered', this.onRendered, this);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The DOM-Path of the active item
|
||||||
|
*
|
||||||
|
* @see getDomPath
|
||||||
|
*
|
||||||
|
* @type {null|Array}
|
||||||
|
*/
|
||||||
|
this.active = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The DOM-Path of the hovered item
|
||||||
|
*
|
||||||
|
* @see getDomPath
|
||||||
|
*
|
||||||
|
* @type {null|Array}
|
||||||
|
*/
|
||||||
|
this.hovered = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {HTMLElement}
|
||||||
|
*/
|
||||||
|
this.element = null;
|
||||||
};
|
};
|
||||||
Navigation.prototype = new Icinga.EventListener();
|
Navigation.prototype = new Icinga.EventListener();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Apply the menu selection and hovering according to the current state
|
||||||
|
*
|
||||||
|
* @param evt {Object} The event context
|
||||||
|
*/
|
||||||
Navigation.prototype.onRendered = function(evt) {
|
Navigation.prototype.onRendered = function(evt) {
|
||||||
var self = evt.data.self;
|
var self = evt.data.self;
|
||||||
// get original source element of the rendered-event
|
this.element = evt.target;
|
||||||
var el = evt.target;
|
|
||||||
if (activeMenuId) {
|
if (! self.active) {
|
||||||
// restore old menu state
|
// There is no stored menu item, therefore it is assumed that this is the first rendering
|
||||||
$('#menu li.active', el).removeClass('active');
|
// of the navigation after the page has been opened.
|
||||||
var $selectedMenu = $('#' + activeMenuId).addClass('active');
|
|
||||||
var $outerMenu = $selectedMenu.parent().closest('li');
|
// initialise the menu selected by the backend as active.
|
||||||
if ($outerMenu.size()) {
|
var $menus = $('#menu li.active', evt.target);
|
||||||
$outerMenu.addClass('active');
|
if ($menus.size()) {
|
||||||
|
$menus.each(function () {
|
||||||
|
self.setActive($(this));
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// if no item is marked as active, try to select the menu from the current URL
|
||||||
|
self.setActiveByUrl($('#col1').data('icingaUrl'));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
self.refresh();
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Re-render the menu selection and menu hovering according to the current state
|
||||||
|
*/
|
||||||
|
Navigation.prototype.refresh = function() {
|
||||||
|
// restore selection to current active element
|
||||||
|
if (this.active) {
|
||||||
|
var $el = $(this.icinga.utils.getElementByDomPath(this.active));
|
||||||
|
this.setActive($el);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Recreate the html content of the menu item to force the browser to update the layout, or else
|
* Recreate the html content of the menu item to force the browser to update the layout, or else
|
||||||
the link would only be visible as active after another click or page reload in Gecko and WebKit.
|
* the link would only be visible as active after another click or page reload in Gecko and WebKit.
|
||||||
|
*
|
||||||
fixes #7897
|
* fixes #7897
|
||||||
*/
|
*/
|
||||||
$selectedMenu.html($selectedMenu.html());
|
if ($el.is('li')) {
|
||||||
|
$el.html($el.html());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
} else {
|
// restore hovered menu to current hovered element
|
||||||
// store menu state
|
if (this.hovered) {
|
||||||
var $menus = $('#menu li.active', el);
|
var hovered = this.icinga.utils.getElementByDomPath(this.hovered);
|
||||||
if ($menus.size()) {
|
|
||||||
activeMenuId = $menus[0].id;
|
|
||||||
$menus.find('li.active').first().each(function () {
|
|
||||||
activeMenuId = this.id;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// restore hovered menu after auto-reload
|
|
||||||
if (self.hovered) {
|
|
||||||
var hovered = self.icinga.utils.getElementByDomPath(self.hovered);
|
|
||||||
if (hovered) {
|
if (hovered) {
|
||||||
self.hoverElement($(hovered));
|
this.hoverElement($(hovered));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle a link click in the menu
|
||||||
|
*
|
||||||
|
* @param event
|
||||||
|
*/
|
||||||
Navigation.prototype.linkClicked = function(event) {
|
Navigation.prototype.linkClicked = function(event) {
|
||||||
var $a = $(this);
|
var $a = $(this);
|
||||||
var href = $a.attr('href');
|
var href = $a.attr('href');
|
||||||
@ -71,10 +113,8 @@
|
|||||||
if (href.match(/#/)) {
|
if (href.match(/#/)) {
|
||||||
// ...it may be a menu section without a dedicated link.
|
// ...it may be a menu section without a dedicated link.
|
||||||
// Switch the active menu item:
|
// Switch the active menu item:
|
||||||
|
self.setActive($a);
|
||||||
$li = $a.closest('li');
|
$li = $a.closest('li');
|
||||||
$('#menu .active').removeClass('active');
|
|
||||||
$li.addClass('active');
|
|
||||||
activeMenuId = $($li).attr('id');
|
|
||||||
if ($li.hasClass('hover')) {
|
if ($li.hasClass('hover')) {
|
||||||
$li.removeClass('hover');
|
$li.removeClass('hover');
|
||||||
}
|
}
|
||||||
@ -86,7 +126,7 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
activeMenuId = $(event.target).closest('li').attr('id');
|
self.setActive($(event.target));
|
||||||
}
|
}
|
||||||
// update target url of the menu container to the clicked link
|
// update target url of the menu container to the clicked link
|
||||||
var $menu = $('#menu');
|
var $menu = $('#menu');
|
||||||
@ -95,9 +135,82 @@
|
|||||||
$menu.data('icinga-url', menuDataUrl);
|
$menu.data('icinga-url', menuDataUrl);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Activate a menu item based on the current URL
|
||||||
|
*
|
||||||
|
* Activate a menu item that is an exact match or fall back to items that match the base URL
|
||||||
|
*
|
||||||
|
* @param url {String} The url to match
|
||||||
|
*/
|
||||||
Navigation.prototype.setActiveByUrl = function(url) {
|
Navigation.prototype.setActiveByUrl = function(url) {
|
||||||
this.resetActive();
|
|
||||||
|
// try to active the first item that has an exact URL match
|
||||||
this.setActive($('#menu [href="' + url + '"]'));
|
this.setActive($('#menu [href="' + url + '"]'));
|
||||||
|
|
||||||
|
// the url may point to the search field, which must be activated too
|
||||||
|
if (! this.active) {
|
||||||
|
this.setActive($('#menu 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 [href="' + this.icinga.utils.parseUrl(url).path + '"]').first());
|
||||||
|
}
|
||||||
|
|
||||||
|
// if no item with the base action exists, activate the first URL that beings with the base path
|
||||||
|
// but may have different filters
|
||||||
|
if (! this.active) {
|
||||||
|
this.setActive($('#menu [href^="' + this.icinga.utils.parseUrl(url).path + '"]').first());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Try to select a new URL by
|
||||||
|
*
|
||||||
|
* @param url
|
||||||
|
*/
|
||||||
|
Navigation.prototype.trySetActiveByUrl = function(url) {
|
||||||
|
var active = this.active;
|
||||||
|
this.setActiveByUrl(url);
|
||||||
|
if (! this.active && active) {
|
||||||
|
this.setActive($(this.icinga.utils.getElementByDomPath(active)));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove all active elements
|
||||||
|
*/
|
||||||
|
Navigation.prototype.clear = function() {
|
||||||
|
// menu items
|
||||||
|
$('#menu li.active', this.element).removeClass('active');
|
||||||
|
|
||||||
|
// search fields
|
||||||
|
$('#menu input.active', this.element).removeClass('active');
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Select all menu items in the selector as active and unfold surrounding menus when necessary
|
||||||
|
*
|
||||||
|
* @param $item {jQuery} The jQuery selector
|
||||||
|
*/
|
||||||
|
Navigation.prototype.select = function($item) {
|
||||||
|
// support selecting the url of the menu entry
|
||||||
|
var $input = $item.find('input');
|
||||||
|
$item = $item.closest('li');
|
||||||
|
|
||||||
|
if ($item.length) {
|
||||||
|
// select the current item
|
||||||
|
var $selectedMenu = $item.addClass('active');
|
||||||
|
|
||||||
|
// unfold the containing menu
|
||||||
|
var $outerMenu = $selectedMenu.parent().closest('li');
|
||||||
|
if ($outerMenu.size()) {
|
||||||
|
$outerMenu.addClass('active');
|
||||||
|
}
|
||||||
|
} else if ($input.length) {
|
||||||
|
$input.addClass('active');
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -106,14 +219,59 @@
|
|||||||
* @param $el {jQuery} A selector pointing to the active element
|
* @param $el {jQuery} A selector pointing to the active element
|
||||||
*/
|
*/
|
||||||
Navigation.prototype.setActive = function($el) {
|
Navigation.prototype.setActive = function($el) {
|
||||||
$el.closest('li').addClass('active');
|
this.clear();
|
||||||
$el.parents('li').addClass('active');
|
this.select($el);
|
||||||
activeMenuId = $el.closest('li').attr('id');
|
if ($el.closest('li')[0]) {
|
||||||
|
this.active = this.icinga.utils.getDomPath($el.closest('li')[0]);
|
||||||
|
} else if ($el.find('input')[0]) {
|
||||||
|
this.active = this.icinga.utils.getDomPath($el[0]);
|
||||||
|
} else {
|
||||||
|
this.active = null;
|
||||||
|
}
|
||||||
|
// TODO: push to history
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reset the active element to nothing
|
||||||
|
*/
|
||||||
Navigation.prototype.resetActive = function() {
|
Navigation.prototype.resetActive = function() {
|
||||||
$('#menu .active').removeClass('active');
|
this.clear();
|
||||||
activeMenuId = null;
|
this.active = null;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the history changes
|
||||||
|
*
|
||||||
|
* @param url The url of the new state
|
||||||
|
* @param data The active menu item of the new state
|
||||||
|
*/
|
||||||
|
Navigation.prototype.onPopState = function (url, data) {
|
||||||
|
// 1. get selection data and set active menu
|
||||||
|
console.log('popstate:', data);
|
||||||
|
if (data) {
|
||||||
|
var active = this.icinga.utils.getElementByDomPath(data);
|
||||||
|
if (!active) {
|
||||||
|
this.logger.fail(
|
||||||
|
'Could not restore active menu from history, path in DOM not found.',
|
||||||
|
data,
|
||||||
|
url
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.setActive($(active));
|
||||||
|
} else {
|
||||||
|
this.resetActive();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the current state gets pushed onto the history, can return a value
|
||||||
|
* to be preserved as the current state
|
||||||
|
*
|
||||||
|
* @returns {null|Array} The currently active menu item
|
||||||
|
*/
|
||||||
|
Navigation.prototype.onPushState = function () {
|
||||||
|
return this.active;
|
||||||
};
|
};
|
||||||
|
|
||||||
Navigation.prototype.menuTitleHovered = function(event) {
|
Navigation.prototype.menuTitleHovered = function(event) {
|
||||||
|
@ -4,7 +4,6 @@
|
|||||||
* Icinga.History
|
* Icinga.History
|
||||||
*
|
*
|
||||||
* This is where we care about the browser History API
|
* This is where we care about the browser History API
|
||||||
*
|
|
||||||
*/
|
*/
|
||||||
(function (Icinga, $) {
|
(function (Icinga, $) {
|
||||||
|
|
||||||
@ -89,7 +88,6 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// TODO: update navigation
|
|
||||||
// Did we find any URL? Then push it!
|
// Did we find any URL? Then push it!
|
||||||
if (url !== '') {
|
if (url !== '') {
|
||||||
this.push(url);
|
this.push(url);
|
||||||
@ -110,7 +108,26 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.lastPushUrl = url;
|
this.lastPushUrl = url;
|
||||||
window.history.pushState({icinga: true}, null, url);
|
window.history.pushState(
|
||||||
|
this.getBehaviorState(),
|
||||||
|
null,
|
||||||
|
url
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch the current state of all JS behaviors that need history support
|
||||||
|
*
|
||||||
|
* @return {Object} A key-value map, mapping behavior names to state
|
||||||
|
*/
|
||||||
|
getBehaviorState: function () {
|
||||||
|
var data = {};
|
||||||
|
$.each(this.icinga.behaviors, function (i, behavior) {
|
||||||
|
if (behavior.onPushState instanceof Function) {
|
||||||
|
data[i] = behavior.onPushState();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return data;
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -143,6 +160,13 @@
|
|||||||
self.lastPushUrl = location.href;
|
self.lastPushUrl = location.href;
|
||||||
|
|
||||||
self.applyLocationBar();
|
self.applyLocationBar();
|
||||||
|
|
||||||
|
// notify behaviors of the state change
|
||||||
|
$.each(this.icinga.behaviors, function (i, behavior) {
|
||||||
|
if (behavior.onPopState instanceof Function && history.state) {
|
||||||
|
behavior.onPopState(location.href, history.state[i]);
|
||||||
|
}
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
applyLocationBar: function (onload) {
|
applyLocationBar: function (onload) {
|
||||||
|
@ -570,27 +570,20 @@
|
|||||||
if (! req.autorefresh) {
|
if (! req.autorefresh) {
|
||||||
// TODO: Hook for response/url?
|
// TODO: Hook for response/url?
|
||||||
var url = req.url;
|
var url = req.url;
|
||||||
|
|
||||||
|
if (req.$target[0].id === 'col1') {
|
||||||
|
self.icinga.behaviors.navigation.trySetActiveByUrl(url);
|
||||||
|
}
|
||||||
|
|
||||||
var $forms = $('[action="' + this.icinga.utils.parseUrl(url).path + '"]');
|
var $forms = $('[action="' + this.icinga.utils.parseUrl(url).path + '"]');
|
||||||
var $matches = $.merge($('[href="' + url + '"]'), $forms);
|
var $matches = $.merge($('[href="' + url + '"]'), $forms);
|
||||||
$matches.each(function (idx, el) {
|
|
||||||
if ($(el).closest('#menu').length) {
|
|
||||||
if (req.$target[0].id === 'col1') {
|
|
||||||
self.icinga.behaviors.navigation.resetActive();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
$matches.each(function (idx, el) {
|
$matches.each(function (idx, el) {
|
||||||
var $el = $(el);
|
var $el = $(el);
|
||||||
if ($el.closest('#menu').length) {
|
if ($el.closest('#menu').length) {
|
||||||
if ($el.is('form')) {
|
if ($el.is('form')) {
|
||||||
$('input', $el).addClass('active');
|
$('input', $el).addClass('active');
|
||||||
} else {
|
|
||||||
if (req.$target[0].id === 'col1') {
|
|
||||||
self.icinga.behaviors.navigation.setActive($el);
|
|
||||||
}
|
}
|
||||||
}
|
// Interrupt .each, only one menu item shall be active
|
||||||
// Interrupt .each, only on menu item shall be active
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -154,7 +154,7 @@
|
|||||||
var kill = this.cutContainer($('#col1'));
|
var kill = this.cutContainer($('#col1'));
|
||||||
this.pasteContainer($('#col1'), col2);
|
this.pasteContainer($('#col1'), col2);
|
||||||
this.fixControls();
|
this.fixControls();
|
||||||
this.icinga.behaviors.navigation.setActiveByUrl($('#col1').data('icingaUrl'));
|
this.icinga.behaviors.navigation.trySetActiveByUrl($('#col1').data('icingaUrl'));
|
||||||
},
|
},
|
||||||
|
|
||||||
cutContainer: function ($col) {
|
cutContainer: function ($col) {
|
||||||
@ -480,8 +480,7 @@
|
|||||||
* @param value {String} The value to set, can be '1', '0' and 'unchanged'
|
* @param value {String} The value to set, can be '1', '0' and 'unchanged'
|
||||||
* @param $checkbox {jQuery} The checkbox
|
* @param $checkbox {jQuery} The checkbox
|
||||||
*/
|
*/
|
||||||
setTriState: function(value, $checkbox)
|
setTriState: function(value, $checkbox) {
|
||||||
{
|
|
||||||
switch (value) {
|
switch (value) {
|
||||||
case ('1'):
|
case ('1'):
|
||||||
$checkbox.prop('checked', true).prop('indeterminate', false);
|
$checkbox.prop('checked', true).prop('indeterminate', false);
|
||||||
|
@ -76,8 +76,7 @@
|
|||||||
* @param {selector} element The element to check
|
* @param {selector} element The element to check
|
||||||
* @returns {Boolean}
|
* @returns {Boolean}
|
||||||
*/
|
*/
|
||||||
isVisible: function(element)
|
isVisible: function(element) {
|
||||||
{
|
|
||||||
var $element = $(element);
|
var $element = $(element);
|
||||||
if (!$element.length) {
|
if (!$element.length) {
|
||||||
return false;
|
return false;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user