mirror of
https://github.com/Icinga/icingaweb2.git
synced 2025-07-30 01:04:09 +02:00
Add/modify JavaScript components for main/detail and their tests
This commit introduces the following changes (although they are not implemented in the templates, this follows): - links in the top and navigation bar are loaded in the background - generic main/detail container component handling click/load delegation - mainDetailGrid implementation handling selection and render targets - Generic history implementation handling URI change detection and reloading refs #4611
This commit is contained in:
parent
62e98ccbd7
commit
e6b3a54e90
481
public/js/icinga/components/container.js
Normal file
481
public/js/icinga/components/container.js
Normal file
@ -0,0 +1,481 @@
|
|||||||
|
/*global Icinga:false, Modernizr: false, document: false, History: false, define:false require:false base_url:false console:false */
|
||||||
|
// {{{ICINGA_LICENSE_HEADER}}}
|
||||||
|
/**
|
||||||
|
* This file is part of Icinga 2 Web.
|
||||||
|
*
|
||||||
|
* Icinga 2 Web - Head for multiple monitoring backends.
|
||||||
|
* Copyright (C) 2013 Icinga Development Team
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or
|
||||||
|
* modify it under the terms of the GNU General Public License
|
||||||
|
* as published by the Free Software Foundation; either version 2
|
||||||
|
* of the License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; if not, write to the Free Software
|
||||||
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||||
|
*
|
||||||
|
* @copyright 2013 Icinga Development Team <info@icinga.org>
|
||||||
|
* @license http://www.gnu.org/licenses/gpl-2.0.txt GPL, version 2
|
||||||
|
* @author Icinga Development Team <info@icinga.org>
|
||||||
|
*/
|
||||||
|
// {{{ICINGA_LICENSE_HEADER}}}
|
||||||
|
|
||||||
|
define(['jquery', 'logging', 'icinga/componentLoader', 'URIjs/URI', 'URIjs/URITemplate'], function($, logger, componentLoader, URI) {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Static reference to the main container, populated on the first 'getMainContainer' call
|
||||||
|
*
|
||||||
|
* @type {Container}
|
||||||
|
*/
|
||||||
|
var mainContainer = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Static reference to the detail container, populated on the first getDetailContainer call
|
||||||
|
*
|
||||||
|
* @type {Container}
|
||||||
|
*/
|
||||||
|
var detailContainer = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A handler for accessing icinga containers, i.e. the #icingamain, #icingadetail containers and specific 'app/container'
|
||||||
|
* components.
|
||||||
|
*
|
||||||
|
* This component can be constructed with every object as the parameter and will provide access to the nearest
|
||||||
|
* container (which could be the applied object itself, if it is a container) wrapping this object.
|
||||||
|
*
|
||||||
|
* The windows url should always be modified with this implementation, so an objects context should point to a
|
||||||
|
* new URL, call new Container('#myObject').updateContainerHref('/my/url')
|
||||||
|
*
|
||||||
|
* This requirejs module also registers a global handler catching all links of the main container and rendering
|
||||||
|
* their content to the main container, in case you don't want to extend the container with additional handlers.
|
||||||
|
*
|
||||||
|
* @param {HTMLElement, jQuery, String} target A jQuery resultset, dom element or matcher string
|
||||||
|
*/
|
||||||
|
var Container = function(target) {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enumeration of possible container types
|
||||||
|
*
|
||||||
|
* @type {{GENERIC: string, MAIN: string, DETAIL: string}}
|
||||||
|
*/
|
||||||
|
var CONTAINER_TYPES = {
|
||||||
|
'GENERIC' : 'generic',
|
||||||
|
'MAIN' : 'icingamain',
|
||||||
|
'DETAIL': 'icingadetail'
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set to true when no history changes should be made
|
||||||
|
*
|
||||||
|
* @type {boolean} true to disable History.js calls, false to reenable them
|
||||||
|
*/
|
||||||
|
this.freezeHistory = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the container that is at the nearest location to this element, or the element itself if it is a container
|
||||||
|
*
|
||||||
|
* Containers are either the icingamain and icingadetail ids or components tagged as app/container
|
||||||
|
*
|
||||||
|
* @param {String, jQuery, HTMLElement} target The node to use as the starting point
|
||||||
|
*
|
||||||
|
* @returns {HTMLElement|null} The nearest container found or null if target is no container
|
||||||
|
* and no container is above target
|
||||||
|
*/
|
||||||
|
var findNearestContainer = function(target) {
|
||||||
|
target = $(target);
|
||||||
|
if (target.attr('data-icinga-component') === 'app/container' ||
|
||||||
|
target.attr('id') === 'icingamain' || target.attr('id') === 'icingadetail') {
|
||||||
|
return target;
|
||||||
|
}
|
||||||
|
return target.parents('[data-icinga-component="app/container"], #icingamain, #icingadetail')[0];
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find the container responsible for target and determine it's type
|
||||||
|
*
|
||||||
|
* @param {HTMLElement, jQuery, String} target A jQuery resultset, dom element or matcher string
|
||||||
|
*/
|
||||||
|
this.construct = function(target) {
|
||||||
|
this.containerDom = $(findNearestContainer(target));
|
||||||
|
this.containerType = CONTAINER_TYPES.GENERIC;
|
||||||
|
|
||||||
|
if (this.containerDom.attr('id') === CONTAINER_TYPES.MAIN) {
|
||||||
|
this.containerType = CONTAINER_TYPES.MAIN;
|
||||||
|
} else if (this.containerDom.attr('id') === CONTAINER_TYPES.DETAIL) {
|
||||||
|
this.containerType = CONTAINER_TYPES.DETAIL;
|
||||||
|
} else {
|
||||||
|
this.containerType = CONTAINER_TYPES.GENERIC;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var getWindowLocationWithoutHost = function() {
|
||||||
|
return window.location.pathname + window.location.search + window.location.hash;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract and return the main container's location from the current Url
|
||||||
|
*
|
||||||
|
* This takes the window's Url and removes the detail part
|
||||||
|
*
|
||||||
|
* @returns {string} The Url of the main container
|
||||||
|
*/
|
||||||
|
var getMainContainerHrefFromUrl = function() {
|
||||||
|
// main has the url without the icingadetail part
|
||||||
|
var href = URI(getWindowLocationWithoutHost());
|
||||||
|
href.removeQuery('detail');
|
||||||
|
return href.href();
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the detail container's location from the current Url
|
||||||
|
*
|
||||||
|
* This takes the detail parameter of the url and returns it or
|
||||||
|
* undefined if no location is given
|
||||||
|
*
|
||||||
|
* @returns {string|undefined} The Url of the detail container or undefined if no detail container is active
|
||||||
|
*/
|
||||||
|
var getDetailContainerHrefFromUrl = function() {
|
||||||
|
var location = new URI(getWindowLocationWithoutHost());
|
||||||
|
var href = URI.parseQuery(location.query()).detail;
|
||||||
|
if (!href) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// detail is a query param, so it is possible that (due to a bug or whatever) multiple
|
||||||
|
// detail fields are declared and returned as arrays
|
||||||
|
if (typeof href !== 'string') {
|
||||||
|
href = href[0];
|
||||||
|
}
|
||||||
|
// transform the detail parmameter to an Url
|
||||||
|
return URI(href).href();
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the Url of this container
|
||||||
|
*
|
||||||
|
* This is mostly determined by the Url of the window, but for generic containers we have to rely on the
|
||||||
|
* "data-icinga-href" attribute of the container (which is also available for main and detail, but less
|
||||||
|
* reliable during history changes)
|
||||||
|
*
|
||||||
|
* @returns {String|undefined} The Url of the container or undefined if the container has no Url set
|
||||||
|
*/
|
||||||
|
this.getContainerHref = function() {
|
||||||
|
switch (this.containerType) {
|
||||||
|
case CONTAINER_TYPES.MAIN:
|
||||||
|
return getMainContainerHrefFromUrl();
|
||||||
|
case CONTAINER_TYPES.DETAIL:
|
||||||
|
return getDetailContainerHrefFromUrl();
|
||||||
|
case CONTAINER_TYPES.GENERIC:
|
||||||
|
if (this.containerDom.attr('data-icinga-href')) {
|
||||||
|
return URI(this.containerDom.attr('data-icinga-href'));
|
||||||
|
} else {
|
||||||
|
return URI(getWindowLocationWithoutHost()).href();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a href with representing the current view, but url as the main container
|
||||||
|
*
|
||||||
|
* @param {URI} url The main Url to use as an URI.js object
|
||||||
|
*
|
||||||
|
* @returns {URI} The modified URI.js containing the new main and the current detail link
|
||||||
|
*/
|
||||||
|
var setMainContainerHref = function(url) {
|
||||||
|
var detail = getDetailContainerHrefFromUrl();
|
||||||
|
if (detail) {
|
||||||
|
url.addQuery('detail', detail);
|
||||||
|
}
|
||||||
|
return url;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a complete Href string representing the current detail href and the provided main Url
|
||||||
|
*
|
||||||
|
* @param {URI} url The detail Url to use as an URI.js object
|
||||||
|
*
|
||||||
|
* @returns {URI} The modified URI.js containing the new detail and the current main link
|
||||||
|
*/
|
||||||
|
var setDetailContainerHref = function(url) {
|
||||||
|
var location = new URI(window.location.href);
|
||||||
|
location.removeQuery('detail');
|
||||||
|
if (typeof url !== 'undefined') { // no detail Url given
|
||||||
|
location.addQuery('detail', url);
|
||||||
|
}
|
||||||
|
return location;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the Url of this container and let the Url reflect the new changes, if required
|
||||||
|
*
|
||||||
|
* This updates the window Url and the data-icinga-href attribute of the container. The latter one is required
|
||||||
|
* to see which url is the last one the container displayed (e.g. after History changes, the url has changed
|
||||||
|
* but the containers data-icinga-href still points to the containers element).
|
||||||
|
*
|
||||||
|
* @param {String|URI} url An Url string or a URI.js object representing the new Url for this container
|
||||||
|
*/
|
||||||
|
this.updateContainerHref = function(url) {
|
||||||
|
if (typeof url === "string") {
|
||||||
|
url = URI(url);
|
||||||
|
}
|
||||||
|
var containerUrl, windowUrl;
|
||||||
|
switch (this.containerType) {
|
||||||
|
case CONTAINER_TYPES.MAIN:
|
||||||
|
windowUrl = setMainContainerHref(url);
|
||||||
|
containerUrl = windowUrl.clone().removeQuery('detail');
|
||||||
|
break;
|
||||||
|
case CONTAINER_TYPES.DETAIL:
|
||||||
|
windowUrl = setDetailContainerHref(url);
|
||||||
|
containerUrl = url;
|
||||||
|
break;
|
||||||
|
case CONTAINER_TYPES.GENERIC:
|
||||||
|
containerUrl = url;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (containerUrl) {
|
||||||
|
this.containerDom.attr('data-icinga-href', containerUrl);
|
||||||
|
} else {
|
||||||
|
this.containerDom.removeAttr('data-icinga-href');
|
||||||
|
}
|
||||||
|
if (!this.freezeHistory) {
|
||||||
|
History.pushState({container: this.containerDom.attr('id')}, document.title, windowUrl.href());
|
||||||
|
}
|
||||||
|
return windowUrl;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Synchronize the container with the currently active window Url
|
||||||
|
*
|
||||||
|
* This is called mostly after changes in the history and makes sure the container contains the same content
|
||||||
|
* as the Url refers to. If the Url is the same as the Url in the container (the one in the data-icinga-href
|
||||||
|
* attribute), the container will be untouched, otherwise it's content and data-icinga-href attribute will be
|
||||||
|
* updated with the Url from the window.
|
||||||
|
*/
|
||||||
|
this.syncWithCurrentUrl = function() {
|
||||||
|
if (this.containerType === CONTAINER_TYPES.GENERIC) {
|
||||||
|
return; // generic containers would require this method to be specialised
|
||||||
|
}
|
||||||
|
// Catch initial page loading: Here no data-icinga-href is set and no url is given, so we're safe to ignore this
|
||||||
|
if (typeof this.containerDom.attr('data-icinga-href') === 'undefined' &&
|
||||||
|
typeof this.getContainerHref() === 'undefined') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// This is the case when an detail is removed on history back
|
||||||
|
if (typeof this.getContainerHref() === 'undefined' && typeof this.containerDom.attr('data-icinga-href') !== 'undefined') {
|
||||||
|
this.containerDom.removeAttr('data-icinga-href');
|
||||||
|
this.containerDom.empty();
|
||||||
|
this.hideDetail();
|
||||||
|
logger.debug("Hiding detail panel on Url change");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!URI(this.getContainerHref()).equals(this.containerDom.attr('data-icinga-href'))) {
|
||||||
|
logger.debug(
|
||||||
|
"Applying URL change for ", this.containerType,
|
||||||
|
"from", this.getContainerHref(),
|
||||||
|
"to", this.containerDom.attr('data-icinga-href')
|
||||||
|
);
|
||||||
|
|
||||||
|
if (typeof this.containerDom.attr('data-icinga-href') === 'undefined') { // container is empty now
|
||||||
|
//this.replaceDom('');
|
||||||
|
} else {
|
||||||
|
this.freezeHistory = true;
|
||||||
|
this.replaceDomFromUrl(this.getContainerHref());
|
||||||
|
this.freezeHistory = false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
logger.debug("No action performed on Url change, same Url for ", this.containerType);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load the provided url, stop all pending requests for this container and call replaceDom for the returned html
|
||||||
|
*
|
||||||
|
* This method relaods the page if a 401 (Authorization required) header is encountered
|
||||||
|
*
|
||||||
|
* @param {String, URI} url The Url to load or and URI.js object encapsulating it
|
||||||
|
*/
|
||||||
|
this.replaceDomFromUrl = function(url) {
|
||||||
|
if (!Modernizr.history) {
|
||||||
|
window.location.href = this.updateContainerHref(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.updateContainerHref(url);
|
||||||
|
var scope = this;
|
||||||
|
if (this.containerDom.pending) {
|
||||||
|
this.containerDom.pending.abort();
|
||||||
|
}
|
||||||
|
this.containerDom.pending = $.ajax({
|
||||||
|
url: url,
|
||||||
|
success: function(domNodes) {
|
||||||
|
scope.replaceDom(domNodes);
|
||||||
|
},
|
||||||
|
error: function(response) {
|
||||||
|
if (response.status === 401) {
|
||||||
|
window.location.reload();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove all dom nodes from this container and replace them with the ones from domNodes
|
||||||
|
*
|
||||||
|
* Triggers the custom "updated" event and causes a rescan for components on the DOM nodes
|
||||||
|
*
|
||||||
|
* If keepLayout is given, the detail panel won't be expanded if this is an update for the detail panel,
|
||||||
|
* otherwise it will be automatically shown.
|
||||||
|
*
|
||||||
|
* @param {String, jQuery, HTMLElement, Array} domNodes Any valid representation of the Dom nodes to insert
|
||||||
|
* @param {boolean} keepLayout Whether to keep the layout untouched, even if detail
|
||||||
|
* is updated end collapsed
|
||||||
|
*
|
||||||
|
* @see registerOnUpdate
|
||||||
|
*/
|
||||||
|
this.replaceDom = function(domNodes, keepLayout) {
|
||||||
|
var newDom = $('#icingamain', domNodes);
|
||||||
|
this.containerDom.empty().append(newDom.children());
|
||||||
|
this.containerDom.trigger('updated', [domNodes]);
|
||||||
|
componentLoader.load();
|
||||||
|
if (!keepLayout) {
|
||||||
|
if (this.containerType === CONTAINER_TYPES.DETAIL) {
|
||||||
|
this.showDetail();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register a method to be called when this container is updated
|
||||||
|
*
|
||||||
|
* @param {function} fn The function to call when the container is updated
|
||||||
|
*/
|
||||||
|
this.registerOnUpdate = function(fn) {
|
||||||
|
this.containerDom.on('updated', fn);
|
||||||
|
};
|
||||||
|
|
||||||
|
this.construct(target);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Static method for detecting whether the given link is external or only browserside (hash links)
|
||||||
|
*
|
||||||
|
* @param {String} link The link to test for being site-related
|
||||||
|
*
|
||||||
|
* @returns {boolean} True when the link should be executed with the browsers normal behaviour, false
|
||||||
|
* when the link should be catched and processed internally
|
||||||
|
*/
|
||||||
|
Container.isExternalLink = function(link) {
|
||||||
|
if (link[0] === '#') {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return (/^\/\//).test(URI(link).relativeTo(window.location.href).href());
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the page's detail container (which is always there)
|
||||||
|
*
|
||||||
|
* @returns {Container} The detail container of the page
|
||||||
|
*/
|
||||||
|
Container.getDetailContainer = function() {
|
||||||
|
detailContainer = detailContainer || new Container('#icingadetail');
|
||||||
|
if(!jQuery.contains(document.body, detailContainer)) {
|
||||||
|
detailContainer = new Container('#icingadetail');
|
||||||
|
}
|
||||||
|
return detailContainer;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the page's main container (which is always there)
|
||||||
|
*
|
||||||
|
* @returns {Container} The main container of the page
|
||||||
|
*/
|
||||||
|
Container.getMainContainer = function() {
|
||||||
|
mainContainer = mainContainer || new Container('#icingamain');
|
||||||
|
if(!jQuery.contains(document.body, mainContainer)) {
|
||||||
|
mainContainer = new Container('#icingamain');
|
||||||
|
}
|
||||||
|
return mainContainer;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Expand the detail container and shrinken the main container
|
||||||
|
*
|
||||||
|
* Available as a static method on the Container object or as an instance method
|
||||||
|
*/
|
||||||
|
Container.prototype.showDetail = Container.showDetail = function() {
|
||||||
|
var mainDom = Container.getMainContainer().containerDom,
|
||||||
|
detailDom = Container.getDetailContainer().containerDom;
|
||||||
|
|
||||||
|
mainDom.removeClass();
|
||||||
|
detailDom.removeClass();
|
||||||
|
mainDom.addClass('hidden-md');
|
||||||
|
detailDom.addClass('col-md-12');
|
||||||
|
mainDom.addClass('col-lg-7');
|
||||||
|
detailDom.addClass('col-lg-5');
|
||||||
|
mainDom.addClass('hidden-xs');
|
||||||
|
detailDom.addClass('col-xs-12');
|
||||||
|
mainDom.addClass('hidden-sm');
|
||||||
|
detailDom.addClass('col-sm-12');
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hide the detail container and expand the main container
|
||||||
|
*
|
||||||
|
* Also updates the Url by removing the detail part
|
||||||
|
*
|
||||||
|
* Available as a static method on the Container object or as an instance method
|
||||||
|
*/
|
||||||
|
Container.prototype.hideDetail = Container.hideDetail = function() {
|
||||||
|
var mainDom = Container.getMainContainer().containerDom,
|
||||||
|
detailDom = Container.getDetailContainer().containerDom;
|
||||||
|
|
||||||
|
mainDom.removeClass();
|
||||||
|
detailDom.removeClass();
|
||||||
|
mainDom.addClass('col-md-12');
|
||||||
|
detailDom.addClass('hidden-md');
|
||||||
|
mainDom.addClass('col-lg-12');
|
||||||
|
detailDom.addClass('hidden-lg');
|
||||||
|
mainDom.addClass('col-xs-12');
|
||||||
|
detailDom.addClass('hidden-xs');
|
||||||
|
mainDom.addClass('col-sm-12');
|
||||||
|
detailDom.addClass('hidden-sm');
|
||||||
|
detailDom.removeAttr('data-icinga-href');
|
||||||
|
if (typeof this.freezeHistory === 'undefined' || !this.freezeHistory) {
|
||||||
|
History.replaceState(
|
||||||
|
{},
|
||||||
|
document.title,
|
||||||
|
URI(window.location.href).removeQuery('detail').href()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if (Modernizr.history) {
|
||||||
|
/**
|
||||||
|
* Register the click behaviour of the main container, which means that every link, if not catched in a
|
||||||
|
* more specific handler, causes an update of the main container if it's not external or a browser behaviour link
|
||||||
|
* (those starting with '#').
|
||||||
|
*/
|
||||||
|
$('body').on('click', '#icingamain', function(ev) {
|
||||||
|
var targetEl = ev.target || ev.toElement || ev.relatedTarget;
|
||||||
|
if (targetEl.tagName.toLowerCase() !== 'a') {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Container.isExternalLink($(targetEl).attr('href'))) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
Container.getMainContainer().replaceDomFromUrl(URI($(targetEl).attr('href')).href());
|
||||||
|
ev.preventDefault();
|
||||||
|
ev.stopPropagation();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return Container;
|
||||||
|
});
|
@ -43,17 +43,7 @@ define(['jquery'], function($) {
|
|||||||
*/
|
*/
|
||||||
var ATTR_MODIFIED = 'data-icinga-form-modified';
|
var ATTR_MODIFIED = 'data-icinga-form-modified';
|
||||||
|
|
||||||
/**
|
|
||||||
* Return true when the input element is a autosubmit field
|
|
||||||
*
|
|
||||||
* @param {string|DOMElement|jQuery} el The element to test for autosubmission
|
|
||||||
*
|
|
||||||
* @returns {boolean} True when the element should be automatically submitted
|
|
||||||
*/
|
|
||||||
var isAutoSubmitInput = function(el) {
|
|
||||||
return $(el).attr('data-icinga-form-autosubmit') === 'true' ||
|
|
||||||
$(el).attr('data-icinga-form-autosubmit') === '1';
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Takes a form and returns an overloaded jQuery object
|
* Takes a form and returns an overloaded jQuery object
|
||||||
@ -105,16 +95,16 @@ define(['jquery'], function($) {
|
|||||||
*/
|
*/
|
||||||
var registerFormEventHandler = function(form) {
|
var registerFormEventHandler = function(form) {
|
||||||
form.change(function(changed) {
|
form.change(function(changed) {
|
||||||
if (isAutoSubmitInput(changed.target)) {
|
if ($(changed.target).attr('data-icinga-form-autosubmit')) {
|
||||||
form.clearModificationFlag();
|
form.clearModificationFlag();
|
||||||
form.submit();
|
|
||||||
} else {
|
} else {
|
||||||
form.setModificationFlag();
|
form.setModificationFlag();
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
// submissions should clear the modification flag
|
// submissions should clear the modification flag
|
||||||
form.submit(form.clearModificationFlag);
|
form.submit(function() {
|
||||||
|
form.clearModificationFlag()
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
199
public/js/icinga/components/mainDetailGrid.js
Normal file
199
public/js/icinga/components/mainDetailGrid.js
Normal file
@ -0,0 +1,199 @@
|
|||||||
|
/*global Icinga:false, document: false, define:false require:false base_url:false console:false */
|
||||||
|
// {{{ICINGA_LICENSE_HEADER}}}
|
||||||
|
/**
|
||||||
|
* This file is part of Icinga 2 Web.
|
||||||
|
*
|
||||||
|
* Icinga 2 Web - Head for multiple monitoring backends.
|
||||||
|
* Copyright (C) 2013 Icinga Development Team
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or
|
||||||
|
* modify it under the terms of the GNU General Public License
|
||||||
|
* as published by the Free Software Foundation; either version 2
|
||||||
|
* of the License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; if not, write to the Free Software
|
||||||
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||||
|
*
|
||||||
|
* @copyright 2013 Icinga Development Team <info@icinga.org>
|
||||||
|
* @license http://www.gnu.org/licenses/gpl-2.0.txt GPL, version 2
|
||||||
|
* @author Icinga Development Team <info@icinga.org>
|
||||||
|
*/
|
||||||
|
// {{{ICINGA_LICENSE_HEADER}}}
|
||||||
|
define(['components/app/container', 'jquery', 'logging', 'icinga/util/async', 'URIjs/URI', 'URIjs/URITemplate'],
|
||||||
|
function(Container, $, logger, async, URI) {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Master/Detail grid component handling history, link behaviour, selection (@TODO 3788) and updates of
|
||||||
|
* grids
|
||||||
|
*
|
||||||
|
* @param {HTMLElement} The outer element to apply the behaviour on
|
||||||
|
*/
|
||||||
|
return function(gridDomNode) {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reference to the outer container of this component
|
||||||
|
*
|
||||||
|
* @type {*|HTMLElement}
|
||||||
|
*/
|
||||||
|
gridDomNode = $(gridDomNode);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A container component to use for updating URLs and content
|
||||||
|
*
|
||||||
|
* @type {Container}
|
||||||
|
*/
|
||||||
|
this.container = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The node wrapping the table and pagination
|
||||||
|
*
|
||||||
|
* @type {jQuery}
|
||||||
|
*/
|
||||||
|
var contentNode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* jQuery matcher result of the form components wrapping the controls
|
||||||
|
*
|
||||||
|
* @type {jQuery}
|
||||||
|
*/
|
||||||
|
var controlForms;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Detect and select control forms for this table and return them
|
||||||
|
*
|
||||||
|
* Form controls are either all forms underneath the of the component, but not underneath the table
|
||||||
|
* or in a dom node explicitly tagged with the 'data-icinga-actiongrid-controls' attribute
|
||||||
|
*
|
||||||
|
* @param {jQuery|null} domContext The context to use as the root node for matching, if null
|
||||||
|
* the component node given in the constructor is used
|
||||||
|
*
|
||||||
|
* @returns {jQuery} A selector result with all forms modifying this grid
|
||||||
|
*/
|
||||||
|
var determineControlForms = function(domContext) {
|
||||||
|
domContext = domContext || gridDomNode;
|
||||||
|
var controls = $('[data-icinga-grid-controls]', domContext);
|
||||||
|
if (controls.length > 0) {
|
||||||
|
return $('form', controls);
|
||||||
|
} else {
|
||||||
|
return $('form', domContext).filter(function () {
|
||||||
|
return $(this).parentsUntil(domContext, 'table').length === 0;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Detect and select the dom of all tables displaying content for this mainDetailGrid component
|
||||||
|
*
|
||||||
|
* The table can either explicitly tagged with the 'data-icinga-grid-content' attribute, if not every table
|
||||||
|
* underneath the components root dom will be used
|
||||||
|
*
|
||||||
|
* @param {jQuery|null} domContext The context to use as the root node for matching, if null
|
||||||
|
* the component node given in the constructor is used
|
||||||
|
*
|
||||||
|
* @returns {jQuery} A selector result with all tables displaying information in the
|
||||||
|
* grid
|
||||||
|
*/
|
||||||
|
var determineContentTable = function(domContext) {
|
||||||
|
domContext = domContext || gridDomNode;
|
||||||
|
var maindetail = $('[data-icinga-grid-content]', domContext);
|
||||||
|
if (maindetail.length > 0) {
|
||||||
|
return maindetail;
|
||||||
|
} else {
|
||||||
|
return $('table', domContext);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register the row links of tables using the first link found in the table (no matter if visible or not)
|
||||||
|
*
|
||||||
|
* Row level links can only be realized via JavaScript, so every row should provide additional links for
|
||||||
|
* Users that don't have javascript enabled
|
||||||
|
*
|
||||||
|
* @param {jQuery|null} domContext The rootnode to use for selecting rows or null to use contentNode
|
||||||
|
*/
|
||||||
|
this.registerTableLinks = function(domContext) {
|
||||||
|
domContext = domContext || contentNode;
|
||||||
|
this.container.disableClickHandler();
|
||||||
|
|
||||||
|
$('tbody tr', domContext).on('click', function(ev) {
|
||||||
|
var targetEl = ev.target || ev.toElement || ev.relatedTarget;
|
||||||
|
|
||||||
|
if (targetEl.nodeName.toLowerCase() === "a") {
|
||||||
|
// test if the URL is on the current server, if not open it directly
|
||||||
|
if(Container.isExternalLink($(targetEl).attr('href'))) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Container.getDetailContainer().replaceDomFromUrl($('a', this).attr('href'));
|
||||||
|
if (!ev.ctrlKey && !ev.metaKey) {
|
||||||
|
$('tr', $(this).parent()).removeClass('active');
|
||||||
|
}
|
||||||
|
|
||||||
|
$(this).addClass('active');
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register submit handler for the form controls (sorting, filtering, etc). Reloading happens in the
|
||||||
|
* current container
|
||||||
|
*/
|
||||||
|
this.registerControls = function() {
|
||||||
|
controlForms.on('submit', (function(evt) {
|
||||||
|
// append the form's parameters to the current container href
|
||||||
|
var form = $(evt.currentTarget);
|
||||||
|
var url = URI(this.container.getContainerHref());
|
||||||
|
url.search(URI.parseQuery(form.serialize()));
|
||||||
|
// reload this container
|
||||||
|
this.container.replaceDomFromUrl(url);
|
||||||
|
|
||||||
|
evt.preventDefault();
|
||||||
|
evt.stopPropagation();
|
||||||
|
return false;
|
||||||
|
}).bind(this));
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Synchronize the current selection with the url displayed in the detail box
|
||||||
|
*/
|
||||||
|
this.syncSelectionWithDetail = function() {
|
||||||
|
$('tr', contentNode).removeClass('active');
|
||||||
|
var selection = $('a[href="' + Container.getDetailContainer().getContainerHref() + '"]', contentNode).
|
||||||
|
parentsUntil('table', 'tr');
|
||||||
|
selection.addClass('active');
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register listener for history changes in the detail box
|
||||||
|
*/
|
||||||
|
this.registerHistoryChanges = function() {
|
||||||
|
Container.getDetailContainer().registerOnUpdate(this.syncSelectionWithDetail.bind(this));
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create this component, setup listeners and behaviour
|
||||||
|
*/
|
||||||
|
this.construct = function(target) {
|
||||||
|
this.container = new Container(target);
|
||||||
|
logger.debug("Registering table events for ", this.container.containerType);
|
||||||
|
controlForms = determineControlForms();
|
||||||
|
contentNode = determineContentTable();
|
||||||
|
|
||||||
|
this.registerControls();
|
||||||
|
this.registerTableLinks();
|
||||||
|
this.registerHistoryChanges();
|
||||||
|
};
|
||||||
|
|
||||||
|
this.construct(gridDomNode);
|
||||||
|
};
|
||||||
|
|
||||||
|
});
|
@ -30,8 +30,10 @@ define([
|
|||||||
'jquery',
|
'jquery',
|
||||||
'logging',
|
'logging',
|
||||||
'icinga/util/async',
|
'icinga/util/async',
|
||||||
'icinga/componentLoader'
|
'icinga/componentLoader',
|
||||||
], function ($, log, async,components) {
|
'components/app/container',
|
||||||
|
'URIjs/URI'
|
||||||
|
], function ($, log, async, components, Container, URI) {
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -39,15 +41,79 @@ define([
|
|||||||
*/
|
*/
|
||||||
var Icinga = function() {
|
var Icinga = function() {
|
||||||
|
|
||||||
|
var ignoreHistoryChanges = false;
|
||||||
|
|
||||||
var initialize = function () {
|
var initialize = function () {
|
||||||
components.load();
|
components.load();
|
||||||
|
registerGenericHistoryHandler();
|
||||||
log.debug("Initialization finished");
|
log.debug("Initialization finished");
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
$(document).ready(initialize.bind(this));
|
/**
|
||||||
|
* Register handler for handling the history state generically
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
var registerGenericHistoryHandler = function() {
|
||||||
|
var lastUrl = URI(window.location.href);
|
||||||
|
History.Adapter.bind(window, 'popstate', function() {
|
||||||
|
if (ignoreHistoryChanges) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
log.debug(URI(History.getState().url).relativeTo(lastUrl).href());
|
||||||
|
var relativeURLPart = URI(History.getState().url).relativeTo(lastUrl).href();
|
||||||
|
if (relativeURLPart !== "" && relativeURLPart[0] === '?' ) {
|
||||||
|
// same controller, different parameters, so only update the container
|
||||||
|
Container.getMainContainer().syncWithCurrentUrl();
|
||||||
|
Container.getDetailContainer().syncWithCurrentUrl();
|
||||||
|
} else {
|
||||||
|
gotoUrl(History.getState().url);
|
||||||
|
}
|
||||||
|
lastUrl = URI(window.location.href);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
var gotoUrl = function(href) {
|
||||||
|
if (typeof document.body.pending !== 'undefined') {
|
||||||
|
document.body.pending.abort();
|
||||||
|
}
|
||||||
|
$.ajax({
|
||||||
|
success: function(domNodes) {
|
||||||
|
ignoreHistoryChanges = true;
|
||||||
|
History.pushState({}, document.title, href);
|
||||||
|
$('body').empty().append($(domNodes));
|
||||||
|
ignoreHistoryChanges = false;
|
||||||
|
components.load();
|
||||||
|
},
|
||||||
|
url: href
|
||||||
|
});
|
||||||
|
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (Modernizr.history) {
|
||||||
|
$(document.body).on('click', '#icinganavigation, #icingatopbar', function(ev) {
|
||||||
|
var targetEl = ev.target || ev.toElement || ev.relatedTarget;
|
||||||
|
if (targetEl.tagName.toLowerCase() !== 'a') {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
var href = $(targetEl).attr('href');
|
||||||
|
if (Container.isExternalLink(href)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
ev.preventDefault();
|
||||||
|
ev.stopPropagation();
|
||||||
|
gotoUrl(href);
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
$(document).ready(initialize.bind(this));
|
||||||
return {
|
return {
|
||||||
components: components
|
|
||||||
|
components: components,
|
||||||
|
gotoUrl: gotoUrl
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
return new Icinga();
|
return new Icinga();
|
||||||
|
@ -1,199 +0,0 @@
|
|||||||
// {{{ICINGA_LICENSE_HEADER}}}
|
|
||||||
/**
|
|
||||||
* This file is part of Icinga 2 Web.
|
|
||||||
*
|
|
||||||
* Icinga 2 Web - Head for multiple monitoring backends.
|
|
||||||
* Copyright (C) 2013 Icinga Development Team
|
|
||||||
*
|
|
||||||
* This program is free software; you can redistribute it and/or
|
|
||||||
* modify it under the terms of the GNU General Public License
|
|
||||||
* as published by the Free Software Foundation; either version 2
|
|
||||||
* of the License, or (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program; if not, write to the Free Software
|
|
||||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
||||||
*
|
|
||||||
* @copyright 2013 Icinga Development Team <info@icinga.org>
|
|
||||||
* @license http://www.gnu.org/licenses/gpl-2.0.txt GPL, version 2
|
|
||||||
* @author Icinga Development Team <info@icinga.org>
|
|
||||||
*/
|
|
||||||
// {{{ICINGA_LICENSE_HEADER}}}
|
|
||||||
/*global Icinga:false define:false require:false base_url:false console:false */
|
|
||||||
|
|
||||||
(function() {
|
|
||||||
"use strict";
|
|
||||||
var asyncMgrInstance = null;
|
|
||||||
|
|
||||||
define(['logging','jquery'],function(log,$,containerMgr) {
|
|
||||||
|
|
||||||
var headerListeners = {};
|
|
||||||
|
|
||||||
var pending = {
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
var encodeForURL = function(param) {
|
|
||||||
return encodeURIComponent(param);
|
|
||||||
};
|
|
||||||
|
|
||||||
var getCurrentGETParameters = function() {
|
|
||||||
var currentGET = window.location.search.substring(1).split("&");
|
|
||||||
var params = {};
|
|
||||||
if(currentGET.length > 0) {
|
|
||||||
$.each(currentGET, function(idx, elem) {
|
|
||||||
var keyVal = elem.split("=");
|
|
||||||
params[keyVal[0]] = encodeForURL(keyVal[1]);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return params;
|
|
||||||
}
|
|
||||||
;
|
|
||||||
var pushGet = function(param, value, url) {
|
|
||||||
url = url || (window.location.origin+window.location.pathname);
|
|
||||||
var params = getCurrentGETParameters();
|
|
||||||
params[param] = encodeForURL(value);
|
|
||||||
var search = "?";
|
|
||||||
for (var name in params) {
|
|
||||||
if (name === "" || typeof params[name] == "undefined") {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (search != "?")
|
|
||||||
search += "&";
|
|
||||||
search += name+"="+params[name];
|
|
||||||
}
|
|
||||||
|
|
||||||
return url+search+"#"+window.location.hash;
|
|
||||||
};
|
|
||||||
|
|
||||||
var getDOMForDestination = function(destination) {
|
|
||||||
var target = destination;
|
|
||||||
if (typeof destination === "string") {
|
|
||||||
target = containerMgr.getContainer(destination)[0];
|
|
||||||
} else if(typeof destination.context !== "undefined") {
|
|
||||||
target = destination[0];
|
|
||||||
}
|
|
||||||
return target;
|
|
||||||
};
|
|
||||||
|
|
||||||
var applyHeaderListeners = function(headers) {
|
|
||||||
for (var header in headerListeners) {
|
|
||||||
if (headers.getResponseHeader(header) === null) {
|
|
||||||
// see if the browser/server converts headers to lowercase
|
|
||||||
if (headers.getResponseHeader(header.toLowerCase()) === null) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
header = header.toLowerCase();
|
|
||||||
}
|
|
||||||
var value = headers.getResponseHeader(header);
|
|
||||||
var listeners = headerListeners[header];
|
|
||||||
for (var i=0;i<listeners.length;i++) {
|
|
||||||
listeners[i].fn.apply(listeners[i].scope, [value, header, headers]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
var handleResponse = function(html, status, response) {
|
|
||||||
applyHeaderListeners(response);
|
|
||||||
if(this.destination) {
|
|
||||||
containerMgr.updateContainer(this.destination,html,this);
|
|
||||||
} else {
|
|
||||||
// tbd
|
|
||||||
// containerMgr.createPopupContainer(html,this);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
var handleFailure = function(result,error) {
|
|
||||||
if(error === "abort") {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
log.error("Error loading resource",error,arguments);
|
|
||||||
if(this.destination) {
|
|
||||||
containerMgr.updateContainer(this.destination,result.responseText,this);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
var isParent = function(dom,parentToCheck) {
|
|
||||||
while(dom.parentNode) {
|
|
||||||
dom = dom.parentNode;
|
|
||||||
if(dom === parentToCheck) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
|
|
||||||
var CallInterface = function() {
|
|
||||||
|
|
||||||
this.__internalXHRImplementation = $.ajax;
|
|
||||||
|
|
||||||
this.clearPendingRequestsFor = function(destination) {
|
|
||||||
if(!$.isArray(pending)) {
|
|
||||||
pending = [];
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
var resultset = [];
|
|
||||||
for(var x=0;x<pending.length;x++) {
|
|
||||||
var container = pending[x].DOM;
|
|
||||||
if(isParent(container,getDOMForDestination(destination))) {
|
|
||||||
pending[x].request.abort();
|
|
||||||
} else {
|
|
||||||
resultset.push(pending[x]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pending = resultset;
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
this.createRequest = function(url,data) {
|
|
||||||
var req = this.__internalXHRImplementation({
|
|
||||||
type : data ? 'POST' : 'GET',
|
|
||||||
url : url,
|
|
||||||
data : data,
|
|
||||||
headers: { 'X-Icinga-Accept': 'text/html' }
|
|
||||||
});
|
|
||||||
req.url = url;
|
|
||||||
req.done(handleResponse.bind(req));
|
|
||||||
req.fail(handleFailure.bind(req));
|
|
||||||
return req;
|
|
||||||
};
|
|
||||||
|
|
||||||
this.loadToTarget = function(destination,url,data) {
|
|
||||||
if(destination) {
|
|
||||||
log.debug("Laoding to container", destination, url);
|
|
||||||
this.clearPendingRequestsFor(destination);
|
|
||||||
}
|
|
||||||
var req = this.createRequest(url,data);
|
|
||||||
if(destination) {
|
|
||||||
pending.push({
|
|
||||||
request: req,
|
|
||||||
DOM: getDOMForDestination(destination)
|
|
||||||
});
|
|
||||||
req.destination = destination;
|
|
||||||
}
|
|
||||||
if (destination == "icinga-main") {
|
|
||||||
history.pushState(data, document.title, url);
|
|
||||||
} else {
|
|
||||||
url = pushGet("c["+destination+"]", url);
|
|
||||||
history.pushState(data, document.title, url);
|
|
||||||
}
|
|
||||||
return req;
|
|
||||||
};
|
|
||||||
|
|
||||||
this.loadCSS = function(name) {
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
this.registerHeaderListener = function(header, fn, scope) {
|
|
||||||
headerListeners[header] = headerListeners[header] || [];
|
|
||||||
headerListeners[header].push({fn: fn, scope:scope});
|
|
||||||
};
|
|
||||||
};
|
|
||||||
return new CallInterface();
|
|
||||||
});
|
|
||||||
})();
|
|
@ -1,10 +1,13 @@
|
|||||||
requirejs.config({
|
requirejs.config({
|
||||||
'baseUrl': window.base_url + '/js',
|
'baseUrl': window.base_url + '/js',
|
||||||
|
'urlArgs': "bust=" + (new Date()).getTime(),
|
||||||
'paths': {
|
'paths': {
|
||||||
'jquery': 'vendor/jquery-1.8.3',
|
'jquery': 'vendor/jquery-1.8.3',
|
||||||
|
'jqueryPlugins': 'vendor/jqueryPlugins/',
|
||||||
'bootstrap': 'vendor/bootstrap/bootstrap.min',
|
'bootstrap': 'vendor/bootstrap/bootstrap.min',
|
||||||
'history': 'vendor/history',
|
'history': 'vendor/history',
|
||||||
'logging': 'icinga/util/logging',
|
'logging': 'icinga/util/logging',
|
||||||
|
'URIjs': 'vendor/uri',
|
||||||
'datetimepicker': 'vendor/bootstrap/datetimepicker.min'
|
'datetimepicker': 'vendor/bootstrap/datetimepicker.min'
|
||||||
},
|
},
|
||||||
'shim': {
|
'shim': {
|
||||||
@ -18,7 +21,8 @@ requirejs.config({
|
|||||||
});
|
});
|
||||||
|
|
||||||
define(['jquery', 'history'], function ($) {
|
define(['jquery', 'history'], function ($) {
|
||||||
requirejs(['bootstrap'], function() {
|
|
||||||
|
requirejs(['bootstrap', 'jqueryPlugins/wookmark'], function() {
|
||||||
requirejs(['datetimepicker']);
|
requirejs(['datetimepicker']);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -27,4 +31,5 @@ define(['jquery', 'history'], function ($) {
|
|||||||
window.jQuery = $;
|
window.jQuery = $;
|
||||||
window.Icinga = Icinga;
|
window.Icinga = Icinga;
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
2
public/js/vendor/history.js
vendored
2
public/js/vendor/history.js
vendored
File diff suppressed because one or more lines are too long
@ -1,34 +0,0 @@
|
|||||||
|
|
||||||
// {{LICENSE_HEADER}}
|
|
||||||
// {{LICENSE_HEADER}}
|
|
||||||
var should = require("should");
|
|
||||||
var rjsmock = require("requiremock.js");
|
|
||||||
var asyncMock = require("asyncmock.js");
|
|
||||||
|
|
||||||
GLOBAL.document = $('body');
|
|
||||||
|
|
||||||
|
|
||||||
describe('The async module', function() {
|
|
||||||
it("Allows to react on specific headers", function(done) {
|
|
||||||
rjsmock.purgeDependencies();
|
|
||||||
rjsmock.registerDependencies({
|
|
||||||
'icinga/container' : {
|
|
||||||
updateContainer : function() {},
|
|
||||||
createPopupContainer: function() {}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
requireNew("icinga/util/async.js");
|
|
||||||
var async = rjsmock.getDefine();
|
|
||||||
var headerValue = null;
|
|
||||||
asyncMock.setNextAsyncResult(async, "result", false, {
|
|
||||||
'X-Dont-Care' : 'Ignore-me',
|
|
||||||
'X-Test-Header' : 'Testme123'
|
|
||||||
});
|
|
||||||
async.registerHeaderListener("X-Test-Header", function(value, header) {
|
|
||||||
should.equal("Testme123", value);
|
|
||||||
done();
|
|
||||||
},this);
|
|
||||||
var test = async.createRequest();
|
|
||||||
});
|
|
||||||
});
|
|
@ -47,7 +47,7 @@ var logger = {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Mock for the 'define' function of requireJS, behaves exactly the same
|
* Mock for the 'define' function of requireJS, behaves exactly the same
|
||||||
* except that it looks up the dependencies in the list provided by registerDepencies()
|
* except that it looks up the dependencies in the list provided by registerDependencies()
|
||||||
* A module that hasn't been defined with a name can be fetched with getDefined() (without parameter)
|
* A module that hasn't been defined with a name can be fetched with getDefined() (without parameter)
|
||||||
*
|
*
|
||||||
**/
|
**/
|
||||||
@ -88,6 +88,7 @@ var defineMock = function() {
|
|||||||
**/
|
**/
|
||||||
function initRequireMethods() {
|
function initRequireMethods() {
|
||||||
GLOBAL.$ = require('jquery');
|
GLOBAL.$ = require('jquery');
|
||||||
|
GLOBAL.jQuery = GLOBAL.$;
|
||||||
GLOBAL.requirejs = requireJsMock;
|
GLOBAL.requirejs = requireJsMock;
|
||||||
GLOBAL.define = defineMock;
|
GLOBAL.define = defineMock;
|
||||||
registeredDependencies = {
|
registeredDependencies = {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user