338 lines
12 KiB
JavaScript
338 lines
12 KiB
JavaScript
/*global Icinga:false, document: false, define:false require:false base_url:false console:false */
|
|
// {{{ICINGA_LICENSE_HEADER}}}
|
|
/**
|
|
* This file is part of Icinga Web 2.
|
|
*
|
|
* Icinga Web 2 - 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', 'URIjs/URI', 'URIjs/URITemplate', 'icinga/util/url', 'icinga/selection/selectable', 'icinga/selection/multiSelection'],
|
|
function(Container, $, logger, URI, tpl, urlMgr, Selectable, TableMultiSelection) {
|
|
"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;
|
|
|
|
/**
|
|
* Handles multi-selection
|
|
*
|
|
* @type {TableMultiSelection}
|
|
*/
|
|
var selection;
|
|
|
|
/**
|
|
* Defines how row clicks are handled. Can either be 'none', 'single' or 'multi'
|
|
*
|
|
* @type {string}
|
|
*/
|
|
var selectionMode;
|
|
|
|
/**
|
|
* 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);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Show a 'hand' to indicate that the row is selectable,
|
|
* when hovering.
|
|
*/
|
|
this.showMousePointerOnRow = function(domContext) {
|
|
domContext = domContext || contentNode;
|
|
$('tbody tr', domContext).css('cursor' ,'pointer');
|
|
};
|
|
|
|
/**
|
|
* Activate a hover effect on all table rows, to indicate that
|
|
* this table row is clickable.
|
|
*
|
|
* @param domContext
|
|
*/
|
|
this.activateRowHovering = function(domContext) {
|
|
domContext = domContext || contentNode;
|
|
//$(domContext).addClass('table-hover');
|
|
$('tbody tr', domContext).hover(
|
|
function(e) {
|
|
$(this).addClass('hover');
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
},
|
|
function(e) {
|
|
$(this).removeClass('hover');
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
}
|
|
);
|
|
};
|
|
|
|
/**
|
|
* 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;
|
|
$('tbody tr button[type=submit], tbody tr submit', domContext).click(function(ev) {
|
|
ev.stopPropagation();
|
|
return true;
|
|
});
|
|
$('tbody tr', domContext).click(function(ev) {
|
|
var targetEl = ev.target || ev.toElement || ev.relatedTarget,
|
|
a = $(targetEl).closest('a');
|
|
ev.preventDefault();
|
|
ev.stopPropagation();
|
|
|
|
var nodeNames = [];
|
|
nodeNames.push($(targetEl).prop('nodeName').toLowerCase());
|
|
nodeNames.push($(targetEl).parent().prop('nodeName').toLowerCase());
|
|
if (a.length) {
|
|
// test if the URL is on the current server, if not open it directly
|
|
if (Container.isExternalLink(a.attr('href'))) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
var selected = new Selectable(this);
|
|
switch (selectionMode) {
|
|
case 'multi':
|
|
if (ev.ctrlKey || ev.metaKey) {
|
|
selection.toggle(selected);
|
|
} else if (ev.shiftKey) {
|
|
selection.add(selected);
|
|
} else {
|
|
var oldState = selected.isActive();
|
|
selection.clear();
|
|
if (!oldState) {
|
|
selection.add(selected);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case 'single':
|
|
oldState = selected.isActive();
|
|
selection.clear();
|
|
if (!oldState) {
|
|
selection.add(selected);
|
|
}
|
|
break;
|
|
|
|
default:
|
|
// don't open the link
|
|
return;
|
|
}
|
|
var url = URI($('a', this).attr('href'));
|
|
if (targetEl.tagName.toLowerCase() === 'a') {
|
|
url = URI($(targetEl).attr('href'));
|
|
}
|
|
var segments = url.segment();
|
|
if (selection.size() === 0) {
|
|
// don't open anything
|
|
urlMgr.setDetailUrl('');
|
|
return false;
|
|
} else if (selection.size() > 1 && segments.length > 3) {
|
|
// open detail view for multiple objects
|
|
segments[2] = 'multi';
|
|
url.pathname('/' + segments.join('/'));
|
|
url.search('?');
|
|
url.setSearch(selection.toQuery());
|
|
}
|
|
urlMgr.setDetailUrl(url);
|
|
return false;
|
|
});
|
|
|
|
/*
|
|
* Clear the URL when deselected or when a wildcard is used
|
|
*/
|
|
$(window).on('hashchange', function(){
|
|
if (
|
|
!location.hash ||
|
|
location.hash.match(/(host=%2A)|(service=%2A)/)
|
|
) {
|
|
selection.clear();
|
|
}
|
|
});
|
|
};
|
|
|
|
/**
|
|
* Register submit handler for the form controls (sorting, filtering, etc). Reloading happens in the
|
|
* current container
|
|
*/
|
|
this.registerControls = function() {
|
|
controlForms.on('submit', function(evt) {
|
|
var container = (new Container(this));
|
|
var form = $(this);
|
|
var url = container.getUrl();
|
|
|
|
if (url.indexOf('?') >= 0) {
|
|
url += '&';
|
|
} else {
|
|
url += '?';
|
|
}
|
|
url += form.serialize();
|
|
container.setUrl(url);
|
|
|
|
evt.preventDefault();
|
|
evt.stopPropagation();
|
|
return false;
|
|
|
|
});
|
|
$('.pagination li a, a.filter-badge', contentNode.parent()).on('click', function(ev) {
|
|
|
|
var container = (new Container(this));
|
|
|
|
// Detail will be removed when main pagination changes
|
|
if (container.containerType === 'icingamain') {
|
|
urlMgr.setMainUrl(URI($(this).attr('href')));
|
|
urlMgr.setDetailUrl('');
|
|
} else {
|
|
urlMgr.setDetailUrl(URI($(this).attr('href')));
|
|
}
|
|
|
|
ev.preventDefault();
|
|
ev.stopPropagation();
|
|
return false;
|
|
});
|
|
};
|
|
|
|
/**
|
|
* Create a new TableMultiSelection, attach it to the content node, and use the
|
|
* current detail url to restore the selection state
|
|
*/
|
|
this.initSelection = function() {
|
|
var detail = urlMgr.getDetailUrl();
|
|
if (typeof detail !== 'string') {
|
|
detail = detail[0] || '';
|
|
}
|
|
selection = new TableMultiSelection(contentNode,new URI(detail));
|
|
};
|
|
|
|
/**
|
|
* Init all objects responsible for selection handling
|
|
*
|
|
* - Indicate selection by showing active and hovered rows
|
|
* - Handle click-events according to the selection mode
|
|
* - Create and follow links according to the row content
|
|
*/
|
|
this.initRowSelection = function() {
|
|
selectionMode = gridDomNode.data('icinga-grid-selection-type');
|
|
if (selectionMode === 'multi' || selectionMode === 'single') {
|
|
// indicate selectable rows
|
|
this.showMousePointerOnRow();
|
|
this.activateRowHovering();
|
|
this.initSelection();
|
|
}
|
|
this.registerTableLinks();
|
|
};
|
|
|
|
/**
|
|
* Create this component, setup listeners and behaviour
|
|
*/
|
|
this.construct = function(target) {
|
|
this.container = new Container(target);
|
|
this.container.removeDefaultLoadIndicator();
|
|
controlForms = determineControlForms();
|
|
contentNode = determineContentTable();
|
|
this.initRowSelection();
|
|
this.registerControls();
|
|
};
|
|
if (typeof $(gridDomNode).attr('id') === 'undefined') {
|
|
this.construct(gridDomNode);
|
|
}
|
|
};
|
|
});
|